Compare commits

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

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

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

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

Guest code that relies on alignment then faults, e.g. musl's fstatat on
x86_64 Alpine, which made every lstat() from an injected agent crash.
2026-11-11 12:43:13 +01:00
Ole André Vadla Ravnås 588f79c5ec fruity: Install idund beside other assets
A shared build loads its assets from disk, so idund was neither
embedded nor installed -- installs skip subprojects -- and idun's guess
at its location next to the library found nothing. Install it into
the plugins directory and point the daemon locator there.
2026-11-11 11:43:22 +01:00
Ole André Vadla Ravnås 588ac3b3de subprojects: Bump outdated 2026-11-09 22:03:43 +01:00
Ole André Vadla Ravnås d0615e7574 submodules: Bump releng 2026-11-09 22:03:42 +01:00
Kaz Wolfe 1b169dec1c exit-monitor: Pass function sizes to Interceptor
On QNX 6.5 ARM, libc's _exit() is a lone BL into __exit(), and the
Interceptor cannot tell where it ends. Look up the symbol sizes and pass
them as redirect_space_hint so the redirect stays within the function.
2026-11-09 21:59:38 +01:00
Kaz Wolfe 0e61ae2762 qnx: Set PC when emitting the ARM payload
The writer assumed the code would run where it was generated, so calls
to libc functions within BL range of the local buffer were encoded with
offsets that are wrong in the target process.
2026-11-09 21:59:38 +01:00
Kaz Wolfe 6a445d9dea qnx: Add x86 support to the injector
Mirror the ARM payload and remote call using cdecl.
2026-11-09 20:34:01 +01:00
Ole André Vadla Ravnås 2a41a960fe subprojects: Prepare for release 2026-11-07 15:45:32 +01:00
Ole André Vadla Ravnås 25bd49299a subprojects: Bump outdated 2026-11-07 15:44:13 +01:00
tubibibo c84323b6ac linux: Don't pass our stdin to crash logcat
Under `adb shell`, frida-server's stdin and stdout are the same socket.
The crash monitor's logcat inherited it, so when frida-server died
abnormally, logcat kept it open and `adb shell` never saw EOF.

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

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

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

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

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

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

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

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

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

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

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

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

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

Leave the agent mapped when the process is already exiting.

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

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

See frida/frida-node#121.
2026-10-30 20:40:24 +01:00
93 changed files with 4902 additions and 3746 deletions

No files matched your search

+19
View File
@@ -0,0 +1,19 @@
name: Set up LLVM
description: Install the LLVM toolchain and put its tools on the path
inputs:
version:
required: false
default: "21"
description: The LLVM major version to install
runs:
using: composite
steps:
- name: Install LLVM
shell: bash
run: |
curl -sSL https://apt.llvm.org/llvm.sh -o llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh $VERSION
echo "/usr/lib/llvm-$VERSION/bin" >> $GITHUB_PATH
env:
VERSION: ${{ inputs.version }}
+245
View File
@@ -0,0 +1,245 @@
name: Barebone agent
on:
push:
paths:
- 'src/barebone/agent/**'
- '.github/workflows/barebone-agent.yml'
pull_request:
branches:
- main
paths:
- 'src/barebone/agent/**'
- '.github/workflows/barebone-agent.yml'
workflow_dispatch:
jobs:
gum-none:
strategy:
matrix:
machine:
- none-x86-softfloat_pic
- none-x86_64-softfloat_pic
- none-x86_64-softfloat_pic_msabi
- none-arm-softfloat_pic
- none-arm64-softfloat_pic
fail-fast: false
runs-on: ubuntu-latest
env:
FRIDA_DEPS: ${{ github.workspace }}/deps
steps:
- name: Check out repo
uses: actions/checkout@v6
with:
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: 24
- name: Set up C toolchain
uses: ./.github/actions/setup-llvm
- name: Grab frida-gum
run: |
python releng/meson/meson.py subprojects download frida-gum
git -C subprojects/frida-gum submodule update --init --recursive --depth 1
- name: Configure Gum
working-directory: subprojects/frida-gum
run: >-
./configure
--host=${{ matrix.machine }}
--enable-gumjs
--with-devkits=gumjs
--with-devkit-symbol-scope=original
env:
CC: clang
CXX: clang++
- name: Compile
working-directory: subprojects/frida-gum
run: make
- name: Upload GumJS devkit
uses: actions/upload-artifact@v7
with:
name: frida-gumjs-devkit-${{ matrix.machine }}
path: subprojects/frida-gum/build/bindings/gumjs/devkit/
- name: Upload SDK
uses: actions/upload-artifact@v7
with:
name: sdk-${{ matrix.machine }}
path: ${{ env.FRIDA_DEPS }}/sdk-${{ matrix.machine }}/
gum-macos-kernel:
runs-on: macos-latest
env:
FRIDA_DEPS: ${{ github.workspace }}/deps
steps:
- name: Check out repo
uses: actions/checkout@v6
with:
submodules: recursive
- name: Grab frida-gum
run: |
python3 releng/meson/meson.py subprojects download frida-gum
git -C subprojects/frida-gum submodule update --init --recursive --depth 1
- name: Configure Gum
working-directory: subprojects/frida-gum
run: >-
./configure
--host=macos-arm64e-kernel
--enable-gumjs
--with-devkits=gumjs
--with-devkit-symbol-scope=original
- name: Compile
working-directory: subprojects/frida-gum
run: make
- name: Upload GumJS devkit
uses: actions/upload-artifact@v7
with:
name: frida-gumjs-devkit-macos-arm64e-kernel
path: subprojects/frida-gum/build/bindings/gumjs/devkit/
- name: Upload SDK
uses: actions/upload-artifact@v7
with:
name: sdk-macos-arm64e-kernel
path: ${{ env.FRIDA_DEPS }}/sdk-macos-arm64e-kernel/
blob:
needs: gum-none
strategy:
matrix:
include:
- flavor: win9x
feature: win9x
arch: x86
machine: none-x86-softfloat_pic
rust_target: i686-unknown-none-pic
- flavor: winnt
feature: winnt
arch: x86
machine: none-x86-softfloat_pic
rust_target: i686-unknown-none-pic
- flavor: winnt
feature: winnt
arch: x86_64
machine: none-x86_64-softfloat_pic_msabi
rust_target: x86_64-unknown-none-pic
- flavor: winnt
feature: winnt
arch: arm64
machine: none-arm64-softfloat_pic
rust_target: aarch64-unknown-none-pic
- flavor: xnu
feature: xnu
arch: arm64
machine: none-arm64-softfloat_pic
rust_target: aarch64-unknown-none-pic
- flavor: linux
feature: linux-injected
arch: x86
machine: none-x86-softfloat_pic
rust_target: i686-unknown-none-pic
- flavor: linux
feature: linux-injected
arch: x86_64
machine: none-x86_64-softfloat_pic
rust_target: x86_64-unknown-none-pic
- flavor: linux
feature: linux-injected
arch: arm
machine: none-arm-softfloat_pic
rust_target: armv7a-unknown-none-pic
- flavor: linux
feature: linux-injected
arch: arm64
machine: none-arm64-softfloat_pic
rust_target: aarch64-unknown-none-pic
fail-fast: false
runs-on: ubuntu-latest
env:
SDK: ${{ github.workspace }}/sdk-${{ matrix.machine }}
GUMJS_DEVKIT_DIR: ${{ github.workspace }}/gumjs-devkit
steps:
- name: Check out repo
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
- name: Detect version
run: |
version=$(releng/frida_version.py)
test -n "$version"
echo "FRIDA_VERSION=$version" >> $GITHUB_ENV
- name: Set up C toolchain
uses: ./.github/actions/setup-llvm
- name: Set up Rust
run: rustup component add rust-src
- name: Download SDK
uses: actions/download-artifact@v8
with:
name: sdk-${{ matrix.machine }}
path: ${{ env.SDK }}
- name: Download GumJS devkit
uses: actions/download-artifact@v8
with:
name: frida-gumjs-devkit-${{ matrix.machine }}
path: ${{ env.GUMJS_DEVKIT_DIR }}
- name: Build agent
working-directory: src/barebone/agent
run: >-
cargo build --release
--features ${{ matrix.feature }}
--target ./${{ matrix.rust_target }}.json -Z json-target-spec
-Z build-std=core,alloc,compiler_builtins
--target-dir target-${{ matrix.flavor }}
env:
RUSTC_BOOTSTRAP: 1
CC: >-
clang
-I${{ env.SDK }}/include
-I${{ env.SDK }}/include/glib-2.0
-I${{ env.SDK }}/lib/glib-2.0/include
-I${{ env.SDK }}/include/capstone
-I${{ env.SDK }}/include/json-glib-1.0
RUSTFLAGS: -L native=${{ env.SDK }}/lib
- name: Upload agent
uses: actions/upload-artifact@v7
with:
name: frida-barebone-agent-${{ matrix.flavor }}-${{ matrix.arch }}
path: src/barebone/agent/target-${{ matrix.flavor }}/${{ matrix.rust_target }}/release/frida-barebone-agent
kext:
needs: gum-macos-kernel
runs-on: macos-latest
env:
FRIDA_SDK: ${{ github.workspace }}/sdk-macos-arm64e-kernel
GUMJS_DEVKIT_DIR: ${{ github.workspace }}/gumjs-devkit
steps:
- name: Check out repo
uses: actions/checkout@v6
with:
submodules: recursive
fetch-depth: 0
- name: Detect version
run: |
version=$(releng/frida_version.py)
test -n "$version"
echo "FRIDA_VERSION=$version" >> $GITHUB_ENV
- name: Set up Rust
run: rustup component add rust-src
- name: Download SDK
uses: actions/download-artifact@v8
with:
name: sdk-macos-arm64e-kernel
path: ${{ env.FRIDA_SDK }}
- name: Download GumJS devkit
uses: actions/download-artifact@v8
with:
name: frida-gumjs-devkit-macos-arm64e-kernel
path: ${{ env.GUMJS_DEVKIT_DIR }}
- name: Build agent
working-directory: src/barebone/agent/macos
run: make
- name: Upload agent
uses: actions/upload-artifact@v7
with:
name: frida-barebone-agent-xnu-kext-arm64e
path: src/barebone/agent/macos/frida-agent.kext
+8 -1
View File
@@ -2,9 +2,13 @@ name: CI
on:
push:
paths-ignore:
- 'src/barebone/agent/**'
pull_request:
branches:
- main
paths-ignore:
- 'src/barebone/agent/**'
workflow_dispatch:
inputs:
tag:
@@ -451,7 +455,10 @@ jobs:
cd build
cp -a tests/frida-tests tests/labrats lib/agent/frida-agent.so /tmp/pkg/
tar -C /tmp/pkg -cf /tmp/runner.tar .
/opt/sabrelite/run.sh /tmp/runner.tar /opt/frida/frida-tests
/opt/sabrelite/run.sh /tmp/runner.tar \
"cp /opt/frida/frida-tests /opt/frida/frida-agent.so /tmp/ && \
ln -sP /opt/frida/labrats /tmp/labrats && \
/tmp/frida-tests"
# This job is used to check that the Linux helpers are built correctly for all
# supported architectures. We do this because the binary artifacts are checked
+1 -1
View File
@@ -6,7 +6,7 @@
/src/barebone/agent/linux/*.mod
/src/barebone/agent/linux/*.mod.c
/src/barebone/agent/linux/*.o
/src/barebone/agent/linux/*.o.bin
/src/barebone/agent/linux/frida-agent.elf
/src/barebone/agent/linux/.*.cmd
/src/barebone/agent/linux/Module.symvers
/src/barebone/agent/linux/modules.order
+1 -1
View File
@@ -43,7 +43,7 @@ platform-dependent (e.g. `linux-x86_64`).
## Build Requirements
- **Vala compiler**: Must be the Frida-optimized fork from
https://github.com/frida/vala (checks for `-frida` suffix
https://github.com/frida-forks/vala (checks for `-frida` suffix
in version)
- **C/C++ standards**: gnu99/c99 for C, c++17 for C++
- **Go >= 1.24**: Required for compiler backend
+8 -27
View File
@@ -510,9 +510,7 @@ def compile(privdir: Path, state: State):
if not (workdir / "build.ninja").exists():
if options is None:
options = load_meson_options(top_builddir, state.role, set(subprojects.keys()))
version_opt = next((opt for opt in options if opt.startswith("-Dfrida_version=")), None)
if version_opt is None:
options += [f"-Dfrida_version={state.frida_version}"]
options += [f"-D:frida_version={state.frida_version}"]
host_machine = MachineSpec(state.host_os, group.arch, state.host_config, group.triplet)
@@ -525,7 +523,7 @@ def compile(privdir: Path, state: State):
forwarded_options = [o for o in options if not is_stripped_compat_option(o)]
forwarded_options += ["-Ddefault_library=static"]
else:
forwarded_options = [o for o in options if not is_overridden_compat_option(o)]
forwarded_options = list(options)
if state.apple_tls == "enabled":
forwarded_options += ["-Dapple_tls=disabled"]
@@ -576,17 +574,10 @@ def compile(privdir: Path, state: State):
def is_stripped_compat_option(o: str) -> bool:
if is_overridden_compat_option(o):
return True
name = o.split("=", 1)[0].removeprefix("-D")
return name in STRIPPED_COMPAT_OPTIONS or name.endswith(":default_library")
def is_overridden_compat_option(o: str) -> bool:
name = o.split("=", 1)[0].removeprefix("-D")
return name in OVERRIDDEN_COMPAT_OPTIONS
def load_meson_options(top_builddir: Path,
role: Role,
subprojects: set[str]) -> Sequence[str]:
@@ -612,13 +603,14 @@ def option_should_be_forwarded(k: "OptionKey",
from mesonbuild import options
from mesonbuild.mesonlib import MachineChoice
subproject = k.subproject or ""
our_project_id = "frida-core" if role == "subproject" else ""
is_for_us = k.subproject == our_project_id
is_for_child = k.subproject in subprojects
is_for_us = subproject == our_project_id
is_for_child = subproject in subprojects
if optstore.is_project_option(k):
if is_for_us:
if k.name == "compiler_backend":
if k.name in {"compiler_backend", "assets"}:
return False
tokens = k.name.split("_")
if tokens[0] in {"helper", "agent"} and tokens[-1] in {"modern", "legacy"}:
@@ -639,9 +631,9 @@ def option_should_be_forwarded(k: "OptionKey",
return False
if not str(v.value):
return False
if not is_for_child and role == "subproject" and k.subproject in {"", our_project_id}:
if not is_for_child and role == "subproject" and subproject in {"", our_project_id}:
if not options.BUILTIN_OPTIONS[k.evolve(subproject=None, machine=MachineChoice.HOST)].yielding:
return k.subproject == our_project_id
return subproject == our_project_id
return True
if k.get_module_prefix() == "python":
@@ -798,17 +790,6 @@ STRIPPED_COMPAT_OPTIONS = {
"frida-gum:inspector",
}
OVERRIDDEN_COMPAT_OPTIONS = {
"compiler_backend",
"assets",
"helper_modern",
"helper_legacy",
"agent_modern",
"agent_legacy",
"agent_emulated_modern",
"agent_emulated_legacy",
}
HELPER_TARGET = "frida-helper"
HELPER_FILE_WINDOWS = Path("src") / "frida-helper.exe"
HELPER_FILE_UNIX = Path("src") / "frida-helper"
+250
View File
@@ -103,6 +103,9 @@ namespace Frida.Agent {
private SpawnMonitor? spawn_monitor;
private ThreadSuspendMonitor? thread_suspend_monitor;
private UnwindSitter? unwind_sitter;
#if ANDROID && (ARM || ARM64)
private NativeCloakMirror? native_cloak_mirror;
#endif
private delegate void CompletionNotify ();
@@ -350,6 +353,9 @@ namespace Frida.Agent {
bool enable_unwind_broker = true;
bool enable_exit_monitor = true;
bool enable_thread_suspend_monitor = true;
#if ANDROID && (ARM || ARM64)
bool in_emulated_realm = false;
#endif
foreach (unowned string option in tokens[1:]) {
if (option == "eternal")
ensure_eternalized ();
@@ -365,6 +371,10 @@ namespace Frida.Agent {
enable_exit_monitor = false;
else if (option == "thread-suspend-monitor:off")
enable_thread_suspend_monitor = false;
#if ANDROID && (ARM || ARM64)
else if (option == "realm:emulated")
in_emulated_realm = true;
#endif
}
if (exceptor_mode != FULL)
@@ -374,6 +384,10 @@ namespace Frida.Agent {
try {
yield setup_connection_with_transport_uri (transport_uri);
#if ANDROID && (ARM || ARM64)
if (in_emulated_realm)
yield connect_to_native_realm ();
#endif
} catch (Error e) {
start_error = e;
main_loop.quit ();
@@ -406,6 +420,23 @@ namespace Frida.Agent {
padder = null;
}
#if ANDROID && (ARM || ARM64)
private async void connect_to_native_realm () throws Error {
try {
NativeCloak cloak = yield connection.get_proxy (null, ObjectPath.NATIVE_CLOAK, DO_NOT_LOAD_PROPERTIES,
null);
native_cloak_mirror = new NativeCloakMirror (cloak);
native_cloak_mirror.activate ();
NativeThreadRegistry registry = yield connection.get_proxy (null, ObjectPath.NATIVE_THREAD_REGISTRY,
DO_NOT_LOAD_PROPERTIES, null);
Gum.ThreadRegistry.set_provider (new NativeThreadProvider (registry));
} catch (GLib.Error e) {
throw new Error.TRANSPORT ("%s", e.message);
}
}
#endif
private static bool can_allocate_code () {
size_t page_size = Gum.query_page_size ();
@@ -1194,6 +1225,8 @@ namespace Frida.Agent {
unwind_sitter = null;
interceptor.end_transaction ();
Gum.shutdown ();
}
private void ensure_eternalized () {
@@ -1476,6 +1509,8 @@ namespace Frida.Agent {
private Socket? emulated_socket;
private BridgeState? emulated_bridge_state;
private Thread<void>? emulated_worker;
private NativeCloakService? native_cloak;
private NativeThreadRegistryService? native_thread_registry;
private async AgentSessionProvider? try_get_emulated_provider (Cancellable? cancellable) throws IOError {
if (get_emulated_request == null)
@@ -1515,6 +1550,8 @@ namespace Frida.Agent {
emulated_agent = nb_api.load_library_ext (emulated_agent_path, RTLD_LAZY, classloader_namespace);
} else {
emulated_agent = nb_api.load_library (emulated_agent_path, RTLD_LAZY);
if (emulated_agent == null && nb_api.load_library_ext != null)
emulated_agent = nb_api.load_library_ext (emulated_agent_path, RTLD_LAZY, null);
}
if (emulated_agent == null)
throw new Error.NOT_SUPPORTED ("Process is not using emulation");
@@ -1554,6 +1591,7 @@ namespace Frida.Agent {
* code being discarded, which seems like an edge-case the translator doesn't handle.
*/
parameters.append ("|exceptor:off|exit-monitor:off");
parameters.append ("|realm:emulated");
emulated_bridge_state = new BridgeState (parameters.str);
@@ -1562,6 +1600,13 @@ namespace Frida.Agent {
var connection = yield new DBusConnection (stream, ServerGuid.HOST_SESSION_SERVICE,
AUTHENTICATION_SERVER | AUTHENTICATION_ALLOW_ANONYMOUS, null, cancellable);
native_cloak = new NativeCloakService ();
connection.register_object (ObjectPath.NATIVE_CLOAK, (NativeCloak) native_cloak);
native_thread_registry = new NativeThreadRegistryService ();
connection.register_object (ObjectPath.NATIVE_THREAD_REGISTRY,
(NativeThreadRegistry) native_thread_registry);
AgentSessionProvider provider = yield connection.get_proxy (null, ObjectPath.AGENT_SESSION_PROVIDER,
DO_NOT_LOAD_PROPERTIES, cancellable);
@@ -1609,6 +1654,16 @@ namespace Frida.Agent {
emulated_worker = null;
}
if (native_thread_registry != null) {
native_thread_registry.close ();
native_thread_registry = null;
}
if (native_cloak != null) {
native_cloak.close ();
native_cloak = null;
}
emulated_socket = null;
if (emulated_agent != null) {
@@ -1769,6 +1824,201 @@ namespace Frida.Agent {
}
#endif
#if ANDROID && (X86 || X86_64)
private sealed class NativeCloakService : Object, NativeCloak, Gum.Cloak.Observer {
private MainContext main_context = MainContext.ref_thread_default ();
private bool observing = false;
public uint[] activate (Cancellable? cancellable) {
Gum.Cloak.add_observer (this);
observing = true;
uint[] threads = {};
Gum.Cloak.enumerate_threads (id => {
threads += (uint) id;
return true;
});
return threads;
}
public void close () {
if (!observing)
return;
Gum.Cloak.remove_observer (this);
observing = false;
}
public void thread_added (Gum.ThreadId id) {
schedule (() => {
thread_cloaked ((uint) id);
return Source.REMOVE;
});
}
public void thread_removed (Gum.ThreadId id) {
schedule (() => {
thread_uncloaked ((uint) id);
return Source.REMOVE;
});
}
private void schedule (owned SourceFunc function) {
var source = new IdleSource ();
source.set_callback ((owned) function);
source.attach (main_context);
}
}
private sealed class NativeThreadRegistryService : Object, NativeThreadRegistry {
private unowned Gum.ThreadRegistry? registry;
private ulong added_handler = 0;
private ulong removed_handler = 0;
private ulong renamed_handler = 0;
public NativeThreadInfo[] activate (Cancellable? cancellable) {
registry = Gum.ThreadRegistry.obtain ();
NativeThreadInfo[] threads = {};
registry.lock ();
added_handler = registry.thread_added.connect (on_thread_added);
removed_handler = registry.thread_removed.connect (on_thread_removed);
renamed_handler = registry.thread_renamed.connect (on_thread_renamed);
registry.enumerate_threads (thread => {
threads += NativeThreadInfo ((uint) thread.id, thread.name ?? "");
return true;
});
registry.unlock ();
return threads;
}
public void deactivate (Cancellable? cancellable) {
close ();
}
public void close () {
if (registry == null)
return;
registry.disconnect (added_handler);
registry.disconnect (removed_handler);
registry.disconnect (renamed_handler);
registry = null;
}
private void on_thread_added (Gum.ThreadDetails thread) {
thread_added (NativeThreadInfo ((uint) thread.id, thread.name ?? ""));
}
private void on_thread_removed (Gum.ThreadDetails thread) {
thread_removed ((uint) thread.id);
}
private void on_thread_renamed (Gum.ThreadDetails thread, string? previous_name) {
thread_renamed ((uint) thread.id, thread.name ?? "");
}
}
#endif
#if ANDROID && (ARM || ARM64)
private sealed class NativeCloakMirror : Object {
public NativeCloak native_cloak {
get;
construct;
}
public NativeCloakMirror (NativeCloak native_cloak) {
Object (native_cloak: native_cloak);
}
public void activate () throws GLib.Error {
native_cloak.thread_cloaked.connect (on_thread_cloaked);
native_cloak.thread_uncloaked.connect (on_thread_uncloaked);
foreach (uint id in native_cloak.activate (null))
on_thread_cloaked (id);
}
private void on_thread_cloaked (uint id) {
if (!Gum.Cloak.has_thread (id))
Gum.Cloak.add_thread (id);
}
private void on_thread_uncloaked (uint id) {
Gum.Cloak.remove_thread (id);
}
}
private sealed class NativeThreadProvider : Object, Gum.ThreadProvider {
public NativeThreadRegistry native_registry {
get;
construct;
}
private unowned Gum.ThreadRegistry registry;
private ulong added_handler = 0;
private ulong removed_handler = 0;
private ulong renamed_handler = 0;
public NativeThreadProvider (NativeThreadRegistry native_registry) {
Object (native_registry: native_registry);
}
public void activate (Gum.ThreadRegistry registry) {
this.registry = registry;
added_handler = native_registry.thread_added.connect (add_thread);
removed_handler = native_registry.thread_removed.connect (on_thread_removed);
renamed_handler = native_registry.thread_renamed.connect (on_thread_renamed);
NativeThreadInfo[] threads;
try {
threads = native_registry.activate (null);
} catch (GLib.Error e) {
return;
}
foreach (var thread in threads)
add_thread (thread);
}
public void deactivate () {
native_registry.disconnect (added_handler);
native_registry.disconnect (removed_handler);
native_registry.disconnect (renamed_handler);
try {
native_registry.deactivate (null);
} catch (GLib.Error e) {
}
}
private void add_thread (NativeThreadInfo thread) {
var details = Gum.ThreadDetails ();
details.id = thread.id;
if (thread.name != "") {
details.name = thread.name;
details.flags = NAME;
}
registry.register (details);
}
private void on_thread_removed (uint id) {
registry.unregister (id);
}
private void on_thread_renamed (uint id, string name) {
registry.rename (id, (name != "") ? name : null);
}
}
#endif
private sealed class LiveAgentSession : BaseAgentSession {
public uint registration_id {
get;
+12 -2
View File
@@ -6,6 +6,8 @@ namespace Frida {
private Gee.Map<ChannelId?, weak IOStream> channels =
new Gee.HashMap<ChannelId?, weak IOStream> (ChannelId.hash, ChannelId.equal);
private Gee.Map<ChannelId?, IOStream> unlinked_channels =
new Gee.HashMap<ChannelId?, IOStream> (ChannelId.hash, ChannelId.equal);
~ChannelRegistry () {
clear ();
@@ -18,25 +20,33 @@ namespace Frida {
channel_stream.abandon ();
}
channels.clear ();
unlinked_channels.clear ();
}
public void register (ChannelId id, IOStream stream) {
stream.set_data ("channel-registry-entry", new Entry (this, id));
lock (channels)
lock (channels) {
channels[id] = stream;
unlinked_channels[id] = stream;
}
}
public IOStream link (ChannelId id) throws Error {
IOStream? stream;
lock (channels)
IOStream? unlinked_stream = null;
lock (channels) {
stream = channels[id];
unlinked_channels.unset (id, out unlinked_stream);
}
if (stream == null)
throw new Error.INVALID_ARGUMENT ("Invalid channel ID");
return stream;
}
public void unlink (ChannelId id) {
IOStream? unlinked_stream = null;
lock (channels) {
unlinked_channels.unset (id, out unlinked_stream);
if (!channels.unset (id))
return;
}
+32
View File
@@ -133,6 +133,36 @@ namespace Frida {
Cancellable? cancellable) throws GLib.Error;
}
#if ANDROID
[DBus (name = "re.frida.NativeThreadRegistry17")]
public interface NativeThreadRegistry : Object {
public abstract NativeThreadInfo[] activate (Cancellable? cancellable) throws GLib.Error;
public abstract void deactivate (Cancellable? cancellable) throws GLib.Error;
public signal void thread_added (NativeThreadInfo thread);
public signal void thread_removed (uint id);
public signal void thread_renamed (uint id, string name);
}
[DBus (name = "re.frida.NativeCloak17")]
public interface NativeCloak : Object {
public abstract uint[] activate (Cancellable? cancellable) throws GLib.Error;
public signal void thread_cloaked (uint id);
public signal void thread_uncloaked (uint id);
}
public struct NativeThreadInfo {
public uint id;
public string name;
public NativeThreadInfo (uint id, string name) {
this.id = id;
this.name = name;
}
}
#endif
[DBus (name = "re.frida.AgentMessageSink17")]
public interface AgentMessageSink : Object {
public abstract async void post_messages (AgentMessage[] messages, uint batch_id,
@@ -2802,6 +2832,8 @@ namespace Frida {
public const string AGENT_SESSION_PROVIDER = "/re/frida/AgentSessionProvider";
public const string AGENT_SESSION = "/re/frida/AgentSession";
public const string AGENT_CONTROLLER = "/re/frida/AgentController";
public const string NATIVE_THREAD_REGISTRY = "/re/frida/NativeThreadRegistry";
public const string NATIVE_CLOAK = "/re/frida/NativeCloak";
public const string AGENT_MESSAGE_SINK = "/re/frida/AgentMessageSink";
public const string GADGET_SESSION = "/re/frida/GadgetSession";
public const string CHANNEL = "/re/frida/Channel";
+18 -2
View File
@@ -46,8 +46,24 @@ namespace Frida {
}
#else
var libc = Gum.Process.get_libc_module ();
foreach (var symbol in apis) {
interceptor.attach ((void *) libc.find_export_by_name (symbol), listener);
var targets = new Gum.Address[apis.length];
for (var i = 0; i != apis.length; i++)
targets[i] = libc.find_export_by_name (apis[i]);
var sizes = new uint[apis.length];
#if ARM
libc.enumerate_symbols (s => {
for (var i = 0; i != targets.length; i++) {
if (s.address == targets[i] && s.size > 0)
sizes[i] = (uint) s.size;
}
return true;
});
#endif
for (var i = 0; i != apis.length; i++) {
var options = Gum.AttachOptions ();
options.instrumentation.redirect_space_hint = sizes[i];
interceptor.attach ((void *) targets[i], listener, options);
}
#endif
#endif
+10 -5
View File
@@ -14,7 +14,7 @@ if not meson.get_compiler('vala').version().endswith('-frida')
Frida currently relies on features not yet upstream. Grab the Frida-optimized
Vala sources from:
https://github.com/frida/vala
https://github.com/frida-forks/vala
and compile them with Meson.''')
endif
@@ -732,17 +732,22 @@ openssl_dep = dependency('openssl',
)
idun_libs = []
have_idund_blob = false
have_idund = false
if have_fruity_backend
idun_dep = dependency('idun-1.0', default_options: ['default_library=static'])
backend_reqs_private += 'idun-1.0'
if idun_dep.type_name() == 'internal'
idun_sp = subproject('idun')
idun_libs += idun_sp.get_variable('idun')
if assets_mode == 'embedded' and idun_sp.get_variable('build_daemon')
have_idund = idun_sp.get_variable('build_daemon')
if have_idund
idund_raw = idun_sp.get_variable('idund')
have_idund_blob = true
vala_flags += '--define=HAVE_IDUND_BLOB'
if assets_mode == 'embedded'
vala_flags += '--define=HAVE_EMBEDDED_IDUND'
else
vala_flags += '--define=HAVE_INSTALLED_IDUND'
cdata.set_quoted('FRIDA_IDUND_NAME', 'idund' + exe_suffix)
endif
endif
endif
idun_vapidir = idun_dep.get_variable('vapidir', default_value: '')
+1 -1
Submodule releng updated: a3f1a54307...fe7d31dfbc.
-4
View File
@@ -9,15 +9,12 @@ name = "frida_barebone_agent"
path = "src/lib.rs"
crate-type = ["rlib", "staticlib"]
# The XNU flavour is a freestanding executable the host injects and calls into.
# The Linux flavour is the staticlib above, linked into a .ko by linux/Kbuild.
[[bin]]
name = "frida-barebone-agent"
path = "src/main.rs"
required-features = ["blob"]
[features]
# Flavors the host injects and calls into, as opposed to the module Linux loads.
blob = []
# The XNU flavour itself, and the two ways it is packaged: a blob the host injects
# into a running kernel, and a kext that kernel loads.
@@ -26,7 +23,6 @@ xnu = ["xnu-core", "blob"]
xnu-kext = ["xnu-core"]
win9x = ["blob"]
winnt = ["blob"]
linux = []
linux-injected = ["blob"]
[profile.release]
+14
View File
@@ -33,8 +33,22 @@ SECTIONS {
_set_memory_x = .; . += 4;
_set_memory_nx = .; . += 4;
_modules = .; . += 4;
_module_mutex = .; . += 4;
_mutex_lock = .; . += 4;
_mutex_unlock = .; . += 4;
_register_module_notifier = .; . += 4;
_unregister_module_notifier = .; . += 4;
_frida_kmod_link_open = .; . += 4;
_frida_kmod_link_close = .; . += 4;
_frida_kmod_link_send = .; . += 4;
_frida_kmod_link_recv = .; . += 4;
_frida_kmod_link_pending = .; . += 4;
_frida_kmod_exit_worker = .; . += 4;
_frida_kmod_layout = .; . += 4;
_frida_kmod_remap_writable = .; . += 4;
_frida_kmod_unmap_writable = .; . += 4;
_execmem_alloc_rw = .; . += 4;
_vmalloc_noprof = .; . += 4;
_execmem_free = .; . += 4;
_module_alloc = .; . += 4;
_module_memfree = .; . += 4;
+40
View File
@@ -33,8 +33,48 @@ SECTIONS {
_set_memory_x = .; . += 8;
_set_memory_nx = .; . += 8;
_modules = .; . += 8;
_module_mutex = .; . += 8;
_mutex_lock = .; . += 8;
_mutex_unlock = .; . += 8;
_register_module_notifier = .; . += 8;
_unregister_module_notifier = .; . += 8;
_frida_kmod_link_open = .; . += 8;
_frida_kmod_link_close = .; . += 8;
_frida_kmod_link_send = .; . += 8;
_frida_kmod_link_recv = .; . += 8;
_frida_kmod_link_pending = .; . += 8;
_frida_kmod_exit_worker = .; . += 8;
_frida_kmod_layout = .; . += 8;
_frida_kmod_remap_writable = .; . += 8;
_frida_kmod_unmap_writable = .; . += 8;
_execmem_alloc_rw = .; . += 8;
_vmalloc_noprof = .; . += 8;
_queued_spin_lock_slowpath = .; . += 8;
_queued_read_lock_slowpath = .; . += 8;
_queued_write_lock_slowpath = .; . += 8;
_find_get_task_by_vpid = .; . += 8;
___put_task_struct = .; . += 8;
_task_join_group_stop = .; . += 8;
___cleanup_sighand = .; . += 8;
_kmem_cache_free = .; . += 8;
_signal_cachep = .; . += 8;
_put_files_struct = .; . += 8;
_switch_task_namespaces = .; . += 8;
_free_fs_struct = .; . += 8;
___put_cred = .; . += 8;
__raw_spin_lock = .; . += 8;
__raw_spin_unlock = .; . += 8;
_capable = .; . += 8;
_show_map = .; . += 8;
_show_smap = .; . += 8;
_proc_fill_cache = .; . += 8;
_proc_pid_status = .; . += 8;
_do_task_stat = .; . += 8;
_release_task = .; . += 8;
_proc_task_instantiate = .; . += 8;
_proc_fd_instantiate = .; . += 8;
_proc_fdinfo_instantiate = .; . += 8;
___arm64_sys_prctl = .; . += 8;
_execmem_free = .; . += 8;
_module_alloc = .; . += 8;
_module_memfree = .; . += 8;
+15
View File
@@ -32,8 +32,22 @@ SECTIONS {
_set_memory_x = .; . += 4;
_set_memory_nx = .; . += 4;
_modules = .; . += 4;
_module_mutex = .; . += 4;
_mutex_lock = .; . += 4;
_mutex_unlock = .; . += 4;
_register_module_notifier = .; . += 4;
_unregister_module_notifier = .; . += 4;
_frida_kmod_link_open = .; . += 4;
_frida_kmod_link_close = .; . += 4;
_frida_kmod_link_send = .; . += 4;
_frida_kmod_link_recv = .; . += 4;
_frida_kmod_link_pending = .; . += 4;
_frida_kmod_exit_worker = .; . += 4;
_frida_kmod_layout = .; . += 4;
_frida_kmod_remap_writable = .; . += 4;
_frida_kmod_unmap_writable = .; . += 4;
_execmem_alloc_rw = .; . += 4;
_vmalloc_noprof = .; . += 4;
_text_poke_kgdb = .; . += 4;
_execmem_free = .; . += 4;
_module_alloc = .; . += 4;
@@ -55,6 +69,7 @@ SECTIONS {
_request_threaded_irq = .; . += 4;
_vmap = .; . += 4;
_current_task = .; . += 4;
_pcpu_hot = .; . += 4;
_ioremap = .; . += 4;
_ioremap_nocache = .; . += 4;
___default_kernel_pte_mask = .; . += 4;
+39
View File
@@ -33,8 +33,45 @@ SECTIONS {
_set_memory_x = .; . += 8;
_set_memory_nx = .; . += 8;
_modules = .; . += 8;
_module_mutex = .; . += 8;
_mutex_lock = .; . += 8;
_mutex_unlock = .; . += 8;
_register_module_notifier = .; . += 8;
_unregister_module_notifier = .; . += 8;
_frida_kmod_link_open = .; . += 8;
_frida_kmod_link_close = .; . += 8;
_frida_kmod_link_send = .; . += 8;
_frida_kmod_link_recv = .; . += 8;
_frida_kmod_link_pending = .; . += 8;
_frida_kmod_exit_worker = .; . += 8;
_frida_kmod_layout = .; . += 8;
_frida_kmod_remap_writable = .; . += 8;
_frida_kmod_unmap_writable = .; . += 8;
_execmem_alloc_rw = .; . += 8;
_vmalloc_noprof = .; . += 8;
_find_get_task_by_vpid = .; . += 8;
___put_task_struct = .; . += 8;
_task_join_group_stop = .; . += 8;
___cleanup_sighand = .; . += 8;
_kmem_cache_free = .; . += 8;
_signal_cachep = .; . += 8;
_put_files_struct = .; . += 8;
_switch_task_namespaces = .; . += 8;
_free_fs_struct = .; . += 8;
___put_cred = .; . += 8;
__raw_spin_lock = .; . += 8;
__raw_spin_unlock = .; . += 8;
_capable = .; . += 8;
_show_map = .; . += 8;
_show_smap = .; . += 8;
_proc_fill_cache = .; . += 8;
_proc_pid_status = .; . += 8;
_do_task_stat = .; . += 8;
_release_task = .; . += 8;
_proc_task_instantiate = .; . += 8;
_proc_fd_instantiate = .; . += 8;
_proc_fdinfo_instantiate = .; . += 8;
___x64_sys_prctl = .; . += 8;
_text_poke_kgdb = .; . += 8;
_execmem_free = .; . += 8;
_module_alloc = .; . += 8;
@@ -59,6 +96,8 @@ SECTIONS {
_page_offset_base = .; . += 8;
_current_task = .; . += 8;
_cpu_current_top_of_stack = .; . += 8;
___preempt_count = .; . += 8;
_pcpu_hot = .; . += 8;
_register_die_notifier = .; . += 8;
_unregister_die_notifier = .; . += 8;
_ioremap = .; . += 8;
+4 -2
View File
@@ -47,8 +47,6 @@ fn main() {
.write_to_file(out_dir.join("bindings.rs"))
.expect("Couldn't write bindings");
// The Linux flavour is a staticlib: kbuild owns the final link, so it — not
// us — decides where the devkit and libc archives come from.
if env::var("CARGO_FEATURE_BLOB").is_ok() {
println!(
"cargo:rustc-link-search=native={}",
@@ -89,6 +87,10 @@ fn main() {
// references the entrypoint the host calls; keep the linker from
// dropping it along with the rest of the unreferenced archive members.
println!("cargo:rustc-link-arg=--undefined=_start");
if env::var("CARGO_FEATURE_LINUX_INJECTED").is_ok() {
println!("cargo:rustc-link-arg=--undefined=frida_agent_wake");
println!("cargo:rustc-link-arg=--undefined=frida_agent_stop");
}
let note = compile_version_note(&out_dir, cc, &cc_args, arch);
println!("cargo:rustc-link-arg={}", note.to_string_lossy());
@@ -1,5 +1,6 @@
{
"agent": {
"type": "resident",
"transport": {
"type": "socket",
"path": "/tmp/frida-guest.sock"
+1
View File
@@ -1,5 +1,6 @@
{
"agent": {
"type": "resident",
"transport": {
"type": "device",
"path": "/dev/frida"
+9 -21
View File
@@ -2,27 +2,15 @@ MODULE_NAME ?= frida-agent
obj-m += $(MODULE_NAME).o
# frida-agent-prelinked.o is not built from source here: it is the Rust agent, the
# GumJS devkit and the C library collapsed into a single relocatable object by the prelink
# step in Makefile. Copy it in through if_changed rather than kbuild's _shipped rule,
# so the .cmd file modpost expects gets written.
$(MODULE_NAME)-y := frida-kmod.o frida-agent-prelinked.o
$(MODULE_NAME)-y := frida-kmod.o frida-agent-image.o
targets += frida-agent-prelinked.o
AFLAGS_frida-agent-image.o := -DFRIDA_AGENT_IMAGE='"$(FRIDA_AGENT_IMAGE)"'
quiet_cmd_frida_agent_prelinked = COPY $@
cmd_frida_agent_prelinked = cp $< $@
frida-header-has = $(if $(shell grep -ls '$(2)' $(srctree)/$(1)),-D$(3))
ccflags-y += $(call frida-header-has,include/linux/sched.h,list_head[[:space:]]*thread_group;,FRIDA_HAVE_TASK_THREAD_GROUP)
ccflags-y += $(call frida-header-has,include/linux/sched/signal.h,int[[:space:]]*quick_threads;,FRIDA_HAVE_SIGNAL_QUICK_THREADS)
ccflags-y += $(call frida-header-has,include/linux/fs_struct.h,spinlock_t[[:space:]]*lock;,FRIDA_HAVE_FS_STRUCT_LOCK)
ccflags-y += $(call frida-header-has,arch/$(SRCARCH)/include/asm/current.h,struct[[:space:]]*pcpu_hot[[:space:]]*{,FRIDA_HAVE_PCPU_HOT)
ccflags-y += $(call frida-header-has,arch/$(SRCARCH)/include/asm/processor.h,vl_onexec\[ARM64_VEC_MAX\],FRIDA_HAVE_VECTOR_LENGTHS)
$(obj)/frida-agent-prelinked.o: $(src)/frida-agent-prelinked.o.bin FORCE
$(call if_changed,frida_agent_prelinked)
# On x86 objtool validates every object going into a module and generates its ORC
# unwind tables. It was not built to read a JavaScript runtime linked against a
# freestanding libc: it gives up on the first Rust switch jump table it cannot
# follow, and the tables would only ever describe frames no kernel stack trace walks
# into. Both the blob and the module object it is linked into have to be excused,
# since objtool runs over each.
OBJECT_FILES_NON_STANDARD_frida-agent-prelinked.o := y
OBJECT_FILES_NON_STANDARD_$(MODULE_NAME).o := y
ccflags-y += $(FRIDA_KMOD_DEFS)
$(obj)/frida-agent-image.o: $(FRIDA_AGENT_IMAGE)
+28 -160
View File
@@ -1,14 +1,3 @@
# Builds frida-agent.ko in two steps, because kbuild insists on owning the final
# link:
#
# 1. Collapse the Rust staticlib plus everything it needs — the GumJS devkit and
# the C library — into one relocatable object, and localize every symbol except
# the entry points frida-kmod.c calls. Localizing matters: Gum and the C library
# define names like memcpy and malloc that must not leak into the kernel's
# namespace.
# 2. Hand that object to kbuild as a shipped object, so vermagic, modinfo and
# CRCs all come from the target kernel's own build system.
#
# See README.md for usage.
# KDIR is the kernel to build against. When it is a source tree that was configured
@@ -17,142 +6,44 @@
KDIR ?= /lib/modules/$(shell uname -r)/build
PROFILE ?= release
# The kernel's own name for the architecture, and the one Rust knows it by. Both
# follow from the machine this is built for, which defaults to the one building.
ARCH ?= $(shell uname -m \
| sed -e 's/^i[3-6]86$$/x86/' -e 's/^x86_64$$/x86/' -e 's/^aarch64$$/arm64/')
BITS ?= $(shell uname -m \
| sed -e 's/^i[3-6]86$$/32/' -e 's/^x86_64$$/64/' -e 's/^aarch64$$/64/')
ifeq ($(ARCH)-$(BITS),x86-32)
RUST_TARGET ?= i686-unknown-none
AGENT_TARGET_FLAGS ?= --target $(AGENT_DIR)/$(RUST_TARGET).json -Z json-target-spec
RUST_TARGET ?= i686-unknown-none-pic
SDK_ARCH ?= x86
TOOLPREFIX ?=
AGENT_LD_EMULATION ?= elf_i386
AGENT_RUSTFLAGS ?=
AGENT_CFLAGS ?= --target=i386-none-elf
else ifeq ($(ARCH)-$(BITS),x86-64)
RUST_TARGET ?= x86_64-unknown-none
RUST_TARGET ?= x86_64-unknown-none-pic
SDK_ARCH ?= x86_64
TOOLPREFIX ?=
AGENT_LD_EMULATION ?= elf_x86_64
AGENT_RUSTFLAGS ?= -Zcf-protection=branch -Cllvm-args=--min-jump-table-entries=65536
AGENT_CFLAGS ?= --target=x86_64-none-elf -fcf-protection=branch -fno-jump-tables
else ifeq ($(ARCH)-$(BITS),arm64-64)
RUST_TARGET ?= aarch64-unknown-none
RUST_TARGET ?= aarch64-unknown-none-pic
SDK_ARCH ?= arm64
TOOLPREFIX ?= aarch64-none-elf-
AGENT_RUSTFLAGS ?=
AGENT_CFLAGS ?= --target=aarch64-none-elf -ffixed-x18
else
$(error Unsupported ARCH "$(ARCH)" and BITS "$(BITS)"; this flavour covers \
$(error Unsupported ARCH "$(ARCH)" and BITS "$(BITS)"; this flavor covers \
x86/32, x86/64 and arm64/64)
endif
AGENT_TARGET_FLAGS ?= --target $(RUST_TARGET)
AGENT_DIR := $(CURDIR)/..
AGENT_TARGET_DIR := $(AGENT_DIR)/target-linux
AGENT_LIB := $(AGENT_TARGET_DIR)/$(RUST_TARGET)/$(PROFILE)/libfrida_barebone_agent.a
FRIDA_SDK ?= $(error Set FRIDA_SDK to a none-$(SDK_ARCH)-softfloat_pic SDK prefix)
GUMJS_DEVKIT_DIR ?= $(error Set GUMJS_DEVKIT_DIR to the directory holding libfrida-gumjs.a)
# Any binutils that speak the target's ELF will do; nothing here is compiled with
# them. Building for the host's own architecture, that is the host's binutils.
AGENT_LD ?= $(TOOLPREFIX)ld
AGENT_AR ?= $(TOOLPREFIX)ar
AGENT_NM ?= $(TOOLPREFIX)nm
AGENT_OBJCOPY ?= $(TOOLPREFIX)objcopy
# The ABI the kernel imposes on anything linked into it. The flags only reach crates
# Cargo compiles, and the toolchain ships core, alloc and compiler_builtins prebuilt
# without them, so those three have to be rebuilt from source as well. That needs the
# rust-src component and an unstable flag, hence RUSTC_BOOTSTRAP.
#
# On x86_64 that is endbr64 on every address-taken function, since a kernel with
# CONFIG_X86_KERNEL_IBT faults an indirect call landing anywhere else, and switches
# compiled as comparisons rather than jump tables, which objtool cannot follow into.
# Soft-float, the kernel code model and the absent red zone come with the target
# itself. On arm64
# it is x18, which the kernel reserves for the shadow call stack, so nothing linked in
# may use it as a general-purpose register.
ifeq ($(ARCH)-$(BITS),x86-32)
AGENT_RUSTFLAGS ?=
AGENT_CFLAGS ?= --target=i386-none-elf \
-Xclang -target-feature -Xclang +soft-float -mno-mmx -mno-sse \
-mlong-double-64 -fno-pic --sysroot=$(FRIDA_SDK)
else ifeq ($(ARCH)-$(BITS),x86-64)
AGENT_RUSTFLAGS ?= -Zcf-protection=branch \
-Cllvm-args=--min-jump-table-entries=65536 \
-Crelocation-model=static -Zplt=yes
AGENT_CFLAGS ?= --target=x86_64-none-elf \
-Xclang -target-feature -Xclang +soft-float -mno-mmx -mno-sse -mno-red-zone \
-mlong-double-64 -mcmodel=kernel -fcf-protection=branch -fno-jump-tables \
-fno-pic --sysroot=$(FRIDA_SDK)
else
AGENT_RUSTFLAGS ?= -Zfixed-x18
AGENT_CFLAGS ?= --target=aarch64-none-elf -mabi=aapcs-soft -mgeneral-regs-only \
-ffixed-x18 -fno-pic --sysroot=$(FRIDA_SDK)
endif
AGENT_BUILD_STD ?= -Z build-std=core,alloc,compiler_builtins
# The devkit header reaches for GLib's and capstone's, which a devkit built against
# an SDK leaves in that SDK rather than beside itself. The sysroot's own include
# directory is spelled out because only clang's bare-metal driver, which it selects
# for arm64 but not for x86_64, searches there on its own.
AGENT_CFLAGS += -isystem $(FRIDA_SDK)/include \
AGENT_CC ?= clang
AGENT_CFLAGS += -I$(FRIDA_SDK)/include \
-I$(FRIDA_SDK)/include/glib-2.0 \
-I$(FRIDA_SDK)/lib/glib-2.0/include \
-I$(FRIDA_SDK)/include/capstone \
-I$(FRIDA_SDK)/include/json-glib-1.0
# The soft-float SDK, whose libc is picolibc and whose compiler runtime is
# compiler-rt. GCC's own newlib and libgcc are hard-float and cannot be used here.
FRIDA_SDK ?= $(error Set FRIDA_SDK to a none-$(SDK_ARCH)-softfloat_nopic SDK prefix)
LIBM = $(FRIDA_SDK)/lib/libm.a
LIBBUILTINS = $(FRIDA_SDK)/lib/libclang_rt.builtins.a
# bindgen reads the devkit header through whatever this compiler reports as its
# include path, so it has to be one that knows where the SDK keeps its libc. It has
# to be clang either way: -mabi=aapcs-soft landed there in 19 and GCC has it at no
# version, and on x86_64 the soft-float ABI is reachable only as an -Xclang feature.
AGENT_CC ?= clang
PRELINKED := frida-agent-prelinked.o.bin
AGENT_LIB_SOFTFLOAT := $(AGENT_TARGET_DIR)/libfrida-barebone-agent-softfloat.a
# On arm64 the toolchain's own compiler_builtins is compiled for the ordinary AAPCS,
# where a double travels in d0: its __muldf3 opens with "fmov x14, d0". Everything
# else there is -mabi=aapcs-soft and passes that same double in x0. Whole-archiving
# the staticlib puts those definitions in front of the SDK's compiler-rt, which has
# the soft-float ones, and every double the C library computes then comes back
# garbage — quietly, since the arithmetic still runs. Drop them and let compiler-rt
# answer instead. x86_64 needs none of this: soft-float is part of the Rust target
# there, so the copy build-std compiles already passes doubles the way the C half
# expects.
ifeq ($(ARCH),arm64)
DROP_HARDFLOAT_BUILTINS = \
$(AGENT_AR) d $@ $$($(AGENT_NM) --print-armap $@ | sed -n 's/^__muldf3 in //p') && \
$(AGENT_AR) s $@
else
DROP_HARDFLOAT_BUILTINS = :
endif
# The agent puts malloc, the C library's locks and its process stubs on the kernel's
# own facilities, so picolibc's versions are duplicate definitions rather than
# fallbacks. Work out which members they live in instead of naming them: the set
# follows from what the agent defines, and picolibc moves things between releases.
LIBC := $(AGENT_TARGET_DIR)/libc-trimmed.a
ENTRY_POINTS := frida_agent_start frida_agent_stop \
gum_interceptor_obtain gum_interceptor_begin_transaction \
gum_interceptor_end_transaction gum_interceptor_replace \
gum_interceptor_revert
KEEP_GLOBAL := $(foreach s,$(ENTRY_POINTS),--keep-global-symbol=$(s))
# Two structures the reparenting path reaches into lost a member upstream, and
# neither change announces itself as anything frida-kmod.c could test for. The
# headers it is compiled against are the authority on both, so ask them.
FRIDA_KMOD_DEFS := \
$(if $(shell grep -l 'list_head[[:space:]]*thread_group;' \
$(KDIR)/include/linux/sched.h),-DFRIDA_HAVE_TASK_THREAD_GROUP) \
$(if $(shell grep -l 'spinlock_t[[:space:]]*lock;' \
$(KDIR)/include/linux/fs_struct.h),-DFRIDA_HAVE_FS_STRUCT_LOCK)
AGENT_DIR := $(CURDIR)/..
AGENT_TARGET_DIR := $(AGENT_DIR)/target-linux-$(SDK_ARCH)
AGENT_IMAGE := frida-agent.elf
all: modules
@@ -160,47 +51,24 @@ all: modules
# next boot, so bring-up wants a fresh one per attempt.
MODULE_NAME ?= frida-agent
modules: $(PRELINKED)
modules: $(AGENT_IMAGE)
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) \
FRIDA_KMOD_DEFS="$(FRIDA_KMOD_DEFS)" MODULE_NAME=$(MODULE_NAME) modules
MODULE_NAME=$(MODULE_NAME) FRIDA_AGENT_IMAGE=$(CURDIR)/$(AGENT_IMAGE) modules
$(PRELINKED): $(AGENT_LIB_SOFTFLOAT) $(LIBC) prelink.lds
$(AGENT_LD) -r $(if $(AGENT_LD_EMULATION),-m $(AGENT_LD_EMULATION)) \
-T prelink.lds -o $@.tmp \
--whole-archive $(AGENT_LIB_SOFTFLOAT) --no-whole-archive \
--start-group \
$(GUMJS_DEVKIT_DIR)/libfrida-gumjs.a \
$(LIBC) \
$(LIBM) \
$(LIBBUILTINS) \
--end-group
$(AGENT_OBJCOPY) $(KEEP_GLOBAL) --strip-debug \
--remove-section=.llvmbc --remove-section=.llvmcmd $@.tmp $@
rm -f $@.tmp
$(AGENT_LIB_SOFTFLOAT): agent
cp $(AGENT_LIB) $@
$(DROP_HARDFLOAT_BUILTINS)
$(LIBC): export LC_ALL = C
$(LIBC): $(AGENT_LIB_SOFTFLOAT)
cp $(FRIDA_SDK)/lib/libc.a $@
$(AGENT_NM) --defined-only $(AGENT_LIB_SOFTFLOAT) \
| awk 'NF == 3 && $$2 ~ /^[TDBR]$$/ { print $$3 }' | sort -u > $@.agent-syms
$(AGENT_NM) --print-armap $@ | sed -n 's/^\([^ ]*\) in \(.*\)$$/\1 \2/p' | sort -u > $@.members
$(AGENT_AR) d $@ $$(join $@.agent-syms $@.members | awk '{ print $$2 }' | sort -u)
$(AGENT_AR) s $@
rm -f $@.agent-syms $@.members
$(AGENT_IMAGE): agent
cp $(AGENT_TARGET_DIR)/$(RUST_TARGET)/$(PROFILE)/frida-barebone-agent $@
agent:
cd $(AGENT_DIR) && RUSTC_BOOTSTRAP=1 RUSTFLAGS="$(AGENT_RUSTFLAGS)" \
CC_$(subst -,_,$(RUST_TARGET))="$(AGENT_CC) $(AGENT_CFLAGS)" \
cargo build --$(PROFILE) \
$(AGENT_TARGET_FLAGS) --features linux \
--target-dir $(AGENT_TARGET_DIR) $(AGENT_BUILD_STD)
cd $(AGENT_DIR) && RUSTC_BOOTSTRAP=1 \
RUSTFLAGS="-L native=$(FRIDA_SDK)/lib $(AGENT_RUSTFLAGS)" \
CC="$(AGENT_CC) $(AGENT_CFLAGS)" \
GUMJS_DEVKIT_DIR=$(GUMJS_DEVKIT_DIR) \
cargo build --$(PROFILE) --features linux-injected \
--target ./$(RUST_TARGET).json -Z json-target-spec \
--target-dir $(AGENT_TARGET_DIR) -Z build-std=core,alloc,compiler_builtins
clean:
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) clean
rm -f $(PRELINKED) $(PRELINKED).tmp $(AGENT_LIB_SOFTFLOAT) $(LIBC)
rm -f $(AGENT_IMAGE)
.PHONY: all modules agent clean
+127 -217
View File
@@ -1,291 +1,201 @@
# Linux kernel module
The same agent that the Barebone backend injects into XNU from the outside, but
loaded from the inside as an ordinary kernel module.
The agent the Barebone backend injects into a Linux guest from the outside,
loaded from the inside instead. The module is a loader and nothing more: it
carries the same `linux-injected` image a hypervisor would write into guest
memory, maps and relocates it, and starts it. Everything past that point is the
one implementation, whichever way the agent got in: the GumJS runtime, process
and module enumeration, cloaking, the userspace injector.
## How it differs from the XNU flavor
## What the loader does
| | XNU | Linux |
|-------------------|--------------------------------------------------|-------------------------------------------|
| Delivery | remote stub injects a freestanding ELF | `insmod frida-agent.ko` |
| Kernel primitives | addresses patched into `.kernel_addrs` by the host | direct calls into `frida-kmod.c` |
| Bootstrap config | GVariant blob written into guest memory | module parameters |
| Transport | virtio hostlink or vsock | `/dev/frida` character device |
| Page permissions | host rewrites descriptors over the hostlink | `set_memory_*()` |
| Writable alias | shadow copy, committed via physical-memory bridge | `vmap()` of the same pages |
| Symbols | table supplied by the host | kallsyms |
| | From the outside | From the inside (`frida-agent.ko`) |
|--------------------|-----------------------------------------------|--------------------------------------------------|
| Delivery | host writes the image into guest memory | `insmod`; the image is embedded in the module |
| Relocation | host applies `.rela.dyn` | loader applies `.rela.dyn` (`.rel.dyn` on x86) |
| `.kernel_addrs` | host fills the slots from its symbol source | loader fills them from kallsyms |
| Struct layouts | BTF | `offsetof()` table from the module's headers, BTF behind it |
| Symbol table | supplied by the host | built by the loader from kallsyms |
| Transport | virtio hostlink or vsock | `/dev/frida` character device |
| Writable alias | host rewrites page tables | `vmap()` of the same pages |
| Lifetime | ends when the host detaches | resident until `rmmod` |
Because the Linux agent performs its own memory work, it never issues the
`RemapWritablePages`, `MemoryProtect` or `PatchCode` host RPCs. The wire protocol
is otherwise identical, so the host side sees the same `(yqv)` frames.
The configuration the agent starts from is the same GVariant either way; the
loader writes one naming the character device as its transport. A slot the
loader fills that the host never does — the link functions, the layout table,
the writable alias — is what tells the agent it was loaded from the inside.
## Building
Four inputs: this repository, a GumJS devkit built for
`none-<arch>-softfloat_nopic`, the SDK it was built against, and the kernel the module is for. `Makefile`
covers x86_64 and arm64, and defaults to the architecture of the machine building.
Building for the running kernel is a good deal shorter than cross-building for a
device, so that walkthrough comes first; the arm64 one below it is the same steps
with the kernel obtained from elsewhere.
Three inputs besides this repository: a GumJS devkit for
`none-<arch>-softfloat_pic`, the SDK it was built against, and the kernel the
module is for. Building the image needs no kernel and runs anywhere; only the
kbuild step needs the kernel's build tree.
### Toolchain
rustup target add x86_64-unknown-none # or aarch64-unknown-none
rustup component add rust-src
sudo dnf install clang binutils kernel-devel-$(uname -r)
rustup toolchain install nightly
rustup component add rust-src --toolchain nightly
clang must be 19 or newer, since that is where `-mabi=aapcs-soft` landed; GCC does
not implement it at any version, nor does it reach x86_64's soft-float ABI. The
binutils are for the prelink, which reads the archive map that only GNU `nm` prints —
the LLVM one does not; cross-building wants the target's, natively the host's own
will do. The host compiler is for Cargo's own build scripts, which run on the machine
doing the building.
The image links with `rust-lld`; nothing GNU is involved. clang has to be 19 or
newer, which is where `-mabi=aapcs-soft` landed. The kbuild step wants whatever
compiler the kernel was built with.
The commands below are run from the top of this repository, with `releng` checked
out — `git submodule update --init releng` if the clone did not recurse.
### The devkit and the SDK
### For the running x86_64 kernel
version=17.26.2
arch=arm64 # or x86_64
base=https://github.com/frida/frida/releases/download/$version
curl -LO $base/frida-gumjs-devkit-$version-none-$arch-softfloat_pic.tar.xz
mkdir -p ~/gumjs-devkit
tar -C ~/gumjs-devkit -xf frida-gumjs-devkit-$version-none-$arch-softfloat_pic.tar.xz
No prebuilt bundles are published for `none-x86_64-softfloat_nopic`, so both inputs are
built locally, against the same SDK:
releng/deps.py sync sdk none-$arch-softfloat_pic ~/sdk-none-$arch-softfloat_pic
releng/deps.py build --bundle=sdk --host=none-x86_64-softfloat_nopic
mkdir -p ~/sdk-none-x86_64-softfloat_nopic
tar -C ~/sdk-none-x86_64-softfloat_nopic -xf deps/sdk-none-x86_64-softfloat_nopic.tar.xz
`releng/deps.toml` pins which SDK a devkit was built against, and mixing the two
leaves symbols undefined at link time.
gum=subprojects/frida-gum
mkdir -p $gum/build/none-x86_64-softfloat_nopic
(cd $gum/build/none-x86_64-softfloat_nopic \
&& FRIDA_DEPS=$PWD/../../../../deps ../../configure \
--host=none-x86_64-softfloat_nopic \
--enable-gumjs \
--with-devkits=gumjs \
--with-devkit-symbol-scope=original \
&& make)
### For the running kernel
make -C src/barebone/agent/linux \
FRIDA_SDK=$HOME/sdk-none-x86_64-softfloat_nopic \
GUMJS_DEVKIT_DIR=$PWD/$gum/build/none-x86_64-softfloat_nopic/bindings/gumjs/devkit
FRIDA_SDK=$HOME/sdk-none-$arch-softfloat_pic \
GUMJS_DEVKIT_DIR=$HOME/gumjs-devkit
Everything else — `KDIR`, the Rust target, the binutils, the kernel's own name for the
architecture — follows from the running kernel.
`KDIR`, the Rust target and the kernel's name for the architecture all follow
from the running kernel. `make frida-agent.elf` stops after the image, which is
also what the Barebone backend injects from the outside.
### For an arm64 device
### For an Android device
Here there are published bundles to start from, and the kernel has to be assembled.
#### The devkit and the SDK
version=17.17.0
base=https://github.com/frida/frida/releases/download/$version
curl -LO $base/frida-gumjs-devkit-$version-none-arm64-softfloat_nopic.tar.xz
mkdir -p ~/gumjs-devkit
tar -C ~/gumjs-devkit -xf frida-gumjs-devkit-$version-none-arm64-softfloat_nopic.tar.xz
releng/deps.py sync sdk none-arm64-softfloat_nopic ~/sdk-none-arm64-softfloat_nopic
The SDK carries picolibc and the compiler-rt builtins the devkit expects to be linked
against, and `releng/deps.toml` pins which one. Mixing a devkit with an SDK built for
a different libc leaves undefined symbols at prelink time.
#### The kernel
Nothing here needs the device's own kernel to be built, but the config alone is not
enough either. `uname -r` ends in the GKI commit and the build number:
The image is built the same way; it is the kernel tree that takes assembling.
`uname -r` ends in the GKI commit and the build number:
adb shell uname -r
6.1.145-android14-11-gc1de4747ac59-ab14219743
6.6.102-android15-8-g6eb5b2a8c46b-ab14739656-4k
which is commit `c1de4747ac59` and build `14219743`. Three pieces are keyed to them:
which is commit `6eb5b2a8c46b` and build `14739656`. The source comes from the
commit, the CRCs `CONFIG_MODVERSIONS` checks from what CI published for the
build, and the config from the device itself:
commit=c1de4747ac59
build=14219743
commit=6eb5b2a8c46b
build=14739656
ci=https://ci.android.com/builds/submitted/$build/kernel_aarch64/latest/raw
mkdir -p ~/kernel-prepared ~/kernel-source
curl -sSL $ci/modules_prepare_outdir.tar.gz | tar -xz -C ~/kernel-prepared
curl -sSL $ci/kernel_aarch64_Module.symvers -o ~/kernel-prepared/Module.symvers
mkdir -p ~/kernel
curl -sSL https://android.googlesource.com/kernel/common/+archive/$commit.tar.gz \
| tar -xz -C ~/kernel-source
| tar -xz -C ~/kernel
curl -sSL $ci/kernel_aarch64_Module.symvers -o ~/kernel/Module.symvers
adb shell "su -c 'cat /proc/config.gz'" | gunzip > ~/kernel/.config
make -C ~/kernel ARCH=arm64 LLVM=1 olddefconfig modules_prepare
`modules_prepare_outdir.tar.gz` is the configured build tree with `scripts/` already
compiled as x86-64 binaries, so the kbuild half has to run on an x86-64 Linux host —
an arm64 one gets as far as `CC [M]` and then fails to run `fixdep`. `Module.symvers`
carries the CRCs `CONFIG_MODVERSIONS` checks. The top-level `Makefile` in that prepared tree is a stub
that includes the source's, hence both `KDIR` and `KOUT` below.
A different GKI build of the same branch does not substitute for these: vermagic is
compared verbatim, and it names the exact build.
#### Build
That wants a Linux host, of either architecture, with clang and lld. With
`CONFIG_MODVERSIONS` the kernel matches a module by its CRCs rather than by the
release vermagic names, so the release this tree reports does not have to be
the device's; the vendor's own modules do not carry it either.
make -C src/barebone/agent/linux \
FRIDA_SDK=$HOME/sdk-none-arm64-softfloat_nopic \
ARCH=arm64 BITS=64 \
FRIDA_SDK=$HOME/sdk-none-arm64-softfloat_pic \
GUMJS_DEVKIT_DIR=$HOME/gumjs-devkit \
AGENT_LD=aarch64-linux-gnu-ld \
AGENT_AR=aarch64-linux-gnu-ar \
AGENT_NM=aarch64-linux-gnu-nm \
AGENT_OBJCOPY=aarch64-linux-gnu-objcopy \
KDIR=$HOME/kernel-source \
KOUT=$HOME/kernel-prepared \
KDIR=$HOME/kernel \
LLVM=1
`make` builds the Rust staticlib itself, prelinks it against the devkit and the SDK,
then hands the result to kbuild. The `AGENT_*` overrides are only needed because the
defaults name an `aarch64-none-elf-` GNU toolchain; drop them if that is what is
installed.
The prelink half runs anywhere; the kbuild half needs the x86-64 Linux host above.
Cross-building from macOS means running that step in a container holding the kernel
tree.
adb push src/barebone/agent/linux/frida-agent.ko /data/local/tmp/
## Loading
insmod frida-agent.ko
The module's init path returns as soon as the worker thread is running — watch
`dmesg` for `frida: listening on /dev/frida`. `rmmod frida-agent` unwinds in the
reverse order and does not return until the worker is off the module's text.
Init returns once the agent's worker is running and `/dev/frida` is there.
`rmmod frida-agent` stops the agent, waits for its worker to leave the image,
and only then frees it.
## Talking to it
The agent exposes `/dev/frida` and waits: one client at a time, `read`/`write` of the
same length-prefixed frames the other transports carry, with `poll` for readiness.
It never dials out, so there is no network namespace to get right and no ordering
requirement between loading the module and attaching a client.
The agent waits on `/dev/frida`: one client at a time, `read`/`write` of the
same length-prefixed frames the other transports carry, with `poll` for
readiness. It never dials out, so there is no network namespace to get right
and no ordering between loading the module and attaching a client.
On Android the node is created by devtmpfs without a policy-specific label, so an
SELinux-enforcing system needs the client's domain granted access to it before it can
open the device. A socket-based transport was the other option considered and is a
worse fit: `AF_VSOCK` only reaches a peer when a hypervisor is on the other side, and
having the kernel connect out to loopback needs something already listening before
the module loads.
On Android devtmpfs creates the node without a policy-specific label, so an
SELinux-enforcing system needs the client's domain granted access to it.
### Reaching it from a host
Serve the Barebone device from a frida-server running on the target, so the `open()`
happens next to the device node and the host talks to an ordinary frida-server:
Serve the Barebone device from a frida-server running on the target, so the
`open()` happens next to the device node:
FRIDA_BAREBONE_CONFIG=/data/local/tmp/linux-kmod.json \
frida-server --device barebone -l 127.0.0.1:27042
`etc/linux-kmod.json` is that config. From the host, with `adb forward tcp:27042
tcp:27042` in place:
`etc/linux-kmod.json` is that config. Its `"type": "resident"` is what keeps a
client leaving from stopping the agent. From the host, with
`adb forward tcp:27042 tcp:27042` in place:
frida -H 127.0.0.1:27042 -p 0
## Injecting into userspace
Loaded on a live system the module doubles as a ptrace-free injector for ordinary
processes — how the Linux local backend reaches its targets when the module is
present, falling back to `ptrace` when it is not. There is no second device node;
the channel is `prctl()` with a magic option and a token in the fifth argument,
On x86_64 and arm64 a resident agent doubles as a ptrace-free injector for
ordinary processes — how the Linux local backend reaches its targets when the
agent is there, falling back to `ptrace` when it is not. The channel is
`prctl()` with a magic option and a token in the fifth argument,
prctl(0x46524944, op, arg1, arg2, 0x1d5f9e6b2c7a4038)
and any call whose option or token does not match falls through to the real
prctl, so probing for the module looks like a stock kernel. `op` 32 pings and
prctl, so probing for the agent looks like a stock kernel. `op` 32 pings and
returns the magic.
Allocate, free, read, write and spawn — the primitives the injector drives
against a target pid — require `CAP_SYS_ADMIN`. A spawned thread is a real
`CLONE_THREAD` sibling that adopts the target's mm, files, credentials,
namespaces, fs and SysV semaphore undo list, so it is a native member of the
group rather than a kernel thread wearing its address space.
Allocate, free, read, write and spawn — what the injector drives against a
target pid — require `CAP_SYS_ADMIN`. A spawned thread is a real member of the
target's thread group that adopts its mm, files, credentials, namespaces, fs
and SysV semaphore undo list, rather than a kernel thread wearing its address
space.
Cloaking is Gum's own registry mirrored into the kernel so it holds against
`/proc` too: the module intercepts the `maps`, `smaps`, task and status readers
Cloaking is Gum's own registry mirrored into the kernel, so it holds against
`/proc` too: the agent intercepts the `maps`, `smaps`, task and status readers
and drops cloaked threads, ranges and fds while keeping the counts consistent. A
process cloaks its own resources with the token alone — no capability — so an
injected agent self-cloaks by forwarding Gum's updates over the same channel,
and the loader adds the footprint the agent itself never sees. A cloaked thread
still sees through the cloak when it reads `/proc`, so the agent is not blind to
its own process.
process cloaks its own resources with the token alone, so an injected agent
self-cloaks by forwarding Gum's updates over the same channel. A cloaked thread
still sees through the cloak when it reads `/proc`.
## What the target kernel has to provide
- **x86_64 or arm64.** The register-level pieces — the page size, cache maintenance,
where the syscall ABI leaves prctl()'s arguments, and how a spawned thread's
registers are set up for its return to userspace — have a half for each. The XNU
flavor remains arm64-only.
- **`CONFIG_KPROBES` and `CONFIG_KALLSYMS_ALL`.** A good deal of what the shim
needs is compiled in but not exported — `kallsyms_lookup_name`,
`kallsyms_on_each_symbol` and `set_memory_*` all are, and GKI trims its export
table to the KMI symbol list on top of that. The shim resolves them by name
through a kprobe, which is the standard way back in. Without kallsyms the module
still loads but cannot look up symbols or change page permissions.
- **x86_64 or arm64** for everything above; 32-bit x86 and ARM load the agent
but have no injector.
- **`CONFIG_KPROBES` and `CONFIG_KALLSYMS_ALL`.** Most of what the agent calls
is compiled in but not exported, and GKI trims its export table to the KMI
symbol list on top of that. The loader finds `kallsyms_lookup_name` through a
kprobe and resolves the rest by name.
- **Unsigned modules.** `CONFIG_MODULE_SIG_FORCE` must be off. Android's
`CONFIG_MODULE_SIG_PROTECT` is fine: it only demands signatures for modules on
the GKI list.
- **No BTI on arm64.** The image has no landing pads, so a CPU implementing BTI
under a kernel built with `CONFIG_ARM64_BTI_KERNEL` faults the first indirect
call into it.
## Kernel ABI constraints
A hardened kernel imposes an ABI on anything linked into it, and the FP constraint
below is why this flavor is built against a soft-float SDK
(`none-arm64-softfloat_nopic`, `none-x86_64-softfloat_nopic`) rather than an ordinary
bare-metal one. The remaining constraints are per-architecture.
A hardened kernel imposes an ABI on anything running inside it, which the image
honors whichever way it got in:
- **FP/SIMD.** JavaScript numbers are doubles, so a hardfloat build would use FP
for as long as the runtime lives, and neither architecture lets a kernel thread
hold it for free — x86 wants every stretch bracketed by `kernel_fpu_begin()`, and
on arm64 kernels before 6.9 there is no correct way to hold it at all:
`kernel_neon_begin()` runs with preemption disabled so we cannot sleep under it,
and the scheduler does not save a kernel thread's FP registers — live values are
lost on preemption and the FP state of whichever user task was interrupted is
corrupted. Building everything soft-float removes the question rather than
answering it: `-mabi=aapcs-soft -mgeneral-regs-only` on arm64, and on x86_64
LLVM's `+soft-float`, which is also what Rust's own `x86_64-unknown-none` turns
on, so the two halves agree on where a double travels. `-mno-sse` alone does not
reach the ABI; and `long double` has to be narrowed to 64 bits with
`-mlong-double-64`, since soft-float has no lowering for x87's 80-bit format and
LLVM crashes rather than calling out to a builtin.
- **Indirect branch tracking (x86_64).** With `CONFIG_X86_KERNEL_IBT` an indirect
call landing on anything but an `endbr64` faults, so everything linked in is built
`-fcf-protection=branch` and the Rust half `-Zcf-protection=branch`.
- **Kernel code model and red zone (x86_64).** Modules are loaded into the top 2GB
of the address space and their relocations are resolved as if by `-mcmodel=kernel`,
and there is no red zone below the stack pointer for an interrupt to step on. Both
come with the Rust target; the C half is told explicitly.
- **objtool (x86_64).** It validates every object entering a module and builds its
ORC unwind tables, and was not written to read a JavaScript runtime linked against
a freestanding libc. `Kbuild` marks the prelinked blob `OBJECT_FILES_NON_STANDARD`.
- **Shadow call stack (arm64).** With `CONFIG_SHADOW_CALL_STACK` the kernel reserves x18, so
the SDK and Gum are built `-ffixed-x18` and the Rust half with `-Zfixed-x18`.
Anything linked in that uses x18 as a scratch register overwrites the kernel's SCS
pointer, and the thread then returns through whatever that address happens to hold.
The Rust flag only reaches crates Cargo compiles, so `core`, `alloc` and
`compiler_builtins` — which the toolchain ships prebuilt, and which between them
account for every x18 write that used to reach the module — are rebuilt from source
via `-Z build-std`. That needs `rustup component add rust-src`.
## The soft-float libc
The libc is picolibc and the compiler runtime is compiler-rt, both from the
soft-float SDK that `FRIDA_SDK` points at. `Makefile` trims a copy of its
`libc.a` before the prelink: the agent implements `malloc`, the C library's locks and
its process stubs over the kernel's own facilities, so picolibc's are duplicate
definitions rather than fallbacks. The set is derived from what the agent defines
rather than listed, since picolibc moves things between releases.
On arm64 those compiler-rt builtins are load-bearing in a way that is easy to miss.
Rust's toolchain carries its own `compiler_builtins`, compiled for the ordinary AAPCS
where a double travels in `d0`, and the prelink whole-archives the Rust staticlib — so
without the filtering step in `Makefile` those definitions answer the C library's calls
to `__muldf3` and friends, which pass the same double in `x0`. Nothing crashes. The
arithmetic simply returns garbage, and the first thing to notice is GLib deciding a
hash table need not grow. x86_64 needs none of that filtering: soft-float is part of
the Rust target there, so the copy `-Z build-std` compiles already agrees with the C
half.
## What a script may look like
The runtime compiles scripts on a kernel thread's stack, and QuickJS parses
expressions by recursion, so the ceiling is nesting rather than length: roughly
fourteen levels of parentheses or fifteen of object literals on arm64 Linux, whose
kernel stack is 16 KiB — as is x86_64's. A script that exceeds it fails to compile rather than taking
anything down, though the message is not always `stack overflow` — an over-deep
`if`/`else` chain reports `expecting ';'`. Long scripts are fine; deep ones are not.
for as long as the runtime lives, and neither architecture lets a kernel
thread hold it for free — x86 wants every stretch bracketed by
`kernel_fpu_begin()`, and on arm64 kernels before 6.9 there is no correct way
to hold it at all. Building everything soft-float removes the question: the
SDK, the devkit and the Rust target all use the soft-float ABI.
- **Indirect branch tracking (x86_64).** With `CONFIG_X86_KERNEL_IBT` an
indirect call landing on anything but an `endbr64` faults, so the image is
built `-fcf-protection=branch` and `-Zcf-protection=branch`, with switches
compiled as comparisons rather than `notrack` jump tables. The kernel seals
the `endbr64` of every function it saw no indirect call to, so the loader
restores it on any it hands the agent to call.
- **Shadow call stack (arm64).** With `CONFIG_SHADOW_CALL_STACK` the kernel
reserves x18, so the SDK, the devkit and the Rust target all leave it alone.
`core`, `alloc` and `compiler_builtins` are rebuilt from source with
`-Z build-std` so that holds for them too.
- **Stack.** The runtime runs on a kernel thread's stack, and QuickJS parses
expressions by recursion, so the ceiling is nesting rather than length: around
fourteen levels of parentheses or fifteen of object literals on a 16 KiB
kernel stack. A script that exceeds it fails to compile rather than taking
anything down, though the message is not always `stack overflow`.
@@ -0,0 +1,7 @@
.section .rodata, "a"
.balign 16
.globl frida_agent_image_start
frida_agent_image_start:
.incbin FRIDA_AGENT_IMAGE
.globl frida_agent_image_end
frida_agent_image_end:
File diff suppressed because it is too large. Load diff
-8
View File
@@ -1,8 +0,0 @@
SECTIONS
{
.init_array 0 : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
}
+14 -21
View File
@@ -236,7 +236,7 @@ unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl) -> bool {
unsafe fn rec_mutex_wait(impl_: &mut RecMutexImpl) {
while impl_.count.load(Ordering::Relaxed) != 0 {
wait_on(&impl_.count, &mut || impl_.count.load(Ordering::Relaxed) == 0);
wait_on(&impl_.count, None, &mut || impl_.count.load(Ordering::Relaxed) == 0);
}
}
@@ -361,7 +361,7 @@ unsafe fn rw_lock_reader_try_acquire(impl_: &mut RWLockImpl) -> bool {
unsafe fn rw_lock_wait_for_writers(impl_: &mut RWLockImpl) {
while impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT != 0 {
wait_on(&impl_.state, &mut || {
wait_on(&impl_.state, None, &mut || {
impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT == 0
});
}
@@ -369,7 +369,7 @@ unsafe fn rw_lock_wait_for_writers(impl_: &mut RWLockImpl) {
unsafe fn rw_lock_wait_for_all(impl_: &mut RWLockImpl) {
while impl_.state.load(Ordering::Relaxed) != 0 {
wait_on(&impl_.state, &mut || impl_.state.load(Ordering::Relaxed) == 0);
wait_on(&impl_.state, None, &mut || impl_.state.load(Ordering::Relaxed) == 0);
}
}
@@ -395,7 +395,7 @@ pub extern "C" fn g_cond_wait(cond: *mut GCond, mutex: *mut GMutex) {
let impl_ = cond_impl(cond);
while impl_.generation.load(Ordering::Acquire) == seen {
wait_on(&impl_.generation, &mut || {
wait_on(&impl_.generation, None, &mut || {
impl_.generation.load(Ordering::Acquire) != seen
});
}
@@ -431,11 +431,13 @@ pub extern "C" fn g_cond_wait_until(cond: *mut GCond, mutex: *mut GMutex, end_ti
let impl_ = cond_impl(cond);
let mut signalled = impl_.generation.load(Ordering::Acquire) != seen;
while !signalled && crate::kernel::monotonic_micros() < end_time {
wait_on(&impl_.generation, &mut || {
let mut now = crate::kernel::monotonic_micros();
while !signalled && now < end_time {
wait_on(&impl_.generation, Some((end_time - now) as u64), &mut || {
impl_.generation.load(Ordering::Acquire) != seen
});
signalled = impl_.generation.load(Ordering::Acquire) != seen;
now = crate::kernel::monotonic_micros();
}
g_mutex_lock(mutex);
@@ -620,7 +622,7 @@ pub extern "C" fn _g_system_thread_wait(thread: *mut GSystemThread) {
let system_thread = thread as *mut SystemThreadImpl;
while (*system_thread).thread_id.load(Ordering::Acquire) == 0 {
wait_on(&(*system_thread).thread_id, &mut || {
wait_on(&(*system_thread).thread_id, None, &mut || {
(*system_thread).thread_id.load(Ordering::Acquire) != 0
});
}
@@ -645,7 +647,7 @@ unsafe fn cleanup_wrapper_data(wrapper_data: *mut ThreadWrapperData) {
unsafe fn lock_acquire(lock: &AtomicU32) {
while !unsafe { lock_try_acquire(lock) } {
wait_on(lock, &mut || lock.load(Ordering::Relaxed) == 0);
wait_on(lock, None, &mut || lock.load(Ordering::Relaxed) == 0);
}
}
@@ -659,19 +661,11 @@ unsafe fn lock_release(lock: &AtomicU32) {
wake_waiters(lock);
}
// Sleeps until `ready` holds or the slice expires, whichever comes first.
//
// These used to spin on wfe/sev. A kernel thread that spins never reaches the
// scheduler, so a lost wakeup — or a genuine deadlock, which is how we found this —
// starves the CPU it is on until the hardware watchdog takes the machine down. The
// bounded sleep turns both into a stall that can be observed instead.
//
// The address of the word being waited on names the wait channel: XNU pairs
// assert_wait()/thread_wakeup() on it directly, while the Linux shim has a single
// channel and treats it as a hint. Every caller re-checks its own condition in a
// loop, so a spurious wakeup costs nothing either way.
fn wait_on<T>(word: &T, ready: &mut dyn FnMut() -> bool) {
crate::kernel::wait(token_of(word), Some(WAIT_SLICE_US), ready);
// assert_wait()/thread_wakeup() on it directly. Every caller re-checks its own
// condition in a loop, so a spurious wakeup costs nothing either way.
fn wait_on<T>(word: &T, timeout_us: Option<u64>, ready: &mut dyn FnMut() -> bool) {
crate::kernel::wait(token_of(word), timeout_us, ready);
// The wait above returns without sleeping whenever `ready` already holds, so on
// its own it is not enough to keep a contended loop off the CPU: every caller
@@ -688,4 +682,3 @@ fn token_of<T>(word: &T) -> *const u8 {
word as *const T as *const u8
}
const WAIT_SLICE_US: u64 = 1000;
+7 -9
View File
@@ -19,14 +19,12 @@ use alloc::vec::Vec;
use core::ptr;
use core::sync::atomic::{AtomicU32, Ordering};
#[cfg(feature = "linux")]
use crate::gum_linux::enumerate_exports_in_range;
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[cfg(feature = "linux-injected")]
use crate::bindings::{
GumFoundSymbolFunc, GumSymbolDetails, GumSymbolType_GUM_SYMBOL_FUNCTION,
GumSymbolType_GUM_SYMBOL_OBJECT, GumSymbolType_GUM_SYMBOL_UNKNOWN, gboolean, gssize,
};
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[cfg(feature = "linux-injected")]
use crate::gum_modules::enumerate_symbols_in_range;
#[cfg(any(feature = "win9x", feature = "winnt"))]
use crate::gum_windows::{enumerate_exports_in_range, enumerate_imports_in_module};
@@ -192,7 +190,7 @@ extern "C" fn gum_native_module_iface_init(g_iface: gpointer, _iface_data: gpoin
Some(crate::signed_to_be_called_back(gum_native_module_enumerate_imports, 0));
}
(*iface).find_export_by_name = Some(crate::signed_to_be_called_back(gum_native_module_find_export_by_name, 0));
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[cfg(feature = "linux-injected")]
{
(*iface).enumerate_symbols =
Some(crate::signed_to_be_called_back(gum_native_module_enumerate_symbols, 0));
@@ -350,7 +348,7 @@ unsafe extern "C" fn gum_native_module_enumerate_imports(
}
}
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[cfg(feature = "linux-injected")]
unsafe extern "C" fn gum_native_module_enumerate_symbols(
self_: *mut GumModule,
func: GumFoundSymbolFunc,
@@ -388,12 +386,12 @@ unsafe extern "C" fn gum_native_module_enumerate_symbols(
/// What the backends implement for `gum_native_module_enumerate_symbols`: call
/// `callback` for every symbol in `[start, end)`, stopping early when it returns
/// false.
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[cfg(feature = "linux-injected")]
pub(crate) type FoundSymbolCallback<'a> = dyn FnMut(*const gchar, u64, u64, u8, bool) -> bool + 'a;
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[cfg(feature = "linux-injected")]
const SYMBOL_IS_OBJECT: u8 = 1;
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[cfg(feature = "linux-injected")]
const SYMBOL_IS_FUNCTION: u8 = 2;
/// What the backends implement for `gum_native_module_enumerate_exports`: call
+11 -1
View File
@@ -116,6 +116,11 @@ pub extern "C" fn gum_memory_try_remap_writable_pages(
return remap_agent_pages(first_page, n_pages);
}
#[cfg(feature = "linux-injected")]
if let Some(alias) = kernel::remap_writable(first_page as u64, n_pages) {
return alias as gpointer;
}
#[cfg(all(feature = "linux-injected", not(target_arch = "arm")))]
if writable_in_place(first_page, n_pages) {
return first_page;
@@ -271,6 +276,11 @@ pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages
return;
}
#[cfg(feature = "linux-injected")]
if kernel::unmap_writable(writable as *mut core::ffi::c_void) {
return;
}
if let Some(executable) = aliases().remove(&(writable as u64)) {
let size = _n_pages as usize * unsafe { gum_query_page_size() } as usize;
libc::__clear_cache(executable as *const u8, (executable + size as u64) as *const u8);
@@ -591,7 +601,7 @@ pub extern "C" fn gum_memory_allocate(
#[cfg(all(not(feature = "xnu-core"), not(feature = "linux-injected")))]
let ptr = kernel::alloc_code(size as usize);
#[cfg(feature = "linux-injected")]
let ptr = kernel::alloc_heap(size as usize);
let ptr = kernel::alloc_code(size as usize);
#[cfg(not(feature = "xnu-core"))]
let _ = address;
#[cfg(not(feature = "xnu-kext"))]
-216
View File
@@ -1,216 +0,0 @@
// Linux half of Gum's platform backend. Everything is done locally: we are the
// kernel, so page permissions go through set_memory_*(), writable aliases of
// write-protected text come from vmap(), and symbols come from kallsyms.
use crate::{
bindings::{
_GumPageProtection_GUM_PAGE_EXECUTE, _GumPageProtection_GUM_PAGE_READ,
_GumPageProtection_GUM_PAGE_WRITE, _GumRwxSupport_GUM_RWX_NONE, GArray,
GumDebugSymbolDetails, GumModuleRegistry, GumPageProtection, GumRwxSupport,
g_array_append_vals, g_array_new, g_pattern_spec_free, g_pattern_spec_match_string,
g_pattern_spec_new, g_strdup, gboolean, gchar, gconstpointer, gpointer, gsize, guint,
gum_mprotect,
},
gum::{self, FoundExportCallback},
kernel,
};
use core::ffi::CStr;
use core::ptr;
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_platform() -> *const crate::bindings::gchar {
c"linux".as_ptr() as *const crate::bindings::gchar
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_stack_size() -> gsize {
crate::linux::stack_headroom() as gsize
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_query_rwx_support() -> GumRwxSupport {
_GumRwxSupport_GUM_RWX_NONE
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_can_remap_writable() -> gboolean {
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_try_remap_writable_pages(
first_page: gpointer,
n_pages: guint,
) -> gpointer {
kernel::remap_writable(first_page as u64, n_pages) as gpointer
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages: guint) {
kernel::unmap_writable(writable as *mut core::ffi::c_void);
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_try_mprotect(
address: gpointer,
size: gsize,
prot: GumPageProtection,
) -> gboolean {
if kernel::protect(address as u64, size as usize, prot as u32) {
1
} else {
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_allocate(
_address: gpointer,
size: gsize,
_alignment: gsize,
prot: GumPageProtection,
) -> gpointer {
let ptr = kernel::alloc_code(size as usize);
unsafe {
core::ptr::write_bytes(ptr, 0, size as usize);
if (prot & _GumPageProtection_GUM_PAGE_EXECUTE) != 0 {
gum::register_slab(ptr as u64, size as usize);
gum_mprotect(ptr as gpointer, size, prot);
}
}
ptr as gpointer
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_free(address: gpointer, size: gsize) -> gboolean {
// vfree() wants the range writable again: the pages go back to the vmalloc
// allocator, and leaving them RX would fault the next consumer.
if gum::is_agent_slab(address as u64) {
unsafe {
gum_mprotect(
address,
size,
(_GumPageProtection_GUM_PAGE_READ | _GumPageProtection_GUM_PAGE_WRITE)
as GumPageProtection,
);
}
gum::unregister_slab(address as u64);
}
kernel::free_code(address as *mut u8, size as usize);
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
crate::gum_modules::publish(registry);
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_on_registry_deactivating(_registry: *mut GumModuleRegistry) {
crate::gum_modules::unpublish();
}
pub(crate) unsafe fn enumerate_exports_in_range(
start_address: u64,
end_address: u64,
callback: &mut FoundExportCallback<'_>,
) {
if crate::gum_modules::enumerate_exports_in_module(start_address, callback) {
return;
}
kernel::enumerate_symbols(&mut |name, address| {
if address < start_address || address >= end_address {
return true;
}
callback(name.as_ptr() as *const gchar, address)
});
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_symbol_details_from_address(
address: gpointer,
details: *mut GumDebugSymbolDetails,
) -> gboolean {
let Some((name, symbol_address)) = kernel::symbol_from_address(address as u64) else {
return 0;
};
unsafe {
let name_bytes = name.as_bytes();
let copy_len = core::cmp::min(name_bytes.len(), (*details).symbol_name.len() - 1);
core::ptr::copy_nonoverlapping(
name_bytes.as_ptr(),
(*details).symbol_name.as_mut_ptr() as *mut u8,
copy_len,
);
(*details).symbol_name[copy_len] = 0;
(*details).address = symbol_address;
(*details).module_name[0] = 0;
(*details).file_name[0] = 0;
(*details).line_number = 0;
(*details).column = 0;
}
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_symbol_name_from_address(address: gpointer) -> *mut gchar {
let Some((name, _)) = kernel::symbol_from_address(address as u64) else {
return ptr::null_mut();
};
let mut name = name;
name.push('\0');
unsafe { g_strdup(name.as_ptr() as *const gchar) }
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_function(name: *const gchar) -> gpointer {
let address = kernel::find_function(unsafe { CStr::from_ptr(name) });
address as gpointer
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_functions_named(name: *const gchar) -> *mut GArray {
let wanted = unsafe { CStr::from_ptr(name) };
collect_addresses(&mut |candidate, _address| candidate == wanted)
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_functions_matching(pattern: *const gchar) -> *mut GArray {
unsafe {
let spec = g_pattern_spec_new(pattern);
let array = collect_addresses(&mut |candidate, _address| {
g_pattern_spec_match_string(spec, candidate.as_ptr() as *const gchar) != 0
});
g_pattern_spec_free(spec);
array
}
}
fn collect_addresses(predicate: &mut dyn FnMut(&CStr, u64) -> bool) -> *mut GArray {
unsafe {
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
kernel::enumerate_symbols(&mut |name, address| {
if predicate(name, address) {
let addr = address as gpointer;
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
}
true
});
array
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
0
}
+35 -3
View File
@@ -6,6 +6,7 @@
// stream; whoever opens the device decides how it reaches the host.
use core::cell::UnsafeCell;
use core::sync::atomic::{AtomicBool, Ordering};
use core::ffi::{c_int, c_void};
use alloc::vec::Vec;
@@ -30,6 +31,7 @@ impl Hostlink {
if unsafe { frida_kmod_link_open() } != 0 {
return Err(());
}
LINK_OPEN.store(true, Ordering::Release);
Ok(Hostlink {
state: UnsafeCell::new(Inner {
@@ -97,25 +99,55 @@ impl Hostlink {
impl Drop for Hostlink {
fn drop(&mut self) {
LINK_OPEN.store(false, Ordering::Release);
unsafe { frida_kmod_link_close() };
}
}
#[cfg(feature = "xnu-kext")]
pub fn a_turn_is_wanted() -> bool {
unsafe { frida_kmod_link_pending() }
LINK_OPEN.load(Ordering::Acquire) && unsafe { frida_kmod_link_pending() }
}
static LINK_OPEN: AtomicBool = AtomicBool::new(false);
fn recv_nonblocking(dst: &mut [u8]) -> usize {
let n = unsafe { frida_kmod_link_recv(dst.as_mut_ptr() as *mut c_void, dst.len()) };
if n <= 0 { 0 } else { n as usize }
}
#[cfg(feature = "xnu-kext")]
unsafe extern "C" {
fn frida_kmod_link_open() -> c_int;
fn frida_kmod_link_close();
fn frida_kmod_link_send(data: *const c_void, size: usize) -> c_int;
fn frida_kmod_link_recv(data: *mut c_void, size: usize) -> isize;
#[cfg(feature = "xnu-kext")]
fn frida_kmod_link_pending() -> bool;
}
#[cfg(all(feature = "linux-injected", not(target_arch = "x86")))]
unsafe extern "C" {
#[link_name = "_frida_kmod_link_open"]
static frida_kmod_link_open: unsafe extern "C" fn() -> c_int;
#[link_name = "_frida_kmod_link_close"]
static frida_kmod_link_close: unsafe extern "C" fn();
#[link_name = "_frida_kmod_link_send"]
static frida_kmod_link_send: unsafe extern "C" fn(*const c_void, usize) -> c_int;
#[link_name = "_frida_kmod_link_recv"]
static frida_kmod_link_recv: unsafe extern "C" fn(*mut c_void, usize) -> isize;
#[link_name = "_frida_kmod_link_pending"]
static frida_kmod_link_pending: unsafe extern "C" fn() -> bool;
}
#[cfg(all(feature = "linux-injected", target_arch = "x86"))]
unsafe extern "C" {
#[link_name = "frida_k_frida_kmod_link_open"]
fn frida_kmod_link_open() -> c_int;
#[link_name = "frida_k_frida_kmod_link_close"]
fn frida_kmod_link_close();
#[link_name = "frida_k_frida_kmod_link_send"]
fn frida_kmod_link_send(data: *const c_void, size: usize) -> c_int;
#[link_name = "frida_k_frida_kmod_link_recv"]
fn frida_kmod_link_recv(data: *mut c_void, size: usize) -> isize;
#[link_name = "frida_k_frida_kmod_link_pending"]
fn frida_kmod_link_pending() -> bool;
}
+1 -7
View File
@@ -1,14 +1,8 @@
// Facade over the host kernel the agent is running inside of. Both backends
// expose the same primitives, so the rest of the agent never names a specific
// kernel.
//
// A kernel is reached from the outside -- a remote stub injects the agent and
// patches the addresses it needs into .kernel_addrs -- or from the inside,
// where the agent is a kernel module whose glue is an ordinary C translation
// unit compiled against the target kernel's headers. Linux is reached either
// way; the others only from the outside.
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[cfg(feature = "linux-injected")]
pub use crate::linux::*;
#[cfg(feature = "win9x")]
pub use crate::win9x::*;
+61 -112
View File
@@ -4,16 +4,12 @@
feature = "xnu-core",
feature = "win9x",
feature = "winnt",
feature = "linux",
feature = "linux-injected"
)))]
compile_error!(
"pick a flavor: --features xnu, --features win9x, --features winnt, --features linux or --features linux-injected"
"pick a flavor: --features xnu, --features win9x, --features winnt or --features linux-injected"
);
#[cfg(all(feature = "blob", feature = "linux"))]
compile_error!("pick one flavour, not both");
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::format;
@@ -58,17 +54,15 @@ mod ring;
pub mod kernel;
#[cfg(feature = "linux")]
mod gum_linux;
#[cfg(feature = "linux-injected")]
mod gum_btf;
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[cfg(feature = "linux-injected")]
mod gum_modules;
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
#[cfg(any(feature = "linux-injected", feature = "xnu-kext"))]
mod hostlink_chardev;
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
mod heap;
#[cfg(any(feature = "linux", feature = "linux-injected"))]
#[cfg(feature = "linux-injected")]
mod linux;
#[cfg(any(feature = "win9x", feature = "winnt"))]
@@ -269,6 +263,19 @@ mod entrypoint_blob {
unsafe { enter(config_data, config_size) }
}
#[cfg(feature = "linux-injected")]
#[unsafe(no_mangle)]
pub extern "C" fn frida_agent_wake() {
nudge_the_loop(ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8);
}
#[cfg(feature = "linux-injected")]
#[unsafe(no_mangle)]
pub extern "C" fn frida_agent_stop() {
STOP_REQUESTED.store(true, Ordering::Release);
frida_agent_wake();
}
unsafe fn enter(config_data: *const u8, config_size: usize) {
unsafe {
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected", feature = "xnu-core"))]
@@ -312,6 +319,12 @@ mod entrypoint_blob {
#[cfg(feature = "linux-injected")]
crate::gum_btf::publish();
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
{
kernel::learn_when_to_sleep();
kernel::open_control_channel();
}
let wake_token = ptr::addr_of_mut!(glib::WAKEUP_TOKEN) as *const u8;
// Install this before the loop waits the first time, thus a copy placed in a process later
// can receive a handle to the same event.
@@ -342,6 +355,10 @@ mod entrypoint_blob {
linux::hostlink_pipe_goldfish::Hostlink::init(&path, Some(on_frame_from_host), wake_token)
.unwrap(),
),
#[cfg(feature = "linux-injected")]
TransportConfig::CharDevice => Transport::CharDevice(
hostlink_chardev::Hostlink::init(Some(on_frame_from_host)).unwrap(),
),
#[cfg(feature = "xnu-core")]
TransportConfig::Vsock { host_port } => Transport::Vsock(
hostlink_vsock::Hostlink::init(host_port, Some(on_frame_from_host), wake_token)
@@ -361,6 +378,9 @@ mod entrypoint_blob {
destroy_all_scripts(context);
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
kernel::close_control_channel();
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected",
feature = "xnu-core"))]
{
@@ -384,6 +404,8 @@ mod entrypoint_blob {
(&raw mut frida_agent_left).write_volatile(1);
#[cfg(feature = "linux-injected")]
kernel::hand_back_to_the_loader();
}
}
@@ -475,6 +497,12 @@ mod entrypoint_blob {
#[cfg(not(feature = "linux-injected"))]
{ panic!("pipe-goldfish is linux-injected's") }
}
6 => {
#[cfg(feature = "linux-injected")]
{ TransportConfig::CharDevice }
#[cfg(not(feature = "linux-injected"))]
{ panic!("the character device is linux-injected's") }
}
_ => panic!("Unsupported transport kind: {}", transport_kind),
};
g_variant_unref(transport_cfg_inner);
@@ -628,7 +656,7 @@ mod entrypoint_xnu_kext {
kernel::wake(ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8);
kernel::wait(
ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8,
Some(10_000),
None,
&mut || WORKER_EXITED.load(Ordering::Acquire),
);
}
@@ -678,94 +706,6 @@ mod entrypoint_xnu_kext {
}
}
#[cfg(feature = "linux")]
mod entrypoint_linux {
use super::*;
use core::ffi::c_int;
use core::sync::atomic::AtomicBool;
static STOP_REQUESTED: AtomicBool = AtomicBool::new(false);
static WORKER_EXITED: AtomicBool = AtomicBool::new(false);
/// Called from the module's init path. Everything interesting happens on our
/// own kernel thread: `insmod` runs with the module mutex held, and bringing
/// up Gum plus connecting to the host both block.
#[unsafe(no_mangle)]
pub extern "C" fn frida_agent_start(own_base: u64, own_size: u64) -> c_int {
kernel::log("frida: agent starting\n\0");
unsafe {
OWN_RANGE = GumMemoryRange {
base_address: own_base,
size: own_size as gsize,
};
}
if kernel::spawn_thread(worker, ptr::null_mut()) != 0 {
kernel::log("frida: failed to spawn worker\n\0");
return -1;
}
0
}
/// Called from the module's exit path, which must not return until the worker
/// is off our text — the module's pages go away right after.
#[unsafe(no_mangle)]
pub extern "C" fn frida_agent_stop() {
kernel::log("frida: agent stopping\n\0");
STOP_REQUESTED.store(true, Ordering::Release);
while !WORKER_EXITED.load(Ordering::Acquire) {
kernel::wake(ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8);
kernel::wait(
ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8,
Some(10_000),
&mut || WORKER_EXITED.load(Ordering::Acquire),
);
}
kernel::log("frida: agent stopped\n\0");
}
pub fn stop_requested() -> bool {
STOP_REQUESTED.load(Ordering::Acquire)
}
unsafe extern "C" fn worker(_parameter: *mut c_void, _wait_result: i32) {
unsafe {
kernel::log("frida: worker entry\n\0");
crate::run_constructors();
init_gum();
kernel::install_hooks();
match hostlink_chardev::Hostlink::init(Some(on_frame_from_host)) {
Ok(hostlink) => transport_set(Transport::CharDevice(hostlink)),
Err(_) => {
kernel::log("frida: failed to connect to peer\n\0");
WORKER_EXITED.store(true, Ordering::Release);
return;
}
}
let main_context = adopt_js_context();
run_main_loop(main_context);
destroy_all_scripts(main_context);
transport_teardown();
WORKER_EXITED.store(true, Ordering::Release);
kernel::wake(ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8);
}
}
}
#[cfg(feature = "linux")]
pub use entrypoint_linux::{frida_agent_start, frida_agent_stop};
#[cfg(feature = "xnu-kext")]
pub use entrypoint_xnu_kext::{frida_agent_start, frida_agent_stop, frida_agent_wake};
#[cfg(feature = "blob")]
@@ -782,7 +722,7 @@ pub enum Transport {
PipeVsock(linux::hostlink_pipe_vsock::Hostlink),
#[cfg(feature = "linux-injected")]
PipeGoldfish(linux::hostlink_pipe_goldfish::Hostlink),
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
#[cfg(any(feature = "linux-injected", feature = "xnu-kext"))]
CharDevice(hostlink_chardev::Hostlink),
}
@@ -799,7 +739,7 @@ impl Transport {
Transport::PipeVsock(h) => h.send(payload),
#[cfg(feature = "linux-injected")]
Transport::PipeGoldfish(h) => h.send(payload),
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
#[cfg(any(feature = "linux-injected", feature = "xnu-kext"))]
Transport::CharDevice(h) => h.send(payload),
}
}
@@ -816,7 +756,7 @@ impl Transport {
Transport::PipeVsock(h) => h.shutdown(),
#[cfg(feature = "linux-injected")]
Transport::PipeGoldfish(h) => h.shutdown(),
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
#[cfg(any(feature = "linux-injected", feature = "xnu-kext"))]
Transport::CharDevice(_) => {}
}
}
@@ -833,7 +773,7 @@ impl Transport {
Transport::PipeVsock(h) => h.process(),
#[cfg(feature = "linux-injected")]
Transport::PipeGoldfish(h) => h.process(),
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
#[cfg(any(feature = "linux-injected", feature = "xnu-kext"))]
Transport::CharDevice(h) => h.process(),
}
}
@@ -872,6 +812,8 @@ pub enum TransportConfig {
PipeVsock { path: alloc::string::String },
#[cfg(feature = "linux-injected")]
PipeGoldfish { path: alloc::string::String },
#[cfg(feature = "linux-injected")]
CharDevice,
}
static mut TRANSPORT_DRIVER: *mut Transport = core::ptr::null_mut();
@@ -1262,11 +1204,6 @@ fn run_main_loop(main_context: *mut GMainContext) {
#[cfg(not(any(feature = "blob", feature = "xnu-core")))]
transport_get_unchecked().process();
#[cfg(feature = "linux")]
if entrypoint_linux::stop_requested() {
return;
}
if stop_requested() {
return;
}
@@ -1298,7 +1235,7 @@ fn kernel_half_has_work() -> bool {
return true;
}
#[cfg(feature = "xnu-kext")]
#[cfg(any(feature = "linux-injected", feature = "xnu-kext"))]
if hostlink_chardev::a_turn_is_wanted() {
return true;
}
@@ -1337,6 +1274,11 @@ fn kernel_half_has_work() -> bool {
return true;
}
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
if kernel::an_operation_waits() {
return true;
}
kernel::injected_arenas()
.iter()
.any(|arena| kernel::holds_a_frame_from_target(*arena))
@@ -1370,6 +1312,9 @@ fn serve_the_kernel_half() {
#[cfg(feature = "linux-injected")]
kernel::report_what_the_copies_hit();
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
kernel::carry_out_operations();
relay_frames_from_targets();
}
@@ -1528,7 +1473,7 @@ unsafe extern "C" fn on_script_unloaded(
static UNLOADS_IN_FLIGHT: AtomicU32 = AtomicU32::new(0);
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
#[cfg(feature = "xnu-kext")]
fn transport_teardown() {
unsafe {
let driver = TRANSPORT_DRIVER;
@@ -2247,7 +2192,6 @@ fn text_or_empty(text: *const u8) -> *const core::ffi::c_char {
}
fn send_command_reply(request_id: u16, response: HandlerResponse) {
unsafe {
let message = g_variant_new(
c"(yquv)".as_ptr(),
@@ -2433,9 +2377,14 @@ fn handle_stop() -> HandlerResponse {
kernel::show_our_threads_again();
}
#[cfg(feature = "blob")]
#[cfg(all(feature = "blob", not(feature = "linux-injected")))]
STOP_REQUESTED.store(true, Ordering::Release);
#[cfg(feature = "linux-injected")]
if !kernel::loaded_from_the_inside() {
STOP_REQUESTED.store(true, Ordering::Release);
}
HandlerResponse::success(unsafe { g_variant_new_uint32(0) })
}
+2 -2
View File
@@ -50,7 +50,7 @@ pub extern "C" fn sysconf(_name: i32) -> isize {
#[unsafe(no_mangle)]
pub extern "C" fn __clear_cache(_start: *const u8, _end: *const u8) {}
#[cfg(all(target_arch = "arm", not(any(feature = "linux", feature = "linux-injected"))))]
#[cfg(all(target_arch = "arm", not(feature = "linux-injected")))]
#[unsafe(no_mangle)]
pub extern "C" fn __clear_cache(start: *const u8, end: *const u8) {
unsafe {
@@ -68,7 +68,7 @@ pub extern "C" fn __clear_cache(start: *const u8, end: *const u8) {
}
}
#[cfg(all(target_arch = "arm", any(feature = "linux", feature = "linux-injected")))]
#[cfg(all(target_arch = "arm", feature = "linux-injected"))]
#[unsafe(no_mangle)]
pub extern "C" fn __clear_cache(start: *const u8, end: *const u8) {
unsafe {
+402
View File
@@ -0,0 +1,402 @@
use alloc::collections::BTreeMap;
use alloc::vec::Vec;
use core::ffi::{c_int, c_void};
use core::sync::atomic::{AtomicBool, Ordering};
use super::layout::field_offset;
use super::native;
pub fn add_thread(tgid: i32, tid: i32) {
let fresh = Cloak::of(tgid);
registry().with(|cloaks| {
let threads = &mut cloaks.entry(tgid).or_insert(fresh).threads;
if !threads.contains(&tid) {
threads.push(tid);
}
});
}
pub fn remove_thread(tgid: i32, tid: i32) {
registry().with(|cloaks| {
if let Some(cloak) = cloaks.get_mut(&tgid) {
cloak.threads.retain(|t| *t != tid);
}
});
}
pub fn add_range(tgid: i32, base: usize, size: usize) {
let fresh = Cloak::of(tgid);
registry().with(|cloaks| {
cloaks.entry(tgid).or_insert(fresh).ranges.push((base, base + size));
});
}
pub fn remove_range(tgid: i32, base: usize, size: usize) {
registry().with(|cloaks| {
if let Some(cloak) = cloaks.get_mut(&tgid) {
if let Some(index) = cloak.ranges.iter().position(|r| *r == (base, base + size)) {
cloak.ranges.swap_remove(index);
}
}
});
}
pub fn add_fd(tgid: i32, fd: i32) {
let fresh = Cloak::of(tgid);
registry().with(|cloaks| {
let fds = &mut cloaks.entry(tgid).or_insert(fresh).fds;
if !fds.contains(&fd) {
fds.push(fd);
}
});
}
pub fn remove_fd(tgid: i32, fd: i32) {
registry().with(|cloaks| {
if let Some(cloak) = cloaks.get_mut(&tgid) {
cloak.fds.retain(|f| *f != fd);
}
});
}
pub fn install_hooks() {
let Some(layout) = describe_readers() else {
return;
};
unsafe { LAYOUT = Some(layout) };
let interceptor = unsafe { crate::bindings::gum_interceptor_obtain() };
unsafe {
crate::bindings::gum_interceptor_begin_transaction(interceptor);
replace(interceptor, _show_map, frida_cb_show_map as *mut c_void, &raw mut SHOW_MAP);
replace(interceptor, _show_smap, frida_cb_show_smap as *mut c_void, &raw mut SHOW_SMAP);
replace(interceptor, _proc_fill_cache, frida_cb_proc_fill_cache as *mut c_void,
&raw mut PROC_FILL_CACHE);
replace(interceptor, _proc_pid_status, frida_cb_proc_pid_status as *mut c_void,
&raw mut PROC_PID_STATUS);
replace(interceptor, _do_task_stat, frida_cb_do_task_stat as *mut c_void, &raw mut DO_TASK_STAT);
replace(interceptor, _release_task, frida_cb_release_task as *mut c_void, &raw mut RELEASE_TASK);
crate::bindings::gum_interceptor_end_transaction(interceptor);
}
}
pub fn remove_hooks() {
if unsafe { (*(&raw const LAYOUT)).is_none() } {
return;
}
let interceptor = unsafe { crate::bindings::gum_interceptor_obtain() };
unsafe {
crate::bindings::gum_interceptor_begin_transaction(interceptor);
for target in [_show_map, _show_smap, _proc_fill_cache, _proc_pid_status, _do_task_stat, _release_task] {
if target != 0 {
crate::bindings::gum_interceptor_revert(interceptor, target as *mut c_void);
}
}
crate::bindings::gum_interceptor_end_transaction(interceptor);
}
registry().with(|cloaks| cloaks.clear());
}
unsafe fn replace(interceptor: *mut crate::bindings::GumInterceptor, target: usize, replacement: *mut c_void,
original: *mut usize) {
if target == 0 {
return;
}
unsafe {
crate::bindings::gum_interceptor_replace(interceptor, target as *mut c_void, replacement,
original as *mut *mut c_void, core::ptr::null_mut());
}
}
unsafe extern "C" fn frida_cb_show_map(m: *mut c_void, vma: *mut c_void) -> c_int {
if hides_the_area(vma) {
return 0;
}
unsafe { call::<ShowFn>(SHOW_MAP)(m, vma) }
}
unsafe extern "C" fn frida_cb_show_smap(m: *mut c_void, vma: *mut c_void) -> c_int {
if hides_the_area(vma) {
return 0;
}
unsafe { call::<ShowFn>(SHOW_SMAP)(m, vma) }
}
unsafe extern "C" fn frida_cb_proc_fill_cache(file: *mut c_void, ctx: *mut c_void, name: *const u8, len: u32,
instantiate: usize, task: *mut c_void, ptr: *const c_void) -> bool {
if hides_the_entry(instantiate, task as usize, ptr as usize) {
return true;
}
unsafe { call::<FillCacheFn>(PROC_FILL_CACHE)(file, ctx, name, len, instantiate, task, ptr) }
}
unsafe extern "C" fn frida_cb_proc_pid_status(m: *mut c_void, ns: *mut c_void, pid: *mut c_void,
task: *mut c_void) -> c_int {
let layout = layout();
let start = read_word(m as usize + layout.seq_count);
let result = unsafe { call::<StatusFn>(PROC_PID_STATUS)(m, ns, pid, task) };
if let Some(hidden) = threads_hidden_from_the_reader(task as usize) {
let said = said_since(m as usize, start);
if let Some(at) = find(said, b"Threads:\t") {
take_from_the_number_at(m as usize, start + at + b"Threads:\t".len(), hidden);
}
}
result
}
unsafe extern "C" fn frida_cb_do_task_stat(m: *mut c_void, ns: *mut c_void, pid: *mut c_void,
task: *mut c_void, whole: c_int) -> c_int {
let layout = layout();
let start = read_word(m as usize + layout.seq_count);
let result = unsafe { call::<StatFn>(DO_TASK_STAT)(m, ns, pid, task, whole) };
if let Some(hidden) = threads_hidden_from_the_reader(task as usize) {
if let Some(at) = where_the_thread_count_is(said_since(m as usize, start)) {
take_from_the_number_at(m as usize, start + at, hidden);
}
}
result
}
unsafe extern "C" fn frida_cb_release_task(task: *mut c_void) {
let layout = layout();
let task = task as usize;
let tgid = read_i32(task + layout.task_tgid);
remove_thread(tgid, read_i32(task + layout.task_pid));
if read_word(task + layout.task_group_leader) == task {
registry().with(|cloaks| {
cloaks.remove(&tgid);
});
}
unsafe { call::<ReleaseFn>(RELEASE_TASK)(task as *mut c_void) };
}
fn hides_the_area(vma: *mut c_void) -> bool {
let layout = layout();
let vma = vma as usize;
let mm = read_word(vma + layout.vma_mm);
let start = read_word(vma + layout.vma_start);
let end = read_word(vma + layout.vma_end);
!the_reader_is_cloaked() && registry().with(|cloaks| {
cloaks.values().any(|cloak| {
cloak.mm == mm && cloak.ranges.iter().any(|(s, e)| start < *e && *s < end)
})
})
}
fn hides_the_entry(instantiate: usize, task: usize, ptr: usize) -> bool {
if the_reader_is_cloaked() {
return false;
}
let layout = layout();
if instantiate == unsafe { _proc_task_instantiate } {
let tid = read_i32(task + layout.task_pid);
return registry().with(|cloaks| cloaks.values().any(|cloak| cloak.threads.contains(&tid)));
}
if instantiate == unsafe { _proc_fd_instantiate } || instantiate == unsafe { _proc_fdinfo_instantiate } {
let tgid = read_i32(task + layout.task_tgid);
let fd = read_i32(ptr + layout.fd_data_fd);
return registry().with(|cloaks| cloaks.get(&tgid).is_some_and(|cloak| cloak.fds.contains(&fd)));
}
false
}
fn threads_hidden_from_the_reader(task: usize) -> Option<i32> {
if the_reader_is_cloaked() {
return None;
}
let tgid = read_i32(task + layout().task_tgid);
let hidden = registry().with(|cloaks| cloaks.get(&tgid).map_or(0, |cloak| cloak.threads.len()));
(hidden != 0).then_some(hidden as i32)
}
// A cloaked thread is one of ours, so let it see through the cloak; RASP threads in the same
// process are not cloaked and keep getting the censored view.
fn the_reader_is_cloaked() -> bool {
let tid = read_i32(native::current_task() as usize + layout().task_pid);
registry().with(|cloaks| cloaks.values().any(|cloak| cloak.threads.contains(&tid)))
}
fn said_since(m: usize, start: usize) -> &'static [u8] {
let layout = layout();
let count = read_word(m + layout.seq_count);
if start >= count || count >= read_word(m + layout.seq_size) {
return &[];
}
unsafe { core::slice::from_raw_parts((read_word(m + layout.seq_buf) + start) as *const u8, count - start) }
}
fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack.windows(needle.len()).position(|window| window == needle)
}
fn where_the_thread_count_is(said: &[u8]) -> Option<usize> {
let name_end = said.iter().rposition(|b| *b == b')')?;
said[name_end..]
.iter()
.enumerate()
.filter(|(_, b)| **b == b' ')
.nth(17)
.map(|(at, _)| name_end + at + 1)
}
fn take_from_the_number_at(m: usize, at: usize, hidden: i32) {
let layout = layout();
let buf = read_word(m + layout.seq_buf);
let count = read_word(m + layout.seq_count);
let said = unsafe { core::slice::from_raw_parts_mut(buf as *mut u8, count) };
let digits = said[at..].iter().take_while(|b| b.is_ascii_digit()).count();
let value = said[at..at + digits].iter().fold(0i32, |n, b| n * 10 + i32::from(b - b'0'));
let shown = if value > hidden { value - hidden } else { 1 };
let rendered = alloc::format!("{shown}");
said.copy_within(at + digits..count, at + rendered.len());
said[at..at + rendered.len()].copy_from_slice(rendered.as_bytes());
write_word(m + layout.seq_count, count - digits + rendered.len());
}
struct Cloak {
mm: usize,
threads: Vec<i32>,
ranges: Vec<(usize, usize)>,
fds: Vec<i32>,
}
impl Cloak {
fn of(tgid: i32) -> Self {
let mm = super::processes::HeldTask::with_id(tgid as u32)
.map_or(0, |task| read_word(task.address() + layout().task_mm));
Self { mm, threads: Vec::new(), ranges: Vec::new(), fds: Vec::new() }
}
}
struct Registry {
busy: AtomicBool,
cloaks: core::cell::UnsafeCell<BTreeMap<i32, Cloak>>,
}
unsafe impl Sync for Registry {}
impl Registry {
fn with<R>(&self, action: impl FnOnce(&mut BTreeMap<i32, Cloak>) -> R) -> R {
native::disable_preemption();
while self.busy.swap(true, Ordering::Acquire) {
core::hint::spin_loop();
}
let result = action(unsafe { &mut *self.cloaks.get() });
self.busy.store(false, Ordering::Release);
native::enable_preemption();
result
}
}
fn registry() -> &'static Registry {
&REGISTRY
}
static REGISTRY: Registry = Registry {
busy: AtomicBool::new(false),
cloaks: core::cell::UnsafeCell::new(BTreeMap::new()),
};
struct ReaderLayout {
task_pid: usize,
task_tgid: usize,
task_mm: usize,
task_group_leader: usize,
vma_mm: usize,
vma_start: usize,
vma_end: usize,
seq_buf: usize,
seq_size: usize,
seq_count: usize,
fd_data_fd: usize,
}
fn describe_readers() -> Option<ReaderLayout> {
Some(ReaderLayout {
task_pid: field_offset("task_struct", "pid")?,
task_tgid: field_offset("task_struct", "tgid")?,
task_mm: field_offset("task_struct", "mm")?,
task_group_leader: field_offset("task_struct", "group_leader")?,
vma_mm: field_offset("vm_area_struct", "vm_mm")?,
vma_start: field_offset("vm_area_struct", "vm_start")?,
vma_end: field_offset("vm_area_struct", "vm_end")?,
seq_buf: field_offset("seq_file", "buf")?,
seq_size: field_offset("seq_file", "size")?,
seq_count: field_offset("seq_file", "count")?,
fd_data_fd: field_offset("fd_data", "fd")?,
})
}
fn layout() -> &'static ReaderLayout {
unsafe { (*(&raw const LAYOUT)).as_ref().unwrap() }
}
static mut LAYOUT: Option<ReaderLayout> = None;
unsafe fn call<F: Copy>(original: usize) -> F {
unsafe { core::mem::transmute_copy(&original) }
}
fn read_word(address: usize) -> usize {
unsafe { (address as *const usize).read() }
}
fn write_word(address: usize, value: usize) {
unsafe { (address as *mut usize).write(value) };
}
fn read_i32(address: usize) -> i32 {
unsafe { (address as *const i32).read() }
}
type ShowFn = unsafe extern "C" fn(*mut c_void, *mut c_void) -> c_int;
type FillCacheFn =
unsafe extern "C" fn(*mut c_void, *mut c_void, *const u8, u32, usize, *mut c_void, *const c_void) -> bool;
type StatusFn = unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void, *mut c_void) -> c_int;
type StatFn = unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void, *mut c_void, c_int) -> c_int;
type ReleaseFn = unsafe extern "C" fn(*mut c_void);
static mut SHOW_MAP: usize = 0;
static mut SHOW_SMAP: usize = 0;
static mut PROC_FILL_CACHE: usize = 0;
static mut PROC_PID_STATUS: usize = 0;
static mut DO_TASK_STAT: usize = 0;
static mut RELEASE_TASK: usize = 0;
unsafe extern "C" {
static _show_map: usize;
static _show_smap: usize;
static _proc_fill_cache: usize;
static _proc_pid_status: usize;
static _do_task_stat: usize;
static _release_task: usize;
static _proc_task_instantiate: usize;
static _proc_fd_instantiate: usize;
static _proc_fdinfo_instantiate: usize;
}
+362
View File
@@ -0,0 +1,362 @@
use alloc::vec;
use core::ffi::{c_int, c_long, c_void};
use core::mem::size_of;
use core::ptr;
use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
use super::layout::field_offset;
use super::{cloak, native, process_memory, user_threads};
pub fn open() {
cloak::install_hooks();
let Some(arguments) = describe_arguments() else {
return;
};
let target = unsafe { SYS_PRCTL_TARGET };
if target == 0 {
return;
}
unsafe { ARGUMENTS = Some(arguments) };
let interceptor = unsafe { crate::bindings::gum_interceptor_obtain() };
unsafe {
crate::bindings::gum_interceptor_begin_transaction(interceptor);
crate::bindings::gum_interceptor_replace(interceptor, target as *mut c_void,
frida_cb_sys_prctl as *mut c_void, (&raw mut SYS_PRCTL) as *mut *mut c_void, ptr::null_mut());
crate::bindings::gum_interceptor_end_transaction(interceptor);
}
}
pub fn close() {
if unsafe { (*(&raw const ARGUMENTS)).is_some() } {
let interceptor = unsafe { crate::bindings::gum_interceptor_obtain() };
unsafe {
crate::bindings::gum_interceptor_begin_transaction(interceptor);
crate::bindings::gum_interceptor_revert(interceptor, SYS_PRCTL_TARGET as *mut c_void);
crate::bindings::gum_interceptor_end_transaction(interceptor);
}
}
cloak::remove_hooks();
}
pub fn an_operation_waits() -> bool {
!PENDING.load(Ordering::Acquire).is_null()
}
pub fn carry_out_operations() {
let operation = PENDING.swap(ptr::null_mut(), Ordering::AcqRel);
if operation.is_null() {
return;
}
unsafe {
(*operation).result = match (*operation).request {
Request::Allocate { id, size, protection } => process_memory::allocate(id, size, protection) as c_long,
Request::Release { id, address, size } => process_memory::release(id, address, size) as c_long,
Request::StartThread { id, entry, stack, argument, tls } => {
user_threads::start_in_process(id, entry, stack, argument, tls) as c_long
}
};
(*operation).done.store(true, Ordering::Release);
}
native::wake(wake_token());
}
unsafe extern "C" fn frida_cb_sys_prctl(regs: *const c_void) -> c_long {
let argument = |index: usize| read_word(regs as usize + unsafe { arguments().at[index] });
if argument(0) as c_int != CONTROL_MAGIC || argument(4) as u64 != CONTROL_TOKEN {
let original: unsafe extern "C" fn(*const c_void) -> c_long = unsafe { core::mem::transmute(SYS_PRCTL) };
return unsafe { original(regs) };
}
let op = argument(1) as u32;
let (first, second) = (argument(2), argument(3));
if op == OP_PING {
return CONTROL_MAGIC as c_long;
}
if op >= OP_ALLOC && !unsafe { _capable(CAP_SYS_ADMIN) } {
return -(EPERM as c_long);
}
let tgid = read_i32(native::current_task() as usize + unsafe { arguments().task_tgid });
match op {
OP_ADD_THREAD => cloak::add_thread(tgid, first as i32),
OP_REMOVE_THREAD => cloak::remove_thread(tgid, first as i32),
OP_ADD_RANGE => cloak::add_range(tgid, first, second),
OP_REMOVE_RANGE => cloak::remove_range(tgid, first, second),
OP_ADD_FD => cloak::add_fd(tgid, first as i32),
OP_REMOVE_FD => cloak::remove_fd(tgid, first as i32),
OP_ALLOC => return allocate(first),
OP_FREE => return release(first),
OP_WRITE => return write(first),
OP_READ => return read(first),
OP_SPAWN => return start_thread(first),
OP_CLOAK_THREAD => return cloak_thread(first),
OP_CLOAK_RANGE => return cloak_range(first),
_ => return -(EINVAL as c_long),
}
0
}
fn allocate(arguments: usize) -> c_long {
let Some(args) = arguments_at::<AllocArgs>(arguments) else {
return -(EFAULT as c_long);
};
hand_to_the_worker(Request::Allocate { id: args.pid, size: args.size as usize, protection: args.prot as usize })
}
fn release(arguments: usize) -> c_long {
let Some(args) = arguments_at::<FreeArgs>(arguments) else {
return -(EFAULT as c_long);
};
hand_to_the_worker(Request::Release { id: args.pid, address: args.address as usize, size: args.size as usize })
}
fn write(arguments: usize) -> c_long {
let Some(args) = arguments_at::<RwArgs>(arguments) else {
return -(EFAULT as c_long);
};
let mut bounce = vec![0u8; args.size as usize];
if !process_memory::read_own(args.buffer as usize, &mut bounce) {
return -(EFAULT as c_long);
}
process_memory::write(args.pid, args.address as usize, &bounce) as c_long
}
fn read(arguments: usize) -> c_long {
let Some(args) = arguments_at::<RwArgs>(arguments) else {
return -(EFAULT as c_long);
};
let mut bounce = vec![0u8; args.size as usize];
let transferred = process_memory::read(args.pid, args.address as usize, &mut bounce);
if !process_memory::write_own(args.buffer as usize, &bounce) {
return -(EFAULT as c_long);
}
transferred as c_long
}
fn start_thread(arguments: usize) -> c_long {
let Some(args) = arguments_at::<SpawnArgs>(arguments) else {
return -(EFAULT as c_long);
};
hand_to_the_worker(Request::StartThread {
id: args.pid,
entry: args.entry as usize,
stack: args.stack as usize,
argument: args.arg as usize,
tls: args.tls as usize,
})
}
fn cloak_thread(arguments: usize) -> c_long {
let Some(args) = arguments_at::<CloakThreadArgs>(arguments) else {
return -(EFAULT as c_long);
};
cloak::add_thread(args.tgid as i32, args.tid as i32);
0
}
fn cloak_range(arguments: usize) -> c_long {
let Some(args) = arguments_at::<CloakRangeArgs>(arguments) else {
return -(EFAULT as c_long);
};
cloak::add_range(args.tgid as i32, args.base as usize, args.size as usize);
0
}
fn arguments_at<T: Default>(address: usize) -> Option<T> {
let mut value = T::default();
let raw = unsafe { core::slice::from_raw_parts_mut(&mut value as *mut T as *mut u8, size_of::<T>()) };
process_memory::read_own(address, raw).then_some(value)
}
fn hand_to_the_worker(request: Request) -> c_long {
let token = wake_token();
while BUSY.swap(true, Ordering::Acquire) {
native::wait(token, None, &mut || !BUSY.load(Ordering::Acquire));
}
let mut operation = Operation { request, result: 0, done: AtomicBool::new(false) };
PENDING.store(&mut operation, Ordering::Release);
crate::nudge_the_loop(token);
while !operation.done.load(Ordering::Acquire) {
native::wait(token, None, &mut || operation.done.load(Ordering::Acquire));
}
BUSY.store(false, Ordering::Release);
native::wake(token);
operation.result
}
fn wake_token() -> *const u8 {
(&raw const crate::glib::WAKEUP_TOKEN) as *const u8
}
struct Operation {
request: Request,
result: c_long,
done: AtomicBool,
}
enum Request {
Allocate { id: u32, size: usize, protection: usize },
Release { id: u32, address: usize, size: usize },
StartThread { id: u32, entry: usize, stack: usize, argument: usize, tls: usize },
}
static PENDING: AtomicPtr<Operation> = AtomicPtr::new(ptr::null_mut());
static BUSY: AtomicBool = AtomicBool::new(false);
#[repr(C)]
#[derive(Default)]
struct AllocArgs {
size: u64,
pid: u32,
prot: u32,
}
#[repr(C)]
#[derive(Default)]
struct FreeArgs {
address: u64,
size: u64,
pid: u32,
}
#[repr(C)]
#[derive(Default)]
struct RwArgs {
address: u64,
buffer: u64,
size: u64,
pid: u32,
}
#[repr(C)]
#[derive(Default)]
struct SpawnArgs {
entry: u64,
stack: u64,
arg: u64,
tls: u64,
pid: u32,
}
#[repr(C)]
#[derive(Default)]
struct CloakThreadArgs {
tgid: u32,
tid: u32,
}
#[repr(C)]
#[derive(Default)]
struct CloakRangeArgs {
base: u64,
size: u64,
tgid: u32,
}
struct ArgumentLayout {
at: [usize; 5],
task_tgid: usize,
}
#[cfg(target_arch = "aarch64")]
fn describe_arguments() -> Option<ArgumentLayout> {
let regs = field_offset("pt_regs", "regs")?;
Some(ArgumentLayout {
at: core::array::from_fn(|index| regs + index * size_of::<usize>()),
task_tgid: field_offset("task_struct", "tgid")?,
})
}
#[cfg(target_arch = "x86_64")]
fn describe_arguments() -> Option<ArgumentLayout> {
Some(ArgumentLayout {
at: [
field_offset("pt_regs", "di")?,
field_offset("pt_regs", "si")?,
field_offset("pt_regs", "dx")?,
field_offset("pt_regs", "r10")?,
field_offset("pt_regs", "r8")?,
],
task_tgid: field_offset("task_struct", "tgid")?,
})
}
unsafe fn arguments() -> &'static ArgumentLayout {
unsafe { (*(&raw const ARGUMENTS)).as_ref().unwrap() }
}
static mut ARGUMENTS: Option<ArgumentLayout> = None;
static mut SYS_PRCTL: usize = 0;
fn read_word(address: usize) -> usize {
unsafe { (address as *const usize).read() }
}
fn read_i32(address: usize) -> i32 {
unsafe { (address as *const i32).read() }
}
const CONTROL_MAGIC: c_int = 0x4652_4944;
const CONTROL_TOKEN: u64 = 0x1d5f_9e6b_2c7a_4038;
const OP_ADD_THREAD: u32 = 1;
const OP_REMOVE_THREAD: u32 = 2;
const OP_ADD_RANGE: u32 = 3;
const OP_REMOVE_RANGE: u32 = 4;
const OP_ADD_FD: u32 = 5;
const OP_REMOVE_FD: u32 = 6;
const OP_PING: u32 = 32;
const OP_ALLOC: u32 = 64;
const OP_FREE: u32 = 65;
const OP_WRITE: u32 = 66;
const OP_READ: u32 = 67;
const OP_SPAWN: u32 = 68;
const OP_CLOAK_THREAD: u32 = 69;
const OP_CLOAK_RANGE: u32 = 70;
const CAP_SYS_ADMIN: c_int = 21;
const EPERM: c_int = 1;
const EFAULT: c_int = 14;
const EINVAL: c_int = 22;
#[cfg(target_arch = "aarch64")]
use self::___arm64_sys_prctl as SYS_PRCTL_TARGET;
#[cfg(target_arch = "x86_64")]
use self::___x64_sys_prctl as SYS_PRCTL_TARGET;
unsafe extern "C" {
#[cfg(target_arch = "aarch64")]
static ___arm64_sys_prctl: usize;
#[cfg(target_arch = "x86_64")]
static ___x64_sys_prctl: usize;
static _capable: unsafe extern "C" fn(c_int) -> bool;
}
+52 -29
View File
@@ -1,6 +1,7 @@
use core::ffi::{c_int, c_void};
use core::ptr;
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
#[cfg(not(target_arch = "x86"))]
@@ -63,17 +64,28 @@ fn take_what_the_copy_opened(placed: &mut Placement) {
let copy = COPY_TASK.load(core::sync::atomic::Ordering::Acquire) as *mut c_void;
placed.says = native::take_the_file_of(copy, says_fd);
placed.hears = native::take_the_file_of(copy, hears_fd);
listen_to_the_copy(placed.says);
listen_to_the_copy(placed);
}
fn listen_to_the_copy(says: *mut c_void) {
native::spawn_thread(carry_what_the_copy_says, says);
fn listen_to_the_copy(placed: &Placement) {
let listener = Box::into_raw(Box::new(Listener { says: placed.says, arena: placed.arena }));
native::spawn_thread(carry_what_the_copy_says, listener as *mut c_void);
}
unsafe extern "C" fn carry_what_the_copy_says(argument: *mut c_void, _reason: i32) {
while native::wait_for_a_word(argument) {
let listener = unsafe { Box::from_raw(argument as *mut Listener) };
while native::wait_for_a_word(listener.says) {
crate::nudge_the_loop(crate::glib::wakeup_token());
}
Arena::at(listener.arena).gone();
crate::nudge_the_loop(crate::glib::wakeup_token());
}
struct Listener {
says: *mut c_void,
arena: usize,
}
struct Placement {
@@ -293,10 +305,6 @@ fn give(destination: usize, source: usize, size: usize) -> Option<()> {
unsafe { copy_to_user(destination as *mut c_void, source as *const c_void, size) };
unsafe { close_user_access(opened) };
if left != 0 {
if left == size {
} else if left < size {
} else {
}
return None;
}
@@ -366,23 +374,25 @@ pub unsafe extern "C" fn frida_cb_user(argument: *mut c_void) -> c_int {
COPY_TASK.store(native::current_task() as usize, core::sync::atomic::Ordering::Release);
hand_userland_to(bootstrap, hand_over_argument(stack, arena), arena);
0
}
pub(crate) fn hand_userland_to(pc: usize, stack: usize, first_argument: usize) {
let Some(places) = describe_registers() else {
return 0;
return;
};
let registers = registers_of_this_task(&places);
let stack = hand_over_argument(stack, arena);
unsafe {
((registers + places.pc) as *mut usize).write(bootstrap & !THUMB_BIT);
((registers + places.pc) as *mut usize).write(pc & !THUMB_BIT);
((registers + places.stack) as *mut usize).write(stack);
((registers + places.flags) as *mut usize).write(
USER_EXECUTION | (bootstrap & THUMB_BIT) * THUMB_STATE);
((registers + places.first) as *mut usize).write(arena);
USER_EXECUTION | (pc & THUMB_BIT) * THUMB_STATE);
((registers + places.first) as *mut usize).write(first_argument);
write_selectors(registers, &places);
}
0
}
// What a task starts with in userland is at the top of the kernel stack it was given.
@@ -394,7 +404,7 @@ fn registers_of_this_task(places: &Places) -> usize {
fn registers_of_task(task: usize, places: &Places) -> usize {
let stack = unsafe { ((task + places.stack_of_task) as *const usize).read() };
stack + STACK_SPAN - TOP_OF_STACK_PADDING - places.size
stack + places.stack_span - TOP_OF_STACK_PADDING - places.size
}
#[cfg(target_arch = "x86_64")]
@@ -688,6 +698,7 @@ unsafe fn write_selectors(registers: usize, _places: &Places) {
unsafe {
((registers + 136) as *mut usize).write(0x33);
((registers + 160) as *mut usize).write(0x2b);
((registers + 120) as *mut isize).write(-1);
}
}
@@ -707,15 +718,25 @@ unsafe fn write_selectors(_registers: usize, _places: &Places) {}
#[cfg(target_arch = "x86")]
fn hand_over_argument(stack: usize, arena: usize) -> usize {
let frame = [0usize, arena];
let sp = stack - size_of::<[usize; 2]>();
let aligned_argument = stack - 16;
let sp = aligned_argument - size_of::<usize>();
match give(sp, frame.as_ptr() as usize, size_of::<[usize; 2]>()) {
Some(()) => sp,
None => stack,
}
give(sp, frame.as_ptr() as usize, size_of::<[usize; 2]>());
sp
}
#[cfg(not(target_arch = "x86"))]
#[cfg(target_arch = "x86_64")]
fn hand_over_argument(stack: usize, _arena: usize) -> usize {
let return_address = 0usize;
let sp = stack - size_of::<usize>();
give(sp, &return_address as *const usize as usize, size_of::<usize>());
sp
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn hand_over_argument(stack: usize, _arena: usize) -> usize {
stack
}
@@ -727,6 +748,7 @@ struct Places {
pc: usize,
flags: usize,
stack_of_task: usize,
stack_span: usize,
}
#[cfg(target_arch = "aarch64")]
@@ -738,6 +760,7 @@ fn describe_registers() -> Option<Places> {
pc: field_offset("user_pt_regs", "pc")?,
flags: field_offset("user_pt_regs", "pstate")?,
stack_of_task: field_offset("task_struct", "stack")?,
stack_span: struct_size("thread_union").unwrap_or(STACK_SPAN),
})
}
@@ -750,6 +773,7 @@ fn describe_registers() -> Option<Places> {
pc: 15 * 4,
flags: 16 * 4,
stack_of_task: 0,
stack_span: STACK_SPAN,
})
}
@@ -762,6 +786,7 @@ fn describe_registers() -> Option<Places> {
pc: 48,
flags: 56,
stack_of_task: 0,
stack_span: STACK_SPAN,
})
}
@@ -774,6 +799,7 @@ fn describe_registers() -> Option<Places> {
pc: 128,
flags: 144,
stack_of_task: 0,
stack_span: STACK_SPAN,
})
}
@@ -926,7 +952,7 @@ pub unsafe extern "C" fn frida_cb_exec(_data: *mut c_void, task: *mut c_void, _w
((program as usize + at) as *const *const u8).read()
});
native::wake(task as *const u8);
native::wake(crate::glib::wakeup_token());
}
#[cfg(not(target_arch = "x86"))]
@@ -963,7 +989,7 @@ unsafe extern "C" fn frida_replaced_set_task_comm(task: *mut c_void, buf: *const
if let Some(id) = id_of(task as usize) {
native::send_signal(STOP, task as usize);
super::spawn::note_pending_spawn(id, name.to_bytes());
native::wake(ptr::null());
native::wake(crate::glib::wakeup_token());
}
}
@@ -1056,9 +1082,8 @@ fn ask_it_to_leave(placed: &Placement) {
arena.tell_it_to_go();
wake_the_copy(placed);
let waited_on = &placed.arena as *const usize as *const u8;
while !arena.has_gone() {
native::wait(waited_on, Some(LEAVING_GRACE_US), &mut || arena.has_gone());
native::wait(crate::glib::wakeup_token(), None, &mut || arena.has_gone());
}
}
@@ -1085,7 +1110,6 @@ fn take_back_what_it_was_given(placed: &Placement, memory: *mut c_void) {
}
}
const LEAVING_GRACE_US: u64 = 100_000;
pub fn tell_the_copy(id: u32) {
let Some(placed) = (unsafe { placements() }).get(&id) else {
@@ -1284,7 +1308,6 @@ const EXEC_PROBE: unsafe extern "C" fn(*mut c_void, *mut c_void, c_int, *mut c_v
const EXEC_PROBE: unsafe extern "C" fn(*mut c_void, *mut c_void, c_int, *mut c_void) =
frida_cb_exec;
#[cfg(target_arch = "x86")]
const THREAD_APPEARED_PROBE: unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void) =
frida_kcb_thread_appeared;
+18 -3
View File
@@ -3,6 +3,8 @@ use core::ffi::{c_int, c_void};
use core::ffi::c_long;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use core::ptr;
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
use core::sync::atomic::{AtomicBool, Ordering};
use crate::bindings::{
gum_barebone_handle_exception, GumCpuContext,
@@ -322,10 +324,15 @@ pub fn install() {
_patch_text(addr.add(1) as *mut c_void, frida_arm_fixup as *const () as usize as u32);
_patch_text(addr as *mut c_void, 0xe51ff004);
}
ACTIVATED.store(true, Ordering::Release);
}
#[cfg(target_arch = "arm")]
pub fn uninstall() {
if !ACTIVATED.swap(false, Ordering::AcqRel) {
return;
}
let target = unsafe { _fixup_exception } as *mut u32;
unsafe {
_patch_text(target.add(1) as *mut c_void, PROLOGUE[1]);
@@ -437,6 +444,9 @@ unsafe extern "C" {
#[cfg(target_arch = "aarch64")]
const PROLOGUE: [u32; 4] = [0xd503233f, 0xa9be7bfd, 0x910003fd, 0xa90153f3];
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
static ACTIVATED: AtomicBool = AtomicBool::new(false);
#[cfg(target_arch = "aarch64")]
#[unsafe(no_mangle)]
static mut FRIDA_ARM64_ORIGINAL_CONT: u64 = 0;
@@ -452,7 +462,7 @@ pub fn install() {
unsafe { FRIDA_ARM64_ORIGINAL_CONT = target.add(4) as u64 };
let jump = detour_to(frida_arm64_fixup as *const () as usize);
let jump = redirect_to(frida_arm64_fixup as *const () as usize);
unsafe {
let addr = target as *mut u32;
_aarch64_insn_patch_text_nosync(addr.add(2) as *mut c_void, jump[2]);
@@ -460,10 +470,15 @@ pub fn install() {
_aarch64_insn_patch_text_nosync(addr.add(1) as *mut c_void, jump[1]);
_aarch64_insn_patch_text_nosync(addr as *mut c_void, jump[0]);
}
ACTIVATED.store(true, Ordering::Release);
}
#[cfg(target_arch = "aarch64")]
pub fn uninstall() {
if !ACTIVATED.swap(false, Ordering::AcqRel) {
return;
}
let target = unsafe { _fixup_exception } as *mut u32;
unsafe {
for slot in 0..4 {
@@ -473,7 +488,7 @@ pub fn uninstall() {
}
#[cfg(target_arch = "aarch64")]
fn detour_to(destination: usize) -> [u32; 4] {
fn redirect_to(destination: usize) -> [u32; 4] {
[0x58000050, 0xd61f0200, destination as u32, (destination >> 32) as u32]
}
@@ -542,7 +557,7 @@ unsafe fn recovered(regs: *mut c_void) -> bool {
extern "C" fn frida_arm64_park() -> ! {
static mut PARK_TOKEN: u8 = 0;
loop {
crate::kernel::wait(core::ptr::addr_of!(PARK_TOKEN), Some(1_000_000), &mut || false);
crate::kernel::wait(core::ptr::addr_of!(PARK_TOKEN), None, &mut || false);
}
}
-357
View File
@@ -1,357 +0,0 @@
// The agent as a kernel module: every primitive is an ordinary call into
// linux/frida-kmod.c, compiled by kbuild against the target kernel's headers,
// which is what keeps us out of the business of guessing struct layouts,
// config-dependent macros and per-version symbol names.
use core::ffi::{CStr, c_char, c_int, c_void};
use alloc::string::String;
use alloc::vec::Vec;
use super::{LoadedModule, ModuleEvent};
use crate::kernel::ThreadEntry;
pub fn log(msg: &str) {
unsafe { frida_kmod_log(msg.as_ptr() as *const c_char) };
}
pub fn panic(msg: &str) -> ! {
unsafe { frida_kmod_panic(msg.as_ptr() as *const c_char) }
}
pub fn run_when_ready(action: fn()) {
action();
}
pub fn spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> isize {
unsafe { frida_kmod_spawn_thread(entry, parameter) as isize }
}
pub fn install_hooks() {
unsafe { frida_kmod_install_hooks() };
}
pub fn alloc(size: usize) -> *mut u8 {
unsafe { frida_kmod_alloc(size) }
}
pub fn free(ptr: *mut u8, size: usize) {
unsafe { frida_kmod_free(ptr, size) };
}
pub fn alloc_code(size: usize) -> *mut u8 {
unsafe { frida_kmod_alloc_code(size) }
}
pub fn free_code(ptr: *mut u8, size: usize) {
unsafe { frida_kmod_free_code(ptr, size) };
}
pub fn page_size() -> usize {
crate::gum::page_size_the_kernel_runs_with()
}
pub fn alloc_heap(size: usize) -> *mut u8 {
alloc(size)
}
pub fn alloc_dma(size: usize) -> *mut u8 {
alloc_code(size)
}
pub fn free_dma(ptr: *mut u8, size: usize) {
free_code(ptr, size);
}
pub fn own_range() -> (u64, u64) {
let mut base: u64 = 0;
let mut size: u64 = 0;
unsafe { frida_kmod_own_range(&mut base, &mut size) };
(base, size)
}
// The sequence number is taken before `check` runs, so a wakeup racing with the
// condition we are about to observe still makes the commit return immediately.
pub fn wait(_token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut() -> bool) {
let seq = unsafe { frida_kmod_wait_prepare() };
if check() {
return;
}
unsafe { frida_kmod_wait_commit(seq, timeout_us.map_or(-1, |us| us as i64)) };
}
pub fn poke_loop_wakeup() {}
pub fn wake(_token: *const u8) {
unsafe { frida_kmod_wake() };
}
/// Gives the scheduler a chance to run something else. A kernel thread that loops
/// without doing this is a hard lockup waiting to happen.
pub fn yield_now() {
unsafe { frida_kmod_yield() };
}
pub fn monotonic_micros() -> i64 {
unsafe { frida_kmod_monotonic_micros() }
}
pub fn wall_clock_micros() -> (u32, u32) {
let mut secs: u32 = 0;
let mut micros: u32 = 0;
unsafe { frida_kmod_wall_clock_micros(&mut secs, &mut micros) };
(secs, micros)
}
pub fn current_process_id() -> u32 {
KERNEL_PROCESS
}
pub fn current_thread_id() -> u64 {
unsafe { frida_kmod_current_thread_id() }
}
pub fn protect(address: u64, size: usize, gum_prot: u32) -> bool {
unsafe { frida_kmod_protect(address, size, gum_prot) != 0 }
}
pub fn remap_writable(first_page: u64, n_pages: u32) -> *mut c_void {
unsafe { frida_kmod_remap_writable(first_page, n_pages) }
}
/// Tears down an alias handed out by [`remap_writable`] and flushes the icache
/// over the range it aliased; the shim remembers which range that was.
pub fn unmap_writable(mapping: *mut c_void) {
unsafe { frida_kmod_unmap_writable(mapping) };
}
pub fn get_kernel_base() -> u64 {
unsafe { frida_kmod_kernel_base() }
}
pub fn get_kernel_size() -> u64 {
unsafe { frida_kmod_kernel_size() }
}
pub fn enumerate_modules() -> Vec<LoadedModule> {
unsafe extern "C" fn on_module(
name: *const c_char,
version: *const c_char,
base: u64,
size: u64,
user_data: *mut c_void,
) -> c_int {
unsafe {
let modules = &mut *(user_data as *mut Vec<LoadedModule>);
modules.push(LoadedModule {
name: String::from(CStr::from_ptr(name).to_str().unwrap_or("?")),
version: String::from(CStr::from_ptr(version).to_str().unwrap_or("")),
base,
size,
});
1
}
}
let mut modules: Vec<LoadedModule> = Vec::new();
unsafe {
frida_kmod_enumerate_modules(on_module, &mut modules as *mut _ as *mut c_void);
}
modules
}
pub fn watch_modules(on_event: fn(ModuleEvent, LoadedModule)) {
unsafe extern "C" fn on_module_event(
loaded: c_int,
name: *const c_char,
version: *const c_char,
base: u64,
size: u64,
user_data: *mut c_void,
) {
unsafe {
let on_event: fn(ModuleEvent, LoadedModule) = core::mem::transmute(user_data);
on_event(
if loaded != 0 {
ModuleEvent::Loaded
} else {
ModuleEvent::Unloaded
},
LoadedModule {
name: String::from(CStr::from_ptr(name).to_str().unwrap_or("?")),
version: String::from(CStr::from_ptr(version).to_str().unwrap_or("")),
base,
size,
},
);
}
}
unsafe {
frida_kmod_watch_modules(on_module_event, on_event as *mut c_void);
}
}
pub fn unwatch_modules() {
unsafe { frida_kmod_unwatch_modules() };
}
pub fn enumerate_module_symbols(
base: u64,
on_symbol: &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool,
) -> bool {
unsafe extern "C" fn on_module_symbol(
name: *const c_char,
address: u64,
size: u64,
kind: u8,
is_global: c_int,
user_data: *mut c_void,
) -> c_int {
unsafe {
let callback =
&mut *(user_data as *mut &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool);
if callback(name, address, size, kind, is_global != 0) { 1 } else { 0 }
}
}
let mut boxed: &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool = on_symbol;
unsafe {
frida_kmod_enumerate_module_symbols(base, on_module_symbol, &mut boxed as *mut _ as *mut c_void) != 0
}
}
pub fn enumerate_module_exports(
base: u64,
on_export: &mut dyn FnMut(*const c_char, u64) -> bool,
) -> bool {
unsafe extern "C" fn on_module_export(
name: *const c_char,
address: u64,
user_data: *mut c_void,
) -> c_int {
unsafe {
let callback = &mut *(user_data as *mut &mut dyn FnMut(*const c_char, u64) -> bool);
if callback(name, address) { 1 } else { 0 }
}
}
let mut boxed: &mut dyn FnMut(*const c_char, u64) -> bool = on_export;
unsafe {
frida_kmod_enumerate_module_exports(base, on_module_export, &mut boxed as *mut _ as *mut c_void) != 0
}
}
pub fn find_symbol(name: &CStr) -> u64 {
unsafe { frida_kmod_find_symbol(name.as_ptr()) }
}
pub fn find_function(name: &CStr) -> u64 {
unsafe { frida_kmod_find_function(name.as_ptr()) }
}
/// Resolves `address` to `(name, symbol_address)`, or `None` when it falls
/// outside every known symbol.
pub fn symbol_from_address(address: u64) -> Option<(String, u64)> {
const NAME_CAPACITY: usize = 256;
let mut buffer = [0u8; NAME_CAPACITY];
let mut symbol_address: u64 = 0;
let found = unsafe {
frida_kmod_symbol_name_from_address(
address,
buffer.as_mut_ptr() as *mut c_char,
NAME_CAPACITY,
&mut symbol_address,
)
};
if found == 0 {
return None;
}
let name = CStr::from_bytes_until_nul(&buffer).unwrap();
Some((String::from(name.to_str().unwrap()), symbol_address))
}
/// Visits every symbol the running kernel knows about. Returns false when
/// kallsyms is unavailable, which is the case on kernels built without
/// CONFIG_KALLSYMS or when the shim could not resolve its entry points.
pub fn enumerate_symbols(callback: &mut dyn FnMut(&CStr, u64) -> bool) -> bool {
unsafe extern "C" fn on_symbol(
name: *const c_char,
address: u64,
user_data: *mut c_void,
) -> c_int {
unsafe {
let callback = &mut *(user_data as *mut &mut dyn FnMut(&CStr, u64) -> bool);
if callback(CStr::from_ptr(name), address) { 1 } else { 0 }
}
}
let mut boxed: &mut dyn FnMut(&CStr, u64) -> bool = callback;
unsafe { frida_kmod_enumerate_symbols(on_symbol, &mut boxed as *mut _ as *mut c_void) != 0 }
}
const KERNEL_PROCESS: u32 = 0;
type FoundSymbolFunc =
unsafe extern "C" fn(name: *const c_char, address: u64, user_data: *mut c_void) -> c_int;
type ModuleEventFunc = unsafe extern "C" fn(c_int, *const c_char, *const c_char, u64, u64, *mut c_void);
type FoundModuleSymbolFunc =
unsafe extern "C" fn(*const c_char, u64, u64, u8, c_int, *mut c_void) -> c_int;
type FoundModuleExportFunc = unsafe extern "C" fn(*const c_char, u64, *mut c_void) -> c_int;
type FoundModuleFunc = unsafe extern "C" fn(
name: *const c_char,
version: *const c_char,
base: u64,
size: u64,
user_data: *mut c_void,
) -> c_int;
unsafe extern "C" {
fn frida_kmod_log(message: *const c_char);
fn frida_kmod_panic(message: *const c_char) -> !;
fn frida_kmod_spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> c_int;
fn frida_kmod_install_hooks();
fn frida_kmod_alloc(size: usize) -> *mut u8;
fn frida_kmod_free(ptr: *mut u8, size: usize);
fn frida_kmod_alloc_code(size: usize) -> *mut u8;
fn frida_kmod_free_code(ptr: *mut u8, size: usize);
fn frida_kmod_own_range(base: *mut u64, size: *mut u64);
fn frida_kmod_wait_prepare() -> u32;
fn frida_kmod_wait_commit(seq: u32, timeout_us: i64);
fn frida_kmod_wake();
fn frida_kmod_yield();
fn frida_kmod_monotonic_micros() -> i64;
fn frida_kmod_wall_clock_micros(secs: *mut u32, micros: *mut u32);
fn frida_kmod_current_thread_id() -> u64;
fn frida_kmod_protect(address: u64, size: usize, gum_prot: u32) -> c_int;
fn frida_kmod_remap_writable(first_page: u64, n_pages: u32) -> *mut c_void;
fn frida_kmod_unmap_writable(mapping: *mut c_void);
fn frida_kmod_kernel_base() -> u64;
fn frida_kmod_kernel_size() -> u64;
fn frida_kmod_enumerate_modules(func: FoundModuleFunc, user_data: *mut c_void);
fn frida_kmod_watch_modules(func: ModuleEventFunc, user_data: *mut c_void);
fn frida_kmod_unwatch_modules();
fn frida_kmod_enumerate_module_symbols(
base: u64,
func: FoundModuleSymbolFunc,
user_data: *mut c_void,
) -> c_int;
fn frida_kmod_enumerate_module_exports(
base: u64,
func: FoundModuleExportFunc,
user_data: *mut c_void,
) -> c_int;
fn frida_kmod_find_symbol(name: *const c_char) -> u64;
fn frida_kmod_find_function(name: *const c_char) -> u64;
fn frida_kmod_symbol_name_from_address(
address: u64,
buffer: *mut c_char,
size: usize,
symbol_address: *mut u64,
) -> c_int;
fn frida_kmod_enumerate_symbols(func: FoundSymbolFunc, user_data: *mut c_void) -> c_int;
}
+58 -2
View File
@@ -1,13 +1,17 @@
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use core::ffi::c_void;
use core::ffi::{CStr, c_char, c_void};
pub fn field_offset(container: &str, field: &str) -> Option<usize> {
if let Some(told) = told_by_the_loader(container, field) {
return Some(told);
}
let types = described_types()?;
let described = types.named(container)?;
types.offset_of(&described, field).map(|bits| bits / 8)
types.offset_of_path(&described, field).map(|bits| bits / 8)
}
pub fn resolve_struct(container: &str) -> Option<usize> {
@@ -96,11 +100,43 @@ pub fn types_are_described() -> bool {
}
pub fn struct_size(container: &str) -> Option<usize> {
if let Some(told) = told_by_the_loader(container, "") {
return Some(told);
}
let types = described_types()?;
Some(types.named(container)?.size as usize)
}
fn told_by_the_loader(container: &str, field: &str) -> Option<usize> {
let mut entry = unsafe { _frida_kmod_layout } as *const ToldLayout;
if entry.is_null() {
return None;
}
unsafe {
while !(*entry).container.is_null() {
let told = &*entry;
if CStr::from_ptr(told.container).to_bytes() == container.as_bytes()
&& CStr::from_ptr(told.field).to_bytes() == field.as_bytes()
{
return Some(told.value);
}
entry = entry.add(1);
}
}
None
}
#[repr(C)]
struct ToldLayout {
container: *const c_char,
field: *const c_char,
value: usize,
}
struct DescribedTypes {
types: &'static [u8],
names: &'static [u8],
@@ -437,6 +473,25 @@ impl DescribedTypes {
described.kind == KIND_ENUM || described.kind == KIND_WIDE_ENUM
}
fn offset_of_path(&self, described: &Described, path: &str) -> Option<usize> {
let Some((member, rest)) = path.split_once('.') else {
return self.offset_of(described, path);
};
let (placement, type_id) = self.member_named(described, member)?;
let within = self.with_id(type_id)?;
Some(placement + self.offset_of_path(&within, rest)?)
}
fn member_named(&self, described: &Described, wanted: &str) -> Option<(usize, u32)> {
(0..described.members).find_map(|index| {
let member = described.body + (index * MEMBER_SIZE);
(self.name_of(word_at(self.types, member)) == wanted)
.then(|| (self.placement_of(described, member), word_at(self.types, member + 4)))
})
}
// A structure the kernel lays out itself keeps its fields in a member with no name of its
// own, so a field is looked for through those as well, and answers where it sits in the
// structure the question was asked about.
@@ -619,4 +674,5 @@ const MAX_DEPTH: usize = 8;
unsafe extern "C" {
static ___start_BTF: *const c_void;
static ___stop_BTF: *const c_void;
static _frida_kmod_layout: usize;
}
+18 -15
View File
@@ -1,10 +1,3 @@
// Linux backend. The agent reaches the kernel in one of two ways, and the rest
// of the agent cannot tell which: loaded from the inside as a kernel module,
// where every primitive is a call into a shim kbuild compiled against the
// target's headers; or injected from the outside like XNU and Windows, where
// the host patches the addresses it needs into .kernel_addrs and the memory it
// cannot touch is reached over the hostlink.
use alloc::string::String;
pub fn stack_headroom() -> usize {
@@ -20,8 +13,6 @@ pub(crate) const STACK_SPAN: usize = 16 * 1024;
#[cfg(any(target_arch = "arm", target_arch = "x86"))]
pub(crate) const STACK_SPAN: usize = 8 * 1024;
#[cfg(feature = "linux")]
mod kmod;
#[cfg(feature = "linux-injected")]
mod arena;
#[cfg(feature = "linux-injected")]
@@ -52,9 +43,15 @@ mod threads;
pub(crate) mod user;
#[cfg(feature = "linux-injected")]
mod processes;
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
mod cloak;
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
mod control;
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
mod process_memory;
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
mod user_threads;
#[cfg(feature = "linux")]
pub use self::kmod::*;
#[cfg(feature = "linux-injected")]
pub use self::injection::*;
#[cfg(feature = "linux-injected")]
@@ -63,17 +60,23 @@ pub use self::relay::*;
pub use self::facade::*;
#[cfg(feature = "linux-injected")]
pub use self::native::{
alloc_dma, free_dma, get_kernel_base, get_kernel_size, install_interrupt_handler, map_io,
map_pages,
mmio_interrupt, pci_interrupt, release_fault_reporter, release_interrupt, run_when_ready,
patch_text, set_kernel_base, set_protection, virt_to_phys,
alloc_dma, free_dma, get_kernel_base, get_kernel_size, hand_back_to_the_loader,
install_interrupt_handler, loaded_from_the_inside, map_io, map_pages, mmio_interrupt, pci_interrupt,
release_fault_reporter, release_interrupt, remap_writable, run_when_ready, patch_text,
set_kernel_base, set_protection, unmap_writable, virt_to_phys,
};
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
pub use self::native::learn_when_to_sleep;
#[cfg(feature = "linux-injected")]
pub use self::mapped::*;
#[cfg(feature = "linux-injected")]
pub use self::modules::*;
#[cfg(feature = "linux-injected")]
pub use self::processes::*;
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
pub use self::control::{
an_operation_waits, carry_out_operations, close as close_control_channel, open as open_control_channel,
};
#[cfg(feature = "linux-injected")]
pub use self::spawn::*;
#[cfg(feature = "linux-injected")]
+54
View File
@@ -9,6 +9,7 @@ use crate::bindings::{g_free, gsize, gum_memory_read};
use super::{LoadedModule, ModuleEvent};
pub fn enumerate_modules() -> Vec<LoadedModule> {
let _held = ModuleListLock::take();
let Some(layout) = module_layout() else {
return Vec::new();
};
@@ -38,6 +39,7 @@ pub fn enumerate_module_symbols(
base: u64,
on_symbol: &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool,
) -> bool {
let _held = ModuleListLock::take();
let Some(layout) = module_layout() else {
return false;
};
@@ -87,6 +89,7 @@ pub fn enumerate_module_exports(
base: u64,
on_export: &mut dyn FnMut(*const c_char, u64) -> bool,
) -> bool {
let _held = ModuleListLock::take();
let Some(layout) = module_layout() else {
return false;
};
@@ -152,6 +155,52 @@ fn relative_target(at: usize) -> usize {
at.wrapping_add_signed(offset as isize)
}
struct ModuleListLock {
mutex: *mut c_void,
}
impl ModuleListLock {
fn take() -> Self {
let mutex = unsafe { _module_mutex } as *mut c_void;
let lockable = !mutex.is_null() && unsafe { _mutex_lock.is_some() && _mutex_unlock.is_some() };
if !lockable {
return Self { mutex: ptr::null_mut() };
}
unsafe { lock_mutex(mutex) };
Self { mutex }
}
}
impl Drop for ModuleListLock {
fn drop(&mut self) {
if !self.mutex.is_null() {
unsafe { unlock_mutex(self.mutex) };
}
}
}
#[cfg(not(target_arch = "x86"))]
unsafe fn lock_mutex(mutex: *mut c_void) {
unsafe { _mutex_lock.unwrap()(mutex) };
}
#[cfg(not(target_arch = "x86"))]
unsafe fn unlock_mutex(mutex: *mut c_void) {
unsafe { _mutex_unlock.unwrap()(mutex) };
}
#[cfg(target_arch = "x86")]
unsafe fn lock_mutex(mutex: *mut c_void) {
unsafe { frida_k_mutex_lock(mutex) };
}
#[cfg(target_arch = "x86")]
unsafe fn unlock_mutex(mutex: *mut c_void) {
unsafe { frida_k_mutex_unlock(mutex) };
}
pub fn watch_modules(on_event: fn(ModuleEvent, LoadedModule)) {
if module_layout().is_none() {
return;
@@ -778,10 +827,15 @@ unsafe extern "C" {
) -> c_int;
fn frida_k_register_module_notifier(block: *mut c_void) -> c_int;
fn frida_k_unregister_module_notifier(block: *mut c_void) -> c_int;
fn frida_k_mutex_lock(mutex: *mut c_void);
fn frida_k_mutex_unlock(mutex: *mut c_void);
}
unsafe extern "C" {
static _modules: *const c_void;
static _module_mutex: *const c_void;
static _mutex_lock: Option<unsafe extern "C" fn(*mut c_void)>;
static _mutex_unlock: Option<unsafe extern "C" fn(*mut c_void)>;
static _register_module_notifier: Option<unsafe extern "C" fn(*mut c_void) -> c_int>;
#[allow(dead_code)]
static _unregister_module_notifier: Option<unsafe extern "C" fn(*mut c_void) -> c_int>;
+447 -42
View File
@@ -1,14 +1,9 @@
// The agent injected from the outside: the host resolves the kernel functions
// below and patches their addresses into .kernel_addrs, the same way it does
// for XNU and Windows. Everything the kernel would otherwise have to lend us --
// its symbol table, its module list, the right to write to its pages -- comes
// from the host instead, over the hostlink.
use core::ffi::{c_char, c_int, c_uint, c_void};
use core::ptr;
use core::ptr::read_volatile;
use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
#[cfg(target_arch = "aarch64")]
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
use core::sync::atomic::{AtomicI32, fence};
#[cfg(not(target_arch = "arm"))]
use core::sync::atomic::AtomicUsize;
use crate::kernel::ThreadEntry;
@@ -47,6 +42,16 @@ pub fn release_fault_reporter() {
super::kfault::uninstall();
}
pub fn loaded_from_the_inside() -> bool {
unsafe { _frida_kmod_exit_worker != 0 }
}
pub fn hand_back_to_the_loader() {
if loaded_from_the_inside() {
unsafe { frida_kmod_exit_worker() };
}
}
pub fn send_signal(signal: c_int, task: usize) {
unsafe { _send_sig(signal, task as *mut c_void, 1) };
}
@@ -152,6 +157,17 @@ pub(crate) fn kcfi_thunk(callback: usize, _type_ref: Option<usize>) -> usize {
callback
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn kernel_thread_function(cache: &AtomicUsize, callback: KthreadFn) -> KthreadFn {
let ref_fn = unsafe { _kthread_worker_fn.map(|f| f as usize) };
unsafe { core::mem::transmute(kcfi_thunk_cached(cache, callback as usize, ref_fn)) }
}
#[cfg(target_arch = "x86_64")]
pub(crate) fn kernel_thread_function(_cache: &AtomicUsize, callback: KthreadFn) -> KthreadFn {
callback
}
#[cfg(target_arch = "aarch64")]
fn kcfi_thunk_cached(cache: &AtomicUsize, callback: usize, type_ref: Option<usize>) -> usize {
let existing = cache.load(Ordering::Acquire);
@@ -212,28 +228,38 @@ pub fn spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> isize {
#[cfg(target_arch = "x86")]
pub fn alloc(size: usize) -> *mut u8 {
unsafe { frida_k_alloc(size, GFP_KERNEL as usize) }
unsafe { frida_k_alloc(size, allocation_flags() as usize) }
}
#[cfg(not(target_arch = "x86"))]
pub fn alloc(size: usize) -> *mut u8 {
let flags = allocation_flags();
unsafe {
if let Some(f) = ___kmalloc_noprof {
f(size, GFP_KERNEL)
f(size, flags)
} else if let Some(f) = _kmalloc {
f(size, GFP_KERNEL)
f(size, flags)
} else {
___kmalloc.unwrap()(size, GFP_KERNEL)
___kmalloc.unwrap()(size, flags)
}
}
}
fn allocation_flags() -> u32 {
if can_sleep() { GFP_KERNEL } else { GFP_ATOMIC }
}
pub fn free(ptr: *mut u8, _size: usize) {
unsafe { _kfree(ptr) };
}
pub fn alloc_heap(size: usize) -> *mut u8 {
unsafe {
#[cfg(not(target_arch = "x86"))]
if let Some(f) = _vmalloc_noprof {
return f(size);
}
if let Some(f) = _vmalloc {
return f(size);
}
@@ -242,7 +268,6 @@ pub fn alloc_heap(size: usize) -> *mut u8 {
alloc(size)
}
#[cfg(target_arch = "x86")]
pub fn alloc_code(size: usize) -> *mut u8 {
unsafe {
@@ -253,7 +278,9 @@ pub fn alloc_code(size: usize) -> *mut u8 {
#[cfg(not(target_arch = "x86"))]
pub fn alloc_code(size: usize) -> *mut u8 {
unsafe {
let code = if let Some(f) = _execmem_alloc {
let code = if let Some(f) = _execmem_alloc_rw {
f(EXECMEM_MODULE_TEXT, size)
} else if let Some(f) = _execmem_alloc {
f(EXECMEM_MODULE_TEXT, size)
} else {
_module_alloc.unwrap()(size)
@@ -283,6 +310,29 @@ pub fn free_code(ptr: *mut u8, _size: usize) {
}
}
#[cfg(not(target_arch = "x86"))]
pub fn remap_writable(first_page: u64, n_pages: u32) -> Option<*mut c_void> {
let remap = unsafe { _frida_kmod_remap_writable }?;
let alias = unsafe { remap(first_page, n_pages) };
(!alias.is_null()).then_some(alias)
}
#[cfg(target_arch = "x86")]
pub fn remap_writable(_first_page: u64, _n_pages: u32) -> Option<*mut c_void> {
None
}
#[cfg(not(target_arch = "x86"))]
pub fn unmap_writable(alias: *mut c_void) -> bool {
unsafe { _frida_kmod_unmap_writable }.is_some_and(|unmap| unsafe { unmap(alias) })
}
#[cfg(target_arch = "x86")]
pub fn unmap_writable(_alias: *mut c_void) -> bool {
false
}
pub fn page_size() -> usize {
crate::gum::page_size_the_kernel_runs_with()
}
@@ -392,13 +442,18 @@ pub fn free_dma(ptr: *mut u8, size: usize) {
free(ptr, size);
}
pub fn wait(_token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut() -> bool) {
pub fn wait(token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut() -> bool) {
if !can_sleep() {
spin_until(check, timeout_us);
return;
}
let waiters = waiters();
let mut waiter = [0usize; WAIT_ENTRY_WORDS];
let waiter = queue_this_thread(&mut waiter);
let waiter = queue_this_thread(&mut waiter, token);
unsafe { _prepare_to_wait_event(waiters, waiter, TASK_INTERRUPTIBLE) };
unsafe { _prepare_to_wait_event(waiters, waiter, TASK_IDLE) };
if !check() {
match timeout_us {
@@ -413,18 +468,19 @@ pub fn wait(_token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut()
unsafe { _finish_wait(waiters, waiter) };
}
pub fn wake(_token: *const u8) {
unsafe { ___wake_up(waiters(), TASK_NORMAL, WAKE_EVERY_WAITER, ptr::null_mut()) };
pub fn wake(token: *const u8) {
unsafe { ___wake_up(waiters(), TASK_NORMAL, WAKE_EVERY_WAITER, token as *mut c_void) };
}
pub fn poke_loop_wakeup() {}
fn queue_this_thread(entry: &mut [usize; WAIT_ENTRY_WORDS]) -> *mut c_void {
fn queue_this_thread(entry: &mut [usize; WAIT_ENTRY_WORDS], token: *const u8) -> *mut c_void {
let entry = entry.as_mut_ptr();
unsafe {
entry.add(WAIT_ENTRY_THREAD).write(current_task() as usize);
entry.add(WAIT_ENTRY_WAKE).write(_autoremove_wake_function as usize);
entry.add(WAIT_ENTRY_WAKE).write(waking_function());
entry.add(WAIT_ENTRY_TOKEN).write(token as usize);
let queued = entry.add(WAIT_ENTRY_QUEUED);
queued.write(queued as usize);
@@ -434,6 +490,37 @@ fn queue_this_thread(entry: &mut [usize; WAIT_ENTRY_WORDS]) -> *mut c_void {
entry as *mut c_void
}
#[cfg(target_arch = "aarch64")]
fn waking_function() -> usize {
let ref_fn = unsafe { _autoremove_wake_function } as usize;
kcfi_thunk_cached(&WAKE_THUNK, frida_cb_wake as *const () as usize, Some(ref_fn))
}
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86")))]
fn waking_function() -> usize {
frida_cb_wake as *const () as usize
}
#[cfg(target_arch = "x86")]
fn waking_function() -> usize {
unsafe { _autoremove_wake_function as usize }
}
#[cfg(not(target_arch = "x86"))]
unsafe extern "C" fn frida_cb_wake(entry: *mut c_void, mode: c_uint, flags: c_int, key: *mut c_void) -> c_int {
let awaited = unsafe { (entry as *const usize).add(WAIT_ENTRY_TOKEN).read() };
if key as usize != awaited {
return 0;
}
let wake_this_one: unsafe extern "C" fn(*mut c_void, c_uint, c_int, *mut c_void) -> c_int =
unsafe { core::mem::transmute(_autoremove_wake_function) };
unsafe { wake_this_one(entry, mode, flags, key) }
}
#[cfg(target_arch = "aarch64")]
static WAKE_THUNK: AtomicUsize = AtomicUsize::new(0);
fn waiters() -> *mut c_void {
let queue = (&raw mut WAITERS) as *mut c_void;
@@ -492,7 +579,7 @@ fn pci_irq_vector() -> Option<unsafe extern "C" fn(*mut c_void, c_uint) -> c_int
#[cfg(target_arch = "arm")]
pub fn mmio_interrupt(mmio_base: u64) -> Option<u32> {
let path = alloc::format!("/virtio_mmio@{:x}\0", mmio_base);
let node = unsafe { _of_find_node_opts_by_path(path.as_ptr() as *const c_char, ptr::null_mut()) };
let node = unsafe { _of_find_node_opts_by_path(path.as_ptr() as *const c_char, core::ptr::null_mut()) };
if node.is_null() {
return None;
}
@@ -513,7 +600,240 @@ pub fn mmio_interrupt(_mmio_base: u64) -> Option<u32> {
}
pub fn yield_now() {
unsafe { _schedule() };
if can_sleep() {
unsafe { _schedule() };
} else {
core::hint::spin_loop();
}
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
pub fn learn_when_to_sleep() {
let above = |found: Option<usize>| found.map_or(usize::MAX, |at| at + 1);
RCU_DEPTH_AT.store(above(super::layout::field_offset("task_struct", "rcu_read_lock_nesting")), Ordering::Relaxed);
PREEMPT_COUNT_AT.store(above(where_the_preempt_count_is()), Ordering::Release);
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
fn can_sleep() -> bool {
let Some(places) = sleeping_places() else {
return true;
};
let rcu_depth = places.rcu_read_lock_nesting
.map_or(0, |at| unsafe { ((current_task() as usize + at) as *const i32).read_volatile() });
current_preempt_count(places.preempt_count) == 0 && !interrupts_masked() && rcu_depth == 0
}
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
fn can_sleep() -> bool {
true
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
fn sleeping_places() -> Option<SleepingPlaces> {
let found = |at: usize| (at != 0 && at != usize::MAX).then(|| at - 1);
Some(SleepingPlaces {
preempt_count: found(PREEMPT_COUNT_AT.load(Ordering::Acquire))?,
rcu_read_lock_nesting: found(RCU_DEPTH_AT.load(Ordering::Relaxed)),
})
}
#[cfg(target_arch = "aarch64")]
fn where_the_preempt_count_is() -> Option<usize> {
super::layout::field_offset("task_struct", "thread_info.preempt_count")
}
#[cfg(target_arch = "x86_64")]
fn where_the_preempt_count_is() -> Option<usize> {
let standalone = unsafe { ___preempt_count };
if standalone != 0 {
return Some(standalone);
}
hot_per_cpu_place("preempt_count")
}
#[cfg(target_arch = "aarch64")]
fn current_preempt_count(at: usize) -> u32 {
unsafe { ((current_task() as usize + at) as *const u32).read_volatile() }
}
#[cfg(target_arch = "x86_64")]
fn current_preempt_count(at: usize) -> u32 {
let count: u32;
unsafe { core::arch::asm!("mov {:e}, gs:[{}]", out(reg) count, in(reg) at, options(nostack, readonly)) };
count & !PREEMPT_NEED_RESCHED
}
#[cfg(target_arch = "aarch64")]
fn interrupts_masked() -> bool {
let daif: usize;
unsafe { core::arch::asm!("mrs {}, daif", out(reg) daif, options(nomem, nostack)) };
daif & DAIF_IRQ_MASKED != 0
}
#[cfg(target_arch = "x86_64")]
fn interrupts_masked() -> bool {
let flags: usize;
unsafe { core::arch::asm!("pushfq", "pop {}", out(reg) flags, options(nomem, preserves_flags)) };
flags & RFLAGS_IF == 0
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
struct SleepingPlaces {
preempt_count: usize,
rcu_read_lock_nesting: Option<usize>,
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
static PREEMPT_COUNT_AT: AtomicUsize = AtomicUsize::new(0);
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
static RCU_DEPTH_AT: AtomicUsize = AtomicUsize::new(0);
fn spin_until(check: &mut dyn FnMut() -> bool, timeout_us: Option<u64>) {
let deadline = timeout_us.map(|us| monotonic_micros() + us as i64);
while !check() && deadline.is_none_or(|deadline| monotonic_micros() < deadline) {
core::hint::spin_loop();
}
}
#[cfg(target_arch = "x86_64")]
pub(crate) fn lock_spinlock(lock: usize) {
unsafe { __raw_spin_lock.unwrap()(lock as *mut c_void) };
}
#[cfg(target_arch = "x86_64")]
pub(crate) fn unlock_spinlock(lock: usize) {
unsafe { __raw_spin_unlock.unwrap()(lock as *mut c_void) };
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn lock_spinlock(lock: usize) {
if let Some(lock_it) = unsafe { __raw_spin_lock } {
return unsafe { lock_it(lock as *mut c_void) };
}
disable_preemption();
if let Err(seen) = counter_at(lock).compare_exchange(0, SPIN_LOCKED, Ordering::Acquire, Ordering::Relaxed) {
unsafe { _queued_spin_lock_slowpath(lock as *mut c_void, seen as u32) };
}
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn unlock_spinlock(lock: usize) {
if let Some(unlock_it) = unsafe { __raw_spin_unlock } {
return unsafe { unlock_it(lock as *mut c_void) };
}
unsafe { core::sync::atomic::AtomicU8::from_ptr(lock as *mut u8) }.store(0, Ordering::Release);
enable_preemption();
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn read_lock_irqsave(lock: usize) -> usize {
let flags = mask_interrupts();
disable_preemption();
if counter_at(lock).fetch_add(READER_BIAS, Ordering::Acquire) & WRITER_MASK != 0 {
unsafe { _queued_read_lock_slowpath(lock as *mut c_void) };
}
flags
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn read_unlock_irqrestore(lock: usize, flags: usize) {
counter_at(lock).fetch_sub(READER_BIAS, Ordering::Release);
enable_preemption();
restore_interrupts(flags);
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn write_lock_irqsave(lock: usize) -> usize {
let flags = mask_interrupts();
disable_preemption();
if counter_at(lock).compare_exchange(0, WRITER_LOCKED, Ordering::Acquire, Ordering::Relaxed).is_err() {
unsafe { _queued_write_lock_slowpath(lock as *mut c_void) };
}
flags
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn write_unlock_irqrestore(lock: usize, flags: usize) {
unsafe { core::sync::atomic::AtomicU8::from_ptr(lock as *mut u8) }.store(0, Ordering::Release);
enable_preemption();
restore_interrupts(flags);
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn disable_preemption() {
let count = preempt_count();
unsafe { count.write_volatile(count.read_volatile() + 1) };
core::sync::atomic::compiler_fence(Ordering::SeqCst);
}
#[cfg(target_arch = "aarch64")]
pub(crate) fn enable_preemption() {
core::sync::atomic::compiler_fence(Ordering::SeqCst);
let count = preempt_count();
unsafe { count.write_volatile(count.read_volatile() - 1) };
}
#[cfg(target_arch = "x86_64")]
pub(crate) fn disable_preemption() {
let at = sleeping_places().unwrap().preempt_count;
unsafe { core::arch::asm!("add dword ptr gs:[{}], 1", in(reg) at, options(nostack)) };
core::sync::atomic::compiler_fence(Ordering::SeqCst);
}
#[cfg(target_arch = "x86_64")]
pub(crate) fn enable_preemption() {
core::sync::atomic::compiler_fence(Ordering::SeqCst);
let at = sleeping_places().unwrap().preempt_count;
unsafe { core::arch::asm!("sub dword ptr gs:[{}], 1", in(reg) at, options(nostack)) };
}
#[cfg(target_arch = "aarch64")]
fn preempt_count() -> *mut u32 {
let at = sleeping_places().unwrap().preempt_count;
(current_task() as usize + at) as *mut u32
}
#[cfg(target_arch = "aarch64")]
fn mask_interrupts() -> usize {
let flags: usize;
unsafe {
core::arch::asm!("mrs {}, daif", "msr daifset, #3", out(reg) flags, options(nostack));
}
flags
}
#[cfg(target_arch = "aarch64")]
fn restore_interrupts(flags: usize) {
unsafe { core::arch::asm!("msr daif, {}", in(reg) flags, options(nostack)) };
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub(crate) fn hold(counter: usize) {
counter_at(counter).fetch_add(1, Ordering::Relaxed);
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub(crate) fn let_go(counter: usize) -> bool {
if counter_at(counter).fetch_sub(1, Ordering::Release) != 1 {
return false;
}
fence(Ordering::Acquire);
true
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
fn counter_at(address: usize) -> &'static AtomicI32 {
unsafe { AtomicI32::from_ptr(address as *mut i32) }
}
pub fn monotonic_micros() -> i64 {
@@ -567,33 +887,64 @@ pub fn current_task() -> u64 {
task
}
#[cfg(target_arch = "x86_64")]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub fn current_task() -> u64 {
let task: u64;
unsafe {
core::arch::asm!("mov {}, gs:[{}]", out(reg) task, in(reg) _current_task, options(nostack));
}
task
let place = per_cpu_place(unsafe { _current_task }, "current_task", &CURRENT_TASK_AT);
read_per_cpu_word(place) as u64
}
#[cfg(target_arch = "x86_64")]
pub fn top_of_stack() -> usize {
let top: usize;
unsafe {
core::arch::asm!("mov {}, gs:[{}]", out(reg) top, in(reg) _cpu_current_top_of_stack, options(nostack));
let place = per_cpu_place(unsafe { _cpu_current_top_of_stack }, "top_of_stack", &TOP_OF_STACK_AT);
read_per_cpu_word(place)
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn per_cpu_place(standalone: usize, hot_member: &str, cache: &AtomicUsize) -> usize {
let known = cache.load(Ordering::Relaxed);
if known != 0 {
return known;
}
top
let place = if standalone != 0 { standalone } else { hot_per_cpu_place(hot_member).unwrap() };
cache.store(place, Ordering::Relaxed);
place
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn hot_per_cpu_place(member: &str) -> Option<usize> {
let hot = unsafe { _pcpu_hot };
if hot == 0 {
return None;
}
Some(hot + super::layout::field_offset("pcpu_hot", member)?)
}
#[cfg(target_arch = "x86_64")]
fn read_per_cpu_word(place: usize) -> usize {
let value: usize;
unsafe { core::arch::asm!("mov {}, gs:[{}]", out(reg) value, in(reg) place, options(nostack, readonly)) };
value
}
#[cfg(target_arch = "x86")]
pub fn current_task() -> u64 {
let task: u32;
unsafe {
core::arch::asm!("mov {}, fs:[{}]", out(reg) task, in(reg) _current_task as u32, options(nostack));
}
task as u64
fn read_per_cpu_word(place: usize) -> usize {
let value: usize;
unsafe { core::arch::asm!("mov {}, fs:[{}]", out(reg) value, in(reg) place, options(nostack, readonly)) };
value
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
static CURRENT_TASK_AT: AtomicUsize = AtomicUsize::new(0);
#[cfg(target_arch = "x86_64")]
static TOP_OF_STACK_AT: AtomicUsize = AtomicUsize::new(0);
pub fn get_kernel_base() -> u64 {
KERNEL_BASE.load(Ordering::Relaxed)
}
@@ -814,22 +1165,38 @@ const KERNEL_PROCESS: u32 = 0;
const JS_SAFE_THREAD_ID_MASK: u64 = (1 << 48) - 1;
const GFP_KERNEL: u32 = 0xcc0;
const GFP_ATOMIC: u32 = 0x820;
const NUMA_NO_NODE: c_int = -1;
const EXECMEM_MODULE_TEXT: u32 = 1;
const EXECMEM_MODULE_TEXT: u32 = 0;
const PAGE_SIZE: usize = 4096;
#[cfg(target_arch = "aarch64")]
const TCR_T1SZ_SHIFT: u64 = 16;
#[cfg(target_arch = "aarch64")]
const TCR_T1SZ_MASK: u64 = 0x3f;
const TASK_INTERRUPTIBLE: c_uint = 1;
const TASK_IDLE: c_uint = 0x402;
const TASK_NORMAL: c_uint = 3;
const WAKE_EVERY_WAITER: c_int = 0;
const WAKE_SLACK_NS: u64 = 50_000;
#[cfg(target_arch = "aarch64")]
const SPIN_LOCKED: i32 = 1;
#[cfg(target_arch = "aarch64")]
const READER_BIAS: i32 = 1 << 9;
#[cfg(target_arch = "aarch64")]
const WRITER_LOCKED: i32 = 0xff;
#[cfg(target_arch = "aarch64")]
const WRITER_MASK: i32 = 0x1ff;
#[cfg(target_arch = "aarch64")]
const DAIF_IRQ_MASKED: usize = 1 << 7;
#[cfg(target_arch = "x86_64")]
const PREEMPT_NEED_RESCHED: u32 = 1 << 31;
#[cfg(target_arch = "x86_64")]
const RFLAGS_IF: usize = 1 << 9;
const HRTIMER_MODE_REL: c_int = 1;
const WAIT_ENTRY_WORDS: usize = 8;
const WAIT_ENTRY_THREAD: usize = 1;
const WAIT_ENTRY_WAKE: usize = 2;
const WAIT_ENTRY_QUEUED: usize = 3;
const WAIT_ENTRY_TOKEN: usize = WAIT_ENTRY_WORDS - 1;
const WAIT_QUEUE_WORDS: usize = 16;
const LOCK_KEY_WORDS: usize = 8;
#[cfg(target_arch = "aarch64")]
@@ -864,7 +1231,33 @@ struct Timespec64 {
tv_nsec: i64,
}
type KthreadFn = unsafe extern "C" fn(data: *mut c_void) -> c_int;
pub(crate) type KthreadFn = unsafe extern "C" fn(data: *mut c_void) -> c_int;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
unsafe extern "C" {
static __raw_spin_lock: Option<unsafe extern "C" fn(*mut c_void)>;
static __raw_spin_unlock: Option<unsafe extern "C" fn(*mut c_void)>;
}
#[cfg(target_arch = "aarch64")]
unsafe extern "C" {
static _queued_spin_lock_slowpath: unsafe extern "C" fn(*mut c_void, u32);
static _queued_read_lock_slowpath: unsafe extern "C" fn(*mut c_void);
static _queued_write_lock_slowpath: unsafe extern "C" fn(*mut c_void);
}
unsafe extern "C" {
static _frida_kmod_exit_worker: usize;
#[cfg(target_arch = "x86")]
#[link_name = "frida_k_frida_kmod_exit_worker"]
fn frida_kmod_exit_worker() -> !;
}
#[cfg(not(target_arch = "x86"))]
unsafe fn frida_kmod_exit_worker() -> ! {
let exit: unsafe extern "C" fn() -> ! = unsafe { core::mem::transmute(_frida_kmod_exit_worker) };
unsafe { exit() }
}
type IrqHandlerFn = unsafe extern "C" fn(irq: c_int, cookie: *mut c_void) -> c_int;
unsafe extern "C" {
@@ -900,6 +1293,14 @@ unsafe extern "C" {
static _vmalloc: Option<unsafe extern "C" fn(usize) -> *mut u8>;
// Executable memory moved out of the module loader in 6.12.
static _execmem_alloc: Option<unsafe extern "C" fn(u32, usize) -> *mut u8>;
#[cfg(not(target_arch = "x86"))]
static _execmem_alloc_rw: Option<unsafe extern "C" fn(u32, usize) -> *mut u8>;
#[cfg(not(target_arch = "x86"))]
static _vmalloc_noprof: Option<unsafe extern "C" fn(usize) -> *mut u8>;
#[cfg(not(target_arch = "x86"))]
static _frida_kmod_remap_writable: Option<unsafe extern "C" fn(u64, u32) -> *mut c_void>;
#[cfg(not(target_arch = "x86"))]
static _frida_kmod_unmap_writable: Option<unsafe extern "C" fn(*mut c_void) -> bool>;
static _set_memory_rw: Option<unsafe extern "C" fn(usize, c_int) -> c_int>;
static _set_memory_ro: Option<unsafe extern "C" fn(usize, c_int) -> c_int>;
static _set_memory_x: Option<unsafe extern "C" fn(usize, c_int) -> c_int>;
@@ -971,6 +1372,10 @@ unsafe extern "C" {
static _current_task: usize;
#[cfg(target_arch = "x86_64")]
static _cpu_current_top_of_stack: usize;
#[cfg(target_arch = "x86_64")]
static ___preempt_count: usize;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
static _pcpu_hot: usize;
#[cfg(target_arch = "arm")]
static ___pv_offset: *const u64;
#[cfg(target_arch = "arm")]
@@ -0,0 +1,99 @@
use core::ffi::{c_int, c_void};
use super::processes::HeldTask;
pub fn allocate(id: u32, size: usize, protection: usize) -> usize {
let Some(mm) = BorrowedMemory::of(id) else {
return 0;
};
let address = unsafe { _vm_mmap(core::ptr::null_mut(), 0, size, protection, MAP_PRIVATE | MAP_ANONYMOUS, 0) };
drop(mm);
if address >= FIRST_ERROR_ADDRESS { 0 } else { address }
}
pub fn release(id: u32, address: usize, size: usize) -> c_int {
let Some(mm) = BorrowedMemory::of(id) else {
return -ESRCH;
};
let result = unsafe { _vm_munmap(address, size) };
drop(mm);
result
}
pub fn write(id: u32, address: usize, data: &[u8]) -> usize {
transfer(id, address, data.as_ptr() as *mut c_void, data.len(), FOLL_WRITE)
}
pub fn read(id: u32, address: usize, buffer: &mut [u8]) -> usize {
transfer(id, address, buffer.as_mut_ptr() as *mut c_void, buffer.len(), 0)
}
pub fn read_own(address: usize, buffer: &mut [u8]) -> bool {
let len = buffer.len();
transfer_with(super::native::current_task() as usize, address, buffer.as_mut_ptr() as *mut c_void,
len, 0) == len
}
pub fn write_own(address: usize, data: &[u8]) -> bool {
transfer_with(super::native::current_task() as usize, address, data.as_ptr() as *mut c_void,
data.len(), FOLL_WRITE) == data.len()
}
fn transfer(id: u32, address: usize, buffer: *mut c_void, size: usize, flags: u32) -> usize {
let Some(task) = HeldTask::with_id(id) else {
return 0;
};
transfer_with(task.address(), address, buffer, size, flags)
}
fn transfer_with(task: usize, address: usize, buffer: *mut c_void, size: usize, flags: u32) -> usize {
let transferred = unsafe { _access_process_vm(task as *mut c_void, address, buffer, size as c_int, flags) };
transferred.max(0) as usize
}
struct BorrowedMemory(*mut c_void);
impl BorrowedMemory {
fn of(id: u32) -> Option<Self> {
let task = HeldTask::with_id(id)?;
let mm = unsafe { _get_task_mm(task.address() as *mut c_void) };
if mm.is_null() {
return None;
}
unsafe { _kthread_use_mm(mm) };
Some(Self(mm))
}
}
impl Drop for BorrowedMemory {
fn drop(&mut self) {
unsafe {
_kthread_unuse_mm(self.0);
_mmput(self.0);
}
}
}
const MAP_PRIVATE: usize = 0x02;
const MAP_ANONYMOUS: usize = 0x20;
const FOLL_WRITE: u32 = 0x01;
const FIRST_ERROR_ADDRESS: usize = usize::MAX - 4095;
const ESRCH: c_int = 3;
unsafe extern "C" {
static _get_task_mm: unsafe extern "C" fn(*mut c_void) -> *mut c_void;
static _mmput: unsafe extern "C" fn(*mut c_void);
static _kthread_use_mm: unsafe extern "C" fn(*mut c_void);
static _kthread_unuse_mm: unsafe extern "C" fn(*mut c_void);
static _vm_mmap: unsafe extern "C" fn(*mut c_void, usize, usize, usize, usize, usize) -> usize;
static _vm_munmap: unsafe extern "C" fn(usize, usize) -> c_int;
static _access_process_vm: unsafe extern "C" fn(*mut c_void, usize, *mut c_void, c_int, u32) -> c_int;
}
+125 -29
View File
@@ -72,6 +72,53 @@ pub unsafe fn let_go_of(file: *mut c_void) {
unsafe { _fput(file) };
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub(crate) struct HeldTask(usize);
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
impl HeldTask {
pub(crate) fn with_id(id: u32) -> Option<Self> {
let task = unsafe { _find_get_task_by_vpid(id as c_int) } as usize;
(task != 0).then(|| Self(task))
}
pub(crate) fn leader(&self) -> Option<Self> {
let group_leader = super::layout::field_offset("task_struct", "group_leader")?;
let usage = super::layout::field_offset("task_struct", "usage")?;
let flags = lock_tasklist();
let leader = unsafe { ((self.0 + group_leader) as *const usize).read() };
super::native::hold(leader + usage);
unlock_tasklist(flags);
Some(Self(leader))
}
pub(crate) fn address(&self) -> usize {
self.0
}
pub(crate) fn into_address(self) -> usize {
let task = self.0;
core::mem::forget(self);
task
}
pub(crate) unsafe fn from_address(task: usize) -> Self {
Self(task)
}
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
impl Drop for HeldTask {
fn drop(&mut self) {
let usage = super::layout::field_offset("task_struct", "usage").unwrap();
if super::native::let_go(self.0 + usage) {
unsafe { ___put_task_struct(self.0 as *mut c_void) };
}
}
}
pub fn task_with_id(id: u32) -> Option<usize> {
let layout = task_layout()?;
@@ -573,12 +620,16 @@ fn lock_tasklist() -> usize {
#[cfg(not(target_arch = "x86"))]
fn lock_tasklist() -> usize {
unsafe {
match (__raw_read_lock_irqsave, __raw_read_unlock_irqrestore) {
(Some(lock), Some(_)) => lock(_tasklist_lock),
_ => {
__raw_read_lock.unwrap()(_tasklist_lock);
match (__raw_read_lock_irqsave, __raw_read_unlock_irqrestore, __raw_read_lock) {
(Some(lock), Some(_), _) => lock(_tasklist_lock),
(_, _, Some(lock)) => {
lock(_tasklist_lock);
0
}
#[cfg(target_arch = "aarch64")]
_ => super::native::read_lock_irqsave(_tasklist_lock as usize),
#[cfg(not(target_arch = "aarch64"))]
_ => panic!("no way to lock the task list"),
}
}
}
@@ -591,9 +642,13 @@ fn unlock_tasklist(flags: usize) {
#[cfg(not(target_arch = "x86"))]
fn unlock_tasklist(flags: usize) {
unsafe {
match (__raw_read_lock_irqsave, __raw_read_unlock_irqrestore) {
(Some(_), Some(unlock)) => unlock(_tasklist_lock, flags),
_ => __raw_read_unlock.unwrap()(_tasklist_lock),
match (__raw_read_lock_irqsave, __raw_read_unlock_irqrestore, __raw_read_unlock) {
(Some(_), Some(unlock), _) => unlock(_tasklist_lock, flags),
(_, _, Some(unlock)) => unlock(_tasklist_lock),
#[cfg(target_arch = "aarch64")]
_ => super::native::read_unlock_irqrestore(_tasklist_lock as usize, flags),
#[cfg(not(target_arch = "aarch64"))]
_ => panic!("no way to unlock the task list"),
}
}
}
@@ -602,10 +657,6 @@ const PIDTYPE_TGID: c_int = 1;
const PIDTYPE_MAX: usize = 4;
pub fn cloak_task(task: *mut c_void) {
// detach_pid's arity differs across kernels (the classic detach_pid(task, type)
// versus the batched detach_pid(pids, task, type) paired with free_pids); calling
// the wrong one drives a UBSAN/CFI trap on hardened kernels. Leave the thread
// visible rather than risk it.
if true {
let _ = task;
return;
@@ -638,8 +689,17 @@ unsafe fn detach_pid(pids: *mut *mut c_void, task: *mut c_void, kind: c_int) {
}
#[cfg(not(target_arch = "x86"))]
unsafe fn detach_pid(pids: *mut *mut c_void, task: *mut c_void, kind: c_int) {
unsafe { _detach_pid(pids, task, kind) };
pub(crate) unsafe fn detach_pid(pids: *mut *mut c_void, task: *mut c_void, kind: c_int) {
type ReleasingDetachPid = unsafe extern "C" fn(*mut c_void, c_int);
unsafe {
if _free_pids.is_some() {
_detach_pid(pids, task, kind);
} else {
let releasing: ReleasingDetachPid = core::mem::transmute(_detach_pid);
releasing(task, kind);
}
}
}
#[cfg(target_arch = "x86")]
@@ -648,11 +708,13 @@ unsafe fn free_pids(pids: *mut *mut c_void) {
}
#[cfg(not(target_arch = "x86"))]
unsafe fn free_pids(pids: *mut *mut c_void) {
unsafe { _free_pids(pids) };
pub(crate) unsafe fn free_pids(pids: *mut *mut c_void) {
if let Some(free) = unsafe { _free_pids } {
unsafe { free(pids) };
}
}
unsafe fn unlink(node: usize) {
pub(crate) unsafe fn unlink(node: usize) {
let next = unsafe { (node as *const usize).read() };
let prev = unsafe { ((node + WORD_SIZE) as *const usize).read() };
unsafe {
@@ -664,34 +726,64 @@ unsafe fn unlink(node: usize) {
}
#[cfg(target_arch = "x86")]
fn write_lock_tasklist() -> usize {
pub(crate) fn write_lock_tasklist() -> usize {
unsafe { frida_k_write_lock_tasklist() }
}
#[cfg(not(target_arch = "x86"))]
fn write_lock_tasklist() -> usize {
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub(crate) unsafe fn unlink_for_readers(node: usize) {
let next = unsafe { (node as *const usize).read() };
let prev = unsafe { ((node + WORD_SIZE) as *const usize).read() };
unsafe {
match (__raw_write_lock_irqsave, __raw_write_unlock_irqrestore) {
(Some(lock), Some(_)) => lock(_tasklist_lock),
_ => {
__raw_write_lock.unwrap()(_tasklist_lock);
((next + WORD_SIZE) as *mut usize).write(prev);
(prev as *mut usize).write(next);
}
}
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
pub(crate) unsafe fn append_for_readers(node: usize, head: usize) {
let prev = unsafe { ((head + WORD_SIZE) as *const usize).read() };
unsafe {
(node as *mut usize).write(head);
((node + WORD_SIZE) as *mut usize).write(prev);
core::sync::atomic::AtomicUsize::from_ptr(prev as *mut usize)
.store(node, core::sync::atomic::Ordering::Release);
((head + WORD_SIZE) as *mut usize).write(node);
}
}
#[cfg(not(target_arch = "x86"))]
pub(crate) fn write_lock_tasklist() -> usize {
unsafe {
match (__raw_write_lock_irqsave, __raw_write_unlock_irqrestore, __raw_write_lock) {
(Some(lock), Some(_), _) => lock(_tasklist_lock),
(_, _, Some(lock)) => {
lock(_tasklist_lock);
0
}
#[cfg(target_arch = "aarch64")]
_ => super::native::write_lock_irqsave(_tasklist_lock as usize),
#[cfg(not(target_arch = "aarch64"))]
_ => panic!("no way to lock the task list"),
}
}
}
#[cfg(target_arch = "x86")]
fn write_unlock_tasklist(flags: usize) {
pub(crate) fn write_unlock_tasklist(flags: usize) {
unsafe { frida_k_write_unlock_tasklist(flags) };
}
#[cfg(not(target_arch = "x86"))]
fn write_unlock_tasklist(flags: usize) {
pub(crate) fn write_unlock_tasklist(flags: usize) {
unsafe {
match (__raw_write_lock_irqsave, __raw_write_unlock_irqrestore) {
(Some(_), Some(unlock)) => unlock(_tasklist_lock, flags),
_ => __raw_write_unlock.unwrap()(_tasklist_lock),
match (__raw_write_lock_irqsave, __raw_write_unlock_irqrestore, __raw_write_unlock) {
(Some(_), Some(unlock), _) => unlock(_tasklist_lock, flags),
(_, _, Some(unlock)) => unlock(_tasklist_lock),
#[cfg(target_arch = "aarch64")]
_ => super::native::write_unlock_irqrestore(_tasklist_lock as usize, flags),
#[cfg(not(target_arch = "aarch64"))]
_ => panic!("no way to unlock the task list"),
}
}
}
@@ -728,7 +820,11 @@ unsafe extern "C" {
#[cfg(not(target_arch = "x86"))]
static _detach_pid: unsafe extern "C" fn(*mut *mut c_void, *mut c_void, c_int);
#[cfg(not(target_arch = "x86"))]
static _free_pids: unsafe extern "C" fn(*mut *mut c_void);
static _free_pids: Option<unsafe extern "C" fn(*mut *mut c_void)>;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
static _find_get_task_by_vpid: unsafe extern "C" fn(c_int) -> *mut c_void;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
static ___put_task_struct: unsafe extern "C" fn(*mut c_void);
#[cfg(not(target_arch = "x86"))]
static __raw_write_lock: Option<unsafe extern "C" fn(*mut c_void)>;
#[cfg(not(target_arch = "x86"))]
+72
View File
@@ -625,6 +625,24 @@ int frida_k_unregister_module_notifier (void * a0)
return ((fn_t) _unregister_module_notifier) (a0);
}
extern void * _mutex_lock;
void frida_k_mutex_lock (void * a0)
{
typedef void (__attribute__ ((regparm (3))) * fn_t) (void *);
((fn_t) _mutex_lock) (a0);
}
extern void * _mutex_unlock;
void frida_k_mutex_unlock (void * a0)
{
typedef void (__attribute__ ((regparm (3))) * fn_t) (void *);
((fn_t) _mutex_unlock) (a0);
}
extern int frida_cb_module_state (void * nb, unsigned long action, void * data);
__attribute__ ((regparm (3))) int
@@ -658,3 +676,57 @@ frida_kcb_die (void * nb, unsigned long action, void * data)
{
return frida_cb_die (nb, action, data);
}
extern void * _frida_kmod_link_open;
int frida_k_frida_kmod_link_open (void)
{
typedef int (__attribute__ ((regparm (3))) * fn_t) (void);
return ((fn_t) _frida_kmod_link_open) ();
}
extern void * _frida_kmod_link_close;
void frida_k_frida_kmod_link_close (void)
{
typedef void (__attribute__ ((regparm (3))) * fn_t) (void);
((fn_t) _frida_kmod_link_close) ();
}
extern void * _frida_kmod_link_send;
int frida_k_frida_kmod_link_send (const void * data, unsigned long size)
{
typedef int (__attribute__ ((regparm (3))) * fn_t) (const void *, unsigned long);
return ((fn_t) _frida_kmod_link_send) (data, size);
}
extern void * _frida_kmod_link_recv;
long frida_k_frida_kmod_link_recv (void * data, unsigned long size)
{
typedef long (__attribute__ ((regparm (3))) * fn_t) (void *, unsigned long);
return ((fn_t) _frida_kmod_link_recv) (data, size);
}
extern void * _frida_kmod_link_pending;
_Bool frida_k_frida_kmod_link_pending (void)
{
typedef _Bool (__attribute__ ((regparm (3))) * fn_t) (void);
return ((fn_t) _frida_kmod_link_pending) ();
}
extern void * _frida_kmod_exit_worker;
void frida_k_frida_kmod_exit_worker (void)
{
typedef void (__attribute__ ((regparm (3))) * fn_t) (void);
((fn_t) _frida_kmod_exit_worker) ();
}
+2 -2
View File
@@ -125,7 +125,6 @@ fn leave() -> ! {
syscall(CLOSE, descriptor as usize, 0, 0, 0, 0, 0);
}
Arena::at(unsafe { ARENA }).gone();
syscall(EXIT_GROUP, 0, 0, 0, 0, 0, 0);
loop {}
@@ -459,7 +458,8 @@ unsafe extern "C" {
#[cfg(target_arch = "x86")]
unsafe fn start_thread(stack: usize, carried: usize) -> isize {
let launch = stack - 8;
let aligned_argument = stack - 16;
let launch = aligned_argument - 4;
unsafe {
(launch as *mut usize).write(enter_thread as *const () as usize);
((launch + 4) as *mut usize).write(carried);
@@ -0,0 +1,419 @@
use core::ffi::{c_int, c_void};
use core::mem::size_of;
use core::ptr;
use core::sync::atomic::{AtomicUsize, Ordering};
use super::layout::field_offset;
use super::native::{self, hold, let_go, lock_spinlock, unlock_spinlock};
use super::processes::{
HeldTask, append_for_readers, detach_pid, free_pids, unlink, unlink_for_readers,
write_lock_tasklist, write_unlock_tasklist,
};
pub fn start_in_process(id: u32, entry: usize, stack: usize, argument: usize, tls: usize) -> c_int {
let Some(layout) = describe_thread_group() else {
return -ENOSYS;
};
let Some(task) = HeldTask::with_id(id) else {
return -ESRCH;
};
let Some(leader) = task.leader() else {
return -ESRCH;
};
let mm = unsafe { _get_task_mm(task.address() as *mut c_void) };
if mm.is_null() {
return -ESRCH;
}
let birth = native::alloc(size_of::<Birth>()) as *mut Birth;
unsafe {
birth.write(Birth { entry, stack, argument, tls, leader: leader.into_address(), layout });
_kthread_use_mm(mm);
let started = _user_mode_thread(thread_entry(), birth as *mut c_void, 0);
_kthread_unuse_mm(mm);
_mmput(mm);
if started < 0 {
drop(HeldTask::from_address((*birth).leader));
native::free(birth as *mut u8, size_of::<Birth>());
}
started
}
}
struct Birth {
entry: usize,
stack: usize,
argument: usize,
tls: usize,
leader: usize,
layout: ThreadGroupLayout,
}
fn thread_entry() -> native::KthreadFn {
native::kernel_thread_function(&THREAD_ENTRY, frida_cb_user_thread)
}
static THREAD_ENTRY: AtomicUsize = AtomicUsize::new(0);
unsafe extern "C" fn frida_cb_user_thread(argument: *mut c_void) -> c_int {
let birth = unsafe { (argument as *const Birth).read() };
native::free(argument as *mut u8, size_of::<Birth>());
let leader = unsafe { HeldTask::from_address(birth.leader) };
let child = native::current_task() as usize;
join_thread_group(child, leader.address(), &birth.layout);
adopt_context_of(child, leader.address(), &birth.layout);
drop(leader);
super::injection::hand_userland_to(birth.entry, birth.stack, birth.argument);
use_thread_pointer(child, birth.tls, &birth.layout);
0
}
fn join_thread_group(child: usize, leader: usize, layout: &ThreadGroupLayout) {
let task = &layout.task;
let old_signal = read_word(child + task.signal);
let old_sighand = read_word(child + task.sighand);
let signal = read_word(leader + task.signal);
let sighand = read_word(leader + task.sighand);
let mut freed = [ptr::null_mut::<c_void>(); PIDTYPE_MAX];
let flags = write_lock_tasklist();
unsafe {
detach_pid(freed.as_mut_ptr(), child as *mut c_void, PIDTYPE_SID);
detach_pid(freed.as_mut_ptr(), child as *mut c_void, PIDTYPE_PGID);
detach_pid(freed.as_mut_ptr(), child as *mut c_void, PIDTYPE_TGID);
unlink_for_readers(child + task.tasks);
unlink(child + task.sibling);
unlink_for_readers(child + task.thread_node);
}
let parent = read_word(leader + task.real_parent);
write_word(child + task.real_parent, parent);
write_word(child + task.parent, parent);
write_word(child + task.group_leader, leader);
write_i32(child + task.tgid, read_i32(leader + task.tgid));
write_i32(child + task.exit_signal, -1);
write_word(child + task.signal, signal);
write_word(child + task.sighand, sighand);
hold(sighand + layout.sighand.count);
lock_spinlock(sighand + layout.sighand.siglock);
write_i32(signal + layout.signal.nr_threads, read_i32(signal + layout.signal.nr_threads) + 1);
if let Some(quick_threads) = layout.signal.quick_threads {
write_i32(signal + quick_threads, read_i32(signal + quick_threads) + 1);
}
hold(signal + layout.signal.live);
hold(signal + layout.signal.sigcnt);
unsafe {
(layout.calls.task_join_group_stop)(child as *mut c_void);
if let Some(thread_group) = task.thread_group {
append_for_readers(child + thread_group, leader + thread_group);
}
append_for_readers(child + task.thread_node, signal + layout.signal.thread_head);
}
unlock_spinlock(sighand + layout.sighand.siglock);
write_unlock_tasklist(flags);
unsafe {
free_pids(freed.as_mut_ptr());
(layout.calls.cleanup_sighand)(old_sighand as *mut c_void);
if let_go(old_signal + layout.signal.sigcnt) {
_kmem_cache_free(*_signal_cachep, old_signal as *mut c_void);
}
}
}
fn adopt_context_of(child: usize, leader: usize, layout: &ThreadGroupLayout) {
let task = &layout.task;
let files = read_word(leader + task.files);
let old_files = read_word(child + task.files);
hold(files + layout.files_count);
lock_spinlock(child + task.alloc_lock);
write_word(child + task.files, files);
unlock_spinlock(child + task.alloc_lock);
unsafe { (layout.calls.put_files_struct)(old_files as *mut c_void) };
let fs = read_word(leader + task.fs);
let old_fs = read_word(child + task.fs);
lock_spinlock(fs + layout.fs.lock);
write_i32(fs + layout.fs.users, read_i32(fs + layout.fs.users) + 1);
unlock_spinlock(fs + layout.fs.lock);
lock_spinlock(child + task.alloc_lock);
write_word(child + task.fs, fs);
unlock_spinlock(child + task.alloc_lock);
lock_spinlock(old_fs + layout.fs.lock);
let users_left = read_i32(old_fs + layout.fs.users) - 1;
write_i32(old_fs + layout.fs.users, users_left);
unlock_spinlock(old_fs + layout.fs.lock);
if users_left == 0 {
unsafe { (layout.calls.free_fs_struct)(old_fs as *mut c_void) };
}
let real_cred = read_word(leader + task.real_cred);
let cred = read_word(leader + task.cred);
let old_real_cred = read_word(child + task.real_cred);
let old_cred = read_word(child + task.cred);
hold(real_cred + layout.cred_usage);
write_word(child + task.real_cred, real_cred);
hold(cred + layout.cred_usage);
unsafe { AtomicUsize::from_ptr((child + task.cred) as *mut usize).store(cred, Ordering::Release) };
release_cred(old_cred, layout);
release_cred(old_real_cred, layout);
let namespaces = read_word(leader + task.nsproxy);
hold(namespaces + layout.nsproxy_count);
unsafe { (layout.calls.switch_task_namespaces)(child as *mut c_void, namespaces as *mut c_void) };
if let Some(undo) = &layout.semaphore_undo {
let undo_list = read_word(leader + undo.list);
if undo_list != 0 {
hold(undo_list + undo.refcnt);
write_word(child + undo.list, undo_list);
}
}
#[cfg(target_arch = "aarch64")]
if let Some(lengths) = &layout.vector_lengths {
adopt_vector_lengths(child, leader, lengths);
}
}
fn release_cred(cred: usize, layout: &ThreadGroupLayout) {
if let_go(cred + layout.cred_usage) {
unsafe { (layout.calls.put_cred)(cred as *mut c_void) };
}
}
#[cfg(target_arch = "aarch64")]
fn adopt_vector_lengths(child: usize, leader: usize, lengths: &VectorLengthLayout) {
for array in [lengths.current, lengths.on_exec] {
for kind in 0..VECTOR_KINDS {
let at = array + kind * size_of::<u32>();
write_i32(child + at, read_i32(leader + at));
}
}
}
#[cfg(target_arch = "x86_64")]
fn use_thread_pointer(child: usize, tls: usize, layout: &ThreadGroupLayout) {
const MSR_FS_BASE: u32 = 0xc000_0100;
write_word(child + layout.task.thread_pointer, tls);
unsafe {
core::arch::asm!("wrmsr", in("ecx") MSR_FS_BASE, in("eax") tls as u32,
in("edx") (tls >> 32) as u32, options(nostack));
}
}
#[cfg(target_arch = "aarch64")]
fn use_thread_pointer(child: usize, tls: usize, layout: &ThreadGroupLayout) {
write_word(child + layout.task.thread_pointer, tls);
unsafe { core::arch::asm!("msr tpidr_el0, {}", in(reg) tls, options(nostack)) };
}
struct ThreadGroupLayout {
calls: ThreadGroupCalls,
task: TaskLayout,
signal: SignalLayout,
sighand: SighandLayout,
fs: FsLayout,
files_count: usize,
cred_usage: usize,
nsproxy_count: usize,
semaphore_undo: Option<SemaphoreUndoLayout>,
#[cfg(target_arch = "aarch64")]
vector_lengths: Option<VectorLengthLayout>,
}
struct ThreadGroupCalls {
task_join_group_stop: unsafe extern "C" fn(*mut c_void),
cleanup_sighand: unsafe extern "C" fn(*mut c_void),
put_files_struct: unsafe extern "C" fn(*mut c_void),
free_fs_struct: unsafe extern "C" fn(*mut c_void),
put_cred: unsafe extern "C" fn(*mut c_void),
switch_task_namespaces: unsafe extern "C" fn(*mut c_void, *mut c_void),
}
struct TaskLayout {
tgid: usize,
real_parent: usize,
parent: usize,
group_leader: usize,
exit_signal: usize,
signal: usize,
sighand: usize,
tasks: usize,
sibling: usize,
thread_node: usize,
thread_group: Option<usize>,
files: usize,
fs: usize,
cred: usize,
real_cred: usize,
nsproxy: usize,
alloc_lock: usize,
thread_pointer: usize,
}
struct SignalLayout {
nr_threads: usize,
quick_threads: Option<usize>,
live: usize,
sigcnt: usize,
thread_head: usize,
}
struct SighandLayout {
count: usize,
siglock: usize,
}
struct FsLayout {
users: usize,
lock: usize,
}
struct SemaphoreUndoLayout {
list: usize,
refcnt: usize,
}
#[cfg(target_arch = "aarch64")]
struct VectorLengthLayout {
current: usize,
on_exec: usize,
}
fn describe_thread_group() -> Option<ThreadGroupLayout> {
Some(ThreadGroupLayout {
calls: unsafe {
ThreadGroupCalls {
task_join_group_stop: _task_join_group_stop?,
cleanup_sighand: ___cleanup_sighand?,
put_files_struct: _put_files_struct?,
free_fs_struct: _free_fs_struct?,
put_cred: ___put_cred?,
switch_task_namespaces: _switch_task_namespaces?,
}
},
task: TaskLayout {
tgid: field_offset("task_struct", "tgid")?,
real_parent: field_offset("task_struct", "real_parent")?,
parent: field_offset("task_struct", "parent")?,
group_leader: field_offset("task_struct", "group_leader")?,
exit_signal: field_offset("task_struct", "exit_signal")?,
signal: field_offset("task_struct", "signal")?,
sighand: field_offset("task_struct", "sighand")?,
tasks: field_offset("task_struct", "tasks")?,
sibling: field_offset("task_struct", "sibling")?,
thread_node: field_offset("task_struct", "thread_node")?,
thread_group: field_offset("task_struct", "thread_group"),
files: field_offset("task_struct", "files")?,
fs: field_offset("task_struct", "fs")?,
cred: field_offset("task_struct", "cred")?,
real_cred: field_offset("task_struct", "real_cred")?,
nsproxy: field_offset("task_struct", "nsproxy")?,
alloc_lock: field_offset("task_struct", "alloc_lock")?,
thread_pointer: field_offset("task_struct", THREAD_POINTER_FIELD)?,
},
signal: SignalLayout {
nr_threads: field_offset("signal_struct", "nr_threads")?,
quick_threads: field_offset("signal_struct", "quick_threads"),
live: field_offset("signal_struct", "live")?,
sigcnt: field_offset("signal_struct", "sigcnt")?,
thread_head: field_offset("signal_struct", "thread_head")?,
},
sighand: SighandLayout {
count: field_offset("sighand_struct", "count")?,
siglock: field_offset("sighand_struct", "siglock")?,
},
fs: FsLayout {
users: field_offset("fs_struct", "users")?,
lock: field_offset("fs_struct", "lock").or_else(|| field_offset("fs_struct", "seq.lock"))?,
},
files_count: field_offset("files_struct", "count")?,
cred_usage: field_offset("cred", "usage")?,
nsproxy_count: field_offset("nsproxy", "count")?,
semaphore_undo: describe_semaphore_undo(),
#[cfg(target_arch = "aarch64")]
vector_lengths: describe_vector_lengths(),
})
}
fn describe_semaphore_undo() -> Option<SemaphoreUndoLayout> {
Some(SemaphoreUndoLayout {
list: field_offset("task_struct", "sysvsem.undo_list")?,
refcnt: field_offset("sem_undo_list", "refcnt")?,
})
}
#[cfg(target_arch = "aarch64")]
fn describe_vector_lengths() -> Option<VectorLengthLayout> {
Some(VectorLengthLayout {
current: field_offset("task_struct", "thread.vl")?,
on_exec: field_offset("task_struct", "thread.vl_onexec")?,
})
}
#[cfg(target_arch = "x86_64")]
const THREAD_POINTER_FIELD: &str = "thread.fsbase";
#[cfg(target_arch = "aarch64")]
const THREAD_POINTER_FIELD: &str = "thread.uw.tp_value";
#[cfg(target_arch = "aarch64")]
const VECTOR_KINDS: usize = 2;
fn read_word(address: usize) -> usize {
unsafe { (address as *const usize).read() }
}
fn write_word(address: usize, value: usize) {
unsafe { (address as *mut usize).write(value) };
}
fn read_i32(address: usize) -> i32 {
unsafe { (address as *const i32).read() }
}
fn write_i32(address: usize, value: i32) {
unsafe { (address as *mut i32).write(value) };
}
const PIDTYPE_TGID: c_int = 1;
const PIDTYPE_PGID: c_int = 2;
const PIDTYPE_SID: c_int = 3;
const PIDTYPE_MAX: usize = 4;
const ESRCH: c_int = 3;
const ENOSYS: c_int = 38;
unsafe extern "C" {
static _get_task_mm: unsafe extern "C" fn(*mut c_void) -> *mut c_void;
static _mmput: unsafe extern "C" fn(*mut c_void);
static _kthread_use_mm: unsafe extern "C" fn(*mut c_void);
static _kthread_unuse_mm: unsafe extern "C" fn(*mut c_void);
static _user_mode_thread: unsafe extern "C" fn(native::KthreadFn, *mut c_void, usize) -> c_int;
static _task_join_group_stop: Option<unsafe extern "C" fn(*mut c_void)>;
static ___cleanup_sighand: Option<unsafe extern "C" fn(*mut c_void)>;
static _kmem_cache_free: unsafe extern "C" fn(*mut c_void, *mut c_void);
static _signal_cachep: *const *mut c_void;
static _put_files_struct: Option<unsafe extern "C" fn(*mut c_void)>;
static _free_fs_struct: Option<unsafe extern "C" fn(*mut c_void)>;
static ___put_cred: Option<unsafe extern "C" fn(*mut c_void)>;
static _switch_task_namespaces: Option<unsafe extern "C" fn(*mut c_void, *mut c_void)>;
}
-3
View File
@@ -2,9 +2,6 @@
// image and calls its `_start`. All of the agent lives in the library crate;
// this target exists only to produce that link. build.rs passes `--undefined
// _start` so the entrypoint survives --gc-sections.
//
// The Linux flavour is built as a static library instead and linked into a
// kernel module by kbuild — see linux/Kbuild.
#![no_std]
#![no_main]
+3 -1
View File
@@ -1059,7 +1059,9 @@ fn write_trampoline(trampoline: u32, entry: u32, stack_top: u32) {
0xff, 0xd2,
];
let stack_bottom = stack_top + 0x40 - INJECTION_STACK_SIZE as u32;
code[5..9].copy_from_slice(&stack_top.to_le_bytes());
let aligned_argument = stack_top - 16;
let sp_before_push = aligned_argument + 4;
code[5..9].copy_from_slice(&sp_before_push.to_le_bytes());
code[11..15].copy_from_slice(&entry.to_le_bytes());
code[32..36].copy_from_slice(&stack_bottom.to_le_bytes());
code[39..43].copy_from_slice(&kernel32_export(b"ExitThread").to_le_bytes());
+15 -8
View File
@@ -3338,18 +3338,12 @@ const BRANCH_TO_STEP_TARGET: u32 = 0xd61f_0200;
#[cfg(target_arch = "aarch64")]
const STATUS_NOT_FOUND: i32 = -1_073_741_275;
// The argument travels in a register here, and the stack only has to be aligned the way a call
// would have left it.
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
fn seed_stack(_process: *mut c_void, top: u64, _argument: u64) -> u64 {
(top - 64) & !15
}
// A cdecl entry looks for its argument above the return address, thus the stack is laid out
// before the thread runs, in the process that will read it.
#[cfg(target_arch = "x86")]
fn seed_stack(process: *mut c_void, top: u64, argument: u64) -> u64 {
let sp = (top - 32) & !15;
let aligned_argument = (top - 32) & !15;
let sp = aligned_argument - 4;
unsafe {
let mut apc_state = [0usize; APC_STATE_WORDS];
@@ -3362,6 +3356,19 @@ fn seed_stack(process: *mut c_void, top: u64, argument: u64) -> u64 {
sp
}
// The argument travels in a register here, and the stack only has to be aligned the way a call
// would have left it.
#[cfg(target_arch = "x86_64")]
fn seed_stack(_process: *mut c_void, top: u64, _argument: u64) -> u64 {
let before_call = (top - 64) & !15;
before_call - size_of::<u64>() as u64
}
#[cfg(target_arch = "aarch64")]
fn seed_stack(_process: *mut c_void, top: u64, _argument: u64) -> u64 {
(top - 64) & !15
}
fn free_in_process(process_handle: *mut c_void, address: u64) {
let mut base = address as *mut u8;
let mut region = 0usize;
+2
View File
@@ -705,6 +705,8 @@ namespace Frida.Barebone {
uint64 landing_zone = (call_landing_zone != 0) ? call_landing_zone : saved_regs["pc"].get_uint64 ();
uint64 doubleword_aligned_sp = saved_regs["sp"].get_uint64 () & ~7ULL;
regs["sp"] = doubleword_aligned_sp;
regs["pc"] = address_from_funcptr (impl);
regs["lr"] = landing_zone;
regs["cpsr"] = instruction_set_of (impl, saved_regs["cpsr"].get_uint64 ());
+4 -4
View File
@@ -8,7 +8,7 @@
"name": "frida-barebone-script-runtime",
"version": "1.0.0",
"devDependencies": {
"@types/frida-gum": "^19.12.0",
"@types/frida-gum": "^19.13.0",
"esbuild": "^0.25.5",
"typescript": "^5.8.3"
}
@@ -439,9 +439,9 @@
}
},
"node_modules/@types/frida-gum": {
"version": "19.12.0",
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.12.0.tgz",
"integrity": "sha512-fqXtwFA8AwE89tSdNHlaY+hNWI5j62bcO1xk5w0NSuHBIH6KRxYzIK6rp/JP7TXtRKU2ewBwx81FrHTfGF0q7w==",
"version": "19.13.0",
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.13.0.tgz",
"integrity": "sha512-tbmEZj9JUt3S6FqFE4XJPi/WhQuUU0/sM1IWS1KDKqvy6077hwNjG1v4MC3wpk+FO/WzfZ8G1gAKqAKJosfj1Q==",
"dev": true,
"license": "MIT"
},
+1 -1
View File
@@ -9,7 +9,7 @@
"watch": "tsc -w"
},
"devDependencies": {
"@types/frida-gum": "^19.12.0",
"@types/frida-gum": "^19.13.0",
"esbuild": "^0.25.5",
"typescript": "^5.8.3"
}
+4 -4
View File
@@ -36,7 +36,7 @@
"@frida/util": "^1.0.3",
"@frida/vm": "^2.0.0",
"@frida/web-streams": "^1.0.0",
"@types/frida-gum": "^19.12.0",
"@types/frida-gum": "^19.13.0",
"@types/node": "18.19.103",
"frida-fs": "7.1.1"
}
@@ -270,9 +270,9 @@
"license": "MIT"
},
"node_modules/@types/frida-gum": {
"version": "19.12.0",
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.12.0.tgz",
"integrity": "sha512-fqXtwFA8AwE89tSdNHlaY+hNWI5j62bcO1xk5w0NSuHBIH6KRxYzIK6rp/JP7TXtRKU2ewBwx81FrHTfGF0q7w==",
"version": "19.13.0",
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.13.0.tgz",
"integrity": "sha512-tbmEZj9JUt3S6FqFE4XJPi/WhQuUU0/sM1IWS1KDKqvy6077hwNjG1v4MC3wpk+FO/WzfZ8G1gAKqAKJosfj1Q==",
"license": "MIT"
},
"node_modules/@types/node": {
+1 -1
View File
@@ -32,7 +32,7 @@
"@frida/util": "^1.0.3",
"@frida/vm": "^2.0.0",
"@frida/web-streams": "^1.0.0",
"@types/frida-gum": "^19.12.0",
"@types/frida-gum": "^19.13.0",
"@types/node": "18.19.103",
"frida-fs": "7.1.1"
}
-6
View File
@@ -328,7 +328,6 @@ namespace Frida {
task = task_for_pid (pid);
resume_process (task);
} catch (Error e) {
} catch (Efni.Error e) {
} finally {
if (task != 0)
deallocate_port (task);
@@ -341,7 +340,6 @@ namespace Frida {
task = task_for_pid (pid);
resume_process_fast (task);
} catch (Error e) {
} catch (Efni.Error e) {
} finally {
if (task != 0)
deallocate_port (task);
@@ -607,8 +605,6 @@ namespace Frida {
policy_softener.retain (pid);
} catch (Error e) {
assert_not_reached ();
} catch (Efni.Error e) {
assert_not_reached ();
}
if (mapped_module != null)
@@ -741,8 +737,6 @@ namespace Frida {
yield disable_spawn_gating (cancellable);
} catch (Error e) {
assert_not_reached ();
} catch (Efni.Error e) {
assert_not_reached ();
}
}
}
-2
View File
@@ -17,8 +17,6 @@ namespace Frida {
_resource_store = new ResourceStore ();
} catch (Error e) {
assert_not_reached ();
} catch (Efni.Error e) {
assert_not_reached ();
}
}
return _resource_store;
+9
View File
@@ -5821,6 +5821,9 @@ namespace Frida {
#if COMPILER_BACKEND_INSTALLED_LIBRARY || COMPILER_BACKEND_INSTALLED_EXECUTABLE
internal string compiler_backend_path;
#endif
#if HAVE_INSTALLED_IDUND
internal string idund_path;
#endif
public void _init_asset_paths () {
var location = AssetLocation.detect ();
@@ -5828,6 +5831,9 @@ namespace Frida {
agent_path = location.derive_asset_path ("<arch>", Config.FRIDA_AGENT_NAME);
#if COMPILER_BACKEND_INSTALLED_LIBRARY || COMPILER_BACKEND_INSTALLED_EXECUTABLE
compiler_backend_path = location.derive_plugin_path (Config.FRIDA_COMPILER_BACKEND_NAME);
#endif
#if HAVE_INSTALLED_IDUND
idund_path = location.derive_plugin_path (Config.FRIDA_IDUND_NAME);
#endif
}
@@ -5836,6 +5842,9 @@ namespace Frida {
agent_path = null;
#if COMPILER_BACKEND_INSTALLED_LIBRARY || COMPILER_BACKEND_INSTALLED_EXECUTABLE
compiler_backend_path = null;
#endif
#if HAVE_INSTALLED_IDUND
idund_path = null;
#endif
}
#endif
+8 -4
View File
@@ -8,7 +8,7 @@ namespace Frida {
new Gee.HashMap<Idun.Device, FruityHostSessionProvider> ();
private Cancellable io_cancellable = new Cancellable ();
#if HAVE_IDUND_BLOB
#if HAVE_EMBEDDED_IDUND
private TemporaryDirectory daemon_dir = new TemporaryDirectory ();
private TemporaryFile? daemon_file;
@@ -28,8 +28,10 @@ namespace Frida {
#if !MACOS
migrate_pairing_store ();
#endif
#if HAVE_IDUND_BLOB
#if HAVE_EMBEDDED_IDUND
device_monitor.set_daemon_locator (extract_daemon);
#elif HAVE_INSTALLED_IDUND
device_monitor.set_daemon_locator (() => Frida.idund_path);
#endif
try {
yield device_monitor.start (cancellable);
@@ -38,7 +40,7 @@ namespace Frida {
}
}
#if HAVE_IDUND_BLOB
#if HAVE_EMBEDDED_IDUND
private string extract_daemon () throws Efni.Error {
#if WINDOWS
var blob = Frida.Data.Idund.get_idund_exe_blob ();
@@ -1074,7 +1076,9 @@ namespace Frida {
lldb_session = new LLDBSession (lldb, process, null, ExceptionHandlingStatus.FULL, device);
yield lldb_session.kill (cancellable);
yield lldb_session.close (cancellable);
} catch (Error e) {
} catch (GLib.Error e) {
if (e is IOError.CANCELLED)
throw (IOError) e;
var process_control = yield Idun.ProcessControlService.open (device, cancellable);
yield process_control.kill (pid, cancellable);
}
+1 -1
View File
@@ -2433,7 +2433,7 @@ namespace Frida {
Gum.Linux.enumerate_ranges ((Posix.pid_t) session.pid, READ | EXECUTE, d => {
unowned Gum.FileMapping? file = d.file;
if (file != null && file.path.has_prefix ("memfd:"))
if (file != null && file.path.has_prefix ("/memfd:"))
return true;
if (d.range.size >= code.length) {
code_start = d.range.base_address + d.range.size - round_size_to_page_size (code.length);
+10 -12
View File
@@ -109,7 +109,6 @@ namespace Frida {
var client = yield get_android_helper_client (cancellable);
yield client.transport.close (cancellable);
} catch (Error e) {
} catch (Efni.Error e) {
}
}
#endif
@@ -578,8 +577,6 @@ namespace Frida {
yield promise.future.wait_async (cancellable);
} catch (Error e) {
assert_not_reached ();
} catch (Efni.Error e) {
assert_not_reached ();
}
worker_thread.join ();
@@ -1448,7 +1445,6 @@ namespace Frida {
Posix.kill ((Posix.pid_t) pid, Posix.Signal.CONT);
}
} catch (Error e) {
} catch (Efni.Error e) {
}
}
zymbiote_patches.clear ();
@@ -1791,7 +1787,11 @@ namespace Frida {
}
MatchInfo info;
if (!/^([0-9a-f]+)-([0-9a-f]+) rw-p 00000000 00:00 0 *$/m.match (raw_maps, 0, out info)) {
try {
var anon_rw_pattern = new Regex ("^([0-9a-f]+)-([0-9a-f]+) rw-p 00000000 00:00 0 *$", MULTILINE | RAW);
if (!anon_rw_pattern.match (raw_maps, 0, out info))
assert_not_reached ();
} catch (RegexError e) {
assert_not_reached ();
}
assert (info != null);
@@ -2341,7 +2341,6 @@ namespace Frida {
var crash = parse_java_report (entry.message);
on_crash_received (crash);
} catch (Error e) {
} catch (Efni.Error e) {
}
return;
@@ -2382,7 +2381,6 @@ namespace Frida {
var crash = parse_native_report (report);
on_crash_received (crash);
} catch (Error e) {
} catch (Efni.Error e) {
}
}
@@ -2478,7 +2476,7 @@ namespace Frida {
if (stdout_pipe == null) {
try {
var process = new Subprocess.newv ({ "logcat", "-b", "crash", "-B" },
STDIN_INHERIT | STDOUT_PIPE | STDERR_SILENCE);
STDOUT_PIPE | STDERR_SILENCE);
logcat = process;
stdout_pipe = process.get_stdout_pipe ();
@@ -2894,7 +2892,7 @@ namespace Frida {
},
module_list => {
foreach (var path in module_list)
modules.add_value (new Variant.variant (new Variant.tuple ({ path })));
modules.add_value (new Variant.variant (new Variant.tuple ({ path.make_valid () })));
});
vardict_add (reply, "modules", modules.end ());
@@ -3111,7 +3109,7 @@ namespace Frida {
switch (attachment_type) {
case SyscallTracer.AttachmentType.STRING:
return (string) data;
return ((string) data).make_valid ();
case SyscallTracer.AttachmentType.BYTES: {
if (arg_type != null) {
Variant? v = try_decode_timespec (arg_type, data, len);
@@ -3208,7 +3206,7 @@ namespace Frida {
if (name_len != 0)
Memory.copy (str, path + 1, name_len);
return new Variant.tuple ({ "unix-abstract", (string) str });
return new Variant.tuple ({ "unix-abstract", ((string) str).make_valid () });
}
size_t n = 1;
@@ -3218,7 +3216,7 @@ namespace Frida {
var str = new uint8[n + 1];
Memory.copy (str, path, n);
return new Variant.tuple ({ "unix", (string) str });
return new Variant.tuple ({ "unix", ((string) str).make_valid () });
}
default:
return null;
+27 -72
View File
@@ -21,7 +21,7 @@ namespace Frida {
}
public Bytes make_request () {
Gee.List<string> missing = new TargetLibraries (pid).find_missing (module);
Gee.List<string> missing = new TargetLibraries (pid, module).find_missing ();
var header = HelperMappedAgentRequest ();
header.size = capacity;
@@ -40,10 +40,10 @@ namespace Frida {
public Bytes map (uint64 base_address, string entrypoint) throws Error {
try {
var libraries = new TargetLibraries (pid);
var libraries = new TargetLibraries (pid, module);
var memory = new MappedAgentMemory (pid, blob, module, base_address, capacity);
var mapper = Gum.Mapper.new_take_blob ("frida-agent.so", blob, libraries.make_resolver (module));
var mapper = Gum.Mapper.new_take_blob ("frida-agent.so", blob, libraries.make_resolver ());
mapper.set_target (memory);
Gum.Address extra_address;
@@ -61,16 +61,21 @@ namespace Frida {
}
private sealed class TargetLibraries {
private uint pid;
private Gum.ElfModule agent;
private ProcMapsSnapshot maps;
private Gee.Map<string, Gum.ElfModule> modules = new Gee.HashMap<string, Gum.ElfModule> ();
private Gee.List<ProcMapsSnapshot.Mapping> library_starts = new Gee.ArrayList<ProcMapsSnapshot.Mapping> ();
public TargetLibraries (uint pid) {
this.pid = pid;
public TargetLibraries (uint pid, Gum.ElfModule agent) {
this.agent = agent;
this.maps = ProcMapsSnapshot.from_pid (pid);
foreach (ProcMapsSnapshot.Mapping m in maps) {
if (is_library_start (m))
library_starts.add (m);
}
}
public Gee.List<string> find_missing (Gum.ElfModule agent) {
public Gee.List<string> find_missing () {
var missing = new Gee.ArrayList<string> ();
agent.enumerate_dependencies (d => {
if (find (d.name) == null)
@@ -80,7 +85,7 @@ namespace Frida {
return missing;
}
public Gum.ElfModuleResolver make_resolver (Gum.ElfModule agent) throws Error {
public Gum.ElfModuleResolver make_resolver () throws Error {
var resolver = new Gum.ElfModuleResolver ();
var pending = new Gee.ArrayQueue<string> ();
@@ -98,7 +103,7 @@ namespace Frida {
if (!seen.add (mapping.path))
continue;
Gum.ElfModule library = load (mapping);
Gum.ElfModule library = maps.load_image (mapping);
resolver.add_module (library, mapping.start);
library.enumerate_dependencies (d => {
@@ -111,16 +116,14 @@ namespace Frida {
}
private ProcMapsSnapshot.Mapping? find (string name) {
foreach (ProcMapsSnapshot.Mapping m in maps) {
if (is_library_start (m) && library_name_matches (name, Path.get_basename (m.path)))
foreach (ProcMapsSnapshot.Mapping m in library_starts) {
if (library_name_matches (name, Path.get_basename (m.path)) && is_compatible_image (m))
return m;
}
foreach (ProcMapsSnapshot.Mapping m in maps) {
if (!is_library_start (m))
continue;
foreach (ProcMapsSnapshot.Mapping m in library_starts) {
try {
if (load (m).soname == name)
if (maps.load_image (m).soname == name && is_compatible_image (m))
return m;
} catch (Error e) {
}
@@ -144,65 +147,17 @@ namespace Frida {
}
}
private Gum.ElfModule load (ProcMapsSnapshot.Mapping mapping) throws Error {
Gum.ElfModule? module = modules[mapping.path];
if (module == null) {
module = open_mapped_file (mapping);
if (module == null)
module = copy_from_target (mapping);
modules[mapping.path] = module;
}
return module;
}
private Gum.ElfModule? open_mapped_file (ProcMapsSnapshot.Mapping mapping) {
private bool is_compatible_image (ProcMapsSnapshot.Mapping start) {
Gum.ElfModule library;
try {
return new Gum.ElfModule.from_file (("/proc/%u/map_files/%" + uint64.FORMAT_MODIFIER + "x-%" +
uint64.FORMAT_MODIFIER + "x").printf (pid, mapping.start, mapping.end));
} catch (Gum.Error e) {
library = maps.load_image (start);
} catch (Error e) {
return false;
}
string path = "/proc/%u/root%s".printf (pid, mapping.path);
Posix.Stat st;
if (Posix.stat (path, out st) != 0 || st.st_ino != mapping.inode)
return null;
try {
return new Gum.ElfModule.from_file (path);
} catch (Gum.Error e) {
return null;
}
}
private Gum.ElfModule copy_from_target (ProcMapsSnapshot.Mapping first) throws Error {
var segments = new Gee.ArrayList<ProcMapsSnapshot.Mapping> ();
uint64 end = first.end;
foreach (ProcMapsSnapshot.Mapping m in maps) {
if (m.path == first.path && m.inode == first.inode && m.readable) {
segments.add (m);
end = uint64.max (end, m.end);
}
}
var image = new uint8[end - first.start];
var mem = new FileDescriptor (Posix.open ("/proc/%u/mem".printf (pid), Posix.O_RDONLY));
if (mem.handle == -1)
throw new Error.PERMISSION_DENIED ("Unable to read memory of process %u", pid);
foreach (ProcMapsSnapshot.Mapping m in segments)
mem.pread (image[m.start - first.start:m.end - first.start], m.start);
var blob = new Bytes.take ((owned) image);
var module = (Gum.ElfModule) Object.new (typeof (Gum.ElfModule),
"base-address", (Gum.Address) first.start,
"source-path", first.path,
"source-blob", blob,
"source-mode", Gum.ElfSourceMode.ONLINE);
try {
module.load ();
} catch (Gum.Error e) {
throw new Error.NOT_SUPPORTED ("Unable to parse %s: %s", first.path, e.message);
}
return module;
return library.machine == agent.machine &&
library.pointer_size == agent.pointer_size &&
maps.is_image_base (start);
}
}
+101 -64
View File
@@ -6,7 +6,9 @@ namespace Frida {
default = 1;
}
private uint32 pid;
private Gee.List<Mapping> maps = new Gee.ArrayList<Mapping> ();
private Gee.Map<string, Gum.ElfModule> images = new Gee.HashMap<string, Gum.ElfModule> ();
private const uint64 VM_READ = 1;
private const uint64 VM_WRITE = 2;
@@ -44,6 +46,7 @@ namespace Frida {
}
private void build_from_pid (uint32 pid) {
this.pid = pid;
maps.clear ();
var it = ProcMapsIter.for_pid (pid);
@@ -268,76 +271,105 @@ namespace Frida {
}
public Mapping? find_module_by_path_at_gen (string path, uint32 at_gen) {
Mapping? best_base = null;
int best_score = int.MIN;
Mapping? cur_base = null;
uint cur_total = 0;
uint cur_exec = 0;
#if ANDROID
unowned string libc_path = Gum.Process.get_libc_module ().path;
#endif
for (int i = 0; i < maps.size; i++) {
var m = maps[i];
if (!mapping_alive_at (m, at_gen))
continue;
unowned string mp = m.path;
if (mp == "[page size compat]")
continue;
if (mp != path) {
consider_candidate (ref best_base, ref best_score, cur_base, cur_total, cur_exec);
cur_base = null;
cur_total = 0;
cur_exec = 0;
continue;
}
#if ANDROID
if (mp == libc_path && m.shared)
continue;
#endif
if (m.file_offset == 0) {
consider_candidate (ref best_base, ref best_score, cur_base, cur_total, cur_exec);
cur_base = m;
cur_total = 0;
cur_exec = 0;
}
if (cur_base != null) {
cur_total++;
if (m.executable)
cur_exec++;
}
foreach (Mapping m in maps) {
if (m.path == path && m.file_offset == 0 && mapping_alive_at (m, at_gen) && is_image_base_at_gen (m, at_gen))
return m;
}
consider_candidate (ref best_base, ref best_score, cur_base, cur_total, cur_exec);
return best_base;
return null;
}
private static void consider_candidate (ref Mapping? best_base, ref int best_score, Mapping? candidate_base, uint total,
uint exec) {
if (candidate_base == null)
return;
public bool is_image_base (Mapping start) {
return is_image_base_at_gen (start, gen);
}
int score = score_candidate (total, exec);
if (score > best_score) {
best_score = score;
best_base = candidate_base;
private bool is_image_base_at_gen (Mapping start, uint32 at_gen) {
Gum.ElfModule image;
try {
image = load_image (start);
} catch (Error e) {
return false;
}
uint64 page_mask = ~((uint64) Gum.query_page_size () - 1);
bool mapped = true;
image.enumerate_segments (s => {
if (s.file_size == 0 || (s.protection & Gum.PageProtection.EXECUTE) == 0)
return true;
uint64 address = start.start + ((s.vm_address - image.preferred_address) & page_mask);
Mapping? m = find_mapping_at_gen (address, at_gen);
mapped = m != null &&
m.executable &&
!m.shared &&
m.inode == start.inode &&
m.path == start.path &&
m.file_offset + (address - m.start) == (s.file_offset & page_mask);
return mapped;
});
return mapped;
}
public Gum.ElfModule load_image (Mapping start) throws Error {
Gum.ElfModule? image = images[start.path];
if (image == null) {
image = open_mapped_file (start);
if (image == null)
image = copy_from_process (start);
images[start.path] = image;
}
return image;
}
private Gum.ElfModule? open_mapped_file (Mapping mapping) {
try {
return new Gum.ElfModule.from_file (("/proc/%u/map_files/%" + uint64.FORMAT_MODIFIER + "x-%" +
uint64.FORMAT_MODIFIER + "x").printf (pid, mapping.start, mapping.end));
} catch (Gum.Error e) {
}
string path = "/proc/%u/root%s".printf (pid, mapping.path);
Posix.Stat st;
if (Posix.stat (path, out st) != 0 || st.st_ino != mapping.inode)
return null;
try {
return new Gum.ElfModule.from_file (path);
} catch (Gum.Error e) {
return null;
}
}
private static int score_candidate (uint total, uint exec) {
int score = (int) total;
if (exec == 0)
score = -score;
return score;
private Gum.ElfModule copy_from_process (Mapping first) throws Error {
var segments = new Gee.ArrayList<Mapping> ();
uint64 end = first.end;
foreach (Mapping m in maps) {
if (m.path == first.path && m.inode == first.inode && m.readable && m.start >= first.start &&
mapping_alive_at (m, gen)) {
segments.add (m);
end = uint64.max (end, m.end);
}
}
var image = new uint8[end - first.start];
var mem = new FileDescriptor (Posix.open ("/proc/%u/mem".printf (pid), Posix.O_RDONLY));
if (mem.handle == -1)
throw new Error.PERMISSION_DENIED ("Unable to read memory of process %u", pid);
foreach (Mapping m in segments)
mem.pread (image[m.start - first.start:m.end - first.start], m.start);
var blob = new Bytes.take ((owned) image);
var module = (Gum.ElfModule) Object.new (typeof (Gum.ElfModule),
"base-address", (Gum.Address) first.start,
"source-path", first.path,
"source-blob", blob,
"source-mode", Gum.ElfSourceMode.ONLINE);
try {
module.load ();
} catch (Gum.Error e) {
throw new Error.NOT_SUPPORTED ("Unable to parse %s: %s", first.path, e.message);
}
return module;
}
}
@@ -417,8 +449,13 @@ namespace Frida {
return;
}
if (!/^([0-9a-f]+)-([0-9a-f]+) (\S{4}) ([0-9a-f]+) ([0-9a-f]{2,}:[0-9a-f]{2,}) (\d+) *([^\n]*)$/m.match (
contents, 0, out info)) {
try {
var line_pattern = new Regex (
"""^([0-9a-f]+)-([0-9a-f]+) (\S{4}) ([0-9a-f]+) ([0-9a-f]{2,}:[0-9a-f]{2,}) (\d+) *([^\n]*)$""",
MULTILINE | RAW);
if (!line_pattern.match (contents, 0, out info))
assert_not_reached ();
} catch (RegexError e) {
assert_not_reached ();
}
}
+25 -20
View File
@@ -597,31 +597,36 @@ if have_fruity_backend
]
backend_deps += idun_dep
if have_idund_blob
if have_idund
idund = custom_target('frida-idund',
input: idund_raw,
output: 'idund' + exe_suffix,
command: post_process + ['executable', 're.frida.Idund'],
install: assets_mode == 'installed',
install_dir: get_option('libdir') / frida_libdir_name / 'plugins',
)
idund_data = custom_target('frida-data-idund',
input: [
'fruity' / 'idund.resources',
idund,
],
output: [
'frida-data-idund.vapi',
'frida-data-idund.h',
'frida-data-idund.c',
'frida-data-idund-blob' + resource_blob_extension,
],
command: [
resource_compiler_cmd_array,
'-c', '@INPUT0@',
'-o', meson.current_build_dir() / 'frida-data-idund',
'@INPUT1@',
],
)
backend_sources += [idund_data]
if assets_mode == 'embedded'
idund_data = custom_target('frida-data-idund',
input: [
'fruity' / 'idund.resources',
idund,
],
output: [
'frida-data-idund.vapi',
'frida-data-idund.h',
'frida-data-idund.c',
'frida-data-idund-blob' + resource_blob_extension,
],
command: [
resource_compiler_cmd_array,
'-c', '@INPUT0@',
'-o', meson.current_build_dir() / 'frida-data-idund',
'@INPUT1@',
],
)
backend_sources += [idund_data]
endif
endif
endif
+5 -1
View File
@@ -1246,7 +1246,7 @@ namespace Frida {
}
private static async Json.Reader? try_open_already_installed_manifest (PackageNode node, File dir, Cancellable? cancellable)
throws Efni.Error, IOError {
throws IOError {
try {
Json.Reader r = yield load_json (dir.get_child ("package.json"), cancellable);
@@ -1264,6 +1264,8 @@ namespace Frida {
return r;
} catch (Error e) {
return null;
} catch (Efni.Error e) {
return null;
}
}
@@ -2255,6 +2257,8 @@ namespace Frida {
yield tar_reader.feed (buffer[:n], cancellable);
} catch (Error e) {
throw new Error.PROTOCOL ("Unable to extract tarball at %s: %s", tarball_url, e.message);
} catch (Efni.Error e) {
throw new Error.PROTOCOL ("Unable to extract tarball at %s: %s", tarball_url, e.message);
}
read_total += (size_t) n;
report_bucket += (size_t) n;
-3
View File
@@ -17,8 +17,6 @@ namespace Frida {
_resource_store = new ResourceStore ();
} catch (Error e) {
assert_not_reached ();
} catch (Efni.Error e) {
assert_not_reached ();
}
}
return _resource_store;
@@ -844,7 +842,6 @@ namespace Frida {
try {
protect_code (load_bias + segment.vm_address, (size_t) round_page (segment.vm_size));
} catch (Error e) {
} catch (Efni.Error e) {
}
return true;
});
+173 -4
View File
@@ -1,8 +1,12 @@
#include "frida-core.h"
#include <gio/gunixinputstream.h>
#include <gum/arch-arm/gumarmwriter.h>
#include <gum/arch-arm/gumthumbwriter.h>
#if defined (HAVE_I386)
# include <gum/arch-x86/gumx86writer.h>
#elif defined (HAVE_ARM)
# include <gum/arch-arm/gumarmwriter.h>
# include <gum/arch-arm/gumthumbwriter.h>
#endif
#include <gum/gum.h>
#include <dlfcn.h>
#include <errno.h>
@@ -22,6 +26,7 @@
#endif
#include <sys/wait.h>
#ifdef HAVE_ARM
enum {
GUM_QNX_ARM_REG_PC = ARM_REG_PC,
GUM_QNX_ARM_REG_LR = ARM_REG_LR,
@@ -52,6 +57,7 @@ enum {
#undef ARM_REG_PC
#define PSR_T_BIT (1 << 5)
#endif
#define CHECK_OS_RESULT(n1, cmp, n2, op) \
if (!(n1 cmp n2)) \
@@ -60,6 +66,14 @@ enum {
goto os_failure; \
}
#if defined (HAVE_I386)
# define FRIDA_QNX_GREG_PC(r) ((r)->x86.eip)
# define FRIDA_QNX_GREG_RETVAL(r) ((r)->x86.eax)
#elif defined (HAVE_ARM)
# define FRIDA_QNX_GREG_PC(r) ((r)->arm.gpr[GUM_QNX_ARM_REG_PC])
# define FRIDA_QNX_GREG_RETVAL(r) ((r)->arm.gpr[GUM_QNX_ARM_REG_R0])
#endif
#define FRIDA_REMOTE_PAYLOAD_SIZE (8192)
#define FRIDA_REMOTE_DATA_OFFSET (512)
#define FRIDA_REMOTE_STACK_OFFSET (FRIDA_REMOTE_PAYLOAD_SIZE - 512)
@@ -294,6 +308,138 @@ frida_emit_and_remote_execute (FridaEmitFunc func, const FridaInjectionParams *
return TRUE;
}
#if defined (HAVE_I386)
#define ARG_IMM(value) \
GUM_ARG_ADDRESS, GUM_ADDRESS (value)
#define ARG_REG(reg) \
GUM_ARG_REGISTER, GUM_X86_##reg
#define EMIT_CALL_IMM(func, n_args, ...) \
gum_x86_writer_put_call_address_with_arguments (&cw, GUM_CALL_CAPI, func, n_args, __VA_ARGS__)
#define EMIT_CALL_REG(reg, n_args, ...) \
gum_x86_writer_put_call_reg_with_arguments (&cw, GUM_CALL_CAPI, GUM_X86_##reg, n_args, __VA_ARGS__)
#define EMIT_LOAD_FIELD(reg, field) \
gum_x86_writer_put_mov_reg_address (&cw, GUM_X86_##reg, FRIDA_REMOTE_DATA_FIELD (field)); \
gum_x86_writer_put_mov_reg_reg_ptr (&cw, GUM_X86_##reg, GUM_X86_##reg)
#define EMIT_STORE_FIELD(field, reg) \
gum_x86_writer_put_mov_reg_address (&cw, GUM_X86_EAX, FRIDA_REMOTE_DATA_FIELD (field)); \
gum_x86_writer_put_mov_reg_ptr_reg (&cw, GUM_X86_EAX, GUM_X86_##reg)
static void
frida_emit_payload_code (const FridaInjectionParams * params, GumAddress remote_address, FridaCodeChunk * code)
{
GumX86Writer cw;
const gchar * skip_dlopen = "skip_dlopen";
const gchar * skip_dlclose = "skip_dlclose";
const gchar * skip_detach = "skip_detach";
gum_x86_writer_init (&cw, code->cur);
cw.pc = remote_address;
gum_x86_writer_put_push_reg (&cw, GUM_X86_EBP);
gum_x86_writer_put_mov_reg_reg (&cw, GUM_X86_EBP, GUM_X86_ESP);
gum_x86_writer_put_push_reg (&cw, GUM_X86_EBX);
gum_x86_writer_put_push_reg (&cw, GUM_X86_ESI);
gum_x86_writer_put_push_reg (&cw, GUM_X86_EDI);
gum_x86_writer_put_sub_reg_imm (&cw, GUM_X86_ESP, 4 + 8);
EMIT_CALL_IMM (frida_resolve_remote_libc_function (params->pid, "ConnectAttach_r"),
5,
ARG_IMM (ND_LOCAL_NODE),
ARG_IMM (getpid ()),
ARG_IMM (params->channel_id),
ARG_IMM (_NTO_SIDE_CHANNEL),
ARG_IMM (_NTO_COF_CLOEXEC));
gum_x86_writer_put_mov_reg_reg (&cw, GUM_X86_EDI, GUM_X86_EAX);
gum_x86_writer_put_call_address_with_arguments (&cw, GUM_CALL_CAPI,
frida_resolve_remote_libc_function (params->pid, "gettid"), 0);
EMIT_CALL_IMM (frida_resolve_remote_libc_function (params->pid, "MsgSendPulse_r"),
4,
ARG_REG (EDI),
ARG_IMM (-1),
ARG_IMM (FRIDA_QNX_PULSE_CODE_HELLO),
ARG_REG (EAX));
EMIT_LOAD_FIELD (ESI, module_handle);
gum_x86_writer_put_test_reg_reg (&cw, GUM_X86_ESI, GUM_X86_ESI);
gum_x86_writer_put_jcc_near_label (&cw, X86_INS_JNE, skip_dlopen, GUM_NO_HINT);
{
EMIT_CALL_IMM (frida_resolve_remote_libc_function (params->pid, "dlopen"),
2,
ARG_IMM (FRIDA_REMOTE_DATA_FIELD (so_path)),
ARG_IMM (RTLD_LAZY));
gum_x86_writer_put_mov_reg_reg (&cw, GUM_X86_ESI, GUM_X86_EAX);
EMIT_STORE_FIELD (module_handle, ESI);
}
gum_x86_writer_put_label (&cw, skip_dlopen);
EMIT_CALL_IMM (frida_resolve_remote_libc_function (params->pid, "dlsym"),
2,
ARG_REG (ESI),
ARG_IMM (FRIDA_REMOTE_DATA_FIELD (entrypoint_name)));
gum_x86_writer_put_mov_reg_reg (&cw, GUM_X86_EBX, GUM_X86_EAX);
gum_x86_writer_put_mov_reg_offset_ptr_u32 (&cw, GUM_X86_ESP, 0, FRIDA_UNLOAD_POLICY_IMMEDIATE);
gum_x86_writer_put_mov_reg_offset_ptr_reg (&cw, GUM_X86_ESP, 4, GUM_X86_EDI);
gum_x86_writer_put_mov_reg_reg (&cw, GUM_X86_EAX, GUM_X86_ESP);
gum_x86_writer_put_lea_reg_reg_offset (&cw, GUM_X86_EDX, GUM_X86_ESP, 4);
EMIT_CALL_REG (EBX,
3,
ARG_IMM (FRIDA_REMOTE_DATA_FIELD (entrypoint_data)),
ARG_REG (EAX),
ARG_REG (EDX));
gum_x86_writer_put_mov_reg_reg_ptr (&cw, GUM_X86_EAX, GUM_X86_ESP);
gum_x86_writer_put_cmp_reg_i32 (&cw, GUM_X86_EAX, FRIDA_UNLOAD_POLICY_IMMEDIATE);
gum_x86_writer_put_jcc_near_label (&cw, X86_INS_JNE, skip_dlclose, GUM_NO_HINT);
{
EMIT_CALL_IMM (frida_resolve_remote_libc_function (params->pid, "dlclose"),
1,
ARG_REG (ESI));
}
gum_x86_writer_put_label (&cw, skip_dlclose);
gum_x86_writer_put_mov_reg_reg_ptr (&cw, GUM_X86_EAX, GUM_X86_ESP);
gum_x86_writer_put_cmp_reg_i32 (&cw, GUM_X86_EAX, FRIDA_UNLOAD_POLICY_DEFERRED);
gum_x86_writer_put_jcc_near_label (&cw, X86_INS_JE, skip_detach, GUM_NO_HINT);
{
EMIT_LOAD_FIELD (EAX, worker_thread);
EMIT_CALL_IMM (frida_resolve_remote_libc_function (params->pid, "pthread_detach"),
1,
ARG_REG (EAX));
}
gum_x86_writer_put_label (&cw, skip_detach);
gum_x86_writer_put_mov_reg_reg_ptr (&cw, GUM_X86_EAX, GUM_X86_ESP);
EMIT_CALL_IMM (frida_resolve_remote_libc_function (params->pid, "MsgSendPulse_r"),
4,
ARG_REG (EDI),
ARG_IMM (-1),
ARG_IMM (FRIDA_QNX_PULSE_CODE_BYE),
ARG_REG (EAX));
EMIT_CALL_IMM (frida_resolve_remote_libc_function (params->pid, "ConnectDetach_r"),
1,
ARG_REG (EDI));
gum_x86_writer_put_add_reg_imm (&cw, GUM_X86_ESP, 4 + 8);
gum_x86_writer_put_pop_reg (&cw, GUM_X86_EDI);
gum_x86_writer_put_pop_reg (&cw, GUM_X86_ESI);
gum_x86_writer_put_pop_reg (&cw, GUM_X86_EBX);
gum_x86_writer_put_pop_reg (&cw, GUM_X86_EBP);
gum_x86_writer_put_xor_reg_reg (&cw, GUM_X86_EAX, GUM_X86_EAX);
gum_x86_writer_put_ret (&cw);
gum_x86_writer_flush (&cw);
code->cur = gum_x86_writer_cur (&cw);
code->size += gum_x86_writer_offset (&cw);
gum_x86_writer_clear (&cw);
}
#elif defined (HAVE_ARM)
#define EMIT_MOVE(dst, src) \
gum_arm_writer_put_mov_reg_reg (&cw, ARM_REG_##dst, ARM_REG_##src)
#define EMIT_ADD(dst, src, offset) \
@@ -333,6 +479,7 @@ frida_emit_payload_code (const FridaInjectionParams * params, GumAddress remote_
const gchar * skip_detach = "skip_detach";
gum_arm_writer_init (&cw, code->cur);
cw.pc = remote_address;
gum_arm_writer_put_push_regs (&cw, 4, ARM_REG_R5, ARM_REG_R6, ARM_REG_R7, ARM_REG_LR);
@@ -427,6 +574,8 @@ frida_emit_payload_code (const FridaInjectionParams * params, GumAddress remote_
gum_arm_writer_clear (&cw);
}
#endif
static GumAddress
frida_remote_alloc (pid_t pid, size_t size, int prot, GError ** error)
{
@@ -649,6 +798,25 @@ frida_remote_call (pid_t pid, GumAddress func, const GumAddress * args, gint arg
memcpy (&modified_registers, &saved_registers, sizeof (saved_registers));
#if defined (HAVE_I386)
{
guint32 sp, frame[1 + 16];
gint n = 0;
g_assert (args_length <= 16);
frame[n++] = 0xfffffff0;
for (i = 0; i != args_length; i++)
frame[n++] = args[i];
sp = ((modified_registers.x86.esp - 128 - (args_length * 4)) & ~15) - 4;
if (!frida_remote_write_fd (fd, sp, frame, n * 4, error))
goto beach;
modified_registers.x86.esp = sp;
modified_registers.x86.eip = func;
}
#elif defined (HAVE_ARM)
/*
* Set the PC to be the function address and SP to the stack address.
*/
@@ -681,11 +849,12 @@ frida_remote_call (pid_t pid, GumAddress func, const GumAddress * args, gint arg
* Set the LR to be a dummy address which will trigger a pagefault.
*/
modified_registers.arm.gpr[GUM_QNX_ARM_REG_LR] = 0xfffffff0;
#endif
ret = devctl (fd, DCMD_PROC_SETGREG, &modified_registers, sizeof (modified_registers), 0);
CHECK_OS_RESULT (ret, ==, 0, "DCMD_PROC_SETGREG");
while (modified_registers.arm.gpr[GUM_QNX_ARM_REG_PC] != 0xfffffff0)
while (FRIDA_QNX_GREG_PC (&modified_registers) != 0xfffffff0)
{
/*
* Continue the process, watching for FLTPAGE which should trigger when
@@ -713,7 +882,7 @@ frida_remote_call (pid_t pid, GumAddress func, const GumAddress * args, gint arg
}
if (retval != NULL)
*retval = modified_registers.arm.gpr[GUM_QNX_ARM_REG_R0];
*retval = FRIDA_QNX_GREG_RETVAL (&modified_registers);
/*
* Restore the registers and continue the process:
-2
View File
@@ -13,8 +13,6 @@ namespace Frida {
_resource_store = new ResourceStore ();
} catch (Error e) {
assert_not_reached ();
} catch (Efni.Error e) {
assert_not_reached ();
}
}
return _resource_store;
-1
View File
@@ -280,7 +280,6 @@ namespace Frida {
try {
icons = yield springboard_agent.fetch_application_icons (app_ids, cancellable);
} catch (Error e) {
} catch (Efni.Error e) {
}
}
+11 -1
View File
@@ -9,6 +9,7 @@
#include <windows.h>
#include <tlhelp32.h>
#include <strsafe.h>
#include <winternl.h>
#define CHECK_OS_RESULT(n1, cmp, n2, op) \
if (!((n1) cmp (n2))) \
@@ -100,6 +101,7 @@ typedef NTSTATUS (WINAPI * RtlCreateUserThreadFunc) (HANDLE process, SECURITY_DE
static void frida_propagate_open_process_error (guint32 pid, DWORD os_error, GError ** error);
static gboolean frida_enable_debug_privilege (void);
static gboolean frida_process_is_exiting (HANDLE process);
static gboolean frida_remote_worker_context_init (FridaRemoteWorkerContext * rwc, FridaInjectionDetails * details, GError ** error);
static gsize frida_remote_worker_context_emit_payload (FridaRemoteWorkerContext * rwc, gpointer code);
@@ -265,7 +267,7 @@ _frida_windows_helper_backend_free_inject_instance (void * inject_instance, gboo
if (instance->mappers != NULL)
{
if (!*is_resident)
if (!*is_resident && !frida_process_is_exiting (instance->process_handle))
g_ptr_array_foreach (instance->mappers, (GFunc) gum_mapper_unmap, NULL);
g_ptr_array_unref (instance->mappers);
}
@@ -713,6 +715,14 @@ beach:
return success;
}
static gboolean
frida_process_is_exiting (HANDLE process)
{
DWORD exit_code;
return GetExitCodeProcess (process, &exit_code) && exit_code != STILL_ACTIVE;
}
static gboolean
frida_remote_worker_context_init (FridaRemoteWorkerContext * rwc, FridaInjectionDetails * details, GError ** error)
{
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/brotli.git
url = https://github.com/frida-forks/brotli.git
revision = dd581fd4fea06096e3949ad6cb16775538d0ef47
depth = 1
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/efni.git
revision = 54525e06e27659afb6c664c18f9a10bbaac2dca9
revision = 3bcd99ff81b7b648ff4d784fb3154e55e7950c20
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/frida-gum.git
revision = 17.25.0
revision = c8f22bb1db7904adcc4ad046c0948ee9597a95fa
depth = 1
[provide]
+2 -2
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/glib.git
revision = c366177b9292432a26f6903fa82a71d83e4927eb
url = https://github.com/frida-forks/glib.git
revision = c6df0665dab0f971f66c5c46055b7d0b6085d1b1
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/idun.git
revision = eaf98270beab0ecf174e611d99036db6843a1865
revision = 6040e17b1ef871ff7ae5b18fffbe5f0396a0810c
depth = 1
[provide]
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/libbpf.git
url = https://github.com/frida-forks/libbpf.git
revision = 9c4e176810717e27b6990a8722b252fc1967667f
depth = 1
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/libgee.git
url = https://github.com/frida-forks/libgee.git
revision = 69621d2c665b75c1e064abdecd540ef1749615ee
depth = 1
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/libnice.git
url = https://github.com/frida-forks/libnice.git
revision = 91e2d02edb2999786d7ac854f99bdfd7832df64f
depth = 1
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/libusb.git
url = https://github.com/frida-forks/libusb.git
revision = 23d5a2cdc55aa4f0895960d08528e5aa13e7f7e2
depth = 1
+2 -2
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/lwip.git
revision = f8424fd177ae3654fa040b8b6bc0c32de7fd6ad9
url = https://github.com/frida-forks/lwip.git
revision = 857ac9ef0de0e47c8b8f7a0b66f09673238368d0
depth = 1
[provide]
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/lzfse.git
url = https://github.com/frida-forks/lzfse.git
revision = 5cfb7c86919d3c2c636d0d7552b51855a611ba1c
depth = 1
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/nghttp2.git
url = https://github.com/frida-forks/nghttp2.git
revision = 3e516202a0df939d6ab6bab47780fa35c5c4662b
depth = 1
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/ngtcp2.git
url = https://github.com/frida-forks/ngtcp2.git
revision = 0009518d5ac5e4ce80343fb5f3c719991ffd47af
depth = 1
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/openssl.git
url = https://github.com/frida-forks/openssl.git
revision = a94348d1cd4fb009e7c590fe4e193c2ea89f5ab8
depth = 1
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/selinux.git
url = https://github.com/frida-forks/selinux.git
revision = 507985d931eb1f0227218d5d845634e602426c20
depth = 1
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/termux-elf-cleaner.git
url = https://github.com/frida-forks/termux-elf-cleaner.git
revision = 85ed0baf1ce6abc1bf2cde92c808cc853d0bf791
depth = 1
+1 -1
View File
@@ -1,5 +1,5 @@
[wrap-git]
url = https://github.com/frida/usrsctp.git
url = https://github.com/frida-forks/usrsctp.git
revision = bfd359be44adc76c55efacce9a2f71ff0a7f8cd4
depth = 1
+3
View File
@@ -12,6 +12,9 @@ test_sources = [
if host_os_family == 'darwin'
test_sources += 'test-host-session-darwin-glue.m'
endif
if host_os_family == 'linux'
test_sources += 'test-host-session-linux-glue.c'
endif
test_vala_args = []
test_depends = []
+53
View File
@@ -0,0 +1,53 @@
#include <glib.h>
#ifdef HAVE_LINUX
#include <signal.h>
static void frida_host_session_test_linux_on_sigchld (int sig);
static struct sigaction frida_host_session_test_linux_saved_sigchld_action;
void
frida_host_session_test_linux_install_sigchld_handler (void)
{
struct sigaction action = { 0, };
action.sa_handler = frida_host_session_test_linux_on_sigchld;
sigemptyset (&action.sa_mask);
action.sa_flags = SA_RESTART;
sigaction (SIGCHLD, &action, &frida_host_session_test_linux_saved_sigchld_action);
}
gboolean
frida_host_session_test_linux_sigchld_handler_is_installed (void)
{
struct sigaction action;
sigaction (SIGCHLD, NULL, &action);
return action.sa_handler == frida_host_session_test_linux_on_sigchld;
}
void
frida_host_session_test_linux_reset_sigchld_handler (void)
{
struct sigaction action = { 0, };
action.sa_handler = SIG_DFL;
sigemptyset (&action.sa_mask);
sigaction (SIGCHLD, &action, &frida_host_session_test_linux_saved_sigchld_action);
}
void
frida_host_session_test_linux_restore_sigchld_handler (void)
{
sigaction (SIGCHLD, &frida_host_session_test_linux_saved_sigchld_action, NULL);
}
static void
frida_host_session_test_linux_on_sigchld (int sig)
{
}
#endif
+119
View File
@@ -113,6 +113,16 @@ namespace Frida.HostSessionTest {
h.run ();
});
GLib.Test.add_func ("/HostSession/Linux/spawn-should-not-replace-sigchld-handler", () => {
var h = new Harness ((h) => Linux.spawn_should_not_replace_sigchld_handler.begin (h as Harness));
h.run ();
});
GLib.Test.add_func ("/HostSession/Linux/spawned-process-should-be-reaped-after-sigchld-reset", () => {
var h = new Harness ((h) => Linux.spawned_process_should_be_reaped_after_sigchld_reset.begin (h as Harness));
h.run ();
});
GLib.Test.add_func ("/HostSession/Linux/ChildGating/fork", () => {
var h = new Harness ((h) => Linux.fork.begin (h as Harness));
h.run ();
@@ -1742,6 +1752,81 @@ namespace Frida.HostSessionTest {
h.done ();
}
private async void spawn_should_not_replace_sigchld_handler (Harness h) {
if (!can_run_sigchld_scenario ()) {
h.done ();
return;
}
var backend = new LinuxHostSessionBackend ();
var prov = yield h.setup_local_backend (backend);
install_sigchld_handler ();
try {
Cancellable? cancellable = null;
var host_session = yield prov.create (new NullHostSessionHub (), null, (status, progress) => {}, cancellable);
var pid = yield host_session.spawn (Frida.Test.Labrats.path_to_executable ("sleeper"), HostSpawnOptions (),
cancellable);
yield host_session.resume (pid, cancellable);
assert_true (sigchld_handler_is_installed ());
yield host_session.kill (pid, cancellable);
var reaped = yield wait_until_reaped (pid);
assert_true (reaped);
assert_true (sigchld_handler_is_installed ());
} catch (GLib.Error e) {
printerr ("Unexpected error: %s\n", e.message);
assert_not_reached ();
}
restore_sigchld_handler ();
yield h.teardown_backend (backend);
h.done ();
}
private async void spawned_process_should_be_reaped_after_sigchld_reset (Harness h) {
if (!can_run_sigchld_scenario ()) {
h.done ();
return;
}
var backend = new LinuxHostSessionBackend ();
var prov = yield h.setup_local_backend (backend);
try {
Cancellable? cancellable = null;
var host_session = yield prov.create (new NullHostSessionHub (), null, (status, progress) => {}, cancellable);
var pid = yield host_session.spawn (Frida.Test.Labrats.path_to_executable ("sleeper"), HostSpawnOptions (),
cancellable);
yield host_session.resume (pid, cancellable);
/* As libuv does once its last child has exited. */
reset_sigchld_handler ();
yield host_session.kill (pid, cancellable);
var reaped = yield wait_until_reaped (pid);
assert_true (reaped);
} catch (GLib.Error e) {
printerr ("Unexpected error: %s\n", e.message);
assert_not_reached ();
}
restore_sigchld_handler ();
yield h.teardown_backend (backend);
h.done ();
}
private async void fork (Harness h) {
yield Unix.run_fork_scenario (h, Frida.Test.Labrats.path_to_executable ("forker"));
}
@@ -1758,6 +1843,40 @@ namespace Frida.HostSessionTest {
yield Unix.run_exec_scenario (h, Frida.Test.Labrats.path_to_executable ("spawner"), "spawn-bad-then-good-path", "execv");
}
private bool can_run_sigchld_scenario () {
if (!GLib.Test.slow () && (
Frida.Test.os () == Frida.Test.OS.ANDROID ||
Frida.Test.os_arch_suffix () == "-linux-armbe8" ||
Frida.Test.os_arch_suffix () == "-linux-arm64be"
)) {
stdout.printf ("<skipping, run in slow mode> ");
return false;
}
if (!PidFileDescriptor.is_supported ()) {
stdout.printf ("<skipping, kernel lacks pidfd support> ");
return false;
}
return true;
}
private async bool wait_until_reaped (uint pid) {
var timer = new Timer ();
while (FileUtils.test ("/proc/%u".printf (pid), EXISTS)) {
if (timer.elapsed () >= 5.0)
return false;
Timeout.add (50, wait_until_reaped.callback);
yield;
}
return true;
}
private extern void install_sigchld_handler ();
private extern bool sigchld_handler_is_installed ();
private extern void reset_sigchld_handler ();
private extern void restore_sigchld_handler ();
namespace Manual {
private async void spawn_android_app (Harness h) {
+48 -1
View File
@@ -22,6 +22,10 @@ namespace Frida.InjectorTest {
GLib.Test.add_func ("/Injector/resource-leaks", test_resource_leaks);
#if LINUX
GLib.Test.add_func ("/Injector/Linux/inject-with-non-utf8-mapping", test_injection_with_non_utf8_mapping);
#endif
#if DARWIN
GLib.Test.add_func ("/Injector/suspended-injection-current-arch", () => {
test_suspended_injection (Frida.Test.Arch.CURRENT);
@@ -149,6 +153,45 @@ namespace Frida.InjectorTest {
rat.close ();
}
#if LINUX
private void test_injection_with_non_utf8_mapping () {
var logfile = File.new_for_path (Frida.Test.path_to_temporary_file ("non-utf8-mapping.log"));
try {
logfile.delete ();
} catch (GLib.Error delete_error) {
}
var envp = new string[] {
"FRIDA_LABRAT_LOGFILE=" + logfile.get_path ()
};
var sleeper = File.new_for_path (Frida.Test.Labrats.path_to_executable ("sleeper"));
var sleeper_copy = File.new_for_path (Frida.Test.path_to_temporary_file ("sleeper-\xff"));
try {
sleeper.copy (sleeper_copy, OVERWRITE);
} catch (GLib.Error e) {
assert_not_reached ();
}
FileUtils.chmod (sleeper_copy.get_path (), 0700);
var rat = new Labrat.from_path (sleeper_copy.get_path (), envp);
rat.inject ("simple-agent", "");
rat.wait_for_uninject ();
assert_true (content_of (logfile) == ">m<");
try {
rat.process.kill ();
logfile.delete ();
sleeper_copy.delete ();
} catch (GLib.Error e) {
assert_not_reached ();
}
rat.close ();
}
#endif
#if DARWIN
private void test_suspended_injection (Frida.Test.Arch arch) {
var logfile = File.new_for_path (Frida.Test.path_to_temporary_file ("suspended-injection.log"));
@@ -193,8 +236,12 @@ namespace Frida.InjectorTest {
private PendingUninject? pending_uninject;
public Labrat (string name, string[] envp, Frida.Test.Arch arch = Frida.Test.Arch.CURRENT) {
this.from_path (Frida.Test.Labrats.path_to_executable (name), envp, arch);
}
public Labrat.from_path (string path, string[] envp, Frida.Test.Arch arch = Frida.Test.Arch.CURRENT) {
try {
process = Frida.Test.Process.start (Frida.Test.Labrats.path_to_executable (name), null, envp, arch);
process = Frida.Test.Process.start (path, null, envp, arch);
} catch (Error e) {
printerr ("\nFAIL: %s\n\n", e.message);
assert_not_reached ();
+1
View File
@@ -6,4 +6,5 @@ namespace Config {
public const string FRIDA_HELPER_NAME;
public const string FRIDA_AGENT_NAME;
public const string FRIDA_COMPILER_BACKEND_NAME;
public const string FRIDA_IDUND_NAME;
}