Compare commits

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Two supporting fixes:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Implementation details:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

This adds support for:

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

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

Expose termination notifications via a new process_terminated signal.

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

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

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

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

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

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

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

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

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

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

Supports both __kernel_timespec and old_timespec32 layouts.
2026-02-16 21:27:26 +01:00
Ole André Vadla Ravnås c0815c4d1a syscall-tracer: Fix a stylistic inconsistency 2026-02-16 21:24:05 +01:00
Ole André Vadla Ravnås ab42fcb1ed syscall-tracer: Remove a redundant check 2026-02-16 21:23:52 +01:00
Ole André Vadla Ravnås cd0a327a3c syscall-tracer: Fix raw attachment lifetime
The GVariant was previously created from data freed before return,
leaving it with a dangling pointer.
2026-02-16 21:01:17 +01:00
Ole André Vadla Ravnås 51ff27a08b syscall-tracer: Fix multi-attachment parsing
Fix parsing of multiple attachments by encoding how much space is
reserved for each attachment.
2026-02-16 20:56:54 +01:00
Ole André Vadla Ravnås 6b54eeb7d8 subprojects: Prepare for release 2026-02-14 10:52:09 +01:00
Ole André Vadla Ravnås 08de3d3d07 subprojects: Bump outdated 2026-02-14 10:50:25 +01:00
Ole André Vadla Ravnås 174b93cbd4 xpc-service: Drop support for redundant casts 2026-02-14 10:44:29 +01:00
Ole André Vadla Ravnås d0f8162a65 android: Fix a stylistic inconsistency 2026-02-13 22:53:59 +01:00
Ole André Vadla Ravnås a3427baeb3 subprojects: Prepare for release 2026-02-13 22:48:30 +01:00
Ole André Vadla Ravnås c792a09002 subprojects: Bump outdated 2026-02-13 22:47:31 +01:00
Ole André Vadla Ravnås 27605f7fde android: Defer spawn completion until setArgV0()
Only record the package name in selinux_android_setcontext(), and delay
contacting RoboLauncher until setArgV0(). This ensures ART is ready so
frida-java-bridge can attach to the VM.

Also refactor Zymbiote code and improve naming.
2026-02-13 22:46:03 +01:00
Ole André Vadla Ravnås ba8784fe8c subprojects: Prepare for release 2026-02-13 15:12:30 +01:00
Ole André Vadla Ravnås 0a6c2103fa subprojects: Bump outdated 2026-02-13 15:09:43 +01:00
Ole André Vadla Ravnås 14de0ef567 android: Fix spawn package name logic on new OSes
Where we should be hooking selinux_android_setcontext() to infer it,
like we used to.
2026-02-13 14:39:22 +01:00
Ole André Vadla Ravnås 4165572919 linux: Avoid repeated /proc/pid/maps parsing
By folding the ProcMapsSoEntry functionality into ProcMapsSnapshot.
2026-02-13 14:39:17 +01:00
297 changed files with 41047 additions and 9877 deletions

No files matched your search

@@ -0,0 +1,4 @@
set(VCPKG_TARGET_ARCHITECTURE arm64)
set(VCPKG_CRT_LINKAGE dynamic)
set(VCPKG_LIBRARY_LINKAGE dynamic)
set(VCPKG_BUILD_TYPE release)
@@ -0,0 +1,4 @@
set(VCPKG_TARGET_ARCHITECTURE x64)
set(VCPKG_CRT_LINKAGE dynamic)
set(VCPKG_LIBRARY_LINKAGE dynamic)
set(VCPKG_BUILD_TYPE release)
+143 -30
View File
@@ -31,7 +31,7 @@ jobs:
runs-on: ${{ fromJSON(matrix.runner) }}
steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Install MinGW toolchain for cgo support
@@ -42,7 +42,7 @@ jobs:
msystem: ${{ matrix.sys }}
install: ${{ matrix.pkg }}
- name: Install Go toolchain
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version-file: 'src/compiler/go.mod'
cache-dependency-path: 'src/compiler/go.mod'
@@ -61,13 +61,17 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install gcc-multilib lib32stdc++-13-dev
- name: Install setuptools for distutils shim
if: ${{ startsWith(matrix.id, 'windows-') }}
run: pip install setuptools
- name: Build
if: ${{ startsWith(matrix.id, 'windows-') }}
run: |
.\configure `
--host=${{ matrix.id }} `
--enable-compiler-backend `
${{ env.FRIDA_CORE_OPTIONS }}
${{ env.FRIDA_CORE_OPTIONS }} `
-- --force-fallback-for=openssl
.\make
env:
MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }}
@@ -91,14 +95,40 @@ jobs:
make
- name: Upload devkit
if: ${{ !startsWith(matrix.id, 'linux-') }}
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: core-devkit-${{ matrix.id }}
path: build/src/devkit/
- name: Upload GObject introspection data
if: ${{ matrix.id == 'linux-x86_64' }}
uses: actions/upload-artifact@v7
with:
name: core-gir
path: build/src/api/Frida-1.0.gir
- name: Test
if: ${{ matrix.id != 'windows-arm64' && !startsWith(matrix.id, 'macos-') }}
run: make test
publish-gir:
if: ${{ !cancelled() && github.ref_type == 'tag' && needs.native.result == 'success' }}
needs:
- native
runs-on: ubuntu-latest
steps:
- name: Download GObject introspection data
uses: actions/download-artifact@v8
with:
name: core-gir
path: gir
- name: Attach to GitHub release
uses: softprops/action-gh-release@v3
with:
files: |
gir/Frida-1.0.gir
prerelease: ${{ contains(github.ref_name, 'snapshot') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
cross:
strategy:
matrix:
@@ -113,7 +143,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Install toolchain
@@ -121,7 +151,7 @@ jobs:
sudo apt-get update
sudo apt-get install ${{ matrix.pkg }}
- name: Install Go toolchain
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version-file: 'src/compiler/go.mod'
cache-dependency-path: 'src/compiler/go.mod'
@@ -134,7 +164,7 @@ jobs:
make
- name: Upload devkit
if: ${{ !startsWith(matrix.id, 'linux-') }}
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: core-devkit-${{ matrix.id }}
path: build/src/devkit/
@@ -148,11 +178,11 @@ jobs:
container: ghcr.io/frida/x-tools-linux-${{ matrix.arch }}:latest
steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Install Go toolchain
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version-file: 'src/compiler/go.mod'
cache-dependency-path: 'src/compiler/go.mod'
@@ -164,7 +194,7 @@ jobs:
${{ env.FRIDA_CORE_OPTIONS }}
make
- name: Upload devkit
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: core-devkit-linux-${{ matrix.arch }}
path: build/src/devkit/
@@ -181,7 +211,7 @@ jobs:
container: ghcr.io/frida/x-tools-linux-${{ matrix.arch }}:latest
steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Build
@@ -312,11 +342,11 @@ jobs:
runs-on: ${{ startsWith(matrix.id, 'ios-') && 'macos-latest' || 'ubuntu-latest' }}
steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Install Go toolchain
uses: actions/setup-go@v5
uses: actions/setup-go@v6
with:
go-version-file: 'src/compiler/go.mod'
cache-dependency-path: 'src/compiler/go.mod'
@@ -331,7 +361,7 @@ jobs:
if: startsWith(matrix.id, 'android-')
id: setup-ndk
with:
ndk-version: r25c
ndk-version: r29
- name: Add NDK to env
if: startsWith(matrix.id, 'android-')
run: echo ANDROID_NDK_ROOT=${{ steps.setup-ndk.outputs.ndk-path }} >> $GITHUB_ENV
@@ -343,7 +373,7 @@ jobs:
${{ env.FRIDA_CORE_OPTIONS }}
make
- name: Upload devkit
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: core-devkit-${{ matrix.id }}
path: build/src/devkit/
@@ -402,7 +432,7 @@ jobs:
container: ghcr.io/frida/qnx-tools:latest
steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Build
@@ -411,7 +441,7 @@ jobs:
./configure --host=arm-unknown-nto-qnx6.5.0eabi ${{ env.FRIDA_CORE_OPTIONS }}
make
- name: Upload devkit
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: core-devkit-qnx-armeabi
path: build/src/devkit/
@@ -428,6 +458,7 @@ jobs:
# in, and we want to ensure that they are up-to-date with respect to the source
# for all architectures.
check-linux-helpers:
if: false
strategy:
matrix:
arch: [x86, x86_64, arm, armbe8, arm64, arm64be, arm64beilp32, mips, mipsel, mips64, mips64el]
@@ -436,7 +467,7 @@ jobs:
container: ghcr.io/frida/core-linux-helpers-${{ matrix.arch }}:latest
steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Build
@@ -445,7 +476,7 @@ jobs:
- name: Check for unexpected changes
run: |
git config --global --add safe.directory "$(realpath .)"
status_output=$(git status --porcelain tests/labrats/{resident-agent,simple-agent}-linux-${{ matrix.arch }}.so tests/labrats/{forker,sleeper,spawner}-linux-${{ matrix.arch }})
status_output=$(git status --porcelain src/linux/helpers/artifacts)
if [ -n "$status_output" ]; then
echo "Unexpected changes detected:"
echo "$status_output"
@@ -461,6 +492,7 @@ jobs:
# in, and we want to ensure that they are up-to-date with respect to the source
# for all architectures.
check-labrats:
if: false
strategy:
matrix:
arch: [x86, x86_64, arm, armbe8, arm64, arm64be, arm64beilp32, mips, mipsel, mips64, mips64el]
@@ -469,7 +501,7 @@ jobs:
container: ghcr.io/frida/core-linux-helpers-${{ matrix.arch }}:latest
steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
submodules: recursive
- name: Build
@@ -478,7 +510,7 @@ jobs:
- name: Check for unexpected changes
run: |
git config --global --add safe.directory "$(realpath .)"
status_output=$(git status --porcelain src/linux/helpers/{bootstrapper,loader}-${{ matrix.arch }}.bin)
status_output=$(git status --porcelain tests/labrats/{resident-agent,simple-agent}-linux-${{ matrix.arch }}.so tests/labrats/{forker,sleeper,spawner}-linux-${{ matrix.arch }})
if [ -n "$status_output" ]; then
echo "Unexpected changes detected:"
echo "$status_output"
@@ -491,30 +523,31 @@ jobs:
xcframework:
name: Build FridaCore.xcframework
if: ${{ !cancelled() }}
runs-on: macos-latest
needs:
- native
- mobile
steps:
- name: Check out repo
uses: actions/checkout@v4
uses: actions/checkout@v6
- name: Download macOS/x86_64 devkit
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: core-devkit-macos-x86_64
path: devkits/macos-x86_64
- name: Download macOS/arm64 devkit
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: core-devkit-macos-arm64
path: devkits/macos-arm64
- name: Download iOS/arm64 devkit
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: core-devkit-ios-arm64
path: devkits/ios-arm64
- name: Download iOS/arm64-simulator devkit
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: core-devkit-ios-arm64-simulator
path: devkits/ios-arm64-simulator
@@ -577,19 +610,19 @@ jobs:
-headers xcframework_headers/ios-arm64-simulator \
-output FridaCore.xcframework
- name: Upload FridaCore.xcframework
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v7
with:
name: FridaCore.xcframework
path: FridaCore.xcframework
publish-xcframework:
if: github.ref_type == 'tag'
if: ${{ !cancelled() && github.ref_type == 'tag' && needs.xcframework.result == 'success' }}
runs-on: macos-latest
needs:
- xcframework
steps:
- name: Download FridaCore.xcframework artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v8
with:
name: FridaCore.xcframework
path: dist
@@ -612,10 +645,90 @@ jobs:
cd dist
zip -r FridaCore.xcframework.zip FridaCore.xcframework
- name: Create GitHub release
uses: softprops/action-gh-release@v2
uses: softprops/action-gh-release@v3
with:
files: |
dist/FridaCore.xcframework.zip
prerelease: ${{ contains(github.ref_name, 'snapshot') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
windows-shared:
strategy:
matrix:
include:
- { arch: x86_64, runner: windows-latest, msvc-arch: amd64, vcpkg-triplet: x64-windows-release }
- { arch: arm64, runner: windows-11-arm, msvc-arch: arm64, vcpkg-triplet: arm64-windows-release }
fail-fast: false
runs-on: ${{ matrix.runner }}
timeout-minutes: 120
steps:
- name: Check out repo
uses: actions/checkout@v6
with:
submodules: recursive
- uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{ matrix.msvc-arch }}
- uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install build tools
run: |
pip install meson ninja setuptools
choco install pkgconfiglite -y
- name: Restore vcpkg cache
id: vcpkg-cache
uses: actions/cache/restore@v5
with:
path: C:\vcpkg\installed
key: vcpkg-${{ matrix.vcpkg-triplet }}-v5
- name: Install dependencies via vcpkg
if: steps.vcpkg-cache.outputs.cache-hit != 'true'
run: >-
vcpkg install
--overlay-triplets=${{ github.workspace }}/.github/vcpkg-triplets
--triplet ${{ matrix.vcpkg-triplet }}
glib json-glib libsoup
- name: Save vcpkg cache
if: steps.vcpkg-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
with:
path: C:\vcpkg\installed
key: vcpkg-${{ matrix.vcpkg-triplet }}-v5
- name: Set up vcpkg environment
shell: bash
run: |
TRIPLET="${{ matrix.vcpkg-triplet }}"
VCPKG_PREFIX="C:/vcpkg/installed/$TRIPLET"
echo "$VCPKG_PREFIX/tools/pkgconf" >> $GITHUB_PATH
echo "$VCPKG_PREFIX/bin" >> $GITHUB_PATH
echo "$VCPKG_PREFIX/tools" >> $GITHUB_PATH
echo "PKG_CONFIG_PATH=$VCPKG_PREFIX/lib/pkgconfig" >> $GITHUB_ENV
- name: Configure
run: |
python -c "import sys; sys.path.insert(0, '.'); from releng.meson_configure import main; main()" `
. `
--host=windows-${{ matrix.arch }} `
--enable-shared `
--without-prebuilds=sdk `
"--prefix=$env:RUNNER_TEMP\frida-prefix" `
-- --force-fallback-for=openssl,libnghttp2 -Dcompiler_backend=disabled -Dtests=disabled
- name: Compile
run: python releng/meson/meson.py compile -C build
- name: Install
run: python releng/meson/meson.py install -C build --no-rebuild --skip-subprojects
- name: List installed files
shell: bash
run: find "$RUNNER_TEMP/frida-prefix" -type f | sort
+5
View File
@@ -1,7 +1,12 @@
/.cache/
/build/
/deps/
/src/android-helper/build/
/src/barebone/agent/target/
/src/barebone/agent/target-linux/
/src/barebone/agent/target-win9x/
/src/barebone/agent/linux/*.o
/src/barebone/agent/linux/*.o.bin
/src/barebone/agent/xpacks/
/src/barebone/helpers/memory-scanner/target/
/src/barebone/helpers/target/
+153
View File
@@ -0,0 +1,153 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code)
when working with code in this repository.
## What This Is
frida-core is the core library of the Frida dynamic
instrumentation toolkit. It packages GumJS into a shared
library, injects JavaScript instrumentation into target
processes, provides bidirectional RPC communication with
injected scripts, and enumerates apps/processes/devices.
Written primarily in Vala with OS-specific glue in
C/Objective-C/asm.
## Build Commands
```bash
# First-time setup (fetches git submodules, configures build):
./configure \
--enable-compiler-backend \
--with-devkits=core \
--enable-tests
# Build:
make
# Run tests:
make test
# Rebuild after code changes
make
```
The `configure` script and `Makefile` are thin wrappers
around `releng/meson_configure.py` and
`releng/meson_make.py`. The actual build system is Meson
(>=1.1.0).
Build artifacts go into `build/`. The triplet is
platform-dependent (e.g. `linux-x86_64`).
## Build Requirements
- **Vala compiler**: Must be the Frida-optimized fork from
https://github.com/frida/vala (checks for `-frida` suffix
in version)
- **C/C++ standards**: gnu99/c99 for C, c++17 for C++
- **Go >= 1.24**: Required for compiler backend
- **Python 3**: Build scripts
## Architecture
### Backend System
The core abstraction is a pluggable backend system. Each
backend implements `HostSessionBackend` and provides a
`HostSessionProvider` to create sessions with target
processes:
- **local** — Injects into processes on the local system
(Linux, macOS, Windows, FreeBSD, QNX)
- **fruity** — iOS/tvOS/xrOS devices via USB/network
(QUIC, lockdown protocols)
- **droidy** — Android devices via ADB/JDWP
- **socket** — Remote devices via TCP (connects to
frida-server, frida-gadget, frida-portal)
- **barebone** — Minimal backend using QuickJS
- **simmy** — macOS Simulator
- **compiler** — TypeScript/ESBuild compilation backend
(Go + TypeScript in `src/compiler/`)
Backends are selected at build time via meson options
(e.g. `-Dlocal_backend=enabled`).
### Key Source Layout
- **`src/frida.vala`** — Public API: `DeviceManager`,
`Device`, `Session`, `Script`
- **`src/host-session-service.vala`** — Backend
orchestration, provider registration
- **`src/control-service.vala`** — the guts of frida-server
- **`src/portal-service.vala`** — the guts of frida-portal,
which looks like a frida-server that surfaces any joined
processes (think aggregator/reverse proxy)
- **`lib/base/`** — Core primitives: Session, RPC, streams,
sockets, P2P, buffers
- **`lib/agent/`** — In-process JavaScript agent runtime
- **`lib/payload/`** — Payload compilation and embedding
- **`lib/gadget/`** — Gadget (preloaded library)
implementation
- **`src/<platform>/`** — Platform-specific injectors and
helpers (`linux/`, `darwin/`, `windows/`, `freebsd/`,
`qnx/`)
- **`src/fruity/`** — iOS device protocol stack (USB, QUIC,
plist, XPC, LLDB)
- **`src/droidy/`** — Android device protocol stack
- **`src/compiler/`** — Go/TypeScript compiler backend
### Injection Flow
Each platform has its own injector (e.g. `Linjector` on
Linux, `Fruitjector` on Darwin, `Winjector` on Windows). A
helper process (`frida-helper`) performs privileged injection
operations out-of-process. Helpers and agents can be embedded
into the library or installed as separate files (controlled
by the `assets` meson option).
### Deliverables
- **frida-server** (`server/`) — Standalone daemon exposing
the full API over the network
- **frida-portal** (`portal/`) — Cluster node for
distributed instrumentation
- **frida-inject** (`inject/`) — CLI tool for one-shot
script injection
- **frida-gadget** (`lib/gadget/`) — Shared library for
preload-based instrumentation
### Resource Embedding
Build artifacts (frida-helper, frida-agent) are compiled
into resource blobs via `tools/resource-compiler.vala` and
embedding scripts (`src/embed-helper.py`,
`src/embed-agent.py`). On non-MSVC toolchains these become
`.S` assembly files; on MSVC they become `.obj` files.
## Tests
Tests are in `tests/` as Vala files using a custom async
test harness (`tests/async-harness.vala`). Test binary is
`tests/frida-tests`. Platform-specific runner scripts exist
(e.g. `tests/run-linux-x86_64.sh`).
Test modules: `test-system`, `test-injector`, `test-agent`,
`test-host-session`, `test-gadget`, `test-compiler`. Test
helper programs ("labrats") are in `tests/labrats/`.
## Code Style
### Function Order
Use newspaper/stepdown order: high-level entry points
first, low-level helpers and constants last. In C,
`main()` is always first. When ordering siblings at the
same level, use the chronological order of first use
within their caller.
## Commit Style
- Subject line: max 50 characters
- Body lines: wrap at 72 characters (use the full width, or
slightly less if it avoids making the next line awkward)
+49 -11
View File
@@ -1,15 +1,18 @@
# frida-core
Frida core library intended for static linking into bindings.
Frida core library.
- Lets you inject your own JavaScript instrumentation code into other processes,
optionally with your own [C code][] for performance-sensitive bits.
- Acts as a logistics layer that packages up [GumJS][] into a shared library.
- Provides a two-way communication channel for talking to your scripts,
if needed, and later unload them.
- Also lets you enumerate installed apps, running processes, and connected
devices.
- Written in [Vala][], with OS-specific glue code in C/Objective-C/asm.
- Lets you inject your own JavaScript instrumentation code
into other processes, optionally with your own [C code][]
for performance-sensitive bits.
- Acts as a logistics layer that packages up [GumJS][] into
a shared library.
- Provides a two-way communication channel for talking to
your scripts, if needed, and later unload them.
- Also lets you enumerate installed apps, running processes,
and connected devices.
- Written in [Vala][], with OS-specific glue code in
C/Objective-C/asm.
## Binaries
@@ -29,8 +32,43 @@ $ pip install frida # Python bindings
$ npm install frida # Node.js bindings
```
Or, for static linking into your own project written in a C-compatible language,
download a devkit from the Frida [releases][] page.
Or, for static linking into your own project written in a
C-compatible language, download a devkit from the Frida
[releases][] page.
## Distro Packaging
For building a shared library suitable for distro packaging:
```console
$ ./configure --enable-shared --without-prebuilds=sdk && make
```
This builds `libfrida-core-1.0.so` against system dependencies,
while `frida-agent.so` is built using Frida's SDK (with its GLib
fork, needed for the agent's inject/unload lifecycle).
Prerequisites on Fedora:
```console
$ sudo dnf install libgee-devel json-glib-devel \
libsoup3-devel libunwind-devel libdwarf-devel \
libnice-devel ngtcp2-crypto-ossl-devel
```
Prerequisites on Ubuntu:
```console
$ sudo apt install libgee-0.8-dev libjson-glib-dev \
libsoup-3.0-dev libunwind-dev libdwarf-dev \
libnice-dev libngtcp2-crypto-ossl-dev
```
Prerequisites on macOS:
```console
$ brew install libgee json-glib libsoup libnice capstone
```
## Internals
+235 -63
View File
@@ -9,6 +9,7 @@ import os
from pathlib import Path
import pickle
import platform
import re
import shlex
import shutil
import subprocess
@@ -35,6 +36,7 @@ def main(argv):
command.add_argument("top_builddir", help="top build directory", type=Path)
command.add_argument("frida_version", help="the Frida version")
command.add_argument("host_os", help="operating system binaries are being built for")
command.add_argument("host_os_family", help="operating system family, e.g. darwin")
command.add_argument("host_arch", help="architecture binaries are being built for")
command.add_argument("host_config", help="configuration binaries are being built for")
command.add_argument("compat", help="support for targets with a different architecture",
@@ -42,17 +44,23 @@ def main(argv):
command.add_argument("assets", help="whether assets are embedded vs installed and loaded at runtime")
command.add_argument("components", help="which components will be built",
type=parse_array_option_value)
command.add_argument("glib_flavor", help="upstream or frida", choices=["upstream", "frida"])
command.add_argument("apple_tls", help="whether the parent build is using the Apple TLS backend",
choices=["enabled", "disabled"])
command.add_argument("compilers", help="compiler command arrays", nargs="+")
command.set_defaults(func=lambda args: setup(args.role,
args.builddir,
args.top_builddir,
args.frida_version,
args.host_os,
args.host_os_family,
args.host_arch,
args.host_config if args.host_config else None,
args.compat,
args.assets,
args.components,
args.glib_flavor,
args.apple_tls,
parse_compilers(args.compilers)))
command = subparsers.add_parser("compile", help="compile compatibility assets")
@@ -113,11 +121,14 @@ def setup(role: Role,
top_builddir: Path,
frida_version: str,
host_os: str,
host_os_family: str,
host_arch: str,
host_config: Optional[str],
compat: set[str],
assets: str,
components: set[str],
glib_flavor: str,
apple_tls: str,
compilers: Compilers):
try:
outputs: Mapping[str, Sequence[Output]] = OrderedDict()
@@ -149,9 +160,20 @@ def setup(role: Role,
other_triplet: Optional[str] = None
extra_environ: dict[str, str] = {}
if host_os == "windows" and host_arch == "x86_64" and host_config == "mingw":
other_label = "x86"
have_toolchain, other_triplet = detect_mingw_toolchain_for("x86")
if host_os == "windows" and host_arch in {"x86_64", "x86"}:
if host_arch == "x86_64":
other_label = "x86"
other_kind = "legacy"
else:
other_label = "x86_64"
other_kind = "modern"
if host_config == "mingw":
have_toolchain, other_triplet = detect_mingw_toolchain_for(other_label)
else:
have_toolchain = True
other_triplet = None
elif host_os == "linux" and host_arch == "x86_64" and host_config is None:
other_label = "x86"
other_triplet = "i686-linux-gnu"
@@ -179,21 +201,26 @@ def setup(role: Role,
raise ToolchainNotFoundError(f"unable to locate toolchain for {other_triplet}")
missing.append(MissingFeature(other_label, other_triplet))
if host_os == "windows" and host_arch == "x86_64" and have_toolchain:
group = OutputGroup("x86", other_triplet, extra_environ)
outputs[group] = [
Output(identifier="helper_legacy",
name=HELPER_FILE_WINDOWS.name,
file=HELPER_FILE_WINDOWS,
target=HELPER_TARGET),
Output(identifier="agent_legacy",
name=AGENT_FILE_WINDOWS.name,
file=AGENT_FILE_WINDOWS,
target=AGENT_TARGET),
]
if host_os == "windows" and host_arch in {"x86_64", "x86"} and have_toolchain:
group = OutputGroup(other_label, other_triplet, extra_environ)
outputs[group] = []
if "helper" in components:
outputs[group] += [
Output(identifier=f"helper_{other_kind}",
name=HELPER_FILE_WINDOWS.name,
file=HELPER_FILE_WINDOWS,
target=HELPER_TARGET),
]
if "agent" in components:
outputs[group] += [
Output(identifier=f"agent_{other_kind}",
name=AGENT_FILE_WINDOWS.name,
file=AGENT_FILE_WINDOWS,
target=AGENT_TARGET),
]
if "gadget" in components:
outputs[group] += [
Output(identifier="gadget_legacy",
Output(identifier=f"gadget_{other_kind}",
name=GADGET_FILE_WINDOWS.name,
file=GADGET_FILE_WINDOWS,
target=GADGET_TARGET),
@@ -207,16 +234,21 @@ def setup(role: Role,
other_arch = "arm64e"
kind = "modern"
group = OutputGroup(other_arch)
outputs[group] = [
Output(identifier=f"helper_{kind}",
name=f"frida-helper-{other_arch}",
file=HELPER_FILE_UNIX,
target=HELPER_TARGET),
Output(identifier=f"agent_{kind}",
name=f"frida-agent-{other_arch}.dylib",
file=AGENT_FILE_DARWIN,
target=AGENT_TARGET),
]
outputs[group] = []
if "helper" in components:
outputs[group] += [
Output(identifier=f"helper_{kind}",
name=f"frida-helper-{other_arch}",
file=HELPER_FILE_UNIX,
target=HELPER_TARGET),
]
if "agent" in components:
outputs[group] += [
Output(identifier=f"agent_{kind}",
name=f"frida-agent-{other_arch}.dylib",
file=AGENT_FILE_DARWIN,
target=AGENT_TARGET),
]
if "gadget" in components:
outputs[group] += [
Output(identifier=f"gadget_{kind}",
@@ -234,16 +266,21 @@ def setup(role: Role,
if host_os == "linux" and host_arch == "x86_64" and have_toolchain:
group = OutputGroup("x86", other_triplet, extra_environ)
outputs[group] = [
Output(identifier="helper_legacy",
name=HELPER_FILE_UNIX.name,
file=HELPER_FILE_UNIX,
target=HELPER_TARGET),
Output(identifier="agent_legacy",
name=AGENT_FILE_ELF.name,
file=AGENT_FILE_ELF,
target=AGENT_TARGET),
]
outputs[group] = []
if "helper" in components:
outputs[group] += [
Output(identifier="helper_legacy",
name=HELPER_FILE_UNIX.name,
file=HELPER_FILE_UNIX,
target=HELPER_TARGET),
]
if "agent" in components:
outputs[group] += [
Output(identifier="agent_legacy",
name=AGENT_FILE_ELF.name,
file=AGENT_FILE_ELF,
target=AGENT_TARGET),
]
if "gadget" in components:
outputs[group] += [
Output(identifier="gadget_legacy",
@@ -254,16 +291,21 @@ def setup(role: Role,
if host_os == "android" and host_arch in {"arm64", "x86_64"}:
group = OutputGroup("arm" if host_arch == "arm64" else "x86")
outputs[group] = [
Output(identifier="helper_legacy",
name=HELPER_FILE_UNIX.name,
file=HELPER_FILE_UNIX,
target=HELPER_TARGET),
Output(identifier="agent_legacy",
name=AGENT_FILE_ELF.name,
file=AGENT_FILE_ELF,
target=AGENT_TARGET),
]
outputs[group] = []
if "helper" in components:
outputs[group] += [
Output(identifier="helper_legacy",
name=HELPER_FILE_UNIX.name,
file=HELPER_FILE_UNIX,
target=HELPER_TARGET),
]
if "agent" in components:
outputs[group] += [
Output(identifier="agent_legacy",
name=AGENT_FILE_ELF.name,
file=AGENT_FILE_ELF,
target=AGENT_TARGET),
]
if "gadget" in components:
outputs[group] += [
Output(identifier="gadget_legacy",
@@ -284,18 +326,24 @@ def setup(role: Role,
continue
else:
emulated_triplet = None
outputs[OutputGroup(emulated_arch, emulated_triplet, extra_environ)] = [
Output(identifier=f"helper_emulated_{kind}",
name=HELPER_FILE_WINDOWS.name.replace(".exe", f"-{emulated_arch}.exe"),
file=HELPER_FILE_WINDOWS,
target=HELPER_TARGET),
Output(identifier=f"agent_emulated_{kind}",
name=AGENT_FILE_WINDOWS.name.replace(".dll", f"-{emulated_arch}.dll"),
file=AGENT_FILE_WINDOWS,
target=AGENT_TARGET),
]
group = OutputGroup(emulated_arch, emulated_triplet, extra_environ)
outputs[group] = []
if "helper" in components:
outputs[group] += [
Output(identifier=f"helper_emulated_{kind}",
name=HELPER_FILE_WINDOWS.name.replace(".exe", f"-{emulated_arch}.exe"),
file=HELPER_FILE_WINDOWS,
target=HELPER_TARGET),
]
if "agent" in components:
outputs[group] += [
Output(identifier=f"agent_emulated_{kind}",
name=AGENT_FILE_WINDOWS.name.replace(".dll", f"-{emulated_arch}.dll"),
file=AGENT_FILE_WINDOWS,
target=AGENT_TARGET),
]
if host_os == "android" and host_arch in {"x86_64", "x86"}:
if host_os == "android" and host_arch in {"x86_64", "x86"} and "agent" in components:
outputs[OutputGroup("arm")] = [
Output(identifier="agent_emulated_legacy",
name="frida-agent-arm.so",
@@ -310,10 +358,46 @@ def setup(role: Role,
target=AGENT_TARGET),
]
raw_allowed_prebuilds = os.environ.get("FRIDA_ALLOWED_PREBUILDS")
allowed_prebuilds = set(raw_allowed_prebuilds.split(",")) if raw_allowed_prebuilds is not None else None
if glib_flavor == "upstream":
kind = "modern" if arch_is_modern(host_os, host_arch) else "legacy"
_, agent_file, gadget_file = native_file_paths(host_os)
group = OutputGroup(host_arch)
outputs[group] = []
if "agent" in components:
outputs[group] += [
Output(identifier=f"agent_{kind}",
name=arch_suffixed_name(agent_file.name, host_arch),
file=agent_file,
target=AGENT_TARGET),
]
if "gadget" in components:
outputs[group] += [
Output(identifier=f"gadget_{kind}",
name=arch_suffixed_name(gadget_file.name, host_arch),
file=gadget_file,
target=GADGET_TARGET),
]
elif apple_tls == "enabled" and "agent" in components:
kind = "modern" if arch_is_modern(host_os, host_arch) else "legacy"
_, agent_file, _ = native_file_paths(host_os)
group = OutputGroup(host_arch)
outputs.setdefault(group, [])
outputs[group] += [
Output(identifier=f"agent_{kind}",
name=arch_suffixed_name(agent_file.name, host_arch),
file=agent_file,
target=AGENT_TARGET),
]
state = State(role, builddir, top_builddir, frida_version, host_os, host_config, allowed_prebuilds, outputs)
outputs = OrderedDict((g, items) for g, items in outputs.items() if items)
raw_allowed_prebuilds = os.environ.get("FRIDA_ALLOWED_PREBUILDS")
if raw_allowed_prebuilds is not None:
allowed_prebuilds = set(filter(None, raw_allowed_prebuilds.split(",")))
else:
allowed_prebuilds = None
state = State(role, builddir, top_builddir, frida_version, host_os, host_os_family, host_config, glib_flavor, apple_tls, allowed_prebuilds, outputs)
serialized_state = base64.b64encode(pickle.dumps(state)).decode('ascii')
if missing:
@@ -349,7 +433,10 @@ class State:
top_builddir: Path
frida_version: str
host_os: str
host_os_family: str
host_config: Optional[str]
glib_flavor: str
apple_tls: str
allowed_prebuilds: Optional[set[str]]
outputs: Mapping[OutputGroup, Sequence[Output]]
@@ -429,20 +516,35 @@ def compile(privdir: Path, state: State):
host_machine = MachineSpec(state.host_os, group.arch, state.host_config, group.triplet)
if state.glib_flavor == "upstream":
allowed = None
else:
allowed = state.allowed_prebuilds
if state.glib_flavor == "upstream":
forwarded_options = [o for o in options if not is_stripped_compat_option(o)]
forwarded_options += ["-Ddefault_library=static"]
else:
forwarded_options = [o for o in options if not is_overridden_compat_option(o)]
if state.apple_tls == "enabled":
forwarded_options += ["-Dapple_tls=disabled"]
configure(sourcedir=REPO_ROOT,
builddir=workdir,
host_machine=host_machine,
environ={**build_env, **group.extra_environ},
allowed_prebuilds=state.allowed_prebuilds,
allowed_prebuilds=allowed,
apple_min_os=detect_parent_apple_min_os(top_builddir, state.host_os, state.host_os_family),
extra_meson_options=[
"-Dcompiler_backend=disabled",
"-Dassets=installed",
"-Dhelper_modern=",
"-Dhelper_legacy=",
"-Dagent_modern=",
"-Dagent_legacy=",
"-Dagent_emulated_modern=",
"-Dagent_emulated_legacy=",
*options,
*forwarded_options,
],
call_meson=call_internal_meson,
on_progress=lambda progress: None)
@@ -473,6 +575,18 @@ def compile(privdir: Path, state: State):
(state.builddir / DEPFILE_FILENAME).write_text("\n".join(depfile_lines), encoding="utf-8")
def is_stripped_compat_option(o: str) -> bool:
if is_overridden_compat_option(o):
return True
name = o.split("=", 1)[0].removeprefix("-D")
return name in STRIPPED_COMPAT_OPTIONS or name.endswith(":default_library")
def is_overridden_compat_option(o: str) -> bool:
name = o.split("=", 1)[0].removeprefix("-D")
return name in OVERRIDDEN_COMPAT_OPTIONS
def load_meson_options(top_builddir: Path,
role: Role,
subprojects: set[str]) -> Sequence[str]:
@@ -510,7 +624,11 @@ def option_should_be_forwarded(k: "OptionKey",
return is_for_us or is_for_child
if coredata.CoreData.is_per_machine_option(k):
return k.machine is coredata.MachineChoice.BUILD
# 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 k.name in {"buildtype", "genvslite"}:
@@ -582,6 +700,19 @@ def ensure_submodules_checked_out(releng_location: Path):
check=True)
def detect_parent_apple_min_os(top_builddir: Path,
host_os: str,
host_os_family: str) -> Optional[Mapping[str, str]]:
if host_os_family != "darwin":
return None
pattern = re.compile(r"-target['\"],\s*['\"][^-]+-apple-" + re.escape(host_os) + r"([0-9.]+)")
for native_file in top_builddir.glob(f"frida-{host_os}-*.txt"):
match = pattern.search(native_file.read_text(encoding="utf-8"))
if match is not None:
return {host_os: match.group(1)}
return None
def detect_relevant_subprojects(releng_location: Path) -> dict[str, Path]:
subprojects = detect_relevant_subprojects_in(REPO_ROOT, releng_location)
gum_location = subprojects.get("frida-gum")
@@ -632,9 +763,48 @@ def detect_mingw_toolchain_for(arch: str) -> Tuple[bool, Optional[str]]:
return (found, triplet)
def arch_is_modern(host_os: str, host_arch: str) -> bool:
if host_os in {"macos", "ios", "tvos"} and host_arch == "arm64":
return host_arch == "arm64e"
return host_arch in MODERN_ARCHS
def native_file_paths(host_os: str) -> Tuple[Path, Path, Path]:
if host_os == "windows":
return (HELPER_FILE_WINDOWS, AGENT_FILE_WINDOWS, GADGET_FILE_WINDOWS)
elif host_os in {"macos", "ios", "tvos"}:
return (HELPER_FILE_UNIX, AGENT_FILE_DARWIN, GADGET_FILE_DARWIN)
else:
return (HELPER_FILE_UNIX, AGENT_FILE_ELF, GADGET_FILE_ELF)
def arch_suffixed_name(name: str, arch: str) -> str:
stem, _, ext = name.partition(".")
if ext:
return f"{stem}-{arch}.{ext}"
return f"{stem}-{arch}"
STATE_FILENAME = "state.dat"
DEPFILE_FILENAME = "compat.deps"
STRIPPED_COMPAT_OPTIONS = {
"default_library",
"frida-gum:gumjs",
"frida-gum:inspector",
}
OVERRIDDEN_COMPAT_OPTIONS = {
"compiler_backend",
"assets",
"helper_modern",
"helper_legacy",
"agent_modern",
"agent_legacy",
"agent_emulated_modern",
"agent_emulated_legacy",
}
HELPER_TARGET = "frida-helper"
HELPER_FILE_WINDOWS = Path("src") / "frida-helper.exe"
HELPER_FILE_UNIX = Path("src") / "frida-helper"
@@ -659,6 +829,8 @@ MSVS_ENVVARS = {
"LIB",
}
MODERN_ARCHS = {"x86_64", "arm64", "arm64e", "mips64", "riscv64"}
MINGW_ARCHS = {
"x86": "i686",
"arm64": "aarch64",
+15
View File
@@ -0,0 +1,15 @@
import os
import shutil
import sys
src = sys.argv[1]
install_subdir = sys.argv[2]
install_name = sys.argv[3]
destdir_prefix = os.environ["MESON_INSTALL_DESTDIR_PREFIX"]
dst_dir = os.path.join(destdir_prefix, install_subdir)
dst = os.path.join(dst_dir, install_name)
os.makedirs(dst_dir, exist_ok=True)
shutil.copy2(src, dst)
print(f"Installing {os.path.basename(src)} to {dst_dir}/{install_name}")
+47 -3
View File
@@ -8,6 +8,7 @@ helper_depends = []
agent_modern = get_option('agent_modern')
agent_legacy = get_option('agent_legacy')
agent_compat = ''
agent_host_compat = ''
agent_emulated_modern = get_option('agent_emulated_modern')
agent_emulated_legacy = get_option('agent_emulated_legacy')
agent_depends = []
@@ -28,6 +29,9 @@ if no_overridden_compat_bits
build_py = [python, files('build.py')]
components = []
if have_local_backend
components += ['helper', 'agent']
endif
if build_gadget
components += 'gadget'
endif
@@ -42,11 +46,14 @@ if no_overridden_compat_bits
meson.global_build_root(),
frida_version,
host_os,
host_os_family,
host_abi,
host_config,
','.join(get_option('compat')),
get_option('assets'),
assets_mode,
','.join(components),
have_shared_glib ? 'upstream' : 'frida',
use_apple_tls ? 'enabled' : 'disabled',
'--',
'>>>', cc.cmd_array(), '<<<',
'>>>', cpp.cmd_array(), '<<<',
@@ -64,18 +71,27 @@ if no_overridden_compat_bits
depfile = lines[4]
state = lines[5]
native_arch_suffix = '-' + host_abi
if host_os_family == 'darwin'
install = false
install_dir = asset_dir
else
install = true
install_dir = []
i = 0
foreach varname : varnames
if varname != 'arch_support_bundle' and (get_option('assets') == 'installed' or varname.startswith('gadget_'))
output_name = outputs[i]
needs_rename = output_name.contains(native_arch_suffix) or varname.contains('_emulated_')
should_install = varname != 'arch_support_bundle' \
and not needs_rename \
and (assets_mode == 'installed' or varname.startswith('gadget_'))
if should_install
install_dir += varname.endswith('_modern') ? asset_dir_modern : asset_dir_legacy
else
install_dir += false
endif
i += 1
endforeach
endif
@@ -96,6 +112,7 @@ if no_overridden_compat_bits
foreach output : arch_support.to_list()
outpath = output.full_path()
varname = varnames[i]
output_name = outputs[i]
set_variable(varname, outpath)
@@ -103,7 +120,11 @@ if no_overridden_compat_bits
helper_compat = outpath
helper_depends += arch_support
elif varname.startswith('agent_')
agent_compat = outpath
if output_name.contains('-' + host_abi + '.')
agent_host_compat = outpath
else
agent_compat = outpath
endif
agent_depends += arch_support
elif varname.startswith('gadget_')
gadget_compat = outpath
@@ -115,6 +136,29 @@ if no_overridden_compat_bits
assert(false, f'unexpected variable name: @varname@')
endif
if host_os_family != 'darwin'
rename_target_dir = ''
if output_name.contains(native_arch_suffix)
rename_target_dir = asset_dir
elif varname.contains('_emulated_')
emulated_arch = output_name.split('-')[-1].split('.')[0]
rename_target_dir = root_asset_dir / emulated_arch
endif
if rename_target_dir != ''
install_name = output_name.replace(native_arch_suffix, '')
foreach arch_suffix : ['-x86_64', '-x86', '-arm64', '-arm']
install_name = install_name.replace(arch_suffix, '')
endforeach
if assets_mode == 'installed' or varname.startswith('gadget_')
meson.add_install_script(python, files('install-renamed.py'),
outpath,
rename_target_dir,
install_name,
)
endif
endif
endif
i += 1
endforeach
else
+2 -15
View File
@@ -4,27 +4,14 @@ inject_sources = [
]
system_vala_args = []
if host_os_family != 'windows'
system_vala_args += ['--pkg=posix']
endif
extra_link_args = []
extra_link_depends = []
if host_os_family == 'darwin'
symlist = 'frida-inject.symbols'
extra_link_args += ['-Wl,-exported_symbols_list,' + meson.current_source_dir() / symlist]
extra_link_depends += [symlist]
elif host_os_family != 'windows'
symscript = 'frida-inject.version'
extra_link_args += ['-Wl,--version-script,' + meson.current_source_dir() / symscript]
extra_link_depends += [symscript]
endif
raw_inject = executable('frida-inject-raw', inject_sources,
vala_args: [core_vala_args, system_vala_args],
link_args: extra_link_args,
link_depends: extra_link_depends,
link_args: executable_no_exports_link_args,
link_depends: executable_no_exports_link_depends,
dependencies: [json_glib_dep, core_dep],
)
+8 -2
View File
@@ -9,7 +9,9 @@
# include <signal.h>
# endif
#endif
#ifdef HAVE_GIOOPENSSL
#if defined (HAVE_GIOAPPLE)
# include <gioapple.h>
#elif defined (HAVE_GIOOPENSSL)
# include <gioopenssl.h>
#endif
@@ -31,7 +33,9 @@ _frida_agent_environment_init (void)
g_thread_set_garbage_handler (_frida_agent_on_pending_thread_garbage, NULL);
#ifdef HAVE_GIOOPENSSL
#if defined (HAVE_GIOAPPLE)
g_io_module_apple_register ();
#elif defined (HAVE_GIOOPENSSL)
g_io_module_openssl_register ();
#endif
@@ -51,6 +55,8 @@ void
_frida_agent_environment_deinit (void)
{
#ifndef HAVE_MUSL
frida_libc_shim_prepare_to_deinit ();
gum_shutdown ();
gio_shutdown ();
glib_shutdown ();
+31 -11
View File
@@ -117,6 +117,8 @@ namespace Frida.Agent {
void * opaque_injector_state) {
Environment._init ();
apply_linker_notifier_offsets (agent_parameters);
{
Gum.MemoryRange? mapped_range = null;
@@ -186,6 +188,25 @@ namespace Frida.Agent {
Environment._deinit ();
}
private static void apply_linker_notifier_offsets (string agent_parameters) {
foreach (unowned string token in agent_parameters.split ("|")) {
if (!token.has_prefix ("linker-notifier-offsets:"))
continue;
uint[] offsets = {};
foreach (unowned string entry in token[24:].split (",")) {
uint offset;
if (uint.try_parse (entry, out offset))
offsets += offset;
}
if (offsets.length != 0)
Gum.ModuleRegistry.set_rtld_notifier_offsets (offsets);
return;
}
}
public static void resume_after_transition (ref Frida.UnloadPolicy unload_policy, void * opaque_injector_state) {
{
#if LINUX || FREEBSD
@@ -282,30 +303,29 @@ namespace Frida.Agent {
private async void start (owned FileDescriptorTablePadder padder) {
string[] tokens = agent_parameters.split ("|");
unowned string transport_uri = tokens[0];
bool enable_exceptor = true;
#if DARWIN
enable_exceptor = !Gum.Darwin.query_hardened ();
#endif
var exceptor_mode = Gum.ExceptorMode.FULL;
bool enable_unwind_broker = true;
bool enable_exit_monitor = true;
bool enable_thread_suspend_monitor = true;
bool enable_unwind_sitter = true;
foreach (unowned string option in tokens[1:]) {
if (option == "eternal")
ensure_eternalized ();
else if (option == "sticky")
stop_thread_on_unload = false;
else if (option == "exceptor:off")
enable_exceptor = false;
exceptor_mode = OFF;
else if (option == "exceptor:handler-only")
exceptor_mode = HANDLER_ONLY;
else if (option == "unwind-broker:off")
enable_unwind_broker = false;
else if (option == "exit-monitor:off")
enable_exit_monitor = false;
else if (option == "thread-suspend-monitor:off")
enable_thread_suspend_monitor = false;
else if (option == "unwind-sitter:off")
enable_unwind_sitter = false;
}
if (!enable_exceptor)
Gum.Exceptor.disable ();
if (exceptor_mode != FULL)
Gum.Exceptor.set_mode (exceptor_mode);
{
var interceptor = Gum.Interceptor.obtain ();
@@ -317,7 +337,7 @@ namespace Frida.Agent {
if (enable_thread_suspend_monitor)
thread_suspend_monitor = new ThreadSuspendMonitor (this);
if (enable_unwind_sitter)
if (enable_unwind_broker)
unwind_sitter = new UnwindSitter (this);
this.interceptor = interceptor;
+14
View File
@@ -0,0 +1,14 @@
FRIDA_1.0 {
global:
frida_agent_main;
local:
*;
};
GLIBC_2.2.5 {
local:
fopen*;
fseeko*;
ftello*;
} FRIDA_1.0;
+119 -90
View File
@@ -1,98 +1,127 @@
agent_sources = [
'agent.vala',
'agent-glue.c',
]
force_cpp_linking = have_v8
if force_cpp_linking
agent_sources += ['dummy.cpp']
endif
extra_vala_args = []
extra_link_args = []
extra_link_depends = []
if host_os_family != 'windows'
extra_vala_args += '--pkg=posix'
endif
if host_os_family == 'darwin'
extra_vala_args += '--pkg=frida-gum-darwin-1.0'
endif
if host_os_family == 'windows'
if host_toolchain != 'microsoft'
symfile = (host_arch == 'x86') ? 'frida-agent-x86.symbols' : 'frida-agent.symbols'
extra_link_args += '-Wl,--retain-symbols-file,' + meson.current_source_dir() / symfile
extra_link_depends += symfile
endif
elif host_os_family == 'darwin'
extra_link_args += '-Wl,-exported_symbol,_frida_agent_main'
else
symscript = (host_os == 'android') ? 'frida-agent-android.version' : 'frida-agent.version'
extra_link_args += '-Wl,--version-script,' + meson.current_source_dir() / symscript
extra_link_depends += symscript
endif
if host_os_family in ['linux', 'qnx']
# For our libc shim to be effective.
extra_link_args += '-Wl,-Bsymbolic'
endif
raw_agent = shared_library('frida-agent-raw', agent_sources,
name_prefix: 'lib',
c_args: frida_component_cflags,
vala_header: 'frida-agent.h',
vala_args: [base_vala_args, payload_vala_args, extra_vala_args],
link_args: extra_link_args,
link_depends: extra_link_depends,
vs_module_defs: 'frida-agent.def',
override_options: ['b_asneeded=true'],
dependencies: [gio_dep, gee_dep, gum_dep, gumjs_dep, base_dep, pipe_dep, tls_provider_dep, payload_dep],
)
operations = ['--move', 'constructor', 'frida_libc_shim_init', 'first']
if host_os_family != 'darwin'
operations += ['--move', 'destructor', 'frida_libc_shim_deinit', 'first']
endif
modulated_agent = custom_target('frida-agent-modulated',
input: raw_agent,
output: 'libfrida-agent-modulated' + shlib_suffix,
command: modulate + operations,
)
need_universal_agent = host_os_family == 'darwin' \
and get_option('assets') == 'installed' \
and assets_mode == 'installed' \
and agent_compat != ''
if host_os_family == 'darwin'
identity = 'FridaAgent'
else
identity = agent_name
endif
agent = custom_target('frida-agent',
input: modulated_agent,
output: need_universal_agent ? f'@agent_name@-@host_abi@' : agent_name,
command: post_process + ['shared-library', identity],
build_by_default: true,
install: get_option('assets') == 'installed' and not need_universal_agent,
install_dir: asset_dir,
)
# When compat provides the host-arch agent (e.g. when use_apple_tls is on, so
# the agent has to be built separately to avoid pulling in Apple frameworks),
# skip building it here.
build_agent = agent_host_compat == ''
agent_dep = declare_dependency(link_with: agent)
if build_agent
agent_sources = [
'agent.vala',
'agent-glue.c',
]
if need_universal_agent
custom_target('frida-agent-universal',
input: agent,
output: agent_name,
command: [
lipo,
'@INPUT@',
agent_compat,
'-create',
'-output', meson.current_build_dir() / agent_name,
],
depends: agent_depends,
install: true,
force_cpp_linking = have_v8
if force_cpp_linking
agent_sources += ['dummy.cpp']
endif
extra_vala_args = []
extra_link_args = []
extra_link_depends = []
if host_os_family != 'windows'
extra_vala_args += '--pkg=posix'
endif
if host_os_family == 'darwin'
extra_vala_args += '--pkg=frida-gum-darwin-1.0'
endif
if host_os_family == 'windows'
if host_toolchain != 'microsoft'
symfile = (host_arch == 'x86') ? 'frida-agent-x86.symbols' : 'frida-agent.symbols'
extra_link_args += '-Wl,--retain-symbols-file,' + meson.current_source_dir() / symfile
extra_link_depends += symfile
endif
elif host_os_family == 'darwin'
extra_link_args += '-Wl,-exported_symbol,_frida_agent_main'
else
if libc == 'glibc'
symscript = 'frida-agent-glibc.version'
elif host_os == 'android'
symscript = 'frida-agent-android.version'
else
symscript = 'frida-agent.version'
endif
extra_link_args += '-Wl,--version-script,' + meson.current_source_dir() / symscript
extra_link_depends += symscript
endif
if host_os_family in ['linux', 'qnx']
# For our libc shim to be effective.
extra_link_args += '-Wl,-Bsymbolic'
endif
raw_agent = shared_library('frida-agent-raw', agent_sources,
name_prefix: 'lib',
c_args: frida_component_cflags,
vala_header: 'frida-agent.h',
vala_args: [base_vala_args, payload_vala_args, extra_vala_args],
link_args: extra_link_args,
link_depends: extra_link_depends,
vs_module_defs: 'frida-agent.def',
override_options: ['b_asneeded=true'],
dependencies: [gio_dep, gee_dep, gum_dep, gumjs_dep, gumpharo_dep, base_dep, pipe_dep, tls_provider_dep, payload_dep],
)
operations = ['--move', 'constructor', 'frida_libc_shim_init', 'first']
if host_os_family != 'darwin'
operations += ['--move', 'destructor', 'frida_libc_shim_deinit', 'first']
endif
modulated_agent = custom_target('frida-agent-modulated',
input: raw_agent,
output: 'libfrida-agent-modulated' + shlib_suffix,
command: modulate + operations,
)
if host_os_family == 'darwin'
identity = 'FridaAgent'
else
identity = agent_name
endif
agent = custom_target('frida-agent',
input: modulated_agent,
output: need_universal_agent ? f'@agent_name@-@host_abi@' : agent_name,
command: post_process + ['shared-library', identity],
build_by_default: true,
install: assets_mode == 'installed' and not need_universal_agent,
install_dir: asset_dir,
)
agent_dep = declare_dependency(link_with: agent)
endif
if need_universal_agent
if build_agent
custom_target('frida-agent-universal',
input: agent,
output: agent_name,
command: [
lipo,
'@INPUT@',
agent_compat,
'-create',
'-output', meson.current_build_dir() / agent_name,
],
depends: agent_depends,
install: true,
install_dir: asset_dir,
)
else
custom_target('frida-agent-universal',
output: agent_name,
command: [
lipo,
agent_host_compat,
agent_compat,
'-create',
'-output', meson.current_build_dir() / agent_name,
],
depends: agent_depends,
install: true,
install_dir: asset_dir,
)
endif
endif
+53
View File
@@ -0,0 +1,53 @@
namespace Frida {
public class AssetLocation : Object {
public string libdir {
get;
construct;
}
public static AssetLocation detect () {
string? module_path = null;
Gum.Address our_address = Gum.Address.from_pointer (Gum.strip_code_pointer ((void *) detect));
Gum.Process.enumerate_modules ((details) => {
var range = details.range;
if (our_address >= range.base_address && our_address < range.base_address + range.size) {
module_path = details.path;
return false;
}
return true;
});
assert (module_path != null);
string libdir = Path.get_dirname (module_path);
string basename = Path.get_basename (libdir).down ();
if (basename == "bin" || basename == "sbin") {
string parent = Path.get_dirname (libdir);
string candidate = Path.build_filename (parent, Config.FRIDA_LIBDIR);
if (FileUtils.test (candidate, IS_DIR))
libdir = candidate;
}
return new AssetLocation (libdir);
}
private AssetLocation (string libdir) {
Object (libdir: libdir);
}
public string derive_asset_path (string arch, string filename) {
string root = Path.build_filename (libdir, Config.FRIDA_LIBDIR_NAME);
if (FileUtils.test (root, IS_DIR))
#if DARWIN
return Path.build_filename (root, filename);
#else
return Path.build_filename (root, arch, filename);
#endif
return Path.build_filename (libdir, filename);
}
public string derive_plugin_path (string filename) {
return Path.build_filename (libdir, Config.FRIDA_LIBDIR_NAME, "plugins", filename);
}
}
}
+262 -127
View File
@@ -309,8 +309,17 @@ namespace Frida {
public sealed class Buffer : Object {
public Bytes bytes {
get;
construct;
get {
if (_bytes == null)
_bytes = new Bytes.static (_data);
return _bytes;
}
}
public size_t size {
get {
return _data.length;
}
}
public ByteOrder byte_order {
@@ -323,20 +332,39 @@ namespace Frida {
construct;
}
private unowned uint8 * data;
private size_t size;
private Bytes? _bytes;
private unowned uint8[] _data;
public Buffer (Bytes bytes, ByteOrder byte_order = HOST, uint pointer_size = (uint) sizeof (size_t)) {
Object (
bytes: bytes,
byte_order: byte_order,
pointer_size: pointer_size
);
reset_bytes (bytes);
}
construct {
data = bytes.get_data ();
size = bytes.get_size ();
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) {
@@ -470,17 +498,13 @@ namespace Frida {
return *((double *) &bits);
}
public string read_string (size_t offset) throws Error {
public unowned string read_string (size_t offset) throws Error {
string * start = (string *) get_pointer (offset, sizeof (char));
size_t max_length = size - offset;
size_t max_length = _data.length - offset;
string * end = memchr (start, 0, max_length);
if (end == null)
throw new Error.PROTOCOL ("Missing null character");
size_t size = end - start;
string val = start->substring (0, (long) size);
if (!val.validate ())
throw new Error.PROTOCOL ("Invalid UTF-8 string");
return val;
return start;
}
[CCode (cname = "memchr", cheader_filename = "string.h")]
@@ -488,17 +512,14 @@ namespace Frida {
public string read_fixed_string (size_t offset, size_t size) throws Error {
string * start = (string *) get_pointer (offset, size);
size_t max_length = size_t.min (size, this.size - offset);
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;
string val = start->substring (0, (long) n);
if (!val.validate ())
throw new Error.PROTOCOL ("Invalid UTF-8 string");
return val;
return start->substring (0, (long) n);
}
public unowned Buffer write_string (size_t offset, string val) {
@@ -508,9 +529,14 @@ namespace Frida {
}
public Bytes read_bytes (size_t offset, size_t size) {
var data = new uint8[size + 1];
Memory.copy (data, get_pointer (offset, size), size);
return new Bytes.take ((owned) data);
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) {
@@ -521,129 +547,49 @@ namespace Frida {
private uint8 * get_pointer (size_t offset, size_t n) {
size_t minimum_size = offset + n;
assert (size >= minimum_size);
return data + offset;
assert (_data.length >= minimum_size);
return (uint8 *) _data + offset;
}
}
public sealed class BufferReader {
public Buffer buffer {
get;
}
public size_t offset {
get {
return _offset;
}
get;
}
public size_t available {
get {
return buffer.bytes.get_size () - _offset;
return _size - _offset;
}
}
private Buffer buffer;
private size_t _offset = 0;
private size_t _size;
public BufferReader (Buffer buf) {
buffer = buf;
public BufferReader (Buffer buffer) {
reset (buffer);
}
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 void reset (Buffer buffer) {
_buffer = buffer;
_size = buffer.size;
_offset = 0;
}
public int8 read_int8 () throws Error {
check_available (sizeof (int8));
var val = buffer.read_int8 (_offset);
_offset += sizeof (int8);
return val;
public void reset_at (Buffer buffer, size_t offset) throws Error {
_buffer = buffer;
_size = buffer.size;
seek (offset);
}
public uint8 read_uint8 () throws Error {
check_available (sizeof (uint8));
var val = buffer.read_uint8 (_offset);
_offset += sizeof (uint8);
return val;
}
public int16 read_int16 () throws Error {
check_available (sizeof (int16));
var val = buffer.read_int16 (_offset);
_offset += sizeof (int16);
return val;
}
public uint16 read_uint16 () throws Error {
check_available (sizeof (uint16));
var val = buffer.read_uint16 (_offset);
_offset += sizeof (uint16);
return val;
}
public int32 read_int32 () throws Error {
check_available (sizeof (int32));
var val = buffer.read_int32 (_offset);
_offset += sizeof (int32);
return val;
}
public uint32 read_uint32 () throws Error {
check_available (sizeof (uint32));
var val = buffer.read_uint32 (_offset);
_offset += sizeof (uint32);
return val;
}
public int64 read_int64 () throws Error {
check_available (sizeof (int64));
var val = buffer.read_int64 (_offset);
_offset += sizeof (int64);
return val;
}
public uint64 read_uint64 () throws Error {
check_available (sizeof (uint64));
var val = buffer.read_uint64 (_offset);
_offset += sizeof (uint64);
return val;
}
public float read_float () throws Error {
check_available (sizeof (float));
var val = buffer.read_float (_offset);
_offset += sizeof (float);
return val;
}
public double read_double () throws Error {
check_available (sizeof (double));
var val = buffer.read_double (_offset);
_offset += sizeof (double);
return val;
}
public string read_string () throws Error {
check_available (1);
var val = buffer.read_string (_offset);
_offset += val.length + 1;
return val;
}
public string read_fixed_string (size_t size) throws Error {
check_available (size);
var val = buffer.read_fixed_string (_offset, size);
_offset += size;
return val;
}
public Bytes read_bytes (size_t size) throws Error {
check_available (size);
var val = buffer.bytes[_offset:_offset + size];
_offset += size;
return val;
public unowned BufferReader 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 {
@@ -652,6 +598,195 @@ namespace Frida {
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");
+12 -2
View File
@@ -1,18 +1,26 @@
#ifndef __FRIDA_ATOMICS_H__
#define __FRIDA_ATOMICS_H__
#include <glib.h>
G_BEGIN_DECLS
#ifdef _MSC_VER
/* TODO: Add once needed. */
#else
static inline guint64 frida_atomics_load_u64_acquire (volatile guint64 * p)
typedef guint64 FridaAtomicU64 __attribute__ ((aligned (8)));
static inline guint64
frida_atomics_load_u64_acquire (volatile FridaAtomicU64 * p)
{
return __atomic_load_n (p, __ATOMIC_ACQUIRE);
}
static inline void frida_atomics_store_u64_release (volatile guint64 * p, guint64 v)
static inline void
frida_atomics_store_u64_release (volatile FridaAtomicU64 * p, guint64 v)
{
__atomic_store_n (p, v, __ATOMIC_RELEASE);
}
@@ -24,4 +32,6 @@ static inline guint32 frida_atomics_load_u32_acquire (volatile guint32 * p)
#endif
G_END_DECLS
#endif
+3 -3
View File
@@ -1,6 +1,6 @@
[CCode (cheader_filename = "frida-atomics.h")]
namespace Frida.Atomics {
public static uint64 load_u64_acquire (void * p);
public static void store_u64_release (void * p, uint64 v);
public static uint32 load_u32_acquire (void * p);
public static uint64 load_u64_acquire (uint64 * p);
public static void store_u64_release (uint64 * p, uint64 v);
public static uint32 load_u32_acquire (uint32 * p);
}
+4
View File
@@ -6,6 +6,8 @@
#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);
@@ -17,6 +19,8 @@ void _frida_xpc_connection_send_message_with_reply (xpc_connection_t connection,
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
+41
View File
@@ -0,0 +1,41 @@
#ifndef __FRIDA_JNI_H__
#define __FRIDA_JNI_H__
#include <jni.h>
G_BEGIN_DECLS
typedef jint (* JNIDestroyJavaVMFunc) (JavaVM *);
typedef jint (* JNIAttachCurrentThreadFunc) (JavaVM *, JNIEnv **, void *);
typedef jint (* JNIDetachCurrentThreadFunc) (JavaVM *);
typedef jint (* JNIGetEnvFunc) (JavaVM *, JNIEnv **, jint);
typedef jclass (* JNIFindClassFunc) (JNIEnv *, const char *);
typedef jthrowable (* JNIExceptionOccurredFunc) (JNIEnv *);
typedef void (* JNIExceptionDescribeFunc) (JNIEnv *);
typedef void (* JNIExceptionClearFunc) (JNIEnv *);
typedef jint (* JNIPushLocalFrameFunc) (JNIEnv *, jint);
typedef jobject (* JNIPopLocalFrameFunc) (JNIEnv *, jobject);
typedef jobject (* JNINewGlobalRefFunc) (JNIEnv *, jobject);
typedef void (* JNIDeleteGlobalRefFunc) (JNIEnv *, jobject);
typedef void (* JNIDeleteLocalRefFunc) (JNIEnv *, jobject);
typedef jobject (* JNINewObjectFunc) (JNIEnv *, jclass, jmethodID, ...);
typedef jclass (* JNIGetObjectClassFunc) (JNIEnv *, jobject);
typedef jmethodID (* JNIGetMethodIDFunc) (JNIEnv *, jclass, const char *, const char *);
typedef jobject (* JNICallObjectMethodFunc) (JNIEnv *, jobject, jmethodID, ...);
typedef jstring (* JNINewStringUTFFunc) (JNIEnv *, const char *);
typedef const char * (* JNIGetStringUTFCharsFunc) (JNIEnv *, jstring, jboolean *);
typedef void (* JNIReleaseStringUTFCharsFunc) (JNIEnv *, jstring, const char *);
typedef jboolean (* JNIExceptionCheckFunc) (JNIEnv *);
G_END_DECLS
#endif
+49 -11
View File
@@ -46,7 +46,7 @@ namespace Frida {
public extern unowned LinuxSyscallSignature[]? get_compat32_syscall_signatures ();
public struct LinuxSyscallSignature {
public uint nr;
public int nr;
public string name;
public uint8 nargs;
public LinuxSyscallArg args[6];
@@ -128,6 +128,7 @@ namespace Frida {
}
}
#if HAVE_LOCAL_BACKEND
public sealed class BpfObject {
public Programs programs {
get;
@@ -138,37 +139,35 @@ namespace Frida {
}
internal Libbpf.Object handle;
private char[] kernel_log_buf;
public static BpfObject open (string name, uint8[] blob) throws Error {
var log_buf = new char[64 * 1024];
var opts = Libbpf.Object.OpenOpts ();
opts.sz = sizeof (Libbpf.Object.OpenOpts);
opts.object_name = name;
opts.kernel_log_buf = log_buf;
var log = new LibbpfLogScope ();
var handle = Libbpf.Object.open_mem (blob, opts);
if (handle == null)
throw_libbpf_error (errno);
throw_libbpf_error (errno, log.take ());
return new BpfObject ((owned) handle, (owned) log_buf);
return new BpfObject ((owned) handle);
}
private BpfObject (owned Libbpf.Object handle, owned char[] kernel_log_buf) {
private BpfObject (owned Libbpf.Object handle) {
this.handle = (owned) handle;
this.kernel_log_buf = (owned) kernel_log_buf;
_programs = new Programs (this);
_maps = new Maps (this);
}
public void prepare () throws Error {
check_libbpf_result (handle.prepare ());
var log = new LibbpfLogScope ();
check_libbpf_result (handle.prepare (), log.take ());
}
public void load () throws Error {
check_libbpf_result (handle.load (), (string) kernel_log_buf);
var log = new LibbpfLogScope ();
check_libbpf_result (handle.load (), log.take ());
}
public sealed class Programs {
@@ -348,6 +347,18 @@ namespace Frida {
remove_raw ((uint8[]) &key);
}
public void update_u64_u8 (uint64 key, uint8 val) throws Error {
update_raw ((uint8[]) &key, (uint8[]) &val);
}
public void update_u64_u32 (uint64 key, uint32 val) throws Error {
update_raw ((uint8[]) &key, (uint8[]) &val);
}
public void remove_u64 (uint64 key) throws Error {
remove_raw ((uint8[]) &key);
}
public void foreach_percpu_value<T> (uint8[] key, PercpuValueFunc<T> func) throws Error {
var ncpus = check_libbpf_result (Libbpf.num_possible_cpus ());
var percpu_stride = round_up_8 (value_size);
@@ -384,6 +395,30 @@ namespace Frida {
}
}
private sealed class LibbpfLogScope {
private static StringBuilder buffer;
public LibbpfLogScope () {
if (buffer == null)
buffer = new StringBuilder ();
buffer.truncate ();
Libbpf.set_print (on_message);
}
~LibbpfLogScope () {
Libbpf.set_print (null);
}
public string take () {
return buffer.str.chomp ();
}
private static int on_message (Libbpf.PrintLevel level, string format, va_list args) {
buffer.append_vprintf (format, args);
return 0;
}
}
private int check_libbpf_result (int result, string details = "") throws Error {
if (result < 0)
throw_libbpf_error (result, details);
@@ -510,6 +545,7 @@ namespace Frida {
STOP,
}
}
#endif
namespace PerfEvent {
public FileDescriptor open (PerfEventAttr * attr, int pid, int cpu, int group_fd, uint flags) throws Error {
@@ -524,7 +560,9 @@ namespace Frida {
throw new Error.NOT_SUPPORTED ("%s (errno=%d: %s)".printf (message, Posix.errno, Posix.strerror (Posix.errno)));
}
#if HAVE_LOCAL_BACKEND
private size_t round_up_8 (size_t x) {
return (x + 7) & ~((size_t) 7);
}
#endif
}
+17 -10
View File
@@ -1,4 +1,5 @@
base_sources = [
'asset-location.vala',
'session.vala',
'session-glue.c',
'channel.vala',
@@ -15,6 +16,8 @@ base_sources = [
'buffer.vala',
'buffer-glue.c',
'value.vala',
'xnu-syscalls' / 'xnu-syscalls.vala',
'xnu-syscalls' / 'xnu-syscalls-glue.c',
]
extra_vala_args = []
@@ -45,8 +48,10 @@ if host_os_family == 'linux'
extra_vala_args += [
'--pkg=linux',
'--pkg=frida-atomics',
'--pkg=libbpf',
]
if have_local_backend
extra_vala_args += '--pkg=libbpf'
endif
syscall_arch_from_abi = {
'armbe': 'arm',
@@ -73,7 +78,7 @@ extra_deps = []
if host_os_family != 'windows'
extra_deps += gio_unix_dep
endif
if host_os_family == 'linux'
if host_os_family == 'linux' and have_local_backend
extra_deps += libbpf_dep
endif
@@ -110,7 +115,7 @@ base = static_library('frida-base-' + api_version, base_sources,
vala_gir: base_gir_name,
vala_vapi: f'frida-base-@[email protected]',
dependencies: [glib_dep, gobject_dep, gio_dep, libsoup_dep, nice_dep, usrsctp_dep, gee_dep, json_glib_dep, gum_dep, openssl_dep] + extra_deps,
install: true,
install: have_in_tree_payloads,
install_dir: [true, header_install_dir, true, false],
)
@@ -122,10 +127,12 @@ base_dep = declare_dependency(
base_build_dir = meson.current_build_dir()
pkg = import('pkgconfig')
pkg.generate(base,
filebase: f'frida-base-@api_version@',
name: 'Frida Base Library',
version: frida_version,
description: 'Basic building blocks',
)
if have_in_tree_payloads
pkg = import('pkgconfig')
pkg.generate(base,
filebase: f'frida-base-@api_version@',
name: 'Frida Base Library',
version: frida_version,
description: 'Basic building blocks',
)
endif
+417 -9
View File
@@ -1,26 +1,432 @@
#ifdef HAVE_NICE
#define OPENSSL_SUPPRESS_DEPRECATED
#include "frida-base.h"
#include <errno.h>
#include <openssl/asn1.h>
#include <openssl/bio.h>
#include <openssl/bn.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/rsa.h>
#include <openssl/x509.h>
#include <usrsctp.h>
#ifdef HAVE_GIOAPPLE
# include <CoreFoundation/CoreFoundation.h>
# include <Security/Security.h>
#else
# define OPENSSL_SUPPRESS_DEPRECATED
# include <openssl/asn1.h>
# include <openssl/bio.h>
# include <openssl/bn.h>
# include <openssl/evp.h>
# include <openssl/pem.h>
# include <openssl/rsa.h>
# include <openssl/x509.h>
#endif
#ifdef HAVE_GIOAPPLE
static SecKeyRef frida_rsa_keypair_generate (void);
static void frida_rsa_export_public_key (SecKeyRef private_key, GByteArray * out);
static void frida_rsa_export_private_key (SecKeyRef private_key, GByteArray * out);
static void frida_rsa_sign_sha256 (SecKeyRef private_key, const guint8 * message, gsize message_length, GByteArray * out);
static void frida_build_tbs_certificate (GByteArray * out, const GByteArray * subject_public_key);
static void frida_build_name (GByteArray * out);
static void frida_build_rdn (GByteArray * out, const guint8 * oid, gsize oid_length, guint8 value_tag, const gchar * value);
static void frida_build_validity (GByteArray * out);
static void frida_build_subject_public_key_info (GByteArray * out, const GByteArray * rsa_public_key);
static void frida_build_signed_certificate (GByteArray * out, const GByteArray * tbs, const GByteArray * signature);
static void frida_build_algorithm_identifier (GByteArray * out, const guint8 * oid, gsize oid_length);
static void frida_build_pkcs8_private_key_info (GByteArray * out, const GByteArray * rsa_private_key);
static gchar * frida_pem_encode (const gchar * type, const guint8 * der, gsize der_length);
static void frida_der_append_tlv (GByteArray * out, guint8 tag, const guint8 * value, gsize length);
static void frida_der_append_length (GByteArray * out, gsize length);
static void frida_der_append_integer_uint (GByteArray * out, guint64 value);
static void frida_der_append_null (GByteArray * out);
static void frida_der_append_oid (GByteArray * out, const guint8 * oid, gsize length);
static void frida_der_append_utctime (GByteArray * out, time_t t);
static void frida_der_append_bit_string (GByteArray * out, const guint8 * bytes, gsize length);
static const guint8 frida_oid_country[] = { 0x55, 0x04, 0x06 };
static const guint8 frida_oid_organization[] = { 0x55, 0x04, 0x0a };
static const guint8 frida_oid_common_name[] = { 0x55, 0x04, 0x03 };
static const guint8 frida_oid_rsa_encryption[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 };
static const guint8 frida_oid_sha256_with_rsa[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b };
#else
static gchar * frida_steal_bio_to_string (BIO ** bio);
#endif
static int frida_on_connection_output (void * addr, void * buffer, size_t length, uint8_t tos, uint8_t set_df);
static void frida_on_debug_printf (const char * format, ...);
static void frida_on_upcall (struct socket * sock, void * user_data, int flags);
#ifdef HAVE_GIOAPPLE
void
_frida_generate_certificate (guint8 ** cert_der, gint * cert_der_length, gchar ** cert_pem, gchar ** key_pem)
{
SecKeyRef private_key;
GByteArray * rsa_public_key, * rsa_private_key, * subject_public_key, * tbs, * signature, * certificate, * pkcs8;
*cert_der = NULL;
*cert_der_length = 0;
*cert_pem = NULL;
*key_pem = NULL;
private_key = frida_rsa_keypair_generate ();
rsa_public_key = g_byte_array_new ();
frida_rsa_export_public_key (private_key, rsa_public_key);
rsa_private_key = g_byte_array_new ();
frida_rsa_export_private_key (private_key, rsa_private_key);
subject_public_key = g_byte_array_new ();
frida_build_subject_public_key_info (subject_public_key, rsa_public_key);
tbs = g_byte_array_new ();
frida_build_tbs_certificate (tbs, subject_public_key);
signature = g_byte_array_new ();
frida_rsa_sign_sha256 (private_key, tbs->data, tbs->len, signature);
certificate = g_byte_array_new ();
frida_build_signed_certificate (certificate, tbs, signature);
pkcs8 = g_byte_array_new ();
frida_build_pkcs8_private_key_info (pkcs8, rsa_private_key);
*cert_der = g_memdup2 (certificate->data, certificate->len);
*cert_der_length = certificate->len;
*cert_pem = frida_pem_encode ("CERTIFICATE", certificate->data, certificate->len);
*key_pem = frida_pem_encode ("PRIVATE KEY", pkcs8->data, pkcs8->len);
g_byte_array_unref (pkcs8);
g_byte_array_unref (certificate);
g_byte_array_unref (signature);
g_byte_array_unref (tbs);
g_byte_array_unref (subject_public_key);
g_byte_array_unref (rsa_private_key);
g_byte_array_unref (rsa_public_key);
CFRelease (private_key);
}
static SecKeyRef
frida_rsa_keypair_generate (void)
{
SecKeyRef private_key;
CFMutableDictionaryRef attrs;
const int key_size_bits = 2048;
CFNumberRef key_size;
attrs = CFDictionaryCreateMutable (kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
key_size = CFNumberCreate (kCFAllocatorDefault, kCFNumberIntType, &key_size_bits);
CFDictionarySetValue (attrs, kSecAttrKeyType, kSecAttrKeyTypeRSA);
CFDictionarySetValue (attrs, kSecAttrKeySizeInBits, key_size);
CFRelease (key_size);
private_key = SecKeyCreateRandomKey (attrs, NULL);
g_assert (private_key != NULL);
CFRelease (attrs);
return private_key;
}
static void
frida_rsa_export_public_key (SecKeyRef private_key, GByteArray * out)
{
SecKeyRef public_key;
CFDataRef data;
public_key = SecKeyCopyPublicKey (private_key);
g_assert (public_key != NULL);
data = SecKeyCopyExternalRepresentation (public_key, NULL);
g_assert (data != NULL);
g_byte_array_append (out, CFDataGetBytePtr (data), CFDataGetLength (data));
CFRelease (data);
CFRelease (public_key);
}
static void
frida_rsa_export_private_key (SecKeyRef private_key, GByteArray * out)
{
CFDataRef data;
data = SecKeyCopyExternalRepresentation (private_key, NULL);
g_assert (data != NULL);
g_byte_array_append (out, CFDataGetBytePtr (data), (guint) CFDataGetLength (data));
CFRelease (data);
}
static void
frida_rsa_sign_sha256 (SecKeyRef private_key, const guint8 * message, gsize message_length, GByteArray * out)
{
CFDataRef to_sign, signature;
to_sign = CFDataCreate (kCFAllocatorDefault, message, (CFIndex) message_length);
signature = SecKeyCreateSignature (private_key, kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA256, to_sign, NULL);
g_assert (signature != NULL);
g_byte_array_append (out, CFDataGetBytePtr (signature), (guint) CFDataGetLength (signature));
CFRelease (signature);
CFRelease (to_sign);
}
static void
frida_build_tbs_certificate (GByteArray * out, const GByteArray * subject_public_key)
{
GByteArray * inner = g_byte_array_new ();
frida_der_append_integer_uint (inner, 1);
frida_build_algorithm_identifier (inner, frida_oid_sha256_with_rsa, sizeof (frida_oid_sha256_with_rsa));
frida_build_name (inner);
frida_build_validity (inner);
frida_build_name (inner);
g_byte_array_append (inner, subject_public_key->data, subject_public_key->len);
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
g_byte_array_unref (inner);
}
static void
frida_build_name (GByteArray * out)
{
GByteArray * inner = g_byte_array_new ();
frida_build_rdn (inner, frida_oid_country, sizeof (frida_oid_country), 0x13, "CA");
frida_build_rdn (inner, frida_oid_organization, sizeof (frida_oid_organization), 0x0c, "Frida");
frida_build_rdn (inner, frida_oid_common_name, sizeof (frida_oid_common_name), 0x0c, "lolcathost");
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
g_byte_array_unref (inner);
}
static void
frida_build_rdn (GByteArray * out, const guint8 * oid, gsize oid_length, guint8 value_tag, const gchar * value)
{
GByteArray * atv, * rdn;
atv = g_byte_array_new ();
rdn = g_byte_array_new ();
frida_der_append_oid (atv, oid, oid_length);
frida_der_append_tlv (atv, value_tag, (const guint8 *) value, strlen (value));
frida_der_append_tlv (rdn, 0x30, atv->data, atv->len);
frida_der_append_tlv (out, 0x31, rdn->data, rdn->len);
g_byte_array_unref (rdn);
g_byte_array_unref (atv);
}
static void
frida_build_validity (GByteArray * out)
{
const time_t lifetime_seconds = 15780000;
GByteArray * inner;
time_t not_before, not_after;
inner = g_byte_array_new ();
not_before = time (NULL);
not_after = not_before + lifetime_seconds;
frida_der_append_utctime (inner, not_before);
frida_der_append_utctime (inner, not_after);
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
g_byte_array_unref (inner);
}
static void
frida_build_subject_public_key_info (GByteArray * out, const GByteArray * rsa_public_key)
{
GByteArray * inner = g_byte_array_new ();
frida_build_algorithm_identifier (inner, frida_oid_rsa_encryption, sizeof (frida_oid_rsa_encryption));
frida_der_append_bit_string (inner, rsa_public_key->data, rsa_public_key->len);
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
g_byte_array_unref (inner);
}
static void
frida_build_signed_certificate (GByteArray * out, const GByteArray * tbs, const GByteArray * signature)
{
GByteArray * inner = g_byte_array_new ();
g_byte_array_append (inner, tbs->data, tbs->len);
frida_build_algorithm_identifier (inner, frida_oid_sha256_with_rsa, sizeof (frida_oid_sha256_with_rsa));
frida_der_append_bit_string (inner, signature->data, signature->len);
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
g_byte_array_unref (inner);
}
static void
frida_build_algorithm_identifier (GByteArray * out, const guint8 * oid, gsize oid_length)
{
GByteArray * inner = g_byte_array_new ();
frida_der_append_oid (inner, oid, oid_length);
frida_der_append_null (inner);
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
g_byte_array_unref (inner);
}
static void
frida_build_pkcs8_private_key_info (GByteArray * out, const GByteArray * rsa_private_key)
{
GByteArray * inner = g_byte_array_new ();
frida_der_append_integer_uint (inner, 0);
frida_build_algorithm_identifier (inner, frida_oid_rsa_encryption, sizeof (frida_oid_rsa_encryption));
frida_der_append_tlv (inner, 0x04, rsa_private_key->data, rsa_private_key->len);
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
g_byte_array_unref (inner);
}
static gchar *
frida_pem_encode (const gchar * type, const guint8 * der, gsize der_length)
{
GString * pem;
gchar * base64;
gsize length, i;
base64 = g_base64_encode (der, der_length);
length = strlen (base64);
pem = g_string_sized_new (length + 128);
g_string_append_printf (pem, "-----BEGIN %s-----\n", type);
for (i = 0; i < length; i += 64)
{
gsize chunk = MIN (64, length - i);
g_string_append_len (pem, base64 + i, chunk);
g_string_append_c (pem, '\n');
}
g_string_append_printf (pem, "-----END %s-----\n", type);
g_free (base64);
return g_string_free (pem, FALSE);
}
static void
frida_der_append_tlv (GByteArray * out, guint8 tag, const guint8 * value, gsize length)
{
g_byte_array_append (out, &tag, 1);
frida_der_append_length (out, length);
if (length > 0)
g_byte_array_append (out, value, length);
}
static void
frida_der_append_length (GByteArray * out, gsize length)
{
gint i;
guint8 buf[9], header;
if (length < 0x80)
{
guint8 b = (guint8) length;
g_byte_array_append (out, &b, 1);
return;
}
i = 0;
while (length > 0)
{
buf[++i] = (guint8) (length & 0xff);
length >>= 8;
}
header = (guint8) (0x80 | i);
g_byte_array_append (out, &header, 1);
while (i > 0)
{
g_byte_array_append (out, &buf[i], 1);
i--;
}
}
static void
frida_der_append_integer_uint (GByteArray * out, guint64 value)
{
gint i;
guint8 buf[9];
gint start;
for (i = 7; i >= 0; i--)
{
buf[i + 1] = (guint8) (value & 0xff);
value >>= 8;
}
buf[0] = 0;
start = 0;
while (start < 8 && buf[start] == 0 && (buf[start + 1] & 0x80) == 0)
start++;
frida_der_append_tlv (out, 0x02, buf + start, (gsize) (9 - start));
}
static void
frida_der_append_null (GByteArray * out)
{
frida_der_append_tlv (out, 0x05, NULL, 0);
}
static void
frida_der_append_oid (GByteArray * out, const guint8 * oid, gsize length)
{
frida_der_append_tlv (out, 0x06, oid, length);
}
static void
frida_der_append_utctime (GByteArray * out, time_t t)
{
struct tm tm;
gchar buf[16];
gmtime_r (&t, &tm);
g_snprintf (buf, sizeof (buf), "%02d%02d%02d%02d%02d%02dZ",
tm.tm_year % 100, tm.tm_mon + 1, tm.tm_mday,
tm.tm_hour, tm.tm_min, tm.tm_sec);
frida_der_append_tlv (out, 0x17, (const guint8 *) buf, strlen (buf));
}
static void
frida_der_append_bit_string (GByteArray * out, const guint8 * bytes, gsize length)
{
guint8 tag = 0x03;
guint8 unused_bits = 0;
g_byte_array_append (out, &tag, 1);
frida_der_append_length (out, length + 1);
g_byte_array_append (out, &unused_bits, 1);
if (length > 0)
g_byte_array_append (out, bytes, length);
}
#else
void
_frida_generate_certificate (guint8 ** cert_der, gint * cert_der_length, gchar ** cert_pem, gchar ** key_pem)
{
@@ -90,6 +496,8 @@ frida_steal_bio_to_string (BIO ** bio)
return result;
}
#endif
void
_frida_sctp_connection_initialize_sctp_backend (void)
{
+36
View File
@@ -1,5 +1,11 @@
namespace Frida {
/**
* Issues RPC calls over a {@link RpcPeer}, matching responses to requests.
*/
public sealed class RpcClient : Object {
/**
* The peer used to send and receive RPC messages.
*/
public weak RpcPeer peer {
get;
construct;
@@ -7,10 +13,23 @@ namespace Frida {
private Gee.HashMap<string, PendingResponse> pending_responses = new Gee.HashMap<string, PendingResponse> ();
/**
* Creates an RPC client that talks over @peer.
*
* @param peer the peer to send and receive messages through
*/
public RpcClient (RpcPeer peer) {
Object (peer: peer);
}
/**
* Calls a remote method and waits for its result.
*
* @param method the method name to invoke
* @param args the JSON arguments
* @param data binary data to accompany the call, if any
* @return the method's JSON result
*/
public async Json.Node call (string method, Json.Node[] args, Bytes? data, Cancellable? cancellable) throws Error, IOError {
string request_id = Uuid.string_random ();
@@ -69,6 +88,13 @@ namespace Frida {
return pending.result;
}
/**
* Feeds an incoming message to the client, matching it against pending
* calls if it is an RPC response.
*
* @param json the incoming message as a JSON string
* @return true if the message was an RPC message and was handled
*/
public bool try_handle_message (string json) {
if (json.index_of ("\"frida:rpc\"") == -1)
return false;
@@ -157,7 +183,17 @@ namespace Frida {
}
}
/**
* A transport over which an {@link RpcClient} sends and receives RPC
* messages.
*/
public interface RpcPeer : Object {
/**
* Posts an RPC message to the peer.
*
* @param json the message as a JSON string
* @param data binary data to accompany the message, if any
*/
public abstract async void post_rpc_message (string json, Bytes? data, Cancellable? cancellable) throws Error, IOError;
}
}
+620 -3
View File
@@ -12,6 +12,8 @@ namespace Frida {
Cancellable? cancellable) throws GLib.Error;
public abstract async void enable_spawn_gating (Cancellable? cancellable) throws GLib.Error;
public abstract async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
Cancellable? cancellable) throws GLib.Error;
public abstract async void disable_spawn_gating (Cancellable? cancellable) throws GLib.Error;
public abstract async HostSpawnInfo[] enumerate_pending_spawn (Cancellable? cancellable) throws GLib.Error;
public abstract async HostChildInfo[] enumerate_pending_children (Cancellable? cancellable) throws GLib.Error;
@@ -31,6 +33,7 @@ namespace Frida {
public abstract async ServiceSessionId open_service (string address, Cancellable? cancellable) throws GLib.Error;
public signal void spawn_gating_disabled (SpawnGatingDisabledReason reason);
public signal void spawn_added (HostSpawnInfo info);
public signal void spawn_removed (HostSpawnInfo info);
public signal void child_added (HostChildInfo info);
@@ -78,6 +81,8 @@ namespace Frida {
Cancellable? cancellable) throws GLib.Error;
public abstract async void destroy_script (AgentScriptId script_id, Cancellable? cancellable) throws GLib.Error;
public abstract async void load_script (AgentScriptId script_id, Cancellable? cancellable) throws GLib.Error;
public abstract async void interrupt_script (AgentScriptId script_id, Cancellable? cancellable) throws GLib.Error;
public abstract async void terminate_script (AgentScriptId script_id, Cancellable? cancellable) throws GLib.Error;
public abstract async void eternalize_script (AgentScriptId script_id, Cancellable? cancellable) throws GLib.Error;
public abstract async void enable_debugger (AgentScriptId script_id, Cancellable? cancellable) throws GLib.Error;
@@ -755,10 +760,25 @@ namespace Frida {
public abstract async void break_and_detach (Cancellable? cancellable) throws GLib.Error;
}
/**
* What the Gadget should do when a configured breakpoint is hit.
*/
public enum GadgetBreakpointAction {
/**
* Invoke the function and return immediately.
*/
INVOKE_RETURN,
/**
* Resume execution.
*/
RESUME,
/**
* Detach the Gadget.
*/
DETACH,
/**
* Apply the page plan.
*/
PAGE_PLAN,
}
@@ -802,16 +822,37 @@ namespace Frida {
}
[DBus (name = "re.frida.AuthenticationService17")]
/**
* Authenticates clients connecting to a {@link PortalService} or
* {@link ControlService}.
*/
public interface AuthenticationService : Object {
/**
* Authenticates a client by its token.
*
* @param token the token presented by the client
* @return a JSON string describing the authenticated session
*/
public abstract async string authenticate (string token, Cancellable? cancellable) throws GLib.Error;
}
/**
* An {@link AuthenticationService} that accepts a single fixed token.
*/
public sealed class StaticAuthenticationService : Object, AuthenticationService {
/**
* The SHA-256 hash of the accepted token.
*/
public string token_hash {
get;
construct;
}
/**
* Creates a service that accepts the given token.
*
* @param token the token clients must present
*/
public StaticAuthenticationService (string token) {
Object (token_hash: Checksum.compute_for_string (SHA256, token));
}
@@ -865,6 +906,11 @@ namespace Frida {
throw_not_authorized ();
}
public async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
Cancellable? cancellable) throws Error, IOError {
throw_not_authorized ();
}
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
throw_not_authorized ();
}
@@ -944,8 +990,57 @@ namespace Frida {
throw new Error.PERMISSION_DENIED ("Not authorized, authentication required");
}
// Fires `expired` when a gated process goes unresumed too long, so the backend can cancel
// gating instead of letting a stalled client wedge process creation.
public sealed class SpawnGatingWatchdog : Object {
public signal void expired ();
public uint timeout_msec {
get;
construct;
}
private Gee.Map<uint, Source> deadlines = new Gee.HashMap<uint, Source> ();
// Default deadline: under the OS's own spawn-start timeouts (Android's is 10s).
public SpawnGatingWatchdog (uint timeout_msec = 8000) {
Object (timeout_msec: timeout_msec);
}
public void arm (uint pid) {
var deadline = new TimeoutSource (timeout_msec);
deadline.set_callback (() => {
expired ();
return Source.REMOVE;
});
deadline.attach (MainContext.get_thread_default ());
deadlines[pid] = deadline;
}
public void cancel (uint pid) {
Source? deadline;
if (deadlines.unset (pid, out deadline))
deadline.destroy ();
}
public void clear () {
foreach (var deadline in deadlines.values)
deadline.destroy ();
deadlines.clear ();
}
}
/**
* The realm an agent should run in.
*/
public enum Realm {
/**
* The process's native realm.
*/
NATIVE,
/**
* An emulated realm, such as code running under binary translation.
*/
EMULATED;
public static Realm from_nick (string nick) throws Error {
@@ -957,6 +1052,32 @@ namespace Frida {
}
}
/**
* How the agent should take over exception handling in the target.
*/
public enum Exceptor {
/**
* Install handlers and keep the target from overriding them.
*/
FULL,
/**
* Install handlers but let the target replace them.
*/
HANDLER_ONLY,
/**
* Do not handle exceptions.
*/
OFF;
public static Exceptor from_nick (string nick) throws Error {
return Marshal.enum_from_nick<Exceptor> (nick);
}
public string to_nick () {
return Marshal.enum_to_nick<Exceptor> (this);
}
}
public enum SpawnStartState {
RUNNING,
SUSPENDED;
@@ -1031,11 +1152,52 @@ namespace Frida {
}
#endif
public enum SessionDetachReason {
/**
* Why spawn gating was disabled on a {@link Device}.
*/
public enum SpawnGatingDisabledReason {
/**
* A client asked to disable spawn gating.
*/
APPLICATION_REQUESTED = 1,
/**
* A caught process went unresumed long enough that the gater cancelled gating to
* avoid stalling process creation.
*/
WATCHDOG_TIMEOUT;
public static SpawnGatingDisabledReason from_nick (string nick) throws Error {
return Marshal.enum_from_nick<SpawnGatingDisabledReason> (nick);
}
public string to_nick () {
return Marshal.enum_to_nick<SpawnGatingDisabledReason> (this);
}
}
/**
* Why a {@link Session} was detached from its target.
*/
public enum SessionDetachReason {
/**
* The host asked to detach.
*/
APPLICATION_REQUESTED = 1,
/**
* The target process was replaced, for example by an exec.
*/
PROCESS_REPLACED,
/**
* The target process exited.
*/
PROCESS_TERMINATED,
/**
* The connection to the target was lost.
*/
CONNECTION_TERMINATED,
/**
* The device became unreachable.
*/
DEVICE_LOST;
public static SessionDetachReason from_nick (string nick) throws Error {
@@ -1048,19 +1210,61 @@ namespace Frida {
}
[DBus (name = "re.frida.Error")]
/**
* The errors that Frida operations can fail with.
*/
public errordomain Error {
/**
* The frida-server is not running.
*/
SERVER_NOT_RUNNING,
/**
* The specified executable was not found.
*/
EXECUTABLE_NOT_FOUND,
/**
* The specified executable is not supported.
*/
EXECUTABLE_NOT_SUPPORTED,
/**
* No process matched the request.
*/
PROCESS_NOT_FOUND,
/**
* The process is not responding.
*/
PROCESS_NOT_RESPONDING,
/**
* An argument was invalid.
*/
INVALID_ARGUMENT,
/**
* The operation is not valid in the current state.
*/
INVALID_OPERATION,
/**
* Permission was denied.
*/
PERMISSION_DENIED,
/**
* The requested address is already in use.
*/
ADDRESS_IN_USE,
/**
* The operation timed out.
*/
TIMED_OUT,
/**
* The operation is not supported.
*/
NOT_SUPPORTED,
/**
* A protocol error occurred.
*/
PROTOCOL,
/**
* A transport-level error occurred.
*/
TRANSPORT
}
@@ -1127,7 +1331,13 @@ namespace Frida {
}
}
/**
* Options for {@link Device.get_frontmost_application}.
*/
public sealed class FrontmostQueryOptions : Object {
/**
* How much detail to include about the application.
*/
public Scope scope {
get;
set;
@@ -1157,7 +1367,14 @@ namespace Frida {
}
}
/**
* Options for {@link Device.enumerate_applications}, including an optional
* set of identifiers to restrict the results to.
*/
public sealed class ApplicationQueryOptions : Object {
/**
* How much detail to include about each application.
*/
public Scope scope {
get;
set;
@@ -1166,14 +1383,30 @@ namespace Frida {
private Gee.List<string> identifiers = new Gee.ArrayList<string> ();
/**
* Restricts the query to the given application identifier. May be called
* multiple times to select several.
*
* @param identifier the application identifier to include
*/
public void select_identifier (string identifier) {
identifiers.add (identifier);
}
/**
* Checks whether any identifiers have been selected.
*
* @return true if the query is restricted to specific identifiers
*/
public bool has_selected_identifiers () {
return !identifiers.is_empty;
}
/**
* Invokes @func for each selected identifier.
*
* @param func function called with each identifier
*/
public void enumerate_selected_identifiers (Func<string> func) {
foreach (var identifier in identifiers)
func (identifier);
@@ -1215,7 +1448,14 @@ namespace Frida {
}
}
/**
* Options for {@link Device.enumerate_processes}, including an optional set
* of PIDs to restrict the results to.
*/
public sealed class ProcessQueryOptions : Object {
/**
* How much detail to include about each process.
*/
public Scope scope {
get;
set;
@@ -1224,14 +1464,30 @@ namespace Frida {
private Gee.List<uint> pids = new Gee.ArrayList<uint> ();
/**
* Restricts the query to the given PID. May be called multiple times to
* select several.
*
* @param pid the process ID to include
*/
public void select_pid (uint pid) {
pids.add (pid);
}
/**
* Checks whether any PIDs have been selected.
*
* @return true if the query is restricted to specific PIDs
*/
public bool has_selected_pids () {
return !pids.is_empty;
}
/**
* Invokes @func for each selected PID.
*
* @param func function called with each PID
*/
public void enumerate_selected_pids (Func<uint> func) {
foreach (var pid in pids)
func (pid);
@@ -1273,9 +1529,21 @@ namespace Frida {
}
}
/**
* How much detail to include in query results.
*/
public enum Scope {
/**
* Only the essential fields.
*/
MINIMAL,
/**
* Essential fields plus metadata.
*/
METADATA,
/**
* Everything available.
*/
FULL;
public static Scope from_nick (string nick) throws Error {
@@ -1287,6 +1555,67 @@ namespace Frida {
}
}
/**
* Options for {@link Device.enable_spawn_gating}.
*/
public sealed class SpawnGatingOptions : Object {
/**
* Which processes to gate. Anything other than DEFAULT requires a Frida new enough to
* support scoping, and fails rather than silently gating something broader than asked.
*/
public SpawnGatingScope scope {
get;
set;
default = DEFAULT;
}
public HashTable<string, Variant> _serialize () {
var dict = make_parameters_dict ();
if (scope != DEFAULT)
dict["scope"] = new Variant.string (scope.to_nick ());
return dict;
}
public static SpawnGatingOptions _deserialize (HashTable<string, Variant> dict) throws Error {
var options = new SpawnGatingOptions ();
Variant? scope = dict["scope"];
if (scope != null) {
if (!scope.is_of_type (VariantType.STRING))
throw new Error.INVALID_ARGUMENT ("The 'scope' option must be a string");
options.scope = SpawnGatingScope.from_nick (scope.get_string ());
}
return options;
}
}
public enum SpawnGatingScope {
/**
* Leave the choice to the implementation. Sent as a plain enable_spawn_gating call, so
* it still works against a Frida too old to know about scopes.
*/
DEFAULT,
/**
* Application launches only.
*/
APPLICATIONS,
/**
* Every process created on the system. More potent but far more intrusive.
*/
PROCESSES;
public static SpawnGatingScope from_nick (string nick) throws Error {
return Marshal.enum_from_nick<SpawnGatingScope> (nick);
}
public string to_nick () {
return Marshal.enum_to_nick<SpawnGatingScope> (this);
}
}
public struct HostSpawnOptions {
public bool has_argv;
public string[] argv;
@@ -1357,24 +1686,103 @@ namespace Frida {
}
}
/**
* Options controlling how {@link Device.attach} sets up a {@link Session}.
*/
public sealed class SessionOptions : Object {
/**
* The realm to attach in.
*/
public Realm realm {
get;
set;
default = NATIVE;
}
/**
* How long the session may survive a disconnection before being torn
* down, in seconds; 0 disables persistence.
*/
public uint persist_timeout {
get;
set;
default = 0;
}
/**
* Path to a custom agent to use in the emulated realm.
*/
public string? emulated_agent_path {
get;
set;
}
/**
* How the agent should handle exceptions in the target.
*/
public Exceptor exceptor {
get;
set;
default = FULL;
}
/**
* Whether to install the unwind broker so exceptions can propagate
* through instrumented code.
*/
public bool unwind_broker {
get;
set;
default = true;
}
/**
* Whether to monitor the target for exit so the session is cleaned up.
*/
public bool exit_monitor {
get;
set;
default = true;
}
/**
* Whether to monitor thread suspension in the target.
*/
public bool thread_suspend_monitor {
get;
set;
default = true;
}
private Gee.List<uint> linker_notifier_offsets = new Gee.ArrayList<uint> ();
/**
* Clears the configured linker notifier offsets.
*/
public void clear_linker_notifier_offsets () {
linker_notifier_offsets.clear ();
}
/**
* Adds a linker notifier offset, used to detect module load and unload
* events in the target.
*
* @param offset the offset into the linker to instrument
*/
public void add_linker_notifier_offset (uint offset) {
linker_notifier_offsets.add (offset);
}
/**
* Invokes @func for each configured linker notifier offset.
*
* @param func function called with each offset
*/
public void enumerate_linker_notifier_offsets (Func<uint> func) {
foreach (var offset in linker_notifier_offsets)
func (offset);
}
public HashTable<string, Variant> _serialize () {
var dict = make_parameters_dict ();
@@ -1387,6 +1795,25 @@ namespace Frida {
if (emulated_agent_path != null)
dict["emulated-agent-path"] = new Variant.string (emulated_agent_path);
if (exceptor != FULL)
dict["exceptor"] = new Variant.string (exceptor.to_nick ());
if (!unwind_broker)
dict["unwind-broker"] = new Variant.string ("off");
if (!exit_monitor)
dict["exit-monitor"] = new Variant.string ("off");
if (!thread_suspend_monitor)
dict["thread-suspend-monitor"] = new Variant.string ("off");
if (!linker_notifier_offsets.is_empty) {
var builder = new VariantBuilder (new VariantType ("au"));
foreach (uint offset in linker_notifier_offsets)
builder.add ("u", offset);
dict["linker-notifier-offsets"] = builder.end ();
}
return dict;
}
@@ -1414,12 +1841,61 @@ namespace Frida {
options.emulated_agent_path = path.get_string ();
}
Variant? exceptor = dict["exceptor"];
if (exceptor != null) {
if (!exceptor.is_of_type (VariantType.STRING))
throw new Error.INVALID_ARGUMENT ("The 'exceptor' option must be a string");
options.exceptor = Exceptor.from_nick (exceptor.get_string ());
}
Variant? unwind_broker = dict["unwind-broker"];
if (unwind_broker != null) {
if (!unwind_broker.is_of_type (VariantType.STRING) || unwind_broker.get_string () != "off")
throw new Error.INVALID_ARGUMENT ("The 'unwind-broker' option is invalid");
options.unwind_broker = false;
}
Variant? exit_monitor = dict["exit-monitor"];
if (exit_monitor != null) {
if (!exit_monitor.is_of_type (VariantType.STRING) || exit_monitor.get_string () != "off")
throw new Error.INVALID_ARGUMENT ("The 'exit-monitor' option is invalid");
options.exit_monitor = false;
}
Variant? thread_suspend_monitor = dict["thread-suspend-monitor"];
if (thread_suspend_monitor != null) {
if (!thread_suspend_monitor.is_of_type (VariantType.STRING) ||
thread_suspend_monitor.get_string () != "off")
throw new Error.INVALID_ARGUMENT ("The 'thread-suspend-monitor' option is invalid");
options.thread_suspend_monitor = false;
}
Variant? offsets = dict["linker-notifier-offsets"];
if (offsets != null) {
if (!offsets.is_of_type (new VariantType ("au")))
throw new Error.INVALID_ARGUMENT (
"The 'linker-notifier-offsets' option must be an array of uint32");
uint n = (uint) offsets.n_children ();
for (uint i = 0; i != n; i++)
options.add_linker_notifier_offset (offsets.get_child_value (i).get_uint32 ());
}
return options;
}
}
/**
* How to set up the standard I/O streams of a spawned process.
*/
public enum Stdio {
/**
* Inherit the host's streams.
*/
INHERIT,
/**
* Pipe the streams through Frida, surfaced via {@link Device.output}
* and {@link Device.input}.
*/
PIPE;
public static Stdio from_nick (string nick) throws Error {
@@ -1483,9 +1959,21 @@ namespace Frida {
}
}
/**
* How a {@link Child} came to be.
*/
public enum ChildOrigin {
/**
* Created by fork().
*/
FORK,
/**
* The result of an exec() replacing the image.
*/
EXEC,
/**
* Spawned as a new process.
*/
SPAWN;
public static ChildOrigin from_nick (string nick) throws Error {
@@ -1602,23 +2090,39 @@ namespace Frida {
}
}
/**
* Options for creating a {@link Script} with {@link Session.create_script}.
*/
public sealed class ScriptOptions : Object {
/**
* A name for the script, used in logging and debugging.
*/
public string? name {
get;
set;
}
/**
* A heap snapshot to start the script from, as produced by
* {@link Session.snapshot_script}.
*/
public Bytes? snapshot {
get;
set;
}
/**
* How the snapshot is delivered to the runtime.
*/
public SnapshotTransport snapshot_transport {
get;
set;
default = INLINE;
}
/**
* Which JavaScript runtime to run the script in.
*/
public ScriptRuntime runtime {
get;
set;
@@ -1687,17 +2191,35 @@ namespace Frida {
}
}
/**
* How a script's heap snapshot is delivered to the runtime.
*/
public enum SnapshotTransport {
/**
* Embed the snapshot inline with the script.
*/
INLINE,
/**
* Pass the snapshot through shared memory.
*/
SHARED_MEMORY
}
/**
* Options for building a heap snapshot with {@link Session.snapshot_script}.
*/
public sealed class SnapshotOptions : Object {
/**
* Script to run to warm up the runtime before capturing the snapshot.
*/
public string? warmup_script {
get;
set;
}
/**
* Which JavaScript runtime to capture the snapshot for.
*/
public ScriptRuntime runtime {
get;
set;
@@ -1737,10 +2259,26 @@ namespace Frida {
}
}
/**
* Which JavaScript runtime a script should use.
*/
public enum ScriptRuntime {
/**
* Let Frida choose.
*/
DEFAULT,
/**
* The QuickJS runtime.
*/
QJS,
V8;
/**
* The V8 runtime.
*/
V8,
/**
* The Pharo runtime.
*/
PHARO;
public static ScriptRuntime from_nick (string nick) throws Error {
return Marshal.enum_from_nick<ScriptRuntime> (nick);
@@ -1767,17 +2305,29 @@ namespace Frida {
}
}
/**
* Options for {@link Session.join_portal}.
*/
public sealed class PortalOptions : Object {
/**
* TLS certificate to present to the portal.
*/
public TlsCertificate? certificate {
get;
set;
}
/**
* Authentication token to present to the portal.
*/
public string? token {
get;
set;
}
/**
* Access-control tags scoping what this membership may reach.
*/
public string[]? acl {
get;
set;
@@ -1830,7 +2380,14 @@ namespace Frida {
}
}
/**
* Options for {@link Session.setup_peer_connection}, configuring how the
* peer-to-peer connection is negotiated.
*/
public sealed class PeerOptions : Object {
/**
* The STUN server to use for discovering the public address, if any.
*/
public string? stun_server {
get;
set;
@@ -1838,14 +2395,27 @@ namespace Frida {
private Gee.List<Relay> relays = new Gee.ArrayList<Relay> ();
/**
* Removes all configured relays.
*/
public void clear_relays () {
relays.clear ();
}
/**
* Adds a relay to use when a direct connection cannot be established.
*
* @param relay the relay to add
*/
public void add_relay (Relay relay) {
relays.add (relay);
}
/**
* Invokes @func for each configured relay.
*
* @param func function called with each relay
*/
public void enumerate_relays (Func<Relay> func) {
foreach (var relay in relays)
func (relay);
@@ -1891,27 +2461,51 @@ namespace Frida {
}
}
/**
* A TURN relay used to establish a peer-to-peer connection when a direct
* one is not possible.
*/
public sealed class Relay : Object {
/**
* The relay's address, as host and port.
*/
public string address {
get;
construct;
}
/**
* The username to authenticate with.
*/
public string username {
get;
construct;
}
/**
* The password to authenticate with.
*/
public string password {
get;
construct;
}
/**
* The kind of relay and transport to use.
*/
public RelayKind kind {
get;
construct;
}
/**
* Creates a relay.
*
* @param address the relay's address, as host and port
* @param username the username to authenticate with
* @param password the password to authenticate with
* @param kind the kind of relay and transport
*/
public Relay (string address, string username, string password, RelayKind kind) {
Object (
address: address,
@@ -1938,9 +2532,21 @@ namespace Frida {
}
}
/**
* The kind of a {@link Relay} used for peer-to-peer connections.
*/
public enum RelayKind {
/**
* A TURN relay reached over UDP.
*/
TURN_UDP,
/**
* A TURN relay reached over TCP.
*/
TURN_TCP,
/**
* A TURN relay reached over TLS.
*/
TURN_TLS
}
@@ -2170,7 +2776,10 @@ namespace Frida {
public static string enum_to_nick<T> (int val) {
var klass = (EnumClass) typeof (T).class_ref ();
return klass.get_value (val).value_nick;
unowned EnumValue? v = klass.get_value (val);
if (v == null)
return "unknown-0x%x".printf (val);
return v.value_nick;
}
}
@@ -2196,5 +2805,13 @@ namespace Frida {
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;
}
}
}
+242 -10
View File
@@ -97,37 +97,77 @@ namespace Frida {
}
}
/**
* Configures the network endpoint a {@link PortalService} or
* {@link ControlService} listens on.
*/
public sealed class EndpointParameters : Object {
/**
* The address to bind to, or null for the default.
*/
public string? address {
get;
construct;
}
/**
* The port to listen on, or 0 for the default.
*/
public uint16 port {
get;
construct;
}
/**
* TLS certificate to serve, enabling TLS when set.
*/
public TlsCertificate? certificate {
get;
construct;
}
/**
* Origin to require from connecting clients, if any.
*/
public string? origin {
get;
construct;
}
/**
* Service used to authenticate connecting clients, if any.
*/
public AuthenticationService? auth_service {
get;
construct;
}
/**
* Directory whose files are served as static web assets, if any.
*/
public File? asset_root {
get;
set;
}
/**
* Handler invoked for web requests, if any.
*/
public WebRequestHandler? request_handler {
get;
set;
}
/**
* Creates endpoint parameters.
*
* @param address the address to bind to, or null for the default
* @param port the port to listen on, or 0 for the default
* @param certificate TLS certificate to serve, or null for plaintext
* @param origin origin to require from clients, or null
* @param auth_service service used to authenticate clients, or null
* @param asset_root directory of static assets to serve, or null
*/
public EndpointParameters (string? address = null, uint16 port = 0, TlsCertificate? certificate = null,
string? origin = null, AuthenticationService? auth_service = null, File? asset_root = null) {
Object (
@@ -141,6 +181,132 @@ namespace Frida {
}
}
/**
* Handles web requests made to an endpoint's served assets.
*/
public interface WebRequestHandler : Object {
/**
* Handles a single web request.
*
* @param request the incoming request
* @return the response to send, or null to fall through to the default
* handling
*/
public abstract async WebResponse? handle_request (WebRequest request, Cancellable? cancellable) throws Error, IOError;
}
/**
* An incoming web request passed to a {@link WebRequestHandler}.
*/
public sealed class WebRequest : Object {
/**
* The HTTP method, such as `GET` or `POST`.
*/
public string method {
get;
construct;
}
/**
* The request path.
*/
public string path {
get;
construct;
}
/**
* The raw query string, if any.
*/
public string? query_string {
get;
construct;
}
/**
* The request body, if any.
*/
public Bytes? body {
get;
construct;
}
private Soup.MessageHeaders raw_headers;
internal WebRequest (string method, string path, string? query_string, Soup.MessageHeaders headers, Bytes? body) {
Object (method: method, path: path, query_string: query_string, body: body);
raw_headers = headers;
}
/**
* Invokes @func for each request header.
*
* @param func function called with each header's name and value
*/
public void foreach_header (WebRequestHeaderFunc func) {
raw_headers.foreach ((name, val) => {
func (name, val);
});
}
}
/**
* Callback invoked for each header while iterating a {@link WebRequest}.
*
* @param name the header name
* @param val the header value
*/
public delegate void WebRequestHeaderFunc (string name, string val);
/**
* A response returned from a {@link WebRequestHandler}.
*/
public sealed class WebResponse : Object {
/**
* The HTTP status code.
*/
public uint status {
get;
construct;
}
/**
* The response body.
*/
public Bytes body {
get;
construct;
}
private Soup.MessageHeaders raw_headers = new Soup.MessageHeaders (RESPONSE);
/**
* Creates a response.
*
* @param status the HTTP status code
* @param body the response body
*/
public WebResponse (uint status, Bytes body) {
Object (status: status, body: body);
}
/**
* Adds a header to the response.
*
* @param name the header name
* @param val the header value
*/
public void add_header (string name, string val) {
raw_headers.append (name, val);
}
internal void write_headers_to (Soup.MessageHeaders target) {
raw_headers.foreach ((name, val) => {
target.replace (name, val);
});
}
}
public async IOStream negotiate_connection (IOStream stream, WebServiceTransport transport, string host, string? origin,
Cancellable? cancellable) throws Error, IOError {
var input = (DataInputStream) Object.new (typeof (DataInputStream),
@@ -468,14 +634,14 @@ namespace Frida {
server.add_websocket_handler ("/ws", endpoint_params.origin, null, on_websocket_opened);
if (endpoint_params.asset_root != null)
server.add_handler (null, on_asset_request);
if (endpoint_params.request_handler != null || endpoint_params.asset_root != null)
server.add_handler (null, on_http_request);
}
public void close () {
io_cancellable.cancel ();
if (endpoint_params.asset_root != null)
if (endpoint_params.request_handler != null || endpoint_params.asset_root != null)
server.remove_handler ("/");
server.remove_handler ("/ws");
@@ -575,20 +741,77 @@ namespace Frida {
connections.remove (connection);
}
private void on_asset_request (Soup.Server server, Soup.ServerMessage msg, string path,
private void on_http_request (Soup.Server server, Soup.ServerMessage msg, string path,
HashTable<string, string>? query) {
msg.get_response_headers ().replace ("Server", "Frida/" + _version_string ());
unowned string method = msg.get_method ();
if (method != "GET" && method != "HEAD") {
msg.set_status (Soup.Status.METHOD_NOT_ALLOWED, null);
msg.pause ();
handle_http_request.begin (msg, path);
}
private async void handle_http_request (Soup.ServerMessage msg, string path) {
WebRequestHandler? handler = endpoint_params.request_handler;
if (handler != null) {
if (yield try_request_handler (handler, msg, path, io_cancellable))
return;
}
File? asset_root = endpoint_params.asset_root;
if (asset_root != null) {
unowned string method = msg.get_method ();
if (method != "GET" && method != "HEAD") {
msg.set_status (Soup.Status.METHOD_NOT_ALLOWED, null);
msg.unpause ();
return;
}
File location = asset_root.resolve_relative_path (path.next_char ());
yield handle_asset_request (path, location, msg);
return;
}
File location = endpoint_params.asset_root.resolve_relative_path (path.next_char ());
msg.set_status (Soup.Status.NOT_FOUND, null);
msg.unpause ();
}
msg.pause ();
handle_asset_request.begin (path, location, msg);
private static async bool try_request_handler (WebRequestHandler handler, Soup.ServerMessage msg, string path,
Cancellable? cancellable) {
var body = msg.get_request_body ();
Bytes? request_body = (body.length != 0) ? body.flatten () : null;
unowned Uri? uri = msg.get_uri ();
string? query_string = (uri != null) ? uri.get_query () : null;
var request = new WebRequest (msg.get_method (), path, query_string, msg.get_request_headers (),
request_body);
WebResponse? response;
try {
response = yield handler.handle_request (request, cancellable);
} catch (Error e) {
msg.set_status (Soup.Status.INTERNAL_SERVER_ERROR, null);
msg.set_response ("text/plain; charset=utf-8", Soup.MemoryUse.COPY, e.message.data);
msg.unpause ();
return true;
} catch (IOError e) {
msg.unpause ();
return true;
}
if (response == null)
return false;
apply_response (msg, response);
msg.unpause ();
return true;
}
private static void apply_response (Soup.ServerMessage msg, WebResponse response) {
msg.set_status (response.status, null);
response.write_headers_to (msg.get_response_headers ());
Bytes body = response.body;
if (body.length > 0)
msg.get_response_body ().append_bytes (body);
}
private async void handle_asset_request (string path, File file, Soup.ServerMessage msg) {
@@ -940,6 +1163,12 @@ namespace Frida {
Memory.copy (buffer, recv_queue.data, n);
recv_queue.remove_range (0, (uint) n);
if (recv_queue.len == 0) {
#if HAVE_SOUP_WEBSOCKET_BACKPRESSURE
websocket.resume_input ();
#endif
}
update_pending_io ();
} else {
if (state == OPEN)
@@ -1006,6 +1235,9 @@ namespace Frida {
private void on_message (int type, Bytes message) {
with_state_lock (() => {
recv_queue.append (message.get_data ());
#if HAVE_SOUP_WEBSOCKET_BACKPRESSURE
websocket.pause_input ();
#endif
update_pending_io ();
});
}
+26 -1
View File
@@ -288,7 +288,7 @@ namespace Frida {
public JsonObjectReader (string json) throws Error {
try {
reader = new Json.Reader (Json.from_string (json));
reader = make_json_reader (json);
} catch (GLib.Error e) {
throw new Error.INVALID_ARGUMENT ("%s", e.message);
}
@@ -404,4 +404,29 @@ namespace Frida {
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;
}
}
+860
View File
@@ -0,0 +1,860 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import annotations
import argparse
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Optional, Tuple
MAX_ARGS = 9
@dataclass(frozen=True)
class SyscallArg:
type: str
name: str
@dataclass(frozen=True)
class SyscallSignature:
name: str
args: Tuple[SyscallArg, ...]
MACH_TRAP_INDEXED_CALL_RE = re.compile(r"/\*\s*(?P<idx>\d+)\s*\*/\s*MACH_TRAP\s*\(")
MACH_TRAPS_IFNDEF_KERNEL_RE = re.compile(r"^\s*#ifndef\s+KERNEL\b", re.MULTILINE)
MACH_TRAPS_ELSE_KERNEL_RE = re.compile(r"^\s*#else\b.*\bKERNEL\b", re.MULTILINE)
USER_EXTERN_PROTO_RE = re.compile(
r"^\s*extern\s+(?P<ret>.+?)\s+(?P<fn>[A-Za-z_]\w*)\s*\((?P<args>.*?)\)\s*;",
re.MULTILINE | re.DOTALL,
)
KERNEL_STRUCT_RE = re.compile(
r"\bstruct\s+(?P<name>[A-Za-z_]\w*)\s*\{\s*(?P<body>.*?)\s*\}\s*;",
re.DOTALL,
)
KERNEL_TRAP_EXTERN_RE = re.compile(
r"^\s*(?:extern\s+)?(?P<ret>.+?)\s+(?P<fn>[A-Za-z_]\w*)\s*\(\s*struct\s+(?P<args_struct>[A-Za-z_]\w*)\s*\*\s*args\s*\)\s*;",
re.MULTILINE | re.DOTALL,
)
PAD_ARG_RE = re.compile(
r"\bPAD_ARG_\s*\(\s*(?P<ty>[^,]+?)\s*,\s*(?P<name>[^)]+?)\s*\)\s*;",
)
PLAIN_FIELD_RE = re.compile(r"^\s*(?P<ty>.+?)\s+(?P<name>[A-Za-z_]\w*)\s*;\s*$")
BSD_MASTER_ENTRY_RE = re.compile(
r"^\s*(?P<nr>\d+)\s+(?P<audit>\S+)\s+(?P<files>\S+)\s+\{\s*(?P<proto>[^}]+?)\s*\}\s*(?:\{.*\})?\s*$"
)
BSD_MASTER_PROTO_RE = re.compile(
r"^\s*(?P<ret>.+?)\s+(?P<fn>[A-Za-z_]\w*)\s*\((?P<args>.*)\)\s*(?P<attrs>.*)$",
re.DOTALL,
)
LINE_COMMENT_RE = re.compile(r"//.*$")
BLOCK_COMMENT_RE = re.compile(r"/\*[^*]*\*+(?:[^/*][^*]*\*+)*/")
TRAILING_ARRAY_SUFFIX_RE = re.compile(r"\[[^\]]*\]\s*$")
POINTER_SPACING_RE = re.compile(r"\s*\*\s*")
PARAM_NAME_RE = re.compile(r"[A-Za-z_]\w*$")
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--kernel-tree", required=True)
ap.add_argument("--out-vala", required=True)
ap.add_argument("--out-c", required=True)
args = ap.parse_args()
kernel_tree = Path(args.kernel_tree).resolve()
if not kernel_tree.exists():
raise SystemExit(f"--kernel-tree does not exist: {kernel_tree}")
mach_numbers, mach_sigs = load_mach_traps(kernel_tree)
bsd_numbers, bsd_sigs = load_bsd_syscalls_master(kernel_tree)
vala = render_vala(
mach_numbers=mach_numbers,
bsd_numbers=bsd_numbers,
)
c_src = render_c_source(
mach_numbers=mach_numbers,
bsd_numbers=bsd_numbers,
mach_sigs=mach_sigs,
bsd_sigs=bsd_sigs,
)
out_vala = Path(args.out_vala)
out_c = Path(args.out_c)
out_vala.parent.mkdir(parents=True, exist_ok=True)
out_c.parent.mkdir(parents=True, exist_ok=True)
out_vala.write_text(vala, encoding="utf-8")
out_c.write_text(c_src, encoding="utf-8")
return 0
def load_mach_traps(
kernel_tree: Path,
) -> Tuple[Dict[int, str], Dict[str, SyscallSignature]]:
table_path = kernel_tree / "osfmk" / "kern" / "syscall_sw.c"
if not table_path.exists():
raise RuntimeError(f"Missing Mach trap table source: {table_path}")
header_path = kernel_tree / "osfmk" / "mach" / "mach_traps.h"
if not header_path.exists():
raise RuntimeError(f"Missing Mach traps header: {header_path}")
table_text = table_path.read_text(encoding="utf-8", errors="ignore")
trap_routines = parse_mach_trap_table(table_text, table_path)
header_text = header_path.read_text(encoding="utf-8", errors="ignore")
userspace_text, kernel_text = split_mach_traps_header(header_text, header_path)
userspace_protos = parse_userspace_mach_traps_prototypes(
userspace_text, header_path
)
kernel_argmap = parse_kernel_mach_traps_args(kernel_text, header_path)
numbers: Dict[int, str] = {}
sigs: Dict[str, SyscallSignature] = {}
for index, routine in trap_routines:
if routine == "kern_invalid":
continue
nr = -index
public_name = normalize_mach_name(routine)
if public_name == "":
raise RuntimeError(f"{table_path}: empty normalized name from {routine!r}")
if nr in numbers:
raise RuntimeError(f"{table_path}: duplicate mach trap number {nr}")
if public_name in sigs:
raise RuntimeError(
f"{table_path}: normalized Mach name collision for {public_name!r}"
)
args = resolve_mach_trap_signature(
routine=routine,
userspace_protos=userspace_protos,
kernel_argmap=kernel_argmap,
table_path=table_path,
)
if len(args) > MAX_ARGS:
raise RuntimeError(
f"{table_path}: trap {routine!r} has {len(args)} args; max supported is {MAX_ARGS}"
)
numbers[nr] = public_name
sigs[public_name] = SyscallSignature(name=public_name, args=tuple(args))
return numbers, sigs
def parse_mach_trap_table(text: str, path: Path) -> List[Tuple[int, str]]:
anchor = text.find("mach_trap_table")
if anchor == -1:
raise RuntimeError(f"{path}: mach_trap_table not found")
brace = text.find("{", anchor)
if brace == -1:
raise RuntimeError(f"{path}: mach_trap_table '{{' not found")
payload, _ = extract_brace_payload(text, brace, path)
by_index: Dict[int, str] = {}
off = 0
while True:
m = MACH_TRAP_INDEXED_CALL_RE.search(payload, off)
if m is None:
break
idx = int(m.group("idx"), 10)
open_paren = m.end(0) - 1
args_blob, end_off = extract_parentheses_payload(payload, open_paren, path)
parts = [
a.strip() for a in split_top_level_commas(args_blob) if a.strip() != ""
]
if len(parts) < 1:
raise RuntimeError(f"{path}: bad MACH_TRAP() entry: {args_blob!r}")
routine = parts[0]
prev = by_index.get(idx)
if prev is None:
by_index[idx] = routine
else:
if prev == routine:
pass
elif prev == "kern_invalid" and routine != "kern_invalid":
by_index[idx] = routine
elif routine == "kern_invalid" and prev != "kern_invalid":
pass
else:
raise RuntimeError(
f"{path}: conflicting MACH_TRAP entries for index {idx}: {prev!r} vs {routine!r}"
)
off = end_off
return sorted(by_index.items(), key=lambda kv: kv[0])
def split_mach_traps_header(text: str, path: Path) -> Tuple[str, str]:
m_start = MACH_TRAPS_IFNDEF_KERNEL_RE.search(text)
if m_start is None:
raise RuntimeError(f"{path}: missing '#ifndef KERNEL'")
m_else = MACH_TRAPS_ELSE_KERNEL_RE.search(text, m_start.end())
if m_else is None:
raise RuntimeError(f"{path}: missing '#else ... KERNEL ...'")
userspace = text[m_start.end():m_else.start()]
kernel = text[m_else.end():]
return userspace, kernel
def parse_userspace_mach_traps_prototypes(
userspace_text: str, path: Path
) -> Dict[str, Tuple[SyscallArg, ...]]:
protos: Dict[str, Tuple[SyscallArg, ...]] = {}
for m in USER_EXTERN_PROTO_RE.finditer(userspace_text):
fn = m.group("fn").strip()
args_blob = m.group("args").strip()
ret_blob = strip_c_comments(m.group("ret")).strip()
if ret_blob == "":
raise RuntimeError(f"{path}: empty return type for {fn!r}")
line_no = line_number_at_offset(userspace_text, m.start())
args = parse_c_param_list(args_blob, path, line_no)
cur = tuple(args)
prev = protos.get(fn)
if prev is not None and prev != cur:
raise RuntimeError(f"{path}:{line_no}: conflicting prototype for {fn!r}")
protos[fn] = cur
return protos
def parse_kernel_mach_traps_args(
kernel_text: str, path: Path
) -> Dict[str, Tuple[SyscallArg, ...]]:
text = strip_c_comments(kernel_text)
structs: Dict[str, str] = {}
for m in KERNEL_STRUCT_RE.finditer(text):
name = m.group("name").strip()
body = m.group("body")
structs[name] = body
fn_to_struct: Dict[str, str] = {}
for m in KERNEL_TRAP_EXTERN_RE.finditer(text):
fn = m.group("fn").strip()
args_struct = m.group("args_struct").strip()
fn_to_struct[fn] = args_struct
out: Dict[str, Tuple[SyscallArg, ...]] = {}
for fn, args_struct in fn_to_struct.items():
body = structs.get(args_struct)
if body is None:
continue
args = parse_mach_args_struct_body(body, path)
out[fn] = tuple(args)
return out
def parse_mach_args_struct_body(body: str, path: Path) -> List[SyscallArg]:
args: List[SyscallArg] = []
body = strip_c_comments(body)
for raw_ln in body.splitlines():
ln = raw_ln.strip()
if ln == "" or ln.startswith("#"):
continue
if "PAD_ARG_8" in ln:
continue
m = PAD_ARG_RE.search(ln)
if m is not None:
ty = normalize_whitespace(m.group("ty").strip())
name = normalize_whitespace(m.group("name").strip())
if ty == "" or name == "":
raise RuntimeError(f"{path}: bad PAD_ARG_ in args struct: {raw_ln!r}")
args.append(SyscallArg(type=ty, name=name))
continue
m = PLAIN_FIELD_RE.match(ln)
if m is not None:
ty = normalize_whitespace(m.group("ty").strip())
name = m.group("name").strip()
if ty == "" or name == "":
raise RuntimeError(f"{path}: bad field in args struct: {raw_ln!r}")
args.append(SyscallArg(type=ty, name=name))
continue
if len(args) == 1 and args[0].name == "dummy":
return []
return args
def resolve_mach_trap_signature(
*,
routine: str,
userspace_protos: Dict[str, Tuple[SyscallArg, ...]],
kernel_argmap: Dict[str, Tuple[SyscallArg, ...]],
table_path: Path,
) -> List[SyscallArg]:
def get_userspace(name: str) -> Optional[Tuple[SyscallArg, ...]]:
return userspace_protos.get(name)
def get_kernel(name: str) -> Optional[Tuple[SyscallArg, ...]]:
return kernel_argmap.get(name)
proto_args = get_userspace(routine)
if proto_args is None and routine.endswith("_trap"):
proto_args = get_userspace(routine[: -len("_trap")])
if proto_args is not None:
return list(proto_args)
proto_args = get_kernel(routine)
if proto_args is None and routine.endswith("_trap"):
proto_args = get_kernel(routine[: -len("_trap")])
if proto_args is None:
raise RuntimeError(
f"{table_path}: missing userspace prototype for {routine!r}, and no kernel extern(struct *_args *args) mapping found"
)
return list(proto_args)
def normalize_mach_name(routine: str) -> str:
if routine.startswith("_"):
routine = routine[1:]
if routine.startswith("kernelrpc_"):
routine = routine[len("kernelrpc_"):]
if routine.endswith("_trap"):
routine = routine[: -len("_trap")]
return routine
def load_bsd_syscalls_master(
kernel_tree: Path,
) -> Tuple[Dict[int, str], Dict[str, SyscallSignature]]:
path = kernel_tree / "bsd" / "kern" / "syscalls.master"
if not path.exists():
raise RuntimeError(f"Missing BSD master file: {path}")
numbers: Dict[int, str] = {}
sigs: Dict[str, SyscallSignature] = {}
lines = path.read_text(encoding="utf-8", errors="ignore").splitlines()
stack: List[bool] = []
active = True
for line_no, raw in enumerate(lines, start=1):
line = raw.rstrip("\n")
stripped = line.lstrip()
if (
stripped.startswith("#if ")
or stripped.startswith("#ifdef ")
or stripped.startswith("#ifndef ")
):
stack.append(active)
continue
if stripped.startswith("#else"):
if not stack:
raise RuntimeError(f"{path}:{line_no}: #else without #if")
active = False
continue
if stripped.startswith("#endif"):
if not stack:
raise RuntimeError(f"{path}:{line_no}: #endif without #if")
active = stack.pop()
continue
if not active:
continue
m = BSD_MASTER_ENTRY_RE.match(line)
if m is None:
continue
nr = int(m.group("nr"), 10)
proto_blob = strip_c_comments(m.group("proto")).strip()
mp = BSD_MASTER_PROTO_RE.match(proto_blob)
if mp is None:
raise RuntimeError(
f"{path}:{line_no}: cannot parse prototype: {proto_blob!r}"
)
fn = mp.group("fn").strip()
args_blob = mp.group("args").strip()
if fn in {"nosys", "enosys"}:
continue
public_name = normalize_bsd_name(fn)
if public_name == "":
raise RuntimeError(f"{path}:{line_no}: empty normalized name from {fn!r}")
if nr in numbers:
raise RuntimeError(f"{path}:{line_no}: duplicate syscall number {nr}")
if public_name in sigs:
raise RuntimeError(
f"{path}:{line_no}: duplicate syscall name {public_name!r}"
)
args = parse_c_param_list(args_blob, path, line_no)
if len(args) > MAX_ARGS:
raise RuntimeError(
f"{path}:{line_no}: syscall {public_name!r} has {len(args)} args; max supported is {MAX_ARGS}"
)
numbers[nr] = public_name
sigs[public_name] = SyscallSignature(name=public_name, args=tuple(args))
return numbers, sigs
def normalize_bsd_name(fn: str) -> str:
if fn.startswith("sys_"):
return fn[len("sys_"):]
if fn.startswith("__"):
return fn[2:]
return fn
def strip_c_comments(s: str) -> str:
s = LINE_COMMENT_RE.sub("", s)
while True:
new_s = BLOCK_COMMENT_RE.sub("", s)
if new_s == s:
break
s = new_s
return s
def split_top_level_commas(text: str) -> List[str]:
parts: List[str] = []
buf: List[str] = []
depth = 0
in_string = False
string_quote = ""
cursor = 0
while cursor < len(text):
ch = text[cursor]
if in_string:
buf.append(ch)
if ch == "\\" and cursor + 1 < len(text):
buf.append(text[cursor + 1])
cursor += 2
continue
if ch == string_quote:
in_string = False
cursor += 1
continue
if ch in ("'", '"'):
in_string = True
string_quote = ch
buf.append(ch)
cursor += 1
continue
if ch == "(":
depth += 1
elif ch == ")":
depth = max(0, depth - 1)
if ch == "," and depth == 0:
parts.append("".join(buf).strip())
buf = []
cursor += 1
continue
buf.append(ch)
cursor += 1
parts.append("".join(buf).strip())
return parts
def parse_c_param_list(args_blob: str, path: Path, line_no: int) -> List[SyscallArg]:
raw = strip_c_comments(args_blob).strip()
if raw == "" or raw == "void":
return []
raw_params = [p.strip() for p in split_top_level_commas(raw) if p.strip() != ""]
if len(raw_params) == 1 and raw_params[0] == "void":
return []
args: List[SyscallArg] = []
for idx, p in enumerate(raw_params):
ty, name = split_param_decl(p, path, line_no)
if name is None:
name = f"arg{idx}"
if ty == "user_addr_t" and name in {"attrname", "link", "path"}:
ty = "char *"
args.append(SyscallArg(type=ty, name=name))
return args
def split_param_decl(decl: str, path: Path, line_no: int) -> Tuple[str, Optional[str]]:
d = strip_c_comments(decl).strip()
if d == "" or d == "void":
raise RuntimeError(f"{path}:{line_no}: bad parameter decl: {decl!r}")
tokens = _tokenize_decl(d)
ty_tokens, name = _split_type_and_name(tokens)
ty = _format_type(ty_tokens)
if not ty:
raise RuntimeError(
f"{path}:{line_no}: cannot parse parameter type from {decl!r}"
)
return ty, _camel_to_snake(name) if name else None
def _tokenize_decl(s: str) -> list[str]:
out = []
for c in s:
out.append(" * " if c == "*" else c)
return "".join(out).split()
def _split_type_and_name(tokens: list[str]) -> Tuple[list[str], Optional[str]]:
if not tokens:
return [], None
last = tokens[-1]
if last == "*":
return tokens, None
stars, remainder = _split_leading_stars(last)
if stars:
if _is_identifier(remainder):
return tokens[:-1] + ["*"] * stars, remainder
return tokens, None
if _is_identifier(last):
return tokens[:-1], last
return tokens, None
def _format_type(tokens: list[str]) -> str:
formatted: list[str] = []
i = 0
while i < len(tokens):
if tokens[i] != "*":
formatted.append(tokens[i])
i += 1
continue
j = i
while j < len(tokens) and tokens[j] == "*":
j += 1
formatted.append("*" * (j - i))
i = j
return " ".join(formatted).strip()
def _split_leading_stars(token: str) -> Tuple[int, str]:
i = 0
while i < len(token) and token[i] == "*":
i += 1
return i, token[i:]
def _is_identifier(s: str) -> bool:
if not s:
return False
if not (s[0].isalpha() or s[0] == "_"):
return False
return all(c.isalnum() or c == "_" for c in s)
def _camel_to_snake(name: str) -> str:
out = []
for i, c in enumerate(name):
if (
c.isupper()
and i > 0
and (name[i - 1].islower() or (i + 1 < len(name) and name[i + 1].islower()))
):
out.append("_")
out.append(c.lower())
return "".join(out)
def normalize_whitespace(s: str) -> str:
return " ".join(s.split())
def line_number_at_offset(text: str, offset: int) -> int:
return text.count("\n", 0, offset) + 1
def extract_parentheses_payload(
text: str, open_paren_offset: int, path: Path
) -> Tuple[str, int]:
if (
open_paren_offset < 0
or open_paren_offset >= len(text)
or text[open_paren_offset] != "("
):
raise RuntimeError(f"{path}: expected '(' at offset {open_paren_offset}")
depth = 0
cursor = open_paren_offset
start = open_paren_offset + 1
in_string = False
string_quote = ""
while cursor < len(text):
ch = text[cursor]
if in_string:
if ch == "\\":
cursor += 2
continue
if ch == string_quote:
in_string = False
cursor += 1
continue
if ch in ("'", '"'):
in_string = True
string_quote = ch
cursor += 1
continue
if ch == "(":
depth += 1
elif ch == ")":
depth -= 1
if depth == 0:
return text[start:cursor], cursor + 1
cursor += 1
raise RuntimeError(f"{path}: unterminated parentheses payload")
def extract_brace_payload(
text: str, open_brace_offset: int, path: Path
) -> Tuple[str, int]:
if (
open_brace_offset < 0
or open_brace_offset >= len(text)
or text[open_brace_offset] != "{"
):
raise RuntimeError(f"{path}: expected '{{' at offset {open_brace_offset}")
depth = 0
cursor = open_brace_offset
start = open_brace_offset + 1
in_string = False
string_quote = ""
while cursor < len(text):
ch = text[cursor]
if in_string:
if ch == "\\":
cursor += 2
continue
if ch == string_quote:
in_string = False
cursor += 1
continue
if ch in ("'", '"'):
in_string = True
string_quote = ch
cursor += 1
continue
if ch == "{":
depth += 1
elif ch == "}":
depth -= 1
if depth == 0:
return text[start:cursor], cursor + 1
cursor += 1
raise RuntimeError(f"{path}: unterminated brace payload")
def render_vala(*, mach_numbers: Dict[int, str], bsd_numbers: Dict[int, str]) -> str:
out: List[str] = []
out += [
"// AUTO-GENERATED FILE. DO NOT EDIT.\n",
"\n",
"namespace Frida {\n",
"\tpublic extern unowned XnuSyscallSignature[] get_xnu_mach_traps ();\n",
"\tpublic extern unowned XnuSyscallSignature[] get_xnu_bsd_syscalls ();\n",
"\n",
"\tpublic struct XnuSyscallSignature {\n",
"\t\tpublic int nr;\n",
"\t\tpublic string name;\n",
"\t\tpublic uint8 nargs;\n",
f"\t\tpublic XnuSyscallArg args[{MAX_ARGS}];\n",
"\t}\n",
"\n",
"\tpublic struct XnuSyscallArg {\n",
"\t\tpublic string? type;\n",
"\t\tpublic string? name;\n",
"\t}\n",
"\n",
"\tpublic enum XnuMachTrap {\n",
]
for nr, name in sorted(mach_numbers.items(), key=lambda kv: kv[0], reverse=True):
out += [f"\t\t{name.upper()} = {nr},\n"]
out += [
"\t}\n",
"\n",
"\tpublic enum XnuBsdSyscall {\n",
]
for nr, name in sorted(bsd_numbers.items(), key=lambda kv: kv[0]):
out += [f"\t\t{name.upper()} = {nr},\n"]
out += [
"\t}\n",
"}\n",
]
return "".join(out)
def render_c_source(
*,
mach_numbers: Dict[int, str],
bsd_numbers: Dict[int, str],
mach_sigs: Dict[str, SyscallSignature],
bsd_sigs: Dict[str, SyscallSignature],
) -> str:
out: List[str] = []
out += [
"/* AUTO-GENERATED FILE. DO NOT EDIT. */\n",
"\n",
'#include "frida-base.h"\n',
"\n",
]
emit_c_signature_table(
out,
c_var="frida_xnu_mach_traps",
numbers=mach_numbers,
sigs=mach_sigs,
reverse=True,
)
emit_c_signature_table(
out,
c_var="frida_xnu_bsd_syscalls",
numbers=bsd_numbers,
sigs=bsd_sigs,
)
emit_c_table_getter(
out, c_func="frida_get_xnu_mach_traps", c_var="frida_xnu_mach_traps"
)
out += ["\n"]
emit_c_table_getter(
out, c_func="frida_get_xnu_bsd_syscalls", c_var="frida_xnu_bsd_syscalls"
)
return "".join(out)
def emit_c_signature_table(
out: List[str],
*,
c_var: str,
numbers: Dict[int, str],
sigs: Dict[str, SyscallSignature],
reverse: bool = False,
) -> None:
out += [
f"static const FridaXnuSyscallSignature {c_var}[] =\n",
"{\n",
]
for nr, name in sorted(numbers.items(), key=lambda kv: kv[0], reverse=reverse):
sig = sigs[name]
args_parts: List[str] = []
for a in sig.args:
args_parts += [f"{{ {c_escape(a.type)}, {c_escape(a.name)} }}"]
args_blob = ", ".join(args_parts)
if args_blob != "":
out += [
f" {{ {nr}, {c_escape(name)}, {len(sig.args)}, {{ {args_blob} }} }},\n"
]
else:
out += [f" {{ {nr}, {c_escape(name)}, 0, {{}} }},\n"]
out += [
"};\n",
"\n",
]
def emit_c_table_getter(out: List[str], *, c_func: str, c_var: str) -> None:
out += [
"FridaXnuSyscallSignature *\n",
f"{c_func} (int * len)\n",
"{\n",
" if (len != NULL)\n",
f" *len = G_N_ELEMENTS ({c_var});\n",
f" return (FridaXnuSyscallSignature *) {c_var};\n",
"}\n",
]
def c_escape(s: str) -> str:
return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"'
if __name__ == "__main__":
raise SystemExit(main())
+542
View File
@@ -0,0 +1,542 @@
/* AUTO-GENERATED FILE. DO NOT EDIT. */
#include "frida-base.h"
static const FridaXnuSyscallSignature frida_xnu_mach_traps[] =
{
{ -10, "mach_vm_allocate", 4, { { "mach_port_name_t", "target" }, { "mach_vm_offset_t *", "addr" }, { "mach_vm_size_t", "size" }, { "int", "flags" } } },
{ -11, "mach_vm_purgable_control", 4, { { "mach_port_name_t", "target" }, { "mach_vm_offset_t", "address" }, { "vm_purgable_t", "control" }, { "int *", "state" } } },
{ -12, "mach_vm_deallocate", 3, { { "mach_port_name_t", "target" }, { "mach_vm_address_t", "address" }, { "mach_vm_size_t", "size" } } },
{ -13, "task_dyld_process_info_notify_get", 2, { { "mach_port_name_array_t", "names_addr" }, { "natural_t *", "names_count_addr" } } },
{ -14, "mach_vm_protect", 5, { { "mach_port_name_t", "target" }, { "mach_vm_address_t", "address" }, { "mach_vm_size_t", "size" }, { "boolean_t", "set_maximum" }, { "vm_prot_t", "new_protection" } } },
{ -15, "mach_vm_map", 6, { { "mach_port_name_t", "target" }, { "mach_vm_offset_t *", "address" }, { "mach_vm_size_t", "size" }, { "mach_vm_offset_t", "mask" }, { "int", "flags" }, { "vm_prot_t", "cur_protection" } } },
{ -16, "mach_port_allocate", 3, { { "mach_port_name_t", "target" }, { "mach_port_right_t", "right" }, { "mach_port_name_t *", "name" } } },
{ -18, "mach_port_deallocate", 2, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" } } },
{ -19, "mach_port_mod_refs", 4, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "mach_port_right_t", "right" }, { "mach_port_delta_t", "delta" } } },
{ -20, "mach_port_move_member", 3, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "member" }, { "mach_port_name_t", "after" } } },
{ -21, "mach_port_insert_right", 4, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "mach_port_name_t", "poly" }, { "mach_msg_type_name_t", "poly_poly" } } },
{ -22, "mach_port_insert_member", 3, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "mach_port_name_t", "pset" } } },
{ -23, "mach_port_extract_member", 3, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "mach_port_name_t", "pset" } } },
{ -24, "mach_port_construct", 4, { { "mach_port_name_t", "target" }, { "mach_port_options_t *", "options" }, { "uint64_t", "context" }, { "mach_port_name_t *", "name" } } },
{ -25, "mach_port_destruct", 4, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "mach_port_delta_t", "srdelta" }, { "uint64_t", "guard" } } },
{ -26, "mach_reply_port", 0, {} },
{ -27, "thread_self", 0, {} },
{ -28, "task_self", 0, {} },
{ -29, "host_self", 0, {} },
{ -31, "mach_msg", 7, { { "mach_msg_header_t *", "msg" }, { "mach_msg_option_t", "option" }, { "mach_msg_size_t", "send_size" }, { "mach_msg_size_t", "rcv_size" }, { "mach_port_name_t", "rcv_name" }, { "mach_msg_timeout_t", "timeout" }, { "mach_port_name_t", "notify" } } },
{ -32, "mach_msg_overwrite", 9, { { "mach_msg_header_t *", "msg" }, { "mach_msg_option_t", "option" }, { "mach_msg_size_t", "send_size" }, { "mach_msg_size_t", "rcv_size" }, { "mach_port_name_t", "rcv_name" }, { "mach_msg_timeout_t", "timeout" }, { "mach_msg_priority_t", "priority" }, { "mach_msg_header_t *", "rcv_msg" }, { "mach_msg_size_t", "rcv_limit" } } },
{ -33, "semaphore_signal", 1, { { "mach_port_name_t", "signal_name" } } },
{ -34, "semaphore_signal_all", 1, { { "mach_port_name_t", "signal_name" } } },
{ -35, "semaphore_signal_thread", 2, { { "mach_port_name_t", "signal_name" }, { "mach_port_name_t", "thread_name" } } },
{ -36, "semaphore_wait", 1, { { "mach_port_name_t", "wait_name" } } },
{ -37, "semaphore_wait_signal", 2, { { "mach_port_name_t", "wait_name" }, { "mach_port_name_t", "signal_name" } } },
{ -38, "semaphore_timedwait", 3, { { "mach_port_name_t", "wait_name" }, { "unsigned int", "sec" }, { "clock_res_t", "nsec" } } },
{ -39, "semaphore_timedwait_signal", 4, { { "mach_port_name_t", "wait_name" }, { "mach_port_name_t", "signal_name" }, { "unsigned int", "sec" }, { "clock_res_t", "nsec" } } },
{ -40, "mach_port_get_attributes", 5, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "mach_port_flavor_t", "flavor" }, { "mach_port_info_t", "port_info_out" }, { "mach_msg_type_number_t *", "port_info_out_cnt" } } },
{ -41, "mach_port_guard", 4, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "uint64_t", "guard" }, { "boolean_t", "strict" } } },
{ -42, "mach_port_unguard", 3, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "uint64_t", "guard" } } },
{ -43, "mach_generate_activity_id", 3, { { "mach_port_name_t", "target" }, { "int", "count" }, { "uint64_t *", "activity_id" } } },
{ -44, "task_name_for_pid", 3, { { "mach_port_name_t", "target_tport" }, { "int", "pid" }, { "mach_port_name_t *", "tn" } } },
{ -45, "task_for_pid", 3, { { "mach_port_name_t", "target_tport" }, { "int", "pid" }, { "mach_port_name_t *", "t" } } },
{ -46, "pid_for_task", 2, { { "mach_port_name_t", "t" }, { "int *", "x" } } },
{ -47, "mach_msg2", 8, { { "void *", "data" }, { "mach_msg_option64_t", "options" }, { "uint64_t", "msgh_bits_and_send_size" }, { "uint64_t", "msgh_remote_and_local_port" }, { "uint64_t", "msgh_voucher_and_id" }, { "uint64_t", "desc_count_and_rcv_name" }, { "uint64_t", "rcv_size_and_priority" }, { "uint64_t", "timeout" } } },
{ -48, "macx_swapon", 4, { { "uint64_t", "filename" }, { "int", "flags" }, { "int", "size" }, { "int", "priority" } } },
{ -49, "macx_swapoff", 2, { { "uint64_t", "filename" }, { "int", "flags" } } },
{ -50, "thread_get_special_reply_port", 0, {} },
{ -51, "macx_triggers", 4, { { "int", "hi_water" }, { "int", "low_water" }, { "int", "flags" }, { "mach_port_t", "alert_port" } } },
{ -52, "macx_backing_store_suspend", 1, { { "boolean_t", "suspend" } } },
{ -53, "macx_backing_store_recovery", 1, { { "int", "pid" } } },
{ -58, "pfz_exit", 0, {} },
{ -59, "swtch_pri", 1, { { "int", "pri" } } },
{ -60, "swtch", 0, {} },
{ -61, "thread_switch", 3, { { "mach_port_name_t", "thread_name" }, { "int", "option" }, { "mach_msg_timeout_t", "option_time" } } },
{ -62, "clock_sleep", 5, { { "mach_port_name_t", "clock_name" }, { "sleep_type_t", "sleep_type" }, { "int", "sleep_sec" }, { "int", "sleep_nsec" }, { "mach_timespec_t *", "wakeup_time" } } },
{ -63, "mach_vm_reclaim_update_kernel_accounting", 2, { { "mach_port_name_t", "target_tport" }, { "uint64_t *", "bytes_reclaimed" } } },
{ -70, "host_create_mach_voucher", 4, { { "mach_port_name_t", "host" }, { "mach_voucher_attr_raw_recipe_array_t", "recipes" }, { "int", "recipes_size" }, { "mach_port_name_t *", "voucher" } } },
{ -72, "mach_voucher_extract_attr_recipe", 4, { { "mach_port_name_t", "voucher_name" }, { "mach_voucher_attr_key_t", "key" }, { "mach_voucher_attr_raw_recipe_t", "recipe" }, { "mach_msg_type_number_t *", "recipe_size" } } },
{ -76, "mach_port_type", 3, { { "ipc_space_t", "task" }, { "mach_port_name_t", "name" }, { "mach_port_type_t *", "ptype" } } },
{ -77, "mach_port_request_notification", 7, { { "ipc_space_t", "task" }, { "mach_port_name_t", "name" }, { "mach_msg_id_t", "msgid" }, { "mach_port_mscount_t", "sync" }, { "mach_port_name_t", "notify" }, { "mach_msg_type_name_t", "notify_poly" }, { "mach_port_name_t *", "previous" } } },
{ -88, "exclaves_ctl", 8, { { "mach_port_name_t", "name" }, { "uint32_t", "operation_and_flags" }, { "uint64_t", "identifier" }, { "mach_vm_address_t", "buffer" }, { "mach_vm_size_t", "size" }, { "mach_vm_size_t", "size2" }, { "mach_vm_size_t", "offset" }, { "mach_vm_address_t", "status" } } },
{ -89, "mach_timebase_info", 1, { { "user_addr_t", "info" } } },
{ -90, "mach_wait_until", 1, { { "uint64_t", "deadline" } } },
{ -91, "mk_timer_create", 0, {} },
{ -92, "mk_timer_destroy", 1, { { "mach_port_name_t", "name" } } },
{ -93, "mk_timer_arm", 2, { { "mach_port_name_t", "name" }, { "uint64_t", "expire_time" } } },
{ -94, "mk_timer_cancel", 2, { { "mach_port_name_t", "name" }, { "user_addr_t", "result_time" } } },
{ -95, "mk_timer_arm_leeway", 4, { { "mach_port_name_t", "name" }, { "uint64_t", "mk_timer_flags" }, { "uint64_t", "expire_time" }, { "uint64_t", "mk_leeway" } } },
{ -96, "debug_control_port_for_pid", 3, { { "mach_port_name_t", "target_tport" }, { "int", "pid" }, { "mach_port_name_t *", "t" } } },
{ -100, "iokit_user_client", 8, { { "void *", "userClientRef" }, { "uint32_t", "index" }, { "void *", "p1" }, { "void *", "p2" }, { "void *", "p3" }, { "void *", "p4" }, { "void *", "p5" }, { "void *", "p6" } } },
};
static const FridaXnuSyscallSignature frida_xnu_bsd_syscalls[] =
{
{ 1, "exit", 1, { { "int", "rval" } } },
{ 2, "fork", 0, {} },
{ 3, "read", 3, { { "int", "fd" }, { "user_addr_t", "cbuf" }, { "user_size_t", "nbyte" } } },
{ 4, "write", 3, { { "int", "fd" }, { "user_addr_t", "cbuf" }, { "user_size_t", "nbyte" } } },
{ 5, "open", 3, { { "char *", "path" }, { "int", "flags" }, { "int", "mode" } } },
{ 6, "close", 1, { { "int", "fd" } } },
{ 7, "wait4", 4, { { "int", "pid" }, { "user_addr_t", "status" }, { "int", "options" }, { "user_addr_t", "rusage" } } },
{ 9, "link", 2, { { "char *", "path" }, { "char *", "link" } } },
{ 10, "unlink", 1, { { "char *", "path" } } },
{ 12, "chdir", 1, { { "char *", "path" } } },
{ 13, "fchdir", 1, { { "int", "fd" } } },
{ 14, "mknod", 3, { { "char *", "path" }, { "int", "mode" }, { "int", "dev" } } },
{ 15, "chmod", 2, { { "char *", "path" }, { "int", "mode" } } },
{ 16, "chown", 3, { { "char *", "path" }, { "int", "uid" }, { "int", "gid" } } },
{ 18, "getfsstat", 3, { { "user_addr_t", "buf" }, { "int", "bufsize" }, { "int", "flags" } } },
{ 20, "getpid", 0, {} },
{ 23, "setuid", 1, { { "uid_t", "uid" } } },
{ 24, "getuid", 0, {} },
{ 25, "geteuid", 0, {} },
{ 26, "ptrace", 4, { { "int", "req" }, { "pid_t", "pid" }, { "caddr_t", "addr" }, { "int", "data" } } },
{ 27, "recvmsg", 3, { { "int", "s" }, { "struct msghdr *", "msg" }, { "int", "flags" } } },
{ 28, "sendmsg", 3, { { "int", "s" }, { "caddr_t", "msg" }, { "int", "flags" } } },
{ 29, "recvfrom", 6, { { "int", "s" }, { "void *", "buf" }, { "size_t", "len" }, { "int", "flags" }, { "struct sockaddr *", "from" }, { "int *", "fromlenaddr" } } },
{ 30, "accept", 3, { { "int", "s" }, { "caddr_t", "name" }, { "socklen_t *", "anamelen" } } },
{ 31, "getpeername", 3, { { "int", "fdes" }, { "caddr_t", "asa" }, { "socklen_t *", "alen" } } },
{ 32, "getsockname", 3, { { "int", "fdes" }, { "caddr_t", "asa" }, { "socklen_t *", "alen" } } },
{ 33, "access", 2, { { "char *", "path" }, { "int", "flags" } } },
{ 34, "chflags", 2, { { "char *", "path" }, { "int", "flags" } } },
{ 35, "fchflags", 2, { { "int", "fd" }, { "int", "flags" } } },
{ 36, "sync", 0, {} },
{ 37, "kill", 3, { { "int", "pid" }, { "int", "signum" }, { "int", "posix" } } },
{ 38, "crossarch_trap", 1, { { "uint32_t", "name" } } },
{ 39, "getppid", 0, {} },
{ 41, "dup", 1, { { "u_int", "fd" } } },
{ 42, "pipe", 0, {} },
{ 43, "getegid", 0, {} },
{ 46, "sigaction", 3, { { "int", "signum" }, { "struct __sigaction *", "nsa" }, { "struct sigaction *", "osa" } } },
{ 47, "getgid", 0, {} },
{ 48, "sigprocmask", 3, { { "int", "how" }, { "user_addr_t", "mask" }, { "user_addr_t", "omask" } } },
{ 49, "getlogin", 2, { { "char *", "namebuf" }, { "u_int", "namelen" } } },
{ 50, "setlogin", 1, { { "char *", "namebuf" } } },
{ 51, "acct", 1, { { "char *", "path" } } },
{ 52, "sigpending", 1, { { "struct sigvec *", "osv" } } },
{ 53, "sigaltstack", 2, { { "struct sigaltstack *", "nss" }, { "struct sigaltstack *", "oss" } } },
{ 54, "ioctl", 3, { { "int", "fd" }, { "u_long", "com" }, { "caddr_t", "data" } } },
{ 55, "reboot", 2, { { "int", "opt" }, { "char *", "msg" } } },
{ 56, "revoke", 1, { { "char *", "path" } } },
{ 57, "symlink", 2, { { "char *", "path" }, { "char *", "link" } } },
{ 58, "readlink", 3, { { "char *", "path" }, { "char *", "buf" }, { "int", "count" } } },
{ 59, "execve", 3, { { "char *", "fname" }, { "char **", "argp" }, { "char **", "envp" } } },
{ 60, "umask", 1, { { "int", "newmask" } } },
{ 61, "chroot", 1, { { "char *", "path" } } },
{ 65, "msync", 3, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "int", "flags" } } },
{ 66, "vfork", 0, {} },
{ 67, "oslog_coproc_reg", 3, { { "const char *", "uuid" }, { "const char *", "file_path" }, { "size_t", "file_path_len" } } },
{ 68, "oslog_coproc", 7, { { "void *", "buff" }, { "uint64_t", "buff_len" }, { "uint32_t", "type" }, { "const char *", "uuid" }, { "uint64_t", "timestamp" }, { "uint32_t", "offset" }, { "uint32_t", "stream_log" } } },
{ 73, "munmap", 2, { { "caddr_ut", "addr" }, { "size_ut", "len" } } },
{ 74, "mprotect", 3, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "int", "prot" } } },
{ 75, "madvise", 3, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "int", "behav" } } },
{ 78, "mincore", 3, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "user_addr_t", "vec" } } },
{ 79, "getgroups", 2, { { "u_int", "gidsetsize" }, { "gid_t *", "gidset" } } },
{ 80, "setgroups", 2, { { "u_int", "gidsetsize" }, { "gid_t *", "gidset" } } },
{ 81, "getpgrp", 0, {} },
{ 82, "setpgid", 2, { { "int", "pid" }, { "int", "pgid" } } },
{ 83, "setitimer", 3, { { "u_int", "which" }, { "struct itimerval *", "itv" }, { "struct itimerval *", "oitv" } } },
{ 85, "swapon", 0, {} },
{ 86, "getitimer", 2, { { "u_int", "which" }, { "struct itimerval *", "itv" } } },
{ 89, "getdtablesize", 0, {} },
{ 90, "dup2", 2, { { "u_int", "from" }, { "u_int", "to" } } },
{ 92, "fcntl", 3, { { "int", "fd" }, { "int", "cmd" }, { "long", "arg" } } },
{ 93, "select", 5, { { "int", "nd" }, { "u_int32_t *", "in" }, { "u_int32_t *", "ou" }, { "u_int32_t *", "ex" }, { "struct timeval *", "tv" } } },
{ 95, "fsync", 1, { { "int", "fd" } } },
{ 96, "setpriority", 3, { { "int", "which" }, { "id_t", "who" }, { "int", "prio" } } },
{ 97, "socket", 3, { { "int", "domain" }, { "int", "type" }, { "int", "protocol" } } },
{ 98, "connect", 3, { { "int", "s" }, { "caddr_t", "name" }, { "socklen_t", "namelen" } } },
{ 100, "getpriority", 2, { { "int", "which" }, { "id_t", "who" } } },
{ 104, "bind", 3, { { "int", "s" }, { "caddr_t", "name" }, { "socklen_t", "namelen" } } },
{ 105, "setsockopt", 5, { { "int", "s" }, { "int", "level" }, { "int", "name" }, { "caddr_t", "val" }, { "socklen_t", "valsize" } } },
{ 106, "listen", 2, { { "int", "s" }, { "int", "backlog" } } },
{ 111, "sigsuspend", 1, { { "sigset_t", "mask" } } },
{ 116, "gettimeofday", 3, { { "struct timeval *", "tp" }, { "struct timezone *", "tzp" }, { "uint64_t *", "mach_absolute_time" } } },
{ 117, "getrusage", 2, { { "int", "who" }, { "struct rusage *", "rusage" } } },
{ 118, "getsockopt", 5, { { "int", "s" }, { "int", "level" }, { "int", "name" }, { "caddr_t", "val" }, { "socklen_t *", "avalsize" } } },
{ 120, "readv", 3, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "u_int", "iovcnt" } } },
{ 121, "writev", 3, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "u_int", "iovcnt" } } },
{ 122, "settimeofday", 2, { { "struct timeval *", "tv" }, { "struct timezone *", "tzp" } } },
{ 123, "fchown", 3, { { "int", "fd" }, { "int", "uid" }, { "int", "gid" } } },
{ 124, "fchmod", 2, { { "int", "fd" }, { "int", "mode" } } },
{ 126, "setreuid", 2, { { "uid_t", "ruid" }, { "uid_t", "euid" } } },
{ 127, "setregid", 2, { { "gid_t", "rgid" }, { "gid_t", "egid" } } },
{ 128, "rename", 2, { { "char *", "from" }, { "char *", "to" } } },
{ 131, "flock", 2, { { "int", "fd" }, { "int", "how" } } },
{ 132, "mkfifo", 2, { { "char *", "path" }, { "int", "mode" } } },
{ 133, "sendto", 6, { { "int", "s" }, { "caddr_t", "buf" }, { "size_t", "len" }, { "int", "flags" }, { "caddr_t", "to" }, { "socklen_t", "tolen" } } },
{ 134, "shutdown", 2, { { "int", "s" }, { "int", "how" } } },
{ 135, "socketpair", 4, { { "int", "domain" }, { "int", "type" }, { "int", "protocol" }, { "int *", "rsv" } } },
{ 136, "mkdir", 2, { { "char *", "path" }, { "int", "mode" } } },
{ 137, "rmdir", 1, { { "char *", "path" } } },
{ 138, "utimes", 2, { { "char *", "path" }, { "struct timeval *", "tptr" } } },
{ 139, "futimes", 2, { { "int", "fd" }, { "struct timeval *", "tptr" } } },
{ 140, "adjtime", 2, { { "struct timeval *", "delta" }, { "struct timeval *", "olddelta" } } },
{ 142, "gethostuuid", 2, { { "unsigned char *", "uuid_buf" }, { "const struct timespec *", "timeoutp" } } },
{ 147, "setsid", 0, {} },
{ 151, "getpgid", 1, { { "pid_t", "pid" } } },
{ 152, "setprivexec", 1, { { "int", "flag" } } },
{ 153, "pread", 4, { { "int", "fd" }, { "user_addr_t", "buf" }, { "user_size_t", "nbyte" }, { "off_t", "offset" } } },
{ 154, "pwrite", 4, { { "int", "fd" }, { "user_addr_t", "buf" }, { "user_size_t", "nbyte" }, { "off_t", "offset" } } },
{ 155, "nfssvc", 2, { { "int", "flag" }, { "caddr_t", "argp" } } },
{ 157, "statfs", 2, { { "char *", "path" }, { "struct statfs *", "buf" } } },
{ 158, "fstatfs", 2, { { "int", "fd" }, { "struct statfs *", "buf" } } },
{ 159, "unmount", 2, { { "char *", "path" }, { "int", "flags" } } },
{ 161, "getfh", 2, { { "char *", "fname" }, { "fhandle_t *", "fhp" } } },
{ 164, "funmount", 2, { { "int", "fd" }, { "int", "flags" } } },
{ 165, "quotactl", 4, { { "const char *", "path" }, { "int", "cmd" }, { "int", "uid" }, { "caddr_t", "arg" } } },
{ 167, "mount", 4, { { "char *", "type" }, { "char *", "path" }, { "int", "flags" }, { "caddr_t", "data" } } },
{ 169, "csops", 4, { { "pid_t", "pid" }, { "uint32_t", "ops" }, { "user_addr_t", "useraddr" }, { "user_size_t", "usersize" } } },
{ 170, "csops_audittoken", 5, { { "pid_t", "pid" }, { "uint32_t", "ops" }, { "user_addr_t", "useraddr" }, { "user_size_t", "usersize" }, { "user_addr_t", "uaudittoken" } } },
{ 173, "waitid", 4, { { "idtype_t", "idtype" }, { "id_t", "id" }, { "siginfo_t *", "infop" }, { "int", "options" } } },
{ 177, "kdebug_typefilter", 2, { { "void **", "addr" }, { "size_t *", "size" } } },
{ 178, "kdebug_trace_string", 3, { { "uint32_t", "debugid" }, { "uint64_t", "str_id" }, { "const char *", "str" } } },
{ 179, "kdebug_trace64", 5, { { "uint32_t", "code" }, { "uint64_t", "arg1" }, { "uint64_t", "arg2" }, { "uint64_t", "arg3" }, { "uint64_t", "arg4" } } },
{ 180, "kdebug_trace", 5, { { "uint32_t", "code" }, { "u_long", "arg1" }, { "u_long", "arg2" }, { "u_long", "arg3" }, { "u_long", "arg4" } } },
{ 181, "setgid", 1, { { "gid_t", "gid" } } },
{ 182, "setegid", 1, { { "gid_t", "egid" } } },
{ 183, "seteuid", 1, { { "uid_t", "euid" } } },
{ 184, "sigreturn", 3, { { "struct ucontext *", "uctx" }, { "int", "infostyle" }, { "user_addr_t", "token" } } },
{ 185, "panic_with_data", 5, { { "uuid_t", "uuid" }, { "void *", "addr" }, { "uint32_t", "len" }, { "uint32_t", "flags" }, { "const char *", "msg" } } },
{ 186, "thread_selfcounts", 3, { { "uint32_t", "kind" }, { "user_addr_t", "buf" }, { "user_size_t", "size" } } },
{ 187, "fdatasync", 1, { { "int", "fd" } } },
{ 188, "stat", 2, { { "char *", "path" }, { "user_addr_t", "ub" } } },
{ 189, "fstat", 2, { { "int", "fd" }, { "user_addr_t", "ub" } } },
{ 190, "lstat", 2, { { "char *", "path" }, { "user_addr_t", "ub" } } },
{ 191, "pathconf", 2, { { "char *", "path" }, { "int", "name" } } },
{ 192, "fpathconf", 2, { { "int", "fd" }, { "int", "name" } } },
{ 194, "getrlimit", 2, { { "u_int", "which" }, { "struct rlimit *", "rlp" } } },
{ 195, "setrlimit", 2, { { "u_int", "which" }, { "struct rlimit *", "rlp" } } },
{ 196, "getdirentries", 4, { { "int", "fd" }, { "char *", "buf" }, { "u_int", "count" }, { "long *", "basep" } } },
{ 197, "mmap", 6, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "int", "prot" }, { "int", "flags" }, { "int", "fd" }, { "off_t", "pos" } } },
{ 199, "lseek", 3, { { "int", "fd" }, { "off_t", "offset" }, { "int", "whence" } } },
{ 200, "truncate", 2, { { "char *", "path" }, { "off_t", "length" } } },
{ 201, "ftruncate", 2, { { "int", "fd" }, { "off_t", "length" } } },
{ 202, "sysctl", 6, { { "int *", "name" }, { "u_int", "namelen" }, { "void *", "old" }, { "size_t *", "oldlenp" }, { "void *", "new" }, { "size_t", "newlen" } } },
{ 203, "mlock", 2, { { "caddr_ut", "addr" }, { "size_ut", "len" } } },
{ 204, "munlock", 2, { { "caddr_ut", "addr" }, { "size_ut", "len" } } },
{ 205, "undelete", 1, { { "char *", "path" } } },
{ 216, "open_dprotected_np", 5, { { "char *", "path" }, { "int", "flags" }, { "int", "class" }, { "int", "dpflags" }, { "int", "mode" } } },
{ 217, "fsgetpath_ext", 5, { { "user_addr_t", "buf" }, { "size_t", "bufsize" }, { "user_addr_t", "fsid" }, { "uint64_t", "objid" }, { "uint32_t", "options" } } },
{ 218, "openat_dprotected_np", 7, { { "int", "fd" }, { "char *", "path" }, { "int", "flags" }, { "int", "class" }, { "int", "dpflags" }, { "int", "mode" }, { "int", "authfd" } } },
{ 220, "getattrlist", 5, { { "const char *", "path" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "u_long", "options" } } },
{ 221, "setattrlist", 5, { { "const char *", "path" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "u_long", "options" } } },
{ 222, "getdirentriesattr", 8, { { "int", "fd" }, { "struct attrlist *", "alist" }, { "void *", "buffer" }, { "size_t", "buffersize" }, { "u_long *", "count" }, { "u_long *", "basep" }, { "u_long *", "newstate" }, { "u_long", "options" } } },
{ 223, "exchangedata", 3, { { "const char *", "path1" }, { "const char *", "path2" }, { "u_long", "options" } } },
{ 225, "searchfs", 6, { { "const char *", "path" }, { "struct fssearchblock *", "searchblock" }, { "uint32_t *", "nummatches" }, { "uint32_t", "scriptcode" }, { "uint32_t", "options" }, { "struct searchstate *", "state" } } },
{ 226, "delete", 1, { { "char *", "path" } } },
{ 227, "copyfile", 4, { { "char *", "from" }, { "char *", "to" }, { "int", "mode" }, { "int", "flags" } } },
{ 228, "fgetattrlist", 5, { { "int", "fd" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "u_long", "options" } } },
{ 229, "fsetattrlist", 5, { { "int", "fd" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "u_long", "options" } } },
{ 230, "poll", 3, { { "struct pollfd *", "fds" }, { "u_int", "nfds" }, { "int", "timeout" } } },
{ 234, "getxattr", 6, { { "char *", "path" }, { "char *", "attrname" }, { "user_addr_t", "value" }, { "size_t", "size" }, { "uint32_t", "position" }, { "int", "options" } } },
{ 235, "fgetxattr", 6, { { "int", "fd" }, { "char *", "attrname" }, { "user_addr_t", "value" }, { "size_t", "size" }, { "uint32_t", "position" }, { "int", "options" } } },
{ 236, "setxattr", 6, { { "char *", "path" }, { "char *", "attrname" }, { "user_addr_t", "value" }, { "size_t", "size" }, { "uint32_t", "position" }, { "int", "options" } } },
{ 237, "fsetxattr", 6, { { "int", "fd" }, { "char *", "attrname" }, { "user_addr_t", "value" }, { "size_t", "size" }, { "uint32_t", "position" }, { "int", "options" } } },
{ 238, "removexattr", 3, { { "char *", "path" }, { "char *", "attrname" }, { "int", "options" } } },
{ 239, "fremovexattr", 3, { { "int", "fd" }, { "char *", "attrname" }, { "int", "options" } } },
{ 240, "listxattr", 4, { { "char *", "path" }, { "user_addr_t", "namebuf" }, { "size_t", "bufsize" }, { "int", "options" } } },
{ 241, "flistxattr", 4, { { "int", "fd" }, { "user_addr_t", "namebuf" }, { "size_t", "bufsize" }, { "int", "options" } } },
{ 242, "fsctl", 4, { { "const char *", "path" }, { "u_long", "cmd" }, { "caddr_t", "data" }, { "u_int", "options" } } },
{ 243, "initgroups", 3, { { "u_int", "gidsetsize" }, { "gid_t *", "gidset" }, { "int", "gmuid" } } },
{ 244, "posix_spawn", 5, { { "pid_t *", "pid" }, { "const char *", "path" }, { "const struct _posix_spawn_args_desc *", "adesc" }, { "char **", "argv" }, { "char **", "envp" } } },
{ 245, "ffsctl", 4, { { "int", "fd" }, { "u_long", "cmd" }, { "caddr_t", "data" }, { "u_int", "options" } } },
{ 248, "fhopen", 2, { { "const struct fhandle *", "u_fhp" }, { "int", "flags" } } },
{ 250, "minherit", 3, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "int", "inherit" } } },
{ 251, "semsys", 5, { { "u_int", "which" }, { "int", "a2" }, { "int", "a3" }, { "int", "a4" }, { "int", "a5" } } },
{ 252, "msgsys", 5, { { "u_int", "which" }, { "int", "a2" }, { "int", "a3" }, { "int", "a4" }, { "int", "a5" } } },
{ 253, "shmsys", 4, { { "u_int", "which" }, { "int", "a2" }, { "int", "a3" }, { "int", "a4" } } },
{ 254, "semctl", 4, { { "int", "semid" }, { "int", "semnum" }, { "int", "cmd" }, { "semun_t", "arg" } } },
{ 255, "semget", 3, { { "key_t", "key" }, { "int", "nsems" }, { "int", "semflg" } } },
{ 256, "semop", 3, { { "int", "semid" }, { "struct sembuf *", "sops" }, { "int", "nsops" } } },
{ 258, "msgctl", 3, { { "int", "msqid" }, { "int", "cmd" }, { "struct msqid_ds *", "buf" } } },
{ 259, "msgget", 2, { { "key_t", "key" }, { "int", "msgflg" } } },
{ 260, "msgsnd", 4, { { "int", "msqid" }, { "void *", "msgp" }, { "size_t", "msgsz" }, { "int", "msgflg" } } },
{ 261, "msgrcv", 5, { { "int", "msqid" }, { "void *", "msgp" }, { "size_t", "msgsz" }, { "long", "msgtyp" }, { "int", "msgflg" } } },
{ 262, "shmat", 3, { { "int", "shmid" }, { "void *", "shmaddr" }, { "int", "shmflg" } } },
{ 263, "shmctl", 3, { { "int", "shmid" }, { "int", "cmd" }, { "struct shmid_ds *", "buf" } } },
{ 264, "shmdt", 1, { { "void *", "shmaddr" } } },
{ 265, "shmget", 3, { { "key_t", "key" }, { "size_t", "size" }, { "int", "shmflg" } } },
{ 266, "shm_open", 3, { { "const char *", "name" }, { "int", "oflag" }, { "int", "mode" } } },
{ 267, "shm_unlink", 1, { { "const char *", "name" } } },
{ 268, "sem_open", 4, { { "const char *", "name" }, { "int", "oflag" }, { "int", "mode" }, { "int", "value" } } },
{ 269, "sem_close", 1, { { "sem_t *", "sem" } } },
{ 270, "sem_unlink", 1, { { "const char *", "name" } } },
{ 271, "sem_wait", 1, { { "sem_t *", "sem" } } },
{ 272, "sem_trywait", 1, { { "sem_t *", "sem" } } },
{ 273, "sem_post", 1, { { "sem_t *", "sem" } } },
{ 274, "sysctlbyname", 6, { { "const char *", "name" }, { "size_t", "namelen" }, { "void *", "old" }, { "size_t *", "oldlenp" }, { "void *", "new" }, { "size_t", "newlen" } } },
{ 277, "open_extended", 6, { { "char *", "path" }, { "int", "flags" }, { "uid_t", "uid" }, { "gid_t", "gid" }, { "int", "mode" }, { "user_addr_t", "xsecurity" } } },
{ 278, "umask_extended", 2, { { "int", "newmask" }, { "user_addr_t", "xsecurity" } } },
{ 279, "stat_extended", 4, { { "char *", "path" }, { "user_addr_t", "ub" }, { "user_addr_t", "xsecurity" }, { "user_addr_t", "xsecurity_size" } } },
{ 280, "lstat_extended", 4, { { "char *", "path" }, { "user_addr_t", "ub" }, { "user_addr_t", "xsecurity" }, { "user_addr_t", "xsecurity_size" } } },
{ 281, "fstat_extended", 4, { { "int", "fd" }, { "user_addr_t", "ub" }, { "user_addr_t", "xsecurity" }, { "user_addr_t", "xsecurity_size" } } },
{ 282, "chmod_extended", 5, { { "char *", "path" }, { "uid_t", "uid" }, { "gid_t", "gid" }, { "int", "mode" }, { "user_addr_t", "xsecurity" } } },
{ 283, "fchmod_extended", 5, { { "int", "fd" }, { "uid_t", "uid" }, { "gid_t", "gid" }, { "int", "mode" }, { "user_addr_t", "xsecurity" } } },
{ 284, "access_extended", 4, { { "user_addr_t", "entries" }, { "size_t", "size" }, { "user_addr_t", "results" }, { "uid_t", "uid" } } },
{ 285, "settid", 2, { { "uid_t", "uid" }, { "gid_t", "gid" } } },
{ 286, "gettid", 2, { { "uid_t *", "uidp" }, { "gid_t *", "gidp" } } },
{ 287, "setsgroups", 2, { { "int", "setlen" }, { "user_addr_t", "guidset" } } },
{ 288, "getsgroups", 2, { { "user_addr_t", "setlen" }, { "user_addr_t", "guidset" } } },
{ 289, "setwgroups", 2, { { "int", "setlen" }, { "user_addr_t", "guidset" } } },
{ 290, "getwgroups", 2, { { "user_addr_t", "setlen" }, { "user_addr_t", "guidset" } } },
{ 291, "mkfifo_extended", 5, { { "char *", "path" }, { "uid_t", "uid" }, { "gid_t", "gid" }, { "int", "mode" }, { "user_addr_t", "xsecurity" } } },
{ 292, "mkdir_extended", 5, { { "char *", "path" }, { "uid_t", "uid" }, { "gid_t", "gid" }, { "int", "mode" }, { "user_addr_t", "xsecurity" } } },
{ 293, "identitysvc", 2, { { "int", "opcode" }, { "user_addr_t", "message" } } },
{ 294, "shared_region_check_np", 1, { { "uint64_t *", "start_address" } } },
{ 296, "vm_pressure_monitor", 3, { { "int", "wait_for_pressure" }, { "int", "nsecs_monitored" }, { "uint32_t *", "pages_reclaimed" } } },
{ 297, "psynch_rw_longrdlock", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
{ 298, "psynch_rw_yieldwrlock", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
{ 299, "psynch_rw_downgrade", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
{ 300, "psynch_rw_upgrade", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
{ 301, "psynch_mutexwait", 5, { { "user_addr_t", "mutex" }, { "uint32_t", "mgen" }, { "uint32_t", "ugen" }, { "uint64_t", "tid" }, { "uint32_t", "flags" } } },
{ 302, "psynch_mutexdrop", 5, { { "user_addr_t", "mutex" }, { "uint32_t", "mgen" }, { "uint32_t", "ugen" }, { "uint64_t", "tid" }, { "uint32_t", "flags" } } },
{ 303, "psynch_cvbroad", 7, { { "user_addr_t", "cv" }, { "uint64_t", "cvlsgen" }, { "uint64_t", "cvudgen" }, { "uint32_t", "flags" }, { "user_addr_t", "mutex" }, { "uint64_t", "mugen" }, { "uint64_t", "tid" } } },
{ 304, "psynch_cvsignal", 8, { { "user_addr_t", "cv" }, { "uint64_t", "cvlsgen" }, { "uint32_t", "cvugen" }, { "int", "thread_port" }, { "user_addr_t", "mutex" }, { "uint64_t", "mugen" }, { "uint64_t", "tid" }, { "uint32_t", "flags" } } },
{ 305, "psynch_cvwait", 8, { { "user_addr_t", "cv" }, { "uint64_t", "cvlsgen" }, { "uint32_t", "cvugen" }, { "user_addr_t", "mutex" }, { "uint64_t", "mugen" }, { "uint32_t", "flags" }, { "int64_t", "sec" }, { "uint32_t", "nsec" } } },
{ 306, "psynch_rw_rdlock", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
{ 307, "psynch_rw_wrlock", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
{ 308, "psynch_rw_unlock", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
{ 309, "psynch_rw_unlock2", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
{ 310, "getsid", 1, { { "pid_t", "pid" } } },
{ 311, "settid_with_pid", 2, { { "pid_t", "pid" }, { "int", "assume" } } },
{ 312, "psynch_cvclrprepost", 7, { { "user_addr_t", "cv" }, { "uint32_t", "cvgen" }, { "uint32_t", "cvugen" }, { "uint32_t", "cvsgen" }, { "uint32_t", "prepocnt" }, { "uint32_t", "preposeq" }, { "uint32_t", "flags" } } },
{ 313, "aio_fsync", 2, { { "int", "op" }, { "user_addr_t", "aiocbp" } } },
{ 314, "aio_return", 1, { { "user_addr_t", "aiocbp" } } },
{ 315, "aio_suspend", 3, { { "user_addr_t", "aiocblist" }, { "int", "nent" }, { "user_addr_t", "timeoutp" } } },
{ 316, "aio_cancel", 2, { { "int", "fd" }, { "user_addr_t", "aiocbp" } } },
{ 317, "aio_error", 1, { { "user_addr_t", "aiocbp" } } },
{ 318, "aio_read", 1, { { "user_addr_t", "aiocbp" } } },
{ 319, "aio_write", 1, { { "user_addr_t", "aiocbp" } } },
{ 320, "lio_listio", 4, { { "int", "mode" }, { "user_addr_t", "aiocblist" }, { "int", "nent" }, { "user_addr_t", "sigp" } } },
{ 322, "iopolicysys", 2, { { "int", "cmd" }, { "void *", "arg" } } },
{ 323, "process_policy", 7, { { "int", "scope" }, { "int", "action" }, { "int", "policy" }, { "int", "policy_subtype" }, { "user_addr_t", "attrp" }, { "pid_t", "target_pid" }, { "uint64_t", "target_threadid" } } },
{ 324, "mlockall", 1, { { "int", "how" } } },
{ 325, "munlockall", 1, { { "int", "how" } } },
{ 327, "issetugid", 0, {} },
{ 328, "pthread_kill", 2, { { "int", "thread_port" }, { "int", "sig" } } },
{ 329, "pthread_sigmask", 3, { { "int", "how" }, { "user_addr_t", "set" }, { "user_addr_t", "oset" } } },
{ 330, "sigwait", 2, { { "user_addr_t", "set" }, { "user_addr_t", "sig" } } },
{ 331, "disable_threadsignal", 1, { { "int", "value" } } },
{ 332, "pthread_markcancel", 1, { { "int", "thread_port" } } },
{ 333, "pthread_canceled", 1, { { "int", "action" } } },
{ 334, "semwait_signal", 6, { { "int", "cond_sem" }, { "int", "mutex_sem" }, { "int", "timeout" }, { "int", "relative" }, { "int64_t", "tv_sec" }, { "int32_t", "tv_nsec" } } },
{ 336, "proc_info", 6, { { "int32_t", "callnum" }, { "int32_t", "pid" }, { "uint32_t", "flavor" }, { "uint64_t", "arg" }, { "user_addr_t", "buffer" }, { "int32_t", "buffersize" } } },
{ 337, "sendfile", 6, { { "int", "fd" }, { "int", "s" }, { "off_t", "offset" }, { "off_t *", "nbytes" }, { "struct sf_hdtr *", "hdtr" }, { "int", "flags" } } },
{ 338, "stat64", 2, { { "char *", "path" }, { "user_addr_t", "ub" } } },
{ 339, "fstat64", 2, { { "int", "fd" }, { "user_addr_t", "ub" } } },
{ 340, "lstat64", 2, { { "char *", "path" }, { "user_addr_t", "ub" } } },
{ 341, "stat64_extended", 4, { { "char *", "path" }, { "user_addr_t", "ub" }, { "user_addr_t", "xsecurity" }, { "user_addr_t", "xsecurity_size" } } },
{ 342, "lstat64_extended", 4, { { "char *", "path" }, { "user_addr_t", "ub" }, { "user_addr_t", "xsecurity" }, { "user_addr_t", "xsecurity_size" } } },
{ 343, "fstat64_extended", 4, { { "int", "fd" }, { "user_addr_t", "ub" }, { "user_addr_t", "xsecurity" }, { "user_addr_t", "xsecurity_size" } } },
{ 344, "getdirentries64", 4, { { "int", "fd" }, { "void *", "buf" }, { "user_size_t", "bufsize" }, { "off_t *", "position" } } },
{ 345, "statfs64", 2, { { "char *", "path" }, { "struct statfs64 *", "buf" } } },
{ 346, "fstatfs64", 2, { { "int", "fd" }, { "struct statfs64 *", "buf" } } },
{ 347, "getfsstat64", 3, { { "user_addr_t", "buf" }, { "int", "bufsize" }, { "int", "flags" } } },
{ 348, "pthread_chdir", 1, { { "char *", "path" } } },
{ 349, "pthread_fchdir", 1, { { "int", "fd" } } },
{ 350, "audit", 2, { { "void *", "record" }, { "int", "length" } } },
{ 351, "auditon", 3, { { "int", "cmd" }, { "void *", "data" }, { "int", "length" } } },
{ 353, "getauid", 1, { { "au_id_t *", "auid" } } },
{ 354, "setauid", 1, { { "au_id_t *", "auid" } } },
{ 357, "getaudit_addr", 2, { { "struct auditinfo_addr *", "auditinfo_addr" }, { "int", "length" } } },
{ 358, "setaudit_addr", 2, { { "struct auditinfo_addr *", "auditinfo_addr" }, { "int", "length" } } },
{ 359, "auditctl", 1, { { "char *", "path" } } },
{ 360, "bsdthread_create", 5, { { "user_addr_t", "func" }, { "user_addr_t", "func_arg" }, { "user_addr_t", "stack" }, { "user_addr_t", "pthread" }, { "uint32_t", "flags" } } },
{ 361, "bsdthread_terminate", 4, { { "user_addr_t", "stackaddr" }, { "size_t", "freesize" }, { "uint32_t", "port" }, { "user_addr_t", "sema_or_ulock" } } },
{ 362, "kqueue", 0, {} },
{ 363, "kevent", 6, { { "int", "fd" }, { "const struct kevent *", "changelist" }, { "int", "nchanges" }, { "struct kevent *", "eventlist" }, { "int", "nevents" }, { "const struct timespec *", "timeout" } } },
{ 364, "lchown", 3, { { "char *", "path" }, { "uid_t", "owner" }, { "gid_t", "group" } } },
{ 366, "bsdthread_register", 7, { { "user_addr_t", "threadstart" }, { "user_addr_t", "wqthread" }, { "uint32_t", "flags" }, { "user_addr_t", "stack_addr_hint" }, { "user_addr_t", "targetconc_ptr" }, { "uint32_t", "dispatchqueue_offset" }, { "uint32_t", "tsd_offset" } } },
{ 367, "workq_open", 0, {} },
{ 368, "workq_kernreturn", 4, { { "int", "options" }, { "user_addr_t", "item" }, { "int", "affinity" }, { "int", "prio" } } },
{ 369, "kevent64", 7, { { "int", "fd" }, { "const struct kevent64_s *", "changelist" }, { "int", "nchanges" }, { "struct kevent64_s *", "eventlist" }, { "int", "nevents" }, { "unsigned int", "flags" }, { "const struct timespec *", "timeout" } } },
{ 372, "thread_selfid", 0, {} },
{ 373, "ledger", 4, { { "int", "cmd" }, { "caddr_t", "arg1" }, { "caddr_t", "arg2" }, { "caddr_t", "arg3" } } },
{ 374, "kevent_qos", 8, { { "int", "fd" }, { "const struct kevent_qos_s *", "changelist" }, { "int", "nchanges" }, { "struct kevent_qos_s *", "eventlist" }, { "int", "nevents" }, { "void *", "data_out" }, { "size_t *", "data_available" }, { "unsigned int", "flags" } } },
{ 375, "kevent_id", 8, { { "uint64_t", "id" }, { "const struct kevent_qos_s *", "changelist" }, { "int", "nchanges" }, { "struct kevent_qos_s *", "eventlist" }, { "int", "nevents" }, { "void *", "data_out" }, { "size_t *", "data_available" }, { "unsigned int", "flags" } } },
{ 380, "mac_execve", 4, { { "char *", "fname" }, { "char **", "argp" }, { "char **", "envp" }, { "struct mac *", "mac_p" } } },
{ 381, "mac_syscall", 3, { { "char *", "policy" }, { "int", "call" }, { "user_addr_t", "arg" } } },
{ 382, "mac_get_file", 2, { { "char *", "path_p" }, { "struct mac *", "mac_p" } } },
{ 383, "mac_set_file", 2, { { "char *", "path_p" }, { "struct mac *", "mac_p" } } },
{ 384, "mac_get_link", 2, { { "char *", "path_p" }, { "struct mac *", "mac_p" } } },
{ 385, "mac_set_link", 2, { { "char *", "path_p" }, { "struct mac *", "mac_p" } } },
{ 386, "mac_get_proc", 1, { { "struct mac *", "mac_p" } } },
{ 387, "mac_set_proc", 1, { { "struct mac *", "mac_p" } } },
{ 388, "mac_get_fd", 2, { { "int", "fd" }, { "struct mac *", "mac_p" } } },
{ 389, "mac_set_fd", 2, { { "int", "fd" }, { "struct mac *", "mac_p" } } },
{ 390, "mac_get_pid", 2, { { "pid_t", "pid" }, { "struct mac *", "mac_p" } } },
{ 394, "pselect", 6, { { "int", "nd" }, { "u_int32_t *", "in" }, { "u_int32_t *", "ou" }, { "u_int32_t *", "ex" }, { "const struct timespec *", "ts" }, { "const struct sigset_t *", "mask" } } },
{ 395, "pselect_nocancel", 6, { { "int", "nd" }, { "u_int32_t *", "in" }, { "u_int32_t *", "ou" }, { "u_int32_t *", "ex" }, { "const struct timespec *", "ts" }, { "const struct sigset_t *", "mask" } } },
{ 396, "read_nocancel", 3, { { "int", "fd" }, { "user_addr_t", "cbuf" }, { "user_size_t", "nbyte" } } },
{ 397, "write_nocancel", 3, { { "int", "fd" }, { "user_addr_t", "cbuf" }, { "user_size_t", "nbyte" } } },
{ 398, "open_nocancel", 3, { { "char *", "path" }, { "int", "flags" }, { "int", "mode" } } },
{ 399, "close_nocancel", 1, { { "int", "fd" } } },
{ 400, "wait4_nocancel", 4, { { "int", "pid" }, { "user_addr_t", "status" }, { "int", "options" }, { "user_addr_t", "rusage" } } },
{ 401, "recvmsg_nocancel", 3, { { "int", "s" }, { "struct msghdr *", "msg" }, { "int", "flags" } } },
{ 402, "sendmsg_nocancel", 3, { { "int", "s" }, { "caddr_t", "msg" }, { "int", "flags" } } },
{ 403, "recvfrom_nocancel", 6, { { "int", "s" }, { "void *", "buf" }, { "size_t", "len" }, { "int", "flags" }, { "struct sockaddr *", "from" }, { "int *", "fromlenaddr" } } },
{ 404, "accept_nocancel", 3, { { "int", "s" }, { "caddr_t", "name" }, { "socklen_t *", "anamelen" } } },
{ 405, "msync_nocancel", 3, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "int", "flags" } } },
{ 406, "fcntl_nocancel", 3, { { "int", "fd" }, { "int", "cmd" }, { "long", "arg" } } },
{ 407, "select_nocancel", 5, { { "int", "nd" }, { "u_int32_t *", "in" }, { "u_int32_t *", "ou" }, { "u_int32_t *", "ex" }, { "struct timeval *", "tv" } } },
{ 408, "fsync_nocancel", 1, { { "int", "fd" } } },
{ 409, "connect_nocancel", 3, { { "int", "s" }, { "caddr_t", "name" }, { "socklen_t", "namelen" } } },
{ 410, "sigsuspend_nocancel", 1, { { "sigset_t", "mask" } } },
{ 411, "readv_nocancel", 3, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "u_int", "iovcnt" } } },
{ 412, "writev_nocancel", 3, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "u_int", "iovcnt" } } },
{ 413, "sendto_nocancel", 6, { { "int", "s" }, { "caddr_t", "buf" }, { "size_t", "len" }, { "int", "flags" }, { "caddr_t", "to" }, { "socklen_t", "tolen" } } },
{ 414, "pread_nocancel", 4, { { "int", "fd" }, { "user_addr_t", "buf" }, { "user_size_t", "nbyte" }, { "off_t", "offset" } } },
{ 415, "pwrite_nocancel", 4, { { "int", "fd" }, { "user_addr_t", "buf" }, { "user_size_t", "nbyte" }, { "off_t", "offset" } } },
{ 416, "waitid_nocancel", 4, { { "idtype_t", "idtype" }, { "id_t", "id" }, { "siginfo_t *", "infop" }, { "int", "options" } } },
{ 417, "poll_nocancel", 3, { { "struct pollfd *", "fds" }, { "u_int", "nfds" }, { "int", "timeout" } } },
{ 418, "msgsnd_nocancel", 4, { { "int", "msqid" }, { "void *", "msgp" }, { "size_t", "msgsz" }, { "int", "msgflg" } } },
{ 419, "msgrcv_nocancel", 5, { { "int", "msqid" }, { "void *", "msgp" }, { "size_t", "msgsz" }, { "long", "msgtyp" }, { "int", "msgflg" } } },
{ 420, "sem_wait_nocancel", 1, { { "sem_t *", "sem" } } },
{ 421, "aio_suspend_nocancel", 3, { { "user_addr_t", "aiocblist" }, { "int", "nent" }, { "user_addr_t", "timeoutp" } } },
{ 422, "sigwait_nocancel", 2, { { "user_addr_t", "set" }, { "user_addr_t", "sig" } } },
{ 423, "semwait_signal_nocancel", 6, { { "int", "cond_sem" }, { "int", "mutex_sem" }, { "int", "timeout" }, { "int", "relative" }, { "int64_t", "tv_sec" }, { "int32_t", "tv_nsec" } } },
{ 424, "mac_mount", 5, { { "char *", "type" }, { "char *", "path" }, { "int", "flags" }, { "caddr_t", "data" }, { "struct mac *", "mac_p" } } },
{ 425, "mac_get_mount", 2, { { "char *", "path" }, { "struct mac *", "mac_p" } } },
{ 426, "mac_getfsstat", 5, { { "user_addr_t", "buf" }, { "int", "bufsize" }, { "user_addr_t", "mac" }, { "int", "macsize" }, { "int", "flags" } } },
{ 427, "fsgetpath", 4, { { "user_addr_t", "buf" }, { "size_t", "bufsize" }, { "user_addr_t", "fsid" }, { "uint64_t", "objid" } } },
{ 428, "audit_session_self", 0, {} },
{ 429, "audit_session_join", 1, { { "mach_port_name_t", "port" } } },
{ 430, "fileport_makeport", 2, { { "int", "fd" }, { "user_addr_t", "portnamep" } } },
{ 431, "fileport_makefd", 1, { { "mach_port_name_t", "port" } } },
{ 432, "audit_session_port", 2, { { "au_asid_t", "asid" }, { "user_addr_t", "portnamep" } } },
{ 433, "pid_suspend", 1, { { "int", "pid" } } },
{ 434, "pid_resume", 1, { { "int", "pid" } } },
{ 435, "pid_hibernate", 1, { { "int", "pid" } } },
{ 436, "pid_shutdown_sockets", 2, { { "int", "pid" }, { "int", "level" } } },
{ 439, "kas_info", 3, { { "int", "selector" }, { "void *", "value" }, { "size_t *", "size" } } },
{ 440, "memorystatus_control", 5, { { "uint32_t", "command" }, { "int32_t", "pid" }, { "uint32_t", "flags" }, { "user_addr_t", "buffer" }, { "size_t", "buffersize" } } },
{ 441, "guarded_open_np", 5, { { "char *", "path" }, { "const guardid_t *", "guard" }, { "u_int", "guardflags" }, { "int", "flags" }, { "int", "mode" } } },
{ 442, "guarded_close_np", 2, { { "int", "fd" }, { "const guardid_t *", "guard" } } },
{ 443, "guarded_kqueue_np", 2, { { "const guardid_t *", "guard" }, { "u_int", "guardflags" } } },
{ 444, "change_fdguard_np", 6, { { "int", "fd" }, { "const guardid_t *", "guard" }, { "u_int", "guardflags" }, { "const guardid_t *", "nguard" }, { "u_int", "nguardflags" }, { "int *", "fdflagsp" } } },
{ 445, "usrctl", 1, { { "uint32_t", "flags" } } },
{ 446, "proc_rlimit_control", 3, { { "pid_t", "pid" }, { "int", "flavor" }, { "void *", "arg" } } },
{ 447, "connectx", 8, { { "int", "socket" }, { "const sa_endpoints_t *", "endpoints" }, { "sae_associd_t", "associd" }, { "unsigned int", "flags" }, { "const struct iovec *", "iov" }, { "unsigned int", "iovcnt" }, { "size_t *", "len" }, { "sae_connid_t *", "connid" } } },
{ 448, "disconnectx", 3, { { "int", "s" }, { "sae_associd_t", "aid" }, { "sae_connid_t", "cid" } } },
{ 449, "peeloff", 2, { { "int", "s" }, { "sae_associd_t", "aid" } } },
{ 450, "socket_delegate", 4, { { "int", "domain" }, { "int", "type" }, { "int", "protocol" }, { "pid_t", "epid" } } },
{ 451, "telemetry", 6, { { "uint64_t", "cmd" }, { "uint64_t", "deadline" }, { "uint64_t", "interval" }, { "uint64_t", "leeway" }, { "uint64_t", "arg4" }, { "uint64_t", "arg5" } } },
{ 452, "proc_uuid_policy", 4, { { "uint32_t", "operation" }, { "uuid_t", "uuid" }, { "size_t", "uuidlen" }, { "uint32_t", "flags" } } },
{ 453, "memorystatus_get_level", 1, { { "user_addr_t", "level" } } },
{ 454, "system_override", 2, { { "uint64_t", "timeout" }, { "uint64_t", "flags" } } },
{ 455, "vfs_purge", 0, {} },
{ 456, "sfi_ctl", 4, { { "uint32_t", "operation" }, { "uint32_t", "sfi_class" }, { "uint64_t", "time" }, { "uint64_t *", "out_time" } } },
{ 457, "sfi_pidctl", 4, { { "uint32_t", "operation" }, { "pid_t", "pid" }, { "uint32_t", "sfi_flags" }, { "uint32_t *", "out_sfi_flags" } } },
{ 458, "coalition", 3, { { "uint32_t", "operation" }, { "uint64_t *", "cid" }, { "uint32_t", "flags" } } },
{ 459, "coalition_info", 4, { { "uint32_t", "flavor" }, { "uint64_t *", "cid" }, { "void *", "buffer" }, { "size_t *", "bufsize" } } },
{ 460, "necp_match_policy", 3, { { "uint8_t *", "parameters" }, { "size_t", "parameters_size" }, { "struct necp_aggregate_result *", "returned_result" } } },
{ 461, "getattrlistbulk", 5, { { "int", "dirfd" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "uint64_t", "options" } } },
{ 462, "clonefileat", 5, { { "int", "src_dirfd" }, { "user_addr_t", "src" }, { "int", "dst_dirfd" }, { "user_addr_t", "dst" }, { "uint32_t", "flags" } } },
{ 463, "openat", 4, { { "int", "fd" }, { "char *", "path" }, { "int", "flags" }, { "int", "mode" } } },
{ 464, "openat_nocancel", 4, { { "int", "fd" }, { "char *", "path" }, { "int", "flags" }, { "int", "mode" } } },
{ 465, "renameat", 4, { { "int", "fromfd" }, { "char *", "from" }, { "int", "tofd" }, { "char *", "to" } } },
{ 466, "faccessat", 4, { { "int", "fd" }, { "char *", "path" }, { "int", "amode" }, { "int", "flag" } } },
{ 467, "fchmodat", 4, { { "int", "fd" }, { "char *", "path" }, { "int", "mode" }, { "int", "flag" } } },
{ 468, "fchownat", 5, { { "int", "fd" }, { "char *", "path" }, { "uid_t", "uid" }, { "gid_t", "gid" }, { "int", "flag" } } },
{ 469, "fstatat", 4, { { "int", "fd" }, { "char *", "path" }, { "user_addr_t", "ub" }, { "int", "flag" } } },
{ 470, "fstatat64", 4, { { "int", "fd" }, { "char *", "path" }, { "user_addr_t", "ub" }, { "int", "flag" } } },
{ 471, "linkat", 5, { { "int", "fd1" }, { "char *", "path" }, { "int", "fd2" }, { "char *", "link" }, { "int", "flag" } } },
{ 472, "unlinkat", 3, { { "int", "fd" }, { "char *", "path" }, { "int", "flag" } } },
{ 473, "readlinkat", 4, { { "int", "fd" }, { "char *", "path" }, { "user_addr_t", "buf" }, { "size_t", "bufsize" } } },
{ 474, "symlinkat", 3, { { "user_addr_t *", "path1" }, { "int", "fd" }, { "user_addr_t", "path2" } } },
{ 475, "mkdirat", 3, { { "int", "fd" }, { "char *", "path" }, { "int", "mode" } } },
{ 476, "getattrlistat", 6, { { "int", "fd" }, { "const char *", "path" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "u_long", "options" } } },
{ 477, "proc_trace_log", 2, { { "pid_t", "pid" }, { "uint64_t", "uniqueid" } } },
{ 478, "bsdthread_ctl", 4, { { "user_addr_t", "cmd" }, { "user_addr_t", "arg1" }, { "user_addr_t", "arg2" }, { "user_addr_t", "arg3" } } },
{ 479, "openbyid_np", 3, { { "user_addr_t", "fsid" }, { "user_addr_t", "objid" }, { "int", "oflags" } } },
{ 480, "recvmsg_x", 4, { { "int", "s" }, { "struct msghdr_x *", "msgp" }, { "u_int", "cnt" }, { "int", "flags" } } },
{ 481, "sendmsg_x", 4, { { "int", "s" }, { "struct msghdr_x *", "msgp" }, { "u_int", "cnt" }, { "int", "flags" } } },
{ 482, "thread_selfusage", 0, {} },
{ 483, "csrctl", 3, { { "uint32_t", "op" }, { "user_addr_t", "useraddr" }, { "user_addr_t", "usersize" } } },
{ 484, "guarded_open_dprotected_np", 7, { { "char *", "path" }, { "const guardid_t *", "guard" }, { "u_int", "guardflags" }, { "int", "flags" }, { "int", "dpclass" }, { "int", "dpflags" }, { "int", "mode" } } },
{ 485, "guarded_write_np", 4, { { "int", "fd" }, { "const guardid_t *", "guard" }, { "user_addr_t", "cbuf" }, { "user_size_t", "nbyte" } } },
{ 486, "guarded_pwrite_np", 5, { { "int", "fd" }, { "const guardid_t *", "guard" }, { "user_addr_t", "buf" }, { "user_size_t", "nbyte" }, { "off_t", "offset" } } },
{ 487, "guarded_writev_np", 4, { { "int", "fd" }, { "const guardid_t *", "guard" }, { "struct iovec *", "iovp" }, { "int", "iovcnt" } } },
{ 488, "renameatx_np", 5, { { "int", "fromfd" }, { "char *", "from" }, { "int", "tofd" }, { "char *", "to" }, { "u_int", "flags" } } },
{ 489, "mremap_encrypted", 5, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "uint32_t", "cryptid" }, { "uint32_t", "cputype" }, { "uint32_t", "cpusubtype" } } },
{ 490, "netagent_trigger", 2, { { "uuid_t", "agent_uuid" }, { "size_t", "agent_uuidlen" } } },
{ 491, "stack_snapshot_with_config", 3, { { "int", "stackshot_config_version" }, { "user_addr_t", "stackshot_config" }, { "size_t", "stackshot_config_size" } } },
{ 492, "microstackshot", 3, { { "user_addr_t", "tracebuf" }, { "uint32_t", "tracebuf_size" }, { "uint32_t", "flags" } } },
{ 493, "grab_pgo_data", 4, { { "user_addr_t", "uuid" }, { "int", "flags" }, { "user_addr_t", "buffer" }, { "user_ssize_t", "size" } } },
{ 494, "persona", 6, { { "uint32_t", "operation" }, { "uint32_t", "flags" }, { "struct kpersona_info *", "info" }, { "uid_t *", "id" }, { "size_t *", "idlen" }, { "char *", "path" } } },
{ 496, "mach_eventlink_signal", 2, { { "mach_port_name_t", "eventlink_port" }, { "uint64_t", "signal_count" } } },
{ 497, "mach_eventlink_wait_until", 5, { { "mach_port_name_t", "eventlink_port" }, { "uint64_t", "wait_count" }, { "uint64_t", "deadline" }, { "uint32_t", "clock_id" }, { "uint32_t", "option" } } },
{ 498, "mach_eventlink_signal_wait_until", 6, { { "mach_port_name_t", "eventlink_port" }, { "uint64_t", "wait_count" }, { "uint64_t", "signal_count" }, { "uint64_t", "deadline" }, { "uint32_t", "clock_id" }, { "uint32_t", "option" } } },
{ 499, "work_interval_ctl", 4, { { "uint32_t", "operation" }, { "uint64_t", "work_interval_id" }, { "void *", "arg" }, { "size_t", "len" } } },
{ 500, "getentropy", 2, { { "void *", "buffer" }, { "size_t", "size" } } },
{ 502, "necp_client_action", 6, { { "int", "necp_fd" }, { "uint32_t", "action" }, { "uuid_t", "client_id" }, { "size_t", "client_id_len" }, { "uint8_t *", "buffer" }, { "size_t", "buffer_size" } } },
{ 503, "nexus_open", 2, { { "struct nxctl_init *", "init" }, { "uint32_t", "init_len" } } },
{ 504, "nexus_register", 5, { { "int", "ctl" }, { "struct nxprov_reg *", "reg" }, { "uint32_t", "reg_len" }, { "uuid_t *", "prov_uuid" }, { "uint32_t", "prov_uuid_len" } } },
{ 505, "nexus_deregister", 3, { { "int", "ctl" }, { "uuid_t", "prov_uuid" }, { "uint32_t", "prov_uuid_len" } } },
{ 506, "nexus_create", 5, { { "int", "ctl" }, { "uuid_t", "prov_uuid" }, { "uint32_t", "prov_uuid_len" }, { "uuid_t *", "nx_uuid" }, { "uint32_t", "nx_uuid_len" } } },
{ 507, "nexus_destroy", 3, { { "int", "ctl" }, { "uuid_t", "nx_uuid" }, { "uint32_t", "nx_uuid_len" } } },
{ 508, "nexus_get_opt", 4, { { "int", "ctl" }, { "uint32_t", "opt" }, { "void *", "aoptval" }, { "uint32_t *", "aoptlen" } } },
{ 509, "nexus_set_opt", 4, { { "int", "ctl" }, { "uint32_t", "opt" }, { "const void *", "aoptval" }, { "uint32_t", "optlen" } } },
{ 510, "channel_open", 2, { { "struct ch_init *", "init" }, { "uint32_t", "init_len" } } },
{ 511, "channel_get_info", 3, { { "int", "c" }, { "struct ch_info *", "cinfo" }, { "uint32_t", "cinfolen" } } },
{ 512, "channel_sync", 3, { { "int", "c" }, { "int", "mode" }, { "int", "flags" } } },
{ 513, "channel_get_opt", 4, { { "int", "c" }, { "uint32_t", "opt" }, { "void *", "aoptval" }, { "uint32_t *", "aoptlen" } } },
{ 514, "channel_set_opt", 4, { { "int", "c" }, { "uint32_t", "opt" }, { "const void *", "aoptval" }, { "uint32_t", "optlen" } } },
{ 515, "ulock_wait", 4, { { "uint32_t", "operation" }, { "void *", "addr" }, { "uint64_t", "value" }, { "uint32_t", "timeout" } } },
{ 516, "ulock_wake", 3, { { "uint32_t", "operation" }, { "void *", "addr" }, { "uint64_t", "wake_value" } } },
{ 517, "fclonefileat", 4, { { "int", "src_fd" }, { "int", "dst_dirfd" }, { "user_addr_t", "dst" }, { "uint32_t", "flags" } } },
{ 518, "fs_snapshot", 6, { { "uint32_t", "op" }, { "int", "dirfd" }, { "user_addr_t", "name1" }, { "user_addr_t", "name2" }, { "user_addr_t", "data" }, { "uint32_t", "flags" } } },
{ 520, "terminate_with_payload", 7, { { "int", "pid" }, { "uint32_t", "reason_namespace" }, { "uint64_t", "reason_code" }, { "void *", "payload" }, { "uint32_t", "payload_size" }, { "const char *", "reason_string" }, { "uint64_t", "reason_flags" } } },
{ 521, "abort_with_payload", 6, { { "uint32_t", "reason_namespace" }, { "uint64_t", "reason_code" }, { "void *", "payload" }, { "uint32_t", "payload_size" }, { "const char *", "reason_string" }, { "uint64_t", "reason_flags" } } },
{ 523, "necp_session_action", 6, { { "int", "necp_fd" }, { "uint32_t", "action" }, { "uint8_t *", "in_buffer" }, { "size_t", "in_buffer_length" }, { "uint8_t *", "out_buffer" }, { "size_t", "out_buffer_length" } } },
{ 524, "setattrlistat", 6, { { "int", "fd" }, { "const char *", "path" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "uint32_t", "options" } } },
{ 525, "net_qos_guideline", 2, { { "struct net_qos_param *", "param" }, { "uint32_t", "param_len" } } },
{ 526, "fmount", 4, { { "const char *", "type" }, { "int", "fd" }, { "int", "flags" }, { "void *", "data" } } },
{ 527, "ntp_adjtime", 1, { { "struct timex *", "tp" } } },
{ 528, "ntp_gettime", 1, { { "struct ntptimeval *", "ntvp" } } },
{ 529, "os_fault_with_payload", 6, { { "uint32_t", "reason_namespace" }, { "uint64_t", "reason_code" }, { "void *", "payload" }, { "uint32_t", "payload_size" }, { "const char *", "reason_string" }, { "uint64_t", "reason_flags" } } },
{ 530, "kqueue_workloop_ctl", 4, { { "user_addr_t", "cmd" }, { "uint64_t", "options" }, { "user_addr_t", "addr" }, { "size_t", "sz" } } },
{ 531, "mach_bridge_remote_time", 1, { { "uint64_t", "local_timestamp" } } },
{ 532, "coalition_ledger", 4, { { "uint32_t", "operation" }, { "uint64_t *", "cid" }, { "void *", "buffer" }, { "size_t *", "bufsize" } } },
{ 533, "log_data", 4, { { "unsigned int", "tag" }, { "unsigned int", "flags" }, { "void *", "buffer" }, { "unsigned int", "size" } } },
{ 534, "memorystatus_available_memory", 0, {} },
{ 535, "objc_bp_assist_cfg_np", 2, { { "uint64_t", "adr" }, { "uint64_t", "ctl" } } },
{ 536, "shared_region_map_and_slide_2_np", 4, { { "uint32_t", "files_count" }, { "const struct shared_file_np *", "files" }, { "uint32_t", "mappings_count" }, { "const shared_file_mapping_slide_np_ut *", "mappings_u" } } },
{ 537, "pivot_root", 2, { { "const char *", "new_rootfs_path_before" }, { "const char *", "old_rootfs_path_after" } } },
{ 538, "task_inspect_for_pid", 3, { { "mach_port_name_t", "target_tport" }, { "int", "pid" }, { "mach_port_name_t *", "t" } } },
{ 539, "task_read_for_pid", 3, { { "mach_port_name_t", "target_tport" }, { "int", "pid" }, { "mach_port_name_t *", "t" } } },
{ 540, "preadv", 4, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "int", "iovcnt" }, { "off_t", "offset" } } },
{ 541, "pwritev", 4, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "int", "iovcnt" }, { "off_t", "offset" } } },
{ 542, "preadv_nocancel", 4, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "int", "iovcnt" }, { "off_t", "offset" } } },
{ 543, "pwritev_nocancel", 4, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "int", "iovcnt" }, { "off_t", "offset" } } },
{ 544, "ulock_wait2", 5, { { "uint32_t", "operation" }, { "void *", "addr" }, { "uint64_t", "value" }, { "uint64_t", "timeout" }, { "uint64_t", "value2" } } },
{ 545, "proc_info_extended_id", 8, { { "int32_t", "callnum" }, { "int32_t", "pid" }, { "uint32_t", "flavor" }, { "uint32_t", "flags" }, { "uint64_t", "ext_id" }, { "uint64_t", "arg" }, { "user_addr_t", "buffer" }, { "int32_t", "buffersize" } } },
{ 546, "tracker_action", 3, { { "int", "action" }, { "char *", "buffer" }, { "size_t", "buffer_size" } } },
{ 547, "debug_syscall_reject", 1, { { "uint64_t", "packed_selectors" } } },
{ 548, "debug_syscall_reject_config", 3, { { "uint64_t", "packed_selectors1" }, { "uint64_t", "packed_selectors2" }, { "uint64_t", "flags" } } },
{ 549, "graftdmg", 4, { { "int", "dmg_fd" }, { "const char *", "mountdir" }, { "uint32_t", "graft_type" }, { "graftdmg_args_un *", "gda" } } },
{ 550, "map_with_linking_np", 4, { { "void *", "regions" }, { "uint32_t", "region_count" }, { "void *", "link_info" }, { "uint32_t", "link_info_size" } } },
{ 551, "freadlink", 3, { { "int", "fd" }, { "user_addr_t", "buf" }, { "user_size_t", "bufsize" } } },
{ 552, "record_system_event", 4, { { "uint32_t", "type" }, { "uint32_t", "subsystem" }, { "const char *", "event" }, { "const char *", "payload" } } },
{ 553, "mkfifoat", 3, { { "int", "fd" }, { "char *", "path" }, { "int", "mode" } } },
{ 554, "mknodat", 4, { { "int", "fd" }, { "char *", "path" }, { "int", "mode" }, { "int", "dev" } } },
{ 555, "ungraftdmg", 2, { { "const char *", "mountdir" }, { "uint64_t", "flags" } } },
{ 556, "coalition_policy_set", 3, { { "uint64_t", "cid" }, { "uint32_t", "flavor" }, { "uint32_t", "value" } } },
{ 557, "coalition_policy_get", 2, { { "uint64_t", "cid" }, { "uint32_t", "flavor" } } },
};
FridaXnuSyscallSignature *
frida_get_xnu_mach_traps (int * len)
{
if (len != NULL)
*len = G_N_ELEMENTS (frida_xnu_mach_traps);
return (FridaXnuSyscallSignature *) frida_xnu_mach_traps;
}
FridaXnuSyscallSignature *
frida_get_xnu_bsd_syscalls (int * len)
{
if (len != NULL)
*len = G_N_ELEMENTS (frida_xnu_bsd_syscalls);
return (FridaXnuSyscallSignature *) frida_xnu_bsd_syscalls;
}
+545
View File
@@ -0,0 +1,545 @@
// AUTO-GENERATED FILE. DO NOT EDIT.
namespace Frida {
public extern unowned XnuSyscallSignature[] get_xnu_mach_traps ();
public extern unowned XnuSyscallSignature[] get_xnu_bsd_syscalls ();
public struct XnuSyscallSignature {
public int nr;
public string name;
public uint8 nargs;
public XnuSyscallArg args[9];
}
public struct XnuSyscallArg {
public string? type;
public string? name;
}
/**
* The XNU Mach trap numbers, as used on Apple platforms.
*/
public enum XnuMachTrap {
MACH_VM_ALLOCATE = -10,
MACH_VM_PURGABLE_CONTROL = -11,
MACH_VM_DEALLOCATE = -12,
TASK_DYLD_PROCESS_INFO_NOTIFY_GET = -13,
MACH_VM_PROTECT = -14,
MACH_VM_MAP = -15,
MACH_PORT_ALLOCATE = -16,
MACH_PORT_DEALLOCATE = -18,
MACH_PORT_MOD_REFS = -19,
MACH_PORT_MOVE_MEMBER = -20,
MACH_PORT_INSERT_RIGHT = -21,
MACH_PORT_INSERT_MEMBER = -22,
MACH_PORT_EXTRACT_MEMBER = -23,
MACH_PORT_CONSTRUCT = -24,
MACH_PORT_DESTRUCT = -25,
MACH_REPLY_PORT = -26,
THREAD_SELF = -27,
TASK_SELF = -28,
HOST_SELF = -29,
MACH_MSG = -31,
MACH_MSG_OVERWRITE = -32,
SEMAPHORE_SIGNAL = -33,
SEMAPHORE_SIGNAL_ALL = -34,
SEMAPHORE_SIGNAL_THREAD = -35,
SEMAPHORE_WAIT = -36,
SEMAPHORE_WAIT_SIGNAL = -37,
SEMAPHORE_TIMEDWAIT = -38,
SEMAPHORE_TIMEDWAIT_SIGNAL = -39,
MACH_PORT_GET_ATTRIBUTES = -40,
MACH_PORT_GUARD = -41,
MACH_PORT_UNGUARD = -42,
MACH_GENERATE_ACTIVITY_ID = -43,
TASK_NAME_FOR_PID = -44,
TASK_FOR_PID = -45,
PID_FOR_TASK = -46,
MACH_MSG2 = -47,
MACX_SWAPON = -48,
MACX_SWAPOFF = -49,
THREAD_GET_SPECIAL_REPLY_PORT = -50,
MACX_TRIGGERS = -51,
MACX_BACKING_STORE_SUSPEND = -52,
MACX_BACKING_STORE_RECOVERY = -53,
PFZ_EXIT = -58,
SWTCH_PRI = -59,
SWTCH = -60,
THREAD_SWITCH = -61,
CLOCK_SLEEP = -62,
MACH_VM_RECLAIM_UPDATE_KERNEL_ACCOUNTING = -63,
HOST_CREATE_MACH_VOUCHER = -70,
MACH_VOUCHER_EXTRACT_ATTR_RECIPE = -72,
MACH_PORT_TYPE = -76,
MACH_PORT_REQUEST_NOTIFICATION = -77,
EXCLAVES_CTL = -88,
MACH_TIMEBASE_INFO = -89,
MACH_WAIT_UNTIL = -90,
MK_TIMER_CREATE = -91,
MK_TIMER_DESTROY = -92,
MK_TIMER_ARM = -93,
MK_TIMER_CANCEL = -94,
MK_TIMER_ARM_LEEWAY = -95,
DEBUG_CONTROL_PORT_FOR_PID = -96,
IOKIT_USER_CLIENT = -100,
}
/**
* The XNU BSD syscall numbers, as used on Apple platforms.
*/
public enum XnuBsdSyscall {
EXIT = 1,
FORK = 2,
READ = 3,
WRITE = 4,
OPEN = 5,
CLOSE = 6,
WAIT4 = 7,
LINK = 9,
UNLINK = 10,
CHDIR = 12,
FCHDIR = 13,
MKNOD = 14,
CHMOD = 15,
CHOWN = 16,
GETFSSTAT = 18,
GETPID = 20,
SETUID = 23,
GETUID = 24,
GETEUID = 25,
PTRACE = 26,
RECVMSG = 27,
SENDMSG = 28,
RECVFROM = 29,
ACCEPT = 30,
GETPEERNAME = 31,
GETSOCKNAME = 32,
ACCESS = 33,
CHFLAGS = 34,
FCHFLAGS = 35,
SYNC = 36,
KILL = 37,
CROSSARCH_TRAP = 38,
GETPPID = 39,
DUP = 41,
PIPE = 42,
GETEGID = 43,
SIGACTION = 46,
GETGID = 47,
SIGPROCMASK = 48,
GETLOGIN = 49,
SETLOGIN = 50,
ACCT = 51,
SIGPENDING = 52,
SIGALTSTACK = 53,
IOCTL = 54,
REBOOT = 55,
REVOKE = 56,
SYMLINK = 57,
READLINK = 58,
EXECVE = 59,
UMASK = 60,
CHROOT = 61,
MSYNC = 65,
VFORK = 66,
OSLOG_COPROC_REG = 67,
OSLOG_COPROC = 68,
MUNMAP = 73,
MPROTECT = 74,
MADVISE = 75,
MINCORE = 78,
GETGROUPS = 79,
SETGROUPS = 80,
GETPGRP = 81,
SETPGID = 82,
SETITIMER = 83,
SWAPON = 85,
GETITIMER = 86,
GETDTABLESIZE = 89,
DUP2 = 90,
FCNTL = 92,
SELECT = 93,
FSYNC = 95,
SETPRIORITY = 96,
SOCKET = 97,
CONNECT = 98,
GETPRIORITY = 100,
BIND = 104,
SETSOCKOPT = 105,
LISTEN = 106,
SIGSUSPEND = 111,
GETTIMEOFDAY = 116,
GETRUSAGE = 117,
GETSOCKOPT = 118,
READV = 120,
WRITEV = 121,
SETTIMEOFDAY = 122,
FCHOWN = 123,
FCHMOD = 124,
SETREUID = 126,
SETREGID = 127,
RENAME = 128,
FLOCK = 131,
MKFIFO = 132,
SENDTO = 133,
SHUTDOWN = 134,
SOCKETPAIR = 135,
MKDIR = 136,
RMDIR = 137,
UTIMES = 138,
FUTIMES = 139,
ADJTIME = 140,
GETHOSTUUID = 142,
SETSID = 147,
GETPGID = 151,
SETPRIVEXEC = 152,
PREAD = 153,
PWRITE = 154,
NFSSVC = 155,
STATFS = 157,
FSTATFS = 158,
UNMOUNT = 159,
GETFH = 161,
FUNMOUNT = 164,
QUOTACTL = 165,
MOUNT = 167,
CSOPS = 169,
CSOPS_AUDITTOKEN = 170,
WAITID = 173,
KDEBUG_TYPEFILTER = 177,
KDEBUG_TRACE_STRING = 178,
KDEBUG_TRACE64 = 179,
KDEBUG_TRACE = 180,
SETGID = 181,
SETEGID = 182,
SETEUID = 183,
SIGRETURN = 184,
PANIC_WITH_DATA = 185,
THREAD_SELFCOUNTS = 186,
FDATASYNC = 187,
STAT = 188,
FSTAT = 189,
LSTAT = 190,
PATHCONF = 191,
FPATHCONF = 192,
GETRLIMIT = 194,
SETRLIMIT = 195,
GETDIRENTRIES = 196,
MMAP = 197,
LSEEK = 199,
TRUNCATE = 200,
FTRUNCATE = 201,
SYSCTL = 202,
MLOCK = 203,
MUNLOCK = 204,
UNDELETE = 205,
OPEN_DPROTECTED_NP = 216,
FSGETPATH_EXT = 217,
OPENAT_DPROTECTED_NP = 218,
GETATTRLIST = 220,
SETATTRLIST = 221,
GETDIRENTRIESATTR = 222,
EXCHANGEDATA = 223,
SEARCHFS = 225,
DELETE = 226,
COPYFILE = 227,
FGETATTRLIST = 228,
FSETATTRLIST = 229,
POLL = 230,
GETXATTR = 234,
FGETXATTR = 235,
SETXATTR = 236,
FSETXATTR = 237,
REMOVEXATTR = 238,
FREMOVEXATTR = 239,
LISTXATTR = 240,
FLISTXATTR = 241,
FSCTL = 242,
INITGROUPS = 243,
POSIX_SPAWN = 244,
FFSCTL = 245,
FHOPEN = 248,
MINHERIT = 250,
SEMSYS = 251,
MSGSYS = 252,
SHMSYS = 253,
SEMCTL = 254,
SEMGET = 255,
SEMOP = 256,
MSGCTL = 258,
MSGGET = 259,
MSGSND = 260,
MSGRCV = 261,
SHMAT = 262,
SHMCTL = 263,
SHMDT = 264,
SHMGET = 265,
SHM_OPEN = 266,
SHM_UNLINK = 267,
SEM_OPEN = 268,
SEM_CLOSE = 269,
SEM_UNLINK = 270,
SEM_WAIT = 271,
SEM_TRYWAIT = 272,
SEM_POST = 273,
SYSCTLBYNAME = 274,
OPEN_EXTENDED = 277,
UMASK_EXTENDED = 278,
STAT_EXTENDED = 279,
LSTAT_EXTENDED = 280,
FSTAT_EXTENDED = 281,
CHMOD_EXTENDED = 282,
FCHMOD_EXTENDED = 283,
ACCESS_EXTENDED = 284,
SETTID = 285,
GETTID = 286,
SETSGROUPS = 287,
GETSGROUPS = 288,
SETWGROUPS = 289,
GETWGROUPS = 290,
MKFIFO_EXTENDED = 291,
MKDIR_EXTENDED = 292,
IDENTITYSVC = 293,
SHARED_REGION_CHECK_NP = 294,
VM_PRESSURE_MONITOR = 296,
PSYNCH_RW_LONGRDLOCK = 297,
PSYNCH_RW_YIELDWRLOCK = 298,
PSYNCH_RW_DOWNGRADE = 299,
PSYNCH_RW_UPGRADE = 300,
PSYNCH_MUTEXWAIT = 301,
PSYNCH_MUTEXDROP = 302,
PSYNCH_CVBROAD = 303,
PSYNCH_CVSIGNAL = 304,
PSYNCH_CVWAIT = 305,
PSYNCH_RW_RDLOCK = 306,
PSYNCH_RW_WRLOCK = 307,
PSYNCH_RW_UNLOCK = 308,
PSYNCH_RW_UNLOCK2 = 309,
GETSID = 310,
SETTID_WITH_PID = 311,
PSYNCH_CVCLRPREPOST = 312,
AIO_FSYNC = 313,
AIO_RETURN = 314,
AIO_SUSPEND = 315,
AIO_CANCEL = 316,
AIO_ERROR = 317,
AIO_READ = 318,
AIO_WRITE = 319,
LIO_LISTIO = 320,
IOPOLICYSYS = 322,
PROCESS_POLICY = 323,
MLOCKALL = 324,
MUNLOCKALL = 325,
ISSETUGID = 327,
PTHREAD_KILL = 328,
PTHREAD_SIGMASK = 329,
SIGWAIT = 330,
DISABLE_THREADSIGNAL = 331,
PTHREAD_MARKCANCEL = 332,
PTHREAD_CANCELED = 333,
SEMWAIT_SIGNAL = 334,
PROC_INFO = 336,
SENDFILE = 337,
STAT64 = 338,
FSTAT64 = 339,
LSTAT64 = 340,
STAT64_EXTENDED = 341,
LSTAT64_EXTENDED = 342,
FSTAT64_EXTENDED = 343,
GETDIRENTRIES64 = 344,
STATFS64 = 345,
FSTATFS64 = 346,
GETFSSTAT64 = 347,
PTHREAD_CHDIR = 348,
PTHREAD_FCHDIR = 349,
AUDIT = 350,
AUDITON = 351,
GETAUID = 353,
SETAUID = 354,
GETAUDIT_ADDR = 357,
SETAUDIT_ADDR = 358,
AUDITCTL = 359,
BSDTHREAD_CREATE = 360,
BSDTHREAD_TERMINATE = 361,
KQUEUE = 362,
KEVENT = 363,
LCHOWN = 364,
BSDTHREAD_REGISTER = 366,
WORKQ_OPEN = 367,
WORKQ_KERNRETURN = 368,
KEVENT64 = 369,
THREAD_SELFID = 372,
LEDGER = 373,
KEVENT_QOS = 374,
KEVENT_ID = 375,
MAC_EXECVE = 380,
MAC_SYSCALL = 381,
MAC_GET_FILE = 382,
MAC_SET_FILE = 383,
MAC_GET_LINK = 384,
MAC_SET_LINK = 385,
MAC_GET_PROC = 386,
MAC_SET_PROC = 387,
MAC_GET_FD = 388,
MAC_SET_FD = 389,
MAC_GET_PID = 390,
PSELECT = 394,
PSELECT_NOCANCEL = 395,
READ_NOCANCEL = 396,
WRITE_NOCANCEL = 397,
OPEN_NOCANCEL = 398,
CLOSE_NOCANCEL = 399,
WAIT4_NOCANCEL = 400,
RECVMSG_NOCANCEL = 401,
SENDMSG_NOCANCEL = 402,
RECVFROM_NOCANCEL = 403,
ACCEPT_NOCANCEL = 404,
MSYNC_NOCANCEL = 405,
FCNTL_NOCANCEL = 406,
SELECT_NOCANCEL = 407,
FSYNC_NOCANCEL = 408,
CONNECT_NOCANCEL = 409,
SIGSUSPEND_NOCANCEL = 410,
READV_NOCANCEL = 411,
WRITEV_NOCANCEL = 412,
SENDTO_NOCANCEL = 413,
PREAD_NOCANCEL = 414,
PWRITE_NOCANCEL = 415,
WAITID_NOCANCEL = 416,
POLL_NOCANCEL = 417,
MSGSND_NOCANCEL = 418,
MSGRCV_NOCANCEL = 419,
SEM_WAIT_NOCANCEL = 420,
AIO_SUSPEND_NOCANCEL = 421,
SIGWAIT_NOCANCEL = 422,
SEMWAIT_SIGNAL_NOCANCEL = 423,
MAC_MOUNT = 424,
MAC_GET_MOUNT = 425,
MAC_GETFSSTAT = 426,
FSGETPATH = 427,
AUDIT_SESSION_SELF = 428,
AUDIT_SESSION_JOIN = 429,
FILEPORT_MAKEPORT = 430,
FILEPORT_MAKEFD = 431,
AUDIT_SESSION_PORT = 432,
PID_SUSPEND = 433,
PID_RESUME = 434,
PID_HIBERNATE = 435,
PID_SHUTDOWN_SOCKETS = 436,
KAS_INFO = 439,
MEMORYSTATUS_CONTROL = 440,
GUARDED_OPEN_NP = 441,
GUARDED_CLOSE_NP = 442,
GUARDED_KQUEUE_NP = 443,
CHANGE_FDGUARD_NP = 444,
USRCTL = 445,
PROC_RLIMIT_CONTROL = 446,
CONNECTX = 447,
DISCONNECTX = 448,
PEELOFF = 449,
SOCKET_DELEGATE = 450,
TELEMETRY = 451,
PROC_UUID_POLICY = 452,
MEMORYSTATUS_GET_LEVEL = 453,
SYSTEM_OVERRIDE = 454,
VFS_PURGE = 455,
SFI_CTL = 456,
SFI_PIDCTL = 457,
COALITION = 458,
COALITION_INFO = 459,
NECP_MATCH_POLICY = 460,
GETATTRLISTBULK = 461,
CLONEFILEAT = 462,
OPENAT = 463,
OPENAT_NOCANCEL = 464,
RENAMEAT = 465,
FACCESSAT = 466,
FCHMODAT = 467,
FCHOWNAT = 468,
FSTATAT = 469,
FSTATAT64 = 470,
LINKAT = 471,
UNLINKAT = 472,
READLINKAT = 473,
SYMLINKAT = 474,
MKDIRAT = 475,
GETATTRLISTAT = 476,
PROC_TRACE_LOG = 477,
BSDTHREAD_CTL = 478,
OPENBYID_NP = 479,
RECVMSG_X = 480,
SENDMSG_X = 481,
THREAD_SELFUSAGE = 482,
CSRCTL = 483,
GUARDED_OPEN_DPROTECTED_NP = 484,
GUARDED_WRITE_NP = 485,
GUARDED_PWRITE_NP = 486,
GUARDED_WRITEV_NP = 487,
RENAMEATX_NP = 488,
MREMAP_ENCRYPTED = 489,
NETAGENT_TRIGGER = 490,
STACK_SNAPSHOT_WITH_CONFIG = 491,
MICROSTACKSHOT = 492,
GRAB_PGO_DATA = 493,
PERSONA = 494,
MACH_EVENTLINK_SIGNAL = 496,
MACH_EVENTLINK_WAIT_UNTIL = 497,
MACH_EVENTLINK_SIGNAL_WAIT_UNTIL = 498,
WORK_INTERVAL_CTL = 499,
GETENTROPY = 500,
NECP_CLIENT_ACTION = 502,
NEXUS_OPEN = 503,
NEXUS_REGISTER = 504,
NEXUS_DEREGISTER = 505,
NEXUS_CREATE = 506,
NEXUS_DESTROY = 507,
NEXUS_GET_OPT = 508,
NEXUS_SET_OPT = 509,
CHANNEL_OPEN = 510,
CHANNEL_GET_INFO = 511,
CHANNEL_SYNC = 512,
CHANNEL_GET_OPT = 513,
CHANNEL_SET_OPT = 514,
ULOCK_WAIT = 515,
ULOCK_WAKE = 516,
FCLONEFILEAT = 517,
FS_SNAPSHOT = 518,
TERMINATE_WITH_PAYLOAD = 520,
ABORT_WITH_PAYLOAD = 521,
NECP_SESSION_ACTION = 523,
SETATTRLISTAT = 524,
NET_QOS_GUIDELINE = 525,
FMOUNT = 526,
NTP_ADJTIME = 527,
NTP_GETTIME = 528,
OS_FAULT_WITH_PAYLOAD = 529,
KQUEUE_WORKLOOP_CTL = 530,
MACH_BRIDGE_REMOTE_TIME = 531,
COALITION_LEDGER = 532,
LOG_DATA = 533,
MEMORYSTATUS_AVAILABLE_MEMORY = 534,
OBJC_BP_ASSIST_CFG_NP = 535,
SHARED_REGION_MAP_AND_SLIDE_2_NP = 536,
PIVOT_ROOT = 537,
TASK_INSPECT_FOR_PID = 538,
TASK_READ_FOR_PID = 539,
PREADV = 540,
PWRITEV = 541,
PREADV_NOCANCEL = 542,
PWRITEV_NOCANCEL = 543,
ULOCK_WAIT2 = 544,
PROC_INFO_EXTENDED_ID = 545,
TRACKER_ACTION = 546,
DEBUG_SYSCALL_REJECT = 547,
DEBUG_SYSCALL_REJECT_CONFIG = 548,
GRAFTDMG = 549,
MAP_WITH_LINKING_NP = 550,
FREADLINK = 551,
RECORD_SYSTEM_EVENT = 552,
MKFIFOAT = 553,
MKNODAT = 554,
UNGRAFTDMG = 555,
COALITION_POLICY_SET = 556,
COALITION_POLICY_GET = 557,
}
}
+11
View File
@@ -0,0 +1,11 @@
FRIDA_1.0 {
local:
*;
};
GLIBC_2.2.5 {
local:
fopen*;
fseeko*;
ftello*;
} FRIDA_1.0;
+2 -4
View File
@@ -144,10 +144,8 @@ frida_gadget_environment_ensure_debugger_breakpoints_only (void)
interceptor = gum_interceptor_obtain ();
gum_interceptor_attach (interceptor, exceptions_set, frida_dont_steal_my_breakpoints,
NULL, GUM_ATTACH_FLAGS_NONE);
gum_interceptor_attach (interceptor, exceptions_swap, frida_dont_steal_my_breakpoints,
NULL, GUM_ATTACH_FLAGS_NONE);
gum_interceptor_attach (interceptor, exceptions_set, frida_dont_steal_my_breakpoints, NULL);
gum_interceptor_attach (interceptor, exceptions_swap, frida_dont_steal_my_breakpoints, NULL);
g_object_unref (interceptor);
}
+8 -2
View File
@@ -9,7 +9,9 @@
# include <signal.h>
#endif
#include <gumjs/gumscriptbackend.h>
#ifdef HAVE_GIOOPENSSL
#if defined (HAVE_GIOAPPLE)
# include <gioapple.h>
#elif defined (HAVE_GIOOPENSSL)
# include <gioopenssl.h>
#endif
@@ -91,7 +93,9 @@ frida_gadget_environment_init (void)
g_thread_set_garbage_handler (_frida_gadget_on_pending_thread_garbage, NULL);
#ifdef HAVE_GIOOPENSSL
#if defined (HAVE_GIOAPPLE)
g_io_module_apple_register ();
#elif defined (HAVE_GIOOPENSSL)
g_io_module_openssl_register ();
#endif
@@ -118,6 +122,8 @@ frida_gadget_environment_deinit (void)
g_assert (worker_loop != NULL);
frida_libc_shim_prepare_to_deinit ();
source = g_idle_source_new ();
g_source_set_priority (source, G_PRIORITY_LOW);
g_source_set_callback (source, stop_worker_loop, NULL, NULL);
+5
View File
@@ -1810,6 +1810,11 @@ namespace Frida.Gadget {
throw new Error.NOT_SUPPORTED ("Not possible when embedded");
}
public async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
Cancellable? cancellable) throws Error, IOError {
throw new Error.NOT_SUPPORTED ("Not possible when embedded");
}
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
throw new Error.NOT_SUPPORTED ("Not possible when embedded");
}
+1 -1
View File
@@ -45,7 +45,7 @@ elif host_os_family == 'darwin'
extra_link_args += '-Wl,-exported_symbols_list,' + meson.current_source_dir() / symlist
extra_link_depends += symlist
else
symscript = 'frida-gadget.version'
symscript = (libc == 'glibc') ? 'frida-gadget-glibc.version' : 'frida-gadget.version'
extra_link_args += '-Wl,--version-script,' + meson.current_source_dir() / symscript
extra_link_depends += symscript
endif
+4 -2
View File
@@ -4,10 +4,12 @@ endif
subdir('base')
subdir('netif')
subdir('pipe')
subdir('payload')
if have_in_tree_payloads
subdir('payload')
endif
if have_local_backend
subdir('agent')
endif
if build_gadget
if have_in_tree_payloads and build_gadget
subdir('gadget')
endif
+12
View File
@@ -179,6 +179,18 @@ namespace Frida {
yield script_engine.load_script (script_id);
}
public async void interrupt_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
check_open ();
script_engine.interrupt_script (script_id);
}
public async void terminate_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
check_open ();
yield script_engine.terminate_script (script_id);
}
public async void eternalize_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
check_open ();
+11 -4
View File
@@ -65,7 +65,9 @@ namespace Frida {
if (runtime != null) {
var set_argv0 = (void *) runtime.find_export_by_name ("_Z27android_os_Process_setArgV0P7_JNIEnvP8_jobjectP8_jstring");
if (set_argv0 != null) {
interceptor.attach (set_argv0, listener, (void *) HookId.SET_ARGV0);
var set_argv0_options = Gum.AttachOptions ();
set_argv0_options.listener_function_data = (void *) HookId.SET_ARGV0;
interceptor.attach (set_argv0, listener, set_argv0_options);
child_recovery_behavior = DEFERRED_UNTIL_SET_ARGV0;
}
}
@@ -73,8 +75,11 @@ namespace Frida {
var selinux = Gum.Process.find_module_by_name ("libselinux.so");
if (selinux != null) {
var setcontext = (void *) selinux.find_export_by_name ("selinux_android_setcontext");
if (setcontext != null)
interceptor.attach (setcontext, listener, (void *) HookId.SET_CTX);
if (setcontext != null) {
var setcontext_options = Gum.AttachOptions ();
setcontext_options.listener_function_data = (void *) HookId.SET_CTX;
interceptor.attach (setcontext, listener, setcontext_options);
}
}
}
} catch (FileError e) {
@@ -82,7 +87,9 @@ namespace Frida {
}
#endif
interceptor.attach (fork_impl, listener, (void *) HookId.FORK);
var fork_options = Gum.AttachOptions ();
fork_options.listener_function_data = (void *) HookId.FORK;
interceptor.attach (fork_impl, listener, fork_options);
interceptor.replace (vfork_impl, fork_impl);
}
+404 -30
View File
@@ -10,6 +10,7 @@
# include <assert.h>
# include <fcntl.h>
# include <unistd.h>
# include <wchar.h>
#endif
#if defined (HAVE_DARWIN)
# include <sys/attr.h>
@@ -23,23 +24,50 @@
# include <xlocale.h>
#endif
#undef __srget
#undef feof
#undef ferror
#undef fgetwc
#undef fopen
#undef fopen64
#undef fputwc
#undef fseek
#undef fseeko
#undef fseeko64
#undef ftell
#undef ftello
#undef ftello64
#undef getc
#undef getc_unlocked
#undef getwc
#undef memcpy
#undef putc
#undef putchar
#undef putwc
#undef setbuf
#undef setbuffer
#undef setlinebuf
#undef snprintf
#undef sprintf
#undef stderr
#undef stdin
#undef stdout
#undef ungetc
#undef ungetwc
#undef vsnprintf
#ifdef HAVE_MUSL
# define FRIDA_STDIO_OPAQUE_FILE 1
#endif
static gboolean frida_deinit_expected = FALSE;
void
frida_libc_shim_prepare_to_deinit (void)
{
frida_deinit_expected = TRUE;
}
#if defined (HAVE_WINDOWS) || defined (HAVE_ASAN)
void
@@ -70,6 +98,9 @@ __attribute__ ((destructor)) static
void
frida_libc_shim_deinit (void)
{
if (!frida_deinit_expected)
return;
# ifdef HAVE_ASAN
gum_deinit ();
# else
@@ -134,16 +165,18 @@ struct _FridaFile
int err;
guint8 * rbuf;
gboolean owns_rbuf;
size_t rcap;
size_t rpos;
size_t rlen;
guint8 * wbuf;
gboolean owns_wbuf;
size_t wcap;
size_t wlen;
int ungot;
gboolean has_ungot;
guint8 ungot_buf[8];
guint ungot_len;
};
struct _FridaFileHandle
@@ -171,6 +204,10 @@ struct _FridaDir
#endif
static FILE * frida_fopen_impl (const char * pathname, const char * mode);
static int frida_fseek_impl (FILE * stream, off_t offset, int whence);
static off_t frida_ftell_impl (FILE * stream);
static void frida_stdio_register_stream (FILE * stream);
static void frida_stdio_unregister_stream (FILE * stream);
#ifdef HAVE_FRIDA_DIR
@@ -193,6 +230,8 @@ static void frida_parse_fopen_mode (const char * mode, int * oflags);
static int frida_write_formatted_to_fd (int fd, const char * format, va_list args);
static gboolean frida_utf8_expected_len (guint8 first, guint * out_len);
#ifdef HAVE_FRIDA_DIR
static FridaDir * frida_dir_get_impl (DIR * dirp);
static DIR * frida_dir_wrap (FridaDir * impl);
@@ -317,6 +356,9 @@ frida_libc_shim_deinit (void)
fflush (NULL);
if (!frida_deinit_expected)
return;
G_LOCK (frida_stdio);
g_hash_table_iter_init (&iter, frida_streams);
@@ -639,8 +681,34 @@ asprintf_l (char ** ret, locale_t loc, const char * format, ...)
#endif
#ifdef HAVE_GLIBC
G_GNUC_INTERNAL FILE *
frida_fopen_glibc_225 (const char * pathname, const char * mode)
{
return frida_fopen_impl (pathname, mode);
}
__asm__ (".symver frida_fopen_glibc_225,fopen@@GLIBC_2.2.5");
G_GNUC_INTERNAL FILE *
frida_fopen64_glibc_225 (const char * pathname, const char * mode)
{
return frida_fopen_impl (pathname, mode);
}
__asm__ (".symver frida_fopen64_glibc_225,fopen64@@GLIBC_2.2.5");
#else
G_GNUC_INTERNAL FILE *
fopen (const char * pathname, const char * mode)
{
return frida_fopen_impl (pathname, mode);
}
#endif
static FILE *
frida_fopen_impl (const char * pathname, const char * mode)
{
FILE * result;
int oflags, fd;
@@ -714,8 +782,17 @@ setvbuf (FILE * stream, char * buf, int mode, size_t size)
if (mode == _IONBF)
{
g_clear_pointer (&f->rbuf, g_free);
g_clear_pointer (&f->wbuf, g_free);
if (f->owns_rbuf)
g_clear_pointer (&f->rbuf, g_free);
else
f->rbuf = NULL;
f->owns_rbuf = FALSE;
if (f->owns_wbuf)
g_clear_pointer (&f->wbuf, g_free);
else
f->wbuf = NULL;
f->owns_wbuf = FALSE;
f->rcap = 0;
f->wcap = 0;
@@ -727,13 +804,40 @@ setvbuf (FILE * stream, char * buf, int mode, size_t size)
}
if (size == 0)
size = FRIDA_STDIO_BUFSIZE;
size = BUFSIZ;
if (!f->owns_rbuf)
{
f->rbuf = NULL;
f->rcap = 0;
}
f->rbuf = g_realloc (f->rbuf, size);
f->wbuf = g_realloc (f->wbuf, size);
f->owns_rbuf = TRUE;
f->rcap = size;
f->wcap = size;
if (buf != NULL)
{
if (f->owns_wbuf)
g_clear_pointer (&f->wbuf, g_free);
f->wbuf = (guint8 *) buf;
f->owns_wbuf = FALSE;
f->wcap = size;
}
else
{
if (!f->owns_wbuf)
{
f->wbuf = NULL;
f->wcap = 0;
}
f->wbuf = g_realloc (f->wbuf, size);
f->owns_wbuf = TRUE;
f->wcap = size;
}
f->rpos = 0;
f->rlen = 0;
f->wlen = 0;
@@ -741,6 +845,39 @@ setvbuf (FILE * stream, char * buf, int mode, size_t size)
return 0;
}
G_GNUC_INTERNAL void
setbuf (FILE * stream, char * buf)
{
if (buf == NULL)
(void) setvbuf (stream, NULL, _IONBF, 0);
else
(void) setvbuf (stream, buf, _IOFBF, BUFSIZ);
}
#if defined (HAVE_DARWIN) || defined (HAVE_ANDROID) || defined (HAVE_FREEBSD)
G_GNUC_INTERNAL int
setlinebuf (FILE * stream)
{
return setvbuf (stream, NULL, _IOLBF, 0);
}
#else
G_GNUC_INTERNAL void
setlinebuf (FILE * stream)
{
setvbuf (stream, NULL, _IOLBF, 0);
}
#endif
G_GNUC_INTERNAL void
#if defined (HAVE_DARWIN) || defined (HAVE_ANDROID) || defined (HAVE_FREEBSD)
setbuffer (FILE * stream, char * buf, int size)
#else
setbuffer (FILE * stream, char * buf, size_t size)
#endif
{
(void) setvbuf (stream, buf, (buf != NULL) ? _IOFBF : _IONBF, size);
}
G_GNUC_INTERNAL int
fflush (FILE * stream)
{
@@ -801,11 +938,8 @@ getc_unlocked (FILE * stream)
f = frida_file_get_impl (stream);
if (f->has_ungot)
{
f->has_ungot = FALSE;
return f->ungot;
}
if (f->ungot_len != 0)
return f->ungot_buf[--f->ungot_len];
if (f->buf_mode == _IONBF)
{
@@ -846,6 +980,64 @@ getc (FILE * stream)
return getc_unlocked (stream);
}
G_GNUC_INTERNAL int
__srget (FILE * stream)
{
return getc_unlocked (stream);
}
G_GNUC_INTERNAL wint_t
getwc (FILE * stream)
{
return fgetwc (stream);
}
G_GNUC_INTERNAL wint_t
fgetwc (FILE * stream)
{
char bytes[4];
guint need, have;
int c;
gunichar ch;
c = getc_unlocked (stream);
if (c == EOF)
return WEOF;
bytes[0] = c;
if (!frida_utf8_expected_len (bytes[0], &need))
{
errno = EILSEQ;
frida_file_get_impl (stream)->err = errno;
return WEOF;
}
have = 1;
while (have != need)
{
c = getc_unlocked (stream);
if (c == EOF)
{
errno = EILSEQ;
frida_file_get_impl (stream)->err = errno;
return WEOF;
}
bytes[have++] = c;
}
ch = g_utf8_get_char_validated (bytes, (gssize) need);
if (ch == (gunichar) -1 || ch == (gunichar) -2)
{
errno = EILSEQ;
frida_file_get_impl (stream)->err = errno;
return WEOF;
}
return (wint_t) ch;
}
G_GNUC_INTERNAL int
ungetc (int c, FILE * stream)
{
@@ -856,16 +1048,46 @@ ungetc (int c, FILE * stream)
if (c == EOF)
return EOF;
if (f->has_ungot)
if (f->ungot_len == G_N_ELEMENTS (f->ungot_buf))
return EOF;
f->ungot = c;
f->has_ungot = TRUE;
f->ungot_buf[f->ungot_len++] = c;
f->eof = FALSE;
return c;
}
G_GNUC_INTERNAL wint_t
ungetwc (wint_t wc, FILE * stream)
{
FridaFile * f;
char tmp[4];
int len;
int i;
if (wc == WEOF)
return WEOF;
f = frida_file_get_impl (stream);
len = g_unichar_to_utf8 ((gunichar) wc, tmp);
if (len <= 0 || len > 4)
{
errno = EILSEQ;
f->err = errno;
return WEOF;
}
if (f->ungot_len + len > G_N_ELEMENTS (f->ungot_buf))
return WEOF;
for (i = 0; i != len; i++)
f->ungot_buf[f->ungot_len++] = tmp[i];
f->eof = FALSE;
return wc;
}
G_GNUC_INTERNAL size_t
fread (void * ptr, size_t size, size_t nmemb, FILE * stream)
{
@@ -884,10 +1106,9 @@ fread (void * ptr, size_t size, size_t nmemb, FILE * stream)
while (got != want)
{
if (f->has_ungot)
if (f->ungot_len != 0)
{
out[got++] = (guint8) f->ungot;
f->has_ungot = FALSE;
out[got++] = f->ungot_buf[--f->ungot_len];
continue;
}
@@ -1006,6 +1227,12 @@ fwrite (const void * ptr, size_t size, size_t nmemb, FILE * stream)
return off / size;
}
G_GNUC_INTERNAL int
putc (int c, FILE * stream)
{
return fputc (c, stream);
}
G_GNUC_INTERNAL int
fputc (int c, FILE * stream)
{
@@ -1019,6 +1246,37 @@ fputc (int c, FILE * stream)
return ch;
}
G_GNUC_INTERNAL wint_t
putwc (wchar_t wc, FILE * stream)
{
return fputwc (wc, stream);
}
G_GNUC_INTERNAL wint_t
fputwc (wchar_t wc, FILE * stream)
{
char tmp[4];
int len;
size_t n;
if (wc == WEOF)
return WEOF;
len = g_unichar_to_utf8 ((gunichar) wc, tmp);
if (len <= 0 || len > 4)
{
errno = EILSEQ;
frida_file_get_impl (stream)->err = errno;
return WEOF;
}
n = fwrite (tmp, 1, len, stream);
if (n != len)
return WEOF;
return (wint_t) wc;
}
G_GNUC_INTERNAL int
fputs (const char * s, FILE * stream)
{
@@ -1062,6 +1320,84 @@ fgets (char * s, int size, FILE * stream)
G_GNUC_INTERNAL int
fseek (FILE * stream, long offset, int whence)
{
return frida_fseek_impl (stream, (off_t) offset, whence);
}
G_GNUC_INTERNAL long
ftell (FILE * stream)
{
off_t pos = frida_ftell_impl (stream);
if (pos == (off_t) -1)
return -1;
return (long) pos;
}
#ifdef HAVE_GLIBC
G_GNUC_INTERNAL int
frida_fseeko_glibc_225 (FILE * stream, off_t offset, int whence)
{
return frida_fseek_impl (stream, offset, whence);
}
__asm__ (".symver frida_fseeko_glibc_225,fseeko@@GLIBC_2.2.5");
G_GNUC_INTERNAL int
frida_fseeko64_glibc_225 (FILE * stream, off_t offset, int whence)
{
return frida_fseek_impl (stream, offset, whence);
}
__asm__ (".symver frida_fseeko64_glibc_225,fseeko64@@GLIBC_2.2.5");
G_GNUC_INTERNAL off_t
frida_ftello_glibc_225 (FILE * stream)
{
return frida_ftell_impl (stream);
}
__asm__ (".symver frida_ftello_glibc_225,ftello@@GLIBC_2.2.5");
G_GNUC_INTERNAL off_t
frida_ftello64_glibc_225 (FILE * stream)
{
return frida_ftell_impl (stream);
}
__asm__ (".symver frida_ftello64_glibc_225,ftello64@@GLIBC_2.2.5");
#else
G_GNUC_INTERNAL int
fseeko (FILE * stream, off_t offset, int whence)
{
return frida_fseek_impl (stream, offset, whence);
}
G_GNUC_INTERNAL off_t
ftello (FILE * stream)
{
return frida_ftell_impl (stream);
}
# ifndef HAVE_QNX
G_GNUC_INTERNAL int
fseeko64 (FILE * stream, off_t offset, int whence)
{
return frida_fseek_impl (stream, offset, whence);
}
G_GNUC_INTERNAL off_t
ftello64 (FILE * stream)
{
return frida_ftell_impl (stream);
}
# endif
#endif
static int
frida_fseek_impl (FILE * stream, off_t offset, int whence)
{
FridaFile * f;
off_t r;
@@ -1073,10 +1409,10 @@ fseek (FILE * stream, long offset, int whence)
f->rpos = 0;
f->rlen = 0;
f->has_ungot = FALSE;
f->ungot_len = 0;
f->eof = FALSE;
r = frida_lseek_nointr (f->fd, (off_t) offset, whence);
r = frida_lseek_nointr (f->fd, offset, whence);
if (r == (off_t) -1)
{
f->err = errno;
@@ -1086,8 +1422,8 @@ fseek (FILE * stream, long offset, int whence)
return 0;
}
G_GNUC_INTERNAL long
ftell (FILE * stream)
static off_t
frida_ftell_impl (FILE * stream)
{
FridaFile * f;
off_t pos;
@@ -1104,10 +1440,9 @@ ftell (FILE * stream)
if (f->buf_mode != _IONBF)
pos -= (off_t) (f->rlen - f->rpos);
if (f->has_ungot)
pos -= 1;
pos -= f->ungot_len;
return (long) pos;
return pos;
}
G_GNUC_INTERNAL void
@@ -1718,10 +2053,12 @@ frida_file_new (int fd, gboolean close_fd, int buf_mode)
if (buf_mode != _IONBF)
{
f->rcap = FRIDA_STDIO_BUFSIZE;
f->wcap = FRIDA_STDIO_BUFSIZE;
f->rbuf = g_malloc (f->rcap);
f->owns_rbuf = TRUE;
f->wcap = FRIDA_STDIO_BUFSIZE;
f->wbuf = g_malloc (f->wcap);
f->owns_wbuf = TRUE;
}
return f;
@@ -1733,8 +2070,15 @@ frida_file_free (FridaFile * f)
if (f == NULL)
return;
g_clear_pointer (&f->rbuf, g_free);
g_clear_pointer (&f->wbuf, g_free);
if (f->owns_rbuf)
g_clear_pointer (&f->rbuf, g_free);
else
f->rbuf = NULL;
if (f->owns_wbuf)
g_clear_pointer (&f->wbuf, g_free);
else
f->wbuf = NULL;
g_free (f);
}
@@ -1866,6 +2210,36 @@ beach:
}
}
static gboolean
frida_utf8_expected_len (guint8 first, guint * out_len)
{
if (first < 0x80)
{
*out_len = 1;
return TRUE;
}
if ((first & 0xe0) == 0xc0)
{
*out_len = 2;
return TRUE;
}
if ((first & 0xf0) == 0xe0)
{
*out_len = 3;
return TRUE;
}
if ((first & 0xf8) == 0xf0)
{
*out_len = 4;
return TRUE;
}
return FALSE;
}
#ifdef HAVE_FRIDA_DIR
static FridaDir *
+1 -1
View File
@@ -49,7 +49,7 @@ payload = static_library('frida-payload-' + api_version, payload_sources,
],
vala_header: 'frida-payload.h',
vala_vapi: f'frida-payload-@[email protected]',
dependencies: [gio_dep, gee_dep, json_glib_dep, gum_dep, gumjs_dep, base_dep],
dependencies: [gio_dep, gee_dep, json_glib_dep, gum_dep, gumjs_dep, gumpharo_dep, base_dep],
install: true,
install_dir: [true, header_install_dir, true],
)
+1
View File
@@ -1,4 +1,5 @@
namespace Frida {
public extern void libc_shim_prepare_to_deinit ();
#if DARWIN
public extern void libc_shim_deinit ();
#endif
+171 -15
View File
@@ -91,7 +91,27 @@ namespace Frida {
if (name == null)
name = "script%u".printf (script_id.handle);
Gum.ScriptBackend backend = pick_backend (options.runtime);
ScriptRuntime runtime = options.runtime;
if (runtime == DEFAULT)
runtime = preferred_runtime;
#if HAVE_GUMPHARO
if (runtime == PHARO) {
if (source == null)
throw new Error.NOT_SUPPORTED ("The Pharo runtime cannot load precompiled scripts");
var instance = new PharoScriptInstance (script_id, new Gum.PharoScript (name, source));
instances[script_id] = instance;
instance.closed.connect (on_instance_closed);
instance.message.connect (on_instance_message);
instance.debug_message.connect (on_instance_debug_message);
return instance;
}
#endif
Gum.ScriptBackend backend = pick_backend (runtime);
Gum.Script script;
try {
@@ -109,7 +129,8 @@ namespace Frida {
return Source.REMOVE;
});
var instance = new ScriptInstance (script_id, script);
var js_instance = new JSScriptInstance (script_id, script);
ScriptInstance instance = js_instance;
instances[script_id] = instance;
instance.closed.connect (on_instance_closed);
@@ -166,8 +187,18 @@ namespace Frida {
yield get_instance (script_id).load ();
}
public void interrupt_script (AgentScriptId script_id) throws Error {
get_instance (script_id).interrupt ();
}
public async void terminate_script (AgentScriptId script_id) throws Error {
yield get_instance (script_id).terminate ();
}
public Gum.Script eternalize_script (AgentScriptId script_id) throws Error {
var instance = get_instance (script_id);
var instance = get_instance (script_id) as JSScriptInstance;
if (instance == null)
throw new Error.NOT_SUPPORTED ("Only JavaScript scripts may be eternalized");
var script = instance.eternalize ();
@@ -223,7 +254,7 @@ namespace Frida {
source.attach (Gum.ScriptBackend.get_scheduler ().get_js_context ());
}
public sealed class ScriptInstance : Object, RpcPeer {
public abstract class ScriptInstance : Object, RpcPeer {
public signal void closed ();
public signal void message (string json, Bytes? data);
public signal void debug_message (string message);
@@ -233,6 +264,120 @@ namespace Frida {
construct;
}
public abstract async void close ();
public abstract async void flush ();
public abstract async void load () throws Error;
public abstract void post (string json, Bytes? data) throws Error;
public abstract async void prepare_for_termination (TerminationReason reason);
public abstract void unprepare_for_termination ();
public abstract async void post_rpc_message (string json, Bytes? data, Cancellable? cancellable)
throws Error, IOError;
public virtual void interrupt () throws Error {
throw new Error.NOT_SUPPORTED ("Interrupt is not available for this runtime");
}
public virtual async void terminate () {
yield close ();
}
public virtual void enable_debugger () throws Error {
throw new Error.NOT_SUPPORTED ("Debugger is not available for this runtime");
}
public virtual void disable_debugger () {
}
public virtual void post_debug_message (string message) throws Error {
throw new Error.NOT_SUPPORTED ("Debugger is not available for this runtime");
}
}
#if HAVE_GUMPHARO
public sealed class PharoScriptInstance : ScriptInstance {
private Gum.PharoScript? script;
private State state = CREATED;
private enum State {
CREATED,
LOADED,
UNLOADED,
DESTROYED
}
private RpcClient rpc_client;
public PharoScriptInstance (AgentScriptId script_id, Gum.PharoScript script) {
Object (script_id: script_id);
this.script = script;
script.set_message_handler (on_message);
}
construct {
rpc_client = new RpcClient (this);
}
public override async void close () {
if (state == DESTROYED)
return;
if (state == LOADED) {
script.unload ();
state = UNLOADED;
}
script = null;
state = DESTROYED;
closed ();
}
public override async void flush () {
yield close ();
}
public override async void load () throws Error {
if (state != CREATED)
throw new Error.INVALID_OPERATION ("Script cannot be loaded in its current state");
script.load ();
state = LOADED;
}
public override void post (string json, Bytes? data) throws Error {
if (state != LOADED)
throw new Error.INVALID_OPERATION ("Only active scripts may be posted to");
script.post (json, data);
}
public override async void prepare_for_termination (TerminationReason reason) {
}
public override void unprepare_for_termination () {
}
private void on_message (Gum.PharoScript script, string json, Bytes? data) {
bool handled = rpc_client.try_handle_message (json);
if (!handled)
this.message (json, data);
}
public override async void post_rpc_message (string json, Bytes? data, Cancellable? cancellable)
throws Error, IOError {
if (script == null)
throw new Error.INVALID_OPERATION ("Script is destroyed");
script.post (json, data);
}
}
#endif
public sealed class JSScriptInstance : ScriptInstance {
public Gum.Script? script {
get {
return _script;
@@ -266,7 +411,7 @@ namespace Frida {
private RpcClient rpc_client;
public ScriptInstance (AgentScriptId script_id, Gum.Script script) {
public JSScriptInstance (AgentScriptId script_id, Gum.Script script) {
Object (script_id: script_id, script: script);
}
@@ -274,7 +419,7 @@ namespace Frida {
rpc_client = new RpcClient (this);
}
public async void close () {
public override async void close () {
if (close_request != null) {
try {
yield close_request.future.wait_async (null);
@@ -330,7 +475,7 @@ namespace Frida {
close_request.resolve (true);
}
public async void flush () {
public override async void flush () {
if (close_request == null)
close.begin ();
@@ -341,7 +486,7 @@ namespace Frida {
}
}
public async void load () throws Error {
public override async void load () throws Error {
if (state != CREATED)
throw new Error.INVALID_OPERATION ("Script cannot be loaded in its current state");
@@ -354,6 +499,16 @@ namespace Frida {
load_request.resolve (true);
}
public override void interrupt () throws Error {
script.interrupt ();
}
public override async void terminate () {
script.interrupt ();
yield close ();
}
private Promise<bool> unload () {
var request = new Promise<bool> ();
@@ -379,7 +534,7 @@ namespace Frida {
return result;
}
public async void prepare_for_termination (TerminationReason reason) {
public override async void prepare_for_termination (TerminationReason reason) {
if (state == LOADED) {
schedule_on_js_thread (() => {
script.get_stalker ().flush ();
@@ -390,7 +545,7 @@ namespace Frida {
yield ensure_dispose_called (reason);
}
public void unprepare_for_termination () {
public override void unprepare_for_termination () {
if (state == DISPOSED) {
state = LOADED;
dispose_request = null;
@@ -430,7 +585,7 @@ namespace Frida {
dispose_request.resolve (true);
}
public void post (string json, Bytes? data) throws Error {
public override void post (string json, Bytes? data) throws Error {
switch (state) {
case LOADING:
case LOADED:
@@ -442,17 +597,17 @@ namespace Frida {
}
}
public void enable_debugger () throws Error {
public override void enable_debugger () throws Error {
if (_script != null)
_script.set_debug_message_handler (on_debug_message);
}
public void disable_debugger () {
public override void disable_debugger () {
if (_script != null)
_script.set_debug_message_handler (null);
}
public void post_debug_message (string message) {
public override void post_debug_message (string message) throws Error {
if (_script != null)
_script.post_debug_message (message);
}
@@ -467,7 +622,8 @@ namespace Frida {
this.debug_message (message);
}
private async void post_rpc_message (string json, Bytes? data, Cancellable? cancellable) throws Error, IOError {
public override async void post_rpc_message (string json, Bytes? data, Cancellable? cancellable)
throws Error, IOError {
if (script == null)
throw new Error.INVALID_OPERATION ("Script is destroyed");
script.post (json, data);
+3 -1
View File
@@ -59,7 +59,9 @@ namespace Frida {
interceptor.attach ((void *) posix_spawn, this);
interceptor.replace (execve, (void *) replacement_execve, this);
var replace_options = Gum.ReplaceOptions ();
replace_options.replacement_data = this;
interceptor.replace (execve, (void *) replacement_execve, null, replace_options);
#else
Gum.Address execve = 0;
#if ANDROID
+5 -3
View File
@@ -31,9 +31,11 @@ namespace Frida {
thread_suspend = (ThreadSuspendFunc) kernel.find_export_by_name ("thread_suspend");
thread_resume = (ThreadResumeFunc) kernel.find_export_by_name ("thread_resume");
interceptor.replace ((void *) task_threads, (void *) replacement_task_threads, this);
interceptor.replace ((void *) thread_suspend, (void *) replacement_thread_suspend, this);
interceptor.replace ((void *) thread_resume, (void *) replacement_thread_resume, this);
var replace_options = Gum.ReplaceOptions ();
replace_options.replacement_data = this;
interceptor.replace ((void *) task_threads, (void *) replacement_task_threads, null, replace_options);
interceptor.replace ((void *) thread_suspend, (void *) replacement_thread_suspend, null, replace_options);
interceptor.replace ((void *) thread_resume, (void *) replacement_thread_resume, null, replace_options);
}
public override void dispose () {
+2 -405
View File
@@ -2,29 +2,14 @@
#ifdef HAVE_DARWIN
#include <capstone.h>
#include <gum/gumdarwin.h>
#include <gum/gummemory.h>
#include <ptrauth.h>
#define FRIDA_MH_MAGIC_64 0xfeedfacf
#define FRIDA_LIBUNWIND_PATH "/usr/lib/system/libunwind.dylib"
#define FRIDA_UNWIND_CURSOR_VTABLE_OFFSET_SET_INFO 0x68
#define FRIDA_UNWIND_CURSOR_VTABLE_OFFSET_GET_REG 0x18
#define FRIDA_FP_TO_SP(fp) (fp + 0x10)
#ifdef HAVE_ARM64
# define FRIDA_UNWIND_CURSOR_unwindInfoMissing 0x268
# define FRIDA_UNWAARCH64_X29 29
# define FRIDA_STRIP_MASK 0x0000007fffffffffULL
#else
# define FRIDA_UNWIND_CURSOR_unwindInfoMissing 0x100
# define FRIDA_UNWX86_64_RBP 6
#endif
typedef struct _FridaFillInfoContext FridaFillInfoContext;
typedef struct _FridaDyldUnwindSections FridaDyldUnwindSections;
typedef struct _FridaCreateArgs FridaCreateArgs;
typedef struct _FridaUnwindHookState FridaUnwindHookState;
struct _FridaFillInfoContext
{
@@ -47,37 +32,9 @@ struct _FridaCreateArgs
GumAddress range_end;
};
struct _FridaUnwindHookState
{
gpointer vtable;
gssize shift;
gpointer * set_info_slot;
gpointer set_info_original;
void (* set_info) (gpointer cursor, gint is_return_address);
gpointer (* get_reg) (gpointer cursor, gint reg);
GumInterceptor * interceptor;
};
#if __has_feature (ptrauth_calls)
# define FRIDA_RESIGN_PTR(x) GSIZE_TO_POINTER (gum_sign_code_address (gum_strip_code_address (GUM_ADDRESS (x))))
#else
# define FRIDA_RESIGN_PTR(x) (x)
#endif
static FridaDyldUnwindSections * frida_get_cached_sections (GumAddress range_start, GumAddress range_end);
static FridaDyldUnwindSections * frida_create_cached_sections (FridaCreateArgs * args);
static gboolean frida_fill_info (const GumDarwinSectionDetails * details, FridaFillInfoContext * ctx);
static void frida_unwind_cursor_set_info_replacement (gpointer cursor, gint is_return_address);
static gpointer frida_find_vtable (void);
static gboolean frida_compute_vtable_shift (gpointer vtable, gssize * shift);
#ifdef HAVE_ARM64
static gboolean frida_find_bss_range (const GumSectionDetails * details, GumMemoryRange * range);
#else
static gboolean frida_is_empty_function (GumAddress address);
static gboolean frida_has_first_match (GumAddress address, gsize size, gboolean * matches);
#endif
static FridaUnwindHookState * state = NULL;
void
_frida_unwind_sitter_fill_unwind_sections (GumAddress invader_start, GumAddress invader_end, void * info)
@@ -92,63 +49,6 @@ _frida_unwind_sitter_fill_unwind_sections (GumAddress invader_start, GumAddress
memcpy (unwind_sections, cached, sizeof (FridaDyldUnwindSections));
}
void
_frida_unwind_sitter_hook_libunwind (void)
{
#if GLIB_SIZEOF_VOID_P == 8
gpointer * set_info_slot;
gpointer get_reg_impl;
if (state != NULL)
return;
state = g_slice_new0 (FridaUnwindHookState);
if (state == NULL)
return;
state->vtable = frida_find_vtable ();
if (state->vtable == NULL)
goto unsupported_version;
if (!frida_compute_vtable_shift (state->vtable, &state->shift))
goto unsupported_version;
set_info_slot = (gpointer *) (GUM_ADDRESS (state->vtable) + FRIDA_UNWIND_CURSOR_VTABLE_OFFSET_SET_INFO + state->shift);
get_reg_impl = *(gpointer *) (GUM_ADDRESS (state->vtable) + FRIDA_UNWIND_CURSOR_VTABLE_OFFSET_GET_REG + state->shift);
state->set_info_slot = set_info_slot;
state->set_info_original = *set_info_slot;
state->set_info = FRIDA_RESIGN_PTR (state->set_info_original);
state->get_reg = FRIDA_RESIGN_PTR (get_reg_impl);
state->interceptor = gum_interceptor_obtain ();
if (gum_interceptor_replace (state->interceptor, state->set_info_original, frida_unwind_cursor_set_info_replacement, NULL, NULL)
!= GUM_REPLACE_OK)
goto unsupported_version;
return;
unsupported_version:
g_slice_free (FridaUnwindHookState, state);
state = NULL;
#endif
}
void
_frida_unwind_sitter_unhook_libunwind (void)
{
if (state == NULL)
return;
gum_interceptor_revert (state->interceptor, state->set_info_original);
g_object_unref (state->interceptor);
g_slice_free (FridaUnwindHookState, state);
state = NULL;
}
static FridaDyldUnwindSections *
frida_get_cached_sections (GumAddress range_start, GumAddress range_end)
{
@@ -187,7 +87,8 @@ 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_darwin_module_new_from_memory ("Frida", mach_task_self (), GPOINTER_TO_SIZE (header),
GUM_DARWIN_MODULE_FLAGS_NONE, NULL);
if (module == NULL)
return cached_sections;
@@ -222,308 +123,4 @@ frida_fill_info (const GumDarwinSectionDetails * details, FridaFillInfoContext *
return ctx->missing_info > 0;
}
static void
frida_unwind_cursor_set_info_replacement (gpointer self, gint is_return_address)
{
gboolean missing_info;
GumAddress fp, stored_pc;
gpointer * stored_pc_slot;
#if defined (HAVE_ARM64) && !__has_feature (ptrauth_calls)
gboolean was_signed = FALSE;
#endif
if (state == NULL)
return;
state->set_info (self, is_return_address);
#ifdef HAVE_ARM64
fp = GUM_ADDRESS (state->get_reg (self, FRIDA_UNWAARCH64_X29));
#else
fp = GUM_ADDRESS (state->get_reg (self, FRIDA_UNWX86_64_RBP));
#endif
if (fp == 0 || fp == -1)
return;
missing_info = *((guint8 *) self + FRIDA_UNWIND_CURSOR_unwindInfoMissing);
stored_pc_slot = GSIZE_TO_POINTER (fp + GLIB_SIZEOF_VOID_P);
stored_pc = GUM_ADDRESS (*stored_pc_slot);
#if __has_feature (ptrauth_calls)
stored_pc = gum_strip_code_address (stored_pc);
#elif defined (HAVE_ARM64)
was_signed = (stored_pc & ~FRIDA_STRIP_MASK) != 0ULL;
if (was_signed)
stored_pc &= FRIDA_STRIP_MASK;
#endif
if (!missing_info)
{
GumAddress translated;
translated = GUM_ADDRESS (gum_invocation_stack_translate (gum_interceptor_get_current_stack (), GSIZE_TO_POINTER (stored_pc)));
if (translated != stored_pc)
{
#if __has_feature (ptrauth_calls)
*stored_pc_slot = ptrauth_sign_unauthenticated (
ptrauth_strip (GSIZE_TO_POINTER (translated), ptrauth_key_asia), ptrauth_key_asib, FRIDA_FP_TO_SP (fp));
#elif defined (HAVE_ARM64)
if (was_signed)
{
GumAddress resigned;
asm volatile (
"mov x17, %1\n\t"
"mov x16, %2\n\t"
".byte 0x5f, 0x21, 0x03, 0xd5\n\t" /* pacib1716 */
"mov %0, x17\n\t"
: "=r" (resigned)
: "r" (translated & FRIDA_STRIP_MASK),
"r" (FRIDA_FP_TO_SP (fp))
: "x16", "x17"
);
*stored_pc_slot = GSIZE_TO_POINTER (resigned);
}
else
{
*stored_pc_slot = GSIZE_TO_POINTER (translated);
}
#else
*stored_pc_slot = GSIZE_TO_POINTER (translated);
#endif
}
}
}
static gpointer
frida_find_vtable (void)
{
GumAddress result = 0;
GumModule * libunwind;
GumAddress export;
uint64_t address;
G_GNUC_UNUSED cs_err err;
csh capstone;
cs_insn * insn = NULL;
const uint8_t * code;
size_t size;
const size_t max_size = 2048;
libunwind = gum_process_find_module_by_name (FRIDA_LIBUNWIND_PATH);
if (libunwind == NULL)
goto beach;
export = gum_module_find_export_by_name (libunwind, "unw_init_local");
if (export == 0)
export = gum_module_find_export_by_name (libunwind, "_Unwind_RaiseException");
if (export == 0)
goto beach;
export = gum_strip_code_address (export);
address = export;
#ifdef HAVE_ARM64
cs_arch_register_arm64 ();
err = cs_open (CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN, &capstone);
#else
cs_arch_register_x86 ();
err = cs_open (CS_ARCH_X86, CS_MODE_64, &capstone);
#endif
g_assert (err == CS_ERR_OK);
err = cs_option (capstone, CS_OPT_DETAIL, CS_OPT_ON);
g_assert (err == CS_ERR_OK);
insn = cs_malloc (capstone);
code = GSIZE_TO_POINTER (export);
size = max_size;
#ifdef HAVE_ARM64
{
GumAddress last_adrp;
guint last_adrp_reg;
GumMemoryRange bss_range;
bss_range.base_address = 0;
gum_module_enumerate_sections (libunwind, (GumFoundSectionFunc) frida_find_bss_range, &bss_range);
while (cs_disasm_iter (capstone, &code, &size, &address, insn))
{
if (insn->id == ARM64_INS_RET || insn->id == ARM64_INS_RETAA || insn->id == ARM64_INS_RETAB)
break;
if (insn->id == ARM64_INS_ADRP)
{
if (result != 0)
break;
last_adrp = (GumAddress) insn->detail->arm64.operands[1].imm;
last_adrp_reg = insn->detail->arm64.operands[0].reg;
}
else if (insn->id == ARM64_INS_ADD && insn->detail->arm64.operands[0].reg == last_adrp_reg)
{
GumAddress candidate;
gboolean is_bss;
candidate = last_adrp + (GumAddress) insn->detail->arm64.operands[2].imm;
is_bss = bss_range.base_address != 0 &&
bss_range.base_address <= candidate &&
candidate < bss_range.base_address + bss_range.size;
if (!is_bss)
{
if (result == 0)
{
result = candidate;
last_adrp = candidate;
}
else
{
result = candidate;
break;
}
}
}
else if (result != 0)
{
break;
}
}
}
#else
while (cs_disasm_iter (capstone, &code, &size, &address, insn))
{
if (insn->id == X86_INS_RET)
break;
if (insn->id == X86_INS_LEA)
{
const cs_x86_op * op = &insn->detail->x86.operands[1];
if (op->type == X86_OP_MEM && op->mem.base == X86_REG_RIP)
{
result = address + op->mem.disp * op->mem.scale;
break;
}
}
}
#endif
if (insn != NULL)
cs_free (insn, 1);
cs_close (&capstone);
beach:
g_clear_object (&libunwind);
return GSIZE_TO_POINTER (result);
}
#ifdef HAVE_ARM64
static gboolean
frida_find_bss_range (const GumSectionDetails * details, GumMemoryRange * range)
{
if (strcmp (details->name, "__bss") == 0)
{
range->base_address = details->address;
range->size = details->size;
return FALSE;
}
return TRUE;
}
static gboolean
frida_compute_vtable_shift (gpointer vtable, gssize * shift)
{
gboolean result = FALSE;
G_GNUC_UNUSED cs_err err;
csh capstone;
cs_insn * insn = NULL;
const uint8_t * code;
uint64_t address;
size_t size = 4;
cs_arch_register_arm64 ();
err = cs_open (CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN, &capstone);
g_assert (err == CS_ERR_OK);
insn = cs_malloc (capstone);
code = gum_strip_code_pointer (*(gpointer *) vtable);
address = GPOINTER_TO_SIZE (code);
if (cs_disasm_iter (capstone, &code, &size, &address, insn))
{
if (insn->id == ARM64_INS_RET || insn->id == ARM64_INS_RETAA || insn->id == ARM64_INS_RETAB)
*shift = 0;
else
*shift = -2 * GLIB_SIZEOF_VOID_P;
result = TRUE;
}
if (insn != NULL)
cs_free (insn, 1);
cs_close (&capstone);
return result;
}
#else
static gboolean
frida_compute_vtable_shift (gpointer vtable, gssize * shift)
{
GumAddress cursor = GPOINTER_TO_SIZE (vtable);
GumAddress error = cursor + 16 * GLIB_SIZEOF_VOID_P;
while (cursor < error && *(gpointer *) GSIZE_TO_POINTER (cursor) == NULL)
cursor += GLIB_SIZEOF_VOID_P;
if (cursor == error)
return FALSE;
if (frida_is_empty_function (GUM_ADDRESS (*(gpointer *) GSIZE_TO_POINTER (cursor))) &&
frida_is_empty_function (GUM_ADDRESS (*(gpointer *) GSIZE_TO_POINTER (cursor + GLIB_SIZEOF_VOID_P))))
{
*shift = cursor - GPOINTER_TO_SIZE (vtable);
}
else
{
*shift = cursor - GPOINTER_TO_SIZE (vtable) - 2 * GLIB_SIZEOF_VOID_P;
}
return TRUE;
}
static gboolean
frida_is_empty_function (GumAddress address)
{
gboolean matches = FALSE;
GumMemoryRange range;
GumMatchPattern * pattern;
range.base_address = address;
range.size = 6;
/*
* 55 push rbp
* 4889e5 mov rbp, rsp
* 5d pop rbp
* c3 ret
*/
pattern = gum_match_pattern_new_from_string ("55 48 89 e5 5d c3");
gum_memory_scan (&range, pattern, (GumMemoryScanMatchFunc) frida_has_first_match, &matches);
gum_match_pattern_unref (pattern);
return matches;
}
static gboolean
frida_has_first_match (GumAddress address, gsize size, gboolean * matches)
{
*matches = TRUE;
return FALSE;
}
#endif
#endif
+13 -37
View File
@@ -1,65 +1,41 @@
namespace Frida {
#if DARWIN
public sealed class UnwindSitter : Object {
public sealed class UnwindSitter : Object, Gum.UnwindSectionsProvider {
public weak ProcessInvader invader {
get;
construct;
}
private DyldFindUnwindSectionsFunc dyld_find_unwind_sections;
public unowned Gum.MemoryRange? range {
get { return _range; }
}
private const string LIBDYLD = "/usr/lib/system/libdyld.dylib";
[CCode (has_target = false)]
private delegate int DyldFindUnwindSectionsFunc (void * addr, void * info);
private Gum.MemoryRange? _range;
private Gum.UnwindBroker broker;
public UnwindSitter (ProcessInvader invader) {
Object (invader: invader);
}
construct {
var interceptor = Gum.Interceptor.obtain ();
_range = invader.get_memory_range ();
dyld_find_unwind_sections = (DyldFindUnwindSectionsFunc)
Gum.Process.find_module_by_name (LIBDYLD).find_export_by_name ("_dyld_find_unwind_sections");
interceptor.replace ((void *) dyld_find_unwind_sections, (void *) replacement_dyld_find_unwind_sections, this);
_hook_libunwind ();
broker = Gum.UnwindBroker.obtain ();
broker.add_sections_provider (this);
}
public override void dispose () {
var interceptor = Gum.Interceptor.obtain ();
_unhook_libunwind ();
interceptor.revert ((void *) dyld_find_unwind_sections);
broker.remove_sections_provider (this);
base.dispose ();
}
private static int replacement_dyld_find_unwind_sections (void * addr, void * info) {
unowned Gum.InvocationContext context = Gum.Interceptor.get_current_invocation ();
unowned UnwindSitter sitter = (UnwindSitter) context.get_replacement_data ();
Gum.MemoryRange range = sitter.invader.get_memory_range ();
var range_end = range.base_address + range.size;
var address = Gum.Address.from_pointer (addr);
#if ARM64
address &= 0x7ffffffffULL;
#endif
var is_ours = address >= range.base_address && address < range_end;
if (!is_ours)
return sitter.dyld_find_unwind_sections (addr, info);
_fill_unwind_sections (range.base_address, range_end, info);
return 1;
public bool fill (Gum.Address address, void * info) {
_fill_unwind_sections (_range.base_address, _range.base_address + _range.size, info);
return true;
}
public extern static void _fill_unwind_sections (Gum.Address invader_start, Gum.Address invader_end, void * info);
public extern static void _hook_libunwind ();
public extern static void _unhook_libunwind ();
}
#else
public sealed class UnwindSitter : Object {
+15 -2
View File
@@ -12,14 +12,27 @@ frida_pipe_get_sddl_string_for_pipe (void)
#define DACL_ACE_EVERYONE_RW L"(A;;GRGW;;;WD)"
#define SACL_START L"S:"
#define SACL_ACE_LOWINTEGRITY_NORW L"(ML;;NWNR;;;LW)"
#define DACL_ACE_SYSTEM_RW L"(A;;GRGW;;;SY)"
#define DACL_ACE_ADMINS_RW L"(A;;GRGW;;;BA)"
if (frida_pipe_is_windows_8_or_greater ())
{
return DACL_START_NOINHERIT DACL_ACE_APPCONTAINER_RW DACL_ACE_EVERYONE_RW SACL_START SACL_ACE_LOWINTEGRITY_NORW;
return DACL_START_NOINHERIT
DACL_ACE_APPCONTAINER_RW
DACL_ACE_EVERYONE_RW
DACL_ACE_SYSTEM_RW
DACL_ACE_ADMINS_RW
SACL_START
SACL_ACE_LOWINTEGRITY_NORW;
}
else if (frida_pipe_is_windows_vista_or_greater ())
{
return DACL_START_NOINHERIT DACL_ACE_EVERYONE_RW SACL_START SACL_ACE_LOWINTEGRITY_NORW;
return DACL_START_NOINHERIT
DACL_ACE_EVERYONE_RW
DACL_ACE_SYSTEM_RW
DACL_ACE_ADMINS_RW
SACL_START
SACL_ACE_LOWINTEGRITY_NORW;
}
else
{
+311 -79
View File
@@ -130,6 +130,10 @@ if host_os_family == 'darwin'
add_languages('objc', 'objcpp', native: false)
endif
if meson.is_cross_build()
add_languages('c', 'cpp', 'vala', native: true)
endif
python = import('python').find_installation()
if cc.get_argument_syntax() == 'msvc'
@@ -224,15 +228,21 @@ helper_name = 'frida-helper' + exe_suffix
agent_name = 'frida-agent' + shlib_suffix
gadget_name = 'frida-gadget' + shlib_suffix
root_asset_dir = get_option('libdir') / 'frida'
frida_libdir_name = f'frida-@api_version@'
root_asset_dir = get_option('libdir') / frida_libdir_name
if host_os_family == 'darwin'
asset_dir = root_asset_dir
default_asset_path_template = get_option('prefix') / get_option('libdir') / 'frida'
default_asset_path_template = get_option('prefix') / get_option('libdir') / frida_libdir_name
elif host_os_family == 'windows'
asset_dir = root_asset_dir / host_abi
asset_dir_modern = root_asset_dir / 'x86_64'
asset_dir_legacy = root_asset_dir / 'x86'
default_asset_path_template = get_option('prefix') / get_option('libdir') / frida_libdir_name / '<arch>'
else
asset_dir = root_asset_dir / host_cpu_mode
asset_dir_modern = root_asset_dir / '64'
asset_dir_legacy = root_asset_dir / '32'
default_asset_path_template = get_option('prefix') / get_option('libdir') / 'frida' / '<arch>'
default_asset_path_template = get_option('prefix') / get_option('libdir') / frida_libdir_name / '<arch>'
endif
asset_path_template = get_option('asset_path_template')
@@ -240,11 +250,22 @@ if asset_path_template == ''
asset_path_template = default_asset_path_template
endif
if get_option('assets') == 'embedded'
is_shared = get_option('default_library') == 'shared'
assets_mode = get_option('assets')
if assets_mode == 'auto'
assets_mode = is_shared ? 'installed' : 'embedded'
endif
cdata.set_quoted('FRIDA_LIBDIR', get_option('libdir'))
cdata.set_quoted('FRIDA_LIBDIR_NAME', frida_libdir_name)
if assets_mode == 'embedded'
cdata.set('HAVE_EMBEDDED_ASSETS', 1)
vala_flags += '--define=HAVE_EMBEDDED_ASSETS'
else
cdata.set_quoted('FRIDA_HELPER_PATH', asset_path_template / helper_name)
cdata.set_quoted('FRIDA_AGENT_PATH', asset_path_template / agent_name)
cdata.set_quoted('FRIDA_HELPER_NAME', helper_name)
cdata.set_quoted('FRIDA_AGENT_NAME', agent_name)
endif
cdata.set('HAVE_' + host_os_family.to_upper(), 1)
@@ -302,21 +323,19 @@ if host_os == 'linux'
endif
cdata.set('HAVE_' + libc.to_upper(), 1)
vala_flags += '--define=' + libc.to_upper()
elif host_os == 'android'
libc = 'bionic'
else
libc = 'other'
endif
if get_option('b_sanitize') == 'address'
cdata.set('HAVE_ASAN', 1)
endif
gum_dep = dependency('frida-gum-1.0', default_options: [
f'frida_version=@frida_version@',
'gumjs=enabled',
])
gumjs_dep = dependency('frida-gumjs-1.0')
gumjs_inspector_dep = dependency('frida-gumjs-inspector-1.0')
glib_version_req = '>=2.74'
glib_options = [
'default_library=static',
'cocoa=disabled',
'selinux=disabled',
'xattr=false',
@@ -332,24 +351,64 @@ if optimize_for_prod
]
endif
glib_dep = dependency('glib-2.0', version: glib_version_req, default_options: glib_options)
if glib_dep.type_name() == 'internal'
have_shared_glib = get_option('default_library') != 'static'
else
glib_flavor_src = '''
#include <glib.h>
#ifdef GLIB_STATIC_COMPILATION
# error GLib is static
#endif
'''
have_shared_glib = cc.compiles(glib_flavor_src, name: 'compiling with shared GLib', dependencies: [glib_dep])
endif
have_frida_glib = glib_dep.type_name() == 'internal' or cc.has_function('g_thread_set_callbacks', dependencies: [glib_dep])
if have_frida_glib
cdata.set('HAVE_FRIDA_GLIB', 1)
vala_flags += '--define=HAVE_FRIDA_GLIB'
endif
have_in_tree_payloads = not have_shared_glib
gum_dep = dependency('frida-gum-1.0', default_options: [
'default_library=static',
f'frida_version=@frida_version@',
'gumjs=' + (have_in_tree_payloads ? 'enabled' : 'disabled'),
'inspector=enabled',
])
if have_in_tree_payloads
gumjs_dep = dependency('frida-gumjs-1.0', default_options: ['default_library=static'])
endif
gumjs_inspector_dep = dependency('frida-gumjs-inspector-1.0', default_options: ['default_library=static'])
gumpharo_dep = dependency('frida-gumpharo-' + api_version, required: false)
have_gumpharo = gumpharo_dep.found()
if have_gumpharo
vala_flags += '--define=HAVE_GUMPHARO'
endif
gee_options = [
'default_library=static',
'disable-introspection=true',
]
if optimize_for_prod
gee_options += 'disable-internal-asserts=true'
endif
glib_dep = dependency('glib-2.0', version: glib_version_req, default_options: glib_options)
gobject_dep = dependency('gobject-2.0')
gmodule_dep = dependency('gmodule-2.0')
gio_dep = dependency('gio-2.0')
gee_dep = dependency('gee-0.8', default_options: gee_options)
json_glib_dep = dependency('json-glib-1.0', version: '>=1.8', default_options: [
'default_library=static',
'introspection=disabled',
'gtk_doc=disabled',
'tests=false',
])
libsoup_dep = dependency('libsoup-3.0', default_options: [
'default_library=static',
'sqlite:default_library=static',
'gssapi=disabled',
'ntlm=disabled',
'brotli=disabled',
@@ -361,19 +420,48 @@ libsoup_dep = dependency('libsoup-3.0', default_options: [
'tests=false',
'sysprof=disabled',
])
brotlidec_dep = dependency('libbrotlidec')
if libsoup_dep.type_name() == 'internal' \
or cc.has_function('soup_websocket_connection_pause_input', dependencies: [libsoup_dep])
vala_flags += '--define=HAVE_SOUP_WEBSOCKET_BACKPRESSURE'
endif
brotlidec_dep = dependency('libbrotlidec', default_options: ['default_library=static'])
capstone_dep = dependency('capstone', default_options: ['default_library=static'])
if capstone_dep.type_name() != 'internal' \
and not cc.has_function('cs_arch_register_x86', dependencies: [capstone_dep])
add_project_arguments(
'-Dcs_arch_register_x86()=',
'-Dcs_arch_register_arm()=',
'-Dcs_arch_register_arm64()=',
'-Dcs_arch_register_mips()=',
language: ['c', 'objc'],
)
endif
quickjs_dep = dependency('quickjs', required: get_option('barebone_backend'), default_options: [
'default_library=static',
'libc=false',
'bignum=true',
'atomics=disabled',
'stack_check=disabled',
])
lzfse_dep = dependency('lzfse', required: get_option('barebone_backend'))
lzfse_dep = dependency('lzfse', required: false, default_options: ['default_library=static'])
lzfse_from_system = false
if not lzfse_dep.found() and not meson.is_cross_build()
if cc.has_header_symbol('lzfse.h', 'lzfse_decode_buffer')
lzfse_lib = cc.find_library('lzfse', required: false)
if lzfse_lib.found()
lzfse_dep = declare_dependency(dependencies: lzfse_lib)
lzfse_from_system = true
endif
endif
endif
if not lzfse_dep.found()
lzfse_dep = dependency('lzfse', required: get_option('barebone_backend'), default_options: ['default_library=static'])
endif
native_glib_dep = dependency('glib-2.0', version: glib_version_req, native: true, default_options: glib_options)
native_gio_dep = dependency('gio-2.0', native: true)
native_gee_dep = dependency('gee-0.8', native: true, default_options: gee_options)
native_brotlienc_dep = dependency('libbrotlienc', native: true)
native_brotlienc_dep = dependency('libbrotlienc', native: true, default_options: ['default_library=static'])
if host_os_family == 'windows'
gio_windows_dep = dependency('gio-windows-2.0')
@@ -381,8 +469,10 @@ else
gio_unix_dep = dependency('gio-unix-2.0')
endif
if host_os_family == 'linux'
libbpf_dep = dependency('libbpf')
have_local_backend = get_option('local_backend').allowed()
if host_os_family == 'linux' and have_local_backend
libbpf_dep = dependency('libbpf', default_options: ['default_library=static'])
endif
if host_os == 'android'
@@ -430,7 +520,6 @@ else
codesign = ''
endif
have_local_backend = get_option('local_backend').allowed()
have_simmy_backend = get_option('simmy_backend') \
.disable_auto_if(host_os != 'macos') \
.require(host_os == 'macos', error_message: 'Simmy backend is only available on macOS') \
@@ -444,11 +533,12 @@ have_droidy_backend = get_option('droidy_backend') \
have_socket_backend = get_option('socket_backend').allowed()
have_barebone_backend = get_option('barebone_backend') \
.disable_if(not (quickjs_dep.found() and lzfse_dep.found())) \
.disable_auto_if(not host_docks_mobile_devices) \
.disable_auto_if(not host_docks_mobile_devices and host_os != 'android') \
.allowed()
compiler_backend_opt = get_option('compiler_backend') \
.disable_auto_if(host_os == 'watchos')
have_compiler_backend = false
compiler_backend_mode = ''
if compiler_backend_opt.allowed()
go = find_program('go', native: true, required: false)
if go.found()
@@ -459,9 +549,13 @@ if compiler_backend_opt.allowed()
files('tools' / 'cgo' / 'detect.py'),
go,
host_os,
host_os_family,
host_abi,
libc,
exe_suffix,
shlib_suffix,
cc.get_id(),
assets_mode,
'>>>', cc.cmd_array(), '<<<',
'>>>', ar, '<<<',
'>>>', nm, '<<<',
@@ -474,12 +568,29 @@ if compiler_backend_opt.allowed()
status = lines[0]
if status == 'ok'
have_compiler_backend = true
tokens = lines[1].split(' ')
go_mode = tokens[0]
go_config = tokens[1]
compiler_backend_static_compilation = go_mode == 'c-archive'
if compiler_backend_static_compilation
vala_flags += '--define=COMPILER_BACKEND_STATIC_COMPILATION'
cdata.set('HAVE_COMPILER_BACKEND', 1)
compiler_backend_mode = lines[1]
go_config = lines[2]
if compiler_backend_mode == 'c-archive'
vala_flags += '--define=COMPILER_BACKEND_LINKED'
cdata.set('HAVE_COMPILER_BACKEND_LINKED', 1)
elif compiler_backend_mode == 'c-shared'
if assets_mode == 'installed'
vala_flags += '--define=COMPILER_BACKEND_INSTALLED_LIBRARY'
cdata.set_quoted('FRIDA_COMPILER_BACKEND_NAME', 'frida-compiler-backend' + shlib_suffix)
else
vala_flags += '--define=COMPILER_BACKEND_EMBEDDED_LIBRARY'
endif
elif compiler_backend_mode == 'pie'
if assets_mode == 'installed'
vala_flags += '--define=COMPILER_BACKEND_INSTALLED_EXECUTABLE'
cdata.set_quoted('FRIDA_COMPILER_BACKEND_NAME', 'frida-compiler-backend' + exe_suffix)
else
vala_flags += '--define=COMPILER_BACKEND_EMBEDDED_EXECUTABLE'
endif
else
error('Unsupported compiler backend mode: ' + compiler_backend_mode)
endif
else
error_message = lines[1]
@@ -548,8 +659,10 @@ endif
gi_dep = dependency('gobject-introspection-1.0', required: false)
if gi_dep.found()
girdir = gi_dep.get_variable('girdir')
typelibdir = gi_dep.get_variable('typelibdir')
else
girdir = get_option('prefix') / get_option('datadir') / 'gir-1.0'
typelibdir = get_option('prefix') / get_option('libdir') / 'girepository-1.0'
endif
vapidir = get_option('prefix') / get_option('datadir') / 'vala' / 'vapi'
@@ -558,12 +671,18 @@ gum_vala_args = [
'--pkg=frida-gum-1.0',
'--vapidir=' + gum_dep.get_variable('frida_vapidir'),
]
gumjs_vala_args = ['--pkg=frida-gumjs-1.0', '--pkg=gio-2.0', gum_vala_args]
gumjs_inspector_vala_args = ['--pkg=frida-gumjs-inspector-1.0', gum_vala_args]
have_v8 = gumjs_dep.get_variable('gumjs_v8') == 'enabled'
if have_v8
vala_flags += ['--define=HAVE_V8']
if have_in_tree_payloads
gumjs_vala_args = ['--pkg=frida-gumjs-1.0', '--pkg=gio-2.0', gum_vala_args]
if have_gumpharo
gumjs_vala_args += ['--pkg=frida-gumpharo-' + api_version]
endif
have_v8 = gumjs_dep.get_variable('gumjs_v8') == 'enabled'
if have_v8
vala_flags += ['--define=HAVE_V8']
endif
endif
backend_deps_private = []
@@ -571,6 +690,7 @@ backend_reqs_private = []
backend_libs_private = []
openssl_options = [
'default_library=static',
'cli=disabled',
]
if host_os_family == 'windows' and cc.get_argument_syntax() != 'msvc'
@@ -583,25 +703,48 @@ openssl_dep = dependency('openssl',
)
if have_fruity_backend
foreach dep : ['libnghttp2', 'libngtcp2', 'libngtcp2_crypto_quictls', 'libusb-1.0']
backend_deps_private += dependency(dep)
backend_reqs_private += dep
endforeach
backend_deps_private += dependency('libnghttp2', default_options: ['default_library=static'])
backend_reqs_private += 'libnghttp2'
lwip_options = [
'ipv4=disabled',
'ipv6=enabled',
'dns=disabled',
'arp=disabled',
'ethernet=enabled',
]
if optimize_for_prod
lwip_options += 'lwip_debug=disabled'
else
lwip_options += 'lwip_debug=enabled'
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'
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
backend_deps_private += dependency('lwip', default_options: lwip_options)
backend_reqs_private += 'lwip'
endif
if quickjs_dep.found()
@@ -611,10 +754,14 @@ endif
if lzfse_dep.found()
backend_deps_private += lzfse_dep
backend_reqs_private += 'lzfse'
if lzfse_from_system
backend_libs_private += '-llzfse'
else
backend_reqs_private += 'lzfse'
endif
endif
if host_os_family == 'linux'
if host_os_family == 'linux' and have_local_backend
backend_deps_private += libbpf_dep
backend_reqs_private += 'libbpf'
endif
@@ -625,43 +772,93 @@ endif
if host_os == 'android'
termux_elf_cleaner = find_program('termux-elf-cleaner', native: true)
libselinux_dep = dependency('libselinux', version: '>=3.0')
libsepol_dep = dependency('libsepol', version: '>=3.0')
libselinux_dep = dependency('libselinux', version: '>=3.0', default_options: ['default_library=static'])
libsepol_dep = dependency('libsepol', version: '>=3.0', default_options: ['default_library=static'])
backend_deps_private += [libselinux_dep, libsepol_dep]
backend_reqs_private += ['libselinux', 'libsepol']
else
termux_elf_cleaner = []
endif
tls_provider_dep = dependency('gioopenssl', required: get_option('connectivity'), default_options: [
'gnutls=disabled',
'openssl=enabled',
'libproxy=disabled',
'gnome_proxy=disabled',
'tests=false',
])
tls_provider_vala_args = []
if tls_provider_dep.found()
tls_provider_vala_args += [
'--pkg=gioopenssl',
'--vapidir=' + tls_provider_dep.get_variable('vapidir'),
]
cdata.set('HAVE_GIOOPENSSL', 1)
vala_flags += ['--define=HAVE_GIOOPENSSL']
backend_deps_private += tls_provider_dep
backend_reqs_private += 'gioopenssl'
use_apple_tls = false
if have_shared_glib
tls_provider_dep = gio_dep
else
apple_tls_opt = get_option('apple_tls')
if host_os_family == 'darwin' and not apple_tls_opt.disabled()
apple_tls_deployment_check = '''
#include <Availability.h>
#if defined (__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 150000
# error macOS deployment target below 15.0
#endif
#if defined (__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 180000
# error iOS deployment target below 18.0
#endif
#if defined (__TV_OS_VERSION_MIN_REQUIRED) && __TV_OS_VERSION_MIN_REQUIRED < 180000
# error tvOS deployment target below 18.0
#endif
#if defined (__WATCH_OS_VERSION_MIN_REQUIRED) && __WATCH_OS_VERSION_MIN_REQUIRED < 110000
# error watchOS deployment target below 11.0
#endif
'''
use_apple_tls = cc.compiles(apple_tls_deployment_check, name: 'Apple TLS backend deployment target')
if apple_tls_opt.enabled() and not use_apple_tls
error('apple_tls explicitly enabled but deployment target is too old')
endif
elif apple_tls_opt.enabled()
error('apple_tls is only supported on Darwin hosts')
endif
if use_apple_tls
tls_provider_name = 'gioapple'
tls_provider_define_name = 'HAVE_GIOAPPLE'
tls_provider_backend_options = [
'gnutls=disabled',
'openssl=disabled',
'apple=enabled',
]
else
tls_provider_name = 'gioopenssl'
tls_provider_define_name = 'HAVE_GIOOPENSSL'
tls_provider_backend_options = [
'gnutls=disabled',
'openssl=enabled',
'apple=disabled',
]
endif
tls_provider_dep = dependency(tls_provider_name,
required: get_option('connectivity'),
default_options: tls_provider_backend_options + [
'libproxy=disabled',
'gnome_proxy=disabled',
'tests=false',
],
)
if tls_provider_dep.found()
tls_provider_vala_args += [
'--pkg=' + tls_provider_name,
'--vapidir=' + tls_provider_dep.get_variable('vapidir'),
]
cdata.set(tls_provider_define_name, 1)
vala_flags += ['--define=' + tls_provider_define_name]
backend_deps_private += tls_provider_dep
backend_reqs_private += tls_provider_name
endif
endif
nice_dep = dependency('nice', required: get_option('connectivity'), default_options: [
'default_library=static',
'gupnp=disabled',
'gstreamer=disabled',
'crypto-library=openssl',
'crypto-library=auto',
'examples=disabled',
'tests=disabled',
'introspection=disabled',
])
if nice_dep.found()
usrsctp_dep = dependency('usrsctp', default_options: [
'default_library=static',
'sctp_inet=false',
'sctp_inet6=false',
'sctp_build_programs=false',
@@ -669,8 +866,12 @@ if nice_dep.found()
cdata.set('HAVE_NICE', 1)
vala_flags += ['--define=HAVE_NICE']
backend_deps_private += [nice_dep, openssl_dep, usrsctp_dep]
backend_reqs_private += ['nice', 'openssl', 'usrsctp']
backend_deps_private += [nice_dep, usrsctp_dep]
backend_reqs_private += ['nice', 'usrsctp']
if host_os != 'macos'
backend_deps_private += openssl_dep
backend_reqs_private += 'openssl'
endif
if host_os_family in ['darwin', 'freebsd', 'qnx']
cdata.set('HAVE_SCONN_LEN', 1)
@@ -680,10 +881,10 @@ else
endif
if host_os_family == 'windows'
backend_libs_private += ['-lsetupapi']
backend_libs_private += ['-lsetupapi', '-lshlwapi']
endif
if host_os_family == 'darwin'
backend_libs_private += ['-Wl,-framework,Foundation', '-lbsm']
backend_libs_private += ['-Wl,-framework,Foundation', '-Wl,-framework,CoreFoundation', '-lbsm']
endif
if host_os == 'macos'
backend_libs_private += ['-Wl,-framework,AppKit']
@@ -753,6 +954,19 @@ post_process = [
'@INPUT0@',
]
executable_no_exports_link_args = []
executable_no_exports_link_depends = []
if host_os_family == 'darwin'
script = meson.project_source_root() / 'tools' / 'executable-no-exports.symbols'
executable_no_exports_link_args = ['-Wl,-exported_symbols_list,' + script]
executable_no_exports_link_depends = [script]
elif host_os_family != 'windows'
script_name = (host_os == 'freebsd') ? 'executable-no-exports-freebsd.version' : 'executable-no-exports.version'
script = meson.project_source_root() / 'tools' / script_name
executable_no_exports_link_args = ['-Wl,--version-script,' + script]
executable_no_exports_link_depends = [script]
endif
compile_js_agent = files('tools' / 'compile-js-agent.py')
mapper_opt = get_option('mapper')
@@ -783,11 +997,20 @@ if cc.get_id() == 'gcc' and cc.compiles('#if __GNUC__ >= 5\n#error NOTNEEDED\n#e
add_project_arguments('-w', language: 'c')
endif
vapidir = meson.current_source_dir() / 'vapi'
local_vapidir = meson.current_source_dir() / 'vapi'
vala_flags += [
'--vapidir=' + vapidir,
'--vapidir=' + local_vapidir,
'--pkg', 'config',
]
if gee_dep.type_name() not in ['internal', 'cmake']
gee_prefix = gee_dep.get_variable('prefix', default_value: '')
if gee_prefix != ''
gee_vapidir = gee_prefix / 'share' / 'vala' / 'vapi'
if fs.is_dir(gee_vapidir)
vala_flags += '--vapidir=' + gee_vapidir
endif
endif
endif
vala_flags += '--define=' + host_os_family.to_upper()
if host_os != host_os_family
@@ -799,13 +1022,22 @@ if host_abi != host_arch
endif
vala_flags += '--define=' + host_machine.endian().to_upper() + '_ENDIAN'
add_project_arguments(vala_flags, language: 'vala')
add_project_arguments('-I' + vapidir, language: 'c')
add_project_arguments('-I' + local_vapidir, language: 'c')
native_vala_flags = [
'--vapidir=' + meson.current_source_dir() / 'vapi',
'--vapidir=' + local_vapidir,
]
if native_gee_dep.type_name() not in ['internal', 'cmake']
native_gee_prefix = native_gee_dep.get_variable('prefix', default_value: '')
if native_gee_prefix != ''
native_gee_vapidir = native_gee_prefix / 'share' / 'vala' / 'vapi'
if fs.is_dir(native_gee_vapidir)
native_vala_flags += '--vapidir=' + native_gee_vapidir
endif
endif
endif
add_project_arguments(native_vala_flags, language: 'vala', native: true)
add_project_arguments('-I' + vapidir, language: 'c', native: true)
add_project_arguments('-I' + local_vapidir, language: 'c', native: true)
subdir('compat')
subdir('tools')
@@ -840,7 +1072,7 @@ summary(
summary(
{
'compat': compat_summary,
'assets': get_option('assets'),
'assets': assets_mode,
'mapper': have_mapper,
'connectivity': tls_provider_dep.found(),
'gadget': build_gadget,
+8 -2
View File
@@ -48,8 +48,8 @@ option('compiler_backend',
option('assets',
type: 'combo',
choices: ['embedded', 'installed'],
value: 'embedded',
choices: ['auto', 'embedded', 'installed'],
value: 'auto',
description: 'Whether assets such as frida-agent should be embedded vs installed and loaded at runtime'
)
@@ -143,6 +143,12 @@ option('connectivity',
description: 'Internet connectivity features (TLS and ICE support)'
)
option('apple_tls',
type: 'feature',
value: 'auto',
description: 'Use the Apple TLS backend (requires recent deployment targets); auto-detected when on a Darwin host'
)
option('mapper',
type: 'feature',
value: 'auto',
-1
View File
@@ -1 +0,0 @@
# No exported symbols.
-9
View File
@@ -1,9 +0,0 @@
{
global:
# FreeBSD needs these two:
__progname;
environ;
local:
*;
};
+2 -14
View File
@@ -2,18 +2,6 @@ portal_sources = [
'portal.vala',
]
extra_link_args = []
extra_link_depends = []
if host_os_family == 'darwin'
symlist = 'frida-portal.symbols'
extra_link_args += ['-Wl,-exported_symbols_list,' + meson.current_source_dir() / symlist]
extra_link_depends += [symlist]
elif host_os_family != 'windows'
symscript = 'frida-portal.version'
extra_link_args += ['-Wl,--version-script,' + meson.current_source_dir() / symscript]
extra_link_depends += [symscript]
endif
extra_deps = []
if tls_provider_dep.found()
extra_deps += tls_provider_dep
@@ -21,8 +9,8 @@ endif
raw_portal = executable('frida-portal-raw', portal_sources,
vala_args: [core_vala_args, tls_provider_vala_args, '--pkg=posix'],
link_args: extra_link_args,
link_depends: extra_link_depends,
link_args: executable_no_exports_link_args,
link_depends: executable_no_exports_link_depends,
dependencies: [core_dep] + extra_deps,
)
+3 -1
View File
@@ -39,7 +39,9 @@ namespace Frida.Portal {
};
private static int main (string[] args) {
#if HAVE_GIOOPENSSL
#if HAVE_GIOAPPLE
GIOApple.register ();
#elif HAVE_GIOOPENSSL
GIOOpenSSL.register ();
#endif
+1 -1
Submodule releng updated: 7ed66fe687...0ad9eef364.
+11
View File
@@ -0,0 +1,11 @@
{
global:
# FreeBSD needs these two:
__progname;
environ;
# Allow users to load the server as a shared library with dlopen() and call main() directly
main;
local:
*;
};
+2 -1
View File
@@ -1 +1,2 @@
# No exported symbols.
# Allow users to load the server as a shared library with dlopen() and call main() directly
_main
-3
View File
@@ -1,8 +1,5 @@
{
global:
# FreeBSD needs these two:
__progname;
environ;
# Allow users to load the server as a shared library with dlopen() and call main() directly
main;
+8 -3
View File
@@ -19,7 +19,7 @@ if host_os_family == 'darwin'
extra_link_args += ['-Wl,-exported_symbols_list,' + meson.current_source_dir() / symlist]
extra_link_depends += [symlist]
elif host_os_family != 'windows'
symscript = 'frida-server.version'
symscript = (host_os == 'freebsd') ? 'frida-server-freebsd.version' : 'frida-server.version'
extra_link_args += ['-Wl,--version-script,' + meson.current_source_dir() / symscript]
extra_link_depends += [symscript]
endif
@@ -29,15 +29,20 @@ if host_os == 'macos'
extra_link_depends += [server_plist]
endif
server_deps = [core_dep]
if host_os_family == 'darwin'
server_deps += dependency('appleframeworks', modules: ['CoreFoundation'])
endif
raw_server = executable('frida-server-raw', server_sources,
vala_args: [core_vala_args, '--pkg=posix'],
link_args: extra_link_args,
link_depends: extra_link_depends,
dependencies: [core_dep],
dependencies: server_deps,
)
server_name = 'frida-server' + exe_suffix
need_universal_server = get_option('assets') == 'installed' and server_compat != ''
need_universal_server = assets_mode == 'installed' and server_compat != ''
server = custom_target('frida-server',
input: [raw_server, 'frida-server.xcent'],
+2 -4
View File
@@ -104,9 +104,8 @@ namespace Frida.Server {
return true;
});
int prefix_pos = program_path.last_index_of (Config.FRIDA_PREFIX + "/");
if (prefix_pos != -1 && prefix_pos != 0) {
options.sysroot = program_path[:prefix_pos];
}
if (prefix_pos != -1 && prefix_pos != 0)
TemporaryDirectory.use_sysroot (program_path[:prefix_pos]);
#endif
PolicySoftenerFlavor softener_flavor = SYSTEM;
@@ -199,7 +198,6 @@ namespace Frida.Server {
private static int run_application (string? device_id, EndpointParameters endpoint_params, ControlServiceOptions options,
ReadyHandler on_ready) {
TemporaryDirectory.always_use ((directory != null) ? directory : DEFAULT_DIRECTORY);
TemporaryDirectory.use_sysroot (options.sysroot);
application = new Application (device_id, endpoint_params, options);
+1 -1
View File
@@ -11,7 +11,7 @@ helper.dex: build/frida-helper.jar
$<
mv build/classes.dex $@
build/frida-helper.jar: re/frida/Helper.java
build/frida-helper.jar: re/frida/Helper.java re/frida/HelperBackend.java
javac \
-cp .:$(android_jar) \
-bootclasspath $(android_jar) \
+93 -77
View File
@@ -1,66 +1,12 @@
namespace Frida {
public sealed class AndroidHelperClient : Object, AsyncInitable {
public signal void closed ();
public IOStream stream {
public sealed class AndroidHelperClient : Object {
public AndroidHelperTransport transport {
get;
construct;
}
private BufferedInputStream input;
private OutputStream output;
private Cancellable io_cancellable = new Cancellable ();
private State state = CLOSED;
private Gee.Queue<Request> pending_requests = new Gee.ArrayQueue<Request> ();
private ByteArray pending_output = new ByteArray ();
private bool writing = false;
private enum State {
CLOSED,
OPEN
}
private const uint32 MAX_RESPONSE_SIZE = 100 * 1024 * 1024;
public AndroidHelperClient (IOStream stream) {
Object (stream: stream);
}
construct {
input = (BufferedInputStream) Object.new (typeof (BufferedInputStream),
"base-stream", stream.get_input_stream (),
"close-base-stream", false,
"buffer-size", 128 * 1024);
output = stream.get_output_stream ();
state = OPEN;
process_incoming_responses.begin ();
}
public async void close (Cancellable? cancellable) throws IOError {
io_cancellable.cancel ();
var source = new IdleSource ();
source.set_callback (close.callback);
source.attach (MainContext.get_thread_default ());
yield;
try {
yield stream.close_async (Priority.DEFAULT, cancellable);
} catch (IOError e) {
}
ensure_closed ();
}
private void ensure_closed () {
if (state == CLOSED)
return;
state = CLOSED;
closed ();
public AndroidHelperClient (AndroidHelperTransport transport) {
Object (transport: transport);
}
public async HostApplicationInfo get_frontmost_application (FrontmostQueryOptions options,
@@ -72,12 +18,12 @@ namespace Frida {
.add_string_value (options.scope.to_nick ())
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
if (response.is_null ())
return HostApplicationInfo.empty ();
Json.Reader reader = new Json.Reader (response);
Json.Reader reader = make_json_reader_taking_node ((owned) response);
string? identifier = null;
string? name = null;
@@ -127,9 +73,9 @@ namespace Frida {
.add_string_value (options.scope.to_nick ())
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
Json.Reader reader = new Json.Reader (response);
Json.Reader reader = make_json_reader_taking_node ((owned) response);
int num_apps = reader.count_elements ();
if (num_apps == -1)
@@ -193,9 +139,9 @@ namespace Frida {
.add_string_value (options.scope.to_nick ())
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
Json.Reader reader = new Json.Reader (response);
Json.Reader reader = make_json_reader_taking_node ((owned) response);
int num_processes = reader.count_elements ();
if (num_processes == -1)
@@ -247,9 +193,9 @@ namespace Frida {
.add_int_value (uid)
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
var r = new Json.Reader (response);
var r = make_json_reader_taking_node ((owned) response);
parse_envelope_or_throw (r);
r.read_element (1);
@@ -317,9 +263,9 @@ namespace Frida {
.add_int_value (uid)
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
var r = new Json.Reader (response);
var r = make_json_reader_taking_node ((owned) response);
parse_envelope_or_throw (r);
}
@@ -331,9 +277,9 @@ namespace Frida {
.add_int_value ((int) pid)
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
var r = new Json.Reader (response);
var r = make_json_reader_taking_node ((owned) response);
parse_envelope_or_throw (r);
r.read_element (1);
@@ -343,6 +289,13 @@ namespace Frida {
return r.get_boolean_value ();
}
private async void request_ok (Json.Builder stanza, Cancellable? cancellable) throws Error, IOError {
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
var r = make_json_reader_taking_node ((owned) response);
parse_envelope_or_throw (r);
}
private void parse_envelope_or_throw (Json.Reader r) throws Error {
r.read_element (0);
string? kind = r.get_string_value ();
@@ -452,6 +405,76 @@ namespace Frida {
throw new Error.PROTOCOL ("Unexpected JSON type: %s", type.name ());
}
}
public interface AndroidHelperTransport : Object {
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;
}
private BufferedInputStream input;
private OutputStream output;
private Cancellable io_cancellable = new Cancellable ();
private State state = CLOSED;
private Gee.Queue<Request> pending_requests = new Gee.ArrayQueue<Request> ();
private ByteArray pending_output = new ByteArray ();
private bool writing = false;
private enum State {
CLOSED,
OPEN
}
private const uint32 MAX_RESPONSE_SIZE = 100 * 1024 * 1024;
public AndroidHelperStreamTransport (IOStream stream) {
Object (stream: stream);
}
construct {
input = (BufferedInputStream) Object.new (typeof (BufferedInputStream),
"base-stream", stream.get_input_stream (),
"close-base-stream", false,
"buffer-size", 128 * 1024);
output = stream.get_output_stream ();
state = OPEN;
process_incoming_responses.begin ();
}
public async void close (Cancellable? cancellable) throws IOError {
io_cancellable.cancel ();
var source = new IdleSource ();
source.set_callback (close.callback);
source.attach (MainContext.get_thread_default ());
yield;
try {
yield stream.close_async (Priority.DEFAULT, cancellable);
} catch (IOError e) {
}
ensure_closed ();
}
private void ensure_closed () {
if (state == CLOSED)
return;
state = CLOSED;
closed ();
}
public async Json.Node request (Json.Node stanza, Cancellable? cancellable) throws Error, IOError {
if (state == CLOSED)
@@ -487,13 +510,6 @@ namespace Frida {
return r.response;
}
private async void request_ok (Json.Builder stanza, Cancellable? cancellable) throws Error, IOError {
Json.Node response = yield request (stanza.get_root (), cancellable);
var r = new Json.Reader (response);
parse_envelope_or_throw (r);
}
private void write_request (Json.Node request) {
string request_str = Json.to_string (request, false);
uint8[] request_data = request_str.data;
Binary file not shown.
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+397 -39
View File
@@ -23,57 +23,49 @@ CORE_TAG_METHOD = f"{{{CORE_NAMESPACE}}}method"
OBJECT_TYPE_PATTERN = re.compile(r"\bpublic\s+(sealed )?(class|interface)\s+(\w+)\b")
TOPLEVEL_NAMES = [
"frida.vala",
"package-manager.vala",
"control-service.vala",
"portal-service.vala",
"file-monitor.vala",
Path("compiler") / "compiler.vala",
]
def main():
parser = argparse.ArgumentParser(description="Generate refined Frida API definitions")
parser.add_argument('--output', dest='output_type', choices=['bundle', 'header', 'gir', 'vapi', 'vapi-stamp'], default='bundle')
parser.add_argument('frida_version', metavar='frida-version', type=str)
parser.add_argument('frida_major_version', metavar='frida-major-version', type=str)
parser.add_argument('frida_minor_version', metavar='frida-minor-version', type=str)
parser.add_argument('frida_micro_version', metavar='frida-micro-version', type=str)
parser.add_argument('frida_nano_version', metavar='frida-nano-version', type=str)
parser.add_argument('api_version', metavar='api-version', type=str)
parser.add_argument('core_header', metavar='/path/to/frida-core.h', type=argparse.FileType('r', encoding='utf-8'))
parser.add_argument('core_gir', metavar='/path/to/Frida-x.y.gir', type=argparse.FileType('r', encoding='utf-8'))
parser.add_argument('core_vapi', metavar='/path/to/frida-core.vapi', type=argparse.FileType('r', encoding='utf-8'))
parser.add_argument('base_header', metavar='/path/to/frida-base.h', type=argparse.FileType('r', encoding='utf-8'))
parser.add_argument('base_gir', metavar='/path/to/FridaBase-x.y.gir', type=argparse.FileType('r', encoding='utf-8'))
parser.add_argument('base_vapi', metavar='/path/to/frida-base.vapi', type=argparse.FileType('r', encoding='utf-8'))
parser.add_argument('--output', dest='output_type', choices=['bundle', 'header', 'gir', 'vapi', 'vapi-stamp', 'symbol-maps'], default='bundle')
parser.add_argument('output_dir', metavar='/output/dir')
parser.add_argument('extra_args', nargs='*')
args = parser.parse_args()
output_type = args.output_type
frida_version = args.frida_version
frida_version_components = (
args.frida_major_version,
args.frida_minor_version,
args.frida_micro_version,
args.frida_nano_version,
)
api_version = args.api_version
core_header = args.core_header.read()
core_gir = args.core_gir.read()
core_vapi = args.core_vapi.read()
base_header = args.base_header.read()
base_gir = args.base_gir.read()
base_vapi = args.base_vapi.read()
output_dir = Path(args.output_dir)
if output_type == 'symbol-maps':
emit_symbol_maps_from_source(output_dir)
return
extra = args.extra_args
frida_version = extra[0]
frida_version_components = tuple(extra[1:5])
api_version = extra[5]
if output_type == 'vapi-stamp':
(output_dir / f"frida-core-{api_version}.vapi.stamp").write_bytes(b"")
return
toplevel_names = [
"frida.vala",
"package-manager.vala",
"control-service.vala",
"portal-service.vala",
"file-monitor.vala",
Path("compiler") / "compiler.vala",
]
core_header = Path(extra[6]).read_text(encoding='utf-8')
core_gir = Path(extra[7]).read_text(encoding='utf-8')
core_vapi = Path(extra[8]).read_text(encoding='utf-8')
base_header = Path(extra[9]).read_text(encoding='utf-8')
base_gir = Path(extra[10]).read_text(encoding='utf-8')
base_vapi = Path(extra[11]).read_text(encoding='utf-8')
toplevel_sources = []
src_dir = Path(__file__).parent.parent.resolve()
for name in toplevel_names:
for name in TOPLEVEL_NAMES:
toplevel_sources.append((src_dir / name).read_text(encoding='utf-8'))
toplevel_code = "\n".join(toplevel_sources)
@@ -97,7 +89,7 @@ def main():
emit_header(api, output_dir)
if enable_gir:
emit_gir(api, core_gir, base_gir, output_dir)
emit_gir(api, core_gir, base_gir, output_dir, build_doc_index(src_dir))
if enable_vapi:
emit_vapi(api, output_dir)
@@ -203,7 +195,7 @@ def emit_header(api, output_dir):
output_header_file.write("\n\n#endif\n")
def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> str:
def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path, docs: DocIndex) -> str:
ET.register_namespace("", CORE_NAMESPACE)
ET.register_namespace("c", C_NAMESPACE)
ET.register_namespace("glib", GLIB_NAMESPACE)
@@ -249,6 +241,31 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> st
merge_and_transform_elements("interface", object_type_names)
merge_and_transform_elements("enumeration", enum_type_names | error_type_names)
for source_root in [core_root, base_root]:
for record in source_root.findall(".//record", GIR_NAMESPACES):
owner = record.get(f"{{{GLIB_NAMESPACE}}}is-gtype-struct-for")
if owner is not None and owner in object_type_names:
merged_namespace.append(record)
known_names = object_type_names | enum_type_names | error_type_names
referenced_names = set()
for elem in merged_namespace.iter():
type_name = elem.get("name")
if type_name is None:
continue
for prefix in ("Frida.", "FridaBase."):
if type_name.startswith(prefix):
referenced_names.add(type_name[len(prefix):])
break
needed_names = referenced_names - known_names
for source_root in [core_root, base_root]:
source_namespace = source_root.find("namespace", GIR_NAMESPACES)
for callback in source_namespace.findall("callback", GIR_NAMESPACES):
if callback.get("name") in needed_names:
merged_namespace.append(callback)
inject_documentation(merged_namespace, docs)
ET.indent(merged_root, space=" ")
result = ET.tostring(merged_root,
encoding="unicode",
@@ -260,6 +277,328 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> st
def filter_elements(elements: List[ET.Element], spec_set: Set[str]):
return [elem for elem in elements if elem.get("name") in spec_set]
@dataclass
class DocComment:
body: str
params: dict
returns: str
filename: str = "<generated>"
line: int = 0
@dataclass
class DocIndex:
types: dict # type_name -> DocComment
members: dict # (type_name, member_name) -> DocComment
enum_members: dict # (enum_name, member_name) -> DocComment
DOC_TYPE_PATTERN = re.compile(r"public\s+(?:sealed\s+|abstract\s+)?(class|interface|enum|errordomain)\s+(\w+)")
DOC_SIGNAL_PATTERN = re.compile(r"public\s+signal\s+\S.*?\b(\w+)\s*\(")
DOC_PROPERTY_PATTERN = re.compile(r"public\s+(?:unowned\s+)?\S.*?\b(\w+)\s*\{")
DOC_METHOD_PATTERN = re.compile(r"public\s+\S.*?\b(\w+)\s*\(")
DOC_ENUM_MEMBER_PATTERN = re.compile(r"([A-Z][A-Z0-9_]*)\b")
def build_doc_index(src_dir: Path) -> DocIndex:
repo_root = src_dir.parent
sources = []
for name in TOPLEVEL_NAMES:
path = src_dir / name
if path.exists():
sources.append(path)
base_dir = src_dir.parent / "lib" / "base"
if base_dir.is_dir():
sources.extend(sorted(base_dir.rglob("*.vala")))
index = DocIndex(types={}, members={}, enum_members={})
for path in sources:
try:
filename = path.relative_to(repo_root).as_posix()
except ValueError:
filename = path.name
_collect_docs_from_source(path.read_text(encoding="utf-8"), index, filename)
return index
def _collect_docs_from_source(source: str, index: DocIndex, filename: str):
lines = source.split("\n")
pending = None
current_type = None
current_is_enum = False
i = 0
n = len(lines)
while i < n:
line = lines[i]
stripped = line.strip()
if stripped.startswith("/**"):
start_line = i + 1
block = [line]
while "*/" not in lines[i]:
i += 1
block.append(lines[i])
pending = _parse_doc_block(block)
pending.filename = filename
pending.line = start_line
i += 1
continue
if stripped == "" or stripped.startswith("//") or stripped.startswith("["):
i += 1
continue
indent = len(line) - len(line.lstrip("\t"))
type_match = DOC_TYPE_PATTERN.match(stripped)
if indent == 1 and type_match is not None:
current_type = type_match.group(2)
current_is_enum = type_match.group(1) in ("enum", "errordomain")
if pending is not None:
index.types[current_type] = pending
pending = None
i += 1
continue
if pending is not None and current_type is not None and indent >= 2:
if current_is_enum:
member_match = DOC_ENUM_MEMBER_PATTERN.match(stripped)
if member_match is not None:
index.enum_members[(current_type, member_match.group(1))] = pending
else:
name = _parse_member_name(stripped, current_type)
if name is not None:
index.members[(current_type, name)] = pending
pending = None
i += 1
continue
pending = None
i += 1
def _parse_member_name(stripped: str, type_name: str):
if not stripped.startswith("public"):
return None
signal_match = DOC_SIGNAL_PATTERN.match(stripped)
if signal_match is not None:
return signal_match.group(1)
brace = stripped.find("{")
if brace != -1 and "(" not in stripped[:brace]:
property_match = DOC_PROPERTY_PATTERN.match(stripped)
if property_match is not None:
return property_match.group(1)
ctor_match = re.match(r"public\s+" + re.escape(type_name) + r"(?:\.(\w+))?\s*\(", stripped)
if ctor_match is not None:
return "new" if ctor_match.group(1) is None else ctor_match.group(1)
method_match = DOC_METHOD_PATTERN.match(stripped)
if method_match is not None:
return method_match.group(1)
return None
def _parse_doc_block(block: List[str]) -> DocComment:
text_lines = []
for raw in block:
s = raw.strip()
if s.startswith("/**"):
s = s[3:]
if s.endswith("*/"):
s = s[:-2]
s = s.strip()
if s.startswith("*"):
s = s[1:]
if s.startswith(" "):
s = s[1:]
text_lines.append(s.rstrip())
# Valadoc block tags: "@param <name> <description>" and "@return <description>".
body_lines = []
params = {}
returns_lines = []
target = body_lines
for line in text_lines:
param_match = re.match(r"@param\s+(\w+)\s*(.*)", line)
return_match = re.match(r"@returns?\s*(.*)", line)
if param_match is not None:
params[param_match.group(1)] = [param_match.group(2)]
target = params[param_match.group(1)]
continue
if return_match is not None:
returns_lines = [return_match.group(1)]
target = returns_lines
continue
target.append(line)
def finish(lines):
return _valadoc_to_markdown("\n".join(lines).strip())
return DocComment(
body=finish(body_lines),
params={k: finish(v) for k, v in params.items()},
returns=finish(returns_lines),
)
def _valadoc_to_markdown(text: str) -> str:
if not text:
return text
# Code blocks: {{{ ... }}} -> fenced ``` blocks.
def code_block(match):
code = match.group(1).strip("\n")
return "\n```\n" + code + "\n```\n"
text = re.sub(r"\{\{\{(.*?)\}\}\}", code_block, text, flags=re.DOTALL)
# Note: {@link ...} cross-references are resolved later, at injection time,
# where the full symbol table is available (see inject_documentation).
# Inline monospace: ``x`` -> `x` (valadoc) is already markdown-compatible.
# Bold: ''x'' -> **x**.
text = re.sub(r"''(.+?)''", r"**\1**", text)
# Italic: //x// -> *x*, but leave protocol-relative URLs (://) alone.
text = re.sub(r"(?<![:/])//(?!/)(.+?)//", r"*\1*", text)
return text
def inject_documentation(merged_namespace: ET.Element, docs: DocIndex):
glib_signal_tag = f"{{{GLIB_NAMESPACE}}}signal"
callable_tags = {
f"{{{CORE_NAMESPACE}}}method",
f"{{{CORE_NAMESPACE}}}constructor",
f"{{{CORE_NAMESPACE}}}function",
f"{{{CORE_NAMESPACE}}}virtual-method",
glib_signal_tag,
}
property_tag = f"{{{CORE_NAMESPACE}}}property"
member_tag = f"{{{CORE_NAMESPACE}}}member"
doc_tag = f"{{{CORE_NAMESPACE}}}doc"
# Build a symbol table so {@link ...} can be turned into real gi-docgen
# cross-references. Keys use Vala-style names (underscores); values carry the
# gi-docgen link kind and the GIR name (dashes for properties/signals).
type_kinds = {} # type_name -> gi-docgen kind ("class"/"iface"/"enum"/"error")
member_kinds = {} # (type_name, vala_member) -> (kind, gir_name)
for type_elem in list(merged_namespace):
type_name = type_elem.get("name")
if type_name is None:
continue
tag = type_elem.tag.split("}")[-1]
if tag == "class":
type_kinds[type_name] = "class"
elif tag == "interface":
type_kinds[type_name] = "iface"
elif tag == "enumeration":
type_kinds[type_name] = "error" \
if type_elem.get(f"{{{GLIB_NAMESPACE}}}error-domain") is not None \
else "enum"
for child in list(type_elem):
cname = child.get("name")
if cname is None:
continue
if child.tag == f"{{{CORE_NAMESPACE}}}method":
member_kinds[(type_name, cname)] = ("method", cname)
elif child.tag == f"{{{CORE_NAMESPACE}}}constructor":
member_kinds[(type_name, cname)] = ("ctor", cname)
elif child.tag == property_tag:
member_kinds[(type_name, cname.replace("-", "_"))] = ("property", cname)
elif child.tag == glib_signal_tag:
member_kinds[(type_name, cname.replace("-", "_"))] = ("signal", cname)
def resolve_links(text: str) -> str:
def repl(match):
target = re.sub(r"^Frida\.", "", match.group(1).strip())
parts = target.split(".")
if len(parts) == 1:
kind = type_kinds.get(parts[0])
if kind is not None:
return f"[{kind}@Frida.{parts[0]}]"
elif len(parts) == 2:
info = member_kinds.get((parts[0], parts[1]))
if info is not None:
kind, gir_name = info
if kind == "method":
return f"[method@Frida.{parts[0]}.{gir_name}]"
if kind == "ctor":
return f"[ctor@Frida.{parts[0]}.{gir_name}]"
if kind == "property":
return f"[property@Frida.{parts[0]}:{gir_name}]"
if kind == "signal":
return f"[signal@Frida.{parts[0]}::{gir_name}]"
return "`" + target + "`"
return re.sub(r"\{@link\s+([^}]+)\}", repl, text)
def set_doc(elem: ET.Element, text: str, filename: str = "<generated>",
line: int = 0):
if not text:
return
if elem.find(doc_tag) is not None:
return
doc = ET.Element(doc_tag)
doc.set("xml:space", "preserve")
# gi-docgen accesses filename/line unconditionally, so always emit them,
# mirroring what g-ir-scanner produces for hand-written C.
doc.set("filename", filename)
doc.set("line", str(line))
doc.text = resolve_links(text)
elem.insert(0, doc)
def member_doc(type_name: str, name: str):
comment = docs.members.get((type_name, name))
if comment is None:
for suffix in ("_finish", "_sync"):
if name.endswith(suffix):
comment = docs.members.get((type_name, name[: -len(suffix)]))
if comment is not None:
break
return comment
def apply_callable(elem: ET.Element, comment: DocComment):
set_doc(elem, comment.body, comment.filename, comment.line)
if comment.returns:
rv = elem.find(f"{{{CORE_NAMESPACE}}}return-value")
if rv is not None:
set_doc(rv, comment.returns, comment.filename, comment.line)
params_elem = elem.find(f"{{{CORE_NAMESPACE}}}parameters")
if params_elem is not None and comment.params:
for param in params_elem.findall(f"{{{CORE_NAMESPACE}}}parameter"):
text = comment.params.get(param.get("name"))
if text:
set_doc(param, text, comment.filename, comment.line)
for type_elem in list(merged_namespace):
type_name = type_elem.get("name")
if type_name is None:
continue
tag = type_elem.tag.split("}")[-1]
type_comment = docs.types.get(type_name)
if type_comment is not None and tag in ("class", "interface", "enumeration"):
set_doc(type_elem, type_comment.body, type_comment.filename,
type_comment.line)
for child in list(type_elem):
child_name = child.get("name")
if child_name is None:
continue
if child.tag in callable_tags:
lookup = child_name.replace("-", "_") if child.tag == glib_signal_tag \
else child_name
comment = member_doc(type_name, lookup)
if comment is not None:
apply_callable(child, comment)
elif child.tag == f"{{{CORE_NAMESPACE}}}constructor" \
and child_name == "new":
# Vala's implicit default constructor; give it a sensible
# description rather than leaving it bare.
set_doc(child, f"Creates a new [class@Frida.{type_name}].")
elif child.tag == property_tag:
vala_name = child_name.replace("-", "_")
comment = docs.members.get((type_name, vala_name))
if comment is not None:
set_doc(child, comment.body, comment.filename, comment.line)
# The C API also exposes the property through getter/setter
# methods; give them the same description.
accessors = {"get_" + vala_name, "set_" + vala_name}
for sibling in type_elem:
if sibling.tag == f"{{{CORE_NAMESPACE}}}method" \
and sibling.get("name") in accessors:
set_doc(sibling, comment.body, comment.filename,
comment.line)
elif child.tag == member_tag:
comment = docs.enum_members.get((type_name, child_name.upper()))
if comment is not None:
set_doc(child, comment.body, comment.filename, comment.line)
def emit_vapi(api, output_dir):
with OutputFile(output_dir / f"frida-core-{api.version}.vapi") as output_vapi_file:
output_vapi_file.write("[CCode (cheader_filename = \"frida-core.h\", cprefix = \"Frida\", lower_case_cprefix = \"frida_\")]")
@@ -304,6 +643,17 @@ def emit_vapi(api, output_dir):
output_deps_file.write("gobject-2.0\n")
output_deps_file.write("gio-2.0\n")
def emit_symbol_maps_from_source(output_dir: Path):
with OutputFile(output_dir / 'frida-core.version') as f:
f.write("{\n")
f.write(" global:\n")
f.write(" frida_*;\n")
f.write(" _frida_*;\n")
f.write("\n")
f.write(" local:\n")
f.write(" *;\n")
f.write("};\n")
def parse_api(frida_version, frida_version_components, api_version, toplevel_code, core_header, core_vapi, base_header, base_vapi):
all_headers = core_header + "\n" + base_header
@@ -311,6 +661,7 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
enum_types = []
base_public_types = {
"SpawnGatingOptions": "ProcessMatchOptions",
"FrontmostQueryOptions": "SpawnOptions",
"ApplicationQueryOptions": "FrontmostQueryOptions",
"ProcessQueryOptions": "ApplicationQueryOptions",
@@ -325,6 +676,9 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
"EndpointParameters": "PortalService",
"AuthenticationService": "EndpointParameters",
"StaticAuthenticationService": "AuthenticationService",
"WebRequestHandler": "StaticAuthenticationService",
"WebRequest": "WebRequestHandler",
"WebResponse": "WebRequest",
}
internal_type_prefixes = [
"AgentMessageKind",
@@ -419,7 +773,7 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
object_type.c_method_prototypes.append(method_cprototype)
elif method_name == 'get_type':
object_type.c_get_type = method_cprototype.replace("G_GNUC_CONST ;", "G_GNUC_CONST;")
for d in re.finditer(r"^typedef.+?\(\*(" + object_type.c_name + r".+?)\) \(.+\);$", core_header, re.MULTILINE):
for d in re.finditer(r"^typedef.+?\(\*(" + object_type.c_name + r".+?)\) \(.+\);$", all_headers, re.MULTILINE):
delegate_cname = d.group(1)
if delegate_cname not in seen_cdelegates:
seen_cdelegates.add(delegate_cname)
@@ -510,6 +864,8 @@ def function_is_public(name):
"generate_certificate",
"get_dbus_context",
"invalidate_dbus_context",
"make_json_reader",
"make_json_reader_taking_node",
"make_parameters_dict",
"compute_system_parameters",
"parse_control_address",
@@ -519,6 +875,8 @@ def function_is_public(name):
"check_kernel_version",
"get_syscall_signatures",
"get_compat32_syscall_signatures",
"get_xnu_mach_traps",
"get_xnu_bsd_syscalls",
"make_stdio_pipes",
"make_stdio_pipe",
}
+75 -39
View File
@@ -3,6 +3,7 @@ fs = import('fs')
generate_api = [python, files('generate.py')]
generate_api_args = [
meson.current_build_dir(),
frida_version,
frida_major_version.to_string(),
frida_minor_version.to_string(),
@@ -15,7 +16,6 @@ generate_api_args = [
base_build_dir / 'frida-base.h',
base_build_dir / base_gir_name,
base_build_dir / f'frida-base-@[email protected]',
meson.current_build_dir(),
]
core_api = custom_target('frida-core-api',
@@ -44,6 +44,24 @@ glib_gir = fs.copyfile('GLib-2.0.gir', install: install_glib_girs, install_dir:
gobject_gir = fs.copyfile('GObject-2.0.gir', install: install_glib_girs, install_dir: girdir)
gio_gir = fs.copyfile('Gio-2.0.gir', install: install_glib_girs, install_dir: girdir)
g_ir_compiler = find_program('g-ir-compiler', required: false)
if g_ir_compiler.found()
custom_target('frida-core-typelib',
command: [
g_ir_compiler,
'--output', '@OUTPUT@',
'--includedir=' + girdir,
'--includedir=' + meson.current_source_dir(),
'@INPUT@',
],
input: core_public_gir,
output: f'Frida-@[email protected]',
depends: core,
install: true,
install_dir: typelibdir,
)
endif
core_public_vapi_stamp = custom_target('frida-core-vapi-stamp',
input: core_public_vapi,
output: f'frida-core-@[email protected]',
@@ -54,22 +72,26 @@ core_public_vapi_stamp = custom_target('frida-core-vapi-stamp',
],
)
core_public_lib = custom_target('frida-core-library',
input: [core] + internal_libs,
output: f'libfrida-core-@[email protected]',
command: [
python,
files('merge.py'),
lib,
libtool,
ar,
meson.current_build_dir(),
'@OUTPUT@',
'@INPUT@'
],
install: true,
install_dir: get_option('libdir'),
)
if is_shared
core_public_lib = core
else
core_public_lib = custom_target('frida-core-library',
input: [core] + internal_libs,
output: f'libfrida-core-@[email protected]',
command: [
python,
files('merge.py'),
lib,
libtool,
ar,
meson.current_build_dir(),
'@OUTPUT@',
'@INPUT@'
],
install: true,
install_dir: get_option('libdir'),
)
endif
core_public_extra_headers = []
if host_os == 'android'
@@ -96,29 +118,43 @@ installed_variables = {
'frida_vapidir': vapidir,
}
core_public_dep = declare_dependency(
sources: [core_public_h, core_public_gir, glib_gir, gobject_gir, gio_gir, core_public_vapi_stamp, core_public_extra_headers],
include_directories: include_directories('.'),
link_args: backend_libs_private,
link_with: core_public_lib,
dependencies: core_deps,
variables: uninstalled_variables,
)
core_public_dep_kwargs = {
'sources': [core_public_h, core_public_gir, glib_gir, gobject_gir, gio_gir, core_public_vapi_stamp] + core_public_extra_headers,
'include_directories': include_directories('.'),
'link_with': core_public_lib,
'dependencies': core_deps,
'variables': uninstalled_variables,
}
if not is_shared
core_public_dep_kwargs += {
'link_args': backend_libs_private,
}
endif
core_public_dep = declare_dependency(kwargs: core_public_dep_kwargs)
pkg = import('pkgconfig')
pkg.generate(
filebase: f'frida-core-@api_version@',
name: 'Frida Core Library',
version: frida_version,
description: 'Instrumentation toolkit for developers, reverse-engineers, and security researchers',
requires: ['glib-2.0', 'gobject-2.0', 'gio-2.0', 'json-glib-1.0'],
requires_private: ['gmodule-2.0', 'gee-0.8', 'libsoup-3.0', 'frida-gum-1.0', 'frida-gumjs-inspector-1.0', 'libbrotlidec'] + backend_reqs_private,
subdirs: f'frida-@api_version@',
libraries: core_public_lib,
libraries_private: backend_libs_private,
extra_cflags: ['-I${frida_includedir}'],
variables: installed_variables,
uninstalled_variables: uninstalled_variables,
)
pkg_kwargs = {
'filebase': f'frida-core-@api_version@',
'name': 'Frida Core Library',
'version': frida_version,
'description': 'Instrumentation toolkit for developers, reverse-engineers, and security researchers',
'requires': ['glib-2.0', 'gobject-2.0', 'gio-2.0', 'json-glib-1.0'],
'subdirs': f'frida-@api_version@',
'extra_cflags': ['-I${frida_includedir}'],
'variables': installed_variables,
'uninstalled_variables': uninstalled_variables,
}
if is_shared
pkg_kwargs += {
'libraries': ['-L${libdir}', f'-lfrida-core-@api_version@'],
}
else
pkg_kwargs += {
'libraries': core_public_lib,
'requires_private': ['gmodule-2.0', 'gee-0.8', 'libsoup-3.0', 'frida-gum-1.0', 'frida-gumjs-inspector-1.0', 'libbrotlidec'] + backend_reqs_private,
'libraries_private': backend_libs_private,
}
endif
pkg.generate(kwargs: pkg_kwargs)
meson.override_dependency(f'frida-core-@api_version@', core_public_dep)
+317 -162
View File
@@ -5,32 +5,44 @@ namespace Frida.Barebone {
private Cancellable io_cancellable = new Cancellable ();
private SocketConnection hostlink;
private IOStream hostlink;
private BufferedInputStream input;
private OutputStream output;
private ByteOrder byte_order;
private uint pointer_size;
private AgentConfig agent_config;
private VsockTransportConfig? vsock_transport;
private ImageConfig? image_config;
private KernelKind kernel_kind;
private KernelRelocation? relocation;
private uint64 kernel_base;
private Machine machine;
private Allocator allocator;
private Gee.List<SymbolInfo> kernel_symbols;
private Allocation elf_allocation;
private Allocation config_allocation;
private Callback mprotect_callback;
private Callback remap_writable_pages_callback;
private Gee.Map<uint16, Promise<Variant>> pending_requests = new Gee.HashMap<uint16, Promise<Variant>> ();
private uint16 next_request_id = 1;
private const int COMMAND_TIMEOUT_MS = 25000;
public static async AgentConnection open (AgentConfig agent_config, ImageConfig? image_config, Machine machine,
Allocator allocator, Cancellable? cancellable) throws Error, IOError {
public static async AgentConnection open (AgentConfig agent_config, ImageConfig? image_config,
KernelKind kernel_kind, KernelRelocation? relocation, uint64 kernel_base, Machine machine,
Allocator allocator, Gee.List<SymbolInfo> kernel_symbols, Cancellable? cancellable)
throws Error, IOError {
var connection = new AgentConnection () {
agent_config = agent_config,
image_config = image_config,
kernel_kind = kernel_kind,
relocation = relocation,
kernel_base = kernel_base,
machine = machine,
allocator = allocator,
kernel_symbols = kernel_symbols,
};
try {
@@ -42,36 +54,51 @@ namespace Frida.Barebone {
return connection;
}
private async bool init_async (int io_priority, Cancellable? cancellable) throws Error, IOError {
HostlinkTransportConfig? transport_config = agent_config.transport as HostlinkTransportConfig;
if (transport_config == null)
throw new Error.NOT_SUPPORTED ("Unsupported transport config: only hostlink is supported for now");
/**
* Attaches to an agent already resident in the target, which brought its own
* transport with it, such as the Linux kernel module exposing /dev/frida.
*/
public static async AgentConnection open_resident (IOStream stream, Cancellable? cancellable)
throws Error, IOError {
var connection = new AgentConnection () {
byte_order = ByteOrder.HOST,
pointer_size = (uint) sizeof (void *),
};
var qmp = yield QmpClient.open (transport_config.qmp, 0, cancellable);
var link = yield qmp.open_hostlink (cancellable);
hostlink = link.connection;
input = (BufferedInputStream) Object.new (typeof (BufferedInputStream),
"base-stream", hostlink.get_input_stream (),
"close-base-stream", false,
"buffer-size", 128 * 1024);
output = hostlink.get_output_stream ();
connection.adopt_hostlink_streams (stream);
connection.process_incoming_messages.begin ();
return connection;
}
private const uint8 TRANSPORT_KIND_VIRTIO = 0;
private const uint8 TRANSPORT_KIND_VSOCK = 1;
private const uint8 TRANSPORT_KIND_VIRTIO_PCI = 2;
private async bool init_async (int io_priority, Cancellable? cancellable) throws Error, IOError {
var transport_tag = yield resolve_transport (cancellable);
var gdb = machine.gdb;
ByteOrder byte_order = gdb.byte_order;
uint pointer_size = gdb.pointer_size;
byte_order = gdb.byte_order;
pointer_size = gdb.pointer_size;
uint64 kernel_base;
Layout layout;
uint64 preferred_base = 0;
if (image_config != null) {
kernel_base = image_config.base.address;
layout = yield Layout.load_from_symbol_source (File.new_for_path (image_config.file), kernel_base,
byte_order, pointer_size, cancellable);
var payload = yield Img4.parse_file (File.new_for_path (image_config.file), cancellable);
var kernelcache = Layout.parse_kernelcache (payload.data);
preferred_base = kernelcache.preferred_address;
if (relocation != null) {
layout = Layout.load_from_module (kernelcache, payload.data, preferred_base, byte_order,
pointer_size);
rebase_layout (layout, preferred_base, relocation);
} else {
layout = Layout.load_from_module (kernelcache, payload.data, kernel_base, byte_order,
pointer_size);
}
} else {
kernel_base = 0;
layout = new Layout.empty ();
}
if (kernel_base == 0)
throw new Error.NOT_SUPPORTED ("Missing kernel_base");
var symbols = new Gee.HashMap<string, SymbolInfo> ();
var hash_builder = new SymbolHashBuilder ();
@@ -83,9 +110,12 @@ namespace Frida.Barebone {
foreach (var e in image_config.symbols.entries) {
unowned string name = e.key;
if (!symbols.has_key (name)) {
uint32 offset = (relocation != null)
? relocation.runtime_offset (preferred_base + e.value)
: (uint32) e.value;
var s = new SymbolInfo () {
name = name,
offset = (uint32) e.value,
offset = offset,
symbol_type = 0xf,
section = 0x10, // FIXME
};
@@ -95,13 +125,71 @@ namespace Frida.Barebone {
}
}
SymbolInfo? thread_block = symbols["thread_block"];
if (thread_block == null)
throw new Error.NOT_SUPPORTED ("Missing symbol for thread_block");
KernelKind kind = kernel_kind;
if (kind == AUTO)
kind = (image_config != null) ? KernelKind.XNU : KernelKind.BARE;
var config_builder = new VariantBuilder (new VariantType ("(tuta(ssuuuu)ay)"));
config_builder.add ("t", link.mmio);
config_builder.add ("u", link.irq);
foreach (var s in kernel_symbols) {
symbols[s.name] = s;
hash_builder.add_symbol (s);
}
KernelFlavor flavor;
if (kind == XNU) {
if (kernel_base == 0)
throw new Error.NOT_SUPPORTED ("Missing kernel_base");
flavor = new XnuKernelFlavor (machine, kernel_base, symbols);
} else if (kind == WIN9X) {
flavor = new Win9xKernelFlavor (machine, symbols);
} else {
flavor = new BareKernelFlavor (machine);
}
Bytes symbol_data = hash_builder.build (byte_order);
Gum.ElfModule elf;
try {
elf = new Gum.ElfModule.from_file (agent_config.path);
} catch (Gum.Error e) {
throw new Error.INVALID_ARGUMENT ("%s", e.message);
}
var raw_elf = gdb.make_buffer (new Bytes (elf.get_file_data ()));
// Slots whose symbol is absent on this kernel are left zero; the agent's
// xnu.rs uses Option<fn> and falls back across per-kernel name variants.
elf.enumerate_symbols (s => {
unowned Gum.ElfSectionDetails? sect = s.section;
if (sect != null && sect.name == ".kernel_addrs") {
string name = s.name[1:];
SymbolInfo? info = symbols[name];
if (info != null) {
size_t file_offset = (size_t) (sect.offset + (s.address - sect.address));
raw_elf.write_pointer (file_offset, kernel_base + info.offset);
}
}
return true;
});
yield flavor.prepare (cancellable);
size_t page_size = yield machine.query_page_size (cancellable);
elf_allocation = yield inject_elf (elf, raw_elf.bytes, page_size, machine, allocator, cancellable);
uint64 start_address = 0;
uint64 base_va = elf_allocation.virtual_address;
elf.enumerate_symbols (e => {
if (e.name == "_start")
start_address = base_va + e.address;
return true;
});
if (start_address == 0)
throw new Error.INVALID_ARGUMENT ("Invalid agent: no _start symbol found");
var config_builder = new VariantBuilder (new VariantType ("((tt)yvta(ssuuuu)ay)"));
config_builder.add ("(tt)", base_va, (uint64) elf_allocation.size);
config_builder.add_value (transport_tag.get_child_value (0));
config_builder.add_value (transport_tag.get_child_value (1));
config_builder.add ("t", kernel_base);
config_builder.open (new VariantType ("a(ssuuuu)"));
@@ -117,78 +205,9 @@ namespace Frida.Barebone {
}
config_builder.close ();
Bytes symbol_data = hash_builder.build (byte_order);
config_builder.add_value (Variant.new_from_data (new VariantType ("ay"), symbol_data.get_data (), true,
symbol_data));
Gum.ElfModule elf;
try {
elf = new Gum.ElfModule.from_file (agent_config.path);
} catch (Gum.Error e) {
throw new Error.INVALID_ARGUMENT ("%s", e.message);
}
var raw_elf = gdb.make_buffer (new Bytes (elf.get_file_data ()));
var missing_symbols = new Gee.ArrayList<string> ();
elf.enumerate_symbols (s => {
unowned Gum.ElfSectionDetails? sect = s.section;
if (sect != null && sect.name == ".kernel_addrs") {
string name = s.name[1:];
SymbolInfo? info = symbols[name];
if (info != null) {
size_t file_offset = (size_t) (sect.offset + (s.address - sect.address));
raw_elf.write_pointer (file_offset, kernel_base + info.offset);
} else {
missing_symbols.add (name);
}
}
return true;
});
if (!missing_symbols.is_empty) {
throw new Error.INVALID_ARGUMENT ("Missing symbols for: %s",
string.joinv (", ", missing_symbols.to_array ()));
}
yield machine.enter_exception_level (1, 1000, cancellable);
var bp = yield gdb.add_breakpoint (SOFT, kernel_base + thread_block.offset, 4, cancellable);
GDB.Breakpoint? hit_breakpoint = null;
do {
var exception = yield gdb.continue_until_exception (cancellable);
hit_breakpoint = exception.breakpoint;
} while (hit_breakpoint != bp);
yield bp.remove (cancellable);
size_t page_size = yield machine.query_page_size (cancellable);
elf_allocation = yield inject_elf (elf, raw_elf.bytes, page_size, machine, allocator, cancellable);
uint64 start_address = 0;
uint64 remap_writable_pages_address = 0;
uint64 mprotect_address = 0;
uint64 base_va = elf_allocation.virtual_address;
elf.enumerate_symbols (e => {
if (e.name == "_start")
start_address = base_va + e.address;
else if (e.name == "gum_barebone_try_remap_writable_pages")
remap_writable_pages_address = base_va + e.address;
else if (e.name == "gum_try_mprotect")
mprotect_address = base_va + e.address;
else
return true;
return start_address == 0 || mprotect_address == 0 || remap_writable_pages_address == 0;
});
if (start_address == 0)
throw new Error.INVALID_ARGUMENT ("Invalid agent: no _start symbol found");
if (remap_writable_pages_address == 0)
throw new Error.INVALID_ARGUMENT ("Invalid agent: no gum_barebone_try_remap_writable_pages symbol found");
if (mprotect_address == 0)
throw new Error.INVALID_ARGUMENT ("Invalid agent: no gum_try_mprotect symbol found");
remap_writable_pages_callback = yield new Callback (remap_writable_pages_address,
new RemapWritablePagesHandler (machine), machine, cancellable);
mprotect_callback = yield new Callback (mprotect_address, new MemoryProtectHandler (machine), machine, cancellable);
var config_blob = config_builder.end ().get_data_as_bytes ();
config_allocation = yield allocator.allocate (config_blob.get_size (), 8, cancellable);
@@ -200,17 +219,120 @@ namespace Frida.Barebone {
},
cancellable);
yield gdb.continue (cancellable);
yield flavor.settle (cancellable);
yield establish_hostlink (cancellable);
process_incoming_messages.begin ();
return true;
}
private async Variant resolve_transport (Cancellable? cancellable) throws Error, IOError {
if (agent_config.transport is HostlinkTransportConfig)
return yield connect_virtio_transport ((HostlinkTransportConfig) agent_config.transport, cancellable);
if (agent_config.transport is VsockTransportConfig) {
var config = (VsockTransportConfig) agent_config.transport;
vsock_transport = config;
return new Variant.tuple ({
new Variant.byte (TRANSPORT_KIND_VSOCK),
new Variant.variant (new Variant.uint32 (config.port))
});
}
throw new Error.NOT_SUPPORTED ("Unsupported transport config");
}
private async void establish_hostlink (Cancellable? cancellable) throws Error, IOError {
if (vsock_transport == null)
return;
#if WINDOWS
throw new Error.NOT_SUPPORTED ("Hostlink transport is not available on this OS");
#else
var address = new UnixSocketAddress (vsock_transport.socket_path);
var client = new SocketClient ();
while (true) {
try {
adopt_hostlink_streams (yield client.connect_async (address, cancellable));
return;
} catch (GLib.Error e) {
if (e is IOError.CANCELLED)
throw (IOError) e;
var source = new TimeoutSource (50);
source.set_callback (establish_hostlink.callback);
source.attach (MainContext.get_thread_default ());
yield;
}
}
#endif
}
private async Variant connect_virtio_transport (HostlinkTransportConfig config, Cancellable? cancellable)
throws Error, IOError {
var qmp = yield QmpClient.open (config.qmp, 0, cancellable);
var link = yield qmp.open_hostlink (config.bus, cancellable);
adopt_hostlink_streams (link.connection);
if (config.bus != null) {
return new Variant.tuple ({
new Variant.byte (TRANSPORT_KIND_VIRTIO_PCI),
new Variant.variant (new Variant.tuple ({}))
});
}
Variant[] virtio_cfg = { new Variant.uint64 (link.mmio), new Variant.uint32 (link.irq) };
return new Variant.tuple ({
new Variant.byte (TRANSPORT_KIND_VIRTIO),
new Variant.variant (new Variant.tuple (virtio_cfg))
});
}
private void adopt_hostlink_streams (IOStream connection) {
hostlink = connection;
input = (BufferedInputStream) Object.new (typeof (BufferedInputStream),
"base-stream", hostlink.get_input_stream (),
"close-base-stream", false,
"buffer-size", 128 * 1024);
output = hostlink.get_output_stream ();
}
private void rebase_layout (Layout layout, uint64 preferred_base, KernelRelocation reloc) throws Error {
foreach (var s in layout.symbols)
s.offset = reloc.runtime_offset (preferred_base + s.offset);
foreach (var m in layout.modules) {
m.offset = reloc.runtime_offset (preferred_base + m.offset);
m.start_func_offset = reloc.runtime_offset (preferred_base + m.start_func_offset);
m.stop_func_offset = reloc.runtime_offset (preferred_base + m.stop_func_offset);
}
}
public async void close (Cancellable? cancellable) throws IOError {
io_cancellable.cancel ();
}
public async HostProcessInfo[] enumerate_processes (Cancellable? cancellable) throws Error, IOError {
var response = yield execute_command (Command.ENUMERATE_PROCESSES, new Variant.tuple ({}), cancellable);
if (!response.check_format_string ("a(us)", false))
throw new Error.PROTOCOL ("Invalid enumerate_processes response format");
var result = new Gee.ArrayList<HostProcessInfo?> ();
uint32 pid;
unowned string path;
var it = response.iterator ();
while (it.next ("(u&s)", out pid, out path)) {
result.add (HostProcessInfo (pid, basename_of (path),
new HashTable<string, Variant> (str_hash, str_equal)));
}
var processes = new HostProcessInfo[result.size];
for (int i = 0; i != result.size; i++)
processes[i] = result[i];
return processes;
}
private static string basename_of (string path) {
int start = path.last_index_of_char ('\\');
return (start != -1) ? path[start + 1:] : path;
}
public async AgentScriptId create_script (string source, Cancellable? cancellable) throws Error, IOError {
var payload = new Variant ("s", source);
var response = yield execute_command (Command.CREATE_SCRIPT, payload, cancellable);
@@ -241,16 +363,7 @@ namespace Frida.Barebone {
private async Variant execute_command (Command command, Variant payload, Cancellable? cancellable) throws Error, IOError {
uint16 request_id = next_request_id++;
var command_message = new Variant ("(yqv)", (uint8) command, request_id, payload);
if (machine.gdb.byte_order != ByteOrder.HOST)
command_message = command_message.byteswap ();
var command_bytes = command_message.get_data_as_bytes ();
var builder = machine.gdb.make_buffer_builder ();
Bytes frame = builder
.append_uint32 ((uint32) command_bytes.get_size ())
.append_bytes (command_bytes)
.build ();
Bytes frame = frame_message (command, request_id, payload);
var promise = new Promise<Variant> ();
pending_requests[request_id] = promise;
@@ -271,16 +384,25 @@ namespace Frida.Barebone {
});
timeout_source.attach (MainContext.get_thread_default ());
Variant response = null;
try {
return yield promise.future.wait_async (cancellable);
response = yield promise.future.wait_async (cancellable);
} finally {
timeout_source.destroy ();
}
if (!response.check_format_string ("(bv)", false))
throw new 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 ());
return detail;
}
private async void process_incoming_messages () {
var byte_order = machine.gdb.byte_order;
try {
while (true) {
size_t header_size = 4;
@@ -324,6 +446,30 @@ namespace Frida.Barebone {
payload.get ("(u&s)", out script_handle, out json);
script_message (AgentScriptId (script_handle), json, null);
} else if (command_code == Command.REMAP_WRITABLE_PAGES) {
Variant result;
try {
result = yield remap_writable_pages (payload, io_cancellable);
} catch (Error e) {
result = new Variant.uint64 (0);
}
yield send_reply (request_id, result);
} else if (command_code == Command.MEMORY_PROTECT) {
Variant result;
try {
result = yield protect_memory (payload, io_cancellable);
} catch (Error e) {
result = new Variant.boolean (false);
}
yield send_reply (request_id, result);
} else if (command_code == Command.PATCH_CODE) {
Variant result;
try {
result = yield patch_code (payload, io_cancellable);
} catch (Error e) {
result = new Variant.boolean (false);
}
yield send_reply (request_id, result);
} else if (command_code == Command.REPLY) {
Promise<Variant>? promise;
if (pending_requests.unset (request_id, out promise))
@@ -355,62 +501,67 @@ namespace Frida.Barebone {
}
}
private class RemapWritablePagesHandler : Object, CallbackHandler {
public signal void output (string message);
public uint arity {
get { return 2; }
private async Variant remap_writable_pages (Variant payload, Cancellable? cancellable) throws Error, IOError {
var arm64 = (Arm64Machine) machine;
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 ();
physical_addresses.add (yield arm64.translate_address (va, cancellable));
}
private Machine machine;
Allocation allocation = yield machine.allocate_pages (physical_addresses, cancellable);
public RemapWritablePagesHandler (Machine machine) {
this.machine = machine;
}
public async uint64 handle_invocation (uint64[] args, CallFrame frame, Cancellable? cancellable)
throws Error, IOError {
var pages = args[0];
var num_pages = (uint) args[1];
var physical_addresses = new Gee.ArrayList<uint64?> ();
var gdb = machine.gdb;
var reader = new BufferReader (yield gdb.read_buffer (pages, num_pages * gdb.pointer_size, cancellable));
for (uint i = 0; i != num_pages; i++)
physical_addresses.add (reader.read_pointer ());
Allocation allocation = yield machine.allocate_pages (physical_addresses, cancellable);
// TODO: Handle cleanup.
return allocation.virtual_address;
}
return new Variant.uint64 (allocation.virtual_address);
}
private class MemoryProtectHandler : Object, CallbackHandler {
public signal void output (string message);
private async Variant protect_memory (Variant payload, Cancellable? cancellable) throws Error, IOError {
uint64 address;
uint64 size;
uint32 prot;
payload.get ("(ttu)", out address, out size, out prot);
public uint arity {
get { return 3; }
yield machine.protect_pages (address, (size_t) size, (Gum.PageProtection) prot, cancellable);
return new Variant.boolean (true);
}
// 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 {
var arm64 = (Arm64Machine) machine;
uint64 va;
Variant bytes_value;
payload.get ("(t@ay)", out va, out bytes_value);
var data = (uint8[]) bytes_value.get_data_as_bytes ().get_data ();
size_t page_size = yield machine.query_page_size (cancellable);
size_t offset = 0;
while (offset < data.length) {
uint64 page_va = va + offset;
uint64 pa = yield arm64.translate_address (page_va, cancellable);
size_t chunk = size_t.min (page_size - (size_t) (page_va & (page_size - 1)), data.length - offset);
yield arm64.write_physical (pa, data[offset : offset + chunk], cancellable);
offset += chunk;
}
private Machine machine;
return new Variant.boolean (true);
}
public MemoryProtectHandler (Machine machine) {
this.machine = machine;
}
private async void send_reply (uint16 request_id, Variant payload) throws GLib.Error {
Bytes frame = frame_message (Command.REPLY, request_id, payload);
yield output.write_all_async (frame.get_data (), Priority.DEFAULT, io_cancellable, null);
}
public async uint64 handle_invocation (uint64[] args, CallFrame frame, Cancellable? cancellable)
throws Error, IOError {
var address = args[0];
var size = (size_t) args[1];
var prot = (Gum.PageProtection) args[2];
try {
yield machine.protect_pages (address, size, prot, cancellable);
return 1;
} catch (GLib.Error e) {
return 0;
}
}
private Bytes frame_message (Command command, uint16 request_id, Variant payload) {
var message = new Variant ("(yqv)", (uint8) command, request_id, payload);
if (byte_order != ByteOrder.HOST)
message = message.byteswap ();
var message_bytes = message.get_data_as_bytes ();
return new BufferBuilder (byte_order, pointer_size)
.append_uint32 ((uint32) message_bytes.get_size ())
.append_bytes (message_bytes)
.build ();
}
private enum Command {
@@ -418,6 +569,10 @@ namespace Frida.Barebone {
LOAD_SCRIPT = 2,
DESTROY_SCRIPT = 3,
POST_SCRIPT_MESSAGE = 4,
REMAP_WRITABLE_PAGES = 5,
MEMORY_PROTECT = 6,
PATCH_CODE = 7,
ENUMERATE_PROCESSES = 8,
REPLY = 128,
SCRIPT_MESSAGE = 129
}
+4 -4
View File
@@ -13,9 +13,9 @@ dependencies = [
[[package]]
name = "bindgen"
version = "0.72.0"
version = "0.72.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
dependencies = [
"bitflags",
"cexpr",
@@ -63,9 +63,9 @@ checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "clang-sys"
version = "1.8.1"
version = "1.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
dependencies = [
"glob",
"libc",
+19
View File
@@ -4,6 +4,25 @@ version = "1.0.0"
edition = "2024"
build = "build.rs"
[lib]
name = "frida_barebone_agent"
path = "src/lib.rs"
crate-type = ["rlib", "staticlib"]
# The XNU flavour is a freestanding executable the host injects and calls into.
# The Linux flavour is the staticlib above, linked into a .ko by linux/Kbuild.
[[bin]]
name = "frida-barebone-agent"
path = "src/main.rs"
required-features = ["blob"]
[features]
# Flavors the host injects and calls into, as opposed to the module Linux loads.
blob = []
xnu = ["blob"]
win9x = ["blob"]
linux = []
[profile.release]
panic = "abort"
opt-level = "z"
+21 -1
View File
@@ -21,10 +21,29 @@
export AR_aarch64_unknown_none=aarch64-none-elf-ar
export RANLIB_aarch64_unknown_none=aarch64-none-elf-ranlib
cargo build --release --features xnu
## Targets
The agent supports two kernels. One of them has to be picked; neither is a
default, since neither follows from the host you build on:
- `xnu` — a freestanding ELF executable that the Barebone backend
injects into a running kernel from the outside, over a remote stub.
- `linux` — a static library that becomes a kernel module, loaded from the
inside. See `linux/README.md`.
Everything above the `kernel` module is shared; each backend supplies the same
set of primitives (logging, allocation, threads, waiting, time, symbols) plus its
half of Gum's platform backend (`gum_xnu.rs` / `gum_linux.rs`).
## Development loop
export FRIDA_BAREBONE_CONFIG=$PWD/etc/xnu.json
cargo build --release && make -C ~/src/frida-python && killall -9 qemu-system-aarch64 && sleep 2 && frida -D barebone -p 0
cargo build --release --features xnu \
&& make -C ~/src/frida-python \
&& killall -9 qemu-system-aarch64 && sleep 2 \
&& frida -D barebone -p 0
## Speeding up loop
@@ -32,6 +51,7 @@
-- \
-Dfrida-core:compat=disabled \
-Dfrida-core:local_backend=disabled \
-Dfrida-core:simmy_backend=disabled \
-Dfrida-core:fruity_backend=disabled \
-Dfrida-core:droidy_backend=disabled \
-Dfrida-core:socket_backend=disabled \
+63
View File
@@ -0,0 +1,63 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(4) {
. = ALIGN(4);
_Get_Cur_VM_Handle = .; . += 4;
_Get_Sys_VM_Handle = .; . += 4;
_Schedule_Global_Event = .; . += 4;
_Create_Semaphore = .; . += 4;
_Wait_Semaphore = .; . += 4;
_Signal_Semaphore = .; . += 4;
_Get_Next_VM_Handle = .; . += 4;
_Release_Time_Slice = .; . += 4;
_Set_Global_Time_Out = .; . += 4;
_Cancel_Time_Out = .; . += 4;
_Get_System_Time = .; . += 4;
__HeapAllocate = .; . += 4;
__HeapFree = .; . += 4;
__PageAllocate = .; . += 4;
__PageFree = .; . += 4;
__CopyPageTable = .; . += 4;
__MapPhysToLinear = .; . += 4;
_Hook_VMM_Fault = .; . += 4;
_Fatal_Error_Handler = .; . += 4;
_VMMCreateThread = .; . += 4;
_Get_Cur_Thread_Handle = .; . += 4;
_Get_Initial_Thread_Handle = .; . += 4;
_Get_Next_Thread_Handle = .; . += 4;
__Debug_Printf_Service = .; . += 4;
__ContextSwitch = .; . += 4;
__PageModifyPermissions = .; . += 4;
__GetCurrentContext = .; . += 4;
_VPICD_Virtualize_IRQ = .; . += 4;
_VPICD_Phys_EOI = .; . += 4;
_VPICD_Physically_Unmask = .; . += 4;
}
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.bss : {
*(.bss*)
}
. = ALIGN(4);
_heap_start = .;
}
VERSION {
{
global:
_start;
local:
*;
};
}
+8
View File
@@ -26,6 +26,14 @@ SECTIONS {
__ZN9IOServiceC2Ev = .; . += 8;
__ZN8OSSymbol17withCStringNoCopyEPKc = .; . += 8;
__ZN6OSData9withBytesEPKvj = .; . += 8;
_kalloc_data = .; . += 8;
_kfree_data = .; . += 8;
_wakeup = .; . += 8;
_sock_socket = .; . += 8;
_sock_connect = .; . += 8;
_sock_send = .; . += 8;
_sock_receive = .; . += 8;
_sock_close = .; . += 8;
}
.data.rel.ro : {
*(.data.rel.ro*)
+47 -21
View File
@@ -8,12 +8,17 @@ fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let devkit_dir = PathBuf::from(env::var("GUMJS_DEVKIT_DIR").unwrap());
let cc_str = env::var("CC_aarch64_unknown_none")
let target = env::var("TARGET").unwrap();
let cc_str = env::var(format!("CC_{}", target.replace('-', "_")))
.or_else(|_| env::var("CC"))
.unwrap_or_else(|_| "cc".to_string());
let cc = Path::new(&cc_str);
let cc_include_paths = detect_gcc_include_paths(cc);
let cc_library_paths = detect_gcc_library_paths(cc);
// Same convention as cc-rs: the variable may carry flags, and a bare-metal
// compiler needs them to find its libc at all.
let mut cc_argv = cc_str.split_whitespace();
let cc = Path::new(cc_argv.next().unwrap());
let cc_args: Vec<&str> = cc_argv.collect();
let cc_include_paths = detect_gcc_include_paths(cc, &cc_args);
let cc_library_paths = detect_gcc_library_paths(cc, &cc_args);
let cc_clang_args: Vec<String> = cc_include_paths
.iter()
@@ -25,9 +30,10 @@ fn main() {
.header(devkit_dir.join("frida-gumjs.h").to_str().unwrap())
.clang_arg("-nostdinc")
.clang_arg("-target")
.clang_arg("aarch64-none-elf")
.clang_arg(format!("{}-none-elf", target.split('-').next().unwrap()))
.clang_args(cc_clang_args)
.clang_arg(format!("-I{}", devkit_dir.to_str().unwrap()))
.blocklist_type("max_align_t")
.merge_extern_blocks(true)
.generate()
.expect("Unable to generate bindings");
@@ -36,25 +42,45 @@ fn main() {
.write_to_file(out_dir.join("bindings.rs"))
.expect("Couldn't write bindings");
println!(
"cargo:rustc-link-search=native={}",
devkit_dir.to_str().unwrap()
);
println!("cargo:rustc-link-lib=static=frida-gumjs");
for path in cc_library_paths {
println!("cargo:rustc-link-search=native={}", path.to_string_lossy());
// The Linux flavour is a staticlib: kbuild owns the final link, so it — not
// us — decides where the devkit and libc archives come from.
if env::var("CARGO_FEATURE_BLOB").is_ok() {
println!(
"cargo:rustc-link-search=native={}",
devkit_dir.to_str().unwrap()
);
println!("cargo:rustc-link-lib=static=frida-gumjs");
for path in cc_library_paths {
println!("cargo:rustc-link-search=native={}", path.to_string_lossy());
}
for arg in &cc_args {
if let Some(path) = arg.strip_prefix("-L") {
println!("cargo:rustc-link-search=native={path}");
}
}
println!("cargo:rustc-link-lib=static=c");
println!("cargo:rustc-link-lib=static=m");
println!("cargo:rustc-link-arg=--export-dynamic");
println!("cargo:rustc-link-arg=--emit-relocs");
let script = if env::var("CARGO_FEATURE_WIN9X").is_ok() {
"agent-win9x.lds"
} else {
"agent.lds"
};
println!("cargo:rustc-link-arg=--script={script}");
println!("cargo:rustc-link-arg=--gc-sections");
// All of the agent lives in the library crate, so nothing in the binary
// references the entrypoint the host calls; keep the linker from
// dropping it along with the rest of the unreferenced archive members.
println!("cargo:rustc-link-arg=--undefined=_start");
}
println!("cargo:rustc-link-lib=static=c");
println!("cargo:rustc-link-lib=static=m");
println!("cargo:rustc-link-arg=--export-dynamic");
println!("cargo:rustc-link-arg=--emit-relocs");
println!("cargo:rustc-link-arg=--script=agent.lds");
println!("cargo:rustc-link-arg=--gc-sections");
println!("cargo:rerun-if-changed=build.rs");
}
pub fn detect_gcc_include_paths(gcc: &Path) -> Vec<PathBuf> {
pub fn detect_gcc_include_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
let out = Command::new(gcc)
.args(args)
.args(["-xc", "-E", "-v", "-"])
.stdin(Stdio::null())
.output()
@@ -94,8 +120,8 @@ pub fn detect_gcc_include_paths(gcc: &Path) -> Vec<PathBuf> {
paths
}
pub fn detect_gcc_library_paths(gcc: &Path) -> Vec<PathBuf> {
let out = Command::new(gcc).arg("-print-search-dirs").output()
pub fn detect_gcc_library_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
let out = Command::new(gcc).args(args).arg("-print-search-dirs").output()
.expect("Failed to execute GCC to detect library paths");
let stdout = String::from_utf8_lossy(&out.stdout);
@@ -0,0 +1,8 @@
{
"agent": {
"transport": {
"type": "socket",
"path": "/tmp/frida-guest.sock"
}
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"agent": {
"transport": {
"type": "device",
"path": "/dev/frida"
}
}
}
+50
View File
@@ -0,0 +1,50 @@
{
"connection": {
"host": "127.0.0.1",
"port": 52494,
"flavor": "vz",
"pid": 49248
},
"allocator": {
"mode": "target-functions",
"alloc_function": "0xfffffe0007ab6cdc",
"free_function": "0xfffffe0007ab74a8",
"alloc_flags": 512
},
"agent": {
"path": "/Users/oleavr/src/frida-core/src/barebone/agent/target/aarch64-unknown-none/release/frida-barebone-agent",
"transport": {
"type": "vsock",
"socket_path": "/tmp/claude-501/vphone-frida-21528.sock",
"port": 1339
}
},
"image": {
"file": "/Users/oleavr/src/vphone-cli/vm/iPhone17,3_26.1_23B85_Restore/kernelcache.research.vresearch101",
"symbols": {
"panic": "0x12a2814",
"IOLog": "0xac44cc",
"thread_block": "0xad3dac",
"assert_wait": "0xacf1f8",
"assert_wait_timeout": "0xacf3a0",
"thread_wakeup": "0xad24e0",
"kalloc_data": "0xab2cdc",
"kfree_data": "0xab34a8",
"sock_socket": "0x1006c88",
"sock_connect": "0x1005714",
"sock_send": "0x1006708",
"sock_receive": "0x100669c",
"sock_close": "0x1006de8",
"mach_absolute_time": "0xc0548c",
"absolutetime_to_nanoseconds": "0xc05a34",
"clock_get_calendar_microtime": "0xa996c0",
"kernel_thread_start": "0xafb660",
"sb_evaluate_internal": "0x214dc00",
"hook_cred_label_update_execve": "0x2156004",
"hook_vnode_check_create": "0x213cf48",
"hook_policy_syscall": "0x21443bc",
"gLoadedKextSummaries": "0x24ec160",
"proc_pid": "0xf65e5c"
}
}
}
+15
View File
@@ -0,0 +1,15 @@
{
"connection": {
"host": "127.0.0.1",
"port": 1234
},
"kernel": "win9x",
"agent": {
"path": "/home/oleavr/src/frida-core/src/barebone/agent/target-win9x/i686-unknown-none/release/frida-barebone-agent",
"transport": {
"type": "hostlink",
"qmp": "unix:/tmp/claude-1000/-home-oleavr-src-frida-core/0f3e358c-8f66-4246-b793-deb2cd3727f3/scratchpad/qmp.sock",
"bus": "frida-vserial.0"
}
}
}
+20
View File
@@ -0,0 +1,20 @@
{
"arch": "x86",
"cpu": "pentium4",
"crt-objects-fallback": "false",
"data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
"features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float",
"linker": "rust-lld",
"linker-flavor": "gnu-lld",
"llvm-target": "i686-unknown-none-elf",
"max-atomic-width": 64,
"panic-strategy": "abort",
"plt-by-default": true,
"position-independent-executables": false,
"relocation-model": "static",
"rustc-abi": "softfloat",
"stack-probes": {
"kind": "inline"
},
"target-pointer-width": 32
}
+28
View File
@@ -0,0 +1,28 @@
MODULE_NAME ?= frida-agent
obj-m += $(MODULE_NAME).o
# frida-agent-prelinked.o is not built from source here: it is the Rust agent, the
# GumJS devkit and the C library collapsed into a single relocatable object by the prelink
# step in Makefile. Copy it in through if_changed rather than kbuild's _shipped rule,
# so the .cmd file modpost expects gets written.
$(MODULE_NAME)-y := frida-kmod.o frida-agent-prelinked.o
targets += frida-agent-prelinked.o
quiet_cmd_frida_agent_prelinked = COPY $@
cmd_frida_agent_prelinked = cp $< $@
$(obj)/frida-agent-prelinked.o: $(src)/frida-agent-prelinked.o.bin FORCE
$(call if_changed,frida_agent_prelinked)
# On x86 objtool validates every object going into a module and generates its ORC
# unwind tables. It was not built to read a JavaScript runtime linked against a
# freestanding libc: it gives up on the first Rust switch jump table it cannot
# follow, and the tables would only ever describe frames no kernel stack trace walks
# into. Both the blob and the module object it is linked into have to be excused,
# since objtool runs over each.
OBJECT_FILES_NON_STANDARD_frida-agent-prelinked.o := y
OBJECT_FILES_NON_STANDARD_$(MODULE_NAME).o := y
ccflags-y += $(FRIDA_KMOD_DEFS)
+205
View File
@@ -0,0 +1,205 @@
# Builds frida-agent.ko in two steps, because kbuild insists on owning the final
# link:
#
# 1. Collapse the Rust staticlib plus everything it needs — the GumJS devkit and
# the C library — into one relocatable object, and localize every symbol except
# the entry points frida-kmod.c calls. Localizing matters: Gum and the C library
# define names like memcpy and malloc that must not leak into the kernel's
# namespace.
# 2. Hand that object to kbuild as a shipped object, so vermagic, modinfo and
# CRCs all come from the target kernel's own build system.
#
# See README.md for usage.
# KDIR is the kernel to build against. When it is a source tree that was configured
# elsewhere — which is what Android's modules_prepare tarball gives — point KOUT at
# the prepared tree.
KDIR ?= /lib/modules/$(shell uname -r)/build
PROFILE ?= release
# The kernel's own name for the architecture, and the one Rust knows it by. Both
# follow from the machine this is built for, which defaults to the one building.
ARCH ?= $(shell uname -m \
| sed -e 's/^i[3-6]86$$/x86/' -e 's/^x86_64$$/x86/' -e 's/^aarch64$$/arm64/')
BITS ?= $(shell uname -m \
| sed -e 's/^i[3-6]86$$/32/' -e 's/^x86_64$$/64/' -e 's/^aarch64$$/64/')
ifeq ($(ARCH)-$(BITS),x86-32)
RUST_TARGET ?= i686-unknown-none
AGENT_TARGET_FLAGS ?= --target $(AGENT_DIR)/$(RUST_TARGET).json -Z json-target-spec
SDK_ARCH ?= x86
TOOLPREFIX ?=
AGENT_LD_EMULATION ?= elf_i386
else ifeq ($(ARCH)-$(BITS),x86-64)
RUST_TARGET ?= x86_64-unknown-none
SDK_ARCH ?= x86_64
TOOLPREFIX ?=
AGENT_LD_EMULATION ?= elf_x86_64
else ifeq ($(ARCH)-$(BITS),arm64-64)
RUST_TARGET ?= aarch64-unknown-none
SDK_ARCH ?= arm64
TOOLPREFIX ?= aarch64-none-elf-
else
$(error Unsupported ARCH "$(ARCH)" and BITS "$(BITS)"; this flavour covers \
x86/32, x86/64 and arm64/64)
endif
AGENT_TARGET_FLAGS ?= --target $(RUST_TARGET)
AGENT_DIR := $(CURDIR)/..
AGENT_TARGET_DIR := $(AGENT_DIR)/target-linux
AGENT_LIB := $(AGENT_TARGET_DIR)/$(RUST_TARGET)/$(PROFILE)/libfrida_barebone_agent.a
GUMJS_DEVKIT_DIR ?= $(error Set GUMJS_DEVKIT_DIR to the directory holding libfrida-gumjs.a)
# Any binutils that speak the target's ELF will do; nothing here is compiled with
# them. Building for the host's own architecture, that is the host's binutils.
AGENT_LD ?= $(TOOLPREFIX)ld
AGENT_AR ?= $(TOOLPREFIX)ar
AGENT_NM ?= $(TOOLPREFIX)nm
AGENT_OBJCOPY ?= $(TOOLPREFIX)objcopy
# The ABI the kernel imposes on anything linked into it. The flags only reach crates
# Cargo compiles, and the toolchain ships core, alloc and compiler_builtins prebuilt
# without them, so those three have to be rebuilt from source as well. That needs the
# rust-src component and an unstable flag, hence RUSTC_BOOTSTRAP.
#
# On x86_64 that is endbr64 on every address-taken function, since a kernel with
# CONFIG_X86_KERNEL_IBT faults an indirect call landing anywhere else, and switches
# compiled as comparisons rather than jump tables, which objtool cannot follow into.
# Soft-float, the kernel code model and the absent red zone come with the target
# itself. On arm64
# it is x18, which the kernel reserves for the shadow call stack, so nothing linked in
# may use it as a general-purpose register.
ifeq ($(ARCH)-$(BITS),x86-32)
AGENT_RUSTFLAGS ?=
AGENT_CFLAGS ?= --target=i386-none-elf \
-Xclang -target-feature -Xclang +soft-float -mno-mmx -mno-sse \
-mlong-double-64 -fno-pic --sysroot=$(FRIDA_SDK)
else ifeq ($(ARCH)-$(BITS),x86-64)
AGENT_RUSTFLAGS ?= -Zcf-protection=branch \
-Cllvm-args=--min-jump-table-entries=65536 \
-Crelocation-model=static -Zplt=yes
AGENT_CFLAGS ?= --target=x86_64-none-elf \
-Xclang -target-feature -Xclang +soft-float -mno-mmx -mno-sse -mno-red-zone \
-mlong-double-64 -mcmodel=kernel -fcf-protection=branch -fno-jump-tables \
-fno-pic --sysroot=$(FRIDA_SDK)
else
AGENT_RUSTFLAGS ?= -Zfixed-x18
AGENT_CFLAGS ?= --target=aarch64-none-elf -mabi=aapcs-soft -mgeneral-regs-only \
-ffixed-x18 -fno-pic --sysroot=$(FRIDA_SDK)
endif
AGENT_BUILD_STD ?= -Z build-std=core,alloc,compiler_builtins
# The devkit header reaches for GLib's and capstone's, which a devkit built against
# an SDK leaves in that SDK rather than beside itself. The sysroot's own include
# directory is spelled out because only clang's bare-metal driver, which it selects
# for arm64 but not for x86_64, searches there on its own.
AGENT_CFLAGS += -isystem $(FRIDA_SDK)/include \
-I$(FRIDA_SDK)/include/glib-2.0 \
-I$(FRIDA_SDK)/lib/glib-2.0/include \
-I$(FRIDA_SDK)/include/capstone \
-I$(FRIDA_SDK)/include/json-glib-1.0
# The soft-float SDK, whose libc is picolibc and whose compiler runtime is
# compiler-rt. GCC's own newlib and libgcc are hard-float and cannot be used here.
FRIDA_SDK ?= $(error Set FRIDA_SDK to a none-$(SDK_ARCH)-softfloat SDK prefix)
LIBM = $(FRIDA_SDK)/lib/libm.a
LIBBUILTINS = $(FRIDA_SDK)/lib/libclang_rt.builtins.a
# bindgen reads the devkit header through whatever this compiler reports as its
# include path, so it has to be one that knows where the SDK keeps its libc. It has
# to be clang either way: -mabi=aapcs-soft landed there in 19 and GCC has it at no
# version, and on x86_64 the soft-float ABI is reachable only as an -Xclang feature.
AGENT_CC ?= clang
PRELINKED := frida-agent-prelinked.o.bin
AGENT_LIB_SOFTFLOAT := $(AGENT_TARGET_DIR)/libfrida-barebone-agent-softfloat.a
# On arm64 the toolchain's own compiler_builtins is compiled for the ordinary AAPCS,
# where a double travels in d0: its __muldf3 opens with "fmov x14, d0". Everything
# else there is -mabi=aapcs-soft and passes that same double in x0. Whole-archiving
# the staticlib puts those definitions in front of the SDK's compiler-rt, which has
# the soft-float ones, and every double the C library computes then comes back
# garbage — quietly, since the arithmetic still runs. Drop them and let compiler-rt
# answer instead. x86_64 needs none of this: soft-float is part of the Rust target
# there, so the copy build-std compiles already passes doubles the way the C half
# expects.
ifeq ($(ARCH),arm64)
DROP_HARDFLOAT_BUILTINS = \
$(AGENT_AR) d $@ $$($(AGENT_NM) --print-armap $@ | sed -n 's/^__muldf3 in //p') && \
$(AGENT_AR) s $@
else
DROP_HARDFLOAT_BUILTINS = :
endif
# The agent puts malloc, the C library's locks and its process stubs on the kernel's
# own facilities, so picolibc's versions are duplicate definitions rather than
# fallbacks. Work out which members they live in instead of naming them: the set
# follows from what the agent defines, and picolibc moves things between releases.
LIBC := $(AGENT_TARGET_DIR)/libc-trimmed.a
ENTRY_POINTS := frida_agent_start frida_agent_stop \
gum_interceptor_obtain gum_interceptor_begin_transaction \
gum_interceptor_end_transaction gum_interceptor_replace \
gum_interceptor_revert
KEEP_GLOBAL := $(foreach s,$(ENTRY_POINTS),--keep-global-symbol=$(s))
# Two structures the reparenting path reaches into lost a member upstream, and
# neither change announces itself as anything frida-kmod.c could test for. The
# headers it is compiled against are the authority on both, so ask them.
FRIDA_KMOD_DEFS := \
$(if $(shell grep -l 'list_head[[:space:]]*thread_group;' \
$(KDIR)/include/linux/sched.h),-DFRIDA_HAVE_TASK_THREAD_GROUP) \
$(if $(shell grep -l 'spinlock_t[[:space:]]*lock;' \
$(KDIR)/include/linux/fs_struct.h),-DFRIDA_HAVE_FS_STRUCT_LOCK)
all: modules
# A module that dies in its init path is left wedged under its own name until the
# next boot, so bring-up wants a fresh one per attempt.
MODULE_NAME ?= frida-agent
modules: $(PRELINKED)
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) \
FRIDA_KMOD_DEFS="$(FRIDA_KMOD_DEFS)" MODULE_NAME=$(MODULE_NAME) modules
$(PRELINKED): $(AGENT_LIB_SOFTFLOAT) $(LIBC)
$(AGENT_LD) -r $(if $(AGENT_LD_EMULATION),-m $(AGENT_LD_EMULATION)) -o $@.tmp \
--whole-archive $(AGENT_LIB_SOFTFLOAT) --no-whole-archive \
--start-group \
$(GUMJS_DEVKIT_DIR)/libfrida-gumjs.a \
$(LIBC) \
$(LIBM) \
$(LIBBUILTINS) \
--end-group
$(AGENT_OBJCOPY) $(KEEP_GLOBAL) --strip-debug \
--remove-section=.llvmbc --remove-section=.llvmcmd $@.tmp $@
rm -f $@.tmp
$(AGENT_LIB_SOFTFLOAT): agent
cp $(AGENT_LIB) $@
$(DROP_HARDFLOAT_BUILTINS)
$(LIBC): export LC_ALL = C
$(LIBC): $(AGENT_LIB_SOFTFLOAT)
cp $(FRIDA_SDK)/lib/libc.a $@
$(AGENT_NM) --defined-only $(AGENT_LIB_SOFTFLOAT) \
| awk 'NF == 3 && $$2 ~ /^[TDBR]$$/ { print $$3 }' | sort -u > $@.agent-syms
$(AGENT_NM) --print-armap $@ | sed -n 's/^\([^ ]*\) in \(.*\)$$/\1 \2/p' | sort -u > $@.members
$(AGENT_AR) d $@ $$(join $@.agent-syms $@.members | awk '{ print $$2 }' | sort -u)
$(AGENT_AR) s $@
rm -f $@.agent-syms $@.members
agent:
cd $(AGENT_DIR) && RUSTC_BOOTSTRAP=1 RUSTFLAGS="$(AGENT_RUSTFLAGS)" \
CC_$(subst -,_,$(RUST_TARGET))="$(AGENT_CC) $(AGENT_CFLAGS)" \
cargo build --$(PROFILE) \
$(AGENT_TARGET_FLAGS) --features linux \
--target-dir $(AGENT_TARGET_DIR) $(AGENT_BUILD_STD)
clean:
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) clean
rm -f $(PRELINKED) $(PRELINKED).tmp $(AGENT_LIB_SOFTFLOAT) $(LIBC)
.PHONY: all modules agent clean
+291
View File
@@ -0,0 +1,291 @@
# Linux kernel module
The same agent that the Barebone backend injects into XNU from the outside, but
loaded from the inside as an ordinary kernel module.
## How it differs from the XNU flavour
| | XNU | Linux |
|-------------------|--------------------------------------------------|-------------------------------------------|
| Delivery | remote stub injects a freestanding ELF | `insmod frida-agent.ko` |
| Kernel primitives | addresses patched into `.kernel_addrs` by the host | direct calls into `frida-kmod.c` |
| Bootstrap config | GVariant blob written into guest memory | module parameters |
| Transport | virtio hostlink or vsock | `/dev/frida` character device |
| Page permissions | host rewrites descriptors over the hostlink | `set_memory_*()` |
| Writable alias | shadow copy, committed via physical-memory bridge | `vmap()` of the same pages |
| Symbols | table supplied by the host | kallsyms |
Because the Linux agent performs its own memory work, it never issues the
`RemapWritablePages`, `MemoryProtect` or `PatchCode` host RPCs. The wire protocol
is otherwise identical, so the host side sees the same `(yqv)` frames.
## Building
Four inputs: this repository, a GumJS devkit built for `none-<arch>-softfloat`, the
soft-float SDK it was built against, and the kernel the module is for. `Makefile`
covers x86_64 and arm64, and defaults to the architecture of the machine building.
Building for the running kernel is a good deal shorter than cross-building for a
device, so that walkthrough comes first; the arm64 one below it is the same steps
with the kernel obtained from elsewhere.
### Toolchain
rustup target add x86_64-unknown-none # or aarch64-unknown-none
rustup component add rust-src
sudo dnf install clang binutils kernel-devel-$(uname -r)
clang must be 19 or newer, since that is where `-mabi=aapcs-soft` landed; GCC does
not implement it at any version, nor does it reach x86_64's soft-float ABI. The
binutils are for the prelink, which reads the archive map that only GNU `nm` prints —
the LLVM one does not; cross-building wants the target's, natively the host's own
will do. The host compiler is for Cargo's own build scripts, which run on the machine
doing the building.
The commands below are run from the top of this repository, with `releng` checked
out — `git submodule update --init releng` if the clone did not recurse.
### For the running x86_64 kernel
No prebuilt bundles are published for `none-x86_64-softfloat`, so both inputs are
built locally, against the same SDK:
releng/deps.py build --bundle=sdk --host=none-x86_64-softfloat
mkdir -p ~/sdk-none-x86_64-softfloat
tar -C ~/sdk-none-x86_64-softfloat -xf deps/sdk-none-x86_64-softfloat.tar.xz
gum=subprojects/frida-gum
mkdir -p $gum/build/none-x86_64-softfloat
(cd $gum/build/none-x86_64-softfloat \
&& FRIDA_DEPS=$PWD/../../../../deps ../../configure \
--host=none-x86_64-softfloat \
--enable-gumjs \
--with-devkits=gumjs \
--with-devkit-symbol-scope=original \
&& make)
make -C src/barebone/agent/linux \
FRIDA_SDK=$HOME/sdk-none-x86_64-softfloat \
GUMJS_DEVKIT_DIR=$PWD/$gum/build/none-x86_64-softfloat/bindings/gumjs/devkit
Everything else — `KDIR`, the Rust target, the binutils, the kernel's own name for the
architecture — follows from the running kernel.
### For an arm64 device
Here there are published bundles to start from, and the kernel has to be assembled.
#### The devkit and the SDK
version=17.17.0
base=https://github.com/frida/frida/releases/download/$version
curl -LO $base/frida-gumjs-devkit-$version-none-arm64-softfloat.tar.xz
mkdir -p ~/gumjs-devkit
tar -C ~/gumjs-devkit -xf frida-gumjs-devkit-$version-none-arm64-softfloat.tar.xz
releng/deps.py sync sdk none-arm64-softfloat ~/sdk-none-arm64-softfloat
The SDK carries picolibc and the compiler-rt builtins the devkit expects to be linked
against, and `releng/deps.toml` pins which one. Mixing a devkit with an SDK built for
a different libc leaves undefined symbols at prelink time.
#### The kernel
Nothing here needs the device's own kernel to be built, but the config alone is not
enough either. `uname -r` ends in the GKI commit and the build number:
adb shell uname -r
6.1.145-android14-11-gc1de4747ac59-ab14219743
which is commit `c1de4747ac59` and build `14219743`. Three pieces are keyed to them:
commit=c1de4747ac59
build=14219743
ci=https://ci.android.com/builds/submitted/$build/kernel_aarch64/latest/raw
mkdir -p ~/kernel-prepared ~/kernel-source
curl -sSL $ci/modules_prepare_outdir.tar.gz | tar -xz -C ~/kernel-prepared
curl -sSL $ci/kernel_aarch64_Module.symvers -o ~/kernel-prepared/Module.symvers
curl -sSL https://android.googlesource.com/kernel/common/+archive/$commit.tar.gz \
| tar -xz -C ~/kernel-source
`modules_prepare_outdir.tar.gz` is the configured build tree with `scripts/` already
compiled as x86-64 binaries, so the kbuild half has to run on an x86-64 Linux host —
an arm64 one gets as far as `CC [M]` and then fails to run `fixdep`. `Module.symvers`
carries the CRCs `CONFIG_MODVERSIONS` checks. The top-level `Makefile` in that prepared tree is a stub
that includes the source's, hence both `KDIR` and `KOUT` below.
A different GKI build of the same branch does not substitute for these: vermagic is
compared verbatim, and it names the exact build.
#### Build
make -C src/barebone/agent/linux \
FRIDA_SDK=$HOME/sdk-none-arm64-softfloat \
GUMJS_DEVKIT_DIR=$HOME/gumjs-devkit \
AGENT_LD=aarch64-linux-gnu-ld \
AGENT_AR=aarch64-linux-gnu-ar \
AGENT_NM=aarch64-linux-gnu-nm \
AGENT_OBJCOPY=aarch64-linux-gnu-objcopy \
KDIR=$HOME/kernel-source \
KOUT=$HOME/kernel-prepared \
LLVM=1
`make` builds the Rust staticlib itself, prelinks it against the devkit and the SDK,
then hands the result to kbuild. The `AGENT_*` overrides are only needed because the
defaults name the `aarch64-none-elf-` toolchain the XNU flavour uses; drop them if
that is what is installed.
The prelink half runs anywhere; the kbuild half needs the x86-64 Linux host above.
Cross-building from macOS means running that step in a container holding the kernel
tree.
adb push src/barebone/agent/linux/frida-agent.ko /data/local/tmp/
## Loading
insmod frida-agent.ko
The module's init path returns as soon as the worker thread is running — watch
`dmesg` for `frida: listening on /dev/frida`. `rmmod frida-agent` unwinds in the
reverse order and does not return until the worker is off the module's text.
## Talking to it
The agent exposes `/dev/frida` and waits: one client at a time, `read`/`write` of the
same length-prefixed frames the other transports carry, with `poll` for readiness.
It never dials out, so there is no network namespace to get right and no ordering
requirement between loading the module and attaching a client.
On Android the node is created by devtmpfs without a policy-specific label, so an
SELinux-enforcing system needs the client's domain granted access to it before it can
open the device. A socket-based transport was the other option considered and is a
worse fit: `AF_VSOCK` only reaches a peer when a hypervisor is on the other side, and
having the kernel connect out to loopback needs something already listening before
the module loads.
### Reaching it from a host
Serve the Barebone device from a frida-server running on the target, so the `open()`
happens next to the device node and the host talks to an ordinary frida-server:
FRIDA_BAREBONE_CONFIG=/data/local/tmp/linux-kmod.json \
frida-server --device barebone -l 127.0.0.1:27042
`etc/linux-kmod.json` is that config. From the host, with `adb forward tcp:27042
tcp:27042` in place:
frida -H 127.0.0.1:27042 -p 0
## Injecting into userspace
Loaded on a live system the module doubles as a ptrace-free injector for ordinary
processes — how the Linux local backend reaches its targets when the module is
present, falling back to `ptrace` when it is not. There is no second device node;
the channel is `prctl()` with a magic option and a token in the fifth argument,
prctl(0x46524944, op, arg1, arg2, 0x1d5f9e6b2c7a4038)
and any call whose option or token does not match falls through to the real
prctl, so probing for the module looks like a stock kernel. `op` 32 pings and
returns the magic.
Allocate, free, read, write and spawn — the primitives the injector drives
against a target pid — require `CAP_SYS_ADMIN`. A spawned thread is a real
`CLONE_THREAD` sibling that adopts the target's mm, files, credentials,
namespaces, fs and SysV semaphore undo list, so it is a native member of the
group rather than a kernel thread wearing its address space.
Cloaking is Gum's own registry mirrored into the kernel so it holds against
`/proc` too: the module intercepts the `maps`, `smaps`, task and status readers
and drops cloaked threads, ranges and fds while keeping the counts consistent. A
process cloaks its own resources with the token alone — no capability — so an
injected agent self-cloaks by forwarding Gum's updates over the same channel,
and the loader adds the footprint the agent itself never sees. A cloaked thread
still sees through the cloak when it reads `/proc`, so the agent is not blind to
its own process.
## What the target kernel has to provide
- **x86_64 or arm64.** The register-level pieces — the page size, cache maintenance,
where the syscall ABI leaves prctl()'s arguments, and how a spawned thread's
registers are set up for its return to userspace — have a half for each. The XNU
flavour remains arm64-only.
- **`CONFIG_KPROBES` and `CONFIG_KALLSYMS_ALL`.** A good deal of what the shim
needs is compiled in but not exported — `kallsyms_lookup_name`,
`kallsyms_on_each_symbol` and `set_memory_*` all are, and GKI trims its export
table to the KMI symbol list on top of that. The shim resolves them by name
through a kprobe, which is the standard way back in. Without kallsyms the module
still loads but cannot look up symbols or change page permissions.
- **Unsigned modules.** `CONFIG_MODULE_SIG_FORCE` must be off. Android's
`CONFIG_MODULE_SIG_PROTECT` is fine: it only demands signatures for modules on
the GKI list.
## Kernel ABI constraints
A hardened kernel imposes an ABI on anything linked into it, and the FP constraint
below is why this flavour is built against a soft-float SDK
(`none-arm64-softfloat`, `none-x86_64-softfloat`) rather than an ordinary bare-metal
one. The remaining constraints are per-architecture.
- **FP/SIMD.** JavaScript numbers are doubles, so a hardfloat build would use FP
for as long as the runtime lives, and neither architecture lets a kernel thread
hold it for free — x86 wants every stretch bracketed by `kernel_fpu_begin()`, and
on arm64 kernels before 6.9 there is no correct way to hold it at all:
`kernel_neon_begin()` runs with preemption disabled so we cannot sleep under it,
and the scheduler does not save a kernel thread's FP registers — live values are
lost on preemption and the FP state of whichever user task was interrupted is
corrupted. Building everything soft-float removes the question rather than
answering it: `-mabi=aapcs-soft -mgeneral-regs-only` on arm64, and on x86_64
LLVM's `+soft-float`, which is also what Rust's own `x86_64-unknown-none` turns
on, so the two halves agree on where a double travels. `-mno-sse` alone does not
reach the ABI; and `long double` has to be narrowed to 64 bits with
`-mlong-double-64`, since soft-float has no lowering for x87's 80-bit format and
LLVM crashes rather than calling out to a builtin.
- **Indirect branch tracking (x86_64).** With `CONFIG_X86_KERNEL_IBT` an indirect
call landing on anything but an `endbr64` faults, so everything linked in is built
`-fcf-protection=branch` and the Rust half `-Zcf-protection=branch`.
- **Kernel code model and red zone (x86_64).** Modules are loaded into the top 2GB
of the address space and their relocations are resolved as if by `-mcmodel=kernel`,
and there is no red zone below the stack pointer for an interrupt to step on. Both
come with the Rust target; the C half is told explicitly.
- **objtool (x86_64).** It validates every object entering a module and builds its
ORC unwind tables, and was not written to read a JavaScript runtime linked against
a freestanding libc. `Kbuild` marks the prelinked blob `OBJECT_FILES_NON_STANDARD`.
- **Shadow call stack (arm64).** With `CONFIG_SHADOW_CALL_STACK` the kernel reserves x18, so
the SDK and Gum are built `-ffixed-x18` and the Rust half with `-Zfixed-x18`.
Anything linked in that uses x18 as a scratch register overwrites the kernel's SCS
pointer, and the thread then returns through whatever that address happens to hold.
The Rust flag only reaches crates Cargo compiles, so `core`, `alloc` and
`compiler_builtins` — which the toolchain ships prebuilt, and which between them
account for every x18 write that used to reach the module — are rebuilt from source
via `-Z build-std`. That needs `rustup component add rust-src`.
## The soft-float libc
The libc is picolibc and the compiler runtime is compiler-rt, both from the
soft-float SDK that `FRIDA_SDK` points at. `Makefile` trims a copy of its
`libc.a` before the prelink: the agent implements `malloc`, the C library's locks and
its process stubs over the kernel's own facilities, so picolibc's are duplicate
definitions rather than fallbacks. The set is derived from what the agent defines
rather than listed, since picolibc moves things between releases.
On arm64 those compiler-rt builtins are load-bearing in a way that is easy to miss.
Rust's toolchain carries its own `compiler_builtins`, compiled for the ordinary AAPCS
where a double travels in `d0`, and the prelink whole-archives the Rust staticlib — so
without the filtering step in `Makefile` those definitions answer the C library's calls
to `__muldf3` and friends, which pass the same double in `x0`. Nothing crashes. The
arithmetic simply returns garbage, and the first thing to notice is GLib deciding a
hash table need not grow. x86_64 needs none of that filtering: soft-float is part of
the Rust target there, so the copy `-Z build-std` compiles already agrees with the C
half.
## What a script may look like
The runtime compiles scripts on a kernel thread's stack, and QuickJS parses
expressions by recursion, so the ceiling is nesting rather than length: roughly
fourteen levels of parentheses or fifteen of object literals on arm64 Linux, whose
kernel stack is 16 KiB — as is x86_64's. A script that exceeds it fails to compile rather than taking
anything down, though the message is not always `stack overflow` — an over-deep
`if`/`else` chain reports `expecting ';'`. Long scripts are fine; deep ones are not.
File diff suppressed because it is too large. Load diff
+50
View File
@@ -0,0 +1,50 @@
use core::ffi::c_void;
use crate::bindings::{gpointer, gsize, gum_try_mprotect, GumPageProtection};
use crate::{kernel, libc};
const GUM_PAGE_READ: GumPageProtection = 1;
const GUM_PAGE_EXECUTE: GumPageProtection = 4;
// libffi (FFI_TRAMP_EMBEDDER) asks us to back a trampoline table: a copy of its
// static trampoline `text` made executable, followed by a writable parameter
// table `map_size` bytes later (the trampolines reach their parameters via that
// fixed offset). The two halves are whole pages, so flipping the first to RX
// leaves the parameter table writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ffi_tramp_embedder_map(
text: *const c_void,
map_size: usize,
code_table: *mut *mut c_void,
parm_table: *mut *mut c_void,
) -> i32 {
unsafe {
let base = kernel::alloc_code(map_size * 2);
if base.is_null() {
return 0;
}
core::ptr::copy_nonoverlapping(text as *const u8, base, map_size);
libc::__clear_cache(base, base.add(map_size));
let made_executable =
gum_try_mprotect(base as gpointer, map_size as gsize, GUM_PAGE_READ | GUM_PAGE_EXECUTE);
if made_executable == 0 {
kernel::free_code(base, map_size * 2);
return 0;
}
*code_table = base as *mut c_void;
*parm_table = base.add(map_size) as *mut c_void;
1
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ffi_tramp_embedder_unmap(
code_table: *mut c_void,
_parm_table: *mut c_void,
map_size: usize,
) {
kernel::free_code(code_table as *mut u8, map_size * 2);
}
+19 -22
View File
@@ -1,7 +1,7 @@
use core::ptr;
use crate::bindings::{g_wait_is_set, gint64, gpointer};
use crate::xnu;
use crate::kernel;
const G_WAIT_INFINITE: gint64 = -1;
@@ -9,38 +9,35 @@ pub static mut WAKEUP_TOKEN: u64 = 0;
#[unsafe(no_mangle)]
pub extern "C" fn g_get_monotonic_time() -> gint64 {
let abstime = xnu::mach_absolute_time();
let time = xnu::absolutetime_to_nanoseconds(abstime);
(time / 1000) as gint64
kernel::monotonic_micros()
}
#[unsafe(no_mangle)]
pub extern "C" fn g_wait_sleep(token: gpointer, timeout_us: gint64) {
let wait_event = ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8;
let wait_result = if timeout_us == G_WAIT_INFINITE {
xnu::assert_wait(wait_event, xnu::THREAD_INTERRUPTIBLE)
let timeout = if timeout_us == G_WAIT_INFINITE {
None
} else {
xnu::assert_wait_timeout(
wait_event,
xnu::THREAD_INTERRUPTIBLE,
(timeout_us * 1000) as u32,
1, // TODO
)
Some(timeout_us as u64)
};
if wait_result != xnu::THREAD_WAITING {
panic!("assert_wait_timeout failed: {}", wait_result);
}
if unsafe { g_wait_is_set(token) != 0 } {
xnu::thread_wakeup(wait_event);
return;
}
xnu::thread_block(None);
kernel::wait(wait_event, timeout, &mut || unsafe {
g_wait_is_set(token) != 0
});
}
#[unsafe(no_mangle)]
pub extern "C" fn g_wait_wake(_token: gpointer) {
xnu::thread_wakeup(ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8);
kernel::wake(ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8);
}
#[unsafe(no_mangle)]
pub extern "C" fn g_io_channel_unix_new(_fd: i32) -> gpointer {
ptr::null_mut()
}
#[unsafe(no_mangle)]
pub extern "C" fn g_spawn_async_with_pipes() -> i32 {
0
}
+104 -72
View File
@@ -8,34 +8,39 @@ use crate::bindings::GThreadFunc;
use crate::bindings::{gpointer, gulong};
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use core::arch::asm;
use core::ffi::c_void;
use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
use core::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering};
// Our implementation structures that overlay the opaque GLib structures
#[repr(C, align(8))]
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
struct MutexImpl {
lock: AtomicU32,
_padding: [u8; core::mem::size_of::<GMutex>() - core::mem::size_of::<AtomicU32>()],
}
#[repr(C, align(8))]
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
struct RecMutexImpl {
owner: AtomicU64, // Thread ID of the owner (uses the 'p' field)
owner: AtomicUsize, // Thread ID of the owner (uses the 'p' field)
count: AtomicU32, // Recursion count (uses i[0])
_unused: u32, // Unused (uses i[1])
}
#[repr(C, align(8))]
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
struct RWLockImpl {
state: AtomicU32, // Lock state (readers count + writer bit)
_padding: [u8; core::mem::size_of::<GRWLock>() - core::mem::size_of::<AtomicU32>()],
}
#[repr(C, align(8))]
// A generation counter rather than a flag: a flag stays set once signalled, so every
// later wait returns at once and the caller's loop spins instead of blocking.
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
struct CondImpl {
signal: AtomicU32, // Signal state
generation: AtomicU32,
_padding: [u8; core::mem::size_of::<GCond>() - core::mem::size_of::<AtomicU32>()],
}
@@ -144,10 +149,9 @@ pub extern "C" fn g_rec_mutex_clear(_rec_mutex: *mut GRecMutex) {
pub extern "C" fn g_rec_mutex_lock(rec_mutex: *mut GRecMutex) {
unsafe {
let impl_ = rec_mutex_impl(rec_mutex);
let current_thread = get_current_thread_id();
loop {
if rec_mutex_try_acquire(impl_, current_thread) {
if rec_mutex_try_acquire(impl_) {
break;
}
rec_mutex_wait(impl_);
@@ -159,9 +163,8 @@ pub extern "C" fn g_rec_mutex_lock(rec_mutex: *mut GRecMutex) {
pub extern "C" fn g_rec_mutex_trylock(rec_mutex: *mut GRecMutex) -> u32 {
unsafe {
let impl_ = rec_mutex_impl(rec_mutex);
let current_thread = get_current_thread_id();
if rec_mutex_try_acquire(impl_, current_thread) {
if rec_mutex_try_acquire(impl_) {
1
} else {
0
@@ -183,7 +186,7 @@ pub extern "C" fn g_rec_mutex_unlock(rec_mutex: *mut GRecMutex) {
.is_ok()
{
impl_.owner.store(0, Ordering::Relaxed);
lock_notify_all();
wake_waiters(&impl_.count);
break;
}
} else {
@@ -198,7 +201,10 @@ pub extern "C" fn g_rec_mutex_unlock(rec_mutex: *mut GRecMutex) {
}
}
unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl, current_thread: u64) -> bool {
// Asking the kernel who we are costs more than the uncontended acquisition itself, and
// every allocation takes this path, so the identity is only fetched once the lock turns
// out to be held.
unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl) -> bool {
let current_count = impl_.count.load(Ordering::Relaxed);
if current_count == 0 {
@@ -206,12 +212,12 @@ unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl, current_thread: u64) -
.compare_exchange_weak(0, 1, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
impl_.owner.store(current_thread, Ordering::Relaxed);
impl_.owner.store(crate::kernel::current_thread_id() as usize, Ordering::Relaxed);
return true;
}
} else {
let current_owner = impl_.owner.load(Ordering::Relaxed);
if current_owner == current_thread {
if current_owner == crate::kernel::current_thread_id() as usize {
if impl_.count
.compare_exchange_weak(
current_count,
@@ -230,7 +236,7 @@ unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl, current_thread: u64) -
unsafe fn rec_mutex_wait(impl_: &mut RecMutexImpl) {
while impl_.count.load(Ordering::Relaxed) != 0 {
unsafe { lock_wait_primitive() };
wait_on(&impl_.count, &mut || impl_.count.load(Ordering::Relaxed) == 0);
}
}
@@ -282,7 +288,7 @@ pub extern "C" fn g_rw_lock_writer_unlock(rw_lock: *mut GRWLock) {
unsafe {
let impl_ = rw_lock_impl(rw_lock);
impl_.state.store(0, Ordering::Release);
lock_notify_all();
wake_waiters(&impl_.state);
}
}
@@ -329,7 +335,7 @@ pub extern "C" fn g_rw_lock_reader_unlock(rw_lock: *mut GRWLock) {
.is_ok()
{
if current - 1 == 0 {
lock_notify_all();
wake_waiters(&impl_.state);
}
break;
}
@@ -355,13 +361,15 @@ unsafe fn rw_lock_reader_try_acquire(impl_: &mut RWLockImpl) -> bool {
unsafe fn rw_lock_wait_for_writers(impl_: &mut RWLockImpl) {
while impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT != 0 {
unsafe { lock_wait_primitive() };
wait_on(&impl_.state, &mut || {
impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT == 0
});
}
}
unsafe fn rw_lock_wait_for_all(impl_: &mut RWLockImpl) {
while impl_.state.load(Ordering::Relaxed) != 0 {
unsafe { lock_wait_primitive() };
wait_on(&impl_.state, &mut || impl_.state.load(Ordering::Relaxed) == 0);
}
}
@@ -369,7 +377,7 @@ unsafe fn rw_lock_wait_for_all(impl_: &mut RWLockImpl) {
pub extern "C" fn g_cond_init(cond: *mut GCond) {
unsafe {
let impl_ = cond_impl(cond);
impl_.signal.store(0, Ordering::Relaxed);
impl_.generation.store(0, Ordering::Relaxed);
}
}
@@ -379,11 +387,17 @@ pub extern "C" fn g_cond_clear(_cond: *mut GCond) {}
#[unsafe(no_mangle)]
pub extern "C" fn g_cond_wait(cond: *mut GCond, mutex: *mut GMutex) {
unsafe {
// Read the generation under the mutex, so a signal that lands after the
// unlock below is still observed as a change.
let seen = cond_impl(cond).generation.load(Ordering::Acquire);
g_mutex_unlock(mutex);
let impl_ = cond_impl(cond);
while impl_.signal.load(Ordering::Acquire) == 0 {
lock_wait_primitive();
while impl_.generation.load(Ordering::Acquire) == seen {
wait_on(&impl_.generation, &mut || {
impl_.generation.load(Ordering::Acquire) != seen
});
}
g_mutex_lock(mutex);
@@ -394,8 +408,8 @@ pub extern "C" fn g_cond_wait(cond: *mut GCond, mutex: *mut GMutex) {
pub extern "C" fn g_cond_signal(cond: *mut GCond) {
unsafe {
let impl_ = cond_impl(cond);
impl_.signal.store(1, Ordering::Release);
lock_notify_all();
impl_.generation.fetch_add(1, Ordering::Release);
wake_waiters(&impl_.generation);
}
}
@@ -403,22 +417,37 @@ pub extern "C" fn g_cond_signal(cond: *mut GCond) {
pub extern "C" fn g_cond_broadcast(cond: *mut GCond) {
unsafe {
let impl_ = cond_impl(cond);
impl_.signal.store(1, Ordering::Release);
lock_notify_all();
impl_.generation.fetch_add(1, Ordering::Release);
wake_waiters(&impl_.generation);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_cond_wait_until(cond: *mut GCond, mutex: *mut GMutex, _end_time: i64) -> u32 {
// TODO: Implement proper timeout handling
g_cond_wait(cond, mutex);
1
pub extern "C" fn g_cond_wait_until(cond: *mut GCond, mutex: *mut GMutex, end_time: i64) -> u32 {
unsafe {
let seen = cond_impl(cond).generation.load(Ordering::Acquire);
g_mutex_unlock(mutex);
let impl_ = cond_impl(cond);
let mut signalled = impl_.generation.load(Ordering::Acquire) != seen;
while !signalled && crate::kernel::monotonic_micros() < end_time {
wait_on(&impl_.generation, &mut || {
impl_.generation.load(Ordering::Acquire) != seen
});
signalled = impl_.generation.load(Ordering::Acquire) != seen;
}
g_mutex_lock(mutex);
if signalled { 1 } else { 0 }
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_private_get(key: *mut GPrivate) -> *mut c_void {
unsafe {
let thread_id = get_current_thread_id();
let thread_id = crate::kernel::current_thread_id();
lock_acquire(&TLS_LOCK);
ensure_tls_storage();
@@ -438,7 +467,7 @@ pub extern "C" fn g_private_get(key: *mut GPrivate) -> *mut c_void {
#[unsafe(no_mangle)]
pub extern "C" fn g_private_set(key: *mut GPrivate, value: *mut c_void) {
unsafe {
let thread_id = get_current_thread_id();
let thread_id = crate::kernel::current_thread_id();
lock_acquire(&TLS_LOCK);
ensure_tls_storage();
@@ -468,7 +497,9 @@ pub extern "C" fn _g_system_thread_create(
data: gpointer,
) -> *mut GSystemThread {
unsafe {
let system_thread = crate::xnu::kalloc(core::mem::size_of::<SystemThreadImpl>()) as *mut SystemThreadImpl;
crate::kernel::log("frida: glib is creating a thread\n\0");
let system_thread = crate::kernel::alloc(core::mem::size_of::<SystemThreadImpl>()) as *mut SystemThreadImpl;
g_mutex_init(&mut (*system_thread).mutex);
g_cond_init(&mut (*system_thread).cond);
@@ -478,12 +509,12 @@ pub extern "C" fn _g_system_thread_create(
(*system_thread).finished.store(0, Ordering::Relaxed);
(*system_thread).detached.store(0, Ordering::Relaxed);
let wrapper_data = crate::xnu::kalloc(core::mem::size_of::<ThreadWrapperData>()) as *mut ThreadWrapperData;
let wrapper_data = crate::kernel::alloc(core::mem::size_of::<ThreadWrapperData>()) as *mut ThreadWrapperData;
(*wrapper_data).func = func;
(*wrapper_data).data = data;
(*wrapper_data).system_thread = system_thread;
let _xnu_result = crate::xnu::kernel_thread_start(thread_wrapper, wrapper_data as *mut c_void);
let _xnu_result = crate::kernel::spawn_thread(thread_wrapper, wrapper_data as *mut c_void);
system_thread as *mut GSystemThread
}
@@ -496,8 +527,9 @@ extern "C" fn thread_wrapper(parameter: *mut c_void, _wait_result: i32) {
let data = (*wrapper_data).data;
let system_thread = (*wrapper_data).system_thread;
let current_thread_id = get_current_thread_id();
(*system_thread).thread_id.store(current_thread_id, Ordering::Relaxed);
let current_thread_id = crate::kernel::current_thread_id();
(*system_thread).thread_id.store(current_thread_id, Ordering::Release);
wake_waiters(&(*system_thread).thread_id);
func.unwrap()(data);
@@ -537,12 +569,10 @@ pub extern "C" fn _g_system_thread_wait(thread: *mut GSystemThread) {
unsafe {
let system_thread = thread as *mut SystemThreadImpl;
loop {
let thread_id = (*system_thread).thread_id.load(Ordering::Acquire);
if thread_id != 0 {
break;
}
core::hint::spin_loop();
while (*system_thread).thread_id.load(Ordering::Acquire) == 0 {
wait_on(&(*system_thread).thread_id, &mut || {
(*system_thread).thread_id.load(Ordering::Acquire) != 0
});
}
g_mutex_lock(&mut (*system_thread).mutex);
@@ -556,19 +586,16 @@ pub extern "C" fn _g_system_thread_wait(thread: *mut GSystemThread) {
}
unsafe fn cleanup_system_thread(system_thread: *mut SystemThreadImpl) {
crate::xnu::free(system_thread as *mut u8, core::mem::size_of::<SystemThreadImpl>());
crate::kernel::free(system_thread as *mut u8, core::mem::size_of::<SystemThreadImpl>());
}
unsafe fn cleanup_wrapper_data(wrapper_data: *mut ThreadWrapperData) {
crate::xnu::free(wrapper_data as *mut u8, core::mem::size_of::<ThreadWrapperData>());
crate::kernel::free(wrapper_data as *mut u8, core::mem::size_of::<ThreadWrapperData>());
}
unsafe fn lock_acquire(lock: &AtomicU32) {
loop {
if unsafe { lock_try_acquire(lock) } {
break;
}
unsafe { lock_wait(lock) };
while !unsafe { lock_try_acquire(lock) } {
wait_on(lock, &mut || lock.load(Ordering::Relaxed) == 0);
}
}
@@ -579,31 +606,36 @@ unsafe fn lock_try_acquire(lock: &AtomicU32) -> bool {
unsafe fn lock_release(lock: &AtomicU32) {
lock.store(0, Ordering::Release);
unsafe { lock_notify_all() };
wake_waiters(lock);
}
unsafe fn lock_wait(lock: &AtomicU32) {
while lock.load(Ordering::Relaxed) != 0 {
unsafe { lock_wait_primitive() };
}
// Sleeps until `ready` holds or the slice expires, whichever comes first.
//
// These used to spin on wfe/sev. A kernel thread that spins never reaches the
// scheduler, so a lost wakeup — or a genuine deadlock, which is how we found this —
// starves the CPU it is on until the hardware watchdog takes the machine down. The
// bounded sleep turns both into a stall that can be observed instead.
//
// The address of the word being waited on names the wait channel: XNU pairs
// assert_wait()/thread_wakeup() on it directly, while the Linux shim has a single
// channel and treats it as a hint. Every caller re-checks its own condition in a
// loop, so a spurious wakeup costs nothing either way.
fn wait_on<T>(word: &T, ready: &mut dyn FnMut() -> bool) {
crate::kernel::wait(token_of(word), Some(WAIT_SLICE_US), ready);
// The wait above returns without sleeping whenever `ready` already holds, so on
// its own it is not enough to keep a contended loop off the CPU: every caller
// here loops, and a loop in a kernel thread that never reaches the scheduler
// takes the machine down rather than merely running hot. Yield unconditionally.
crate::kernel::yield_now();
}
unsafe fn lock_wait_primitive() {
unsafe {
asm!("wfe", options(nomem, nostack));
}
fn wake_waiters<T>(word: &T) {
crate::kernel::wake(token_of(word));
}
unsafe fn lock_notify_all() {
unsafe {
asm!("sev", options(nomem, nostack));
}
fn token_of<T>(word: &T) -> *const u8 {
word as *const T as *const u8
}
pub unsafe fn get_current_thread_id() -> u64 {
let thread_ptr: u64;
unsafe {
asm!("mrs {}, tpidr_el1", out(reg) thread_ptr, options(nomem, nostack));
}
thread_ptr
}
const WAIT_SLICE_US: u64 = 1000;
+89 -242
View File
@@ -1,30 +1,42 @@
// Gum's platform backend, minus the parts that depend on which kernel we are
// running inside: memory protection, symbols and the module registry live in
// gum_xnu.rs and gum_linux.rs respectively.
use crate::{
bindings::{
_GInterfaceInfo, _GTypeInfo, _GumPageProtection_GUM_PAGE_EXECUTE,
_GumRwxSupport_GUM_RWX_NONE, GArray, GObject, GObjectClass, GPrivate, GType,
GumDebugSymbolDetails, GumExportDetails, GumExportType_GUM_EXPORT_FUNCTION,
GumFoundExportFunc, GumMemoryRange, GumModule, GumModuleInterface, GumModuleRegistry,
GumPageProtection, GumRwxSupport, GumThreadId, GumTlsKey, g_array_append_vals, g_array_new,
g_free, g_object_get_type, g_object_new, g_object_unref, g_once_init_enter,
g_once_init_leave, g_strdup, g_type_add_interface_static, g_type_class_peek_parent,
g_type_register_static, gboolean, gchar, gconstpointer, gpointer, gsize, guint,
gum_barebone_register_module,
gum_barebone_try_remap_writable_pages as _gum_barebone_try_remap_writable_pages,
gum_module_get_type, gum_mprotect, gum_query_page_size,
_GInterfaceInfo, _GTypeInfo, GObject, GObjectClass, GPrivate, GType, GumExportDetails,
GumExportType_GUM_EXPORT_FUNCTION, GumFoundExportFunc, GumMemoryRange, GumModule,
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, gpointer, gsize, guint,
},
gthread, libc, xnu,
gthread, kernel, libc,
};
use alloc::boxed::Box;
use alloc::ffi::CString;
use alloc::format;
use alloc::vec::Vec;
use core::ptr;
use core::{ffi::CStr, ptr::read_volatile};
use core::sync::atomic::{AtomicU32, Ordering};
#[cfg(feature = "linux")]
use crate::gum_linux::enumerate_exports_in_range;
#[cfg(feature = "win9x")]
use crate::gum_win9x::enumerate_exports_in_range;
#[cfg(feature = "xnu")]
use crate::gum_xnu::enumerate_exports_in_range;
#[unsafe(no_mangle)]
pub extern "C" fn gum_process_get_current_thread_id() -> GumThreadId {
unsafe { gthread::get_current_thread_id() }
kernel::current_thread_id() as GumThreadId
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_page_size() -> guint {
4096
}
#[cfg(target_arch = "aarch64")]
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_page_size() -> guint {
unsafe {
@@ -42,83 +54,6 @@ pub extern "C" fn gum_barebone_query_page_size() -> guint {
}
}
#[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 {
unsafe {
let page_size = gum_query_page_size() as usize;
let mut physical_addrs = alloc::vec::Vec::with_capacity(n_pages as usize);
let mut current_page = first_page as u64;
for _ in 0..n_pages {
let phys_addr = xnu::ml_vtophys(current_page);
physical_addrs.push(phys_addr as gpointer);
current_page += page_size as u64;
}
_gum_barebone_try_remap_writable_pages(
physical_addrs.as_ptr() as *mut *const core::ffi::c_void,
physical_addrs.len() as guint,
)
}
}
#[unsafe(no_mangle)]
#[inline(never)]
pub extern "C" fn gum_barebone_try_remap_writable_pages(
addrs: *const gpointer,
n_addrs: guint,
) -> gpointer {
unsafe {
read_volatile(&n_addrs);
read_volatile(addrs)
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_try_mprotect(
_address: gpointer,
_size: gsize,
_prot: GumPageProtection,
) -> gboolean {
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_allocate(
_address: gpointer,
size: gsize,
_alignment: gsize,
prot: GumPageProtection,
) -> gpointer {
let ptr = crate::xnu::kalloc(size as usize);
unsafe {
core::ptr::write_bytes(ptr, 0, size as usize);
if (prot & _GumPageProtection_GUM_PAGE_EXECUTE) != 0 {
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 {
crate::xnu::free(address as *mut u8, size as usize);
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_clear_cache(address: gpointer, size: gsize) {
unsafe {
@@ -150,40 +85,6 @@ pub extern "C" fn gum_tls_key_set_value(key: GumTlsKey, value: gpointer) {
gthread::g_private_set(key as *mut GPrivate, value)
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
let kernel_base = crate::xnu::get_kernel_base();
unsafe {
let module_infos = core::ptr::addr_of!(crate::MODULE_INFO);
let module_infos = &*module_infos;
let mut i = 0;
for module_info in module_infos.iter() {
let module_base = kernel_base + module_info.offset as u64;
let module_path = if i == 0 {
"/System/Library/Kernels/kernel"
} else {
&format!(
"/System/Library/Extensions/{}.kext/{}",
module_info.name, module_info.name
)
};
let module_range = GumMemoryRange {
base_address: module_base,
size: module_info.size as u64,
};
let module = gum_native_module_new(&module_path, &module_info.version, &module_range);
gum_barebone_register_module(registry, module);
g_object_unref(module as gpointer);
i += 1;
}
}
}
#[repr(C)]
pub struct GumNativeModule {
parent: GObject,
@@ -231,7 +132,7 @@ fn gum_native_module_get_type() -> GType {
interface_data: core::ptr::null_mut(),
};
g_type_add_interface_static(new_type, gum_module_get_type(), &interface_info);
g_type_add_interface_static(new_type, crate::bindings::gum_module_get_type(), &interface_info);
g_once_init_leave(
core::ptr::addr_of_mut!(GUM_NATIVE_MODULE_TYPE) as *mut ::core::ffi::c_void,
@@ -274,7 +175,11 @@ unsafe extern "C" fn gum_native_module_finalize(object: *mut GObject) {
}
}
fn gum_native_module_new(path: &str, version: &str, range: &GumMemoryRange) -> *mut GumModule {
pub(crate) fn gum_native_module_new(
path: &str,
version: &str,
range: &GumMemoryRange,
) -> *mut GumModule {
unsafe {
let path_cstr = CString::new(path).unwrap();
let version_cstr = CString::new(version).unwrap();
@@ -325,133 +230,75 @@ unsafe extern "C" fn gum_native_module_enumerate_exports(
) {
unsafe {
let module = self_ as *mut GumNativeModule;
let module_range = &(*module).range;
let symbol_table = core::ptr::addr_of!(crate::SYMBOL_TABLE);
let symbol_table = &*symbol_table;
const N_EXT: u8 = 0x01; // External symbol flag
const N_TYPE: u8 = 0x0e; // Type mask
const N_SECT: u8 = 0x0e; // Defined in section
let start_address = module_range.base_address;
let end_address = module_range.base_address + module_range.size;
for symbol_ref in symbol_table.iter_symbols_in_range(start_address, end_address) {
let is_external = (symbol_ref.symbol_type() & N_EXT) != 0;
let is_defined = (symbol_ref.symbol_type() & N_TYPE) == N_SECT;
if !is_external || !is_defined {
continue;
}
let export_type = GumExportType_GUM_EXPORT_FUNCTION;
let range = (*module).range;
let mut on_export = |name: *const gchar, address: u64| {
let export_details = GumExportDetails {
type_: export_type,
name: g_strdup(symbol_ref.name_ptr()),
address: symbol_ref.address(),
type_: GumExportType_GUM_EXPORT_FUNCTION,
name: g_strdup(name),
address,
size: -1,
};
let should_continue =
func.unwrap()(&export_details as *const GumExportDetails, user_data);
if should_continue == 0 {
break;
}
}
func.unwrap()(&export_details as *const GumExportDetails, user_data) != 0
};
enumerate_exports_in_range(
range.base_address,
range.base_address + range.size as u64,
&mut on_export,
);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_symbol_details_from_address(
address: gpointer,
details: *mut GumDebugSymbolDetails,
) -> gboolean {
/// What the backends implement for `gum_native_module_enumerate_exports`: call
/// `callback` for every exported function in `[start, end)`, stopping early when
/// it returns false.
pub(crate) type FoundExportCallback<'a> = dyn FnMut(*const gchar, 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
// we treat it differently. We distinguish the two by tracking the executable ranges we hand out.
static SLAB_LOCK: AtomicU32 = AtomicU32::new(0);
static mut SLABS: Vec<(u64, u64)> = Vec::new();
fn slab_lock() {
while SLAB_LOCK
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
kernel::yield_now();
}
}
fn slab_unlock() {
SLAB_LOCK.store(0, Ordering::Release);
}
pub(crate) fn register_slab(start: u64, size: usize) {
slab_lock();
unsafe {
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
if let Some(symbol) = table.find_symbol_by_address(address as u64) {
let name_bytes = symbol.name().as_bytes();
let copy_len = core::cmp::min(name_bytes.len(), 2048);
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;
return 1;
}
0
(*core::ptr::addr_of_mut!(SLABS)).push((start, start + size as u64));
}
slab_unlock();
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_symbol_name_from_address(address: gpointer) -> *mut gchar {
pub(crate) fn unregister_slab(start: u64) {
slab_lock();
unsafe {
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
let name_ptr = table.find_symbol_name_ptr_by_address(address as u64);
g_strdup(name_ptr as *const gchar)
(*core::ptr::addr_of_mut!(SLABS)).retain(|&(begin, _)| begin != start);
}
slab_unlock();
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_function(name: *const gchar) -> gpointer {
unsafe {
let target_name = CStr::from_ptr(name).to_string_lossy();
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
if let Some(symbol) = table.find_symbol_by_name(&target_name) {
return symbol.address() as gpointer;
}
core::ptr::null_mut()
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_functions_named(name: *const gchar) -> *mut GArray {
unsafe {
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
let target_name = CStr::from_ptr(name).to_string_lossy();
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
let symbols = table.find_symbols_by_name(&target_name);
for symbol in symbols {
let addr = symbol.address() as gpointer;
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
}
array
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_functions_matching(pattern: *const gchar) -> *mut GArray {
unsafe {
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
let glob_pattern = CStr::from_ptr(pattern).to_string_lossy();
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
let symbols = table.find_symbols_matching_glob(&glob_pattern);
for symbol in symbols {
let addr = symbol.address() as gpointer;
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
}
array
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
0
pub(crate) fn is_agent_slab(address: u64) -> bool {
slab_lock();
let found = unsafe {
(*core::ptr::addr_of!(SLABS))
.iter()
.any(|&(begin, end)| address >= begin && address < end)
};
slab_unlock();
found
}
+226
View File
@@ -0,0 +1,226 @@
// 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_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) {
unsafe {
let kernel_base = kernel::get_kernel_base();
if kernel_base != 0 {
register_module(registry, "/boot/vmlinux", "", kernel_base, kernel::get_kernel_size());
}
for m in kernel::enumerate_modules() {
let path = format!("/lib/modules/{}.ko", m.name);
register_module(registry, &path, &m.version, m.base, m.size);
}
}
}
unsafe fn register_module(
registry: *mut GumModuleRegistry,
path: &str,
version: &str,
base: u64,
size: u64,
) {
unsafe {
let range = GumMemoryRange {
base_address: base,
size: size as gsize,
};
let module = gum::gum_native_module_new(path, version, &range);
gum_barebone_register_module(registry, module);
g_object_unref(module as gpointer);
}
}
pub(crate) unsafe fn enumerate_exports_in_range(
start_address: u64,
end_address: u64,
callback: &mut FoundExportCallback<'_>,
) {
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
}
+108
View File
@@ -0,0 +1,108 @@
// Win9x half of Gum's platform backend. The processors this kernel runs on have
// no execute permission to withhold and no supervisor write protection worth
// speaking of, so code is writable where it lies and none of the aliasing the
// other backends need arises.
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,
},
gum::{self, FoundExportCallback},
kernel,
};
use core::ptr;
#[unsafe(no_mangle)]
pub extern "C" fn gum_query_rwx_support() -> GumRwxSupport {
_GumRwxSupport_GUM_RWX_FULL
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_can_remap_writable() -> gboolean {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_try_remap_writable_pages(
first_page: gpointer,
_n_pages: guint,
) -> gpointer {
first_page
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_dispose_writable_pages(_writable: gpointer, _n_pages: guint) {}
#[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);
if ptr.is_null() {
return ptr::null_mut();
}
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 {
if gum::is_agent_slab(address as u64) {
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) {
let kernel_base = kernel::get_kernel_base();
if kernel_base == 0 {
return;
}
unsafe {
let range = GumMemoryRange {
base_address: kernel_base,
size: 0,
};
let module = gum::gum_native_module_new("/VMM32.VXD", "", &range);
gum_barebone_register_module(registry, module);
g_object_unref(module as gpointer);
}
}
// Nothing here has a symbol table to walk: the loader resolves VxD services
// through a dispatch table rather than by name.
pub(crate) unsafe fn enumerate_exports_in_range(
_start: u64,
_end: u64,
_callback: &mut FoundExportCallback,
) {
}
+379
View File
@@ -0,0 +1,379 @@
// XNU half of Gum's platform backend. Memory permissions and kernel-text writes
// go out over the hostlink, because the guest CPU cannot perform them itself:
// the host owns the page tables and the physical-memory bridge.
use crate::{
FridaCommand,
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_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,
},
gum::{self, FoundExportCallback},
host_rpc, kernel, libc,
};
use alloc::format;
use alloc::vec::Vec;
use core::ffi::CStr;
use core::mem::size_of;
use core::ptr;
const SHADOW_MAGIC: u64 = 0x4644_4f48_5341_4853;
const SHADOW_HEADER: usize = 24;
const SHADOW_MIN_ADDRESS: u64 = 0xffff_f000_0000_0000;
#[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 {
if gum::is_agent_slab(first_page as u64) {
return remap_agent_pages(first_page, n_pages);
}
shadow_kernel_pages(first_page, n_pages)
}
fn remap_agent_pages(first_page: gpointer, n_pages: guint) -> gpointer {
unsafe {
let page_size = gum_query_page_size() as usize;
let mut virtual_addrs = Vec::with_capacity(n_pages as usize);
let mut current_page = first_page as u64;
for _ in 0..n_pages {
virtual_addrs.push(current_page as gpointer);
current_page += page_size as u64;
}
_gum_barebone_try_remap_writable_pages(
virtual_addrs.as_ptr() as *mut *const core::ffi::c_void,
virtual_addrs.len() as guint,
)
}
}
fn shadow_kernel_pages(first_page: gpointer, n_pages: guint) -> gpointer {
unsafe {
let total = n_pages as usize * gum_query_page_size() as usize;
let buffer = kernel::alloc(SHADOW_HEADER + total);
*(buffer as *mut u64) = SHADOW_MAGIC;
*(buffer.add(8) as *mut u64) = first_page as u64;
*(buffer.add(16) as *mut u32) = n_pages;
let body = buffer.add(SHADOW_HEADER);
core::ptr::copy_nonoverlapping(first_page as *const u8, body, total);
body as gpointer
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages: guint) {
if (writable as u64) < SHADOW_MIN_ADDRESS {
return;
}
unsafe {
let buffer = (writable as *mut u8).sub(SHADOW_HEADER);
if *(buffer as *const u64) != SHADOW_MAGIC {
return;
}
let first_page = *(buffer.add(8) as *const u64);
let n_pages = *(buffer.add(16) as *const u32);
let total = n_pages as usize * gum_query_page_size() as usize;
commit_kernel_patch(first_page, writable as *const u8, total);
libc::__clear_cache(first_page as *const u8, (first_page + total as u64) as *const u8);
kernel::free(buffer, SHADOW_HEADER + total);
}
}
unsafe fn commit_kernel_patch(address: u64, data: *const u8, len: usize) {
unsafe {
let element_type = g_variant_type_new(c"y".as_ptr());
let bytes = g_variant_new_fixed_array(element_type, data as gconstpointer, len as gsize, 1);
g_variant_type_free(element_type);
let payload = g_variant_new(c"(t@ay)".as_ptr(), address, bytes);
let reply = host_rpc(FridaCommand::PatchCode, payload);
g_variant_unref(reply);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_try_remap_writable_pages(
addrs: *const gpointer,
n_addrs: guint,
) -> gpointer {
if !crate::transport_is_up() {
return ptr::null_mut();
}
unsafe {
let element_type = g_variant_type_new(c"t".as_ptr());
let payload = g_variant_new_fixed_array(
element_type,
addrs as gconstpointer,
n_addrs as gsize,
size_of::<u64>() as gsize,
);
g_variant_type_free(element_type);
let reply = host_rpc(FridaCommand::RemapWritablePages, payload);
let virtual_address = g_variant_get_uint64(reply);
g_variant_unref(reply);
virtual_address as gpointer
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_try_mprotect(
address: gpointer,
size: gsize,
prot: GumPageProtection,
) -> gboolean {
if !crate::transport_is_up() {
return 1;
}
unsafe {
let payload = g_variant_new(c"(ttu)".as_ptr(), address as u64, size as u64, prot as u32);
let reply = host_rpc(FridaCommand::MemoryProtect, payload);
let success = g_variant_get_boolean(reply);
g_variant_unref(reply);
if success != 0 {
flush_tlb_range(address as u64, size as u64);
}
success
}
}
// The host rewrites our page-table descriptors through the physical-memory
// 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.
unsafe fn flush_tlb_range(address: u64, size: u64) {
unsafe {
let page_size = gum_query_page_size() as u64;
let start = address & !(page_size - 1);
let end = (address + size + page_size - 1) & !(page_size - 1);
core::arch::asm!("dsb ish", options(nostack, preserves_flags));
let mut va = start;
while va < end {
core::arch::asm!("tlbi vaae1is, {operand}", operand = in(reg) va >> 12,
options(nostack, preserves_flags));
va += page_size;
}
core::arch::asm!("dsb ish", "isb", options(nostack, preserves_flags));
}
}
#[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 {
// Executable slabs were flipped to RX in the page tables; restore RW before returning them to
// the allocator, otherwise the reclaimed pages stay non-writable and the next consumer faults.
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) {
let kernel_base = kernel::get_kernel_base();
unsafe {
let module_infos = core::ptr::addr_of!(crate::MODULE_INFO);
let module_infos = &*module_infos;
let mut i = 0;
for module_info in module_infos.iter() {
let module_base = kernel_base + module_info.offset as u64;
let module_path = if i == 0 {
"/System/Library/Kernels/kernel"
} else {
&format!(
"/System/Library/Extensions/{}.kext/{}",
module_info.name, module_info.name
)
};
let module_range = GumMemoryRange {
base_address: module_base,
size: module_info.size as u64,
};
let module = gum::gum_native_module_new(&module_path, &module_info.version, &module_range);
gum_barebone_register_module(registry, module);
g_object_unref(module as gpointer);
i += 1;
}
}
}
pub(crate) unsafe fn enumerate_exports_in_range(
start_address: u64,
end_address: u64,
callback: &mut FoundExportCallback<'_>,
) {
unsafe {
let symbol_table = core::ptr::addr_of!(crate::SYMBOL_TABLE);
let symbol_table = &*symbol_table;
const N_EXT: u8 = 0x01; // External symbol flag
const N_TYPE: u8 = 0x0e; // Type mask
const N_SECT: u8 = 0x0e; // Defined in section
for symbol_ref in symbol_table.iter_symbols_in_range(start_address, end_address) {
let is_external = (symbol_ref.symbol_type() & N_EXT) != 0;
let is_defined = (symbol_ref.symbol_type() & N_TYPE) == N_SECT;
if !is_external || !is_defined {
continue;
}
if !callback(symbol_ref.name_ptr(), symbol_ref.address()) {
break;
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_symbol_details_from_address(
address: gpointer,
details: *mut GumDebugSymbolDetails,
) -> gboolean {
unsafe {
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
if let Some(symbol) = table.find_symbol_by_address(address as u64) {
let name_bytes = symbol.name().as_bytes();
let copy_len = core::cmp::min(name_bytes.len(), 2048);
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;
return 1;
}
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_symbol_name_from_address(address: gpointer) -> *mut gchar {
unsafe {
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
let name_ptr = table.find_symbol_name_ptr_by_address(address as u64);
g_strdup(name_ptr as *const gchar)
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_function(name: *const gchar) -> gpointer {
unsafe {
let target_name = CStr::from_ptr(name).to_string_lossy();
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
if let Some(symbol) = table.find_symbol_by_name(&target_name) {
return symbol.address() as gpointer;
}
core::ptr::null_mut()
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_functions_named(name: *const gchar) -> *mut GArray {
unsafe {
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
let target_name = CStr::from_ptr(name).to_string_lossy();
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
let symbols = table.find_symbols_by_name(&target_name);
for symbol in symbols {
let addr = symbol.address() as gpointer;
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
}
array
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_functions_matching(pattern: *const gchar) -> *mut GArray {
unsafe {
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
let glob_pattern = CStr::from_ptr(pattern).to_string_lossy();
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
let symbols = table.find_symbols_matching_glob(&glob_pattern);
for symbol in symbols {
let addr = symbol.address() as gpointer;
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
}
array
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
0
}
+114
View File
@@ -0,0 +1,114 @@
// Linux transport: /dev/frida, framed the same way as the XNU hostlinks (4-byte
// little-endian length prefix) so the host-side (yqv) protocol is identical across
// every transport.
//
// The character device itself lives in linux/frida-kmod.c and hands us a byte
// stream; whoever opens the device decides how it reaches the host.
use core::cell::UnsafeCell;
use core::ffi::{c_int, c_void};
use alloc::vec::Vec;
struct Inner {
rx_lenbuf: [u8; 4],
rx_lenhave: usize,
rx_buf: Vec<u8>,
rx_have: usize,
rx_need: usize,
}
pub struct Hostlink {
state: UnsafeCell<Inner>,
on_rx: Option<fn(&[u8])>,
}
unsafe impl Send for Hostlink {}
impl Hostlink {
pub fn init(on_rx: Option<fn(&[u8])>) -> Result<Self, ()> {
if unsafe { frida_kmod_link_open() } != 0 {
return Err(());
}
Ok(Hostlink {
state: UnsafeCell::new(Inner {
rx_lenbuf: [0; 4],
rx_lenhave: 0,
rx_buf: Vec::new(),
rx_have: 0,
rx_need: 0,
}),
on_rx,
})
}
pub fn send(&self, payload: &[u8]) {
let header = (payload.len() as u32).to_le_bytes();
unsafe {
frida_kmod_link_send(header.as_ptr() as *const c_void, header.len());
frida_kmod_link_send(payload.as_ptr() as *const c_void, payload.len());
}
}
pub fn process(&self) {
let s = unsafe { &mut *self.state.get() };
loop {
if s.rx_lenhave < 4 {
let lo = s.rx_lenhave;
let n = recv_nonblocking(&mut s.rx_lenbuf[lo..4]);
if n == 0 {
return;
}
s.rx_lenhave += n;
if s.rx_lenhave < 4 {
continue;
}
s.rx_need = u32::from_le_bytes(s.rx_lenbuf) as usize;
s.rx_have = 0;
s.rx_buf.resize(s.rx_need, 0);
}
while s.rx_have < s.rx_need {
let lo = s.rx_have;
let hi = s.rx_need;
let n = recv_nonblocking(&mut s.rx_buf[lo..hi]);
if n == 0 {
return;
}
s.rx_have += n;
}
// Detach the frame and reset receive state before dispatching: the callback may
// re-enter process() (a synchronous host RPC issued while handling a command), and
// it must start from a clean state instead of re-dispatching this same frame.
let frame = core::mem::take(&mut s.rx_buf);
let need = s.rx_need;
s.rx_lenhave = 0;
s.rx_have = 0;
s.rx_need = 0;
if let Some(cb) = self.on_rx {
cb(&frame[..need]);
}
}
}
}
impl Drop for Hostlink {
fn drop(&mut self) {
unsafe { frida_kmod_link_close() };
}
}
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 }
}
unsafe extern "C" {
fn frida_kmod_link_open() -> c_int;
fn frida_kmod_link_close();
fn frida_kmod_link_send(data: *const c_void, size: usize) -> c_int;
fn frida_kmod_link_recv(data: *mut c_void, size: usize) -> isize;
}
+403 -68
View File
@@ -5,7 +5,7 @@ use core::ptr::{read_volatile, write_volatile};
use core::sync::atomic::{AtomicUsize, Ordering};
use crate::gum::gum_barebone_query_page_size;
use crate::xnu;
use crate::kernel;
const MMIO_SIZE: u64 = 0x200;
@@ -51,6 +51,46 @@ const F_MULTIPORT: u64 = 1u64 << 1;
const INT_VRING: u32 = 1;
const PCI_CONFIG_ADDRESS: u16 = 0xcf8;
const PCI_CONFIG_DATA: u16 = 0xcfc;
const PCI_VENDOR_VIRTIO: u16 = 0x1af4;
const PCI_DEVICE_CONSOLE_LEGACY: u16 = 0x1003;
const PCI_DEVICE_CONSOLE_MODERN: u16 = 0x1043;
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;
const PCI_COMMAND_MEMORY: u32 = 1 << 1;
const PCI_COMMAND_BUS_MASTER: u32 = 1 << 2;
const PCI_CAP_ID_VENDOR: u8 = 0x09;
const VIRTIO_CAP_CFG_TYPE: u8 = 3;
const VIRTIO_CAP_BAR: u8 = 4;
const VIRTIO_CAP_OFFSET: u8 = 8;
const VIRTIO_CAP_LENGTH: u8 = 12;
const VIRTIO_CAP_NOTIFY_MULTIPLIER: u8 = 16;
const CFG_TYPE_COMMON: u8 = 1;
const CFG_TYPE_NOTIFY: u8 = 2;
const CFG_TYPE_ISR: u8 = 3;
const COMMON_DEVFEAT_SEL: usize = 0x00;
const COMMON_DEVFEAT: usize = 0x04;
const COMMON_DRVFEAT_SEL: usize = 0x08;
const COMMON_DRVFEAT: usize = 0x0c;
const COMMON_STATUS: usize = 0x14;
const COMMON_QSEL: usize = 0x16;
const COMMON_QNUM: usize = 0x18;
const COMMON_QREADY: usize = 0x1c;
const COMMON_QNOTIFY_OFF: usize = 0x1e;
const COMMON_QDESC: usize = 0x20;
const COMMON_QAVAIL: usize = 0x28;
const COMMON_QUSED: usize = 0x30;
const Q_RX0: u16 = 0;
const Q_TX0: u16 = 1;
const Q_CTRL_RX: u16 = 2;
@@ -107,18 +147,19 @@ struct DmaPage {
fn dma_page_alloc() -> DmaPage {
let len = PAGE_SIZE.load(Ordering::Relaxed);
let va = xnu::kalloc(len);
let pa = xnu::ml_vtophys(va as u64);
let va = kernel::alloc_code(len);
let pa = kernel::virt_to_phys(va as u64);
DmaPage { va, pa }
}
fn dma_page_free(p: DmaPage) {
let len = PAGE_SIZE.load(Ordering::Relaxed);
xnu::free(p.va, len);
kernel::free_code(p.va, len);
}
struct Vq {
sel: u16,
notify_off: u16,
size: u16,
desc_va: *mut u8,
avail_va: *mut u8,
@@ -129,12 +170,7 @@ struct Vq {
free_cnt: u16,
}
impl Vq {
fn new(mmio: *mut u8, sel: u16, size: u16) -> Self {
w32(mmio, QSEL, sel as u32);
let max = r32(mmio, QNUM_MAX) as u16;
debug_assert!(max >= size && max != 0);
w32(mmio, QNUM, size as u32);
fn new(regs: &Regs, sel: u16, size: u16) -> Self {
let d = dma_page_alloc();
let a = dma_page_alloc();
let u = dma_page_alloc();
@@ -146,10 +182,6 @@ impl Vq {
core::ptr::write_bytes(u.va, 0, ps);
}
w64(mmio, QDESC_LO, QDESC_HI, d.pa);
w64(mmio, QAVAIL_LO, QAVAIL_HI, a.pa);
w64(mmio, QUSED_LO, QUSED_HI, u.pa);
let dp = d.va as *mut Desc;
for i in 0..size {
unsafe {
@@ -158,10 +190,11 @@ impl Vq {
}
}
w32(mmio, QREADY, 1);
let notify_off = regs.queue_prepare(sel, size, d.pa, a.pa, u.pa);
Self {
sel,
notify_off,
size,
desc_va: d.va,
avail_va: a.va,
@@ -230,7 +263,7 @@ impl Vq {
}
struct Inner {
mmio: *mut u8,
regs: Regs,
ctrl_rx: Vq,
ctrl_tx: Vq,
@@ -271,27 +304,46 @@ unsafe impl Send for Hostlink {}
impl Hostlink {
pub fn init(mmio_base: u64, irq_line: u32, on_rx: Option<fn(&[u8])>, wake_token: *const u8) -> Result<Self, ()> {
let page_size = gum_barebone_query_page_size();
PAGE_SIZE.store(page_size as usize, Ordering::Relaxed);
let mmio = xnu::ml_io_map(mmio_base, MMIO_SIZE) as *mut u8;
let mmio = kernel::map_io(mmio_base, MMIO_SIZE) as *mut u8;
if mmio.is_null() {
return Err(());
}
let regs = Regs::Mmio(mmio);
w32(mmio, STATUS, 0);
w32(mmio, STATUS, ST_ACK | ST_DRV);
regs.reset();
let magic_ok = r32(mmio, MAGIC) == 0x7472_6976;
let version_ok = r32(mmio, VERSION) == 2;
let device_ok = r32(mmio, DEVICE) == DEV_ID_CONSOLE;
if !(magic_ok && version_ok && device_ok) {
w32(mmio, STATUS, ST_FAILED);
regs.set_status(ST_FAILED);
return Err(());
}
let dev_lo = feat_get(mmio, 0) as u64;
let dev_hi = (feat_get(mmio, 1) as u64) << 32;
Self::start(regs, irq_line, on_rx, wake_token)
}
#[cfg(target_arch = "x86")]
pub fn init_pci(on_rx: Option<fn(&[u8])>, wake_token: *const u8) -> Result<Self, ()> {
let Some(device) = PciDevice::find_console() else {
return Err(());
};
let Some(regs) = device.map_virtio_regs() else {
return Err(());
};
regs.reset();
Self::start(regs, device.irq_line(), on_rx, wake_token)
}
fn start(regs: Regs, irq_line: u32, on_rx: Option<fn(&[u8])>, wake_token: *const u8) -> Result<Self, ()> {
let page_size = gum_barebone_query_page_size();
PAGE_SIZE.store(page_size as usize, Ordering::Relaxed);
let dev_lo = regs.feat_get(0) as u64;
let dev_hi = (regs.feat_get(1) as u64) << 32;
let mut drv: u64 = 0;
if (dev_hi & F_VERSION_1) != 0 {
drv |= F_VERSION_1;
@@ -299,21 +351,24 @@ impl Hostlink {
if (dev_lo & F_MULTIPORT) != 0 {
drv |= F_MULTIPORT;
}
feat_set(mmio, 0, (drv & 0xffff_ffff) as u32);
feat_set(mmio, 1, (drv >> 32) as u32);
w32(mmio, STATUS, r32(mmio, STATUS) | ST_FEAT_OK);
let feats_ok = (r32(mmio, STATUS) & ST_FEAT_OK) != 0;
regs.feat_set(0, (drv & 0xffff_ffff) as u32);
regs.feat_set(1, (drv >> 32) as u32);
regs.set_status(regs.status() | ST_FEAT_OK);
let feats_ok = (regs.status() & ST_FEAT_OK) != 0;
if !feats_ok {
w32(mmio, STATUS, ST_FAILED);
regs.set_status(ST_FAILED);
return Err(());
}
let ctrl_rx = Vq::new(mmio, Q_CTRL_RX, QSZ);
let ctrl_tx = Vq::new(mmio, Q_CTRL_TX, QSZ);
let ctrl_rx = Vq::new(&regs, Q_CTRL_RX, QSZ);
let ctrl_tx = Vq::new(&regs, Q_CTRL_TX, QSZ);
w32(mmio, STATUS, r32(mmio, STATUS) | ST_DRV_OK);
regs.set_status(regs.status() | ST_DRV_OK);
xnu::install_interrupt_handler(
unsafe {
ISR_REGS = Some(regs);
}
kernel::install_interrupt_handler(
irq_line,
wake_token as *mut c_void,
isr_wake,
@@ -321,7 +376,7 @@ impl Hostlink {
);
let inner = Inner {
mmio,
regs,
ctrl_rx,
ctrl_tx,
port_id: None,
@@ -359,7 +414,7 @@ impl Hostlink {
let s = unsafe { &mut *self.state.get() };
let total = 4 + payload.len();
let buf = xnu::kalloc(total);
let buf = kernel::alloc(total);
unsafe {
let len = payload.len() as u32;
*buf.add(0) = (len & 0xFF) as u8;
@@ -370,7 +425,7 @@ impl Hostlink {
}
let frame: &'static [u8] = unsafe { core::slice::from_raw_parts(buf, total) };
let node = xnu::kalloc(size_of::<TxNode>()) as *mut TxNode;
let node = kernel::alloc(size_of::<TxNode>()) as *mut TxNode;
unsafe {
(*node).next = core::ptr::null_mut();
(*node).frame = frame;
@@ -386,15 +441,13 @@ impl Hostlink {
s.tx_tail = node;
}
xnu::thread_wakeup(s.wake_token);
kernel::wake(s.wake_token);
}
pub fn process(&self) {
let s = unsafe { &mut *self.state.get() };
if (r32(s.mmio, ISR) & INT_VRING) != 0 {
w32(s.mmio, ISR_ACK, INT_VRING);
}
s.regs.isr_ack();
self.ctrl_complete();
self.ctrl_prime_rx(QSZ as usize);
@@ -425,8 +478,8 @@ impl Hostlink {
s.ctrl_rx.push_avail(h);
posted += 1;
}
let sel = s.ctrl_rx.sel;
self.kick(sel);
let (sel, notify_off) = (s.ctrl_rx.sel, s.ctrl_rx.notify_off);
self.kick(sel, notify_off);
}
fn ctrl_send(&self, msg: VConsCtrl) {
@@ -445,8 +498,8 @@ impl Hostlink {
}
s.tx_pages[h as usize] = Some(pg);
s.ctrl_tx.push_avail(h);
let sel = s.ctrl_tx.sel;
self.kick(sel);
let (sel, notify_off) = (s.ctrl_tx.sel, s.ctrl_tx.notify_off);
self.kick(sel, notify_off);
}
fn ctrl_complete(&self) {
@@ -488,8 +541,8 @@ impl Hostlink {
s.ctrl_rx.push_avail(h);
}
}
let sel = s.ctrl_rx.sel;
self.kick(sel);
let (sel, notify_off) = (s.ctrl_rx.sel, s.ctrl_rx.notify_off);
self.kick(sel, notify_off);
while let Some(u) = s.ctrl_tx.pop_used() {
let head = u.id as u16;
@@ -511,8 +564,8 @@ impl Hostlink {
let base = 4 + ((id as u16 - 1) * 2);
(base, base + 1)
};
let rx = Vq::new(s.mmio, rx_i, QSZ);
let tx = Vq::new(s.mmio, tx_i, QSZ);
let rx = Vq::new(&s.regs, rx_i, QSZ);
let tx = Vq::new(&s.regs, tx_i, QSZ);
s.rx = Some(rx);
s.tx = Some(tx);
s.port_id = Some(id);
@@ -537,8 +590,8 @@ impl Hostlink {
s.data_rx_pages[h as usize] = Some(pg);
rxq.push_avail(h);
}
let sel = rxq.sel;
self.kick(sel);
let (sel, notify_off) = (rxq.sel, rxq.notify_off);
self.kick(sel, notify_off);
}
fn data_rx_complete(&self) {
@@ -569,8 +622,8 @@ impl Hostlink {
rxq.push_avail(h);
}
}
let sel = rxq.sel;
self.kick(sel);
let (sel, notify_off) = (rxq.sel, rxq.notify_off);
self.kick(sel, notify_off);
}
fn feed_rx_stream(&self, mut chunk: &[u8]) {
@@ -592,7 +645,7 @@ impl Hostlink {
| ((s.rx_lenbuf[3] as usize) << 24);
if s.rx_buf.is_none() && len > 0 {
let buf = xnu::kalloc(len);
let buf = kernel::alloc(len);
let slice: &'static mut [u8] =
unsafe { core::slice::from_raw_parts_mut(buf, len) };
s.rx_buf = Some(slice);
@@ -713,26 +766,278 @@ impl Hostlink {
}
if let Some(h) = head {
let sel = txq.sel;
let (sel, notify_off) = (txq.sel, txq.notify_off);
txq.push_avail(h);
self.kick(sel);
self.kick(sel, notify_off);
}
xnu::free(frame.as_ptr() as *mut u8, frame.len());
xnu::free(node as *mut u8, core::mem::size_of::<TxNode>());
kernel::free(frame.as_ptr() as *mut u8, frame.len());
kernel::free(node as *mut u8, core::mem::size_of::<TxNode>());
}
}
fn kick(&self, sel: u16) {
fn kick(&self, sel: u16, notify_off: u16) {
let s = unsafe { &*self.state.get() };
wmb();
w32(s.mmio, QSEL, sel as u32);
w32(s.mmio, QNOTIFY, sel as u32);
s.regs.notify(sel, notify_off);
}
}
extern "C" fn isr_wake(token: *mut c_void, _refcon: *mut c_void, _nub: *mut c_void, _src: i32) {
xnu::thread_wakeup(token as *const u8);
unsafe {
if let Some(regs) = ISR_REGS {
regs.isr_ack();
}
}
kernel::wake(token as *const u8);
}
static mut ISR_REGS: Option<Regs> = None;
#[derive(Copy, Clone)]
enum Regs {
Mmio(*mut u8),
Pci(PciRegs),
}
#[derive(Copy, Clone)]
struct PciRegs {
common: *mut u8,
notify: *mut u8,
notify_off_multiplier: u32,
isr: *mut u8,
}
impl Regs {
fn reset(&self) {
match self {
Regs::Mmio(base) => {
w32(*base, STATUS, 0);
w32(*base, STATUS, ST_ACK | ST_DRV);
}
Regs::Pci(p) => {
w8(p.common, COMMON_STATUS, 0);
while r8(p.common, COMMON_STATUS) != 0 {}
w8(p.common, COMMON_STATUS, (ST_ACK | ST_DRV) as u8);
}
}
}
fn feat_get(&self, sel: u32) -> u32 {
match self {
Regs::Mmio(base) => {
w32(*base, DEVFEAT_SEL, sel);
r32(*base, DEVFEAT)
}
Regs::Pci(p) => {
w32(p.common, COMMON_DEVFEAT_SEL, sel);
r32(p.common, COMMON_DEVFEAT)
}
}
}
fn feat_set(&self, sel: u32, v: u32) {
match self {
Regs::Mmio(base) => {
w32(*base, DRVFEAT_SEL, sel);
w32(*base, DRVFEAT, v);
}
Regs::Pci(p) => {
w32(p.common, COMMON_DRVFEAT_SEL, sel);
w32(p.common, COMMON_DRVFEAT, v);
}
}
}
fn status(&self) -> u32 {
match self {
Regs::Mmio(base) => r32(*base, STATUS),
Regs::Pci(p) => r8(p.common, COMMON_STATUS) as u32,
}
}
fn set_status(&self, v: u32) {
match self {
Regs::Mmio(base) => w32(*base, STATUS, v),
Regs::Pci(p) => w8(p.common, COMMON_STATUS, v as u8),
}
}
fn queue_prepare(&self, sel: u16, size: u16, desc: u64, avail: u64, used: u64) -> u16 {
match self {
Regs::Mmio(base) => {
let base = *base;
w32(base, QSEL, sel as u32);
debug_assert!(r32(base, QNUM_MAX) as u16 >= size);
w32(base, QNUM, size as u32);
w64(base, QDESC_LO, QDESC_HI, desc);
w64(base, QAVAIL_LO, QAVAIL_HI, avail);
w64(base, QUSED_LO, QUSED_HI, used);
w32(base, QREADY, 1);
0
}
Regs::Pci(p) => {
w16(p.common, COMMON_QSEL, sel);
debug_assert!(r16(p.common, COMMON_QNUM) >= size);
w16(p.common, COMMON_QNUM, size);
w64(p.common, COMMON_QDESC, COMMON_QDESC + 4, desc);
w64(p.common, COMMON_QAVAIL, COMMON_QAVAIL + 4, avail);
w64(p.common, COMMON_QUSED, COMMON_QUSED + 4, used);
let notify_off = r16(p.common, COMMON_QNOTIFY_OFF);
w16(p.common, COMMON_QREADY, 1);
notify_off
}
}
}
fn notify(&self, sel: u16, notify_off: u16) {
match self {
Regs::Mmio(base) => {
w32(*base, QSEL, sel as u32);
w32(*base, QNOTIFY, sel as u32);
}
Regs::Pci(p) => {
let off = notify_off as usize * p.notify_off_multiplier as usize;
w16(p.notify, off, sel);
}
}
}
fn isr_ack(&self) {
match self {
Regs::Mmio(base) => {
if (r32(*base, ISR) & INT_VRING) != 0 {
w32(*base, ISR_ACK, INT_VRING);
}
}
Regs::Pci(p) => {
r8(p.isr, 0);
}
}
}
}
#[cfg(target_arch = "x86")]
#[derive(Copy, Clone)]
struct PciDevice {
bus: u8,
devfn: u8,
}
#[cfg(target_arch = "x86")]
impl PciDevice {
fn find_console() -> Option<Self> {
for devfn in 0..=u8::MAX {
let device = PciDevice { bus: 0, devfn };
let identity = device.read_config(0);
let vendor = (identity & 0xffff) as u16;
let model = (identity >> 16) as u16;
if vendor != PCI_VENDOR_VIRTIO {
continue;
}
if model != PCI_DEVICE_CONSOLE_LEGACY && model != PCI_DEVICE_CONSOLE_MODERN {
continue;
}
return Some(device);
}
None
}
fn map_virtio_regs(&self) -> Option<Regs> {
self.enable_memory_and_bus_mastering();
let mut common = core::ptr::null_mut();
let mut notify = core::ptr::null_mut();
let mut notify_off_multiplier = 0;
let mut isr = core::ptr::null_mut();
let mut cap = (self.read_config(PCI_CAP_LIST_POINTER) & 0xfc) as u8;
while cap != 0 {
let header = self.read_config(cap);
if (header & 0xff) as u8 == PCI_CAP_ID_VENDOR {
let cfg_type = self.read_config_byte(cap + VIRTIO_CAP_CFG_TYPE);
let bar = self.read_config_byte(cap + VIRTIO_CAP_BAR);
let offset = self.read_config(cap + VIRTIO_CAP_OFFSET);
let length = self.read_config(cap + VIRTIO_CAP_LENGTH);
match cfg_type {
CFG_TYPE_COMMON => {
common = self.map_bar_region(bar, offset, length);
}
CFG_TYPE_NOTIFY => {
notify = self.map_bar_region(bar, offset, length);
notify_off_multiplier =
self.read_config(cap + VIRTIO_CAP_NOTIFY_MULTIPLIER);
}
CFG_TYPE_ISR => {
isr = self.map_bar_region(bar, offset, length);
}
_ => {}
}
}
cap = ((header >> 8) & 0xfc) as u8;
}
if common.is_null() || notify.is_null() || isr.is_null() {
return None;
}
Some(Regs::Pci(PciRegs {
common,
notify,
notify_off_multiplier,
isr,
}))
}
fn irq_line(&self) -> u32 {
self.read_config_byte(PCI_INTERRUPT_LINE) as u32
}
fn enable_memory_and_bus_mastering(&self) {
let command = self.read_config(PCI_COMMAND);
self.write_config(
PCI_COMMAND,
command | PCI_COMMAND_MEMORY | PCI_COMMAND_BUS_MASTER,
);
}
fn map_bar_region(&self, bar: u8, offset: u32, length: u32) -> *mut u8 {
let slot = PCI_BASE_ADDRESS_0 + bar * 4;
let lo = self.read_config(slot);
let is_sixty_four_bit = (lo & 0b110) == 0b100;
let base = if is_sixty_four_bit {
((self.read_config(slot + 4) as u64) << 32) | ((lo & !0xf) as u64)
} else {
(lo & !0xf) as u64
};
kernel::map_io(base + offset as u64, length as u64) as *mut u8
}
fn read_config_byte(&self, offset: u8) -> u8 {
(self.read_config(offset) >> ((offset & 3) * 8)) as u8
}
fn read_config(&self, offset: u8) -> u32 {
outl(PCI_CONFIG_ADDRESS, self.config_address(offset));
inl(PCI_CONFIG_DATA)
}
fn write_config(&self, offset: u8, value: u32) {
outl(PCI_CONFIG_ADDRESS, self.config_address(offset));
outl(PCI_CONFIG_DATA, value);
}
fn config_address(&self, offset: u8) -> u32 {
0x8000_0000
| ((self.bus as u32) << 16)
| ((self.devfn as u32) << 8)
| ((offset as u32) & 0xfc)
}
}
fn r32(mmio: *mut u8, off: usize) -> u32 {
@@ -748,16 +1053,46 @@ fn w64(mmio: *mut u8, lo: usize, hi: usize, v: u64) {
w32(mmio, hi, (v >> 32) as u32);
}
fn feat_get(mmio: *mut u8, sel: u32) -> u32 {
w32(mmio, DEVFEAT_SEL, sel);
r32(mmio, DEVFEAT)
fn r16(mmio: *mut u8, off: usize) -> u16 {
unsafe { read_volatile(mmio.add(off) as *const u16) }
}
fn feat_set(mmio: *mut u8, sel: u32, v: u32) {
w32(mmio, DRVFEAT_SEL, sel);
w32(mmio, DRVFEAT, v)
fn w16(mmio: *mut u8, off: usize, val: u16) {
unsafe { write_volatile(mmio.add(off) as *mut u16, val) }
}
fn r8(mmio: *mut u8, off: usize) -> u8 {
unsafe { read_volatile(mmio.add(off)) }
}
fn w8(mmio: *mut u8, off: usize, val: u8) {
unsafe { write_volatile(mmio.add(off), val) }
}
#[cfg(target_arch = "x86")]
fn inl(port: u16) -> u32 {
let value: u32;
unsafe {
core::arch::asm!("in eax, dx", out("eax") value, in("dx") port,
options(nomem, nostack, preserves_flags));
}
value
}
#[cfg(target_arch = "x86")]
fn outl(port: u16, value: u32) {
unsafe {
core::arch::asm!("out dx, eax", in("dx") port, in("eax") value,
options(nomem, nostack, preserves_flags));
}
}
#[cfg(target_arch = "x86")]
fn wmb() {
unsafe { core::arch::asm!("sfence", options(nostack, preserves_flags)) }
}
#[cfg(target_arch = "aarch64")]
fn wmb() {
unsafe { core::arch::asm!("dmb ishst", options(nostack, preserves_flags)) }
}
+267
View File
@@ -0,0 +1,267 @@
// Same 4-byte LE length-prefix framing as hostlink_virtio so the host-side
// (yqv) protocol is identical across transports.
use core::cell::UnsafeCell;
use core::ffi::{c_int, c_void};
use core::ptr;
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;
const MSG_DONTWAIT: c_int = 0x80;
// Darwin errno values for a non-blocking connect handshake that completes in the background.
const EWOULDBLOCK: c_int = 35;
const EINPROGRESS: c_int = 36;
const EISCONN: c_int = 56;
#[repr(C)]
struct SockaddrVm {
svm_len: u8,
svm_family: u8,
svm_reserved1: u16,
svm_port: u32,
svm_cid: u32,
svm_zero: [u8; 4],
}
#[repr(C)]
struct Iovec {
iov_base: *mut c_void,
iov_len: usize,
}
#[repr(C)]
struct Msghdr {
msg_name: *mut c_void,
msg_namelen: u32,
msg_iov: *mut Iovec,
msg_iovlen: c_int,
msg_control: *mut c_void,
msg_controllen: u32,
msg_flags: c_int,
}
type SocketT = *mut c_void;
type SockUpcall = unsafe extern "C" fn(so: SocketT, cookie: *mut c_void, waitf: c_int);
unsafe extern "C" {
static _sock_socket: unsafe extern "C" fn(
domain: c_int,
type_: c_int,
proto: c_int,
callback: Option<SockUpcall>,
cookie: *mut c_void,
new_so: *mut SocketT,
) -> c_int;
static _sock_connect:
unsafe extern "C" fn(so: SocketT, to: *const c_void, flags: c_int) -> c_int;
static _sock_send: unsafe extern "C" fn(
so: SocketT,
msg: *const Msghdr,
flags: c_int,
sentlen: *mut usize,
) -> c_int;
static _sock_receive: unsafe extern "C" fn(
so: SocketT,
msg: *mut Msghdr,
flags: c_int,
recvlen: *mut usize,
) -> c_int;
static _sock_close: unsafe extern "C" fn(so: SocketT);
}
static UPCALL_PENDING: AtomicU32 = AtomicU32::new(0);
struct Inner {
so: SocketT,
rx_lenbuf: [u8; 4],
rx_lenhave: usize,
rx_buf: Vec<u8>,
rx_have: usize,
rx_need: usize,
}
pub struct Hostlink {
state: UnsafeCell<Inner>,
on_rx: Option<fn(&[u8])>,
}
unsafe impl Send for Hostlink {}
impl Hostlink {
pub fn init(
host_port: u32,
on_rx: Option<fn(&[u8])>,
wake_token: *const u8,
) -> Result<Self, ()> {
unsafe {
let mut so: SocketT = ptr::null_mut();
// The kernel invokes the sock_upcall via an authenticated branch (blraa, IA key)
// with the sock_upcall type discriminator, so the pointer must be signed to match.
let signed_upcall: SockUpcall =
core::mem::transmute(crate::pac::ptrauth_sign(upcall as *const u8, 0x12f7));
let rc = _sock_socket(
AF_VSOCK,
SOCK_STREAM,
0,
Some(signed_upcall),
wake_token as *mut c_void,
&mut so,
);
if rc != 0 || so.is_null() {
return Err(());
}
let addr = SockaddrVm {
svm_len: core::mem::size_of::<SockaddrVm>() as u8,
svm_family: AF_VSOCK as u8,
svm_reserved1: 0,
svm_port: host_port,
svm_cid: VMADDR_CID_HOST,
svm_zero: [0; 4],
};
// The KPI connect is asynchronous: it returns EWOULDBLOCK/EINPROGRESS and the
// handshake completes in the background once the host accepts. Re-calling connect to
// poll is invalid (it corrupts the connecting state), so accept the in-progress codes
// as success and let the main loop drive I/O once the link is up.
let rc = _sock_connect(so, &addr as *const _ as *const c_void, 0);
if rc != 0 && !matches!(rc, EWOULDBLOCK | EINPROGRESS | EISCONN) {
_sock_close(so);
return Err(());
}
Ok(Hostlink {
state: UnsafeCell::new(Inner {
so,
rx_lenbuf: [0; 4],
rx_lenhave: 0,
rx_buf: Vec::new(),
rx_have: 0,
rx_need: 0,
}),
on_rx,
})
}
}
pub fn send(&self, payload: &[u8]) {
let s = unsafe { &*self.state.get() };
unsafe {
let len = payload.len() as u32;
let mut hdr = [
(len & 0xff) as u8,
((len >> 8) & 0xff) as u8,
((len >> 16) & 0xff) as u8,
((len >> 24) & 0xff) as u8,
];
let mut iov = [
Iovec {
iov_base: hdr.as_mut_ptr() as *mut c_void,
iov_len: 4,
},
Iovec {
iov_base: payload.as_ptr() as *mut c_void,
iov_len: payload.len(),
},
];
let msg = Msghdr {
msg_name: ptr::null_mut(),
msg_namelen: 0,
msg_iov: iov.as_mut_ptr(),
msg_iovlen: 2,
msg_control: ptr::null_mut(),
msg_controllen: 0,
msg_flags: 0,
};
let mut sent: usize = 0;
let _ = _sock_send(s.so, &msg, 0, &mut sent);
}
}
pub fn process(&self) {
let s = unsafe { &mut *self.state.get() };
loop {
if s.rx_lenhave < 4 {
let lo = s.rx_lenhave;
let n = unsafe { recv_nonblocking(s.so, &mut s.rx_lenbuf[lo..4]) };
if n == 0 {
return;
}
s.rx_lenhave += n;
if s.rx_lenhave < 4 {
continue;
}
s.rx_need = u32::from_le_bytes(s.rx_lenbuf) as usize;
s.rx_have = 0;
s.rx_buf.resize(s.rx_need, 0);
}
while s.rx_have < s.rx_need {
let lo = s.rx_have;
let hi = s.rx_need;
let n = unsafe { recv_nonblocking(s.so, &mut s.rx_buf[lo..hi]) };
if n == 0 {
return;
}
s.rx_have += n;
}
// Detach the frame and reset receive state before dispatching: the callback may
// re-enter process() (a synchronous host RPC issued while handling a command), and
// it must start from a clean state instead of re-dispatching this same frame.
let frame = core::mem::take(&mut s.rx_buf);
let need = s.rx_need;
s.rx_lenhave = 0;
s.rx_have = 0;
s.rx_need = 0;
if let Some(cb) = self.on_rx {
cb(&frame[..need]);
}
}
}
}
impl Drop for Hostlink {
fn drop(&mut self) {
unsafe {
let s = &*self.state.get();
_sock_close(s.so);
}
}
}
unsafe fn recv_nonblocking(so: SocketT, dst: &mut [u8]) -> usize {
unsafe {
let mut iov = Iovec {
iov_base: dst.as_mut_ptr() as *mut c_void,
iov_len: dst.len(),
};
let mut msg = Msghdr {
msg_name: ptr::null_mut(),
msg_namelen: 0,
msg_iov: &mut iov,
msg_iovlen: 1,
msg_control: ptr::null_mut(),
msg_controllen: 0,
msg_flags: 0,
};
let mut got: usize = 0;
let _ = _sock_receive(so, &mut msg, MSG_DONTWAIT, &mut got);
got
}
}
unsafe extern "C" fn upcall(_so: SocketT, cookie: *mut c_void, _waitf: c_int) {
UPCALL_PENDING.fetch_add(1, Ordering::Release);
if !cookie.is_null() {
kernel::wake(cookie as *const u8);
}
}
+19
View File
@@ -0,0 +1,19 @@
// 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.
//
// XNU is reached from the outside: a remote stub injects the agent and patches
// the addresses it needs into .kernel_addrs. Linux is reached from the inside:
// the agent is a kernel module, so its glue is an ordinary C translation unit
// (linux/frida-kmod.c) compiled against the target kernel's headers by kbuild.
#[cfg(feature = "linux")]
pub use crate::linux::*;
#[cfg(feature = "win9x")]
pub use crate::win9x::*;
#[cfg(feature = "xnu")]
pub use crate::xnu::*;
use core::ffi::c_void;
pub type ThreadEntry = unsafe extern "C" fn(parameter: *mut c_void, wait_result: i32);
File diff suppressed because it is too large. Load diff
+545 -77
View File
@@ -1,35 +1,69 @@
// The C library's lower edge. Nothing here does real work: the agent has no
// filesystem and never exits, so these exist to satisfy references and to give
// malloc some memory to hand out.
//
// The two flavours link against different C libraries, and they disagree on what
// this layer is called. newlib asks for the underscore-prefixed syscall names;
// picolibc, which the soft-float flavour needs because newlib has no soft-float
// build for arm64, calls them without the prefix. The bodies are shared and the
// exported names live in the two `exports` modules at the end.
use core::ptr;
use core::sync::atomic::Ordering;
use crate::bindings::{GMutex, GRecMutex};
use crate::gthread;
static mut MALLOC_LOCK: GRecMutex = unsafe { core::mem::zeroed() };
// Serialises the C library. Which hooks the library reaches for differs: newlib asks
// for __malloc_lock/__malloc_unlock, picolibc for the retargetable lock API below,
// whose default implementation is an empty function — so on picolibc malloc runs
// unlocked unless we supply these, and any second thread corrupts the heap.
static mut LIBC_LOCK: GRecMutex = unsafe { core::mem::zeroed() };
#[unsafe(no_mangle)]
pub extern "C" fn __malloc_lock(_reent: *mut core::ffi::c_void) {
gthread::g_rec_mutex_lock(ptr::addr_of_mut!(MALLOC_LOCK));
fn libc_lock() {
gthread::g_rec_mutex_lock(ptr::addr_of_mut!(LIBC_LOCK));
}
#[unsafe(no_mangle)]
pub extern "C" fn __malloc_unlock(_reent: *mut core::ffi::c_void) {
gthread::g_rec_mutex_unlock(ptr::addr_of_mut!(MALLOC_LOCK));
fn libc_unlock() {
gthread::g_rec_mutex_unlock(ptr::addr_of_mut!(LIBC_LOCK));
offer_preemption_point();
}
#[unsafe(no_mangle)]
pub extern "C" fn _fini() {
fn offer_preemption_point() {
let total = ALLOCATIONS_TOTAL.fetch_add(1, Ordering::Relaxed) + 1;
if total % ALLOCATIONS_PER_YIELD == 0 {
crate::kernel::yield_now();
}
}
static ALLOCATIONS_TOTAL: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
const ALLOCATIONS_PER_YIELD: u32 = 1024;
// GLib's g_get_num_processors() probes this; report a single CPU so the
// parallel scanners fall back to their inline path.
#[unsafe(no_mangle)]
pub extern "C" fn _exit(_status: i32) -> ! {
loop {}
pub extern "C" fn sysconf(_name: i32) -> isize {
1
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[unsafe(no_mangle)]
pub extern "C" fn _kill(_pid: i32, _sig: i32) -> i32 {
-1
pub extern "C" fn __clear_cache(_start: *const u8, _end: *const u8) {}
#[cfg(target_arch = "aarch64")]
#[unsafe(no_mangle)]
pub extern "C" fn __clear_cache(_start: *const u8, _end: *const u8) {
unsafe {
core::arch::asm!(
"ic iallu", // Invalidate all instruction caches
"dsb sy", // Data synchronization barrier
"isb", // Instruction synchronization barrier
options(nomem, nostack),
);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _sbrk(incr: isize) -> *mut u8 {
fn sbrk_impl(incr: isize) -> *mut u8 {
const HEAP_SIZE: usize = 32 * 1024 * 1024; // 32 MB
static mut HEAP_START: *mut u8 = ptr::null_mut();
@@ -40,7 +74,7 @@ pub extern "C" fn _sbrk(incr: isize) -> *mut u8 {
gthread::g_mutex_lock(ptr::addr_of_mut!(HEAP_MUTEX));
if HEAP_START.is_null() {
HEAP_START = crate::xnu::kalloc(HEAP_SIZE);
HEAP_START = crate::kernel::alloc(HEAP_SIZE);
if HEAP_START.is_null() {
gthread::g_mutex_unlock(ptr::addr_of_mut!(HEAP_MUTEX));
panic!("Failed to allocate 32 MB heap");
@@ -49,7 +83,8 @@ pub extern "C" fn _sbrk(incr: isize) -> *mut u8 {
}
let prev_heap_current = HEAP_CURRENT;
let new_heap_current = HEAP_CURRENT.add(incr as usize);
// Signed: the allocator hands memory back by asking for a negative increment.
let new_heap_current = HEAP_CURRENT.offset(incr);
if new_heap_current > HEAP_START.add(HEAP_SIZE) {
gthread::g_mutex_unlock(ptr::addr_of_mut!(HEAP_MUTEX));
@@ -65,20 +100,14 @@ pub extern "C" fn _sbrk(incr: isize) -> *mut u8 {
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _getpid() -> i32 {
0
}
#[repr(C)]
struct Timeval {
tv_sec: i64,
tv_usec: i64,
}
#[unsafe(no_mangle)]
pub extern "C" fn _gettimeofday(tp: *mut core::ffi::c_void, _tzp: *mut core::ffi::c_void) -> i32 {
let (secs, microsecs) = crate::xnu::clock_get_calendar_microtime();
fn gettimeofday_impl(tp: *mut core::ffi::c_void) -> i32 {
let (secs, microsecs) = crate::kernel::wall_clock_micros();
let timeval = Timeval {
tv_sec: secs as i64,
tv_usec: microsecs as i64,
@@ -93,61 +122,500 @@ pub extern "C" fn _gettimeofday(tp: *mut core::ffi::c_void, _tzp: *mut core::ffi
0
}
#[unsafe(no_mangle)]
pub extern "C" fn _isatty(_fd: i32) -> i32 {
1
}
#[unsafe(no_mangle)]
pub extern "C" fn _open(_path: *const u8, _flags: i32, _mode: i32) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn _close(_fd: i32) -> i32 {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn _read(_fd: i32, _buf: *mut u8, _count: usize) -> isize {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn _write(_fd: i32, _buf: *const u8, count: usize) -> isize {
fn write_impl(count: usize) -> isize {
count as isize
}
#[unsafe(no_mangle)]
pub extern "C" fn _lseek(_fd: i32, _offset: isize, _whence: i32) -> isize {
0
}
#[cfg(feature = "xnu")]
mod exports {
use super::*;
#[unsafe(no_mangle)]
pub extern "C" fn _fstat(_fd: i32, _st: *mut core::ffi::c_void) -> i32 {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn _link(_old: *const u8, _new: *const u8) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn _unlink(_path: *const u8) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn __clear_cache(_start: *const u8, _end: *const u8) {
// On AArch64, we need to flush the instruction cache
// This is a simplified implementation for the kernel environment
unsafe {
core::arch::asm!(
"ic iallu", // Invalidate all instruction caches
"dsb sy", // Data synchronization barrier
"isb", // Instruction synchronization barrier
options(nomem, nostack),
);
#[unsafe(no_mangle)]
pub extern "C" fn __malloc_lock(_reent: *mut core::ffi::c_void) {
libc_lock();
}
#[unsafe(no_mangle)]
pub extern "C" fn __malloc_unlock(_reent: *mut core::ffi::c_void) {
libc_unlock();
}
#[unsafe(no_mangle)]
pub extern "C" fn _fini() {}
#[unsafe(no_mangle)]
pub extern "C" fn _exit(_status: i32) -> ! {
loop {}
}
#[unsafe(no_mangle)]
pub extern "C" fn _kill(_pid: i32, _sig: i32) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn _sbrk(incr: isize) -> *mut u8 {
sbrk_impl(incr)
}
#[unsafe(no_mangle)]
pub extern "C" fn _getpid() -> i32 {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn _gettimeofday(
tp: *mut core::ffi::c_void,
_tzp: *mut core::ffi::c_void,
) -> i32 {
gettimeofday_impl(tp)
}
#[unsafe(no_mangle)]
pub extern "C" fn _isatty(_fd: i32) -> i32 {
1
}
#[unsafe(no_mangle)]
pub extern "C" fn _open(_path: *const u8, _flags: i32, _mode: i32) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn _close(_fd: i32) -> i32 {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn _read(_fd: i32, _buf: *mut u8, _count: usize) -> isize {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn _write(_fd: i32, _buf: *const u8, count: usize) -> isize {
write_impl(count)
}
#[unsafe(no_mangle)]
pub extern "C" fn _lseek(_fd: i32, _offset: isize, _whence: i32) -> isize {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn _fstat(_fd: i32, _st: *mut core::ffi::c_void) -> i32 {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn _stat(_path: *const u8, _st: *mut core::ffi::c_void) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn _link(_old: *const u8, _new: *const u8) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn _unlink(_path: *const u8) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn creat(_path: *const u8, _mode: i32) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn access(_path: *const u8, _mode: i32) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn chmod(_path: *const u8, _mode: i32) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn chdir(_path: *const u8) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn rmdir(_path: *const u8) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn gethostname(_name: *mut u8, _len: usize) -> i32 {
-1
}
// newlib wires these up for exit(), which we never reach: the agent runs until
// the kernel goes down or the module is unloaded.
#[unsafe(no_mangle)]
pub static __libc_fini: Option<extern "C" fn()> = None;
#[unsafe(no_mangle)]
pub static __fini_array_start: [extern "C" fn(); 0] = [];
#[unsafe(no_mangle)]
pub static __fini_array_end: [extern "C" fn(); 0] = [];
}
#[cfg(any(feature = "linux", feature = "win9x"))]
mod exports {
use super::*;
// The C library's own allocator sits on a fixed sbrk heap and carries its own
// locking; inside a kernel module the kernel's allocator is already there, already
// safe to share between threads, and not capped at a size we picked up front.
// Every C allocation goes here instead.
//
// The sixteen bytes before the returned pointer hold the block the kernel gave us
// and its total size, so a pointer that was moved forward to satisfy an alignment
// request frees exactly like one that was not.
use core::ffi::c_void;
const HEAP_HEADER_SIZE: usize = 16;
const MALLOC_ALIGNMENT: usize = 2 * core::mem::size_of::<*const u8>();
#[unsafe(no_mangle)]
pub extern "C" fn malloc(size: usize) -> *mut c_void {
let total = size + HEAP_HEADER_SIZE + MALLOC_ALIGNMENT;
let block = crate::kernel::alloc(total);
if block.is_null() {
return ptr::null_mut();
}
offer_preemption_point();
unsafe {
let lowest = block.add(HEAP_HEADER_SIZE) as usize;
let aligned = (lowest + MALLOC_ALIGNMENT - 1) & !(MALLOC_ALIGNMENT - 1);
attach_header(block, total, aligned as *mut u8)
}
}
#[unsafe(no_mangle)]
pub extern "C" fn posix_memalign(
result: *mut *mut c_void,
alignment: usize,
size: usize,
) -> i32 {
let total = size + alignment + HEAP_HEADER_SIZE;
let block = crate::kernel::alloc(total);
if block.is_null() {
return 12; // ENOMEM
}
unsafe {
let lowest = block.add(HEAP_HEADER_SIZE) as usize;
let aligned = (lowest + alignment - 1) & !(alignment - 1);
*result = attach_header(block, total, aligned as *mut u8);
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn free(p: *mut c_void) {
if p.is_null() {
return;
}
unsafe {
let (block, total) = header_of(p);
crate::kernel::free(block, total);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn malloc_usable_size(p: *mut c_void) -> usize {
unsafe { usable_size(p) }
}
#[unsafe(no_mangle)]
pub extern "C" fn calloc(count: usize, size: usize) -> *mut c_void {
let total = count * size;
let p = malloc(total);
if !p.is_null() {
unsafe { ptr::write_bytes(p as *mut u8, 0, total) };
}
p
}
#[unsafe(no_mangle)]
pub extern "C" fn realloc(p: *mut c_void, size: usize) -> *mut c_void {
if p.is_null() {
return malloc(size);
}
let previous_size = unsafe { usable_size(p) };
let grown = malloc(size);
if !grown.is_null() {
unsafe {
ptr::copy_nonoverlapping(p as *const u8, grown as *mut u8, previous_size.min(size))
};
free(p);
}
grown
}
unsafe fn attach_header(block: *mut u8, total: usize, p: *mut u8) -> *mut c_void {
unsafe {
let header = p.sub(HEAP_HEADER_SIZE) as *mut usize;
*header = block as usize;
*header.add(1) = total;
}
p as *mut c_void
}
unsafe fn header_of(p: *mut c_void) -> (*mut u8, usize) {
unsafe {
let header = (p as *mut u8).sub(HEAP_HEADER_SIZE) as *const usize;
(*header as *mut u8, *header.add(1))
}
}
unsafe fn usable_size(p: *mut c_void) -> usize {
unsafe {
let (block, total) = header_of(p);
total - (p as usize - block as usize)
}
}
// One recursive mutex covers every lock the C library asks for. They are coarse
// and few, and over-serialising them is safe where getting it wrong is not.
type Lock = *mut core::ffi::c_void;
// The lock object itself, which we define so the C library's own no-op lock
// implementation is never pulled out of its archive. Its contents are never
// read: the address is just a token, and one mutex covers them all.
// Nothing here references it, so without #[used] it is dropped before the linker
// ever sees it, and the archive member arrives after all to supply it.
#[allow(non_upper_case_globals)]
#[unsafe(no_mangle)]
#[used]
pub static mut __lock___libc_recursive_mutex: u8 = 0;
#[unsafe(no_mangle)]
pub extern "C" fn __retarget_lock_init(_lock: *mut Lock) {}
#[unsafe(no_mangle)]
pub extern "C" fn __retarget_lock_init_recursive(_lock: *mut Lock) {}
#[unsafe(no_mangle)]
pub extern "C" fn __retarget_lock_close(_lock: Lock) {}
#[unsafe(no_mangle)]
pub extern "C" fn __retarget_lock_close_recursive(_lock: Lock) {}
#[unsafe(no_mangle)]
pub extern "C" fn __retarget_lock_acquire(_lock: Lock) {
libc_lock();
}
#[unsafe(no_mangle)]
pub extern "C" fn __retarget_lock_acquire_recursive(_lock: Lock) {
libc_lock();
}
#[unsafe(no_mangle)]
pub extern "C" fn __retarget_lock_release(_lock: Lock) {
libc_unlock();
}
#[unsafe(no_mangle)]
pub extern "C" fn __retarget_lock_release_recursive(_lock: Lock) {
libc_unlock();
}
#[unsafe(no_mangle)]
pub extern "C" fn exit(_status: i32) -> ! {
loop {}
}
#[unsafe(no_mangle)]
pub extern "C" fn _exit(_status: i32) -> ! {
loop {}
}
#[unsafe(no_mangle)]
pub extern "C" fn kill(_pid: i32, _sig: i32) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn sbrk(incr: isize) -> *mut u8 {
sbrk_impl(incr)
}
#[unsafe(no_mangle)]
pub extern "C" fn getpid() -> i32 {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn gettimeofday(
tp: *mut core::ffi::c_void,
_tzp: *mut core::ffi::c_void,
) -> i32 {
gettimeofday_impl(tp)
}
#[unsafe(no_mangle)]
pub extern "C" fn isatty(_fd: i32) -> i32 {
1
}
#[unsafe(no_mangle)]
pub extern "C" fn open(_path: *const u8, _flags: i32, _mode: i32) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn close(_fd: i32) -> i32 {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn read(_fd: i32, _buf: *mut u8, _count: usize) -> isize {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn write(_fd: i32, _buf: *const u8, count: usize) -> isize {
write_impl(count)
}
#[unsafe(no_mangle)]
pub extern "C" fn lseek(_fd: i32, _offset: isize, _whence: i32) -> isize {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn fstat(_fd: i32, _st: *mut core::ffi::c_void) -> i32 {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn stat(_path: *const u8, _st: *mut core::ffi::c_void) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn unlink(_path: *const u8) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn rename(_old: *const u8, _new: *const u8) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn access(_path: *const u8, _mode: i32) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn chmod(_path: *const u8, _mode: i32) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn chdir(_path: *const u8) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn rmdir(_path: *const u8) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn gethostname(_name: *mut u8, _len: usize) -> i32 {
-1
}
#[repr(C)]
pub struct Timespec {
tv_sec: i64,
tv_nsec: i64,
}
const CLOCK_MONOTONIC: i32 = 1;
#[unsafe(no_mangle)]
pub extern "C" fn clock_gettime(clock_id: i32, tp: *mut Timespec) -> i32 {
let (secs, nsecs) = if clock_id == CLOCK_MONOTONIC {
let micros = crate::kernel::monotonic_micros();
(micros / 1_000_000, (micros % 1_000_000) * 1000)
} else {
let (secs, micros) = crate::kernel::wall_clock_micros();
(secs as i64, (micros as i64) * 1000)
};
unsafe {
(*tp).tv_sec = secs;
(*tp).tv_nsec = nsecs;
}
0
}
// picolibc consults this for errno values it does not know itself.
#[unsafe(no_mangle)]
pub extern "C" fn _user_strerror(_errnum: i32) -> *const core::ffi::c_char {
ptr::null()
}
// No dynamic loader here. Gum calls dladdr() to describe an address and already
// falls back to printing it raw when the lookup fails.
#[unsafe(no_mangle)]
pub extern "C" fn dladdr(_address: *const core::ffi::c_void, _info: *mut core::ffi::c_void) -> i32 {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn dlopen(_file: *const u8, _mode: i32) -> *mut core::ffi::c_void {
ptr::null_mut()
}
#[unsafe(no_mangle)]
pub extern "C" fn dlsym(
_handle: *mut core::ffi::c_void,
_name: *const u8,
) -> *mut core::ffi::c_void {
ptr::null_mut()
}
#[unsafe(no_mangle)]
pub extern "C" fn dlclose(_handle: *mut core::ffi::c_void) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn dlerror() -> *mut core::ffi::c_char {
ptr::null_mut()
}
// picolibc declares the standard streams and leaves defining them to the
// application. The agent has no I/O — its logging goes to the kernel's own log —
// so nothing should ever reach for one of these.
#[repr(transparent)]
pub struct Stream(*mut core::ffi::c_void);
unsafe impl Sync for Stream {}
#[unsafe(no_mangle)]
pub static stdin: Stream = Stream(ptr::null_mut());
#[unsafe(no_mangle)]
pub static stdout: Stream = Stream(ptr::null_mut());
#[unsafe(no_mangle)]
pub static stderr: Stream = Stream(ptr::null_mut());
}
+244
View File
@@ -0,0 +1,244 @@
// Linux backend. Unlike XNU — where a remote stub injects the agent and patches
// resolved kernel addresses into .kernel_addrs — here the agent is loaded from
// the inside as a kernel module, so every primitive is an ordinary call into
// linux/frida-kmod.c. That shim is 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 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 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 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_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
}
#[derive(Debug, Clone)]
pub struct LoadedModule {
pub name: String,
pub version: String,
pub base: u64,
pub size: u64,
}
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 }
}
type FoundSymbolFunc =
unsafe extern "C" fn(name: *const c_char, address: u64, user_data: *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_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;
}
+10 -542
View File
@@ -1,544 +1,12 @@
#![no_main]
// The XNU flavour ships as a freestanding ELF executable: the host injects the
// 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]
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use core::alloc::{GlobalAlloc, Layout};
use core::ffi::{CStr, c_void};
use core::ptr;
use core::ptr::null_mut;
use core::sync::atomic::{AtomicU32, Ordering};
use bindings::{
GBytes, GCancellable, GError, GVariant, GVariantIter, GumScript, g_error_free, g_free,
g_main_context_default, g_main_context_iteration, g_memdup2, g_object_unref,
g_variant_check_format_string, g_variant_get, g_variant_get_child_value, g_variant_get_data,
g_variant_get_size, g_variant_get_string, g_variant_get_uint32, g_variant_get_uint64,
g_variant_iter_init, g_variant_iter_next, 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_unref, gchar, gpointer, gsize, gum_script_backend_create_sync,
gum_script_backend_obtain_qjs, gum_script_load_sync, gum_script_post,
gum_script_set_message_handler, gum_script_unload_sync,
};
use hostlink_virtio::Hostlink;
use symbols::SymbolTable;
mod glib;
mod gthread;
mod gum;
mod hostlink_virtio;
mod libc;
mod pac;
mod symbols;
mod xnu;
mod bindings {
#![allow(
dead_code,
improper_ctypes,
non_camel_case_types,
non_snake_case,
non_upper_case_globals,
unused_imports
)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum FridaCommand {
CreateScript = 1,
LoadScript = 2,
DestroyScript = 3,
PostScriptMessage = 4,
Reply = 128,
ScriptMessage = 129,
}
impl core::fmt::Display for FridaCommand {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
FridaCommand::CreateScript => write!(f, "CreateScript"),
FridaCommand::LoadScript => write!(f, "LoadScript"),
FridaCommand::DestroyScript => write!(f, "DestroyScript"),
FridaCommand::PostScriptMessage => write!(f, "PostScriptMessage"),
FridaCommand::Reply => write!(f, "Reply"),
FridaCommand::ScriptMessage => write!(f, "ScriptMessage"),
}
}
}
#[derive(Debug)]
struct HandlerResponse {
variant: *mut GVariant,
}
impl HandlerResponse {
fn success(variant: *mut GVariant) -> Self {
Self { variant }
}
fn success_empty() -> Self {
let variant = unsafe { g_variant_new_tuple(ptr::null(), 0) };
Self { variant }
}
fn error(message: &str) -> Self {
let mut c_message = String::from(message);
c_message.push('\0');
let error_variant = unsafe { g_variant_new_string(c_message.as_ptr()) };
Self {
variant: error_variant,
}
}
}
static mut CONFIG_DATA: &'static [u8] = &[];
pub static mut MODULE_INFO: Vec<ModuleInfo> = Vec::new();
pub static mut SYMBOL_TABLE: SymbolTable = SymbolTable::empty();
static mut TRANSPORT_DRIVER: *mut Hostlink = core::ptr::null_mut();
#[inline(always)]
fn transport_set(driver: Hostlink) {
unsafe {
let boxed = Box::into_raw(Box::new(driver));
TRANSPORT_DRIVER = boxed;
}
}
#[inline(always)]
fn transport_get_unchecked() -> &'static Hostlink {
unsafe {
debug_assert!(!TRANSPORT_DRIVER.is_null());
&*TRANSPORT_DRIVER
}
}
static mut SCRIPTS: BTreeMap<u32, *mut GumScript> = BTreeMap::new();
static NEXT_SCRIPT_ID: AtomicU32 = AtomicU32::new(1);
#[derive(Debug, Clone)]
pub struct ModuleInfo {
pub name: String,
pub version: String,
pub offset: u32,
pub size: u32,
pub start_func_offset: u32,
pub stop_func_offset: u32,
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn _start(config_data: *const u8, config_size: usize) {
unsafe {
CONFIG_DATA = core::slice::from_raw_parts(config_data, config_size);
xnu::kernel_thread_start(frida_agent_worker, 12345usize as *mut core::ffi::c_void);
}
}
unsafe extern "C" fn frida_agent_worker(_parameter: *mut core::ffi::c_void, _wait_result: i32) {
unsafe {
bindings::g_set_panic_handler(Some(frida_panic_handler), ptr::null_mut());
bindings::gum_init_embedded();
bindings::g_log_set_default_handler(Some(frida_log_handler), ptr::null_mut());
let (mmio, irq, kernel_base, module_info, symbol_table) =
parse_config(core::ptr::addr_of!(CONFIG_DATA).read());
xnu::set_kernel_base(kernel_base);
MODULE_INFO = module_info;
SYMBOL_TABLE = symbol_table;
transport_set(
Hostlink::init(
mmio,
irq,
Some(on_frame_from_host),
ptr::addr_of_mut!(glib::WAKEUP_TOKEN) as *const u8,
)
.unwrap(),
);
let main_context = g_main_context_default();
loop {
transport_get_unchecked().process();
g_main_context_iteration(main_context, 1);
}
}
}
fn on_frame_from_host(frame: &[u8]) {
if let Some(variant) = deserialize_message(&frame) {
process_incoming_message(variant);
unsafe {
g_variant_unref(variant);
}
}
}
unsafe fn parse_config(config: &[u8]) -> (u64, u32, u64, Vec<ModuleInfo>, SymbolTable) {
unsafe {
let type_string = c"(tuta(ssuuuu)ay)".as_ptr() as *const gchar;
let variant_type = g_variant_type_new(type_string);
let root_variant = g_variant_new_from_data(
variant_type,
config.as_ptr() as *const core::ffi::c_void,
config.len() as gsize,
1,
None,
ptr::null_mut(),
);
let hostlink_mmio_variant = g_variant_get_child_value(root_variant, 0);
let hostlink_mmio = g_variant_get_uint64(hostlink_mmio_variant);
let hostlink_irq_variant = g_variant_get_child_value(root_variant, 1);
let hostlink_irq = g_variant_get_uint32(hostlink_irq_variant);
let kernel_base_variant = g_variant_get_child_value(root_variant, 2);
let kernel_base = g_variant_get_uint64(kernel_base_variant);
xnu::set_kernel_base(kernel_base);
let module_info_variant = g_variant_get_child_value(root_variant, 3);
let mut iter: GVariantIter = core::mem::zeroed();
g_variant_iter_init(&mut iter as *mut GVariantIter, module_info_variant);
let mut module_info: Vec<ModuleInfo> = Vec::new();
let mut raw_name: *mut gchar = null_mut();
let mut raw_version: *mut gchar = null_mut();
let mut offset: u32 = 0;
let mut size: u32 = 0;
let mut start_func_offset: u32 = 0;
let mut stop_func_offset: u32 = 0;
while g_variant_iter_next(
&mut iter as *mut GVariantIter,
c"(&s&suuuu)".as_ptr(),
&mut raw_name,
&mut raw_version,
&mut offset,
&mut size,
&mut start_func_offset,
&mut stop_func_offset,
) != 0
{
module_info.push(ModuleInfo {
name: String::from(CStr::from_ptr(raw_name).to_str().unwrap()),
version: String::from(CStr::from_ptr(raw_version).to_str().unwrap()),
offset,
size,
start_func_offset,
stop_func_offset,
});
}
let symbol_array_variant = g_variant_get_child_value(root_variant, 4);
let symbol_data_ptr = g_variant_get_data(symbol_array_variant) as *const u8;
let symbol_data_size = g_variant_get_size(symbol_array_variant) as usize;
let symbol_table = SymbolTable::new(core::slice::from_raw_parts(
symbol_data_ptr,
symbol_data_size,
));
g_variant_unref(symbol_array_variant);
g_variant_unref(module_info_variant);
g_variant_unref(kernel_base_variant);
g_variant_unref(hostlink_irq_variant);
g_variant_unref(hostlink_mmio_variant);
g_variant_unref(root_variant);
g_variant_type_free(variant_type);
(
hostlink_mmio,
hostlink_irq,
kernel_base,
module_info,
symbol_table,
)
}
}
unsafe fn serialize_message(variant: *mut GVariant) -> Option<Vec<u8>> {
unsafe {
let size = g_variant_get_size(variant) as usize;
if size == 0 {
return None;
}
let data_ptr = g_variant_get_data(variant) as *const u8;
let mut result = Vec::with_capacity(size);
result.resize(size, 0);
core::ptr::copy_nonoverlapping(data_ptr, result.as_mut_ptr(), size);
Some(result)
}
}
fn deserialize_message(data: &[u8]) -> Option<*mut GVariant> {
unsafe {
if data.is_empty() {
return None;
}
let variant_type = g_variant_type_new(c"(yqv)".as_ptr());
let data_copy = g_memdup2(data.as_ptr() as *const c_void, data.len() as u64);
let variant = g_variant_new_from_data(
variant_type,
data_copy,
data.len() as u64,
0,
Some(g_free),
data_copy,
);
g_variant_type_free(variant_type);
if variant.is_null() {
None
} else {
Some(variant)
}
}
}
fn process_incoming_message(variant: *mut GVariant) {
{
let mut cmd_value: u8 = 0;
let mut request_id: u16 = 0;
let mut payload_variant: *mut GVariant = ptr::null_mut();
let cmd = unsafe {
g_variant_get(
variant,
c"(yqv)".as_ptr(),
&mut cmd_value,
&mut request_id,
&mut payload_variant,
);
core::mem::transmute::<u8, FridaCommand>(cmd_value)
};
let response = match cmd {
FridaCommand::CreateScript => handle_create_script(payload_variant),
FridaCommand::LoadScript => handle_load_script(payload_variant),
FridaCommand::DestroyScript => handle_destroy_script(payload_variant),
FridaCommand::PostScriptMessage => handle_post_script_message(payload_variant),
_ => HandlerResponse::error("Unknown command"),
};
send_command_reply(request_id, response);
unsafe { g_variant_unref(payload_variant) };
}
}
fn send_command_reply(request_id: u16, response: HandlerResponse) {
unsafe {
let message = g_variant_new(
c"(yqv)".as_ptr(),
FridaCommand::Reply as u8 as u32,
request_id as u32,
response.variant,
);
if let Some(serialized) = serialize_message(message) {
transport_get_unchecked().send(&serialized);
}
g_variant_unref(message);
}
}
fn handle_create_script(payload_variant: *mut GVariant) -> HandlerResponse {
unsafe {
if g_variant_check_format_string(payload_variant, c"s".as_ptr(), 0) == 0 {
return HandlerResponse::error("Invalid payload format: expected string");
}
let source = g_variant_get_string(payload_variant, core::ptr::null_mut());
let backend = gum_script_backend_obtain_qjs();
let cancellable: *mut GCancellable = ptr::null_mut();
let mut error: *mut GError = ptr::null_mut();
let script = gum_script_backend_create_sync(
backend,
c"agent.js".as_ptr(),
source,
ptr::null_mut(),
cancellable,
&mut error,
);
if !error.is_null() {
let error_msg = CStr::from_ptr((*error).message).to_str().unwrap();
let error_string = String::from(error_msg);
g_error_free(error);
return HandlerResponse::error(&error_string);
}
let script_id = NEXT_SCRIPT_ID.fetch_add(1, Ordering::Relaxed);
let script_id_ptr = Box::into_raw(Box::new(script_id));
gum_script_set_message_handler(
script,
Some(frida_message_handler),
script_id_ptr as *mut c_void,
None,
);
core::ptr::addr_of_mut!(SCRIPTS)
.as_mut()
.unwrap()
.insert(script_id, script);
HandlerResponse::success(g_variant_new_uint32(script_id))
}
}
unsafe extern "C" fn frida_message_handler(
message: *const gchar,
_data: *mut GBytes,
user_data: gpointer,
) {
unsafe {
let script_id = *(user_data as *const u32);
let message_variant = g_variant_new(
c"(yqv)".as_ptr(),
FridaCommand::ScriptMessage as u8 as u32,
0u32,
g_variant_new(c"(us)".as_ptr(), script_id, message),
);
if let Some(serialized) = serialize_message(message_variant) {
transport_get_unchecked().send(&serialized);
}
g_variant_unref(message_variant);
}
}
fn handle_load_script(payload_variant: *mut GVariant) -> HandlerResponse {
unsafe {
if g_variant_check_format_string(payload_variant, c"u".as_ptr(), 0) == 0 {
return HandlerResponse::error("Invalid payload format: expected uint32");
}
let script_id = g_variant_get_uint32(payload_variant);
let Some(script) = get_script_by_id(script_id) else {
return HandlerResponse::error(&format!("Script with ID {} not found", script_id));
};
gum_script_load_sync(script, ptr::null_mut());
HandlerResponse::success_empty()
}
}
fn handle_destroy_script(payload_variant: *mut GVariant) -> HandlerResponse {
unsafe {
if g_variant_check_format_string(payload_variant, c"u".as_ptr(), 0) == 0 {
return HandlerResponse::error("Invalid payload format: expected uint32");
}
let script_id = g_variant_get_uint32(payload_variant);
let scripts = core::ptr::addr_of_mut!(SCRIPTS).as_mut().unwrap();
let Some(script) = scripts.remove(&script_id) else {
return HandlerResponse::error(&format!("Script with ID {} not found", script_id));
};
gum_script_unload_sync(script, ptr::null_mut());
g_object_unref(script as *mut c_void);
HandlerResponse::success_empty()
}
}
fn handle_post_script_message(payload_variant: *mut GVariant) -> HandlerResponse {
unsafe {
if g_variant_check_format_string(payload_variant, c"(us)".as_ptr(), 0) == 0 {
return HandlerResponse::error("Invalid payload format: expected (uint32, string)");
}
let mut script_id: u32 = 0;
let mut message: *const gchar = ptr::null();
g_variant_get(
payload_variant,
c"(u&s)".as_ptr(),
&mut script_id,
&mut message,
);
let Some(script) = get_script_by_id(script_id) else {
return HandlerResponse::error(&format!("Script with ID {} not found", script_id));
};
gum_script_post(script, message, ptr::null_mut());
HandlerResponse::success_empty()
}
}
unsafe fn get_script_by_id(script_id: u32) -> Option<*mut GumScript> {
unsafe {
let scripts = core::ptr::addr_of_mut!(SCRIPTS).as_mut().unwrap();
scripts.get(&script_id).copied()
}
}
unsafe extern "C" fn frida_panic_handler(message: *const u8, _user_data: *mut core::ffi::c_void) {
let msg = unsafe { core::ffi::CStr::from_ptr(message).to_str().unwrap() };
panic!("[Frida] {}", msg);
}
unsafe extern "C" fn frida_log_handler(
_log_domain: *const core::ffi::c_char,
_log_level: i32,
message: *const core::ffi::c_char,
_user_data: *mut core::ffi::c_void,
) {
let msg = unsafe { core::ffi::CStr::from_ptr(message).to_str().unwrap() };
kprintln!("[Frida] {}", msg);
}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo<'_>) -> ! {
let mut s = format!("{}", info);
s.push('\0');
xnu::panic(s.as_str());
loop {}
}
pub struct XnuAllocator;
unsafe impl GlobalAlloc for XnuAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
xnu::kalloc(layout.size())
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
xnu::free(ptr, layout.size());
}
}
#[global_allocator]
static GLOBAL: XnuAllocator = XnuAllocator;
extern crate alloc;
#[macro_export]
macro_rules! kprintln {
($($arg:tt)*) => {{
use core::fmt::Write;
let mut buf = alloc::string::String::new();
write!(&mut buf, $($arg)*).unwrap();
buf.push('\n');
buf.push('\0');
crate::xnu::io_log(&buf)
}};
}
use frida_barebone_agent as _;
+1 -1
View File
@@ -1,7 +1,7 @@
use crate::bindings::{
GPatternSpec, g_pattern_spec_free, g_pattern_spec_match_string, g_pattern_spec_new,
};
use crate::xnu::get_kernel_base;
use crate::kernel::get_kernel_base;
use alloc::ffi::CString;
use core::cmp::Ordering;
use core::ffi::{CStr, c_char};
+922
View File
@@ -0,0 +1,922 @@
// The VMM side of the blob flavor. Reached the way XNU is: the host fills in a
// slot per service before calling _start, so nothing here is a link-time import.
//
// The signatures follow the VxD service documentation; none of them has been
// exercised against a running kernel yet.
use core::ffi::c_void;
use core::sync::atomic::{AtomicU32, Ordering};
use crate::kernel::ThreadEntry;
const PAGE_SIZE: u32 = 4096;
const PG_SYS: u32 = 1;
const PAGE_FLAGS_CODE: u32 = PAGE_FIXED | PAGE_LOCKED | PAGE_ZERO_INIT;
const PAGE_FIXED: u32 = 0x8;
const PAGE_LOCKED: u32 = 0x80;
const PAGE_ZERO_INIT: u32 = 0x1;
const PAGE_PRESENT: u32 = 0x1;
const PAGE_WRITEABLE: u32 = 0x2;
const GUM_PAGE_WRITE: u32 = 0x2;
const BLOCK_SVC_INTS: u32 = 1 << 0;
const DEBUG_CONSOLE_PORT: u16 = 0xe9;
pub fn log(msg: &str) {
for byte in msg.bytes() {
if byte == 0 {
break;
}
write_debug_byte(byte);
}
}
pub fn log_hex(value: u32) {
let mut shift = 32;
while shift != 0 {
shift -= 4;
let digit = ((value >> shift) & 0xf) as u8;
write_debug_byte(if digit < 10 { b'0' + digit } else { b'a' + digit - 10 });
}
write_debug_byte(b'\n');
}
fn write_debug_byte(byte: u8) {
unsafe {
core::arch::asm!("out dx, al", in("dx") DEBUG_CONSOLE_PORT, in("al") byte,
options(nomem, nostack, preserves_flags));
}
}
pub fn panic(msg: &str) {
log(msg);
unsafe {
fatal_error_handler(msg.as_ptr(), 0);
}
}
pub fn alloc(size: usize) -> *mut u8 {
unsafe { __HeapAllocate(size as u32, 0) }
}
pub fn free(ptr: *mut u8, _size: usize) {
unsafe {
__HeapFree(ptr, 0);
}
}
pub fn alloc_code(size: usize) -> *mut u8 {
let pages = size.div_ceil(PAGE_SIZE as usize) as u32;
unsafe { __PageAllocate(pages, PG_SYS, 0, 0, 0, 0, core::ptr::null_mut(), PAGE_FLAGS_CODE) }
}
pub fn free_code(ptr: *mut u8, _size: usize) {
unsafe {
__PageFree(ptr, 0);
}
}
pub fn spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> isize {
let stack = alloc_code(THREAD_STACK_SIZE);
if stack.is_null() {
return 0;
}
unsafe {
THREAD_ENTRY = Some(entry);
THREAD_PARAMETER = parameter;
vmm_create_thread(
segment_ss(),
stack as u32 + THREAD_STACK_SIZE as u32,
segment_cs(),
frida_win9x_thread_thunk as u32,
segment_ds(),
segment_es(),
0,
)
}
}
// VMM refuses to build a thread from the borrowed context the host enters us on, and only
// says so by never returning, so hand the work to a point where VMM is between jobs.
pub fn run_when_ready(action: fn()) {
unsafe {
READY_ACTION = Some(action);
schedule_global_event(frida_win9x_event_thunk);
}
}
#[unsafe(no_mangle)]
extern "C" fn frida_win9x_on_event() {
if let Some(action) = unsafe { READY_ACTION } {
action();
}
}
static mut READY_ACTION: Option<fn()> = None;
#[unsafe(no_mangle)]
extern "C" fn frida_win9x_thread_start() {
unsafe {
if let Some(entry) = THREAD_ENTRY {
entry(THREAD_PARAMETER, 0);
}
}
}
static mut THREAD_ENTRY: Option<ThreadEntry> = None;
static mut THREAD_PARAMETER: *mut c_void = core::ptr::null_mut();
const THREAD_STACK_SIZE: usize = 64 * 1024;
fn segment_cs() -> u32 {
let selector: u32;
unsafe { core::arch::asm!("mov {0:e}, cs", out(reg) selector, options(nomem, nostack, preserves_flags)) };
selector
}
fn segment_ss() -> u32 {
let selector: u32;
unsafe { core::arch::asm!("mov {0:e}, ss", out(reg) selector, options(nomem, nostack, preserves_flags)) };
selector
}
fn segment_ds() -> u32 {
let selector: u32;
unsafe { core::arch::asm!("mov {0:e}, ds", out(reg) selector, options(nomem, nostack, preserves_flags)) };
selector
}
fn segment_es() -> u32 {
let selector: u32;
unsafe { core::arch::asm!("mov {0:e}, es", out(reg) selector, options(nomem, nostack, preserves_flags)) };
selector
}
pub fn wait(token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut() -> bool) {
let semaphore = semaphore_for(token);
if check() {
return;
}
match timeout_us {
None => unsafe { wait_semaphore(semaphore, BLOCK_SVC_INTS) },
Some(us) => block_until_signalled_or_timed_out(semaphore, us),
}
}
fn block_until_signalled_or_timed_out(semaphore: u32, timeout_us: u64) {
let timeout_ms = (timeout_us / 1000).max(1) as u32;
let timeout = unsafe { set_global_time_out(timeout_ms, semaphore) };
unsafe {
wait_semaphore(semaphore, BLOCK_SVC_INTS);
}
if timeout != 0 {
unsafe {
cancel_time_out(timeout);
}
}
}
pub fn wake(token: *const u8) {
unsafe {
signal_semaphore(semaphore_for(token));
}
}
pub fn yield_now() {
unsafe {
_Release_Time_Slice();
}
}
pub fn monotonic_micros() -> i64 {
unsafe { _Get_System_Time() as i64 * 1000 }
}
pub fn wall_clock_micros() -> (u32, u32) {
let micros = monotonic_micros() as u64;
((micros / 1_000_000) as u32, (micros % 1_000_000) as u32)
}
pub fn current_thread_id() -> u64 {
unsafe { get_cur_thread_handle() as u64 }
}
pub fn protect(address: u64, size: usize, gum_prot: u32) -> bool {
let first_page = address / PAGE_SIZE as u64;
let pages = size.div_ceil(PAGE_SIZE as usize) as u32;
let mut set = PAGE_PRESENT;
if (gum_prot & GUM_PAGE_WRITE) != 0 {
set |= PAGE_WRITEABLE;
}
let clear = PAGE_WRITEABLE & !set;
unsafe { __PageModifyPermissions(first_page as u32, pages, !clear, set) != 0xffff_ffff }
}
pub fn map_io(phys_addr: u64, size: u64) -> *mut c_void {
let pages = (size as usize).div_ceil(PAGE_SIZE as usize) as u32;
unsafe { __MapPhysToLinear(phys_addr as u32, pages * PAGE_SIZE, 0) as *mut c_void }
}
pub fn virt_to_phys(vaddr: u64) -> u64 {
let mut entry: u32 = 0;
let page = (vaddr / PAGE_SIZE as u64) as u32;
unsafe {
__CopyPageTable(page, 1, &mut entry, 0);
}
((entry & !(PAGE_SIZE - 1)) as u64) | (vaddr & (PAGE_SIZE - 1) as u64)
}
pub fn install_interrupt_handler(
irq: u32,
target: *mut c_void,
handler: InterruptHandler,
refcon: *mut c_void,
) -> i32 {
unsafe {
HW_INT_HANDLER = Some(handler);
HW_INT_TARGET = target;
HW_INT_REFCON = refcon;
}
unsafe {
IRQ_DESCRIPTOR = VpicdIrqDescriptor {
irq_number: irq as u16,
options: VPICD_OPT_REF_DATA | VPICD_OPT_CAN_SHARE,
hw_int_proc: frida_win9x_hw_int_thunk,
virt_int_proc: 0,
eoi_proc: 0,
mask_change_proc: 0,
iret_proc: 0,
iret_time_out: VPICD_DEFAULT_IRET_TIME_OUT,
hw_int_ref: 0,
};
}
let handle = unsafe { vpicd_virtualize_irq(core::ptr::addr_of_mut!(IRQ_DESCRIPTOR)) };
if handle != 0 {
unsafe {
IRQ_HANDLE = handle;
vpicd_physically_unmask(handle);
}
}
if handle == 0 { -1 } else { 0 }
}
pub struct ProcessInfo {
pub id: u32,
pub path: *const u8,
}
// Win32 threads carry the process they belong to in VWIN32's per-thread block, so the
// process list is the deduplicated set of those, and the image path is where the command
// line starts.
pub fn enumerate_processes(found: &mut dyn FnMut(ProcessInfo)) {
let slot = unsafe { (0xc00211ccu32 as *const u32).read() };
let vm = unsafe { get_sys_vm_handle() };
let first = unsafe { get_initial_thread_handle(vm) };
let mut seen: [u32; 64] = [0; 64];
let mut count = 0usize;
let mut thread = first;
while thread != 0 && count < seen.len() {
if unsafe { (thread as *const u32).add(0x2c / 4).read() } == WIN32_THREAD {
let block = unsafe { (thread as *const u32).byte_add(slot as usize).read() };
let pdb = unsafe { (block as *const u32).add(1).read() };
if !seen[..count].contains(&pdb) {
seen[count] = pdb;
count += 1;
found(ProcessInfo {
id: pdb,
path: image_path(pdb),
});
}
}
let next = unsafe { get_next_thread_handle(thread) };
thread = if next == first { 0 } else { next };
}
}
fn image_path(pdb: u32) -> *const u8 {
let env_db = unsafe { (pdb as *const u32).byte_add(0x40).read() };
if env_db < ARENA_FLOOR {
return core::ptr::null();
}
let command_line = unsafe { (env_db as *const u32).byte_add(0x08).read() };
if command_line < ARENA_FLOOR || unsafe { (command_line as *const u8).add(1).read() } != b':' {
return core::ptr::null();
}
command_line as *const u8
}
const WIN32_THREAD: u32 = 0x2a;
const ARENA_FLOOR: u32 = 0x10000;
pub fn install_fault_reporter() {
unsafe {
FAULT_CHAIN[INVALID_OPCODE as usize] = hook_vmm_fault(INVALID_OPCODE, frida_win9x_fault_thunk_ud);
FAULT_CHAIN[GENERAL_PROTECTION as usize] =
hook_vmm_fault(GENERAL_PROTECTION, frida_win9x_fault_thunk_gp);
FAULT_CHAIN[PAGE_FAULT as usize] = hook_vmm_fault(PAGE_FAULT, frida_win9x_fault_thunk_pf);
}
}
#[unsafe(no_mangle)]
extern "C" fn frida_win9x_on_fault(fault: u32, frame: *mut u32) -> u32 {
let mut cpu_context = unsafe {
crate::bindings::_GumIA32CpuContext {
eip: frame.add(fault_frame_eip_slot(fault)).read(),
edi: frame.read(),
esi: frame.add(1).read(),
ebp: frame.add(2).read(),
esp: frame.add(3).read(),
ebx: frame.add(4).read(),
edx: frame.add(5).read(),
ecx: frame.add(6).read(),
eax: frame.add(7).read(),
xmm: core::ptr::null_mut(),
}
};
let handled = unsafe {
crate::bindings::gum_barebone_handle_exception(
exception_type_for(fault),
cpu_context.eip as *mut c_void,
faulting_address() as *mut c_void,
&mut cpu_context,
)
};
if handled == 0 {
return unsafe { FAULT_CHAIN[fault as usize] };
}
unsafe {
frida_win9x_resume = [
cpu_context.eip,
cpu_context.edi,
cpu_context.esi,
cpu_context.ebp,
cpu_context.esp,
cpu_context.ebx,
cpu_context.edx,
cpu_context.ecx,
cpu_context.eax,
];
}
0
}
#[unsafe(no_mangle)]
static mut frida_win9x_resume: [u32; 9] = [0; 9];
fn exception_type_for(fault: u32) -> crate::bindings::GumExceptionType {
use crate::bindings::*;
match fault {
DIVIDE_ERROR | OVERFLOW | BOUND_RANGE_EXCEEDED | X87_FLOATING_POINT
| SIMD_FLOATING_POINT => _GumExceptionType_GUM_EXCEPTION_ARITHMETIC,
DEBUG => _GumExceptionType_GUM_EXCEPTION_SINGLE_STEP,
BREAKPOINT => _GumExceptionType_GUM_EXCEPTION_BREAKPOINT,
INVALID_OPCODE => _GumExceptionType_GUM_EXCEPTION_ILLEGAL_INSTRUCTION,
STACK_SEGMENT_FAULT => _GumExceptionType_GUM_EXCEPTION_STACK_OVERFLOW,
GENERAL_PROTECTION | PAGE_FAULT => _GumExceptionType_GUM_EXCEPTION_ACCESS_VIOLATION,
_ => _GumExceptionType_GUM_EXCEPTION_SYSTEM,
}
}
const DIVIDE_ERROR: u32 = 0;
const DEBUG: u32 = 1;
const BREAKPOINT: u32 = 3;
const OVERFLOW: u32 = 4;
const BOUND_RANGE_EXCEEDED: u32 = 5;
const STACK_SEGMENT_FAULT: u32 = 12;
const X87_FLOATING_POINT: u32 = 16;
const SIMD_FLOATING_POINT: u32 = 19;
fn fault_frame_eip_slot(fault: u32) -> usize {
const PUSHED_REGISTERS: usize = 8;
const VECTOR: usize = 1;
let error_code = match fault {
8 | 10 | 11 | 12 | 13 | 14 | 17 | 21 => 1,
_ => 0,
};
PUSHED_REGISTERS + VECTOR + error_code
}
fn faulting_address() -> u32 {
let address: u32;
unsafe { core::arch::asm!("mov {0:e}, cr2", out(reg) address, options(nomem, nostack, preserves_flags)) };
address
}
static mut FAULT_CHAIN: [u32; 32] = [0; 32];
#[unsafe(no_mangle)]
static mut frida_win9x_fault_chain: u32 = 0;
const INVALID_OPCODE: u32 = 6;
const GENERAL_PROTECTION: u32 = 13;
const PAGE_FAULT: u32 = 14;
#[unsafe(no_mangle)]
extern "C" fn frida_win9x_on_hw_int(_ref_data: *mut c_void) {
unsafe {
if let Some(handler) = HW_INT_HANDLER {
handler(HW_INT_TARGET, HW_INT_REFCON, core::ptr::null_mut(), 0);
}
vpicd_phys_eoi(IRQ_HANDLE);
}
}
static mut IRQ_HANDLE: u32 = 0;
static mut IRQ_DESCRIPTOR: VpicdIrqDescriptor = VpicdIrqDescriptor {
irq_number: 0,
options: 0,
hw_int_proc: frida_win9x_hw_int_thunk,
virt_int_proc: 0,
eoi_proc: 0,
mask_change_proc: 0,
iret_proc: 0,
iret_time_out: 0,
hw_int_ref: 0,
};
static mut HW_INT_HANDLER: Option<InterruptHandler> = None;
static mut HW_INT_TARGET: *mut c_void = core::ptr::null_mut();
static mut HW_INT_REFCON: *mut c_void = core::ptr::null_mut();
#[repr(C)]
struct VpicdIrqDescriptor {
irq_number: u16,
options: u16,
hw_int_proc: unsafe extern "C" fn(),
virt_int_proc: u32,
eoi_proc: u32,
mask_change_proc: u32,
iret_proc: u32,
iret_time_out: u32,
hw_int_ref: u32,
}
const VPICD_OPT_CAN_SHARE: u16 = 0x02;
const VPICD_OPT_REF_DATA: u16 = 0x04;
const VPICD_DEFAULT_IRET_TIME_OUT: u32 = 500;
pub type InterruptHandler =
unsafe extern "C" fn(target: *mut c_void, refcon: *mut c_void, nub: *mut c_void, source: i32);
pub fn get_kernel_base() -> u64 {
KERNEL_BASE.load(Ordering::Relaxed) as u64
}
pub fn set_kernel_base(base: u64) {
KERNEL_BASE.store(base as u32, Ordering::Relaxed);
}
static KERNEL_BASE: AtomicU32 = AtomicU32::new(0);
// One semaphore per waited-on address, created on first use. VMM has no
// futex-alike, so the token has to be mapped onto something it does have.
fn semaphore_for(token: *const u8) -> u32 {
let slot = (token as usize / core::mem::align_of::<usize>()) % SEMAPHORES.len();
let existing = SEMAPHORES[slot].load(Ordering::Acquire);
if existing != 0 {
return existing;
}
let created = unsafe { create_semaphore(0) };
match SEMAPHORES[slot].compare_exchange(0, created, Ordering::AcqRel, Ordering::Acquire) {
Ok(_) => created,
Err(raced) => raced,
}
}
const NUM_SEMAPHORES: usize = 64;
static SEMAPHORES: [AtomicU32; NUM_SEMAPHORES] = [const { AtomicU32::new(0) }; NUM_SEMAPHORES];
unsafe extern "C" {
fn wait_semaphore(semaphore: u32, flags: u32);
fn signal_semaphore(semaphore: u32);
fn create_semaphore(token_count: u32) -> u32;
fn get_cur_thread_handle() -> u32;
fn fatal_error_handler(message: *const u8, flags: u32);
fn vpicd_virtualize_irq(descriptor: *mut VpicdIrqDescriptor) -> u32;
fn vpicd_physically_unmask(handle: u32);
fn vpicd_phys_eoi(handle: u32);
fn set_global_time_out(milliseconds: u32, semaphore: u32) -> u32;
fn cancel_time_out(timeout: u32);
fn hook_vmm_fault(fault: u32, handler: unsafe extern "C" fn()) -> u32;
fn frida_win9x_fault_thunk_ud();
fn frida_win9x_fault_thunk_gp();
fn frida_win9x_fault_thunk_pf();
fn frida_win9x_time_out_thunk();
fn get_cur_vm_handle() -> u32;
fn get_sys_vm_handle() -> u32;
fn schedule_global_event(callback: unsafe extern "C" fn()) -> u32;
fn frida_win9x_event_thunk();
fn get_next_vm_handle(vm: u32) -> u32;
fn get_initial_thread_handle(vm: u32) -> u32;
fn get_next_thread_handle(thread: u32) -> u32;
fn frida_win9x_hw_int_thunk();
fn frida_win9x_thread_thunk();
fn vmm_create_thread(ss: u32, esp: u32, cs: u32, eip: u32, ds: u32, es: u32,
ref_data: u32) -> isize;
}
core::arch::global_asm!(
r#"
.intel_syntax noprefix
.global wait_semaphore
wait_semaphore:
push ebp
mov ebp, esp
push ebx
push esi
push edi
mov eax, [ebp + 8]
mov ecx, [ebp + 12]
call dword ptr [_Wait_Semaphore]
pop edi
pop esi
pop ebx
pop ebp
ret
.global signal_semaphore
signal_semaphore:
push ebp
mov ebp, esp
push ebx
push esi
push edi
mov eax, [ebp + 8]
call dword ptr [_Signal_Semaphore]
pop edi
pop esi
pop ebx
pop ebp
ret
.global create_semaphore
create_semaphore:
push ebp
mov ebp, esp
push ebx
push esi
push edi
mov ecx, [ebp + 8]
call dword ptr [_Create_Semaphore]
cmc
sbb ecx, ecx
and eax, ecx
pop edi
pop esi
pop ebx
pop ebp
ret
.global get_cur_thread_handle
get_cur_thread_handle:
push ebp
mov ebp, esp
push ebx
push esi
push edi
call dword ptr [_Get_Cur_Thread_Handle]
mov eax, edi
pop edi
pop esi
pop ebx
pop ebp
ret
.global fatal_error_handler
fatal_error_handler:
push ebp
mov ebp, esp
push ebx
push esi
push edi
mov esi, [ebp + 8]
mov eax, [ebp + 12]
call dword ptr [_Fatal_Error_Handler]
pop edi
pop esi
pop ebx
pop ebp
ret
.global schedule_global_event
schedule_global_event:
push ebp
mov ebp, esp
push ebx
push esi
push edi
mov esi, [ebp + 8]
call dword ptr [_Schedule_Global_Event]
mov eax, ebx
pop edi
pop esi
pop ebx
pop ebp
ret
.global frida_win9x_event_thunk
frida_win9x_event_thunk:
pushad
call frida_win9x_on_event
popad
ret
.global get_sys_vm_handle
get_sys_vm_handle:
push ebx
call dword ptr [_Get_Sys_VM_Handle]
mov eax, ebx
pop ebx
ret
.global get_next_vm_handle
get_next_vm_handle:
push ebp
mov ebp, esp
push ebx
mov ebx, [ebp + 8]
call dword ptr [_Get_Next_VM_Handle]
mov eax, ebx
pop ebx
pop ebp
ret
.global get_initial_thread_handle
get_initial_thread_handle:
push ebp
mov ebp, esp
push ebx
push edi
mov ebx, [ebp + 8]
call dword ptr [_Get_Initial_Thread_Handle]
mov eax, edi
pop edi
pop ebx
pop ebp
ret
.global get_next_thread_handle
get_next_thread_handle:
push ebp
mov ebp, esp
push ebx
push edi
mov edi, [ebp + 8]
call dword ptr [_Get_Next_Thread_Handle]
mov eax, edi
pop edi
pop ebx
pop ebp
ret
.global get_cur_vm_handle
get_cur_vm_handle:
push ebx
call dword ptr [_Get_Cur_VM_Handle]
mov eax, ebx
pop ebx
ret
.global hook_vmm_fault
hook_vmm_fault:
push ebp
mov ebp, esp
push ebx
push esi
push edi
mov eax, [ebp + 8]
mov esi, [ebp + 12]
call dword ptr [_Hook_VMM_Fault]
mov eax, esi
pop edi
pop esi
pop ebx
pop ebp
ret
.global frida_win9x_fault_thunk_ud
frida_win9x_fault_thunk_ud:
push 6
jmp frida_win9x_fault_common
.global frida_win9x_fault_thunk_gp
frida_win9x_fault_thunk_gp:
push 13
jmp frida_win9x_fault_common
.global frida_win9x_fault_thunk_pf
frida_win9x_fault_thunk_pf:
push 14
jmp frida_win9x_fault_common
frida_win9x_fault_common:
pushad
mov eax, [esp + 32]
push esp
push eax
call frida_win9x_on_fault
add esp, 8
mov [frida_win9x_fault_chain], eax
popad
add esp, 4
cmp dword ptr [frida_win9x_fault_chain], 0
je frida_win9x_fault_resume
jmp dword ptr [frida_win9x_fault_chain]
frida_win9x_fault_resume:
mov esp, [frida_win9x_resume + 16]
mov edi, [frida_win9x_resume + 4]
mov esi, [frida_win9x_resume + 8]
mov ebp, [frida_win9x_resume + 12]
mov ebx, [frida_win9x_resume + 20]
mov edx, [frida_win9x_resume + 24]
mov ecx, [frida_win9x_resume + 28]
mov eax, [frida_win9x_resume + 32]
push dword ptr [frida_win9x_resume]
ret
.global set_global_time_out
set_global_time_out:
push ebp
mov ebp, esp
push ebx
push esi
push edi
mov eax, [ebp + 8]
mov edx, [ebp + 12]
lea esi, [frida_win9x_time_out_thunk]
call dword ptr [_Set_Global_Time_Out]
mov eax, esi
pop edi
pop esi
pop ebx
pop ebp
ret
.global cancel_time_out
cancel_time_out:
push ebp
mov ebp, esp
push ebx
push esi
push edi
mov esi, [ebp + 8]
call dword ptr [_Cancel_Time_Out]
pop edi
pop esi
pop ebx
pop ebp
ret
.global frida_win9x_time_out_thunk
frida_win9x_time_out_thunk:
pushad
mov eax, edx
call dword ptr [_Signal_Semaphore]
popad
ret
.global vpicd_phys_eoi
vpicd_phys_eoi:
push ebp
mov ebp, esp
push ebx
push esi
push edi
mov eax, [ebp + 8]
call dword ptr [_VPICD_Phys_EOI]
pop edi
pop esi
pop ebx
pop ebp
ret
.global vpicd_physically_unmask
vpicd_physically_unmask:
push ebp
mov ebp, esp
push ebx
push esi
push edi
mov eax, [ebp + 8]
call dword ptr [_VPICD_Physically_Unmask]
pop edi
pop esi
pop ebx
pop ebp
ret
.global vpicd_virtualize_irq
vpicd_virtualize_irq:
push ebp
mov ebp, esp
push ebx
push esi
push edi
call dword ptr [_Get_Cur_VM_Handle]
mov edi, [ebp + 8]
call dword ptr [_VPICD_Virtualize_IRQ]
cmc
sbb ecx, ecx
and eax, ecx
pop edi
pop esi
pop ebx
pop ebp
ret
.global vmm_create_thread
vmm_create_thread:
push ebp
mov ebp, esp
push ebx
push esi
push edi
push dword ptr [ebp + 32]
push dword ptr [ebp + 28]
push dword ptr [ebp + 24]
push dword ptr [ebp + 20]
push dword ptr [ebp + 16]
push dword ptr [ebp + 12]
push dword ptr [ebp + 8]
call dword ptr [_VMMCreateThread]
add esp, 28
pop edi
pop esi
pop ebx
pop ebp
ret
.global frida_win9x_thread_thunk
frida_win9x_thread_thunk:
call frida_win9x_thread_start
1:
jmp 1b
.global frida_win9x_hw_int_thunk
frida_win9x_hw_int_thunk:
pushad
push edx
call frida_win9x_on_hw_int
add esp, 4
popad
clc
ret
"#
);
unsafe extern "C" {
static _Get_Cur_VM_Handle: unsafe extern "C" fn();
static _Get_Sys_VM_Handle: unsafe extern "C" fn();
static _Schedule_Global_Event: unsafe extern "C" fn();
static _Create_Semaphore: unsafe extern "C" fn();
static _Wait_Semaphore: unsafe extern "C" fn();
static _Signal_Semaphore: unsafe extern "C" fn();
static _Get_Next_VM_Handle: unsafe extern "C" fn();
static _Release_Time_Slice: unsafe extern "C" fn();
static _Set_Global_Time_Out: unsafe extern "C" fn();
static _Cancel_Time_Out: unsafe extern "C" fn();
static _Get_System_Time: unsafe extern "C" fn() -> u32;
static __HeapAllocate: unsafe extern "C" fn(u32, u32) -> *mut u8;
static __HeapFree: unsafe extern "C" fn(*mut u8, u32);
static __PageAllocate:
unsafe extern "C" fn(u32, u32, u32, u32, u32, u32, *mut c_void, u32) -> *mut u8;
static __PageFree: unsafe extern "C" fn(*mut u8, u32);
static __CopyPageTable: unsafe extern "C" fn(u32, u32, *mut u32, u32) -> u32;
static __MapPhysToLinear: unsafe extern "C" fn(u32, u32, u32) -> u32;
static _Hook_VMM_Fault: unsafe extern "C" fn();
static _Fatal_Error_Handler: unsafe extern "C" fn();
static _VMMCreateThread: unsafe extern "C" fn();
static _Get_Cur_Thread_Handle: unsafe extern "C" fn();
static _Get_Initial_Thread_Handle: unsafe extern "C" fn();
static _Get_Next_Thread_Handle: unsafe extern "C" fn();
static __Debug_Printf_Service: unsafe extern "C" fn(*const u8, ...);
static __ContextSwitch: unsafe extern "C" fn(u32);
static __PageModifyPermissions: unsafe extern "C" fn(u32, u32, u32, u32) -> u32;
static __GetCurrentContext: unsafe extern "C" fn() -> u32;
static _VPICD_Virtualize_IRQ: unsafe extern "C" fn();
static _VPICD_Phys_EOI: unsafe extern "C" fn();
static _VPICD_Physically_Unmask: unsafe extern "C" fn();
}
Loaded 100 of 297 files, more files were not shown because too many files have changed in this diff. Show more