mirror of
https://github.com/frida/frida-core.git
synced 2026-11-10 01:51:27 -10:00
Compare commits
441
Commits
No files matched your search
@@ -0,0 +1,4 @@
|
||||
set(VCPKG_TARGET_ARCHITECTURE arm64)
|
||||
set(VCPKG_CRT_LINKAGE dynamic)
|
||||
set(VCPKG_LIBRARY_LINKAGE dynamic)
|
||||
set(VCPKG_BUILD_TYPE release)
|
||||
@@ -0,0 +1,4 @@
|
||||
set(VCPKG_TARGET_ARCHITECTURE x64)
|
||||
set(VCPKG_CRT_LINKAGE dynamic)
|
||||
set(VCPKG_LIBRARY_LINKAGE dynamic)
|
||||
set(VCPKG_BUILD_TYPE release)
|
||||
+143
-30
@@ -31,7 +31,7 @@ jobs:
|
||||
runs-on: ${{ fromJSON(matrix.runner) }}
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install MinGW toolchain for cgo support
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
msystem: ${{ matrix.sys }}
|
||||
install: ${{ matrix.pkg }}
|
||||
- name: Install Go toolchain
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: 'src/compiler/go.mod'
|
||||
cache-dependency-path: 'src/compiler/go.mod'
|
||||
@@ -61,13 +61,17 @@ jobs:
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install gcc-multilib lib32stdc++-13-dev
|
||||
- name: Install setuptools for distutils shim
|
||||
if: ${{ startsWith(matrix.id, 'windows-') }}
|
||||
run: pip install setuptools
|
||||
- name: Build
|
||||
if: ${{ startsWith(matrix.id, 'windows-') }}
|
||||
run: |
|
||||
.\configure `
|
||||
--host=${{ matrix.id }} `
|
||||
--enable-compiler-backend `
|
||||
${{ env.FRIDA_CORE_OPTIONS }}
|
||||
${{ env.FRIDA_CORE_OPTIONS }} `
|
||||
-- --force-fallback-for=openssl
|
||||
.\make
|
||||
env:
|
||||
MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }}
|
||||
@@ -91,14 +95,40 @@ jobs:
|
||||
make
|
||||
- name: Upload devkit
|
||||
if: ${{ !startsWith(matrix.id, 'linux-') }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: core-devkit-${{ matrix.id }}
|
||||
path: build/src/devkit/
|
||||
- name: Upload GObject introspection data
|
||||
if: ${{ matrix.id == 'linux-x86_64' }}
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: core-gir
|
||||
path: build/src/api/Frida-1.0.gir
|
||||
- name: Test
|
||||
if: ${{ matrix.id != 'windows-arm64' && !startsWith(matrix.id, 'macos-') }}
|
||||
run: make test
|
||||
|
||||
publish-gir:
|
||||
if: ${{ !cancelled() && github.ref_type == 'tag' && needs.native.result == 'success' }}
|
||||
needs:
|
||||
- native
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download GObject introspection data
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: core-gir
|
||||
path: gir
|
||||
- name: Attach to GitHub release
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
files: |
|
||||
gir/Frida-1.0.gir
|
||||
prerelease: ${{ contains(github.ref_name, 'snapshot') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
cross:
|
||||
strategy:
|
||||
matrix:
|
||||
@@ -113,7 +143,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install toolchain
|
||||
@@ -121,7 +151,7 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install ${{ matrix.pkg }}
|
||||
- name: Install Go toolchain
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: 'src/compiler/go.mod'
|
||||
cache-dependency-path: 'src/compiler/go.mod'
|
||||
@@ -134,7 +164,7 @@ jobs:
|
||||
make
|
||||
- name: Upload devkit
|
||||
if: ${{ !startsWith(matrix.id, 'linux-') }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: core-devkit-${{ matrix.id }}
|
||||
path: build/src/devkit/
|
||||
@@ -148,11 +178,11 @@ jobs:
|
||||
container: ghcr.io/frida/x-tools-linux-${{ matrix.arch }}:latest
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install Go toolchain
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: 'src/compiler/go.mod'
|
||||
cache-dependency-path: 'src/compiler/go.mod'
|
||||
@@ -164,7 +194,7 @@ jobs:
|
||||
${{ env.FRIDA_CORE_OPTIONS }}
|
||||
make
|
||||
- name: Upload devkit
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: core-devkit-linux-${{ matrix.arch }}
|
||||
path: build/src/devkit/
|
||||
@@ -181,7 +211,7 @@ jobs:
|
||||
container: ghcr.io/frida/x-tools-linux-${{ matrix.arch }}:latest
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
@@ -312,11 +342,11 @@ jobs:
|
||||
runs-on: ${{ startsWith(matrix.id, 'ios-') && 'macos-latest' || 'ubuntu-latest' }}
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install Go toolchain
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: 'src/compiler/go.mod'
|
||||
cache-dependency-path: 'src/compiler/go.mod'
|
||||
@@ -331,7 +361,7 @@ jobs:
|
||||
if: startsWith(matrix.id, 'android-')
|
||||
id: setup-ndk
|
||||
with:
|
||||
ndk-version: r25c
|
||||
ndk-version: r29
|
||||
- name: Add NDK to env
|
||||
if: startsWith(matrix.id, 'android-')
|
||||
run: echo ANDROID_NDK_ROOT=${{ steps.setup-ndk.outputs.ndk-path }} >> $GITHUB_ENV
|
||||
@@ -343,7 +373,7 @@ jobs:
|
||||
${{ env.FRIDA_CORE_OPTIONS }}
|
||||
make
|
||||
- name: Upload devkit
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: core-devkit-${{ matrix.id }}
|
||||
path: build/src/devkit/
|
||||
@@ -402,7 +432,7 @@ jobs:
|
||||
container: ghcr.io/frida/qnx-tools:latest
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
@@ -411,7 +441,7 @@ jobs:
|
||||
./configure --host=arm-unknown-nto-qnx6.5.0eabi ${{ env.FRIDA_CORE_OPTIONS }}
|
||||
make
|
||||
- name: Upload devkit
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: core-devkit-qnx-armeabi
|
||||
path: build/src/devkit/
|
||||
@@ -428,6 +458,7 @@ jobs:
|
||||
# in, and we want to ensure that they are up-to-date with respect to the source
|
||||
# for all architectures.
|
||||
check-linux-helpers:
|
||||
if: false
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x86, x86_64, arm, armbe8, arm64, arm64be, arm64beilp32, mips, mipsel, mips64, mips64el]
|
||||
@@ -436,7 +467,7 @@ jobs:
|
||||
container: ghcr.io/frida/core-linux-helpers-${{ matrix.arch }}:latest
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
@@ -445,7 +476,7 @@ jobs:
|
||||
- name: Check for unexpected changes
|
||||
run: |
|
||||
git config --global --add safe.directory "$(realpath .)"
|
||||
status_output=$(git status --porcelain tests/labrats/{resident-agent,simple-agent}-linux-${{ matrix.arch }}.so tests/labrats/{forker,sleeper,spawner}-linux-${{ matrix.arch }})
|
||||
status_output=$(git status --porcelain src/linux/helpers/artifacts)
|
||||
if [ -n "$status_output" ]; then
|
||||
echo "Unexpected changes detected:"
|
||||
echo "$status_output"
|
||||
@@ -461,6 +492,7 @@ jobs:
|
||||
# in, and we want to ensure that they are up-to-date with respect to the source
|
||||
# for all architectures.
|
||||
check-labrats:
|
||||
if: false
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x86, x86_64, arm, armbe8, arm64, arm64be, arm64beilp32, mips, mipsel, mips64, mips64el]
|
||||
@@ -469,7 +501,7 @@ jobs:
|
||||
container: ghcr.io/frida/core-linux-helpers-${{ matrix.arch }}:latest
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Build
|
||||
@@ -478,7 +510,7 @@ jobs:
|
||||
- name: Check for unexpected changes
|
||||
run: |
|
||||
git config --global --add safe.directory "$(realpath .)"
|
||||
status_output=$(git status --porcelain src/linux/helpers/{bootstrapper,loader}-${{ matrix.arch }}.bin)
|
||||
status_output=$(git status --porcelain tests/labrats/{resident-agent,simple-agent}-linux-${{ matrix.arch }}.so tests/labrats/{forker,sleeper,spawner}-linux-${{ matrix.arch }})
|
||||
if [ -n "$status_output" ]; then
|
||||
echo "Unexpected changes detected:"
|
||||
echo "$status_output"
|
||||
@@ -491,30 +523,31 @@ jobs:
|
||||
|
||||
xcframework:
|
||||
name: Build FridaCore.xcframework
|
||||
if: ${{ !cancelled() }}
|
||||
runs-on: macos-latest
|
||||
needs:
|
||||
- native
|
||||
- mobile
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Download macOS/x86_64 devkit
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: core-devkit-macos-x86_64
|
||||
path: devkits/macos-x86_64
|
||||
- name: Download macOS/arm64 devkit
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: core-devkit-macos-arm64
|
||||
path: devkits/macos-arm64
|
||||
- name: Download iOS/arm64 devkit
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: core-devkit-ios-arm64
|
||||
path: devkits/ios-arm64
|
||||
- name: Download iOS/arm64-simulator devkit
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: core-devkit-ios-arm64-simulator
|
||||
path: devkits/ios-arm64-simulator
|
||||
@@ -577,19 +610,19 @@ jobs:
|
||||
-headers xcframework_headers/ios-arm64-simulator \
|
||||
-output FridaCore.xcframework
|
||||
- name: Upload FridaCore.xcframework
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: FridaCore.xcframework
|
||||
path: FridaCore.xcframework
|
||||
|
||||
publish-xcframework:
|
||||
if: github.ref_type == 'tag'
|
||||
if: ${{ !cancelled() && github.ref_type == 'tag' && needs.xcframework.result == 'success' }}
|
||||
runs-on: macos-latest
|
||||
needs:
|
||||
- xcframework
|
||||
steps:
|
||||
- name: Download FridaCore.xcframework artifact
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: FridaCore.xcframework
|
||||
path: dist
|
||||
@@ -612,10 +645,90 @@ jobs:
|
||||
cd dist
|
||||
zip -r FridaCore.xcframework.zip FridaCore.xcframework
|
||||
- name: Create GitHub release
|
||||
uses: softprops/action-gh-release@v2
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
files: |
|
||||
dist/FridaCore.xcframework.zip
|
||||
prerelease: ${{ contains(github.ref_name, 'snapshot') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
windows-shared:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- { arch: x86_64, runner: windows-latest, msvc-arch: amd64, vcpkg-triplet: x64-windows-release }
|
||||
- { arch: arm64, runner: windows-11-arm, msvc-arch: arm64, vcpkg-triplet: arm64-windows-release }
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: ${{ matrix.msvc-arch }}
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install build tools
|
||||
run: |
|
||||
pip install meson ninja setuptools
|
||||
choco install pkgconfiglite -y
|
||||
|
||||
- name: Restore vcpkg cache
|
||||
id: vcpkg-cache
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
path: C:\vcpkg\installed
|
||||
key: vcpkg-${{ matrix.vcpkg-triplet }}-v5
|
||||
|
||||
- name: Install dependencies via vcpkg
|
||||
if: steps.vcpkg-cache.outputs.cache-hit != 'true'
|
||||
run: >-
|
||||
vcpkg install
|
||||
--overlay-triplets=${{ github.workspace }}/.github/vcpkg-triplets
|
||||
--triplet ${{ matrix.vcpkg-triplet }}
|
||||
glib json-glib libsoup
|
||||
|
||||
- name: Save vcpkg cache
|
||||
if: steps.vcpkg-cache.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
path: C:\vcpkg\installed
|
||||
key: vcpkg-${{ matrix.vcpkg-triplet }}-v5
|
||||
|
||||
- name: Set up vcpkg environment
|
||||
shell: bash
|
||||
run: |
|
||||
TRIPLET="${{ matrix.vcpkg-triplet }}"
|
||||
VCPKG_PREFIX="C:/vcpkg/installed/$TRIPLET"
|
||||
echo "$VCPKG_PREFIX/tools/pkgconf" >> $GITHUB_PATH
|
||||
echo "$VCPKG_PREFIX/bin" >> $GITHUB_PATH
|
||||
echo "$VCPKG_PREFIX/tools" >> $GITHUB_PATH
|
||||
echo "PKG_CONFIG_PATH=$VCPKG_PREFIX/lib/pkgconfig" >> $GITHUB_ENV
|
||||
|
||||
- name: Configure
|
||||
run: |
|
||||
python -c "import sys; sys.path.insert(0, '.'); from releng.meson_configure import main; main()" `
|
||||
. `
|
||||
--host=windows-${{ matrix.arch }} `
|
||||
--enable-shared `
|
||||
--without-prebuilds=sdk `
|
||||
"--prefix=$env:RUNNER_TEMP\frida-prefix" `
|
||||
-- --force-fallback-for=openssl,libnghttp2 -Dcompiler_backend=disabled -Dtests=disabled
|
||||
|
||||
- name: Compile
|
||||
run: python releng/meson/meson.py compile -C build
|
||||
|
||||
- name: Install
|
||||
run: python releng/meson/meson.py install -C build --no-rebuild --skip-subprojects
|
||||
|
||||
- name: List installed files
|
||||
shell: bash
|
||||
run: find "$RUNNER_TEMP/frida-prefix" -type f | sort
|
||||
@@ -1,7 +1,11 @@
|
||||
/.cache/
|
||||
/build/
|
||||
/deps/
|
||||
/src/android-helper/build/
|
||||
/src/barebone/agent/target/
|
||||
/src/barebone/agent/target-linux/
|
||||
/src/barebone/agent/linux/*.o
|
||||
/src/barebone/agent/linux/*.o.bin
|
||||
/src/barebone/agent/xpacks/
|
||||
/src/barebone/helpers/memory-scanner/target/
|
||||
/src/barebone/helpers/target/
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code)
|
||||
when working with code in this repository.
|
||||
|
||||
## What This Is
|
||||
|
||||
frida-core is the core library of the Frida dynamic
|
||||
instrumentation toolkit. It packages GumJS into a shared
|
||||
library, injects JavaScript instrumentation into target
|
||||
processes, provides bidirectional RPC communication with
|
||||
injected scripts, and enumerates apps/processes/devices.
|
||||
Written primarily in Vala with OS-specific glue in
|
||||
C/Objective-C/asm.
|
||||
|
||||
## Build Commands
|
||||
|
||||
```bash
|
||||
# First-time setup (fetches git submodules, configures build):
|
||||
./configure \
|
||||
--enable-compiler-backend \
|
||||
--with-devkits=core \
|
||||
--enable-tests
|
||||
|
||||
# Build:
|
||||
make
|
||||
|
||||
# Run tests:
|
||||
make test
|
||||
|
||||
# Rebuild after code changes
|
||||
make
|
||||
```
|
||||
|
||||
The `configure` script and `Makefile` are thin wrappers
|
||||
around `releng/meson_configure.py` and
|
||||
`releng/meson_make.py`. The actual build system is Meson
|
||||
(>=1.1.0).
|
||||
|
||||
Build artifacts go into `build/`. The triplet is
|
||||
platform-dependent (e.g. `linux-x86_64`).
|
||||
|
||||
## Build Requirements
|
||||
|
||||
- **Vala compiler**: Must be the Frida-optimized fork from
|
||||
https://github.com/frida/vala (checks for `-frida` suffix
|
||||
in version)
|
||||
- **C/C++ standards**: gnu99/c99 for C, c++17 for C++
|
||||
- **Go >= 1.24**: Required for compiler backend
|
||||
- **Python 3**: Build scripts
|
||||
|
||||
## Architecture
|
||||
|
||||
### Backend System
|
||||
|
||||
The core abstraction is a pluggable backend system. Each
|
||||
backend implements `HostSessionBackend` and provides a
|
||||
`HostSessionProvider` to create sessions with target
|
||||
processes:
|
||||
|
||||
- **local** — Injects into processes on the local system
|
||||
(Linux, macOS, Windows, FreeBSD, QNX)
|
||||
- **fruity** — iOS/tvOS/xrOS devices via USB/network
|
||||
(QUIC, lockdown protocols)
|
||||
- **droidy** — Android devices via ADB/JDWP
|
||||
- **socket** — Remote devices via TCP (connects to
|
||||
frida-server, frida-gadget, frida-portal)
|
||||
- **barebone** — Minimal backend using QuickJS
|
||||
- **simmy** — macOS Simulator
|
||||
- **compiler** — TypeScript/ESBuild compilation backend
|
||||
(Go + TypeScript in `src/compiler/`)
|
||||
|
||||
Backends are selected at build time via meson options
|
||||
(e.g. `-Dlocal_backend=enabled`).
|
||||
|
||||
### Key Source Layout
|
||||
|
||||
- **`src/frida.vala`** — Public API: `DeviceManager`,
|
||||
`Device`, `Session`, `Script`
|
||||
- **`src/host-session-service.vala`** — Backend
|
||||
orchestration, provider registration
|
||||
- **`src/control-service.vala`** — the guts of frida-server
|
||||
- **`src/portal-service.vala`** — the guts of frida-portal,
|
||||
which looks like a frida-server that surfaces any joined
|
||||
processes (think aggregator/reverse proxy)
|
||||
- **`lib/base/`** — Core primitives: Session, RPC, streams,
|
||||
sockets, P2P, buffers
|
||||
- **`lib/agent/`** — In-process JavaScript agent runtime
|
||||
- **`lib/payload/`** — Payload compilation and embedding
|
||||
- **`lib/gadget/`** — Gadget (preloaded library)
|
||||
implementation
|
||||
- **`src/<platform>/`** — Platform-specific injectors and
|
||||
helpers (`linux/`, `darwin/`, `windows/`, `freebsd/`,
|
||||
`qnx/`)
|
||||
- **`src/fruity/`** — iOS device protocol stack (USB, QUIC,
|
||||
plist, XPC, LLDB)
|
||||
- **`src/droidy/`** — Android device protocol stack
|
||||
- **`src/compiler/`** — Go/TypeScript compiler backend
|
||||
|
||||
### Injection Flow
|
||||
|
||||
Each platform has its own injector (e.g. `Linjector` on
|
||||
Linux, `Fruitjector` on Darwin, `Winjector` on Windows). A
|
||||
helper process (`frida-helper`) performs privileged injection
|
||||
operations out-of-process. Helpers and agents can be embedded
|
||||
into the library or installed as separate files (controlled
|
||||
by the `assets` meson option).
|
||||
|
||||
### Deliverables
|
||||
|
||||
- **frida-server** (`server/`) — Standalone daemon exposing
|
||||
the full API over the network
|
||||
- **frida-portal** (`portal/`) — Cluster node for
|
||||
distributed instrumentation
|
||||
- **frida-inject** (`inject/`) — CLI tool for one-shot
|
||||
script injection
|
||||
- **frida-gadget** (`lib/gadget/`) — Shared library for
|
||||
preload-based instrumentation
|
||||
|
||||
### Resource Embedding
|
||||
|
||||
Build artifacts (frida-helper, frida-agent) are compiled
|
||||
into resource blobs via `tools/resource-compiler.vala` and
|
||||
embedding scripts (`src/embed-helper.py`,
|
||||
`src/embed-agent.py`). On non-MSVC toolchains these become
|
||||
`.S` assembly files; on MSVC they become `.obj` files.
|
||||
|
||||
## Tests
|
||||
|
||||
Tests are in `tests/` as Vala files using a custom async
|
||||
test harness (`tests/async-harness.vala`). Test binary is
|
||||
`tests/frida-tests`. Platform-specific runner scripts exist
|
||||
(e.g. `tests/run-linux-x86_64.sh`).
|
||||
|
||||
Test modules: `test-system`, `test-injector`, `test-agent`,
|
||||
`test-host-session`, `test-gadget`, `test-compiler`. Test
|
||||
helper programs ("labrats") are in `tests/labrats/`.
|
||||
|
||||
## Code Style
|
||||
|
||||
### Function Order
|
||||
|
||||
Use newspaper/stepdown order: high-level entry points
|
||||
first, low-level helpers and constants last. In C,
|
||||
`main()` is always first. When ordering siblings at the
|
||||
same level, use the chronological order of first use
|
||||
within their caller.
|
||||
|
||||
## Commit Style
|
||||
|
||||
- Subject line: max 50 characters
|
||||
- Body lines: wrap at 72 characters (use the full width, or
|
||||
slightly less if it avoids making the next line awkward)
|
||||
@@ -1,15 +1,18 @@
|
||||
# frida-core
|
||||
|
||||
Frida core library intended for static linking into bindings.
|
||||
Frida core library.
|
||||
|
||||
- Lets you inject your own JavaScript instrumentation code into other processes,
|
||||
optionally with your own [C code][] for performance-sensitive bits.
|
||||
- Acts as a logistics layer that packages up [GumJS][] into a shared library.
|
||||
- Provides a two-way communication channel for talking to your scripts,
|
||||
if needed, and later unload them.
|
||||
- Also lets you enumerate installed apps, running processes, and connected
|
||||
devices.
|
||||
- Written in [Vala][], with OS-specific glue code in C/Objective-C/asm.
|
||||
- Lets you inject your own JavaScript instrumentation code
|
||||
into other processes, optionally with your own [C code][]
|
||||
for performance-sensitive bits.
|
||||
- Acts as a logistics layer that packages up [GumJS][] into
|
||||
a shared library.
|
||||
- Provides a two-way communication channel for talking to
|
||||
your scripts, if needed, and later unload them.
|
||||
- Also lets you enumerate installed apps, running processes,
|
||||
and connected devices.
|
||||
- Written in [Vala][], with OS-specific glue code in
|
||||
C/Objective-C/asm.
|
||||
|
||||
## Binaries
|
||||
|
||||
@@ -29,8 +32,43 @@ $ pip install frida # Python bindings
|
||||
$ npm install frida # Node.js bindings
|
||||
```
|
||||
|
||||
Or, for static linking into your own project written in a C-compatible language,
|
||||
download a devkit from the Frida [releases][] page.
|
||||
Or, for static linking into your own project written in a
|
||||
C-compatible language, download a devkit from the Frida
|
||||
[releases][] page.
|
||||
|
||||
## Distro Packaging
|
||||
|
||||
For building a shared library suitable for distro packaging:
|
||||
|
||||
```console
|
||||
$ ./configure --enable-shared --without-prebuilds=sdk && make
|
||||
```
|
||||
|
||||
This builds `libfrida-core-1.0.so` against system dependencies,
|
||||
while `frida-agent.so` is built using Frida's SDK (with its GLib
|
||||
fork, needed for the agent's inject/unload lifecycle).
|
||||
|
||||
Prerequisites on Fedora:
|
||||
|
||||
```console
|
||||
$ sudo dnf install libgee-devel json-glib-devel \
|
||||
libsoup3-devel libunwind-devel libdwarf-devel \
|
||||
libnice-devel ngtcp2-crypto-ossl-devel
|
||||
```
|
||||
|
||||
Prerequisites on Ubuntu:
|
||||
|
||||
```console
|
||||
$ sudo apt install libgee-0.8-dev libjson-glib-dev \
|
||||
libsoup-3.0-dev libunwind-dev libdwarf-dev \
|
||||
libnice-dev libngtcp2-crypto-ossl-dev
|
||||
```
|
||||
|
||||
Prerequisites on macOS:
|
||||
|
||||
```console
|
||||
$ brew install libgee json-glib libsoup libnice capstone
|
||||
```
|
||||
|
||||
## Internals
|
||||
|
||||
|
||||
+235
-63
@@ -9,6 +9,7 @@ import os
|
||||
from pathlib import Path
|
||||
import pickle
|
||||
import platform
|
||||
import re
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
@@ -35,6 +36,7 @@ def main(argv):
|
||||
command.add_argument("top_builddir", help="top build directory", type=Path)
|
||||
command.add_argument("frida_version", help="the Frida version")
|
||||
command.add_argument("host_os", help="operating system binaries are being built for")
|
||||
command.add_argument("host_os_family", help="operating system family, e.g. darwin")
|
||||
command.add_argument("host_arch", help="architecture binaries are being built for")
|
||||
command.add_argument("host_config", help="configuration binaries are being built for")
|
||||
command.add_argument("compat", help="support for targets with a different architecture",
|
||||
@@ -42,17 +44,23 @@ def main(argv):
|
||||
command.add_argument("assets", help="whether assets are embedded vs installed and loaded at runtime")
|
||||
command.add_argument("components", help="which components will be built",
|
||||
type=parse_array_option_value)
|
||||
command.add_argument("glib_flavor", help="upstream or frida", choices=["upstream", "frida"])
|
||||
command.add_argument("apple_tls", help="whether the parent build is using the Apple TLS backend",
|
||||
choices=["enabled", "disabled"])
|
||||
command.add_argument("compilers", help="compiler command arrays", nargs="+")
|
||||
command.set_defaults(func=lambda args: setup(args.role,
|
||||
args.builddir,
|
||||
args.top_builddir,
|
||||
args.frida_version,
|
||||
args.host_os,
|
||||
args.host_os_family,
|
||||
args.host_arch,
|
||||
args.host_config if args.host_config else None,
|
||||
args.compat,
|
||||
args.assets,
|
||||
args.components,
|
||||
args.glib_flavor,
|
||||
args.apple_tls,
|
||||
parse_compilers(args.compilers)))
|
||||
|
||||
command = subparsers.add_parser("compile", help="compile compatibility assets")
|
||||
@@ -113,11 +121,14 @@ def setup(role: Role,
|
||||
top_builddir: Path,
|
||||
frida_version: str,
|
||||
host_os: str,
|
||||
host_os_family: str,
|
||||
host_arch: str,
|
||||
host_config: Optional[str],
|
||||
compat: set[str],
|
||||
assets: str,
|
||||
components: set[str],
|
||||
glib_flavor: str,
|
||||
apple_tls: str,
|
||||
compilers: Compilers):
|
||||
try:
|
||||
outputs: Mapping[str, Sequence[Output]] = OrderedDict()
|
||||
@@ -149,9 +160,20 @@ def setup(role: Role,
|
||||
other_triplet: Optional[str] = None
|
||||
extra_environ: dict[str, str] = {}
|
||||
|
||||
if host_os == "windows" and host_arch == "x86_64" and host_config == "mingw":
|
||||
other_label = "x86"
|
||||
have_toolchain, other_triplet = detect_mingw_toolchain_for("x86")
|
||||
if host_os == "windows" and host_arch in {"x86_64", "x86"}:
|
||||
if host_arch == "x86_64":
|
||||
other_label = "x86"
|
||||
other_kind = "legacy"
|
||||
else:
|
||||
other_label = "x86_64"
|
||||
other_kind = "modern"
|
||||
|
||||
if host_config == "mingw":
|
||||
have_toolchain, other_triplet = detect_mingw_toolchain_for(other_label)
|
||||
else:
|
||||
have_toolchain = True
|
||||
other_triplet = None
|
||||
|
||||
elif host_os == "linux" and host_arch == "x86_64" and host_config is None:
|
||||
other_label = "x86"
|
||||
other_triplet = "i686-linux-gnu"
|
||||
@@ -179,21 +201,26 @@ def setup(role: Role,
|
||||
raise ToolchainNotFoundError(f"unable to locate toolchain for {other_triplet}")
|
||||
missing.append(MissingFeature(other_label, other_triplet))
|
||||
|
||||
if host_os == "windows" and host_arch == "x86_64" and have_toolchain:
|
||||
group = OutputGroup("x86", other_triplet, extra_environ)
|
||||
outputs[group] = [
|
||||
Output(identifier="helper_legacy",
|
||||
name=HELPER_FILE_WINDOWS.name,
|
||||
file=HELPER_FILE_WINDOWS,
|
||||
target=HELPER_TARGET),
|
||||
Output(identifier="agent_legacy",
|
||||
name=AGENT_FILE_WINDOWS.name,
|
||||
file=AGENT_FILE_WINDOWS,
|
||||
target=AGENT_TARGET),
|
||||
]
|
||||
if host_os == "windows" and host_arch in {"x86_64", "x86"} and have_toolchain:
|
||||
group = OutputGroup(other_label, other_triplet, extra_environ)
|
||||
outputs[group] = []
|
||||
if "helper" in components:
|
||||
outputs[group] += [
|
||||
Output(identifier=f"helper_{other_kind}",
|
||||
name=HELPER_FILE_WINDOWS.name,
|
||||
file=HELPER_FILE_WINDOWS,
|
||||
target=HELPER_TARGET),
|
||||
]
|
||||
if "agent" in components:
|
||||
outputs[group] += [
|
||||
Output(identifier=f"agent_{other_kind}",
|
||||
name=AGENT_FILE_WINDOWS.name,
|
||||
file=AGENT_FILE_WINDOWS,
|
||||
target=AGENT_TARGET),
|
||||
]
|
||||
if "gadget" in components:
|
||||
outputs[group] += [
|
||||
Output(identifier="gadget_legacy",
|
||||
Output(identifier=f"gadget_{other_kind}",
|
||||
name=GADGET_FILE_WINDOWS.name,
|
||||
file=GADGET_FILE_WINDOWS,
|
||||
target=GADGET_TARGET),
|
||||
@@ -207,16 +234,21 @@ def setup(role: Role,
|
||||
other_arch = "arm64e"
|
||||
kind = "modern"
|
||||
group = OutputGroup(other_arch)
|
||||
outputs[group] = [
|
||||
Output(identifier=f"helper_{kind}",
|
||||
name=f"frida-helper-{other_arch}",
|
||||
file=HELPER_FILE_UNIX,
|
||||
target=HELPER_TARGET),
|
||||
Output(identifier=f"agent_{kind}",
|
||||
name=f"frida-agent-{other_arch}.dylib",
|
||||
file=AGENT_FILE_DARWIN,
|
||||
target=AGENT_TARGET),
|
||||
]
|
||||
outputs[group] = []
|
||||
if "helper" in components:
|
||||
outputs[group] += [
|
||||
Output(identifier=f"helper_{kind}",
|
||||
name=f"frida-helper-{other_arch}",
|
||||
file=HELPER_FILE_UNIX,
|
||||
target=HELPER_TARGET),
|
||||
]
|
||||
if "agent" in components:
|
||||
outputs[group] += [
|
||||
Output(identifier=f"agent_{kind}",
|
||||
name=f"frida-agent-{other_arch}.dylib",
|
||||
file=AGENT_FILE_DARWIN,
|
||||
target=AGENT_TARGET),
|
||||
]
|
||||
if "gadget" in components:
|
||||
outputs[group] += [
|
||||
Output(identifier=f"gadget_{kind}",
|
||||
@@ -234,16 +266,21 @@ def setup(role: Role,
|
||||
|
||||
if host_os == "linux" and host_arch == "x86_64" and have_toolchain:
|
||||
group = OutputGroup("x86", other_triplet, extra_environ)
|
||||
outputs[group] = [
|
||||
Output(identifier="helper_legacy",
|
||||
name=HELPER_FILE_UNIX.name,
|
||||
file=HELPER_FILE_UNIX,
|
||||
target=HELPER_TARGET),
|
||||
Output(identifier="agent_legacy",
|
||||
name=AGENT_FILE_ELF.name,
|
||||
file=AGENT_FILE_ELF,
|
||||
target=AGENT_TARGET),
|
||||
]
|
||||
outputs[group] = []
|
||||
if "helper" in components:
|
||||
outputs[group] += [
|
||||
Output(identifier="helper_legacy",
|
||||
name=HELPER_FILE_UNIX.name,
|
||||
file=HELPER_FILE_UNIX,
|
||||
target=HELPER_TARGET),
|
||||
]
|
||||
if "agent" in components:
|
||||
outputs[group] += [
|
||||
Output(identifier="agent_legacy",
|
||||
name=AGENT_FILE_ELF.name,
|
||||
file=AGENT_FILE_ELF,
|
||||
target=AGENT_TARGET),
|
||||
]
|
||||
if "gadget" in components:
|
||||
outputs[group] += [
|
||||
Output(identifier="gadget_legacy",
|
||||
@@ -254,16 +291,21 @@ def setup(role: Role,
|
||||
|
||||
if host_os == "android" and host_arch in {"arm64", "x86_64"}:
|
||||
group = OutputGroup("arm" if host_arch == "arm64" else "x86")
|
||||
outputs[group] = [
|
||||
Output(identifier="helper_legacy",
|
||||
name=HELPER_FILE_UNIX.name,
|
||||
file=HELPER_FILE_UNIX,
|
||||
target=HELPER_TARGET),
|
||||
Output(identifier="agent_legacy",
|
||||
name=AGENT_FILE_ELF.name,
|
||||
file=AGENT_FILE_ELF,
|
||||
target=AGENT_TARGET),
|
||||
]
|
||||
outputs[group] = []
|
||||
if "helper" in components:
|
||||
outputs[group] += [
|
||||
Output(identifier="helper_legacy",
|
||||
name=HELPER_FILE_UNIX.name,
|
||||
file=HELPER_FILE_UNIX,
|
||||
target=HELPER_TARGET),
|
||||
]
|
||||
if "agent" in components:
|
||||
outputs[group] += [
|
||||
Output(identifier="agent_legacy",
|
||||
name=AGENT_FILE_ELF.name,
|
||||
file=AGENT_FILE_ELF,
|
||||
target=AGENT_TARGET),
|
||||
]
|
||||
if "gadget" in components:
|
||||
outputs[group] += [
|
||||
Output(identifier="gadget_legacy",
|
||||
@@ -284,18 +326,24 @@ def setup(role: Role,
|
||||
continue
|
||||
else:
|
||||
emulated_triplet = None
|
||||
outputs[OutputGroup(emulated_arch, emulated_triplet, extra_environ)] = [
|
||||
Output(identifier=f"helper_emulated_{kind}",
|
||||
name=HELPER_FILE_WINDOWS.name.replace(".exe", f"-{emulated_arch}.exe"),
|
||||
file=HELPER_FILE_WINDOWS,
|
||||
target=HELPER_TARGET),
|
||||
Output(identifier=f"agent_emulated_{kind}",
|
||||
name=AGENT_FILE_WINDOWS.name.replace(".dll", f"-{emulated_arch}.dll"),
|
||||
file=AGENT_FILE_WINDOWS,
|
||||
target=AGENT_TARGET),
|
||||
]
|
||||
group = OutputGroup(emulated_arch, emulated_triplet, extra_environ)
|
||||
outputs[group] = []
|
||||
if "helper" in components:
|
||||
outputs[group] += [
|
||||
Output(identifier=f"helper_emulated_{kind}",
|
||||
name=HELPER_FILE_WINDOWS.name.replace(".exe", f"-{emulated_arch}.exe"),
|
||||
file=HELPER_FILE_WINDOWS,
|
||||
target=HELPER_TARGET),
|
||||
]
|
||||
if "agent" in components:
|
||||
outputs[group] += [
|
||||
Output(identifier=f"agent_emulated_{kind}",
|
||||
name=AGENT_FILE_WINDOWS.name.replace(".dll", f"-{emulated_arch}.dll"),
|
||||
file=AGENT_FILE_WINDOWS,
|
||||
target=AGENT_TARGET),
|
||||
]
|
||||
|
||||
if host_os == "android" and host_arch in {"x86_64", "x86"}:
|
||||
if host_os == "android" and host_arch in {"x86_64", "x86"} and "agent" in components:
|
||||
outputs[OutputGroup("arm")] = [
|
||||
Output(identifier="agent_emulated_legacy",
|
||||
name="frida-agent-arm.so",
|
||||
@@ -310,10 +358,46 @@ def setup(role: Role,
|
||||
target=AGENT_TARGET),
|
||||
]
|
||||
|
||||
raw_allowed_prebuilds = os.environ.get("FRIDA_ALLOWED_PREBUILDS")
|
||||
allowed_prebuilds = set(raw_allowed_prebuilds.split(",")) if raw_allowed_prebuilds is not None else None
|
||||
if glib_flavor == "upstream":
|
||||
kind = "modern" if arch_is_modern(host_os, host_arch) else "legacy"
|
||||
_, agent_file, gadget_file = native_file_paths(host_os)
|
||||
group = OutputGroup(host_arch)
|
||||
outputs[group] = []
|
||||
if "agent" in components:
|
||||
outputs[group] += [
|
||||
Output(identifier=f"agent_{kind}",
|
||||
name=arch_suffixed_name(agent_file.name, host_arch),
|
||||
file=agent_file,
|
||||
target=AGENT_TARGET),
|
||||
]
|
||||
if "gadget" in components:
|
||||
outputs[group] += [
|
||||
Output(identifier=f"gadget_{kind}",
|
||||
name=arch_suffixed_name(gadget_file.name, host_arch),
|
||||
file=gadget_file,
|
||||
target=GADGET_TARGET),
|
||||
]
|
||||
elif apple_tls == "enabled" and "agent" in components:
|
||||
kind = "modern" if arch_is_modern(host_os, host_arch) else "legacy"
|
||||
_, agent_file, _ = native_file_paths(host_os)
|
||||
group = OutputGroup(host_arch)
|
||||
outputs.setdefault(group, [])
|
||||
outputs[group] += [
|
||||
Output(identifier=f"agent_{kind}",
|
||||
name=arch_suffixed_name(agent_file.name, host_arch),
|
||||
file=agent_file,
|
||||
target=AGENT_TARGET),
|
||||
]
|
||||
|
||||
state = State(role, builddir, top_builddir, frida_version, host_os, host_config, allowed_prebuilds, outputs)
|
||||
outputs = OrderedDict((g, items) for g, items in outputs.items() if items)
|
||||
|
||||
raw_allowed_prebuilds = os.environ.get("FRIDA_ALLOWED_PREBUILDS")
|
||||
if raw_allowed_prebuilds is not None:
|
||||
allowed_prebuilds = set(filter(None, raw_allowed_prebuilds.split(",")))
|
||||
else:
|
||||
allowed_prebuilds = None
|
||||
|
||||
state = State(role, builddir, top_builddir, frida_version, host_os, host_os_family, host_config, glib_flavor, apple_tls, allowed_prebuilds, outputs)
|
||||
serialized_state = base64.b64encode(pickle.dumps(state)).decode('ascii')
|
||||
|
||||
if missing:
|
||||
@@ -349,7 +433,10 @@ class State:
|
||||
top_builddir: Path
|
||||
frida_version: str
|
||||
host_os: str
|
||||
host_os_family: str
|
||||
host_config: Optional[str]
|
||||
glib_flavor: str
|
||||
apple_tls: str
|
||||
allowed_prebuilds: Optional[set[str]]
|
||||
outputs: Mapping[OutputGroup, Sequence[Output]]
|
||||
|
||||
@@ -429,20 +516,35 @@ def compile(privdir: Path, state: State):
|
||||
|
||||
host_machine = MachineSpec(state.host_os, group.arch, state.host_config, group.triplet)
|
||||
|
||||
if state.glib_flavor == "upstream":
|
||||
allowed = None
|
||||
else:
|
||||
allowed = state.allowed_prebuilds
|
||||
|
||||
if state.glib_flavor == "upstream":
|
||||
forwarded_options = [o for o in options if not is_stripped_compat_option(o)]
|
||||
forwarded_options += ["-Ddefault_library=static"]
|
||||
else:
|
||||
forwarded_options = [o for o in options if not is_overridden_compat_option(o)]
|
||||
if state.apple_tls == "enabled":
|
||||
forwarded_options += ["-Dapple_tls=disabled"]
|
||||
|
||||
configure(sourcedir=REPO_ROOT,
|
||||
builddir=workdir,
|
||||
host_machine=host_machine,
|
||||
environ={**build_env, **group.extra_environ},
|
||||
allowed_prebuilds=state.allowed_prebuilds,
|
||||
allowed_prebuilds=allowed,
|
||||
apple_min_os=detect_parent_apple_min_os(top_builddir, state.host_os, state.host_os_family),
|
||||
extra_meson_options=[
|
||||
"-Dcompiler_backend=disabled",
|
||||
"-Dassets=installed",
|
||||
"-Dhelper_modern=",
|
||||
"-Dhelper_legacy=",
|
||||
"-Dagent_modern=",
|
||||
"-Dagent_legacy=",
|
||||
"-Dagent_emulated_modern=",
|
||||
"-Dagent_emulated_legacy=",
|
||||
*options,
|
||||
*forwarded_options,
|
||||
],
|
||||
call_meson=call_internal_meson,
|
||||
on_progress=lambda progress: None)
|
||||
@@ -473,6 +575,18 @@ def compile(privdir: Path, state: State):
|
||||
(state.builddir / DEPFILE_FILENAME).write_text("\n".join(depfile_lines), encoding="utf-8")
|
||||
|
||||
|
||||
def is_stripped_compat_option(o: str) -> bool:
|
||||
if is_overridden_compat_option(o):
|
||||
return True
|
||||
name = o.split("=", 1)[0].removeprefix("-D")
|
||||
return name in STRIPPED_COMPAT_OPTIONS or name.endswith(":default_library")
|
||||
|
||||
|
||||
def is_overridden_compat_option(o: str) -> bool:
|
||||
name = o.split("=", 1)[0].removeprefix("-D")
|
||||
return name in OVERRIDDEN_COMPAT_OPTIONS
|
||||
|
||||
|
||||
def load_meson_options(top_builddir: Path,
|
||||
role: Role,
|
||||
subprojects: set[str]) -> Sequence[str]:
|
||||
@@ -510,7 +624,11 @@ def option_should_be_forwarded(k: "OptionKey",
|
||||
return is_for_us or is_for_child
|
||||
|
||||
if coredata.CoreData.is_per_machine_option(k):
|
||||
return k.machine is coredata.MachineChoice.BUILD
|
||||
# The compat build generates its own machine files, so forwarding the
|
||||
# parent's build-machine options would inject a foreign toolchain. In a
|
||||
# native compat build that conjures a compiler-less build machine and
|
||||
# breaks any native: true target.
|
||||
return False
|
||||
|
||||
if k.is_builtin():
|
||||
if k.name in {"buildtype", "genvslite"}:
|
||||
@@ -582,6 +700,19 @@ def ensure_submodules_checked_out(releng_location: Path):
|
||||
check=True)
|
||||
|
||||
|
||||
def detect_parent_apple_min_os(top_builddir: Path,
|
||||
host_os: str,
|
||||
host_os_family: str) -> Optional[Mapping[str, str]]:
|
||||
if host_os_family != "darwin":
|
||||
return None
|
||||
pattern = re.compile(r"-target['\"],\s*['\"][^-]+-apple-" + re.escape(host_os) + r"([0-9.]+)")
|
||||
for native_file in top_builddir.glob(f"frida-{host_os}-*.txt"):
|
||||
match = pattern.search(native_file.read_text(encoding="utf-8"))
|
||||
if match is not None:
|
||||
return {host_os: match.group(1)}
|
||||
return None
|
||||
|
||||
|
||||
def detect_relevant_subprojects(releng_location: Path) -> dict[str, Path]:
|
||||
subprojects = detect_relevant_subprojects_in(REPO_ROOT, releng_location)
|
||||
gum_location = subprojects.get("frida-gum")
|
||||
@@ -632,9 +763,48 @@ def detect_mingw_toolchain_for(arch: str) -> Tuple[bool, Optional[str]]:
|
||||
return (found, triplet)
|
||||
|
||||
|
||||
def arch_is_modern(host_os: str, host_arch: str) -> bool:
|
||||
if host_os in {"macos", "ios", "tvos"} and host_arch == "arm64":
|
||||
return host_arch == "arm64e"
|
||||
return host_arch in MODERN_ARCHS
|
||||
|
||||
|
||||
def native_file_paths(host_os: str) -> Tuple[Path, Path, Path]:
|
||||
if host_os == "windows":
|
||||
return (HELPER_FILE_WINDOWS, AGENT_FILE_WINDOWS, GADGET_FILE_WINDOWS)
|
||||
elif host_os in {"macos", "ios", "tvos"}:
|
||||
return (HELPER_FILE_UNIX, AGENT_FILE_DARWIN, GADGET_FILE_DARWIN)
|
||||
else:
|
||||
return (HELPER_FILE_UNIX, AGENT_FILE_ELF, GADGET_FILE_ELF)
|
||||
|
||||
|
||||
def arch_suffixed_name(name: str, arch: str) -> str:
|
||||
stem, _, ext = name.partition(".")
|
||||
if ext:
|
||||
return f"{stem}-{arch}.{ext}"
|
||||
return f"{stem}-{arch}"
|
||||
|
||||
|
||||
STATE_FILENAME = "state.dat"
|
||||
DEPFILE_FILENAME = "compat.deps"
|
||||
|
||||
STRIPPED_COMPAT_OPTIONS = {
|
||||
"default_library",
|
||||
"frida-gum:gumjs",
|
||||
"frida-gum:inspector",
|
||||
}
|
||||
|
||||
OVERRIDDEN_COMPAT_OPTIONS = {
|
||||
"compiler_backend",
|
||||
"assets",
|
||||
"helper_modern",
|
||||
"helper_legacy",
|
||||
"agent_modern",
|
||||
"agent_legacy",
|
||||
"agent_emulated_modern",
|
||||
"agent_emulated_legacy",
|
||||
}
|
||||
|
||||
HELPER_TARGET = "frida-helper"
|
||||
HELPER_FILE_WINDOWS = Path("src") / "frida-helper.exe"
|
||||
HELPER_FILE_UNIX = Path("src") / "frida-helper"
|
||||
@@ -659,6 +829,8 @@ MSVS_ENVVARS = {
|
||||
"LIB",
|
||||
}
|
||||
|
||||
MODERN_ARCHS = {"x86_64", "arm64", "arm64e", "mips64", "riscv64"}
|
||||
|
||||
MINGW_ARCHS = {
|
||||
"x86": "i686",
|
||||
"arm64": "aarch64",
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
src = sys.argv[1]
|
||||
install_subdir = sys.argv[2]
|
||||
install_name = sys.argv[3]
|
||||
|
||||
destdir_prefix = os.environ["MESON_INSTALL_DESTDIR_PREFIX"]
|
||||
dst_dir = os.path.join(destdir_prefix, install_subdir)
|
||||
dst = os.path.join(dst_dir, install_name)
|
||||
|
||||
os.makedirs(dst_dir, exist_ok=True)
|
||||
shutil.copy2(src, dst)
|
||||
print(f"Installing {os.path.basename(src)} to {dst_dir}/{install_name}")
|
||||
+47
-3
@@ -8,6 +8,7 @@ helper_depends = []
|
||||
agent_modern = get_option('agent_modern')
|
||||
agent_legacy = get_option('agent_legacy')
|
||||
agent_compat = ''
|
||||
agent_host_compat = ''
|
||||
agent_emulated_modern = get_option('agent_emulated_modern')
|
||||
agent_emulated_legacy = get_option('agent_emulated_legacy')
|
||||
agent_depends = []
|
||||
@@ -28,6 +29,9 @@ if no_overridden_compat_bits
|
||||
build_py = [python, files('build.py')]
|
||||
|
||||
components = []
|
||||
if have_local_backend
|
||||
components += ['helper', 'agent']
|
||||
endif
|
||||
if build_gadget
|
||||
components += 'gadget'
|
||||
endif
|
||||
@@ -42,11 +46,14 @@ if no_overridden_compat_bits
|
||||
meson.global_build_root(),
|
||||
frida_version,
|
||||
host_os,
|
||||
host_os_family,
|
||||
host_abi,
|
||||
host_config,
|
||||
','.join(get_option('compat')),
|
||||
get_option('assets'),
|
||||
assets_mode,
|
||||
','.join(components),
|
||||
have_shared_glib ? 'upstream' : 'frida',
|
||||
use_apple_tls ? 'enabled' : 'disabled',
|
||||
'--',
|
||||
'>>>', cc.cmd_array(), '<<<',
|
||||
'>>>', cpp.cmd_array(), '<<<',
|
||||
@@ -64,18 +71,27 @@ if no_overridden_compat_bits
|
||||
depfile = lines[4]
|
||||
state = lines[5]
|
||||
|
||||
native_arch_suffix = '-' + host_abi
|
||||
|
||||
if host_os_family == 'darwin'
|
||||
install = false
|
||||
install_dir = asset_dir
|
||||
else
|
||||
install = true
|
||||
install_dir = []
|
||||
i = 0
|
||||
foreach varname : varnames
|
||||
if varname != 'arch_support_bundle' and (get_option('assets') == 'installed' or varname.startswith('gadget_'))
|
||||
output_name = outputs[i]
|
||||
needs_rename = output_name.contains(native_arch_suffix) or varname.contains('_emulated_')
|
||||
should_install = varname != 'arch_support_bundle' \
|
||||
and not needs_rename \
|
||||
and (assets_mode == 'installed' or varname.startswith('gadget_'))
|
||||
if should_install
|
||||
install_dir += varname.endswith('_modern') ? asset_dir_modern : asset_dir_legacy
|
||||
else
|
||||
install_dir += false
|
||||
endif
|
||||
i += 1
|
||||
endforeach
|
||||
endif
|
||||
|
||||
@@ -96,6 +112,7 @@ if no_overridden_compat_bits
|
||||
foreach output : arch_support.to_list()
|
||||
outpath = output.full_path()
|
||||
varname = varnames[i]
|
||||
output_name = outputs[i]
|
||||
|
||||
set_variable(varname, outpath)
|
||||
|
||||
@@ -103,7 +120,11 @@ if no_overridden_compat_bits
|
||||
helper_compat = outpath
|
||||
helper_depends += arch_support
|
||||
elif varname.startswith('agent_')
|
||||
agent_compat = outpath
|
||||
if output_name.contains('-' + host_abi + '.')
|
||||
agent_host_compat = outpath
|
||||
else
|
||||
agent_compat = outpath
|
||||
endif
|
||||
agent_depends += arch_support
|
||||
elif varname.startswith('gadget_')
|
||||
gadget_compat = outpath
|
||||
@@ -115,6 +136,29 @@ if no_overridden_compat_bits
|
||||
assert(false, f'unexpected variable name: @varname@')
|
||||
endif
|
||||
|
||||
if host_os_family != 'darwin'
|
||||
rename_target_dir = ''
|
||||
if output_name.contains(native_arch_suffix)
|
||||
rename_target_dir = asset_dir
|
||||
elif varname.contains('_emulated_')
|
||||
emulated_arch = output_name.split('-')[-1].split('.')[0]
|
||||
rename_target_dir = root_asset_dir / emulated_arch
|
||||
endif
|
||||
if rename_target_dir != ''
|
||||
install_name = output_name.replace(native_arch_suffix, '')
|
||||
foreach arch_suffix : ['-x86_64', '-x86', '-arm64', '-arm']
|
||||
install_name = install_name.replace(arch_suffix, '')
|
||||
endforeach
|
||||
if assets_mode == 'installed' or varname.startswith('gadget_')
|
||||
meson.add_install_script(python, files('install-renamed.py'),
|
||||
outpath,
|
||||
rename_target_dir,
|
||||
install_name,
|
||||
)
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
i += 1
|
||||
endforeach
|
||||
else
|
||||
|
||||
+2
-15
@@ -4,27 +4,14 @@ inject_sources = [
|
||||
]
|
||||
|
||||
system_vala_args = []
|
||||
|
||||
if host_os_family != 'windows'
|
||||
system_vala_args += ['--pkg=posix']
|
||||
endif
|
||||
|
||||
extra_link_args = []
|
||||
extra_link_depends = []
|
||||
if host_os_family == 'darwin'
|
||||
symlist = 'frida-inject.symbols'
|
||||
extra_link_args += ['-Wl,-exported_symbols_list,' + meson.current_source_dir() / symlist]
|
||||
extra_link_depends += [symlist]
|
||||
elif host_os_family != 'windows'
|
||||
symscript = 'frida-inject.version'
|
||||
extra_link_args += ['-Wl,--version-script,' + meson.current_source_dir() / symscript]
|
||||
extra_link_depends += [symscript]
|
||||
endif
|
||||
|
||||
raw_inject = executable('frida-inject-raw', inject_sources,
|
||||
vala_args: [core_vala_args, system_vala_args],
|
||||
link_args: extra_link_args,
|
||||
link_depends: extra_link_depends,
|
||||
link_args: executable_no_exports_link_args,
|
||||
link_depends: executable_no_exports_link_depends,
|
||||
dependencies: [json_glib_dep, core_dep],
|
||||
)
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
# include <signal.h>
|
||||
# endif
|
||||
#endif
|
||||
#ifdef HAVE_GIOOPENSSL
|
||||
#if defined (HAVE_GIOAPPLE)
|
||||
# include <gioapple.h>
|
||||
#elif defined (HAVE_GIOOPENSSL)
|
||||
# include <gioopenssl.h>
|
||||
#endif
|
||||
|
||||
@@ -31,7 +33,9 @@ _frida_agent_environment_init (void)
|
||||
|
||||
g_thread_set_garbage_handler (_frida_agent_on_pending_thread_garbage, NULL);
|
||||
|
||||
#ifdef HAVE_GIOOPENSSL
|
||||
#if defined (HAVE_GIOAPPLE)
|
||||
g_io_module_apple_register ();
|
||||
#elif defined (HAVE_GIOOPENSSL)
|
||||
g_io_module_openssl_register ();
|
||||
#endif
|
||||
|
||||
@@ -51,6 +55,8 @@ void
|
||||
_frida_agent_environment_deinit (void)
|
||||
{
|
||||
#ifndef HAVE_MUSL
|
||||
frida_libc_shim_prepare_to_deinit ();
|
||||
|
||||
gum_shutdown ();
|
||||
gio_shutdown ();
|
||||
glib_shutdown ();
|
||||
|
||||
+31
-11
@@ -117,6 +117,8 @@ namespace Frida.Agent {
|
||||
void * opaque_injector_state) {
|
||||
Environment._init ();
|
||||
|
||||
apply_linker_notifier_offsets (agent_parameters);
|
||||
|
||||
{
|
||||
Gum.MemoryRange? mapped_range = null;
|
||||
|
||||
@@ -186,6 +188,25 @@ namespace Frida.Agent {
|
||||
Environment._deinit ();
|
||||
}
|
||||
|
||||
private static void apply_linker_notifier_offsets (string agent_parameters) {
|
||||
foreach (unowned string token in agent_parameters.split ("|")) {
|
||||
if (!token.has_prefix ("linker-notifier-offsets:"))
|
||||
continue;
|
||||
|
||||
uint[] offsets = {};
|
||||
foreach (unowned string entry in token[24:].split (",")) {
|
||||
uint offset;
|
||||
if (uint.try_parse (entry, out offset))
|
||||
offsets += offset;
|
||||
}
|
||||
|
||||
if (offsets.length != 0)
|
||||
Gum.ModuleRegistry.set_rtld_notifier_offsets (offsets);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public static void resume_after_transition (ref Frida.UnloadPolicy unload_policy, void * opaque_injector_state) {
|
||||
{
|
||||
#if LINUX || FREEBSD
|
||||
@@ -282,30 +303,29 @@ namespace Frida.Agent {
|
||||
private async void start (owned FileDescriptorTablePadder padder) {
|
||||
string[] tokens = agent_parameters.split ("|");
|
||||
unowned string transport_uri = tokens[0];
|
||||
bool enable_exceptor = true;
|
||||
#if DARWIN
|
||||
enable_exceptor = !Gum.Darwin.query_hardened ();
|
||||
#endif
|
||||
var exceptor_mode = Gum.ExceptorMode.FULL;
|
||||
bool enable_unwind_broker = true;
|
||||
bool enable_exit_monitor = true;
|
||||
bool enable_thread_suspend_monitor = true;
|
||||
bool enable_unwind_sitter = true;
|
||||
foreach (unowned string option in tokens[1:]) {
|
||||
if (option == "eternal")
|
||||
ensure_eternalized ();
|
||||
else if (option == "sticky")
|
||||
stop_thread_on_unload = false;
|
||||
else if (option == "exceptor:off")
|
||||
enable_exceptor = false;
|
||||
exceptor_mode = OFF;
|
||||
else if (option == "exceptor:handler-only")
|
||||
exceptor_mode = HANDLER_ONLY;
|
||||
else if (option == "unwind-broker:off")
|
||||
enable_unwind_broker = false;
|
||||
else if (option == "exit-monitor:off")
|
||||
enable_exit_monitor = false;
|
||||
else if (option == "thread-suspend-monitor:off")
|
||||
enable_thread_suspend_monitor = false;
|
||||
else if (option == "unwind-sitter:off")
|
||||
enable_unwind_sitter = false;
|
||||
}
|
||||
|
||||
if (!enable_exceptor)
|
||||
Gum.Exceptor.disable ();
|
||||
if (exceptor_mode != FULL)
|
||||
Gum.Exceptor.set_mode (exceptor_mode);
|
||||
|
||||
{
|
||||
var interceptor = Gum.Interceptor.obtain ();
|
||||
@@ -317,7 +337,7 @@ namespace Frida.Agent {
|
||||
if (enable_thread_suspend_monitor)
|
||||
thread_suspend_monitor = new ThreadSuspendMonitor (this);
|
||||
|
||||
if (enable_unwind_sitter)
|
||||
if (enable_unwind_broker)
|
||||
unwind_sitter = new UnwindSitter (this);
|
||||
|
||||
this.interceptor = interceptor;
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
FRIDA_1.0 {
|
||||
global:
|
||||
frida_agent_main;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
|
||||
GLIBC_2.2.5 {
|
||||
local:
|
||||
fopen*;
|
||||
fseeko*;
|
||||
ftello*;
|
||||
} FRIDA_1.0;
|
||||
+119
-90
@@ -1,98 +1,127 @@
|
||||
agent_sources = [
|
||||
'agent.vala',
|
||||
'agent-glue.c',
|
||||
]
|
||||
|
||||
force_cpp_linking = have_v8
|
||||
if force_cpp_linking
|
||||
agent_sources += ['dummy.cpp']
|
||||
endif
|
||||
|
||||
extra_vala_args = []
|
||||
extra_link_args = []
|
||||
extra_link_depends = []
|
||||
|
||||
if host_os_family != 'windows'
|
||||
extra_vala_args += '--pkg=posix'
|
||||
endif
|
||||
if host_os_family == 'darwin'
|
||||
extra_vala_args += '--pkg=frida-gum-darwin-1.0'
|
||||
endif
|
||||
|
||||
if host_os_family == 'windows'
|
||||
if host_toolchain != 'microsoft'
|
||||
symfile = (host_arch == 'x86') ? 'frida-agent-x86.symbols' : 'frida-agent.symbols'
|
||||
extra_link_args += '-Wl,--retain-symbols-file,' + meson.current_source_dir() / symfile
|
||||
extra_link_depends += symfile
|
||||
endif
|
||||
elif host_os_family == 'darwin'
|
||||
extra_link_args += '-Wl,-exported_symbol,_frida_agent_main'
|
||||
else
|
||||
symscript = (host_os == 'android') ? 'frida-agent-android.version' : 'frida-agent.version'
|
||||
extra_link_args += '-Wl,--version-script,' + meson.current_source_dir() / symscript
|
||||
extra_link_depends += symscript
|
||||
endif
|
||||
|
||||
if host_os_family in ['linux', 'qnx']
|
||||
# For our libc shim to be effective.
|
||||
extra_link_args += '-Wl,-Bsymbolic'
|
||||
endif
|
||||
|
||||
raw_agent = shared_library('frida-agent-raw', agent_sources,
|
||||
name_prefix: 'lib',
|
||||
c_args: frida_component_cflags,
|
||||
vala_header: 'frida-agent.h',
|
||||
vala_args: [base_vala_args, payload_vala_args, extra_vala_args],
|
||||
link_args: extra_link_args,
|
||||
link_depends: extra_link_depends,
|
||||
vs_module_defs: 'frida-agent.def',
|
||||
override_options: ['b_asneeded=true'],
|
||||
dependencies: [gio_dep, gee_dep, gum_dep, gumjs_dep, base_dep, pipe_dep, tls_provider_dep, payload_dep],
|
||||
)
|
||||
|
||||
operations = ['--move', 'constructor', 'frida_libc_shim_init', 'first']
|
||||
if host_os_family != 'darwin'
|
||||
operations += ['--move', 'destructor', 'frida_libc_shim_deinit', 'first']
|
||||
endif
|
||||
modulated_agent = custom_target('frida-agent-modulated',
|
||||
input: raw_agent,
|
||||
output: 'libfrida-agent-modulated' + shlib_suffix,
|
||||
command: modulate + operations,
|
||||
)
|
||||
|
||||
need_universal_agent = host_os_family == 'darwin' \
|
||||
and get_option('assets') == 'installed' \
|
||||
and assets_mode == 'installed' \
|
||||
and agent_compat != ''
|
||||
|
||||
if host_os_family == 'darwin'
|
||||
identity = 'FridaAgent'
|
||||
else
|
||||
identity = agent_name
|
||||
endif
|
||||
agent = custom_target('frida-agent',
|
||||
input: modulated_agent,
|
||||
output: need_universal_agent ? f'@agent_name@-@host_abi@' : agent_name,
|
||||
command: post_process + ['shared-library', identity],
|
||||
build_by_default: true,
|
||||
install: get_option('assets') == 'installed' and not need_universal_agent,
|
||||
install_dir: asset_dir,
|
||||
)
|
||||
# When compat provides the host-arch agent (e.g. when use_apple_tls is on, so
|
||||
# the agent has to be built separately to avoid pulling in Apple frameworks),
|
||||
# skip building it here.
|
||||
build_agent = agent_host_compat == ''
|
||||
|
||||
agent_dep = declare_dependency(link_with: agent)
|
||||
if build_agent
|
||||
agent_sources = [
|
||||
'agent.vala',
|
||||
'agent-glue.c',
|
||||
]
|
||||
|
||||
if need_universal_agent
|
||||
custom_target('frida-agent-universal',
|
||||
input: agent,
|
||||
output: agent_name,
|
||||
command: [
|
||||
lipo,
|
||||
'@INPUT@',
|
||||
agent_compat,
|
||||
'-create',
|
||||
'-output', meson.current_build_dir() / agent_name,
|
||||
],
|
||||
depends: agent_depends,
|
||||
install: true,
|
||||
force_cpp_linking = have_v8
|
||||
if force_cpp_linking
|
||||
agent_sources += ['dummy.cpp']
|
||||
endif
|
||||
|
||||
extra_vala_args = []
|
||||
extra_link_args = []
|
||||
extra_link_depends = []
|
||||
|
||||
if host_os_family != 'windows'
|
||||
extra_vala_args += '--pkg=posix'
|
||||
endif
|
||||
if host_os_family == 'darwin'
|
||||
extra_vala_args += '--pkg=frida-gum-darwin-1.0'
|
||||
endif
|
||||
|
||||
if host_os_family == 'windows'
|
||||
if host_toolchain != 'microsoft'
|
||||
symfile = (host_arch == 'x86') ? 'frida-agent-x86.symbols' : 'frida-agent.symbols'
|
||||
extra_link_args += '-Wl,--retain-symbols-file,' + meson.current_source_dir() / symfile
|
||||
extra_link_depends += symfile
|
||||
endif
|
||||
elif host_os_family == 'darwin'
|
||||
extra_link_args += '-Wl,-exported_symbol,_frida_agent_main'
|
||||
else
|
||||
if libc == 'glibc'
|
||||
symscript = 'frida-agent-glibc.version'
|
||||
elif host_os == 'android'
|
||||
symscript = 'frida-agent-android.version'
|
||||
else
|
||||
symscript = 'frida-agent.version'
|
||||
endif
|
||||
extra_link_args += '-Wl,--version-script,' + meson.current_source_dir() / symscript
|
||||
extra_link_depends += symscript
|
||||
endif
|
||||
|
||||
if host_os_family in ['linux', 'qnx']
|
||||
# For our libc shim to be effective.
|
||||
extra_link_args += '-Wl,-Bsymbolic'
|
||||
endif
|
||||
|
||||
raw_agent = shared_library('frida-agent-raw', agent_sources,
|
||||
name_prefix: 'lib',
|
||||
c_args: frida_component_cflags,
|
||||
vala_header: 'frida-agent.h',
|
||||
vala_args: [base_vala_args, payload_vala_args, extra_vala_args],
|
||||
link_args: extra_link_args,
|
||||
link_depends: extra_link_depends,
|
||||
vs_module_defs: 'frida-agent.def',
|
||||
override_options: ['b_asneeded=true'],
|
||||
dependencies: [gio_dep, gee_dep, gum_dep, gumjs_dep, base_dep, pipe_dep, tls_provider_dep, payload_dep],
|
||||
)
|
||||
|
||||
operations = ['--move', 'constructor', 'frida_libc_shim_init', 'first']
|
||||
if host_os_family != 'darwin'
|
||||
operations += ['--move', 'destructor', 'frida_libc_shim_deinit', 'first']
|
||||
endif
|
||||
modulated_agent = custom_target('frida-agent-modulated',
|
||||
input: raw_agent,
|
||||
output: 'libfrida-agent-modulated' + shlib_suffix,
|
||||
command: modulate + operations,
|
||||
)
|
||||
|
||||
if host_os_family == 'darwin'
|
||||
identity = 'FridaAgent'
|
||||
else
|
||||
identity = agent_name
|
||||
endif
|
||||
agent = custom_target('frida-agent',
|
||||
input: modulated_agent,
|
||||
output: need_universal_agent ? f'@agent_name@-@host_abi@' : agent_name,
|
||||
command: post_process + ['shared-library', identity],
|
||||
build_by_default: true,
|
||||
install: assets_mode == 'installed' and not need_universal_agent,
|
||||
install_dir: asset_dir,
|
||||
)
|
||||
|
||||
agent_dep = declare_dependency(link_with: agent)
|
||||
endif
|
||||
|
||||
if need_universal_agent
|
||||
if build_agent
|
||||
custom_target('frida-agent-universal',
|
||||
input: agent,
|
||||
output: agent_name,
|
||||
command: [
|
||||
lipo,
|
||||
'@INPUT@',
|
||||
agent_compat,
|
||||
'-create',
|
||||
'-output', meson.current_build_dir() / agent_name,
|
||||
],
|
||||
depends: agent_depends,
|
||||
install: true,
|
||||
install_dir: asset_dir,
|
||||
)
|
||||
else
|
||||
custom_target('frida-agent-universal',
|
||||
output: agent_name,
|
||||
command: [
|
||||
lipo,
|
||||
agent_host_compat,
|
||||
agent_compat,
|
||||
'-create',
|
||||
'-output', meson.current_build_dir() / agent_name,
|
||||
],
|
||||
depends: agent_depends,
|
||||
install: true,
|
||||
install_dir: asset_dir,
|
||||
)
|
||||
endif
|
||||
endif
|
||||
@@ -0,0 +1,53 @@
|
||||
namespace Frida {
|
||||
public class AssetLocation : Object {
|
||||
public string libdir {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public static AssetLocation detect () {
|
||||
string? module_path = null;
|
||||
Gum.Address our_address = Gum.Address.from_pointer (Gum.strip_code_pointer ((void *) detect));
|
||||
|
||||
Gum.Process.enumerate_modules ((details) => {
|
||||
var range = details.range;
|
||||
if (our_address >= range.base_address && our_address < range.base_address + range.size) {
|
||||
module_path = details.path;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
assert (module_path != null);
|
||||
|
||||
string libdir = Path.get_dirname (module_path);
|
||||
string basename = Path.get_basename (libdir).down ();
|
||||
if (basename == "bin" || basename == "sbin") {
|
||||
string parent = Path.get_dirname (libdir);
|
||||
string candidate = Path.build_filename (parent, Config.FRIDA_LIBDIR);
|
||||
if (FileUtils.test (candidate, IS_DIR))
|
||||
libdir = candidate;
|
||||
}
|
||||
|
||||
return new AssetLocation (libdir);
|
||||
}
|
||||
|
||||
private AssetLocation (string libdir) {
|
||||
Object (libdir: libdir);
|
||||
}
|
||||
|
||||
public string derive_asset_path (string arch, string filename) {
|
||||
string root = Path.build_filename (libdir, Config.FRIDA_LIBDIR_NAME);
|
||||
if (FileUtils.test (root, IS_DIR))
|
||||
#if DARWIN
|
||||
return Path.build_filename (root, filename);
|
||||
#else
|
||||
return Path.build_filename (root, arch, filename);
|
||||
#endif
|
||||
return Path.build_filename (libdir, filename);
|
||||
}
|
||||
|
||||
public string derive_plugin_path (string filename) {
|
||||
return Path.build_filename (libdir, Config.FRIDA_LIBDIR_NAME, "plugins", filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
+262
-127
@@ -309,8 +309,17 @@ namespace Frida {
|
||||
|
||||
public sealed class Buffer : Object {
|
||||
public Bytes bytes {
|
||||
get;
|
||||
construct;
|
||||
get {
|
||||
if (_bytes == null)
|
||||
_bytes = new Bytes.static (_data);
|
||||
return _bytes;
|
||||
}
|
||||
}
|
||||
|
||||
public size_t size {
|
||||
get {
|
||||
return _data.length;
|
||||
}
|
||||
}
|
||||
|
||||
public ByteOrder byte_order {
|
||||
@@ -323,20 +332,39 @@ namespace Frida {
|
||||
construct;
|
||||
}
|
||||
|
||||
private unowned uint8 * data;
|
||||
private size_t size;
|
||||
private Bytes? _bytes;
|
||||
private unowned uint8[] _data;
|
||||
|
||||
public Buffer (Bytes bytes, ByteOrder byte_order = HOST, uint pointer_size = (uint) sizeof (size_t)) {
|
||||
Object (
|
||||
bytes: bytes,
|
||||
byte_order: byte_order,
|
||||
pointer_size: pointer_size
|
||||
);
|
||||
|
||||
reset_bytes (bytes);
|
||||
}
|
||||
|
||||
construct {
|
||||
data = bytes.get_data ();
|
||||
size = bytes.get_size ();
|
||||
public Buffer.from_data (uint8[] data, ByteOrder byte_order = HOST, uint pointer_size = (uint) sizeof (size_t)) {
|
||||
Object (
|
||||
byte_order: byte_order,
|
||||
pointer_size: pointer_size
|
||||
);
|
||||
|
||||
reset_data (data);
|
||||
}
|
||||
|
||||
public void reset_bytes (Bytes bytes) {
|
||||
_bytes = bytes;
|
||||
_data = bytes.get_data ();
|
||||
}
|
||||
|
||||
public void reset_data (uint8[] data) {
|
||||
_bytes = null;
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public unowned uint8[] peek_data () {
|
||||
return _data;
|
||||
}
|
||||
|
||||
public uint64 read_pointer (size_t offset) {
|
||||
@@ -470,17 +498,13 @@ namespace Frida {
|
||||
return *((double *) &bits);
|
||||
}
|
||||
|
||||
public string read_string (size_t offset) throws Error {
|
||||
public unowned string read_string (size_t offset) throws Error {
|
||||
string * start = (string *) get_pointer (offset, sizeof (char));
|
||||
size_t max_length = size - offset;
|
||||
size_t max_length = _data.length - offset;
|
||||
string * end = memchr (start, 0, max_length);
|
||||
if (end == null)
|
||||
throw new Error.PROTOCOL ("Missing null character");
|
||||
size_t size = end - start;
|
||||
string val = start->substring (0, (long) size);
|
||||
if (!val.validate ())
|
||||
throw new Error.PROTOCOL ("Invalid UTF-8 string");
|
||||
return val;
|
||||
return start;
|
||||
}
|
||||
|
||||
[CCode (cname = "memchr", cheader_filename = "string.h")]
|
||||
@@ -488,17 +512,14 @@ namespace Frida {
|
||||
|
||||
public string read_fixed_string (size_t offset, size_t size) throws Error {
|
||||
string * start = (string *) get_pointer (offset, size);
|
||||
size_t max_length = size_t.min (size, this.size - offset);
|
||||
size_t max_length = size_t.min (size, _data.length - offset);
|
||||
string * end = memchr (start, 0, max_length);
|
||||
size_t n;
|
||||
if (end != null)
|
||||
n = end - start;
|
||||
else
|
||||
n = size;
|
||||
string val = start->substring (0, (long) n);
|
||||
if (!val.validate ())
|
||||
throw new Error.PROTOCOL ("Invalid UTF-8 string");
|
||||
return val;
|
||||
return start->substring (0, (long) n);
|
||||
}
|
||||
|
||||
public unowned Buffer write_string (size_t offset, string val) {
|
||||
@@ -508,9 +529,14 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public Bytes read_bytes (size_t offset, size_t size) {
|
||||
var data = new uint8[size + 1];
|
||||
Memory.copy (data, get_pointer (offset, size), size);
|
||||
return new Bytes.take ((owned) data);
|
||||
if (_bytes == null)
|
||||
return make_bytes_with_owner ((uint8 *) _data + offset, _data.length - size, this);
|
||||
return _bytes[offset:offset + size];
|
||||
}
|
||||
|
||||
public unowned uint8[] read_data (size_t offset, size_t size) {
|
||||
unowned uint8 * ptr = get_pointer (offset, size);
|
||||
return ((uint8[]) ptr)[:size];
|
||||
}
|
||||
|
||||
public unowned Buffer write_bytes (size_t offset, Bytes bytes) {
|
||||
@@ -521,129 +547,49 @@ namespace Frida {
|
||||
|
||||
private uint8 * get_pointer (size_t offset, size_t n) {
|
||||
size_t minimum_size = offset + n;
|
||||
assert (size >= minimum_size);
|
||||
|
||||
return data + offset;
|
||||
assert (_data.length >= minimum_size);
|
||||
return (uint8 *) _data + offset;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class BufferReader {
|
||||
public Buffer buffer {
|
||||
get;
|
||||
}
|
||||
|
||||
public size_t offset {
|
||||
get {
|
||||
return _offset;
|
||||
}
|
||||
get;
|
||||
}
|
||||
|
||||
public size_t available {
|
||||
get {
|
||||
return buffer.bytes.get_size () - _offset;
|
||||
return _size - _offset;
|
||||
}
|
||||
}
|
||||
|
||||
private Buffer buffer;
|
||||
private size_t _offset = 0;
|
||||
private size_t _size;
|
||||
|
||||
public BufferReader (Buffer buf) {
|
||||
buffer = buf;
|
||||
public BufferReader (Buffer buffer) {
|
||||
reset (buffer);
|
||||
}
|
||||
|
||||
public uint64 read_pointer () throws Error {
|
||||
var pointer_size = buffer.pointer_size;
|
||||
check_available (pointer_size);
|
||||
var ptr = buffer.read_pointer (_offset);
|
||||
_offset += pointer_size;
|
||||
return ptr;
|
||||
public void reset (Buffer buffer) {
|
||||
_buffer = buffer;
|
||||
_size = buffer.size;
|
||||
_offset = 0;
|
||||
}
|
||||
|
||||
public int8 read_int8 () throws Error {
|
||||
check_available (sizeof (int8));
|
||||
var val = buffer.read_int8 (_offset);
|
||||
_offset += sizeof (int8);
|
||||
return val;
|
||||
public void reset_at (Buffer buffer, size_t offset) throws Error {
|
||||
_buffer = buffer;
|
||||
_size = buffer.size;
|
||||
seek (offset);
|
||||
}
|
||||
|
||||
public uint8 read_uint8 () throws Error {
|
||||
check_available (sizeof (uint8));
|
||||
var val = buffer.read_uint8 (_offset);
|
||||
_offset += sizeof (uint8);
|
||||
return val;
|
||||
}
|
||||
|
||||
public int16 read_int16 () throws Error {
|
||||
check_available (sizeof (int16));
|
||||
var val = buffer.read_int16 (_offset);
|
||||
_offset += sizeof (int16);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint16 read_uint16 () throws Error {
|
||||
check_available (sizeof (uint16));
|
||||
var val = buffer.read_uint16 (_offset);
|
||||
_offset += sizeof (uint16);
|
||||
return val;
|
||||
}
|
||||
|
||||
public int32 read_int32 () throws Error {
|
||||
check_available (sizeof (int32));
|
||||
var val = buffer.read_int32 (_offset);
|
||||
_offset += sizeof (int32);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint32 read_uint32 () throws Error {
|
||||
check_available (sizeof (uint32));
|
||||
var val = buffer.read_uint32 (_offset);
|
||||
_offset += sizeof (uint32);
|
||||
return val;
|
||||
}
|
||||
|
||||
public int64 read_int64 () throws Error {
|
||||
check_available (sizeof (int64));
|
||||
var val = buffer.read_int64 (_offset);
|
||||
_offset += sizeof (int64);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint64 read_uint64 () throws Error {
|
||||
check_available (sizeof (uint64));
|
||||
var val = buffer.read_uint64 (_offset);
|
||||
_offset += sizeof (uint64);
|
||||
return val;
|
||||
}
|
||||
|
||||
public float read_float () throws Error {
|
||||
check_available (sizeof (float));
|
||||
var val = buffer.read_float (_offset);
|
||||
_offset += sizeof (float);
|
||||
return val;
|
||||
}
|
||||
|
||||
public double read_double () throws Error {
|
||||
check_available (sizeof (double));
|
||||
var val = buffer.read_double (_offset);
|
||||
_offset += sizeof (double);
|
||||
return val;
|
||||
}
|
||||
|
||||
public string read_string () throws Error {
|
||||
check_available (1);
|
||||
var val = buffer.read_string (_offset);
|
||||
_offset += val.length + 1;
|
||||
return val;
|
||||
}
|
||||
|
||||
public string read_fixed_string (size_t size) throws Error {
|
||||
check_available (size);
|
||||
var val = buffer.read_fixed_string (_offset, size);
|
||||
_offset += size;
|
||||
return val;
|
||||
}
|
||||
|
||||
public Bytes read_bytes (size_t size) throws Error {
|
||||
check_available (size);
|
||||
var val = buffer.bytes[_offset:_offset + size];
|
||||
_offset += size;
|
||||
return val;
|
||||
public unowned BufferReader seek (size_t offset) throws Error {
|
||||
if (offset > _size)
|
||||
throw new Error.PROTOCOL ("Malformed buffer: truncated");
|
||||
_offset = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferReader skip (size_t n) throws Error {
|
||||
@@ -652,6 +598,195 @@ namespace Frida {
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferReader align (size_t alignment) throws Error {
|
||||
size_t mask = alignment - 1;
|
||||
size_t rem = _offset & mask;
|
||||
if (rem != 0)
|
||||
skip (alignment - rem);
|
||||
return this;
|
||||
}
|
||||
|
||||
public uint64 peek_pointer () throws Error {
|
||||
var pointer_size = _buffer.pointer_size;
|
||||
check_available (pointer_size);
|
||||
return _buffer.read_pointer (_offset);
|
||||
}
|
||||
|
||||
public uint64 read_pointer () throws Error {
|
||||
var pointer_size = _buffer.pointer_size;
|
||||
check_available (pointer_size);
|
||||
var ptr = _buffer.read_pointer (_offset);
|
||||
_offset += pointer_size;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
public int8 peek_int8 () throws Error {
|
||||
check_available (sizeof (int8));
|
||||
return _buffer.read_int8 (_offset);
|
||||
}
|
||||
|
||||
public int8 read_int8 () throws Error {
|
||||
check_available (sizeof (int8));
|
||||
var val = _buffer.read_int8 (_offset);
|
||||
_offset += sizeof (int8);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint8 peek_uint8 () throws Error {
|
||||
check_available (sizeof (uint8));
|
||||
return _buffer.read_uint8 (_offset);
|
||||
}
|
||||
|
||||
public uint8 read_uint8 () throws Error {
|
||||
check_available (sizeof (uint8));
|
||||
var val = _buffer.read_uint8 (_offset);
|
||||
_offset += sizeof (uint8);
|
||||
return val;
|
||||
}
|
||||
|
||||
public int16 peek_int16 () throws Error {
|
||||
check_available (sizeof (int16));
|
||||
return _buffer.read_int16 (_offset);
|
||||
}
|
||||
|
||||
public int16 read_int16 () throws Error {
|
||||
check_available (sizeof (int16));
|
||||
var val = _buffer.read_int16 (_offset);
|
||||
_offset += sizeof (int16);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint16 peek_uint16 () throws Error {
|
||||
check_available (sizeof (uint16));
|
||||
return _buffer.read_uint16 (_offset);
|
||||
}
|
||||
|
||||
public uint16 read_uint16 () throws Error {
|
||||
check_available (sizeof (uint16));
|
||||
var val = _buffer.read_uint16 (_offset);
|
||||
_offset += sizeof (uint16);
|
||||
return val;
|
||||
}
|
||||
|
||||
public int32 peek_int32 () throws Error {
|
||||
check_available (sizeof (int32));
|
||||
return _buffer.read_int32 (_offset);
|
||||
}
|
||||
|
||||
public int32 read_int32 () throws Error {
|
||||
check_available (sizeof (int32));
|
||||
var val = _buffer.read_int32 (_offset);
|
||||
_offset += sizeof (int32);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint32 peek_uint32 () throws Error {
|
||||
check_available (sizeof (uint32));
|
||||
return _buffer.read_uint32 (_offset);
|
||||
}
|
||||
|
||||
public uint32 read_uint32 () throws Error {
|
||||
check_available (sizeof (uint32));
|
||||
var val = _buffer.read_uint32 (_offset);
|
||||
_offset += sizeof (uint32);
|
||||
return val;
|
||||
}
|
||||
|
||||
public int64 peek_int64 () throws Error {
|
||||
check_available (sizeof (int64));
|
||||
return _buffer.read_int64 (_offset);
|
||||
}
|
||||
|
||||
public int64 read_int64 () throws Error {
|
||||
check_available (sizeof (int64));
|
||||
var val = _buffer.read_int64 (_offset);
|
||||
_offset += sizeof (int64);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint64 peek_uint64 () throws Error {
|
||||
check_available (sizeof (uint64));
|
||||
return _buffer.read_uint64 (_offset);
|
||||
}
|
||||
|
||||
public uint64 read_uint64 () throws Error {
|
||||
check_available (sizeof (uint64));
|
||||
var val = _buffer.read_uint64 (_offset);
|
||||
_offset += sizeof (uint64);
|
||||
return val;
|
||||
}
|
||||
|
||||
public float peek_float () throws Error {
|
||||
check_available (sizeof (float));
|
||||
return _buffer.read_float (_offset);
|
||||
}
|
||||
|
||||
public float read_float () throws Error {
|
||||
check_available (sizeof (float));
|
||||
var val = _buffer.read_float (_offset);
|
||||
_offset += sizeof (float);
|
||||
return val;
|
||||
}
|
||||
|
||||
public double peek_double () throws Error {
|
||||
check_available (sizeof (double));
|
||||
return _buffer.read_double (_offset);
|
||||
}
|
||||
|
||||
public double read_double () throws Error {
|
||||
check_available (sizeof (double));
|
||||
var val = _buffer.read_double (_offset);
|
||||
_offset += sizeof (double);
|
||||
return val;
|
||||
}
|
||||
|
||||
public unowned string peek_string () throws Error {
|
||||
check_available (1);
|
||||
return _buffer.read_string (_offset);
|
||||
}
|
||||
|
||||
public unowned string read_string () throws Error {
|
||||
check_available (1);
|
||||
unowned string val = _buffer.read_string (_offset);
|
||||
_offset += val.length + 1;
|
||||
return val;
|
||||
}
|
||||
|
||||
public string peek_fixed_string (size_t size) throws Error {
|
||||
check_available (size);
|
||||
return _buffer.read_fixed_string (_offset, size);
|
||||
}
|
||||
|
||||
public string read_fixed_string (size_t size) throws Error {
|
||||
check_available (size);
|
||||
var val = _buffer.read_fixed_string (_offset, size);
|
||||
_offset += size;
|
||||
return val;
|
||||
}
|
||||
|
||||
public unowned uint8[] peek_data (size_t size) throws Error {
|
||||
check_available (size);
|
||||
return _buffer.read_data (_offset, size);
|
||||
}
|
||||
|
||||
public unowned uint8[] read_data (size_t size) throws Error {
|
||||
check_available (size);
|
||||
unowned uint8[] slice = _buffer.read_data (_offset, size);
|
||||
_offset += size;
|
||||
return slice;
|
||||
}
|
||||
|
||||
public Bytes read_bytes (size_t size) throws Error {
|
||||
check_available (size);
|
||||
var val = _buffer.read_bytes (_offset, size);
|
||||
_offset += size;
|
||||
return val;
|
||||
}
|
||||
|
||||
public unowned uint8[] read_remaining_data () throws Error {
|
||||
return read_data (available);
|
||||
}
|
||||
|
||||
private void check_available (size_t n) throws Error {
|
||||
if (available < n)
|
||||
throw new Error.PROTOCOL ("Malformed buffer: truncated");
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
#ifndef __FRIDA_ATOMICS_H__
|
||||
#define __FRIDA_ATOMICS_H__
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
/* TODO: Add once needed. */
|
||||
|
||||
#else
|
||||
|
||||
static inline guint64 frida_atomics_load_u64_acquire (volatile guint64 * p)
|
||||
typedef guint64 FridaAtomicU64 __attribute__ ((aligned (8)));
|
||||
|
||||
static inline guint64
|
||||
frida_atomics_load_u64_acquire (volatile FridaAtomicU64 * p)
|
||||
{
|
||||
return __atomic_load_n (p, __ATOMIC_ACQUIRE);
|
||||
}
|
||||
|
||||
static inline void frida_atomics_store_u64_release (volatile guint64 * p, guint64 v)
|
||||
static inline void
|
||||
frida_atomics_store_u64_release (volatile FridaAtomicU64 * p, guint64 v)
|
||||
{
|
||||
__atomic_store_n (p, v, __ATOMIC_RELEASE);
|
||||
}
|
||||
@@ -24,4 +32,6 @@ static inline guint32 frida_atomics_load_u32_acquire (volatile guint32 * p)
|
||||
|
||||
#endif
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,6 @@
|
||||
[CCode (cheader_filename = "frida-atomics.h")]
|
||||
namespace Frida.Atomics {
|
||||
public static uint64 load_u64_acquire (void * p);
|
||||
public static void store_u64_release (void * p, uint64 v);
|
||||
public static uint32 load_u32_acquire (void * p);
|
||||
public static uint64 load_u64_acquire (uint64 * p);
|
||||
public static void store_u64_release (uint64 * p, uint64 v);
|
||||
public static uint32 load_u32_acquire (uint32 * p);
|
||||
}
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <glib.h>
|
||||
#include <xpc/xpc.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef void (* FridaXpcHandler) (xpc_object_t object, gpointer user_data);
|
||||
typedef gboolean (* FridaXpcDictionaryApplier) (const gchar * key, xpc_object_t val, gpointer user_data);
|
||||
|
||||
@@ -17,6 +19,8 @@ void _frida_xpc_connection_send_message_with_reply (xpc_connection_t connection,
|
||||
gchar * _frida_xpc_object_to_string (xpc_object_t object);
|
||||
gboolean _frida_xpc_dictionary_apply (xpc_object_t dict, FridaXpcDictionaryApplier applier, gpointer user_data);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,41 @@
|
||||
#ifndef __FRIDA_JNI_H__
|
||||
#define __FRIDA_JNI_H__
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef jint (* JNIDestroyJavaVMFunc) (JavaVM *);
|
||||
typedef jint (* JNIAttachCurrentThreadFunc) (JavaVM *, JNIEnv **, void *);
|
||||
typedef jint (* JNIDetachCurrentThreadFunc) (JavaVM *);
|
||||
typedef jint (* JNIGetEnvFunc) (JavaVM *, JNIEnv **, jint);
|
||||
|
||||
typedef jclass (* JNIFindClassFunc) (JNIEnv *, const char *);
|
||||
|
||||
typedef jthrowable (* JNIExceptionOccurredFunc) (JNIEnv *);
|
||||
typedef void (* JNIExceptionDescribeFunc) (JNIEnv *);
|
||||
typedef void (* JNIExceptionClearFunc) (JNIEnv *);
|
||||
|
||||
typedef jint (* JNIPushLocalFrameFunc) (JNIEnv *, jint);
|
||||
typedef jobject (* JNIPopLocalFrameFunc) (JNIEnv *, jobject);
|
||||
|
||||
typedef jobject (* JNINewGlobalRefFunc) (JNIEnv *, jobject);
|
||||
typedef void (* JNIDeleteGlobalRefFunc) (JNIEnv *, jobject);
|
||||
typedef void (* JNIDeleteLocalRefFunc) (JNIEnv *, jobject);
|
||||
|
||||
typedef jobject (* JNINewObjectFunc) (JNIEnv *, jclass, jmethodID, ...);
|
||||
|
||||
typedef jclass (* JNIGetObjectClassFunc) (JNIEnv *, jobject);
|
||||
typedef jmethodID (* JNIGetMethodIDFunc) (JNIEnv *, jclass, const char *, const char *);
|
||||
|
||||
typedef jobject (* JNICallObjectMethodFunc) (JNIEnv *, jobject, jmethodID, ...);
|
||||
|
||||
typedef jstring (* JNINewStringUTFFunc) (JNIEnv *, const char *);
|
||||
typedef const char * (* JNIGetStringUTFCharsFunc) (JNIEnv *, jstring, jboolean *);
|
||||
typedef void (* JNIReleaseStringUTFCharsFunc) (JNIEnv *, jstring, const char *);
|
||||
|
||||
typedef jboolean (* JNIExceptionCheckFunc) (JNIEnv *);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
+49
-11
@@ -46,7 +46,7 @@ namespace Frida {
|
||||
public extern unowned LinuxSyscallSignature[]? get_compat32_syscall_signatures ();
|
||||
|
||||
public struct LinuxSyscallSignature {
|
||||
public uint nr;
|
||||
public int nr;
|
||||
public string name;
|
||||
public uint8 nargs;
|
||||
public LinuxSyscallArg args[6];
|
||||
@@ -128,6 +128,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
#if HAVE_LOCAL_BACKEND
|
||||
public sealed class BpfObject {
|
||||
public Programs programs {
|
||||
get;
|
||||
@@ -138,37 +139,35 @@ namespace Frida {
|
||||
}
|
||||
|
||||
internal Libbpf.Object handle;
|
||||
private char[] kernel_log_buf;
|
||||
|
||||
public static BpfObject open (string name, uint8[] blob) throws Error {
|
||||
var log_buf = new char[64 * 1024];
|
||||
|
||||
var opts = Libbpf.Object.OpenOpts ();
|
||||
opts.sz = sizeof (Libbpf.Object.OpenOpts);
|
||||
opts.object_name = name;
|
||||
opts.kernel_log_buf = log_buf;
|
||||
|
||||
var log = new LibbpfLogScope ();
|
||||
var handle = Libbpf.Object.open_mem (blob, opts);
|
||||
if (handle == null)
|
||||
throw_libbpf_error (errno);
|
||||
throw_libbpf_error (errno, log.take ());
|
||||
|
||||
return new BpfObject ((owned) handle, (owned) log_buf);
|
||||
return new BpfObject ((owned) handle);
|
||||
}
|
||||
|
||||
private BpfObject (owned Libbpf.Object handle, owned char[] kernel_log_buf) {
|
||||
private BpfObject (owned Libbpf.Object handle) {
|
||||
this.handle = (owned) handle;
|
||||
this.kernel_log_buf = (owned) kernel_log_buf;
|
||||
|
||||
_programs = new Programs (this);
|
||||
_maps = new Maps (this);
|
||||
}
|
||||
|
||||
public void prepare () throws Error {
|
||||
check_libbpf_result (handle.prepare ());
|
||||
var log = new LibbpfLogScope ();
|
||||
check_libbpf_result (handle.prepare (), log.take ());
|
||||
}
|
||||
|
||||
public void load () throws Error {
|
||||
check_libbpf_result (handle.load (), (string) kernel_log_buf);
|
||||
var log = new LibbpfLogScope ();
|
||||
check_libbpf_result (handle.load (), log.take ());
|
||||
}
|
||||
|
||||
public sealed class Programs {
|
||||
@@ -348,6 +347,18 @@ namespace Frida {
|
||||
remove_raw ((uint8[]) &key);
|
||||
}
|
||||
|
||||
public void update_u64_u8 (uint64 key, uint8 val) throws Error {
|
||||
update_raw ((uint8[]) &key, (uint8[]) &val);
|
||||
}
|
||||
|
||||
public void update_u64_u32 (uint64 key, uint32 val) throws Error {
|
||||
update_raw ((uint8[]) &key, (uint8[]) &val);
|
||||
}
|
||||
|
||||
public void remove_u64 (uint64 key) throws Error {
|
||||
remove_raw ((uint8[]) &key);
|
||||
}
|
||||
|
||||
public void foreach_percpu_value<T> (uint8[] key, PercpuValueFunc<T> func) throws Error {
|
||||
var ncpus = check_libbpf_result (Libbpf.num_possible_cpus ());
|
||||
var percpu_stride = round_up_8 (value_size);
|
||||
@@ -384,6 +395,30 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class LibbpfLogScope {
|
||||
private static StringBuilder buffer;
|
||||
|
||||
public LibbpfLogScope () {
|
||||
if (buffer == null)
|
||||
buffer = new StringBuilder ();
|
||||
buffer.truncate ();
|
||||
Libbpf.set_print (on_message);
|
||||
}
|
||||
|
||||
~LibbpfLogScope () {
|
||||
Libbpf.set_print (null);
|
||||
}
|
||||
|
||||
public string take () {
|
||||
return buffer.str.chomp ();
|
||||
}
|
||||
|
||||
private static int on_message (Libbpf.PrintLevel level, string format, va_list args) {
|
||||
buffer.append_vprintf (format, args);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private int check_libbpf_result (int result, string details = "") throws Error {
|
||||
if (result < 0)
|
||||
throw_libbpf_error (result, details);
|
||||
@@ -510,6 +545,7 @@ namespace Frida {
|
||||
STOP,
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace PerfEvent {
|
||||
public FileDescriptor open (PerfEventAttr * attr, int pid, int cpu, int group_fd, uint flags) throws Error {
|
||||
@@ -524,7 +560,9 @@ namespace Frida {
|
||||
throw new Error.NOT_SUPPORTED ("%s (errno=%d: %s)".printf (message, Posix.errno, Posix.strerror (Posix.errno)));
|
||||
}
|
||||
|
||||
#if HAVE_LOCAL_BACKEND
|
||||
private size_t round_up_8 (size_t x) {
|
||||
return (x + 7) & ~((size_t) 7);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
+17
-10
@@ -1,4 +1,5 @@
|
||||
base_sources = [
|
||||
'asset-location.vala',
|
||||
'session.vala',
|
||||
'session-glue.c',
|
||||
'channel.vala',
|
||||
@@ -15,6 +16,8 @@ base_sources = [
|
||||
'buffer.vala',
|
||||
'buffer-glue.c',
|
||||
'value.vala',
|
||||
'xnu-syscalls' / 'xnu-syscalls.vala',
|
||||
'xnu-syscalls' / 'xnu-syscalls-glue.c',
|
||||
]
|
||||
|
||||
extra_vala_args = []
|
||||
@@ -45,8 +48,10 @@ if host_os_family == 'linux'
|
||||
extra_vala_args += [
|
||||
'--pkg=linux',
|
||||
'--pkg=frida-atomics',
|
||||
'--pkg=libbpf',
|
||||
]
|
||||
if have_local_backend
|
||||
extra_vala_args += '--pkg=libbpf'
|
||||
endif
|
||||
|
||||
syscall_arch_from_abi = {
|
||||
'armbe': 'arm',
|
||||
@@ -73,7 +78,7 @@ extra_deps = []
|
||||
if host_os_family != 'windows'
|
||||
extra_deps += gio_unix_dep
|
||||
endif
|
||||
if host_os_family == 'linux'
|
||||
if host_os_family == 'linux' and have_local_backend
|
||||
extra_deps += libbpf_dep
|
||||
endif
|
||||
|
||||
@@ -110,7 +115,7 @@ base = static_library('frida-base-' + api_version, base_sources,
|
||||
vala_gir: base_gir_name,
|
||||
vala_vapi: f'frida-base-@[email protected]',
|
||||
dependencies: [glib_dep, gobject_dep, gio_dep, libsoup_dep, nice_dep, usrsctp_dep, gee_dep, json_glib_dep, gum_dep, openssl_dep] + extra_deps,
|
||||
install: true,
|
||||
install: have_in_tree_payloads,
|
||||
install_dir: [true, header_install_dir, true, false],
|
||||
)
|
||||
|
||||
@@ -122,10 +127,12 @@ base_dep = declare_dependency(
|
||||
|
||||
base_build_dir = meson.current_build_dir()
|
||||
|
||||
pkg = import('pkgconfig')
|
||||
pkg.generate(base,
|
||||
filebase: f'frida-base-@api_version@',
|
||||
name: 'Frida Base Library',
|
||||
version: frida_version,
|
||||
description: 'Basic building blocks',
|
||||
)
|
||||
if have_in_tree_payloads
|
||||
pkg = import('pkgconfig')
|
||||
pkg.generate(base,
|
||||
filebase: f'frida-base-@api_version@',
|
||||
name: 'Frida Base Library',
|
||||
version: frida_version,
|
||||
description: 'Basic building blocks',
|
||||
)
|
||||
endif
|
||||
+417
-9
@@ -1,26 +1,432 @@
|
||||
#ifdef HAVE_NICE
|
||||
|
||||
#define OPENSSL_SUPPRESS_DEPRECATED
|
||||
|
||||
#include "frida-base.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <openssl/asn1.h>
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/bn.h>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <usrsctp.h>
|
||||
|
||||
#ifdef HAVE_GIOAPPLE
|
||||
# include <CoreFoundation/CoreFoundation.h>
|
||||
# include <Security/Security.h>
|
||||
#else
|
||||
# define OPENSSL_SUPPRESS_DEPRECATED
|
||||
# include <openssl/asn1.h>
|
||||
# include <openssl/bio.h>
|
||||
# include <openssl/bn.h>
|
||||
# include <openssl/evp.h>
|
||||
# include <openssl/pem.h>
|
||||
# include <openssl/rsa.h>
|
||||
# include <openssl/x509.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GIOAPPLE
|
||||
static SecKeyRef frida_rsa_keypair_generate (void);
|
||||
static void frida_rsa_export_public_key (SecKeyRef private_key, GByteArray * out);
|
||||
static void frida_rsa_export_private_key (SecKeyRef private_key, GByteArray * out);
|
||||
static void frida_rsa_sign_sha256 (SecKeyRef private_key, const guint8 * message, gsize message_length, GByteArray * out);
|
||||
|
||||
static void frida_build_tbs_certificate (GByteArray * out, const GByteArray * subject_public_key);
|
||||
static void frida_build_name (GByteArray * out);
|
||||
static void frida_build_rdn (GByteArray * out, const guint8 * oid, gsize oid_length, guint8 value_tag, const gchar * value);
|
||||
static void frida_build_validity (GByteArray * out);
|
||||
static void frida_build_subject_public_key_info (GByteArray * out, const GByteArray * rsa_public_key);
|
||||
static void frida_build_signed_certificate (GByteArray * out, const GByteArray * tbs, const GByteArray * signature);
|
||||
static void frida_build_algorithm_identifier (GByteArray * out, const guint8 * oid, gsize oid_length);
|
||||
static void frida_build_pkcs8_private_key_info (GByteArray * out, const GByteArray * rsa_private_key);
|
||||
static gchar * frida_pem_encode (const gchar * type, const guint8 * der, gsize der_length);
|
||||
|
||||
static void frida_der_append_tlv (GByteArray * out, guint8 tag, const guint8 * value, gsize length);
|
||||
static void frida_der_append_length (GByteArray * out, gsize length);
|
||||
static void frida_der_append_integer_uint (GByteArray * out, guint64 value);
|
||||
static void frida_der_append_null (GByteArray * out);
|
||||
static void frida_der_append_oid (GByteArray * out, const guint8 * oid, gsize length);
|
||||
static void frida_der_append_utctime (GByteArray * out, time_t t);
|
||||
static void frida_der_append_bit_string (GByteArray * out, const guint8 * bytes, gsize length);
|
||||
|
||||
static const guint8 frida_oid_country[] = { 0x55, 0x04, 0x06 };
|
||||
static const guint8 frida_oid_organization[] = { 0x55, 0x04, 0x0a };
|
||||
static const guint8 frida_oid_common_name[] = { 0x55, 0x04, 0x03 };
|
||||
static const guint8 frida_oid_rsa_encryption[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 };
|
||||
static const guint8 frida_oid_sha256_with_rsa[] = { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b };
|
||||
#else
|
||||
static gchar * frida_steal_bio_to_string (BIO ** bio);
|
||||
#endif
|
||||
|
||||
static int frida_on_connection_output (void * addr, void * buffer, size_t length, uint8_t tos, uint8_t set_df);
|
||||
static void frida_on_debug_printf (const char * format, ...);
|
||||
|
||||
static void frida_on_upcall (struct socket * sock, void * user_data, int flags);
|
||||
|
||||
#ifdef HAVE_GIOAPPLE
|
||||
|
||||
void
|
||||
_frida_generate_certificate (guint8 ** cert_der, gint * cert_der_length, gchar ** cert_pem, gchar ** key_pem)
|
||||
{
|
||||
SecKeyRef private_key;
|
||||
GByteArray * rsa_public_key, * rsa_private_key, * subject_public_key, * tbs, * signature, * certificate, * pkcs8;
|
||||
|
||||
*cert_der = NULL;
|
||||
*cert_der_length = 0;
|
||||
*cert_pem = NULL;
|
||||
*key_pem = NULL;
|
||||
|
||||
private_key = frida_rsa_keypair_generate ();
|
||||
|
||||
rsa_public_key = g_byte_array_new ();
|
||||
frida_rsa_export_public_key (private_key, rsa_public_key);
|
||||
|
||||
rsa_private_key = g_byte_array_new ();
|
||||
frida_rsa_export_private_key (private_key, rsa_private_key);
|
||||
|
||||
subject_public_key = g_byte_array_new ();
|
||||
frida_build_subject_public_key_info (subject_public_key, rsa_public_key);
|
||||
|
||||
tbs = g_byte_array_new ();
|
||||
frida_build_tbs_certificate (tbs, subject_public_key);
|
||||
|
||||
signature = g_byte_array_new ();
|
||||
frida_rsa_sign_sha256 (private_key, tbs->data, tbs->len, signature);
|
||||
|
||||
certificate = g_byte_array_new ();
|
||||
frida_build_signed_certificate (certificate, tbs, signature);
|
||||
|
||||
pkcs8 = g_byte_array_new ();
|
||||
frida_build_pkcs8_private_key_info (pkcs8, rsa_private_key);
|
||||
|
||||
*cert_der = g_memdup2 (certificate->data, certificate->len);
|
||||
*cert_der_length = certificate->len;
|
||||
*cert_pem = frida_pem_encode ("CERTIFICATE", certificate->data, certificate->len);
|
||||
*key_pem = frida_pem_encode ("PRIVATE KEY", pkcs8->data, pkcs8->len);
|
||||
|
||||
g_byte_array_unref (pkcs8);
|
||||
g_byte_array_unref (certificate);
|
||||
g_byte_array_unref (signature);
|
||||
g_byte_array_unref (tbs);
|
||||
g_byte_array_unref (subject_public_key);
|
||||
g_byte_array_unref (rsa_private_key);
|
||||
g_byte_array_unref (rsa_public_key);
|
||||
CFRelease (private_key);
|
||||
}
|
||||
|
||||
static SecKeyRef
|
||||
frida_rsa_keypair_generate (void)
|
||||
{
|
||||
SecKeyRef private_key;
|
||||
CFMutableDictionaryRef attrs;
|
||||
const int key_size_bits = 2048;
|
||||
CFNumberRef key_size;
|
||||
|
||||
attrs = CFDictionaryCreateMutable (kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
|
||||
|
||||
key_size = CFNumberCreate (kCFAllocatorDefault, kCFNumberIntType, &key_size_bits);
|
||||
CFDictionarySetValue (attrs, kSecAttrKeyType, kSecAttrKeyTypeRSA);
|
||||
CFDictionarySetValue (attrs, kSecAttrKeySizeInBits, key_size);
|
||||
CFRelease (key_size);
|
||||
|
||||
private_key = SecKeyCreateRandomKey (attrs, NULL);
|
||||
g_assert (private_key != NULL);
|
||||
|
||||
CFRelease (attrs);
|
||||
|
||||
return private_key;
|
||||
}
|
||||
|
||||
static void
|
||||
frida_rsa_export_public_key (SecKeyRef private_key, GByteArray * out)
|
||||
{
|
||||
SecKeyRef public_key;
|
||||
CFDataRef data;
|
||||
|
||||
public_key = SecKeyCopyPublicKey (private_key);
|
||||
g_assert (public_key != NULL);
|
||||
|
||||
data = SecKeyCopyExternalRepresentation (public_key, NULL);
|
||||
g_assert (data != NULL);
|
||||
|
||||
g_byte_array_append (out, CFDataGetBytePtr (data), CFDataGetLength (data));
|
||||
|
||||
CFRelease (data);
|
||||
CFRelease (public_key);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_rsa_export_private_key (SecKeyRef private_key, GByteArray * out)
|
||||
{
|
||||
CFDataRef data;
|
||||
|
||||
data = SecKeyCopyExternalRepresentation (private_key, NULL);
|
||||
g_assert (data != NULL);
|
||||
|
||||
g_byte_array_append (out, CFDataGetBytePtr (data), (guint) CFDataGetLength (data));
|
||||
|
||||
CFRelease (data);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_rsa_sign_sha256 (SecKeyRef private_key, const guint8 * message, gsize message_length, GByteArray * out)
|
||||
{
|
||||
CFDataRef to_sign, signature;
|
||||
|
||||
to_sign = CFDataCreate (kCFAllocatorDefault, message, (CFIndex) message_length);
|
||||
|
||||
signature = SecKeyCreateSignature (private_key, kSecKeyAlgorithmRSASignatureMessagePKCS1v15SHA256, to_sign, NULL);
|
||||
g_assert (signature != NULL);
|
||||
|
||||
g_byte_array_append (out, CFDataGetBytePtr (signature), (guint) CFDataGetLength (signature));
|
||||
|
||||
CFRelease (signature);
|
||||
CFRelease (to_sign);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_build_tbs_certificate (GByteArray * out, const GByteArray * subject_public_key)
|
||||
{
|
||||
GByteArray * inner = g_byte_array_new ();
|
||||
|
||||
frida_der_append_integer_uint (inner, 1);
|
||||
frida_build_algorithm_identifier (inner, frida_oid_sha256_with_rsa, sizeof (frida_oid_sha256_with_rsa));
|
||||
frida_build_name (inner);
|
||||
frida_build_validity (inner);
|
||||
frida_build_name (inner);
|
||||
g_byte_array_append (inner, subject_public_key->data, subject_public_key->len);
|
||||
|
||||
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
|
||||
|
||||
g_byte_array_unref (inner);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_build_name (GByteArray * out)
|
||||
{
|
||||
GByteArray * inner = g_byte_array_new ();
|
||||
|
||||
frida_build_rdn (inner, frida_oid_country, sizeof (frida_oid_country), 0x13, "CA");
|
||||
frida_build_rdn (inner, frida_oid_organization, sizeof (frida_oid_organization), 0x0c, "Frida");
|
||||
frida_build_rdn (inner, frida_oid_common_name, sizeof (frida_oid_common_name), 0x0c, "lolcathost");
|
||||
|
||||
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
|
||||
|
||||
g_byte_array_unref (inner);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_build_rdn (GByteArray * out, const guint8 * oid, gsize oid_length, guint8 value_tag, const gchar * value)
|
||||
{
|
||||
GByteArray * atv, * rdn;
|
||||
|
||||
atv = g_byte_array_new ();
|
||||
rdn = g_byte_array_new ();
|
||||
|
||||
frida_der_append_oid (atv, oid, oid_length);
|
||||
frida_der_append_tlv (atv, value_tag, (const guint8 *) value, strlen (value));
|
||||
frida_der_append_tlv (rdn, 0x30, atv->data, atv->len);
|
||||
frida_der_append_tlv (out, 0x31, rdn->data, rdn->len);
|
||||
|
||||
g_byte_array_unref (rdn);
|
||||
g_byte_array_unref (atv);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_build_validity (GByteArray * out)
|
||||
{
|
||||
const time_t lifetime_seconds = 15780000;
|
||||
GByteArray * inner;
|
||||
time_t not_before, not_after;
|
||||
|
||||
inner = g_byte_array_new ();
|
||||
|
||||
not_before = time (NULL);
|
||||
not_after = not_before + lifetime_seconds;
|
||||
|
||||
frida_der_append_utctime (inner, not_before);
|
||||
frida_der_append_utctime (inner, not_after);
|
||||
|
||||
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
|
||||
|
||||
g_byte_array_unref (inner);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_build_subject_public_key_info (GByteArray * out, const GByteArray * rsa_public_key)
|
||||
{
|
||||
GByteArray * inner = g_byte_array_new ();
|
||||
|
||||
frida_build_algorithm_identifier (inner, frida_oid_rsa_encryption, sizeof (frida_oid_rsa_encryption));
|
||||
frida_der_append_bit_string (inner, rsa_public_key->data, rsa_public_key->len);
|
||||
|
||||
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
|
||||
|
||||
g_byte_array_unref (inner);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_build_signed_certificate (GByteArray * out, const GByteArray * tbs, const GByteArray * signature)
|
||||
{
|
||||
GByteArray * inner = g_byte_array_new ();
|
||||
|
||||
g_byte_array_append (inner, tbs->data, tbs->len);
|
||||
frida_build_algorithm_identifier (inner, frida_oid_sha256_with_rsa, sizeof (frida_oid_sha256_with_rsa));
|
||||
frida_der_append_bit_string (inner, signature->data, signature->len);
|
||||
|
||||
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
|
||||
|
||||
g_byte_array_unref (inner);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_build_algorithm_identifier (GByteArray * out, const guint8 * oid, gsize oid_length)
|
||||
{
|
||||
GByteArray * inner = g_byte_array_new ();
|
||||
|
||||
frida_der_append_oid (inner, oid, oid_length);
|
||||
frida_der_append_null (inner);
|
||||
|
||||
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
|
||||
|
||||
g_byte_array_unref (inner);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_build_pkcs8_private_key_info (GByteArray * out, const GByteArray * rsa_private_key)
|
||||
{
|
||||
GByteArray * inner = g_byte_array_new ();
|
||||
|
||||
frida_der_append_integer_uint (inner, 0);
|
||||
frida_build_algorithm_identifier (inner, frida_oid_rsa_encryption, sizeof (frida_oid_rsa_encryption));
|
||||
frida_der_append_tlv (inner, 0x04, rsa_private_key->data, rsa_private_key->len);
|
||||
|
||||
frida_der_append_tlv (out, 0x30, inner->data, inner->len);
|
||||
|
||||
g_byte_array_unref (inner);
|
||||
}
|
||||
|
||||
static gchar *
|
||||
frida_pem_encode (const gchar * type, const guint8 * der, gsize der_length)
|
||||
{
|
||||
GString * pem;
|
||||
gchar * base64;
|
||||
gsize length, i;
|
||||
|
||||
base64 = g_base64_encode (der, der_length);
|
||||
length = strlen (base64);
|
||||
|
||||
pem = g_string_sized_new (length + 128);
|
||||
|
||||
g_string_append_printf (pem, "-----BEGIN %s-----\n", type);
|
||||
|
||||
for (i = 0; i < length; i += 64)
|
||||
{
|
||||
gsize chunk = MIN (64, length - i);
|
||||
g_string_append_len (pem, base64 + i, chunk);
|
||||
g_string_append_c (pem, '\n');
|
||||
}
|
||||
|
||||
g_string_append_printf (pem, "-----END %s-----\n", type);
|
||||
|
||||
g_free (base64);
|
||||
|
||||
return g_string_free (pem, FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_der_append_tlv (GByteArray * out, guint8 tag, const guint8 * value, gsize length)
|
||||
{
|
||||
g_byte_array_append (out, &tag, 1);
|
||||
frida_der_append_length (out, length);
|
||||
if (length > 0)
|
||||
g_byte_array_append (out, value, length);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_der_append_length (GByteArray * out, gsize length)
|
||||
{
|
||||
gint i;
|
||||
guint8 buf[9], header;
|
||||
|
||||
if (length < 0x80)
|
||||
{
|
||||
guint8 b = (guint8) length;
|
||||
g_byte_array_append (out, &b, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
i = 0;
|
||||
while (length > 0)
|
||||
{
|
||||
buf[++i] = (guint8) (length & 0xff);
|
||||
length >>= 8;
|
||||
}
|
||||
header = (guint8) (0x80 | i);
|
||||
g_byte_array_append (out, &header, 1);
|
||||
while (i > 0)
|
||||
{
|
||||
g_byte_array_append (out, &buf[i], 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
frida_der_append_integer_uint (GByteArray * out, guint64 value)
|
||||
{
|
||||
gint i;
|
||||
guint8 buf[9];
|
||||
gint start;
|
||||
|
||||
for (i = 7; i >= 0; i--)
|
||||
{
|
||||
buf[i + 1] = (guint8) (value & 0xff);
|
||||
value >>= 8;
|
||||
}
|
||||
buf[0] = 0;
|
||||
|
||||
start = 0;
|
||||
while (start < 8 && buf[start] == 0 && (buf[start + 1] & 0x80) == 0)
|
||||
start++;
|
||||
|
||||
frida_der_append_tlv (out, 0x02, buf + start, (gsize) (9 - start));
|
||||
}
|
||||
|
||||
static void
|
||||
frida_der_append_null (GByteArray * out)
|
||||
{
|
||||
frida_der_append_tlv (out, 0x05, NULL, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_der_append_oid (GByteArray * out, const guint8 * oid, gsize length)
|
||||
{
|
||||
frida_der_append_tlv (out, 0x06, oid, length);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_der_append_utctime (GByteArray * out, time_t t)
|
||||
{
|
||||
struct tm tm;
|
||||
gchar buf[16];
|
||||
|
||||
gmtime_r (&t, &tm);
|
||||
g_snprintf (buf, sizeof (buf), "%02d%02d%02d%02d%02d%02dZ",
|
||||
tm.tm_year % 100, tm.tm_mon + 1, tm.tm_mday,
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec);
|
||||
|
||||
frida_der_append_tlv (out, 0x17, (const guint8 *) buf, strlen (buf));
|
||||
}
|
||||
|
||||
static void
|
||||
frida_der_append_bit_string (GByteArray * out, const guint8 * bytes, gsize length)
|
||||
{
|
||||
guint8 tag = 0x03;
|
||||
guint8 unused_bits = 0;
|
||||
|
||||
g_byte_array_append (out, &tag, 1);
|
||||
frida_der_append_length (out, length + 1);
|
||||
g_byte_array_append (out, &unused_bits, 1);
|
||||
if (length > 0)
|
||||
g_byte_array_append (out, bytes, length);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void
|
||||
_frida_generate_certificate (guint8 ** cert_der, gint * cert_der_length, gchar ** cert_pem, gchar ** key_pem)
|
||||
{
|
||||
@@ -90,6 +496,8 @@ frida_steal_bio_to_string (BIO ** bio)
|
||||
return result;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void
|
||||
_frida_sctp_connection_initialize_sctp_backend (void)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
namespace Frida {
|
||||
/**
|
||||
* Issues RPC calls over a {@link RpcPeer}, matching responses to requests.
|
||||
*/
|
||||
public sealed class RpcClient : Object {
|
||||
/**
|
||||
* The peer used to send and receive RPC messages.
|
||||
*/
|
||||
public weak RpcPeer peer {
|
||||
get;
|
||||
construct;
|
||||
@@ -7,10 +13,23 @@ namespace Frida {
|
||||
|
||||
private Gee.HashMap<string, PendingResponse> pending_responses = new Gee.HashMap<string, PendingResponse> ();
|
||||
|
||||
/**
|
||||
* Creates an RPC client that talks over @peer.
|
||||
*
|
||||
* @param peer the peer to send and receive messages through
|
||||
*/
|
||||
public RpcClient (RpcPeer peer) {
|
||||
Object (peer: peer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls a remote method and waits for its result.
|
||||
*
|
||||
* @param method the method name to invoke
|
||||
* @param args the JSON arguments
|
||||
* @param data binary data to accompany the call, if any
|
||||
* @return the method's JSON result
|
||||
*/
|
||||
public async Json.Node call (string method, Json.Node[] args, Bytes? data, Cancellable? cancellable) throws Error, IOError {
|
||||
string request_id = Uuid.string_random ();
|
||||
|
||||
@@ -69,6 +88,13 @@ namespace Frida {
|
||||
return pending.result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Feeds an incoming message to the client, matching it against pending
|
||||
* calls if it is an RPC response.
|
||||
*
|
||||
* @param json the incoming message as a JSON string
|
||||
* @return true if the message was an RPC message and was handled
|
||||
*/
|
||||
public bool try_handle_message (string json) {
|
||||
if (json.index_of ("\"frida:rpc\"") == -1)
|
||||
return false;
|
||||
@@ -157,7 +183,17 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A transport over which an {@link RpcClient} sends and receives RPC
|
||||
* messages.
|
||||
*/
|
||||
public interface RpcPeer : Object {
|
||||
/**
|
||||
* Posts an RPC message to the peer.
|
||||
*
|
||||
* @param json the message as a JSON string
|
||||
* @param data binary data to accompany the message, if any
|
||||
*/
|
||||
public abstract async void post_rpc_message (string json, Bytes? data, Cancellable? cancellable) throws Error, IOError;
|
||||
}
|
||||
}
|
||||
+615
-2
@@ -12,6 +12,8 @@ namespace Frida {
|
||||
Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
public abstract async void enable_spawn_gating (Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void disable_spawn_gating (Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async HostSpawnInfo[] enumerate_pending_spawn (Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async HostChildInfo[] enumerate_pending_children (Cancellable? cancellable) throws GLib.Error;
|
||||
@@ -31,6 +33,7 @@ namespace Frida {
|
||||
|
||||
public abstract async ServiceSessionId open_service (string address, Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
public signal void spawn_gating_disabled (SpawnGatingDisabledReason reason);
|
||||
public signal void spawn_added (HostSpawnInfo info);
|
||||
public signal void spawn_removed (HostSpawnInfo info);
|
||||
public signal void child_added (HostChildInfo info);
|
||||
@@ -78,6 +81,8 @@ namespace Frida {
|
||||
Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void destroy_script (AgentScriptId script_id, Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void load_script (AgentScriptId script_id, Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void interrupt_script (AgentScriptId script_id, Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void terminate_script (AgentScriptId script_id, Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract async void eternalize_script (AgentScriptId script_id, Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
public abstract async void enable_debugger (AgentScriptId script_id, Cancellable? cancellable) throws GLib.Error;
|
||||
@@ -755,10 +760,25 @@ namespace Frida {
|
||||
public abstract async void break_and_detach (Cancellable? cancellable) throws GLib.Error;
|
||||
}
|
||||
|
||||
/**
|
||||
* What the Gadget should do when a configured breakpoint is hit.
|
||||
*/
|
||||
public enum GadgetBreakpointAction {
|
||||
/**
|
||||
* Invoke the function and return immediately.
|
||||
*/
|
||||
INVOKE_RETURN,
|
||||
/**
|
||||
* Resume execution.
|
||||
*/
|
||||
RESUME,
|
||||
/**
|
||||
* Detach the Gadget.
|
||||
*/
|
||||
DETACH,
|
||||
/**
|
||||
* Apply the page plan.
|
||||
*/
|
||||
PAGE_PLAN,
|
||||
}
|
||||
|
||||
@@ -802,16 +822,37 @@ namespace Frida {
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.AuthenticationService17")]
|
||||
/**
|
||||
* Authenticates clients connecting to a {@link PortalService} or
|
||||
* {@link ControlService}.
|
||||
*/
|
||||
public interface AuthenticationService : Object {
|
||||
/**
|
||||
* Authenticates a client by its token.
|
||||
*
|
||||
* @param token the token presented by the client
|
||||
* @return a JSON string describing the authenticated session
|
||||
*/
|
||||
public abstract async string authenticate (string token, Cancellable? cancellable) throws GLib.Error;
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link AuthenticationService} that accepts a single fixed token.
|
||||
*/
|
||||
public sealed class StaticAuthenticationService : Object, AuthenticationService {
|
||||
/**
|
||||
* The SHA-256 hash of the accepted token.
|
||||
*/
|
||||
public string token_hash {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a service that accepts the given token.
|
||||
*
|
||||
* @param token the token clients must present
|
||||
*/
|
||||
public StaticAuthenticationService (string token) {
|
||||
Object (token_hash: Checksum.compute_for_string (SHA256, token));
|
||||
}
|
||||
@@ -865,6 +906,11 @@ namespace Frida {
|
||||
throw_not_authorized ();
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_authorized ();
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_authorized ();
|
||||
}
|
||||
@@ -944,8 +990,57 @@ namespace Frida {
|
||||
throw new Error.PERMISSION_DENIED ("Not authorized, authentication required");
|
||||
}
|
||||
|
||||
// Fires `expired` when a gated process goes unresumed too long, so the backend can cancel
|
||||
// gating instead of letting a stalled client wedge process creation.
|
||||
public sealed class SpawnGatingWatchdog : Object {
|
||||
public signal void expired ();
|
||||
|
||||
public uint timeout_msec {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private Gee.Map<uint, Source> deadlines = new Gee.HashMap<uint, Source> ();
|
||||
|
||||
// Default deadline: under the OS's own spawn-start timeouts (Android's is 10s).
|
||||
public SpawnGatingWatchdog (uint timeout_msec = 8000) {
|
||||
Object (timeout_msec: timeout_msec);
|
||||
}
|
||||
|
||||
public void arm (uint pid) {
|
||||
var deadline = new TimeoutSource (timeout_msec);
|
||||
deadline.set_callback (() => {
|
||||
expired ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
deadline.attach (MainContext.get_thread_default ());
|
||||
deadlines[pid] = deadline;
|
||||
}
|
||||
|
||||
public void cancel (uint pid) {
|
||||
Source? deadline;
|
||||
if (deadlines.unset (pid, out deadline))
|
||||
deadline.destroy ();
|
||||
}
|
||||
|
||||
public void clear () {
|
||||
foreach (var deadline in deadlines.values)
|
||||
deadline.destroy ();
|
||||
deadlines.clear ();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The realm an agent should run in.
|
||||
*/
|
||||
public enum Realm {
|
||||
/**
|
||||
* The process's native realm.
|
||||
*/
|
||||
NATIVE,
|
||||
/**
|
||||
* An emulated realm, such as code running under binary translation.
|
||||
*/
|
||||
EMULATED;
|
||||
|
||||
public static Realm from_nick (string nick) throws Error {
|
||||
@@ -957,6 +1052,32 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* How the agent should take over exception handling in the target.
|
||||
*/
|
||||
public enum Exceptor {
|
||||
/**
|
||||
* Install handlers and keep the target from overriding them.
|
||||
*/
|
||||
FULL,
|
||||
/**
|
||||
* Install handlers but let the target replace them.
|
||||
*/
|
||||
HANDLER_ONLY,
|
||||
/**
|
||||
* Do not handle exceptions.
|
||||
*/
|
||||
OFF;
|
||||
|
||||
public static Exceptor from_nick (string nick) throws Error {
|
||||
return Marshal.enum_from_nick<Exceptor> (nick);
|
||||
}
|
||||
|
||||
public string to_nick () {
|
||||
return Marshal.enum_to_nick<Exceptor> (this);
|
||||
}
|
||||
}
|
||||
|
||||
public enum SpawnStartState {
|
||||
RUNNING,
|
||||
SUSPENDED;
|
||||
@@ -1031,11 +1152,52 @@ namespace Frida {
|
||||
}
|
||||
#endif
|
||||
|
||||
public enum SessionDetachReason {
|
||||
/**
|
||||
* Why spawn gating was disabled on a {@link Device}.
|
||||
*/
|
||||
public enum SpawnGatingDisabledReason {
|
||||
/**
|
||||
* A client asked to disable spawn gating.
|
||||
*/
|
||||
APPLICATION_REQUESTED = 1,
|
||||
/**
|
||||
* A caught process went unresumed long enough that the gater cancelled gating to
|
||||
* avoid stalling process creation.
|
||||
*/
|
||||
WATCHDOG_TIMEOUT;
|
||||
|
||||
public static SpawnGatingDisabledReason from_nick (string nick) throws Error {
|
||||
return Marshal.enum_from_nick<SpawnGatingDisabledReason> (nick);
|
||||
}
|
||||
|
||||
public string to_nick () {
|
||||
return Marshal.enum_to_nick<SpawnGatingDisabledReason> (this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Why a {@link Session} was detached from its target.
|
||||
*/
|
||||
public enum SessionDetachReason {
|
||||
/**
|
||||
* The host asked to detach.
|
||||
*/
|
||||
APPLICATION_REQUESTED = 1,
|
||||
/**
|
||||
* The target process was replaced, for example by an exec.
|
||||
*/
|
||||
PROCESS_REPLACED,
|
||||
/**
|
||||
* The target process exited.
|
||||
*/
|
||||
PROCESS_TERMINATED,
|
||||
/**
|
||||
* The connection to the target was lost.
|
||||
*/
|
||||
CONNECTION_TERMINATED,
|
||||
/**
|
||||
* The device became unreachable.
|
||||
*/
|
||||
DEVICE_LOST;
|
||||
|
||||
public static SessionDetachReason from_nick (string nick) throws Error {
|
||||
@@ -1048,19 +1210,61 @@ namespace Frida {
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.Error")]
|
||||
/**
|
||||
* The errors that Frida operations can fail with.
|
||||
*/
|
||||
public errordomain Error {
|
||||
/**
|
||||
* The frida-server is not running.
|
||||
*/
|
||||
SERVER_NOT_RUNNING,
|
||||
/**
|
||||
* The specified executable was not found.
|
||||
*/
|
||||
EXECUTABLE_NOT_FOUND,
|
||||
/**
|
||||
* The specified executable is not supported.
|
||||
*/
|
||||
EXECUTABLE_NOT_SUPPORTED,
|
||||
/**
|
||||
* No process matched the request.
|
||||
*/
|
||||
PROCESS_NOT_FOUND,
|
||||
/**
|
||||
* The process is not responding.
|
||||
*/
|
||||
PROCESS_NOT_RESPONDING,
|
||||
/**
|
||||
* An argument was invalid.
|
||||
*/
|
||||
INVALID_ARGUMENT,
|
||||
/**
|
||||
* The operation is not valid in the current state.
|
||||
*/
|
||||
INVALID_OPERATION,
|
||||
/**
|
||||
* Permission was denied.
|
||||
*/
|
||||
PERMISSION_DENIED,
|
||||
/**
|
||||
* The requested address is already in use.
|
||||
*/
|
||||
ADDRESS_IN_USE,
|
||||
/**
|
||||
* The operation timed out.
|
||||
*/
|
||||
TIMED_OUT,
|
||||
/**
|
||||
* The operation is not supported.
|
||||
*/
|
||||
NOT_SUPPORTED,
|
||||
/**
|
||||
* A protocol error occurred.
|
||||
*/
|
||||
PROTOCOL,
|
||||
/**
|
||||
* A transport-level error occurred.
|
||||
*/
|
||||
TRANSPORT
|
||||
}
|
||||
|
||||
@@ -1127,7 +1331,13 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link Device.get_frontmost_application}.
|
||||
*/
|
||||
public sealed class FrontmostQueryOptions : Object {
|
||||
/**
|
||||
* How much detail to include about the application.
|
||||
*/
|
||||
public Scope scope {
|
||||
get;
|
||||
set;
|
||||
@@ -1157,7 +1367,14 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link Device.enumerate_applications}, including an optional
|
||||
* set of identifiers to restrict the results to.
|
||||
*/
|
||||
public sealed class ApplicationQueryOptions : Object {
|
||||
/**
|
||||
* How much detail to include about each application.
|
||||
*/
|
||||
public Scope scope {
|
||||
get;
|
||||
set;
|
||||
@@ -1166,14 +1383,30 @@ namespace Frida {
|
||||
|
||||
private Gee.List<string> identifiers = new Gee.ArrayList<string> ();
|
||||
|
||||
/**
|
||||
* Restricts the query to the given application identifier. May be called
|
||||
* multiple times to select several.
|
||||
*
|
||||
* @param identifier the application identifier to include
|
||||
*/
|
||||
public void select_identifier (string identifier) {
|
||||
identifiers.add (identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether any identifiers have been selected.
|
||||
*
|
||||
* @return true if the query is restricted to specific identifiers
|
||||
*/
|
||||
public bool has_selected_identifiers () {
|
||||
return !identifiers.is_empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes @func for each selected identifier.
|
||||
*
|
||||
* @param func function called with each identifier
|
||||
*/
|
||||
public void enumerate_selected_identifiers (Func<string> func) {
|
||||
foreach (var identifier in identifiers)
|
||||
func (identifier);
|
||||
@@ -1215,7 +1448,14 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link Device.enumerate_processes}, including an optional set
|
||||
* of PIDs to restrict the results to.
|
||||
*/
|
||||
public sealed class ProcessQueryOptions : Object {
|
||||
/**
|
||||
* How much detail to include about each process.
|
||||
*/
|
||||
public Scope scope {
|
||||
get;
|
||||
set;
|
||||
@@ -1224,14 +1464,30 @@ namespace Frida {
|
||||
|
||||
private Gee.List<uint> pids = new Gee.ArrayList<uint> ();
|
||||
|
||||
/**
|
||||
* Restricts the query to the given PID. May be called multiple times to
|
||||
* select several.
|
||||
*
|
||||
* @param pid the process ID to include
|
||||
*/
|
||||
public void select_pid (uint pid) {
|
||||
pids.add (pid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether any PIDs have been selected.
|
||||
*
|
||||
* @return true if the query is restricted to specific PIDs
|
||||
*/
|
||||
public bool has_selected_pids () {
|
||||
return !pids.is_empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes @func for each selected PID.
|
||||
*
|
||||
* @param func function called with each PID
|
||||
*/
|
||||
public void enumerate_selected_pids (Func<uint> func) {
|
||||
foreach (var pid in pids)
|
||||
func (pid);
|
||||
@@ -1273,9 +1529,21 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* How much detail to include in query results.
|
||||
*/
|
||||
public enum Scope {
|
||||
/**
|
||||
* Only the essential fields.
|
||||
*/
|
||||
MINIMAL,
|
||||
/**
|
||||
* Essential fields plus metadata.
|
||||
*/
|
||||
METADATA,
|
||||
/**
|
||||
* Everything available.
|
||||
*/
|
||||
FULL;
|
||||
|
||||
public static Scope from_nick (string nick) throws Error {
|
||||
@@ -1287,6 +1555,67 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link Device.enable_spawn_gating}.
|
||||
*/
|
||||
public sealed class SpawnGatingOptions : Object {
|
||||
/**
|
||||
* Which processes to gate. Anything other than DEFAULT requires a Frida new enough to
|
||||
* support scoping, and fails rather than silently gating something broader than asked.
|
||||
*/
|
||||
public SpawnGatingScope scope {
|
||||
get;
|
||||
set;
|
||||
default = DEFAULT;
|
||||
}
|
||||
|
||||
public HashTable<string, Variant> _serialize () {
|
||||
var dict = make_parameters_dict ();
|
||||
|
||||
if (scope != DEFAULT)
|
||||
dict["scope"] = new Variant.string (scope.to_nick ());
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
public static SpawnGatingOptions _deserialize (HashTable<string, Variant> dict) throws Error {
|
||||
var options = new SpawnGatingOptions ();
|
||||
|
||||
Variant? scope = dict["scope"];
|
||||
if (scope != null) {
|
||||
if (!scope.is_of_type (VariantType.STRING))
|
||||
throw new Error.INVALID_ARGUMENT ("The 'scope' option must be a string");
|
||||
options.scope = SpawnGatingScope.from_nick (scope.get_string ());
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
public enum SpawnGatingScope {
|
||||
/**
|
||||
* Leave the choice to the implementation. Sent as a plain enable_spawn_gating call, so
|
||||
* it still works against a Frida too old to know about scopes.
|
||||
*/
|
||||
DEFAULT,
|
||||
/**
|
||||
* Application launches only.
|
||||
*/
|
||||
APPLICATIONS,
|
||||
/**
|
||||
* Every process created on the system. More potent but far more intrusive.
|
||||
*/
|
||||
PROCESSES;
|
||||
|
||||
public static SpawnGatingScope from_nick (string nick) throws Error {
|
||||
return Marshal.enum_from_nick<SpawnGatingScope> (nick);
|
||||
}
|
||||
|
||||
public string to_nick () {
|
||||
return Marshal.enum_to_nick<SpawnGatingScope> (this);
|
||||
}
|
||||
}
|
||||
|
||||
public struct HostSpawnOptions {
|
||||
public bool has_argv;
|
||||
public string[] argv;
|
||||
@@ -1357,24 +1686,103 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options controlling how {@link Device.attach} sets up a {@link Session}.
|
||||
*/
|
||||
public sealed class SessionOptions : Object {
|
||||
/**
|
||||
* The realm to attach in.
|
||||
*/
|
||||
public Realm realm {
|
||||
get;
|
||||
set;
|
||||
default = NATIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* How long the session may survive a disconnection before being torn
|
||||
* down, in seconds; 0 disables persistence.
|
||||
*/
|
||||
public uint persist_timeout {
|
||||
get;
|
||||
set;
|
||||
default = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path to a custom agent to use in the emulated realm.
|
||||
*/
|
||||
public string? emulated_agent_path {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* How the agent should handle exceptions in the target.
|
||||
*/
|
||||
public Exceptor exceptor {
|
||||
get;
|
||||
set;
|
||||
default = FULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to install the unwind broker so exceptions can propagate
|
||||
* through instrumented code.
|
||||
*/
|
||||
public bool unwind_broker {
|
||||
get;
|
||||
set;
|
||||
default = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to monitor the target for exit so the session is cleaned up.
|
||||
*/
|
||||
public bool exit_monitor {
|
||||
get;
|
||||
set;
|
||||
default = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to monitor thread suspension in the target.
|
||||
*/
|
||||
public bool thread_suspend_monitor {
|
||||
get;
|
||||
set;
|
||||
default = true;
|
||||
}
|
||||
|
||||
private Gee.List<uint> linker_notifier_offsets = new Gee.ArrayList<uint> ();
|
||||
|
||||
/**
|
||||
* Clears the configured linker notifier offsets.
|
||||
*/
|
||||
public void clear_linker_notifier_offsets () {
|
||||
linker_notifier_offsets.clear ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a linker notifier offset, used to detect module load and unload
|
||||
* events in the target.
|
||||
*
|
||||
* @param offset the offset into the linker to instrument
|
||||
*/
|
||||
public void add_linker_notifier_offset (uint offset) {
|
||||
linker_notifier_offsets.add (offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes @func for each configured linker notifier offset.
|
||||
*
|
||||
* @param func function called with each offset
|
||||
*/
|
||||
public void enumerate_linker_notifier_offsets (Func<uint> func) {
|
||||
foreach (var offset in linker_notifier_offsets)
|
||||
func (offset);
|
||||
}
|
||||
|
||||
public HashTable<string, Variant> _serialize () {
|
||||
var dict = make_parameters_dict ();
|
||||
|
||||
@@ -1387,6 +1795,25 @@ namespace Frida {
|
||||
if (emulated_agent_path != null)
|
||||
dict["emulated-agent-path"] = new Variant.string (emulated_agent_path);
|
||||
|
||||
if (exceptor != FULL)
|
||||
dict["exceptor"] = new Variant.string (exceptor.to_nick ());
|
||||
|
||||
if (!unwind_broker)
|
||||
dict["unwind-broker"] = new Variant.string ("off");
|
||||
|
||||
if (!exit_monitor)
|
||||
dict["exit-monitor"] = new Variant.string ("off");
|
||||
|
||||
if (!thread_suspend_monitor)
|
||||
dict["thread-suspend-monitor"] = new Variant.string ("off");
|
||||
|
||||
if (!linker_notifier_offsets.is_empty) {
|
||||
var builder = new VariantBuilder (new VariantType ("au"));
|
||||
foreach (uint offset in linker_notifier_offsets)
|
||||
builder.add ("u", offset);
|
||||
dict["linker-notifier-offsets"] = builder.end ();
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
|
||||
@@ -1414,12 +1841,61 @@ namespace Frida {
|
||||
options.emulated_agent_path = path.get_string ();
|
||||
}
|
||||
|
||||
Variant? exceptor = dict["exceptor"];
|
||||
if (exceptor != null) {
|
||||
if (!exceptor.is_of_type (VariantType.STRING))
|
||||
throw new Error.INVALID_ARGUMENT ("The 'exceptor' option must be a string");
|
||||
options.exceptor = Exceptor.from_nick (exceptor.get_string ());
|
||||
}
|
||||
|
||||
Variant? unwind_broker = dict["unwind-broker"];
|
||||
if (unwind_broker != null) {
|
||||
if (!unwind_broker.is_of_type (VariantType.STRING) || unwind_broker.get_string () != "off")
|
||||
throw new Error.INVALID_ARGUMENT ("The 'unwind-broker' option is invalid");
|
||||
options.unwind_broker = false;
|
||||
}
|
||||
|
||||
Variant? exit_monitor = dict["exit-monitor"];
|
||||
if (exit_monitor != null) {
|
||||
if (!exit_monitor.is_of_type (VariantType.STRING) || exit_monitor.get_string () != "off")
|
||||
throw new Error.INVALID_ARGUMENT ("The 'exit-monitor' option is invalid");
|
||||
options.exit_monitor = false;
|
||||
}
|
||||
|
||||
Variant? thread_suspend_monitor = dict["thread-suspend-monitor"];
|
||||
if (thread_suspend_monitor != null) {
|
||||
if (!thread_suspend_monitor.is_of_type (VariantType.STRING) ||
|
||||
thread_suspend_monitor.get_string () != "off")
|
||||
throw new Error.INVALID_ARGUMENT ("The 'thread-suspend-monitor' option is invalid");
|
||||
options.thread_suspend_monitor = false;
|
||||
}
|
||||
|
||||
Variant? offsets = dict["linker-notifier-offsets"];
|
||||
if (offsets != null) {
|
||||
if (!offsets.is_of_type (new VariantType ("au")))
|
||||
throw new Error.INVALID_ARGUMENT (
|
||||
"The 'linker-notifier-offsets' option must be an array of uint32");
|
||||
uint n = (uint) offsets.n_children ();
|
||||
for (uint i = 0; i != n; i++)
|
||||
options.add_linker_notifier_offset (offsets.get_child_value (i).get_uint32 ());
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* How to set up the standard I/O streams of a spawned process.
|
||||
*/
|
||||
public enum Stdio {
|
||||
/**
|
||||
* Inherit the host's streams.
|
||||
*/
|
||||
INHERIT,
|
||||
/**
|
||||
* Pipe the streams through Frida, surfaced via {@link Device.output}
|
||||
* and {@link Device.input}.
|
||||
*/
|
||||
PIPE;
|
||||
|
||||
public static Stdio from_nick (string nick) throws Error {
|
||||
@@ -1483,9 +1959,21 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* How a {@link Child} came to be.
|
||||
*/
|
||||
public enum ChildOrigin {
|
||||
/**
|
||||
* Created by fork().
|
||||
*/
|
||||
FORK,
|
||||
/**
|
||||
* The result of an exec() replacing the image.
|
||||
*/
|
||||
EXEC,
|
||||
/**
|
||||
* Spawned as a new process.
|
||||
*/
|
||||
SPAWN;
|
||||
|
||||
public static ChildOrigin from_nick (string nick) throws Error {
|
||||
@@ -1602,23 +2090,39 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for creating a {@link Script} with {@link Session.create_script}.
|
||||
*/
|
||||
public sealed class ScriptOptions : Object {
|
||||
/**
|
||||
* A name for the script, used in logging and debugging.
|
||||
*/
|
||||
public string? name {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* A heap snapshot to start the script from, as produced by
|
||||
* {@link Session.snapshot_script}.
|
||||
*/
|
||||
public Bytes? snapshot {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* How the snapshot is delivered to the runtime.
|
||||
*/
|
||||
public SnapshotTransport snapshot_transport {
|
||||
get;
|
||||
set;
|
||||
default = INLINE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Which JavaScript runtime to run the script in.
|
||||
*/
|
||||
public ScriptRuntime runtime {
|
||||
get;
|
||||
set;
|
||||
@@ -1687,17 +2191,35 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* How a script's heap snapshot is delivered to the runtime.
|
||||
*/
|
||||
public enum SnapshotTransport {
|
||||
/**
|
||||
* Embed the snapshot inline with the script.
|
||||
*/
|
||||
INLINE,
|
||||
/**
|
||||
* Pass the snapshot through shared memory.
|
||||
*/
|
||||
SHARED_MEMORY
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for building a heap snapshot with {@link Session.snapshot_script}.
|
||||
*/
|
||||
public sealed class SnapshotOptions : Object {
|
||||
/**
|
||||
* Script to run to warm up the runtime before capturing the snapshot.
|
||||
*/
|
||||
public string? warmup_script {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Which JavaScript runtime to capture the snapshot for.
|
||||
*/
|
||||
public ScriptRuntime runtime {
|
||||
get;
|
||||
set;
|
||||
@@ -1737,9 +2259,21 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Which JavaScript runtime a script should use.
|
||||
*/
|
||||
public enum ScriptRuntime {
|
||||
/**
|
||||
* Let Frida choose.
|
||||
*/
|
||||
DEFAULT,
|
||||
/**
|
||||
* The QuickJS runtime.
|
||||
*/
|
||||
QJS,
|
||||
/**
|
||||
* The V8 runtime.
|
||||
*/
|
||||
V8;
|
||||
|
||||
public static ScriptRuntime from_nick (string nick) throws Error {
|
||||
@@ -1767,17 +2301,29 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link Session.join_portal}.
|
||||
*/
|
||||
public sealed class PortalOptions : Object {
|
||||
/**
|
||||
* TLS certificate to present to the portal.
|
||||
*/
|
||||
public TlsCertificate? certificate {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication token to present to the portal.
|
||||
*/
|
||||
public string? token {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access-control tags scoping what this membership may reach.
|
||||
*/
|
||||
public string[]? acl {
|
||||
get;
|
||||
set;
|
||||
@@ -1830,7 +2376,14 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for {@link Session.setup_peer_connection}, configuring how the
|
||||
* peer-to-peer connection is negotiated.
|
||||
*/
|
||||
public sealed class PeerOptions : Object {
|
||||
/**
|
||||
* The STUN server to use for discovering the public address, if any.
|
||||
*/
|
||||
public string? stun_server {
|
||||
get;
|
||||
set;
|
||||
@@ -1838,14 +2391,27 @@ namespace Frida {
|
||||
|
||||
private Gee.List<Relay> relays = new Gee.ArrayList<Relay> ();
|
||||
|
||||
/**
|
||||
* Removes all configured relays.
|
||||
*/
|
||||
public void clear_relays () {
|
||||
relays.clear ();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a relay to use when a direct connection cannot be established.
|
||||
*
|
||||
* @param relay the relay to add
|
||||
*/
|
||||
public void add_relay (Relay relay) {
|
||||
relays.add (relay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes @func for each configured relay.
|
||||
*
|
||||
* @param func function called with each relay
|
||||
*/
|
||||
public void enumerate_relays (Func<Relay> func) {
|
||||
foreach (var relay in relays)
|
||||
func (relay);
|
||||
@@ -1891,27 +2457,51 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A TURN relay used to establish a peer-to-peer connection when a direct
|
||||
* one is not possible.
|
||||
*/
|
||||
public sealed class Relay : Object {
|
||||
/**
|
||||
* The relay's address, as host and port.
|
||||
*/
|
||||
public string address {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The username to authenticate with.
|
||||
*/
|
||||
public string username {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The password to authenticate with.
|
||||
*/
|
||||
public string password {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of relay and transport to use.
|
||||
*/
|
||||
public RelayKind kind {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a relay.
|
||||
*
|
||||
* @param address the relay's address, as host and port
|
||||
* @param username the username to authenticate with
|
||||
* @param password the password to authenticate with
|
||||
* @param kind the kind of relay and transport
|
||||
*/
|
||||
public Relay (string address, string username, string password, RelayKind kind) {
|
||||
Object (
|
||||
address: address,
|
||||
@@ -1938,9 +2528,21 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The kind of a {@link Relay} used for peer-to-peer connections.
|
||||
*/
|
||||
public enum RelayKind {
|
||||
/**
|
||||
* A TURN relay reached over UDP.
|
||||
*/
|
||||
TURN_UDP,
|
||||
/**
|
||||
* A TURN relay reached over TCP.
|
||||
*/
|
||||
TURN_TCP,
|
||||
/**
|
||||
* A TURN relay reached over TLS.
|
||||
*/
|
||||
TURN_TLS
|
||||
}
|
||||
|
||||
@@ -2170,7 +2772,10 @@ namespace Frida {
|
||||
|
||||
public static string enum_to_nick<T> (int val) {
|
||||
var klass = (EnumClass) typeof (T).class_ref ();
|
||||
return klass.get_value (val).value_nick;
|
||||
unowned EnumValue? v = klass.get_value (val);
|
||||
if (v == null)
|
||||
return "unknown-0x%x".printf (val);
|
||||
return v.value_nick;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2196,5 +2801,13 @@ namespace Frida {
|
||||
uint64 b = val_b;
|
||||
return a == b;
|
||||
}
|
||||
|
||||
public uint bytes_hash (Bytes b) {
|
||||
return b.hash ();
|
||||
}
|
||||
|
||||
public static bool bytes_equal (Bytes a, Bytes b) {
|
||||
return a.compare (b) == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
+242
-10
@@ -97,37 +97,77 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures the network endpoint a {@link PortalService} or
|
||||
* {@link ControlService} listens on.
|
||||
*/
|
||||
public sealed class EndpointParameters : Object {
|
||||
/**
|
||||
* The address to bind to, or null for the default.
|
||||
*/
|
||||
public string? address {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The port to listen on, or 0 for the default.
|
||||
*/
|
||||
public uint16 port {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* TLS certificate to serve, enabling TLS when set.
|
||||
*/
|
||||
public TlsCertificate? certificate {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Origin to require from connecting clients, if any.
|
||||
*/
|
||||
public string? origin {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service used to authenticate connecting clients, if any.
|
||||
*/
|
||||
public AuthenticationService? auth_service {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Directory whose files are served as static web assets, if any.
|
||||
*/
|
||||
public File? asset_root {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler invoked for web requests, if any.
|
||||
*/
|
||||
public WebRequestHandler? request_handler {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates endpoint parameters.
|
||||
*
|
||||
* @param address the address to bind to, or null for the default
|
||||
* @param port the port to listen on, or 0 for the default
|
||||
* @param certificate TLS certificate to serve, or null for plaintext
|
||||
* @param origin origin to require from clients, or null
|
||||
* @param auth_service service used to authenticate clients, or null
|
||||
* @param asset_root directory of static assets to serve, or null
|
||||
*/
|
||||
public EndpointParameters (string? address = null, uint16 port = 0, TlsCertificate? certificate = null,
|
||||
string? origin = null, AuthenticationService? auth_service = null, File? asset_root = null) {
|
||||
Object (
|
||||
@@ -141,6 +181,132 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles web requests made to an endpoint's served assets.
|
||||
*/
|
||||
public interface WebRequestHandler : Object {
|
||||
/**
|
||||
* Handles a single web request.
|
||||
*
|
||||
* @param request the incoming request
|
||||
* @return the response to send, or null to fall through to the default
|
||||
* handling
|
||||
*/
|
||||
public abstract async WebResponse? handle_request (WebRequest request, Cancellable? cancellable) throws Error, IOError;
|
||||
}
|
||||
|
||||
/**
|
||||
* An incoming web request passed to a {@link WebRequestHandler}.
|
||||
*/
|
||||
public sealed class WebRequest : Object {
|
||||
/**
|
||||
* The HTTP method, such as `GET` or `POST`.
|
||||
*/
|
||||
public string method {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The request path.
|
||||
*/
|
||||
public string path {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The raw query string, if any.
|
||||
*/
|
||||
public string? query_string {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The request body, if any.
|
||||
*/
|
||||
public Bytes? body {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private Soup.MessageHeaders raw_headers;
|
||||
|
||||
internal WebRequest (string method, string path, string? query_string, Soup.MessageHeaders headers, Bytes? body) {
|
||||
Object (method: method, path: path, query_string: query_string, body: body);
|
||||
raw_headers = headers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes @func for each request header.
|
||||
*
|
||||
* @param func function called with each header's name and value
|
||||
*/
|
||||
public void foreach_header (WebRequestHeaderFunc func) {
|
||||
raw_headers.foreach ((name, val) => {
|
||||
func (name, val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback invoked for each header while iterating a {@link WebRequest}.
|
||||
*
|
||||
* @param name the header name
|
||||
* @param val the header value
|
||||
*/
|
||||
public delegate void WebRequestHeaderFunc (string name, string val);
|
||||
|
||||
/**
|
||||
* A response returned from a {@link WebRequestHandler}.
|
||||
*/
|
||||
public sealed class WebResponse : Object {
|
||||
/**
|
||||
* The HTTP status code.
|
||||
*/
|
||||
public uint status {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The response body.
|
||||
*/
|
||||
public Bytes body {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private Soup.MessageHeaders raw_headers = new Soup.MessageHeaders (RESPONSE);
|
||||
|
||||
/**
|
||||
* Creates a response.
|
||||
*
|
||||
* @param status the HTTP status code
|
||||
* @param body the response body
|
||||
*/
|
||||
public WebResponse (uint status, Bytes body) {
|
||||
Object (status: status, body: body);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a header to the response.
|
||||
*
|
||||
* @param name the header name
|
||||
* @param val the header value
|
||||
*/
|
||||
public void add_header (string name, string val) {
|
||||
raw_headers.append (name, val);
|
||||
}
|
||||
|
||||
internal void write_headers_to (Soup.MessageHeaders target) {
|
||||
raw_headers.foreach ((name, val) => {
|
||||
target.replace (name, val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async IOStream negotiate_connection (IOStream stream, WebServiceTransport transport, string host, string? origin,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
var input = (DataInputStream) Object.new (typeof (DataInputStream),
|
||||
@@ -468,14 +634,14 @@ namespace Frida {
|
||||
|
||||
server.add_websocket_handler ("/ws", endpoint_params.origin, null, on_websocket_opened);
|
||||
|
||||
if (endpoint_params.asset_root != null)
|
||||
server.add_handler (null, on_asset_request);
|
||||
if (endpoint_params.request_handler != null || endpoint_params.asset_root != null)
|
||||
server.add_handler (null, on_http_request);
|
||||
}
|
||||
|
||||
public void close () {
|
||||
io_cancellable.cancel ();
|
||||
|
||||
if (endpoint_params.asset_root != null)
|
||||
if (endpoint_params.request_handler != null || endpoint_params.asset_root != null)
|
||||
server.remove_handler ("/");
|
||||
server.remove_handler ("/ws");
|
||||
|
||||
@@ -575,20 +741,77 @@ namespace Frida {
|
||||
connections.remove (connection);
|
||||
}
|
||||
|
||||
private void on_asset_request (Soup.Server server, Soup.ServerMessage msg, string path,
|
||||
private void on_http_request (Soup.Server server, Soup.ServerMessage msg, string path,
|
||||
HashTable<string, string>? query) {
|
||||
msg.get_response_headers ().replace ("Server", "Frida/" + _version_string ());
|
||||
|
||||
unowned string method = msg.get_method ();
|
||||
if (method != "GET" && method != "HEAD") {
|
||||
msg.set_status (Soup.Status.METHOD_NOT_ALLOWED, null);
|
||||
msg.pause ();
|
||||
handle_http_request.begin (msg, path);
|
||||
}
|
||||
|
||||
private async void handle_http_request (Soup.ServerMessage msg, string path) {
|
||||
WebRequestHandler? handler = endpoint_params.request_handler;
|
||||
if (handler != null) {
|
||||
if (yield try_request_handler (handler, msg, path, io_cancellable))
|
||||
return;
|
||||
}
|
||||
|
||||
File? asset_root = endpoint_params.asset_root;
|
||||
if (asset_root != null) {
|
||||
unowned string method = msg.get_method ();
|
||||
if (method != "GET" && method != "HEAD") {
|
||||
msg.set_status (Soup.Status.METHOD_NOT_ALLOWED, null);
|
||||
msg.unpause ();
|
||||
return;
|
||||
}
|
||||
|
||||
File location = asset_root.resolve_relative_path (path.next_char ());
|
||||
yield handle_asset_request (path, location, msg);
|
||||
return;
|
||||
}
|
||||
|
||||
File location = endpoint_params.asset_root.resolve_relative_path (path.next_char ());
|
||||
msg.set_status (Soup.Status.NOT_FOUND, null);
|
||||
msg.unpause ();
|
||||
}
|
||||
|
||||
msg.pause ();
|
||||
handle_asset_request.begin (path, location, msg);
|
||||
private static async bool try_request_handler (WebRequestHandler handler, Soup.ServerMessage msg, string path,
|
||||
Cancellable? cancellable) {
|
||||
var body = msg.get_request_body ();
|
||||
Bytes? request_body = (body.length != 0) ? body.flatten () : null;
|
||||
unowned Uri? uri = msg.get_uri ();
|
||||
string? query_string = (uri != null) ? uri.get_query () : null;
|
||||
var request = new WebRequest (msg.get_method (), path, query_string, msg.get_request_headers (),
|
||||
request_body);
|
||||
|
||||
WebResponse? response;
|
||||
try {
|
||||
response = yield handler.handle_request (request, cancellable);
|
||||
} catch (Error e) {
|
||||
msg.set_status (Soup.Status.INTERNAL_SERVER_ERROR, null);
|
||||
msg.set_response ("text/plain; charset=utf-8", Soup.MemoryUse.COPY, e.message.data);
|
||||
msg.unpause ();
|
||||
return true;
|
||||
} catch (IOError e) {
|
||||
msg.unpause ();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (response == null)
|
||||
return false;
|
||||
|
||||
apply_response (msg, response);
|
||||
msg.unpause ();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void apply_response (Soup.ServerMessage msg, WebResponse response) {
|
||||
msg.set_status (response.status, null);
|
||||
|
||||
response.write_headers_to (msg.get_response_headers ());
|
||||
|
||||
Bytes body = response.body;
|
||||
if (body.length > 0)
|
||||
msg.get_response_body ().append_bytes (body);
|
||||
}
|
||||
|
||||
private async void handle_asset_request (string path, File file, Soup.ServerMessage msg) {
|
||||
@@ -940,6 +1163,12 @@ namespace Frida {
|
||||
Memory.copy (buffer, recv_queue.data, n);
|
||||
recv_queue.remove_range (0, (uint) n);
|
||||
|
||||
if (recv_queue.len == 0) {
|
||||
#if HAVE_SOUP_WEBSOCKET_BACKPRESSURE
|
||||
websocket.resume_input ();
|
||||
#endif
|
||||
}
|
||||
|
||||
update_pending_io ();
|
||||
} else {
|
||||
if (state == OPEN)
|
||||
@@ -1006,6 +1235,9 @@ namespace Frida {
|
||||
private void on_message (int type, Bytes message) {
|
||||
with_state_lock (() => {
|
||||
recv_queue.append (message.get_data ());
|
||||
#if HAVE_SOUP_WEBSOCKET_BACKPRESSURE
|
||||
websocket.pause_input ();
|
||||
#endif
|
||||
update_pending_io ();
|
||||
});
|
||||
}
|
||||
|
||||
+26
-1
@@ -288,7 +288,7 @@ namespace Frida {
|
||||
|
||||
public JsonObjectReader (string json) throws Error {
|
||||
try {
|
||||
reader = new Json.Reader (Json.from_string (json));
|
||||
reader = make_json_reader (json);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
}
|
||||
@@ -404,4 +404,29 @@ namespace Frida {
|
||||
throw new Error.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Constructs a Json.Reader for the given JSON string, working around the
|
||||
* json_node_copy() bug in unpatched json-glib (GNOME bugzilla #774684).
|
||||
*
|
||||
* Json.Reader's set_root() copies the supplied node via json_node_copy().
|
||||
* For object/array nodes that copy is a thin shell that shares the original
|
||||
* JsonObject/JsonArray via refcount, but the members inside the shared
|
||||
* container have their parent pointers set to the *original* root, not the
|
||||
* shell. If the original is then dropped, json_reader_end_member()'s walk
|
||||
* via json_node_get_parent() touches a freed node — which on builds with
|
||||
* glib checks enabled (e.g. Fedora) trips JSON_NODE_IS_VALID and aborts.
|
||||
*
|
||||
* We keep the original root alive by attaching it to the reader as object
|
||||
* data; the destroy notify drops our reference when the reader is finalized.
|
||||
*/
|
||||
public Json.Reader make_json_reader (string json) throws GLib.Error {
|
||||
return make_json_reader_taking_node (Json.from_string (json));
|
||||
}
|
||||
|
||||
public Json.Reader make_json_reader_taking_node (owned Json.Node root) {
|
||||
var reader = new Json.Reader (root);
|
||||
reader.set_data<Json.Node> ("frida-json-root", (owned) root);
|
||||
return reader;
|
||||
}
|
||||
}
|
||||
Executable
+860
@@ -0,0 +1,860 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Dict, List, Optional, Tuple
|
||||
|
||||
MAX_ARGS = 9
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SyscallArg:
|
||||
type: str
|
||||
name: str
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SyscallSignature:
|
||||
name: str
|
||||
args: Tuple[SyscallArg, ...]
|
||||
|
||||
|
||||
MACH_TRAP_INDEXED_CALL_RE = re.compile(r"/\*\s*(?P<idx>\d+)\s*\*/\s*MACH_TRAP\s*\(")
|
||||
MACH_TRAPS_IFNDEF_KERNEL_RE = re.compile(r"^\s*#ifndef\s+KERNEL\b", re.MULTILINE)
|
||||
MACH_TRAPS_ELSE_KERNEL_RE = re.compile(r"^\s*#else\b.*\bKERNEL\b", re.MULTILINE)
|
||||
|
||||
USER_EXTERN_PROTO_RE = re.compile(
|
||||
r"^\s*extern\s+(?P<ret>.+?)\s+(?P<fn>[A-Za-z_]\w*)\s*\((?P<args>.*?)\)\s*;",
|
||||
re.MULTILINE | re.DOTALL,
|
||||
)
|
||||
KERNEL_STRUCT_RE = re.compile(
|
||||
r"\bstruct\s+(?P<name>[A-Za-z_]\w*)\s*\{\s*(?P<body>.*?)\s*\}\s*;",
|
||||
re.DOTALL,
|
||||
)
|
||||
KERNEL_TRAP_EXTERN_RE = re.compile(
|
||||
r"^\s*(?:extern\s+)?(?P<ret>.+?)\s+(?P<fn>[A-Za-z_]\w*)\s*\(\s*struct\s+(?P<args_struct>[A-Za-z_]\w*)\s*\*\s*args\s*\)\s*;",
|
||||
re.MULTILINE | re.DOTALL,
|
||||
)
|
||||
PAD_ARG_RE = re.compile(
|
||||
r"\bPAD_ARG_\s*\(\s*(?P<ty>[^,]+?)\s*,\s*(?P<name>[^)]+?)\s*\)\s*;",
|
||||
)
|
||||
PLAIN_FIELD_RE = re.compile(r"^\s*(?P<ty>.+?)\s+(?P<name>[A-Za-z_]\w*)\s*;\s*$")
|
||||
|
||||
BSD_MASTER_ENTRY_RE = re.compile(
|
||||
r"^\s*(?P<nr>\d+)\s+(?P<audit>\S+)\s+(?P<files>\S+)\s+\{\s*(?P<proto>[^}]+?)\s*\}\s*(?:\{.*\})?\s*$"
|
||||
)
|
||||
BSD_MASTER_PROTO_RE = re.compile(
|
||||
r"^\s*(?P<ret>.+?)\s+(?P<fn>[A-Za-z_]\w*)\s*\((?P<args>.*)\)\s*(?P<attrs>.*)$",
|
||||
re.DOTALL,
|
||||
)
|
||||
|
||||
LINE_COMMENT_RE = re.compile(r"//.*$")
|
||||
BLOCK_COMMENT_RE = re.compile(r"/\*[^*]*\*+(?:[^/*][^*]*\*+)*/")
|
||||
|
||||
TRAILING_ARRAY_SUFFIX_RE = re.compile(r"\[[^\]]*\]\s*$")
|
||||
POINTER_SPACING_RE = re.compile(r"\s*\*\s*")
|
||||
PARAM_NAME_RE = re.compile(r"[A-Za-z_]\w*$")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--kernel-tree", required=True)
|
||||
ap.add_argument("--out-vala", required=True)
|
||||
ap.add_argument("--out-c", required=True)
|
||||
args = ap.parse_args()
|
||||
|
||||
kernel_tree = Path(args.kernel_tree).resolve()
|
||||
if not kernel_tree.exists():
|
||||
raise SystemExit(f"--kernel-tree does not exist: {kernel_tree}")
|
||||
|
||||
mach_numbers, mach_sigs = load_mach_traps(kernel_tree)
|
||||
bsd_numbers, bsd_sigs = load_bsd_syscalls_master(kernel_tree)
|
||||
|
||||
vala = render_vala(
|
||||
mach_numbers=mach_numbers,
|
||||
bsd_numbers=bsd_numbers,
|
||||
)
|
||||
c_src = render_c_source(
|
||||
mach_numbers=mach_numbers,
|
||||
bsd_numbers=bsd_numbers,
|
||||
mach_sigs=mach_sigs,
|
||||
bsd_sigs=bsd_sigs,
|
||||
)
|
||||
|
||||
out_vala = Path(args.out_vala)
|
||||
out_c = Path(args.out_c)
|
||||
out_vala.parent.mkdir(parents=True, exist_ok=True)
|
||||
out_c.parent.mkdir(parents=True, exist_ok=True)
|
||||
out_vala.write_text(vala, encoding="utf-8")
|
||||
out_c.write_text(c_src, encoding="utf-8")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def load_mach_traps(
|
||||
kernel_tree: Path,
|
||||
) -> Tuple[Dict[int, str], Dict[str, SyscallSignature]]:
|
||||
table_path = kernel_tree / "osfmk" / "kern" / "syscall_sw.c"
|
||||
if not table_path.exists():
|
||||
raise RuntimeError(f"Missing Mach trap table source: {table_path}")
|
||||
|
||||
header_path = kernel_tree / "osfmk" / "mach" / "mach_traps.h"
|
||||
if not header_path.exists():
|
||||
raise RuntimeError(f"Missing Mach traps header: {header_path}")
|
||||
|
||||
table_text = table_path.read_text(encoding="utf-8", errors="ignore")
|
||||
trap_routines = parse_mach_trap_table(table_text, table_path)
|
||||
|
||||
header_text = header_path.read_text(encoding="utf-8", errors="ignore")
|
||||
userspace_text, kernel_text = split_mach_traps_header(header_text, header_path)
|
||||
|
||||
userspace_protos = parse_userspace_mach_traps_prototypes(
|
||||
userspace_text, header_path
|
||||
)
|
||||
kernel_argmap = parse_kernel_mach_traps_args(kernel_text, header_path)
|
||||
|
||||
numbers: Dict[int, str] = {}
|
||||
sigs: Dict[str, SyscallSignature] = {}
|
||||
|
||||
for index, routine in trap_routines:
|
||||
if routine == "kern_invalid":
|
||||
continue
|
||||
|
||||
nr = -index
|
||||
public_name = normalize_mach_name(routine)
|
||||
if public_name == "":
|
||||
raise RuntimeError(f"{table_path}: empty normalized name from {routine!r}")
|
||||
|
||||
if nr in numbers:
|
||||
raise RuntimeError(f"{table_path}: duplicate mach trap number {nr}")
|
||||
if public_name in sigs:
|
||||
raise RuntimeError(
|
||||
f"{table_path}: normalized Mach name collision for {public_name!r}"
|
||||
)
|
||||
|
||||
args = resolve_mach_trap_signature(
|
||||
routine=routine,
|
||||
userspace_protos=userspace_protos,
|
||||
kernel_argmap=kernel_argmap,
|
||||
table_path=table_path,
|
||||
)
|
||||
|
||||
if len(args) > MAX_ARGS:
|
||||
raise RuntimeError(
|
||||
f"{table_path}: trap {routine!r} has {len(args)} args; max supported is {MAX_ARGS}"
|
||||
)
|
||||
|
||||
numbers[nr] = public_name
|
||||
sigs[public_name] = SyscallSignature(name=public_name, args=tuple(args))
|
||||
|
||||
return numbers, sigs
|
||||
|
||||
|
||||
def parse_mach_trap_table(text: str, path: Path) -> List[Tuple[int, str]]:
|
||||
anchor = text.find("mach_trap_table")
|
||||
if anchor == -1:
|
||||
raise RuntimeError(f"{path}: mach_trap_table not found")
|
||||
|
||||
brace = text.find("{", anchor)
|
||||
if brace == -1:
|
||||
raise RuntimeError(f"{path}: mach_trap_table '{{' not found")
|
||||
|
||||
payload, _ = extract_brace_payload(text, brace, path)
|
||||
|
||||
by_index: Dict[int, str] = {}
|
||||
|
||||
off = 0
|
||||
while True:
|
||||
m = MACH_TRAP_INDEXED_CALL_RE.search(payload, off)
|
||||
if m is None:
|
||||
break
|
||||
|
||||
idx = int(m.group("idx"), 10)
|
||||
open_paren = m.end(0) - 1
|
||||
args_blob, end_off = extract_parentheses_payload(payload, open_paren, path)
|
||||
|
||||
parts = [
|
||||
a.strip() for a in split_top_level_commas(args_blob) if a.strip() != ""
|
||||
]
|
||||
if len(parts) < 1:
|
||||
raise RuntimeError(f"{path}: bad MACH_TRAP() entry: {args_blob!r}")
|
||||
|
||||
routine = parts[0]
|
||||
|
||||
prev = by_index.get(idx)
|
||||
if prev is None:
|
||||
by_index[idx] = routine
|
||||
else:
|
||||
if prev == routine:
|
||||
pass
|
||||
elif prev == "kern_invalid" and routine != "kern_invalid":
|
||||
by_index[idx] = routine
|
||||
elif routine == "kern_invalid" and prev != "kern_invalid":
|
||||
pass
|
||||
else:
|
||||
raise RuntimeError(
|
||||
f"{path}: conflicting MACH_TRAP entries for index {idx}: {prev!r} vs {routine!r}"
|
||||
)
|
||||
|
||||
off = end_off
|
||||
|
||||
return sorted(by_index.items(), key=lambda kv: kv[0])
|
||||
|
||||
|
||||
def split_mach_traps_header(text: str, path: Path) -> Tuple[str, str]:
|
||||
m_start = MACH_TRAPS_IFNDEF_KERNEL_RE.search(text)
|
||||
if m_start is None:
|
||||
raise RuntimeError(f"{path}: missing '#ifndef KERNEL'")
|
||||
|
||||
m_else = MACH_TRAPS_ELSE_KERNEL_RE.search(text, m_start.end())
|
||||
if m_else is None:
|
||||
raise RuntimeError(f"{path}: missing '#else ... KERNEL ...'")
|
||||
|
||||
userspace = text[m_start.end():m_else.start()]
|
||||
kernel = text[m_else.end():]
|
||||
return userspace, kernel
|
||||
|
||||
|
||||
def parse_userspace_mach_traps_prototypes(
|
||||
userspace_text: str, path: Path
|
||||
) -> Dict[str, Tuple[SyscallArg, ...]]:
|
||||
protos: Dict[str, Tuple[SyscallArg, ...]] = {}
|
||||
|
||||
for m in USER_EXTERN_PROTO_RE.finditer(userspace_text):
|
||||
fn = m.group("fn").strip()
|
||||
args_blob = m.group("args").strip()
|
||||
ret_blob = strip_c_comments(m.group("ret")).strip()
|
||||
if ret_blob == "":
|
||||
raise RuntimeError(f"{path}: empty return type for {fn!r}")
|
||||
|
||||
line_no = line_number_at_offset(userspace_text, m.start())
|
||||
|
||||
args = parse_c_param_list(args_blob, path, line_no)
|
||||
cur = tuple(args)
|
||||
|
||||
prev = protos.get(fn)
|
||||
if prev is not None and prev != cur:
|
||||
raise RuntimeError(f"{path}:{line_no}: conflicting prototype for {fn!r}")
|
||||
|
||||
protos[fn] = cur
|
||||
|
||||
return protos
|
||||
|
||||
|
||||
def parse_kernel_mach_traps_args(
|
||||
kernel_text: str, path: Path
|
||||
) -> Dict[str, Tuple[SyscallArg, ...]]:
|
||||
text = strip_c_comments(kernel_text)
|
||||
|
||||
structs: Dict[str, str] = {}
|
||||
for m in KERNEL_STRUCT_RE.finditer(text):
|
||||
name = m.group("name").strip()
|
||||
body = m.group("body")
|
||||
structs[name] = body
|
||||
|
||||
fn_to_struct: Dict[str, str] = {}
|
||||
for m in KERNEL_TRAP_EXTERN_RE.finditer(text):
|
||||
fn = m.group("fn").strip()
|
||||
args_struct = m.group("args_struct").strip()
|
||||
fn_to_struct[fn] = args_struct
|
||||
|
||||
out: Dict[str, Tuple[SyscallArg, ...]] = {}
|
||||
|
||||
for fn, args_struct in fn_to_struct.items():
|
||||
body = structs.get(args_struct)
|
||||
if body is None:
|
||||
continue
|
||||
|
||||
args = parse_mach_args_struct_body(body, path)
|
||||
out[fn] = tuple(args)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def parse_mach_args_struct_body(body: str, path: Path) -> List[SyscallArg]:
|
||||
args: List[SyscallArg] = []
|
||||
|
||||
body = strip_c_comments(body)
|
||||
for raw_ln in body.splitlines():
|
||||
ln = raw_ln.strip()
|
||||
if ln == "" or ln.startswith("#"):
|
||||
continue
|
||||
if "PAD_ARG_8" in ln:
|
||||
continue
|
||||
|
||||
m = PAD_ARG_RE.search(ln)
|
||||
if m is not None:
|
||||
ty = normalize_whitespace(m.group("ty").strip())
|
||||
name = normalize_whitespace(m.group("name").strip())
|
||||
if ty == "" or name == "":
|
||||
raise RuntimeError(f"{path}: bad PAD_ARG_ in args struct: {raw_ln!r}")
|
||||
args.append(SyscallArg(type=ty, name=name))
|
||||
continue
|
||||
|
||||
m = PLAIN_FIELD_RE.match(ln)
|
||||
if m is not None:
|
||||
ty = normalize_whitespace(m.group("ty").strip())
|
||||
name = m.group("name").strip()
|
||||
if ty == "" or name == "":
|
||||
raise RuntimeError(f"{path}: bad field in args struct: {raw_ln!r}")
|
||||
args.append(SyscallArg(type=ty, name=name))
|
||||
continue
|
||||
|
||||
if len(args) == 1 and args[0].name == "dummy":
|
||||
return []
|
||||
|
||||
return args
|
||||
|
||||
|
||||
def resolve_mach_trap_signature(
|
||||
*,
|
||||
routine: str,
|
||||
userspace_protos: Dict[str, Tuple[SyscallArg, ...]],
|
||||
kernel_argmap: Dict[str, Tuple[SyscallArg, ...]],
|
||||
table_path: Path,
|
||||
) -> List[SyscallArg]:
|
||||
def get_userspace(name: str) -> Optional[Tuple[SyscallArg, ...]]:
|
||||
return userspace_protos.get(name)
|
||||
|
||||
def get_kernel(name: str) -> Optional[Tuple[SyscallArg, ...]]:
|
||||
return kernel_argmap.get(name)
|
||||
|
||||
proto_args = get_userspace(routine)
|
||||
|
||||
if proto_args is None and routine.endswith("_trap"):
|
||||
proto_args = get_userspace(routine[: -len("_trap")])
|
||||
|
||||
if proto_args is not None:
|
||||
return list(proto_args)
|
||||
|
||||
proto_args = get_kernel(routine)
|
||||
if proto_args is None and routine.endswith("_trap"):
|
||||
proto_args = get_kernel(routine[: -len("_trap")])
|
||||
|
||||
if proto_args is None:
|
||||
raise RuntimeError(
|
||||
f"{table_path}: missing userspace prototype for {routine!r}, and no kernel extern(struct *_args *args) mapping found"
|
||||
)
|
||||
|
||||
return list(proto_args)
|
||||
|
||||
|
||||
def normalize_mach_name(routine: str) -> str:
|
||||
if routine.startswith("_"):
|
||||
routine = routine[1:]
|
||||
if routine.startswith("kernelrpc_"):
|
||||
routine = routine[len("kernelrpc_"):]
|
||||
if routine.endswith("_trap"):
|
||||
routine = routine[: -len("_trap")]
|
||||
return routine
|
||||
|
||||
|
||||
def load_bsd_syscalls_master(
|
||||
kernel_tree: Path,
|
||||
) -> Tuple[Dict[int, str], Dict[str, SyscallSignature]]:
|
||||
path = kernel_tree / "bsd" / "kern" / "syscalls.master"
|
||||
if not path.exists():
|
||||
raise RuntimeError(f"Missing BSD master file: {path}")
|
||||
|
||||
numbers: Dict[int, str] = {}
|
||||
sigs: Dict[str, SyscallSignature] = {}
|
||||
|
||||
lines = path.read_text(encoding="utf-8", errors="ignore").splitlines()
|
||||
|
||||
stack: List[bool] = []
|
||||
active = True
|
||||
|
||||
for line_no, raw in enumerate(lines, start=1):
|
||||
line = raw.rstrip("\n")
|
||||
stripped = line.lstrip()
|
||||
|
||||
if (
|
||||
stripped.startswith("#if ")
|
||||
or stripped.startswith("#ifdef ")
|
||||
or stripped.startswith("#ifndef ")
|
||||
):
|
||||
stack.append(active)
|
||||
continue
|
||||
|
||||
if stripped.startswith("#else"):
|
||||
if not stack:
|
||||
raise RuntimeError(f"{path}:{line_no}: #else without #if")
|
||||
active = False
|
||||
continue
|
||||
|
||||
if stripped.startswith("#endif"):
|
||||
if not stack:
|
||||
raise RuntimeError(f"{path}:{line_no}: #endif without #if")
|
||||
active = stack.pop()
|
||||
continue
|
||||
|
||||
if not active:
|
||||
continue
|
||||
|
||||
m = BSD_MASTER_ENTRY_RE.match(line)
|
||||
if m is None:
|
||||
continue
|
||||
|
||||
nr = int(m.group("nr"), 10)
|
||||
proto_blob = strip_c_comments(m.group("proto")).strip()
|
||||
|
||||
mp = BSD_MASTER_PROTO_RE.match(proto_blob)
|
||||
if mp is None:
|
||||
raise RuntimeError(
|
||||
f"{path}:{line_no}: cannot parse prototype: {proto_blob!r}"
|
||||
)
|
||||
|
||||
fn = mp.group("fn").strip()
|
||||
args_blob = mp.group("args").strip()
|
||||
|
||||
if fn in {"nosys", "enosys"}:
|
||||
continue
|
||||
|
||||
public_name = normalize_bsd_name(fn)
|
||||
if public_name == "":
|
||||
raise RuntimeError(f"{path}:{line_no}: empty normalized name from {fn!r}")
|
||||
|
||||
if nr in numbers:
|
||||
raise RuntimeError(f"{path}:{line_no}: duplicate syscall number {nr}")
|
||||
if public_name in sigs:
|
||||
raise RuntimeError(
|
||||
f"{path}:{line_no}: duplicate syscall name {public_name!r}"
|
||||
)
|
||||
|
||||
args = parse_c_param_list(args_blob, path, line_no)
|
||||
|
||||
if len(args) > MAX_ARGS:
|
||||
raise RuntimeError(
|
||||
f"{path}:{line_no}: syscall {public_name!r} has {len(args)} args; max supported is {MAX_ARGS}"
|
||||
)
|
||||
|
||||
numbers[nr] = public_name
|
||||
sigs[public_name] = SyscallSignature(name=public_name, args=tuple(args))
|
||||
|
||||
return numbers, sigs
|
||||
|
||||
|
||||
def normalize_bsd_name(fn: str) -> str:
|
||||
if fn.startswith("sys_"):
|
||||
return fn[len("sys_"):]
|
||||
if fn.startswith("__"):
|
||||
return fn[2:]
|
||||
return fn
|
||||
|
||||
|
||||
def strip_c_comments(s: str) -> str:
|
||||
s = LINE_COMMENT_RE.sub("", s)
|
||||
while True:
|
||||
new_s = BLOCK_COMMENT_RE.sub("", s)
|
||||
if new_s == s:
|
||||
break
|
||||
s = new_s
|
||||
return s
|
||||
|
||||
|
||||
def split_top_level_commas(text: str) -> List[str]:
|
||||
parts: List[str] = []
|
||||
buf: List[str] = []
|
||||
depth = 0
|
||||
in_string = False
|
||||
string_quote = ""
|
||||
|
||||
cursor = 0
|
||||
while cursor < len(text):
|
||||
ch = text[cursor]
|
||||
|
||||
if in_string:
|
||||
buf.append(ch)
|
||||
if ch == "\\" and cursor + 1 < len(text):
|
||||
buf.append(text[cursor + 1])
|
||||
cursor += 2
|
||||
continue
|
||||
if ch == string_quote:
|
||||
in_string = False
|
||||
cursor += 1
|
||||
continue
|
||||
|
||||
if ch in ("'", '"'):
|
||||
in_string = True
|
||||
string_quote = ch
|
||||
buf.append(ch)
|
||||
cursor += 1
|
||||
continue
|
||||
|
||||
if ch == "(":
|
||||
depth += 1
|
||||
elif ch == ")":
|
||||
depth = max(0, depth - 1)
|
||||
|
||||
if ch == "," and depth == 0:
|
||||
parts.append("".join(buf).strip())
|
||||
buf = []
|
||||
cursor += 1
|
||||
continue
|
||||
|
||||
buf.append(ch)
|
||||
cursor += 1
|
||||
|
||||
parts.append("".join(buf).strip())
|
||||
return parts
|
||||
|
||||
|
||||
def parse_c_param_list(args_blob: str, path: Path, line_no: int) -> List[SyscallArg]:
|
||||
raw = strip_c_comments(args_blob).strip()
|
||||
if raw == "" or raw == "void":
|
||||
return []
|
||||
|
||||
raw_params = [p.strip() for p in split_top_level_commas(raw) if p.strip() != ""]
|
||||
if len(raw_params) == 1 and raw_params[0] == "void":
|
||||
return []
|
||||
|
||||
args: List[SyscallArg] = []
|
||||
for idx, p in enumerate(raw_params):
|
||||
ty, name = split_param_decl(p, path, line_no)
|
||||
if name is None:
|
||||
name = f"arg{idx}"
|
||||
if ty == "user_addr_t" and name in {"attrname", "link", "path"}:
|
||||
ty = "char *"
|
||||
args.append(SyscallArg(type=ty, name=name))
|
||||
return args
|
||||
|
||||
|
||||
def split_param_decl(decl: str, path: Path, line_no: int) -> Tuple[str, Optional[str]]:
|
||||
d = strip_c_comments(decl).strip()
|
||||
|
||||
if d == "" or d == "void":
|
||||
raise RuntimeError(f"{path}:{line_no}: bad parameter decl: {decl!r}")
|
||||
|
||||
tokens = _tokenize_decl(d)
|
||||
ty_tokens, name = _split_type_and_name(tokens)
|
||||
|
||||
ty = _format_type(ty_tokens)
|
||||
if not ty:
|
||||
raise RuntimeError(
|
||||
f"{path}:{line_no}: cannot parse parameter type from {decl!r}"
|
||||
)
|
||||
|
||||
return ty, _camel_to_snake(name) if name else None
|
||||
|
||||
|
||||
def _tokenize_decl(s: str) -> list[str]:
|
||||
out = []
|
||||
for c in s:
|
||||
out.append(" * " if c == "*" else c)
|
||||
return "".join(out).split()
|
||||
|
||||
|
||||
def _split_type_and_name(tokens: list[str]) -> Tuple[list[str], Optional[str]]:
|
||||
if not tokens:
|
||||
return [], None
|
||||
|
||||
last = tokens[-1]
|
||||
|
||||
if last == "*":
|
||||
return tokens, None
|
||||
|
||||
stars, remainder = _split_leading_stars(last)
|
||||
if stars:
|
||||
if _is_identifier(remainder):
|
||||
return tokens[:-1] + ["*"] * stars, remainder
|
||||
return tokens, None
|
||||
|
||||
if _is_identifier(last):
|
||||
return tokens[:-1], last
|
||||
|
||||
return tokens, None
|
||||
|
||||
|
||||
def _format_type(tokens: list[str]) -> str:
|
||||
formatted: list[str] = []
|
||||
i = 0
|
||||
while i < len(tokens):
|
||||
if tokens[i] != "*":
|
||||
formatted.append(tokens[i])
|
||||
i += 1
|
||||
continue
|
||||
|
||||
j = i
|
||||
while j < len(tokens) and tokens[j] == "*":
|
||||
j += 1
|
||||
formatted.append("*" * (j - i))
|
||||
i = j
|
||||
|
||||
return " ".join(formatted).strip()
|
||||
|
||||
|
||||
def _split_leading_stars(token: str) -> Tuple[int, str]:
|
||||
i = 0
|
||||
while i < len(token) and token[i] == "*":
|
||||
i += 1
|
||||
return i, token[i:]
|
||||
|
||||
|
||||
def _is_identifier(s: str) -> bool:
|
||||
if not s:
|
||||
return False
|
||||
if not (s[0].isalpha() or s[0] == "_"):
|
||||
return False
|
||||
return all(c.isalnum() or c == "_" for c in s)
|
||||
|
||||
|
||||
def _camel_to_snake(name: str) -> str:
|
||||
out = []
|
||||
for i, c in enumerate(name):
|
||||
if (
|
||||
c.isupper()
|
||||
and i > 0
|
||||
and (name[i - 1].islower() or (i + 1 < len(name) and name[i + 1].islower()))
|
||||
):
|
||||
out.append("_")
|
||||
out.append(c.lower())
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def normalize_whitespace(s: str) -> str:
|
||||
return " ".join(s.split())
|
||||
|
||||
|
||||
def line_number_at_offset(text: str, offset: int) -> int:
|
||||
return text.count("\n", 0, offset) + 1
|
||||
|
||||
|
||||
def extract_parentheses_payload(
|
||||
text: str, open_paren_offset: int, path: Path
|
||||
) -> Tuple[str, int]:
|
||||
if (
|
||||
open_paren_offset < 0
|
||||
or open_paren_offset >= len(text)
|
||||
or text[open_paren_offset] != "("
|
||||
):
|
||||
raise RuntimeError(f"{path}: expected '(' at offset {open_paren_offset}")
|
||||
|
||||
depth = 0
|
||||
cursor = open_paren_offset
|
||||
start = open_paren_offset + 1
|
||||
|
||||
in_string = False
|
||||
string_quote = ""
|
||||
|
||||
while cursor < len(text):
|
||||
ch = text[cursor]
|
||||
|
||||
if in_string:
|
||||
if ch == "\\":
|
||||
cursor += 2
|
||||
continue
|
||||
if ch == string_quote:
|
||||
in_string = False
|
||||
cursor += 1
|
||||
continue
|
||||
|
||||
if ch in ("'", '"'):
|
||||
in_string = True
|
||||
string_quote = ch
|
||||
cursor += 1
|
||||
continue
|
||||
|
||||
if ch == "(":
|
||||
depth += 1
|
||||
elif ch == ")":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return text[start:cursor], cursor + 1
|
||||
|
||||
cursor += 1
|
||||
|
||||
raise RuntimeError(f"{path}: unterminated parentheses payload")
|
||||
|
||||
|
||||
def extract_brace_payload(
|
||||
text: str, open_brace_offset: int, path: Path
|
||||
) -> Tuple[str, int]:
|
||||
if (
|
||||
open_brace_offset < 0
|
||||
or open_brace_offset >= len(text)
|
||||
or text[open_brace_offset] != "{"
|
||||
):
|
||||
raise RuntimeError(f"{path}: expected '{{' at offset {open_brace_offset}")
|
||||
|
||||
depth = 0
|
||||
cursor = open_brace_offset
|
||||
start = open_brace_offset + 1
|
||||
|
||||
in_string = False
|
||||
string_quote = ""
|
||||
|
||||
while cursor < len(text):
|
||||
ch = text[cursor]
|
||||
|
||||
if in_string:
|
||||
if ch == "\\":
|
||||
cursor += 2
|
||||
continue
|
||||
if ch == string_quote:
|
||||
in_string = False
|
||||
cursor += 1
|
||||
continue
|
||||
|
||||
if ch in ("'", '"'):
|
||||
in_string = True
|
||||
string_quote = ch
|
||||
cursor += 1
|
||||
continue
|
||||
|
||||
if ch == "{":
|
||||
depth += 1
|
||||
elif ch == "}":
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
return text[start:cursor], cursor + 1
|
||||
|
||||
cursor += 1
|
||||
|
||||
raise RuntimeError(f"{path}: unterminated brace payload")
|
||||
|
||||
|
||||
def render_vala(*, mach_numbers: Dict[int, str], bsd_numbers: Dict[int, str]) -> str:
|
||||
out: List[str] = []
|
||||
|
||||
out += [
|
||||
"// AUTO-GENERATED FILE. DO NOT EDIT.\n",
|
||||
"\n",
|
||||
"namespace Frida {\n",
|
||||
"\tpublic extern unowned XnuSyscallSignature[] get_xnu_mach_traps ();\n",
|
||||
"\tpublic extern unowned XnuSyscallSignature[] get_xnu_bsd_syscalls ();\n",
|
||||
"\n",
|
||||
"\tpublic struct XnuSyscallSignature {\n",
|
||||
"\t\tpublic int nr;\n",
|
||||
"\t\tpublic string name;\n",
|
||||
"\t\tpublic uint8 nargs;\n",
|
||||
f"\t\tpublic XnuSyscallArg args[{MAX_ARGS}];\n",
|
||||
"\t}\n",
|
||||
"\n",
|
||||
"\tpublic struct XnuSyscallArg {\n",
|
||||
"\t\tpublic string? type;\n",
|
||||
"\t\tpublic string? name;\n",
|
||||
"\t}\n",
|
||||
"\n",
|
||||
"\tpublic enum XnuMachTrap {\n",
|
||||
]
|
||||
|
||||
for nr, name in sorted(mach_numbers.items(), key=lambda kv: kv[0], reverse=True):
|
||||
out += [f"\t\t{name.upper()} = {nr},\n"]
|
||||
|
||||
out += [
|
||||
"\t}\n",
|
||||
"\n",
|
||||
"\tpublic enum XnuBsdSyscall {\n",
|
||||
]
|
||||
|
||||
for nr, name in sorted(bsd_numbers.items(), key=lambda kv: kv[0]):
|
||||
out += [f"\t\t{name.upper()} = {nr},\n"]
|
||||
|
||||
out += [
|
||||
"\t}\n",
|
||||
"}\n",
|
||||
]
|
||||
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def render_c_source(
|
||||
*,
|
||||
mach_numbers: Dict[int, str],
|
||||
bsd_numbers: Dict[int, str],
|
||||
mach_sigs: Dict[str, SyscallSignature],
|
||||
bsd_sigs: Dict[str, SyscallSignature],
|
||||
) -> str:
|
||||
out: List[str] = []
|
||||
|
||||
out += [
|
||||
"/* AUTO-GENERATED FILE. DO NOT EDIT. */\n",
|
||||
"\n",
|
||||
'#include "frida-base.h"\n',
|
||||
"\n",
|
||||
]
|
||||
|
||||
emit_c_signature_table(
|
||||
out,
|
||||
c_var="frida_xnu_mach_traps",
|
||||
numbers=mach_numbers,
|
||||
sigs=mach_sigs,
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
emit_c_signature_table(
|
||||
out,
|
||||
c_var="frida_xnu_bsd_syscalls",
|
||||
numbers=bsd_numbers,
|
||||
sigs=bsd_sigs,
|
||||
)
|
||||
|
||||
emit_c_table_getter(
|
||||
out, c_func="frida_get_xnu_mach_traps", c_var="frida_xnu_mach_traps"
|
||||
)
|
||||
out += ["\n"]
|
||||
emit_c_table_getter(
|
||||
out, c_func="frida_get_xnu_bsd_syscalls", c_var="frida_xnu_bsd_syscalls"
|
||||
)
|
||||
|
||||
return "".join(out)
|
||||
|
||||
|
||||
def emit_c_signature_table(
|
||||
out: List[str],
|
||||
*,
|
||||
c_var: str,
|
||||
numbers: Dict[int, str],
|
||||
sigs: Dict[str, SyscallSignature],
|
||||
reverse: bool = False,
|
||||
) -> None:
|
||||
out += [
|
||||
f"static const FridaXnuSyscallSignature {c_var}[] =\n",
|
||||
"{\n",
|
||||
]
|
||||
|
||||
for nr, name in sorted(numbers.items(), key=lambda kv: kv[0], reverse=reverse):
|
||||
sig = sigs[name]
|
||||
|
||||
args_parts: List[str] = []
|
||||
for a in sig.args:
|
||||
args_parts += [f"{{ {c_escape(a.type)}, {c_escape(a.name)} }}"]
|
||||
|
||||
args_blob = ", ".join(args_parts)
|
||||
|
||||
if args_blob != "":
|
||||
out += [
|
||||
f" {{ {nr}, {c_escape(name)}, {len(sig.args)}, {{ {args_blob} }} }},\n"
|
||||
]
|
||||
else:
|
||||
out += [f" {{ {nr}, {c_escape(name)}, 0, {{}} }},\n"]
|
||||
|
||||
out += [
|
||||
"};\n",
|
||||
"\n",
|
||||
]
|
||||
|
||||
|
||||
def emit_c_table_getter(out: List[str], *, c_func: str, c_var: str) -> None:
|
||||
out += [
|
||||
"FridaXnuSyscallSignature *\n",
|
||||
f"{c_func} (int * len)\n",
|
||||
"{\n",
|
||||
" if (len != NULL)\n",
|
||||
f" *len = G_N_ELEMENTS ({c_var});\n",
|
||||
f" return (FridaXnuSyscallSignature *) {c_var};\n",
|
||||
"}\n",
|
||||
]
|
||||
|
||||
|
||||
def c_escape(s: str) -> str:
|
||||
return '"' + s.replace("\\", "\\\\").replace('"', '\\"') + '"'
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,542 @@
|
||||
/* AUTO-GENERATED FILE. DO NOT EDIT. */
|
||||
|
||||
#include "frida-base.h"
|
||||
|
||||
static const FridaXnuSyscallSignature frida_xnu_mach_traps[] =
|
||||
{
|
||||
{ -10, "mach_vm_allocate", 4, { { "mach_port_name_t", "target" }, { "mach_vm_offset_t *", "addr" }, { "mach_vm_size_t", "size" }, { "int", "flags" } } },
|
||||
{ -11, "mach_vm_purgable_control", 4, { { "mach_port_name_t", "target" }, { "mach_vm_offset_t", "address" }, { "vm_purgable_t", "control" }, { "int *", "state" } } },
|
||||
{ -12, "mach_vm_deallocate", 3, { { "mach_port_name_t", "target" }, { "mach_vm_address_t", "address" }, { "mach_vm_size_t", "size" } } },
|
||||
{ -13, "task_dyld_process_info_notify_get", 2, { { "mach_port_name_array_t", "names_addr" }, { "natural_t *", "names_count_addr" } } },
|
||||
{ -14, "mach_vm_protect", 5, { { "mach_port_name_t", "target" }, { "mach_vm_address_t", "address" }, { "mach_vm_size_t", "size" }, { "boolean_t", "set_maximum" }, { "vm_prot_t", "new_protection" } } },
|
||||
{ -15, "mach_vm_map", 6, { { "mach_port_name_t", "target" }, { "mach_vm_offset_t *", "address" }, { "mach_vm_size_t", "size" }, { "mach_vm_offset_t", "mask" }, { "int", "flags" }, { "vm_prot_t", "cur_protection" } } },
|
||||
{ -16, "mach_port_allocate", 3, { { "mach_port_name_t", "target" }, { "mach_port_right_t", "right" }, { "mach_port_name_t *", "name" } } },
|
||||
{ -18, "mach_port_deallocate", 2, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" } } },
|
||||
{ -19, "mach_port_mod_refs", 4, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "mach_port_right_t", "right" }, { "mach_port_delta_t", "delta" } } },
|
||||
{ -20, "mach_port_move_member", 3, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "member" }, { "mach_port_name_t", "after" } } },
|
||||
{ -21, "mach_port_insert_right", 4, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "mach_port_name_t", "poly" }, { "mach_msg_type_name_t", "poly_poly" } } },
|
||||
{ -22, "mach_port_insert_member", 3, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "mach_port_name_t", "pset" } } },
|
||||
{ -23, "mach_port_extract_member", 3, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "mach_port_name_t", "pset" } } },
|
||||
{ -24, "mach_port_construct", 4, { { "mach_port_name_t", "target" }, { "mach_port_options_t *", "options" }, { "uint64_t", "context" }, { "mach_port_name_t *", "name" } } },
|
||||
{ -25, "mach_port_destruct", 4, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "mach_port_delta_t", "srdelta" }, { "uint64_t", "guard" } } },
|
||||
{ -26, "mach_reply_port", 0, {} },
|
||||
{ -27, "thread_self", 0, {} },
|
||||
{ -28, "task_self", 0, {} },
|
||||
{ -29, "host_self", 0, {} },
|
||||
{ -31, "mach_msg", 7, { { "mach_msg_header_t *", "msg" }, { "mach_msg_option_t", "option" }, { "mach_msg_size_t", "send_size" }, { "mach_msg_size_t", "rcv_size" }, { "mach_port_name_t", "rcv_name" }, { "mach_msg_timeout_t", "timeout" }, { "mach_port_name_t", "notify" } } },
|
||||
{ -32, "mach_msg_overwrite", 9, { { "mach_msg_header_t *", "msg" }, { "mach_msg_option_t", "option" }, { "mach_msg_size_t", "send_size" }, { "mach_msg_size_t", "rcv_size" }, { "mach_port_name_t", "rcv_name" }, { "mach_msg_timeout_t", "timeout" }, { "mach_msg_priority_t", "priority" }, { "mach_msg_header_t *", "rcv_msg" }, { "mach_msg_size_t", "rcv_limit" } } },
|
||||
{ -33, "semaphore_signal", 1, { { "mach_port_name_t", "signal_name" } } },
|
||||
{ -34, "semaphore_signal_all", 1, { { "mach_port_name_t", "signal_name" } } },
|
||||
{ -35, "semaphore_signal_thread", 2, { { "mach_port_name_t", "signal_name" }, { "mach_port_name_t", "thread_name" } } },
|
||||
{ -36, "semaphore_wait", 1, { { "mach_port_name_t", "wait_name" } } },
|
||||
{ -37, "semaphore_wait_signal", 2, { { "mach_port_name_t", "wait_name" }, { "mach_port_name_t", "signal_name" } } },
|
||||
{ -38, "semaphore_timedwait", 3, { { "mach_port_name_t", "wait_name" }, { "unsigned int", "sec" }, { "clock_res_t", "nsec" } } },
|
||||
{ -39, "semaphore_timedwait_signal", 4, { { "mach_port_name_t", "wait_name" }, { "mach_port_name_t", "signal_name" }, { "unsigned int", "sec" }, { "clock_res_t", "nsec" } } },
|
||||
{ -40, "mach_port_get_attributes", 5, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "mach_port_flavor_t", "flavor" }, { "mach_port_info_t", "port_info_out" }, { "mach_msg_type_number_t *", "port_info_out_cnt" } } },
|
||||
{ -41, "mach_port_guard", 4, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "uint64_t", "guard" }, { "boolean_t", "strict" } } },
|
||||
{ -42, "mach_port_unguard", 3, { { "mach_port_name_t", "target" }, { "mach_port_name_t", "name" }, { "uint64_t", "guard" } } },
|
||||
{ -43, "mach_generate_activity_id", 3, { { "mach_port_name_t", "target" }, { "int", "count" }, { "uint64_t *", "activity_id" } } },
|
||||
{ -44, "task_name_for_pid", 3, { { "mach_port_name_t", "target_tport" }, { "int", "pid" }, { "mach_port_name_t *", "tn" } } },
|
||||
{ -45, "task_for_pid", 3, { { "mach_port_name_t", "target_tport" }, { "int", "pid" }, { "mach_port_name_t *", "t" } } },
|
||||
{ -46, "pid_for_task", 2, { { "mach_port_name_t", "t" }, { "int *", "x" } } },
|
||||
{ -47, "mach_msg2", 8, { { "void *", "data" }, { "mach_msg_option64_t", "options" }, { "uint64_t", "msgh_bits_and_send_size" }, { "uint64_t", "msgh_remote_and_local_port" }, { "uint64_t", "msgh_voucher_and_id" }, { "uint64_t", "desc_count_and_rcv_name" }, { "uint64_t", "rcv_size_and_priority" }, { "uint64_t", "timeout" } } },
|
||||
{ -48, "macx_swapon", 4, { { "uint64_t", "filename" }, { "int", "flags" }, { "int", "size" }, { "int", "priority" } } },
|
||||
{ -49, "macx_swapoff", 2, { { "uint64_t", "filename" }, { "int", "flags" } } },
|
||||
{ -50, "thread_get_special_reply_port", 0, {} },
|
||||
{ -51, "macx_triggers", 4, { { "int", "hi_water" }, { "int", "low_water" }, { "int", "flags" }, { "mach_port_t", "alert_port" } } },
|
||||
{ -52, "macx_backing_store_suspend", 1, { { "boolean_t", "suspend" } } },
|
||||
{ -53, "macx_backing_store_recovery", 1, { { "int", "pid" } } },
|
||||
{ -58, "pfz_exit", 0, {} },
|
||||
{ -59, "swtch_pri", 1, { { "int", "pri" } } },
|
||||
{ -60, "swtch", 0, {} },
|
||||
{ -61, "thread_switch", 3, { { "mach_port_name_t", "thread_name" }, { "int", "option" }, { "mach_msg_timeout_t", "option_time" } } },
|
||||
{ -62, "clock_sleep", 5, { { "mach_port_name_t", "clock_name" }, { "sleep_type_t", "sleep_type" }, { "int", "sleep_sec" }, { "int", "sleep_nsec" }, { "mach_timespec_t *", "wakeup_time" } } },
|
||||
{ -63, "mach_vm_reclaim_update_kernel_accounting", 2, { { "mach_port_name_t", "target_tport" }, { "uint64_t *", "bytes_reclaimed" } } },
|
||||
{ -70, "host_create_mach_voucher", 4, { { "mach_port_name_t", "host" }, { "mach_voucher_attr_raw_recipe_array_t", "recipes" }, { "int", "recipes_size" }, { "mach_port_name_t *", "voucher" } } },
|
||||
{ -72, "mach_voucher_extract_attr_recipe", 4, { { "mach_port_name_t", "voucher_name" }, { "mach_voucher_attr_key_t", "key" }, { "mach_voucher_attr_raw_recipe_t", "recipe" }, { "mach_msg_type_number_t *", "recipe_size" } } },
|
||||
{ -76, "mach_port_type", 3, { { "ipc_space_t", "task" }, { "mach_port_name_t", "name" }, { "mach_port_type_t *", "ptype" } } },
|
||||
{ -77, "mach_port_request_notification", 7, { { "ipc_space_t", "task" }, { "mach_port_name_t", "name" }, { "mach_msg_id_t", "msgid" }, { "mach_port_mscount_t", "sync" }, { "mach_port_name_t", "notify" }, { "mach_msg_type_name_t", "notify_poly" }, { "mach_port_name_t *", "previous" } } },
|
||||
{ -88, "exclaves_ctl", 8, { { "mach_port_name_t", "name" }, { "uint32_t", "operation_and_flags" }, { "uint64_t", "identifier" }, { "mach_vm_address_t", "buffer" }, { "mach_vm_size_t", "size" }, { "mach_vm_size_t", "size2" }, { "mach_vm_size_t", "offset" }, { "mach_vm_address_t", "status" } } },
|
||||
{ -89, "mach_timebase_info", 1, { { "user_addr_t", "info" } } },
|
||||
{ -90, "mach_wait_until", 1, { { "uint64_t", "deadline" } } },
|
||||
{ -91, "mk_timer_create", 0, {} },
|
||||
{ -92, "mk_timer_destroy", 1, { { "mach_port_name_t", "name" } } },
|
||||
{ -93, "mk_timer_arm", 2, { { "mach_port_name_t", "name" }, { "uint64_t", "expire_time" } } },
|
||||
{ -94, "mk_timer_cancel", 2, { { "mach_port_name_t", "name" }, { "user_addr_t", "result_time" } } },
|
||||
{ -95, "mk_timer_arm_leeway", 4, { { "mach_port_name_t", "name" }, { "uint64_t", "mk_timer_flags" }, { "uint64_t", "expire_time" }, { "uint64_t", "mk_leeway" } } },
|
||||
{ -96, "debug_control_port_for_pid", 3, { { "mach_port_name_t", "target_tport" }, { "int", "pid" }, { "mach_port_name_t *", "t" } } },
|
||||
{ -100, "iokit_user_client", 8, { { "void *", "userClientRef" }, { "uint32_t", "index" }, { "void *", "p1" }, { "void *", "p2" }, { "void *", "p3" }, { "void *", "p4" }, { "void *", "p5" }, { "void *", "p6" } } },
|
||||
};
|
||||
|
||||
static const FridaXnuSyscallSignature frida_xnu_bsd_syscalls[] =
|
||||
{
|
||||
{ 1, "exit", 1, { { "int", "rval" } } },
|
||||
{ 2, "fork", 0, {} },
|
||||
{ 3, "read", 3, { { "int", "fd" }, { "user_addr_t", "cbuf" }, { "user_size_t", "nbyte" } } },
|
||||
{ 4, "write", 3, { { "int", "fd" }, { "user_addr_t", "cbuf" }, { "user_size_t", "nbyte" } } },
|
||||
{ 5, "open", 3, { { "char *", "path" }, { "int", "flags" }, { "int", "mode" } } },
|
||||
{ 6, "close", 1, { { "int", "fd" } } },
|
||||
{ 7, "wait4", 4, { { "int", "pid" }, { "user_addr_t", "status" }, { "int", "options" }, { "user_addr_t", "rusage" } } },
|
||||
{ 9, "link", 2, { { "char *", "path" }, { "char *", "link" } } },
|
||||
{ 10, "unlink", 1, { { "char *", "path" } } },
|
||||
{ 12, "chdir", 1, { { "char *", "path" } } },
|
||||
{ 13, "fchdir", 1, { { "int", "fd" } } },
|
||||
{ 14, "mknod", 3, { { "char *", "path" }, { "int", "mode" }, { "int", "dev" } } },
|
||||
{ 15, "chmod", 2, { { "char *", "path" }, { "int", "mode" } } },
|
||||
{ 16, "chown", 3, { { "char *", "path" }, { "int", "uid" }, { "int", "gid" } } },
|
||||
{ 18, "getfsstat", 3, { { "user_addr_t", "buf" }, { "int", "bufsize" }, { "int", "flags" } } },
|
||||
{ 20, "getpid", 0, {} },
|
||||
{ 23, "setuid", 1, { { "uid_t", "uid" } } },
|
||||
{ 24, "getuid", 0, {} },
|
||||
{ 25, "geteuid", 0, {} },
|
||||
{ 26, "ptrace", 4, { { "int", "req" }, { "pid_t", "pid" }, { "caddr_t", "addr" }, { "int", "data" } } },
|
||||
{ 27, "recvmsg", 3, { { "int", "s" }, { "struct msghdr *", "msg" }, { "int", "flags" } } },
|
||||
{ 28, "sendmsg", 3, { { "int", "s" }, { "caddr_t", "msg" }, { "int", "flags" } } },
|
||||
{ 29, "recvfrom", 6, { { "int", "s" }, { "void *", "buf" }, { "size_t", "len" }, { "int", "flags" }, { "struct sockaddr *", "from" }, { "int *", "fromlenaddr" } } },
|
||||
{ 30, "accept", 3, { { "int", "s" }, { "caddr_t", "name" }, { "socklen_t *", "anamelen" } } },
|
||||
{ 31, "getpeername", 3, { { "int", "fdes" }, { "caddr_t", "asa" }, { "socklen_t *", "alen" } } },
|
||||
{ 32, "getsockname", 3, { { "int", "fdes" }, { "caddr_t", "asa" }, { "socklen_t *", "alen" } } },
|
||||
{ 33, "access", 2, { { "char *", "path" }, { "int", "flags" } } },
|
||||
{ 34, "chflags", 2, { { "char *", "path" }, { "int", "flags" } } },
|
||||
{ 35, "fchflags", 2, { { "int", "fd" }, { "int", "flags" } } },
|
||||
{ 36, "sync", 0, {} },
|
||||
{ 37, "kill", 3, { { "int", "pid" }, { "int", "signum" }, { "int", "posix" } } },
|
||||
{ 38, "crossarch_trap", 1, { { "uint32_t", "name" } } },
|
||||
{ 39, "getppid", 0, {} },
|
||||
{ 41, "dup", 1, { { "u_int", "fd" } } },
|
||||
{ 42, "pipe", 0, {} },
|
||||
{ 43, "getegid", 0, {} },
|
||||
{ 46, "sigaction", 3, { { "int", "signum" }, { "struct __sigaction *", "nsa" }, { "struct sigaction *", "osa" } } },
|
||||
{ 47, "getgid", 0, {} },
|
||||
{ 48, "sigprocmask", 3, { { "int", "how" }, { "user_addr_t", "mask" }, { "user_addr_t", "omask" } } },
|
||||
{ 49, "getlogin", 2, { { "char *", "namebuf" }, { "u_int", "namelen" } } },
|
||||
{ 50, "setlogin", 1, { { "char *", "namebuf" } } },
|
||||
{ 51, "acct", 1, { { "char *", "path" } } },
|
||||
{ 52, "sigpending", 1, { { "struct sigvec *", "osv" } } },
|
||||
{ 53, "sigaltstack", 2, { { "struct sigaltstack *", "nss" }, { "struct sigaltstack *", "oss" } } },
|
||||
{ 54, "ioctl", 3, { { "int", "fd" }, { "u_long", "com" }, { "caddr_t", "data" } } },
|
||||
{ 55, "reboot", 2, { { "int", "opt" }, { "char *", "msg" } } },
|
||||
{ 56, "revoke", 1, { { "char *", "path" } } },
|
||||
{ 57, "symlink", 2, { { "char *", "path" }, { "char *", "link" } } },
|
||||
{ 58, "readlink", 3, { { "char *", "path" }, { "char *", "buf" }, { "int", "count" } } },
|
||||
{ 59, "execve", 3, { { "char *", "fname" }, { "char **", "argp" }, { "char **", "envp" } } },
|
||||
{ 60, "umask", 1, { { "int", "newmask" } } },
|
||||
{ 61, "chroot", 1, { { "char *", "path" } } },
|
||||
{ 65, "msync", 3, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "int", "flags" } } },
|
||||
{ 66, "vfork", 0, {} },
|
||||
{ 67, "oslog_coproc_reg", 3, { { "const char *", "uuid" }, { "const char *", "file_path" }, { "size_t", "file_path_len" } } },
|
||||
{ 68, "oslog_coproc", 7, { { "void *", "buff" }, { "uint64_t", "buff_len" }, { "uint32_t", "type" }, { "const char *", "uuid" }, { "uint64_t", "timestamp" }, { "uint32_t", "offset" }, { "uint32_t", "stream_log" } } },
|
||||
{ 73, "munmap", 2, { { "caddr_ut", "addr" }, { "size_ut", "len" } } },
|
||||
{ 74, "mprotect", 3, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "int", "prot" } } },
|
||||
{ 75, "madvise", 3, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "int", "behav" } } },
|
||||
{ 78, "mincore", 3, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "user_addr_t", "vec" } } },
|
||||
{ 79, "getgroups", 2, { { "u_int", "gidsetsize" }, { "gid_t *", "gidset" } } },
|
||||
{ 80, "setgroups", 2, { { "u_int", "gidsetsize" }, { "gid_t *", "gidset" } } },
|
||||
{ 81, "getpgrp", 0, {} },
|
||||
{ 82, "setpgid", 2, { { "int", "pid" }, { "int", "pgid" } } },
|
||||
{ 83, "setitimer", 3, { { "u_int", "which" }, { "struct itimerval *", "itv" }, { "struct itimerval *", "oitv" } } },
|
||||
{ 85, "swapon", 0, {} },
|
||||
{ 86, "getitimer", 2, { { "u_int", "which" }, { "struct itimerval *", "itv" } } },
|
||||
{ 89, "getdtablesize", 0, {} },
|
||||
{ 90, "dup2", 2, { { "u_int", "from" }, { "u_int", "to" } } },
|
||||
{ 92, "fcntl", 3, { { "int", "fd" }, { "int", "cmd" }, { "long", "arg" } } },
|
||||
{ 93, "select", 5, { { "int", "nd" }, { "u_int32_t *", "in" }, { "u_int32_t *", "ou" }, { "u_int32_t *", "ex" }, { "struct timeval *", "tv" } } },
|
||||
{ 95, "fsync", 1, { { "int", "fd" } } },
|
||||
{ 96, "setpriority", 3, { { "int", "which" }, { "id_t", "who" }, { "int", "prio" } } },
|
||||
{ 97, "socket", 3, { { "int", "domain" }, { "int", "type" }, { "int", "protocol" } } },
|
||||
{ 98, "connect", 3, { { "int", "s" }, { "caddr_t", "name" }, { "socklen_t", "namelen" } } },
|
||||
{ 100, "getpriority", 2, { { "int", "which" }, { "id_t", "who" } } },
|
||||
{ 104, "bind", 3, { { "int", "s" }, { "caddr_t", "name" }, { "socklen_t", "namelen" } } },
|
||||
{ 105, "setsockopt", 5, { { "int", "s" }, { "int", "level" }, { "int", "name" }, { "caddr_t", "val" }, { "socklen_t", "valsize" } } },
|
||||
{ 106, "listen", 2, { { "int", "s" }, { "int", "backlog" } } },
|
||||
{ 111, "sigsuspend", 1, { { "sigset_t", "mask" } } },
|
||||
{ 116, "gettimeofday", 3, { { "struct timeval *", "tp" }, { "struct timezone *", "tzp" }, { "uint64_t *", "mach_absolute_time" } } },
|
||||
{ 117, "getrusage", 2, { { "int", "who" }, { "struct rusage *", "rusage" } } },
|
||||
{ 118, "getsockopt", 5, { { "int", "s" }, { "int", "level" }, { "int", "name" }, { "caddr_t", "val" }, { "socklen_t *", "avalsize" } } },
|
||||
{ 120, "readv", 3, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "u_int", "iovcnt" } } },
|
||||
{ 121, "writev", 3, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "u_int", "iovcnt" } } },
|
||||
{ 122, "settimeofday", 2, { { "struct timeval *", "tv" }, { "struct timezone *", "tzp" } } },
|
||||
{ 123, "fchown", 3, { { "int", "fd" }, { "int", "uid" }, { "int", "gid" } } },
|
||||
{ 124, "fchmod", 2, { { "int", "fd" }, { "int", "mode" } } },
|
||||
{ 126, "setreuid", 2, { { "uid_t", "ruid" }, { "uid_t", "euid" } } },
|
||||
{ 127, "setregid", 2, { { "gid_t", "rgid" }, { "gid_t", "egid" } } },
|
||||
{ 128, "rename", 2, { { "char *", "from" }, { "char *", "to" } } },
|
||||
{ 131, "flock", 2, { { "int", "fd" }, { "int", "how" } } },
|
||||
{ 132, "mkfifo", 2, { { "char *", "path" }, { "int", "mode" } } },
|
||||
{ 133, "sendto", 6, { { "int", "s" }, { "caddr_t", "buf" }, { "size_t", "len" }, { "int", "flags" }, { "caddr_t", "to" }, { "socklen_t", "tolen" } } },
|
||||
{ 134, "shutdown", 2, { { "int", "s" }, { "int", "how" } } },
|
||||
{ 135, "socketpair", 4, { { "int", "domain" }, { "int", "type" }, { "int", "protocol" }, { "int *", "rsv" } } },
|
||||
{ 136, "mkdir", 2, { { "char *", "path" }, { "int", "mode" } } },
|
||||
{ 137, "rmdir", 1, { { "char *", "path" } } },
|
||||
{ 138, "utimes", 2, { { "char *", "path" }, { "struct timeval *", "tptr" } } },
|
||||
{ 139, "futimes", 2, { { "int", "fd" }, { "struct timeval *", "tptr" } } },
|
||||
{ 140, "adjtime", 2, { { "struct timeval *", "delta" }, { "struct timeval *", "olddelta" } } },
|
||||
{ 142, "gethostuuid", 2, { { "unsigned char *", "uuid_buf" }, { "const struct timespec *", "timeoutp" } } },
|
||||
{ 147, "setsid", 0, {} },
|
||||
{ 151, "getpgid", 1, { { "pid_t", "pid" } } },
|
||||
{ 152, "setprivexec", 1, { { "int", "flag" } } },
|
||||
{ 153, "pread", 4, { { "int", "fd" }, { "user_addr_t", "buf" }, { "user_size_t", "nbyte" }, { "off_t", "offset" } } },
|
||||
{ 154, "pwrite", 4, { { "int", "fd" }, { "user_addr_t", "buf" }, { "user_size_t", "nbyte" }, { "off_t", "offset" } } },
|
||||
{ 155, "nfssvc", 2, { { "int", "flag" }, { "caddr_t", "argp" } } },
|
||||
{ 157, "statfs", 2, { { "char *", "path" }, { "struct statfs *", "buf" } } },
|
||||
{ 158, "fstatfs", 2, { { "int", "fd" }, { "struct statfs *", "buf" } } },
|
||||
{ 159, "unmount", 2, { { "char *", "path" }, { "int", "flags" } } },
|
||||
{ 161, "getfh", 2, { { "char *", "fname" }, { "fhandle_t *", "fhp" } } },
|
||||
{ 164, "funmount", 2, { { "int", "fd" }, { "int", "flags" } } },
|
||||
{ 165, "quotactl", 4, { { "const char *", "path" }, { "int", "cmd" }, { "int", "uid" }, { "caddr_t", "arg" } } },
|
||||
{ 167, "mount", 4, { { "char *", "type" }, { "char *", "path" }, { "int", "flags" }, { "caddr_t", "data" } } },
|
||||
{ 169, "csops", 4, { { "pid_t", "pid" }, { "uint32_t", "ops" }, { "user_addr_t", "useraddr" }, { "user_size_t", "usersize" } } },
|
||||
{ 170, "csops_audittoken", 5, { { "pid_t", "pid" }, { "uint32_t", "ops" }, { "user_addr_t", "useraddr" }, { "user_size_t", "usersize" }, { "user_addr_t", "uaudittoken" } } },
|
||||
{ 173, "waitid", 4, { { "idtype_t", "idtype" }, { "id_t", "id" }, { "siginfo_t *", "infop" }, { "int", "options" } } },
|
||||
{ 177, "kdebug_typefilter", 2, { { "void **", "addr" }, { "size_t *", "size" } } },
|
||||
{ 178, "kdebug_trace_string", 3, { { "uint32_t", "debugid" }, { "uint64_t", "str_id" }, { "const char *", "str" } } },
|
||||
{ 179, "kdebug_trace64", 5, { { "uint32_t", "code" }, { "uint64_t", "arg1" }, { "uint64_t", "arg2" }, { "uint64_t", "arg3" }, { "uint64_t", "arg4" } } },
|
||||
{ 180, "kdebug_trace", 5, { { "uint32_t", "code" }, { "u_long", "arg1" }, { "u_long", "arg2" }, { "u_long", "arg3" }, { "u_long", "arg4" } } },
|
||||
{ 181, "setgid", 1, { { "gid_t", "gid" } } },
|
||||
{ 182, "setegid", 1, { { "gid_t", "egid" } } },
|
||||
{ 183, "seteuid", 1, { { "uid_t", "euid" } } },
|
||||
{ 184, "sigreturn", 3, { { "struct ucontext *", "uctx" }, { "int", "infostyle" }, { "user_addr_t", "token" } } },
|
||||
{ 185, "panic_with_data", 5, { { "uuid_t", "uuid" }, { "void *", "addr" }, { "uint32_t", "len" }, { "uint32_t", "flags" }, { "const char *", "msg" } } },
|
||||
{ 186, "thread_selfcounts", 3, { { "uint32_t", "kind" }, { "user_addr_t", "buf" }, { "user_size_t", "size" } } },
|
||||
{ 187, "fdatasync", 1, { { "int", "fd" } } },
|
||||
{ 188, "stat", 2, { { "char *", "path" }, { "user_addr_t", "ub" } } },
|
||||
{ 189, "fstat", 2, { { "int", "fd" }, { "user_addr_t", "ub" } } },
|
||||
{ 190, "lstat", 2, { { "char *", "path" }, { "user_addr_t", "ub" } } },
|
||||
{ 191, "pathconf", 2, { { "char *", "path" }, { "int", "name" } } },
|
||||
{ 192, "fpathconf", 2, { { "int", "fd" }, { "int", "name" } } },
|
||||
{ 194, "getrlimit", 2, { { "u_int", "which" }, { "struct rlimit *", "rlp" } } },
|
||||
{ 195, "setrlimit", 2, { { "u_int", "which" }, { "struct rlimit *", "rlp" } } },
|
||||
{ 196, "getdirentries", 4, { { "int", "fd" }, { "char *", "buf" }, { "u_int", "count" }, { "long *", "basep" } } },
|
||||
{ 197, "mmap", 6, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "int", "prot" }, { "int", "flags" }, { "int", "fd" }, { "off_t", "pos" } } },
|
||||
{ 199, "lseek", 3, { { "int", "fd" }, { "off_t", "offset" }, { "int", "whence" } } },
|
||||
{ 200, "truncate", 2, { { "char *", "path" }, { "off_t", "length" } } },
|
||||
{ 201, "ftruncate", 2, { { "int", "fd" }, { "off_t", "length" } } },
|
||||
{ 202, "sysctl", 6, { { "int *", "name" }, { "u_int", "namelen" }, { "void *", "old" }, { "size_t *", "oldlenp" }, { "void *", "new" }, { "size_t", "newlen" } } },
|
||||
{ 203, "mlock", 2, { { "caddr_ut", "addr" }, { "size_ut", "len" } } },
|
||||
{ 204, "munlock", 2, { { "caddr_ut", "addr" }, { "size_ut", "len" } } },
|
||||
{ 205, "undelete", 1, { { "char *", "path" } } },
|
||||
{ 216, "open_dprotected_np", 5, { { "char *", "path" }, { "int", "flags" }, { "int", "class" }, { "int", "dpflags" }, { "int", "mode" } } },
|
||||
{ 217, "fsgetpath_ext", 5, { { "user_addr_t", "buf" }, { "size_t", "bufsize" }, { "user_addr_t", "fsid" }, { "uint64_t", "objid" }, { "uint32_t", "options" } } },
|
||||
{ 218, "openat_dprotected_np", 7, { { "int", "fd" }, { "char *", "path" }, { "int", "flags" }, { "int", "class" }, { "int", "dpflags" }, { "int", "mode" }, { "int", "authfd" } } },
|
||||
{ 220, "getattrlist", 5, { { "const char *", "path" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "u_long", "options" } } },
|
||||
{ 221, "setattrlist", 5, { { "const char *", "path" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "u_long", "options" } } },
|
||||
{ 222, "getdirentriesattr", 8, { { "int", "fd" }, { "struct attrlist *", "alist" }, { "void *", "buffer" }, { "size_t", "buffersize" }, { "u_long *", "count" }, { "u_long *", "basep" }, { "u_long *", "newstate" }, { "u_long", "options" } } },
|
||||
{ 223, "exchangedata", 3, { { "const char *", "path1" }, { "const char *", "path2" }, { "u_long", "options" } } },
|
||||
{ 225, "searchfs", 6, { { "const char *", "path" }, { "struct fssearchblock *", "searchblock" }, { "uint32_t *", "nummatches" }, { "uint32_t", "scriptcode" }, { "uint32_t", "options" }, { "struct searchstate *", "state" } } },
|
||||
{ 226, "delete", 1, { { "char *", "path" } } },
|
||||
{ 227, "copyfile", 4, { { "char *", "from" }, { "char *", "to" }, { "int", "mode" }, { "int", "flags" } } },
|
||||
{ 228, "fgetattrlist", 5, { { "int", "fd" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "u_long", "options" } } },
|
||||
{ 229, "fsetattrlist", 5, { { "int", "fd" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "u_long", "options" } } },
|
||||
{ 230, "poll", 3, { { "struct pollfd *", "fds" }, { "u_int", "nfds" }, { "int", "timeout" } } },
|
||||
{ 234, "getxattr", 6, { { "char *", "path" }, { "char *", "attrname" }, { "user_addr_t", "value" }, { "size_t", "size" }, { "uint32_t", "position" }, { "int", "options" } } },
|
||||
{ 235, "fgetxattr", 6, { { "int", "fd" }, { "char *", "attrname" }, { "user_addr_t", "value" }, { "size_t", "size" }, { "uint32_t", "position" }, { "int", "options" } } },
|
||||
{ 236, "setxattr", 6, { { "char *", "path" }, { "char *", "attrname" }, { "user_addr_t", "value" }, { "size_t", "size" }, { "uint32_t", "position" }, { "int", "options" } } },
|
||||
{ 237, "fsetxattr", 6, { { "int", "fd" }, { "char *", "attrname" }, { "user_addr_t", "value" }, { "size_t", "size" }, { "uint32_t", "position" }, { "int", "options" } } },
|
||||
{ 238, "removexattr", 3, { { "char *", "path" }, { "char *", "attrname" }, { "int", "options" } } },
|
||||
{ 239, "fremovexattr", 3, { { "int", "fd" }, { "char *", "attrname" }, { "int", "options" } } },
|
||||
{ 240, "listxattr", 4, { { "char *", "path" }, { "user_addr_t", "namebuf" }, { "size_t", "bufsize" }, { "int", "options" } } },
|
||||
{ 241, "flistxattr", 4, { { "int", "fd" }, { "user_addr_t", "namebuf" }, { "size_t", "bufsize" }, { "int", "options" } } },
|
||||
{ 242, "fsctl", 4, { { "const char *", "path" }, { "u_long", "cmd" }, { "caddr_t", "data" }, { "u_int", "options" } } },
|
||||
{ 243, "initgroups", 3, { { "u_int", "gidsetsize" }, { "gid_t *", "gidset" }, { "int", "gmuid" } } },
|
||||
{ 244, "posix_spawn", 5, { { "pid_t *", "pid" }, { "const char *", "path" }, { "const struct _posix_spawn_args_desc *", "adesc" }, { "char **", "argv" }, { "char **", "envp" } } },
|
||||
{ 245, "ffsctl", 4, { { "int", "fd" }, { "u_long", "cmd" }, { "caddr_t", "data" }, { "u_int", "options" } } },
|
||||
{ 248, "fhopen", 2, { { "const struct fhandle *", "u_fhp" }, { "int", "flags" } } },
|
||||
{ 250, "minherit", 3, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "int", "inherit" } } },
|
||||
{ 251, "semsys", 5, { { "u_int", "which" }, { "int", "a2" }, { "int", "a3" }, { "int", "a4" }, { "int", "a5" } } },
|
||||
{ 252, "msgsys", 5, { { "u_int", "which" }, { "int", "a2" }, { "int", "a3" }, { "int", "a4" }, { "int", "a5" } } },
|
||||
{ 253, "shmsys", 4, { { "u_int", "which" }, { "int", "a2" }, { "int", "a3" }, { "int", "a4" } } },
|
||||
{ 254, "semctl", 4, { { "int", "semid" }, { "int", "semnum" }, { "int", "cmd" }, { "semun_t", "arg" } } },
|
||||
{ 255, "semget", 3, { { "key_t", "key" }, { "int", "nsems" }, { "int", "semflg" } } },
|
||||
{ 256, "semop", 3, { { "int", "semid" }, { "struct sembuf *", "sops" }, { "int", "nsops" } } },
|
||||
{ 258, "msgctl", 3, { { "int", "msqid" }, { "int", "cmd" }, { "struct msqid_ds *", "buf" } } },
|
||||
{ 259, "msgget", 2, { { "key_t", "key" }, { "int", "msgflg" } } },
|
||||
{ 260, "msgsnd", 4, { { "int", "msqid" }, { "void *", "msgp" }, { "size_t", "msgsz" }, { "int", "msgflg" } } },
|
||||
{ 261, "msgrcv", 5, { { "int", "msqid" }, { "void *", "msgp" }, { "size_t", "msgsz" }, { "long", "msgtyp" }, { "int", "msgflg" } } },
|
||||
{ 262, "shmat", 3, { { "int", "shmid" }, { "void *", "shmaddr" }, { "int", "shmflg" } } },
|
||||
{ 263, "shmctl", 3, { { "int", "shmid" }, { "int", "cmd" }, { "struct shmid_ds *", "buf" } } },
|
||||
{ 264, "shmdt", 1, { { "void *", "shmaddr" } } },
|
||||
{ 265, "shmget", 3, { { "key_t", "key" }, { "size_t", "size" }, { "int", "shmflg" } } },
|
||||
{ 266, "shm_open", 3, { { "const char *", "name" }, { "int", "oflag" }, { "int", "mode" } } },
|
||||
{ 267, "shm_unlink", 1, { { "const char *", "name" } } },
|
||||
{ 268, "sem_open", 4, { { "const char *", "name" }, { "int", "oflag" }, { "int", "mode" }, { "int", "value" } } },
|
||||
{ 269, "sem_close", 1, { { "sem_t *", "sem" } } },
|
||||
{ 270, "sem_unlink", 1, { { "const char *", "name" } } },
|
||||
{ 271, "sem_wait", 1, { { "sem_t *", "sem" } } },
|
||||
{ 272, "sem_trywait", 1, { { "sem_t *", "sem" } } },
|
||||
{ 273, "sem_post", 1, { { "sem_t *", "sem" } } },
|
||||
{ 274, "sysctlbyname", 6, { { "const char *", "name" }, { "size_t", "namelen" }, { "void *", "old" }, { "size_t *", "oldlenp" }, { "void *", "new" }, { "size_t", "newlen" } } },
|
||||
{ 277, "open_extended", 6, { { "char *", "path" }, { "int", "flags" }, { "uid_t", "uid" }, { "gid_t", "gid" }, { "int", "mode" }, { "user_addr_t", "xsecurity" } } },
|
||||
{ 278, "umask_extended", 2, { { "int", "newmask" }, { "user_addr_t", "xsecurity" } } },
|
||||
{ 279, "stat_extended", 4, { { "char *", "path" }, { "user_addr_t", "ub" }, { "user_addr_t", "xsecurity" }, { "user_addr_t", "xsecurity_size" } } },
|
||||
{ 280, "lstat_extended", 4, { { "char *", "path" }, { "user_addr_t", "ub" }, { "user_addr_t", "xsecurity" }, { "user_addr_t", "xsecurity_size" } } },
|
||||
{ 281, "fstat_extended", 4, { { "int", "fd" }, { "user_addr_t", "ub" }, { "user_addr_t", "xsecurity" }, { "user_addr_t", "xsecurity_size" } } },
|
||||
{ 282, "chmod_extended", 5, { { "char *", "path" }, { "uid_t", "uid" }, { "gid_t", "gid" }, { "int", "mode" }, { "user_addr_t", "xsecurity" } } },
|
||||
{ 283, "fchmod_extended", 5, { { "int", "fd" }, { "uid_t", "uid" }, { "gid_t", "gid" }, { "int", "mode" }, { "user_addr_t", "xsecurity" } } },
|
||||
{ 284, "access_extended", 4, { { "user_addr_t", "entries" }, { "size_t", "size" }, { "user_addr_t", "results" }, { "uid_t", "uid" } } },
|
||||
{ 285, "settid", 2, { { "uid_t", "uid" }, { "gid_t", "gid" } } },
|
||||
{ 286, "gettid", 2, { { "uid_t *", "uidp" }, { "gid_t *", "gidp" } } },
|
||||
{ 287, "setsgroups", 2, { { "int", "setlen" }, { "user_addr_t", "guidset" } } },
|
||||
{ 288, "getsgroups", 2, { { "user_addr_t", "setlen" }, { "user_addr_t", "guidset" } } },
|
||||
{ 289, "setwgroups", 2, { { "int", "setlen" }, { "user_addr_t", "guidset" } } },
|
||||
{ 290, "getwgroups", 2, { { "user_addr_t", "setlen" }, { "user_addr_t", "guidset" } } },
|
||||
{ 291, "mkfifo_extended", 5, { { "char *", "path" }, { "uid_t", "uid" }, { "gid_t", "gid" }, { "int", "mode" }, { "user_addr_t", "xsecurity" } } },
|
||||
{ 292, "mkdir_extended", 5, { { "char *", "path" }, { "uid_t", "uid" }, { "gid_t", "gid" }, { "int", "mode" }, { "user_addr_t", "xsecurity" } } },
|
||||
{ 293, "identitysvc", 2, { { "int", "opcode" }, { "user_addr_t", "message" } } },
|
||||
{ 294, "shared_region_check_np", 1, { { "uint64_t *", "start_address" } } },
|
||||
{ 296, "vm_pressure_monitor", 3, { { "int", "wait_for_pressure" }, { "int", "nsecs_monitored" }, { "uint32_t *", "pages_reclaimed" } } },
|
||||
{ 297, "psynch_rw_longrdlock", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
|
||||
{ 298, "psynch_rw_yieldwrlock", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
|
||||
{ 299, "psynch_rw_downgrade", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
|
||||
{ 300, "psynch_rw_upgrade", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
|
||||
{ 301, "psynch_mutexwait", 5, { { "user_addr_t", "mutex" }, { "uint32_t", "mgen" }, { "uint32_t", "ugen" }, { "uint64_t", "tid" }, { "uint32_t", "flags" } } },
|
||||
{ 302, "psynch_mutexdrop", 5, { { "user_addr_t", "mutex" }, { "uint32_t", "mgen" }, { "uint32_t", "ugen" }, { "uint64_t", "tid" }, { "uint32_t", "flags" } } },
|
||||
{ 303, "psynch_cvbroad", 7, { { "user_addr_t", "cv" }, { "uint64_t", "cvlsgen" }, { "uint64_t", "cvudgen" }, { "uint32_t", "flags" }, { "user_addr_t", "mutex" }, { "uint64_t", "mugen" }, { "uint64_t", "tid" } } },
|
||||
{ 304, "psynch_cvsignal", 8, { { "user_addr_t", "cv" }, { "uint64_t", "cvlsgen" }, { "uint32_t", "cvugen" }, { "int", "thread_port" }, { "user_addr_t", "mutex" }, { "uint64_t", "mugen" }, { "uint64_t", "tid" }, { "uint32_t", "flags" } } },
|
||||
{ 305, "psynch_cvwait", 8, { { "user_addr_t", "cv" }, { "uint64_t", "cvlsgen" }, { "uint32_t", "cvugen" }, { "user_addr_t", "mutex" }, { "uint64_t", "mugen" }, { "uint32_t", "flags" }, { "int64_t", "sec" }, { "uint32_t", "nsec" } } },
|
||||
{ 306, "psynch_rw_rdlock", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
|
||||
{ 307, "psynch_rw_wrlock", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
|
||||
{ 308, "psynch_rw_unlock", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
|
||||
{ 309, "psynch_rw_unlock2", 5, { { "user_addr_t", "rwlock" }, { "uint32_t", "lgenval" }, { "uint32_t", "ugenval" }, { "uint32_t", "rw_wc" }, { "int", "flags" } } },
|
||||
{ 310, "getsid", 1, { { "pid_t", "pid" } } },
|
||||
{ 311, "settid_with_pid", 2, { { "pid_t", "pid" }, { "int", "assume" } } },
|
||||
{ 312, "psynch_cvclrprepost", 7, { { "user_addr_t", "cv" }, { "uint32_t", "cvgen" }, { "uint32_t", "cvugen" }, { "uint32_t", "cvsgen" }, { "uint32_t", "prepocnt" }, { "uint32_t", "preposeq" }, { "uint32_t", "flags" } } },
|
||||
{ 313, "aio_fsync", 2, { { "int", "op" }, { "user_addr_t", "aiocbp" } } },
|
||||
{ 314, "aio_return", 1, { { "user_addr_t", "aiocbp" } } },
|
||||
{ 315, "aio_suspend", 3, { { "user_addr_t", "aiocblist" }, { "int", "nent" }, { "user_addr_t", "timeoutp" } } },
|
||||
{ 316, "aio_cancel", 2, { { "int", "fd" }, { "user_addr_t", "aiocbp" } } },
|
||||
{ 317, "aio_error", 1, { { "user_addr_t", "aiocbp" } } },
|
||||
{ 318, "aio_read", 1, { { "user_addr_t", "aiocbp" } } },
|
||||
{ 319, "aio_write", 1, { { "user_addr_t", "aiocbp" } } },
|
||||
{ 320, "lio_listio", 4, { { "int", "mode" }, { "user_addr_t", "aiocblist" }, { "int", "nent" }, { "user_addr_t", "sigp" } } },
|
||||
{ 322, "iopolicysys", 2, { { "int", "cmd" }, { "void *", "arg" } } },
|
||||
{ 323, "process_policy", 7, { { "int", "scope" }, { "int", "action" }, { "int", "policy" }, { "int", "policy_subtype" }, { "user_addr_t", "attrp" }, { "pid_t", "target_pid" }, { "uint64_t", "target_threadid" } } },
|
||||
{ 324, "mlockall", 1, { { "int", "how" } } },
|
||||
{ 325, "munlockall", 1, { { "int", "how" } } },
|
||||
{ 327, "issetugid", 0, {} },
|
||||
{ 328, "pthread_kill", 2, { { "int", "thread_port" }, { "int", "sig" } } },
|
||||
{ 329, "pthread_sigmask", 3, { { "int", "how" }, { "user_addr_t", "set" }, { "user_addr_t", "oset" } } },
|
||||
{ 330, "sigwait", 2, { { "user_addr_t", "set" }, { "user_addr_t", "sig" } } },
|
||||
{ 331, "disable_threadsignal", 1, { { "int", "value" } } },
|
||||
{ 332, "pthread_markcancel", 1, { { "int", "thread_port" } } },
|
||||
{ 333, "pthread_canceled", 1, { { "int", "action" } } },
|
||||
{ 334, "semwait_signal", 6, { { "int", "cond_sem" }, { "int", "mutex_sem" }, { "int", "timeout" }, { "int", "relative" }, { "int64_t", "tv_sec" }, { "int32_t", "tv_nsec" } } },
|
||||
{ 336, "proc_info", 6, { { "int32_t", "callnum" }, { "int32_t", "pid" }, { "uint32_t", "flavor" }, { "uint64_t", "arg" }, { "user_addr_t", "buffer" }, { "int32_t", "buffersize" } } },
|
||||
{ 337, "sendfile", 6, { { "int", "fd" }, { "int", "s" }, { "off_t", "offset" }, { "off_t *", "nbytes" }, { "struct sf_hdtr *", "hdtr" }, { "int", "flags" } } },
|
||||
{ 338, "stat64", 2, { { "char *", "path" }, { "user_addr_t", "ub" } } },
|
||||
{ 339, "fstat64", 2, { { "int", "fd" }, { "user_addr_t", "ub" } } },
|
||||
{ 340, "lstat64", 2, { { "char *", "path" }, { "user_addr_t", "ub" } } },
|
||||
{ 341, "stat64_extended", 4, { { "char *", "path" }, { "user_addr_t", "ub" }, { "user_addr_t", "xsecurity" }, { "user_addr_t", "xsecurity_size" } } },
|
||||
{ 342, "lstat64_extended", 4, { { "char *", "path" }, { "user_addr_t", "ub" }, { "user_addr_t", "xsecurity" }, { "user_addr_t", "xsecurity_size" } } },
|
||||
{ 343, "fstat64_extended", 4, { { "int", "fd" }, { "user_addr_t", "ub" }, { "user_addr_t", "xsecurity" }, { "user_addr_t", "xsecurity_size" } } },
|
||||
{ 344, "getdirentries64", 4, { { "int", "fd" }, { "void *", "buf" }, { "user_size_t", "bufsize" }, { "off_t *", "position" } } },
|
||||
{ 345, "statfs64", 2, { { "char *", "path" }, { "struct statfs64 *", "buf" } } },
|
||||
{ 346, "fstatfs64", 2, { { "int", "fd" }, { "struct statfs64 *", "buf" } } },
|
||||
{ 347, "getfsstat64", 3, { { "user_addr_t", "buf" }, { "int", "bufsize" }, { "int", "flags" } } },
|
||||
{ 348, "pthread_chdir", 1, { { "char *", "path" } } },
|
||||
{ 349, "pthread_fchdir", 1, { { "int", "fd" } } },
|
||||
{ 350, "audit", 2, { { "void *", "record" }, { "int", "length" } } },
|
||||
{ 351, "auditon", 3, { { "int", "cmd" }, { "void *", "data" }, { "int", "length" } } },
|
||||
{ 353, "getauid", 1, { { "au_id_t *", "auid" } } },
|
||||
{ 354, "setauid", 1, { { "au_id_t *", "auid" } } },
|
||||
{ 357, "getaudit_addr", 2, { { "struct auditinfo_addr *", "auditinfo_addr" }, { "int", "length" } } },
|
||||
{ 358, "setaudit_addr", 2, { { "struct auditinfo_addr *", "auditinfo_addr" }, { "int", "length" } } },
|
||||
{ 359, "auditctl", 1, { { "char *", "path" } } },
|
||||
{ 360, "bsdthread_create", 5, { { "user_addr_t", "func" }, { "user_addr_t", "func_arg" }, { "user_addr_t", "stack" }, { "user_addr_t", "pthread" }, { "uint32_t", "flags" } } },
|
||||
{ 361, "bsdthread_terminate", 4, { { "user_addr_t", "stackaddr" }, { "size_t", "freesize" }, { "uint32_t", "port" }, { "user_addr_t", "sema_or_ulock" } } },
|
||||
{ 362, "kqueue", 0, {} },
|
||||
{ 363, "kevent", 6, { { "int", "fd" }, { "const struct kevent *", "changelist" }, { "int", "nchanges" }, { "struct kevent *", "eventlist" }, { "int", "nevents" }, { "const struct timespec *", "timeout" } } },
|
||||
{ 364, "lchown", 3, { { "char *", "path" }, { "uid_t", "owner" }, { "gid_t", "group" } } },
|
||||
{ 366, "bsdthread_register", 7, { { "user_addr_t", "threadstart" }, { "user_addr_t", "wqthread" }, { "uint32_t", "flags" }, { "user_addr_t", "stack_addr_hint" }, { "user_addr_t", "targetconc_ptr" }, { "uint32_t", "dispatchqueue_offset" }, { "uint32_t", "tsd_offset" } } },
|
||||
{ 367, "workq_open", 0, {} },
|
||||
{ 368, "workq_kernreturn", 4, { { "int", "options" }, { "user_addr_t", "item" }, { "int", "affinity" }, { "int", "prio" } } },
|
||||
{ 369, "kevent64", 7, { { "int", "fd" }, { "const struct kevent64_s *", "changelist" }, { "int", "nchanges" }, { "struct kevent64_s *", "eventlist" }, { "int", "nevents" }, { "unsigned int", "flags" }, { "const struct timespec *", "timeout" } } },
|
||||
{ 372, "thread_selfid", 0, {} },
|
||||
{ 373, "ledger", 4, { { "int", "cmd" }, { "caddr_t", "arg1" }, { "caddr_t", "arg2" }, { "caddr_t", "arg3" } } },
|
||||
{ 374, "kevent_qos", 8, { { "int", "fd" }, { "const struct kevent_qos_s *", "changelist" }, { "int", "nchanges" }, { "struct kevent_qos_s *", "eventlist" }, { "int", "nevents" }, { "void *", "data_out" }, { "size_t *", "data_available" }, { "unsigned int", "flags" } } },
|
||||
{ 375, "kevent_id", 8, { { "uint64_t", "id" }, { "const struct kevent_qos_s *", "changelist" }, { "int", "nchanges" }, { "struct kevent_qos_s *", "eventlist" }, { "int", "nevents" }, { "void *", "data_out" }, { "size_t *", "data_available" }, { "unsigned int", "flags" } } },
|
||||
{ 380, "mac_execve", 4, { { "char *", "fname" }, { "char **", "argp" }, { "char **", "envp" }, { "struct mac *", "mac_p" } } },
|
||||
{ 381, "mac_syscall", 3, { { "char *", "policy" }, { "int", "call" }, { "user_addr_t", "arg" } } },
|
||||
{ 382, "mac_get_file", 2, { { "char *", "path_p" }, { "struct mac *", "mac_p" } } },
|
||||
{ 383, "mac_set_file", 2, { { "char *", "path_p" }, { "struct mac *", "mac_p" } } },
|
||||
{ 384, "mac_get_link", 2, { { "char *", "path_p" }, { "struct mac *", "mac_p" } } },
|
||||
{ 385, "mac_set_link", 2, { { "char *", "path_p" }, { "struct mac *", "mac_p" } } },
|
||||
{ 386, "mac_get_proc", 1, { { "struct mac *", "mac_p" } } },
|
||||
{ 387, "mac_set_proc", 1, { { "struct mac *", "mac_p" } } },
|
||||
{ 388, "mac_get_fd", 2, { { "int", "fd" }, { "struct mac *", "mac_p" } } },
|
||||
{ 389, "mac_set_fd", 2, { { "int", "fd" }, { "struct mac *", "mac_p" } } },
|
||||
{ 390, "mac_get_pid", 2, { { "pid_t", "pid" }, { "struct mac *", "mac_p" } } },
|
||||
{ 394, "pselect", 6, { { "int", "nd" }, { "u_int32_t *", "in" }, { "u_int32_t *", "ou" }, { "u_int32_t *", "ex" }, { "const struct timespec *", "ts" }, { "const struct sigset_t *", "mask" } } },
|
||||
{ 395, "pselect_nocancel", 6, { { "int", "nd" }, { "u_int32_t *", "in" }, { "u_int32_t *", "ou" }, { "u_int32_t *", "ex" }, { "const struct timespec *", "ts" }, { "const struct sigset_t *", "mask" } } },
|
||||
{ 396, "read_nocancel", 3, { { "int", "fd" }, { "user_addr_t", "cbuf" }, { "user_size_t", "nbyte" } } },
|
||||
{ 397, "write_nocancel", 3, { { "int", "fd" }, { "user_addr_t", "cbuf" }, { "user_size_t", "nbyte" } } },
|
||||
{ 398, "open_nocancel", 3, { { "char *", "path" }, { "int", "flags" }, { "int", "mode" } } },
|
||||
{ 399, "close_nocancel", 1, { { "int", "fd" } } },
|
||||
{ 400, "wait4_nocancel", 4, { { "int", "pid" }, { "user_addr_t", "status" }, { "int", "options" }, { "user_addr_t", "rusage" } } },
|
||||
{ 401, "recvmsg_nocancel", 3, { { "int", "s" }, { "struct msghdr *", "msg" }, { "int", "flags" } } },
|
||||
{ 402, "sendmsg_nocancel", 3, { { "int", "s" }, { "caddr_t", "msg" }, { "int", "flags" } } },
|
||||
{ 403, "recvfrom_nocancel", 6, { { "int", "s" }, { "void *", "buf" }, { "size_t", "len" }, { "int", "flags" }, { "struct sockaddr *", "from" }, { "int *", "fromlenaddr" } } },
|
||||
{ 404, "accept_nocancel", 3, { { "int", "s" }, { "caddr_t", "name" }, { "socklen_t *", "anamelen" } } },
|
||||
{ 405, "msync_nocancel", 3, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "int", "flags" } } },
|
||||
{ 406, "fcntl_nocancel", 3, { { "int", "fd" }, { "int", "cmd" }, { "long", "arg" } } },
|
||||
{ 407, "select_nocancel", 5, { { "int", "nd" }, { "u_int32_t *", "in" }, { "u_int32_t *", "ou" }, { "u_int32_t *", "ex" }, { "struct timeval *", "tv" } } },
|
||||
{ 408, "fsync_nocancel", 1, { { "int", "fd" } } },
|
||||
{ 409, "connect_nocancel", 3, { { "int", "s" }, { "caddr_t", "name" }, { "socklen_t", "namelen" } } },
|
||||
{ 410, "sigsuspend_nocancel", 1, { { "sigset_t", "mask" } } },
|
||||
{ 411, "readv_nocancel", 3, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "u_int", "iovcnt" } } },
|
||||
{ 412, "writev_nocancel", 3, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "u_int", "iovcnt" } } },
|
||||
{ 413, "sendto_nocancel", 6, { { "int", "s" }, { "caddr_t", "buf" }, { "size_t", "len" }, { "int", "flags" }, { "caddr_t", "to" }, { "socklen_t", "tolen" } } },
|
||||
{ 414, "pread_nocancel", 4, { { "int", "fd" }, { "user_addr_t", "buf" }, { "user_size_t", "nbyte" }, { "off_t", "offset" } } },
|
||||
{ 415, "pwrite_nocancel", 4, { { "int", "fd" }, { "user_addr_t", "buf" }, { "user_size_t", "nbyte" }, { "off_t", "offset" } } },
|
||||
{ 416, "waitid_nocancel", 4, { { "idtype_t", "idtype" }, { "id_t", "id" }, { "siginfo_t *", "infop" }, { "int", "options" } } },
|
||||
{ 417, "poll_nocancel", 3, { { "struct pollfd *", "fds" }, { "u_int", "nfds" }, { "int", "timeout" } } },
|
||||
{ 418, "msgsnd_nocancel", 4, { { "int", "msqid" }, { "void *", "msgp" }, { "size_t", "msgsz" }, { "int", "msgflg" } } },
|
||||
{ 419, "msgrcv_nocancel", 5, { { "int", "msqid" }, { "void *", "msgp" }, { "size_t", "msgsz" }, { "long", "msgtyp" }, { "int", "msgflg" } } },
|
||||
{ 420, "sem_wait_nocancel", 1, { { "sem_t *", "sem" } } },
|
||||
{ 421, "aio_suspend_nocancel", 3, { { "user_addr_t", "aiocblist" }, { "int", "nent" }, { "user_addr_t", "timeoutp" } } },
|
||||
{ 422, "sigwait_nocancel", 2, { { "user_addr_t", "set" }, { "user_addr_t", "sig" } } },
|
||||
{ 423, "semwait_signal_nocancel", 6, { { "int", "cond_sem" }, { "int", "mutex_sem" }, { "int", "timeout" }, { "int", "relative" }, { "int64_t", "tv_sec" }, { "int32_t", "tv_nsec" } } },
|
||||
{ 424, "mac_mount", 5, { { "char *", "type" }, { "char *", "path" }, { "int", "flags" }, { "caddr_t", "data" }, { "struct mac *", "mac_p" } } },
|
||||
{ 425, "mac_get_mount", 2, { { "char *", "path" }, { "struct mac *", "mac_p" } } },
|
||||
{ 426, "mac_getfsstat", 5, { { "user_addr_t", "buf" }, { "int", "bufsize" }, { "user_addr_t", "mac" }, { "int", "macsize" }, { "int", "flags" } } },
|
||||
{ 427, "fsgetpath", 4, { { "user_addr_t", "buf" }, { "size_t", "bufsize" }, { "user_addr_t", "fsid" }, { "uint64_t", "objid" } } },
|
||||
{ 428, "audit_session_self", 0, {} },
|
||||
{ 429, "audit_session_join", 1, { { "mach_port_name_t", "port" } } },
|
||||
{ 430, "fileport_makeport", 2, { { "int", "fd" }, { "user_addr_t", "portnamep" } } },
|
||||
{ 431, "fileport_makefd", 1, { { "mach_port_name_t", "port" } } },
|
||||
{ 432, "audit_session_port", 2, { { "au_asid_t", "asid" }, { "user_addr_t", "portnamep" } } },
|
||||
{ 433, "pid_suspend", 1, { { "int", "pid" } } },
|
||||
{ 434, "pid_resume", 1, { { "int", "pid" } } },
|
||||
{ 435, "pid_hibernate", 1, { { "int", "pid" } } },
|
||||
{ 436, "pid_shutdown_sockets", 2, { { "int", "pid" }, { "int", "level" } } },
|
||||
{ 439, "kas_info", 3, { { "int", "selector" }, { "void *", "value" }, { "size_t *", "size" } } },
|
||||
{ 440, "memorystatus_control", 5, { { "uint32_t", "command" }, { "int32_t", "pid" }, { "uint32_t", "flags" }, { "user_addr_t", "buffer" }, { "size_t", "buffersize" } } },
|
||||
{ 441, "guarded_open_np", 5, { { "char *", "path" }, { "const guardid_t *", "guard" }, { "u_int", "guardflags" }, { "int", "flags" }, { "int", "mode" } } },
|
||||
{ 442, "guarded_close_np", 2, { { "int", "fd" }, { "const guardid_t *", "guard" } } },
|
||||
{ 443, "guarded_kqueue_np", 2, { { "const guardid_t *", "guard" }, { "u_int", "guardflags" } } },
|
||||
{ 444, "change_fdguard_np", 6, { { "int", "fd" }, { "const guardid_t *", "guard" }, { "u_int", "guardflags" }, { "const guardid_t *", "nguard" }, { "u_int", "nguardflags" }, { "int *", "fdflagsp" } } },
|
||||
{ 445, "usrctl", 1, { { "uint32_t", "flags" } } },
|
||||
{ 446, "proc_rlimit_control", 3, { { "pid_t", "pid" }, { "int", "flavor" }, { "void *", "arg" } } },
|
||||
{ 447, "connectx", 8, { { "int", "socket" }, { "const sa_endpoints_t *", "endpoints" }, { "sae_associd_t", "associd" }, { "unsigned int", "flags" }, { "const struct iovec *", "iov" }, { "unsigned int", "iovcnt" }, { "size_t *", "len" }, { "sae_connid_t *", "connid" } } },
|
||||
{ 448, "disconnectx", 3, { { "int", "s" }, { "sae_associd_t", "aid" }, { "sae_connid_t", "cid" } } },
|
||||
{ 449, "peeloff", 2, { { "int", "s" }, { "sae_associd_t", "aid" } } },
|
||||
{ 450, "socket_delegate", 4, { { "int", "domain" }, { "int", "type" }, { "int", "protocol" }, { "pid_t", "epid" } } },
|
||||
{ 451, "telemetry", 6, { { "uint64_t", "cmd" }, { "uint64_t", "deadline" }, { "uint64_t", "interval" }, { "uint64_t", "leeway" }, { "uint64_t", "arg4" }, { "uint64_t", "arg5" } } },
|
||||
{ 452, "proc_uuid_policy", 4, { { "uint32_t", "operation" }, { "uuid_t", "uuid" }, { "size_t", "uuidlen" }, { "uint32_t", "flags" } } },
|
||||
{ 453, "memorystatus_get_level", 1, { { "user_addr_t", "level" } } },
|
||||
{ 454, "system_override", 2, { { "uint64_t", "timeout" }, { "uint64_t", "flags" } } },
|
||||
{ 455, "vfs_purge", 0, {} },
|
||||
{ 456, "sfi_ctl", 4, { { "uint32_t", "operation" }, { "uint32_t", "sfi_class" }, { "uint64_t", "time" }, { "uint64_t *", "out_time" } } },
|
||||
{ 457, "sfi_pidctl", 4, { { "uint32_t", "operation" }, { "pid_t", "pid" }, { "uint32_t", "sfi_flags" }, { "uint32_t *", "out_sfi_flags" } } },
|
||||
{ 458, "coalition", 3, { { "uint32_t", "operation" }, { "uint64_t *", "cid" }, { "uint32_t", "flags" } } },
|
||||
{ 459, "coalition_info", 4, { { "uint32_t", "flavor" }, { "uint64_t *", "cid" }, { "void *", "buffer" }, { "size_t *", "bufsize" } } },
|
||||
{ 460, "necp_match_policy", 3, { { "uint8_t *", "parameters" }, { "size_t", "parameters_size" }, { "struct necp_aggregate_result *", "returned_result" } } },
|
||||
{ 461, "getattrlistbulk", 5, { { "int", "dirfd" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "uint64_t", "options" } } },
|
||||
{ 462, "clonefileat", 5, { { "int", "src_dirfd" }, { "user_addr_t", "src" }, { "int", "dst_dirfd" }, { "user_addr_t", "dst" }, { "uint32_t", "flags" } } },
|
||||
{ 463, "openat", 4, { { "int", "fd" }, { "char *", "path" }, { "int", "flags" }, { "int", "mode" } } },
|
||||
{ 464, "openat_nocancel", 4, { { "int", "fd" }, { "char *", "path" }, { "int", "flags" }, { "int", "mode" } } },
|
||||
{ 465, "renameat", 4, { { "int", "fromfd" }, { "char *", "from" }, { "int", "tofd" }, { "char *", "to" } } },
|
||||
{ 466, "faccessat", 4, { { "int", "fd" }, { "char *", "path" }, { "int", "amode" }, { "int", "flag" } } },
|
||||
{ 467, "fchmodat", 4, { { "int", "fd" }, { "char *", "path" }, { "int", "mode" }, { "int", "flag" } } },
|
||||
{ 468, "fchownat", 5, { { "int", "fd" }, { "char *", "path" }, { "uid_t", "uid" }, { "gid_t", "gid" }, { "int", "flag" } } },
|
||||
{ 469, "fstatat", 4, { { "int", "fd" }, { "char *", "path" }, { "user_addr_t", "ub" }, { "int", "flag" } } },
|
||||
{ 470, "fstatat64", 4, { { "int", "fd" }, { "char *", "path" }, { "user_addr_t", "ub" }, { "int", "flag" } } },
|
||||
{ 471, "linkat", 5, { { "int", "fd1" }, { "char *", "path" }, { "int", "fd2" }, { "char *", "link" }, { "int", "flag" } } },
|
||||
{ 472, "unlinkat", 3, { { "int", "fd" }, { "char *", "path" }, { "int", "flag" } } },
|
||||
{ 473, "readlinkat", 4, { { "int", "fd" }, { "char *", "path" }, { "user_addr_t", "buf" }, { "size_t", "bufsize" } } },
|
||||
{ 474, "symlinkat", 3, { { "user_addr_t *", "path1" }, { "int", "fd" }, { "user_addr_t", "path2" } } },
|
||||
{ 475, "mkdirat", 3, { { "int", "fd" }, { "char *", "path" }, { "int", "mode" } } },
|
||||
{ 476, "getattrlistat", 6, { { "int", "fd" }, { "const char *", "path" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "u_long", "options" } } },
|
||||
{ 477, "proc_trace_log", 2, { { "pid_t", "pid" }, { "uint64_t", "uniqueid" } } },
|
||||
{ 478, "bsdthread_ctl", 4, { { "user_addr_t", "cmd" }, { "user_addr_t", "arg1" }, { "user_addr_t", "arg2" }, { "user_addr_t", "arg3" } } },
|
||||
{ 479, "openbyid_np", 3, { { "user_addr_t", "fsid" }, { "user_addr_t", "objid" }, { "int", "oflags" } } },
|
||||
{ 480, "recvmsg_x", 4, { { "int", "s" }, { "struct msghdr_x *", "msgp" }, { "u_int", "cnt" }, { "int", "flags" } } },
|
||||
{ 481, "sendmsg_x", 4, { { "int", "s" }, { "struct msghdr_x *", "msgp" }, { "u_int", "cnt" }, { "int", "flags" } } },
|
||||
{ 482, "thread_selfusage", 0, {} },
|
||||
{ 483, "csrctl", 3, { { "uint32_t", "op" }, { "user_addr_t", "useraddr" }, { "user_addr_t", "usersize" } } },
|
||||
{ 484, "guarded_open_dprotected_np", 7, { { "char *", "path" }, { "const guardid_t *", "guard" }, { "u_int", "guardflags" }, { "int", "flags" }, { "int", "dpclass" }, { "int", "dpflags" }, { "int", "mode" } } },
|
||||
{ 485, "guarded_write_np", 4, { { "int", "fd" }, { "const guardid_t *", "guard" }, { "user_addr_t", "cbuf" }, { "user_size_t", "nbyte" } } },
|
||||
{ 486, "guarded_pwrite_np", 5, { { "int", "fd" }, { "const guardid_t *", "guard" }, { "user_addr_t", "buf" }, { "user_size_t", "nbyte" }, { "off_t", "offset" } } },
|
||||
{ 487, "guarded_writev_np", 4, { { "int", "fd" }, { "const guardid_t *", "guard" }, { "struct iovec *", "iovp" }, { "int", "iovcnt" } } },
|
||||
{ 488, "renameatx_np", 5, { { "int", "fromfd" }, { "char *", "from" }, { "int", "tofd" }, { "char *", "to" }, { "u_int", "flags" } } },
|
||||
{ 489, "mremap_encrypted", 5, { { "caddr_ut", "addr" }, { "size_ut", "len" }, { "uint32_t", "cryptid" }, { "uint32_t", "cputype" }, { "uint32_t", "cpusubtype" } } },
|
||||
{ 490, "netagent_trigger", 2, { { "uuid_t", "agent_uuid" }, { "size_t", "agent_uuidlen" } } },
|
||||
{ 491, "stack_snapshot_with_config", 3, { { "int", "stackshot_config_version" }, { "user_addr_t", "stackshot_config" }, { "size_t", "stackshot_config_size" } } },
|
||||
{ 492, "microstackshot", 3, { { "user_addr_t", "tracebuf" }, { "uint32_t", "tracebuf_size" }, { "uint32_t", "flags" } } },
|
||||
{ 493, "grab_pgo_data", 4, { { "user_addr_t", "uuid" }, { "int", "flags" }, { "user_addr_t", "buffer" }, { "user_ssize_t", "size" } } },
|
||||
{ 494, "persona", 6, { { "uint32_t", "operation" }, { "uint32_t", "flags" }, { "struct kpersona_info *", "info" }, { "uid_t *", "id" }, { "size_t *", "idlen" }, { "char *", "path" } } },
|
||||
{ 496, "mach_eventlink_signal", 2, { { "mach_port_name_t", "eventlink_port" }, { "uint64_t", "signal_count" } } },
|
||||
{ 497, "mach_eventlink_wait_until", 5, { { "mach_port_name_t", "eventlink_port" }, { "uint64_t", "wait_count" }, { "uint64_t", "deadline" }, { "uint32_t", "clock_id" }, { "uint32_t", "option" } } },
|
||||
{ 498, "mach_eventlink_signal_wait_until", 6, { { "mach_port_name_t", "eventlink_port" }, { "uint64_t", "wait_count" }, { "uint64_t", "signal_count" }, { "uint64_t", "deadline" }, { "uint32_t", "clock_id" }, { "uint32_t", "option" } } },
|
||||
{ 499, "work_interval_ctl", 4, { { "uint32_t", "operation" }, { "uint64_t", "work_interval_id" }, { "void *", "arg" }, { "size_t", "len" } } },
|
||||
{ 500, "getentropy", 2, { { "void *", "buffer" }, { "size_t", "size" } } },
|
||||
{ 502, "necp_client_action", 6, { { "int", "necp_fd" }, { "uint32_t", "action" }, { "uuid_t", "client_id" }, { "size_t", "client_id_len" }, { "uint8_t *", "buffer" }, { "size_t", "buffer_size" } } },
|
||||
{ 503, "nexus_open", 2, { { "struct nxctl_init *", "init" }, { "uint32_t", "init_len" } } },
|
||||
{ 504, "nexus_register", 5, { { "int", "ctl" }, { "struct nxprov_reg *", "reg" }, { "uint32_t", "reg_len" }, { "uuid_t *", "prov_uuid" }, { "uint32_t", "prov_uuid_len" } } },
|
||||
{ 505, "nexus_deregister", 3, { { "int", "ctl" }, { "uuid_t", "prov_uuid" }, { "uint32_t", "prov_uuid_len" } } },
|
||||
{ 506, "nexus_create", 5, { { "int", "ctl" }, { "uuid_t", "prov_uuid" }, { "uint32_t", "prov_uuid_len" }, { "uuid_t *", "nx_uuid" }, { "uint32_t", "nx_uuid_len" } } },
|
||||
{ 507, "nexus_destroy", 3, { { "int", "ctl" }, { "uuid_t", "nx_uuid" }, { "uint32_t", "nx_uuid_len" } } },
|
||||
{ 508, "nexus_get_opt", 4, { { "int", "ctl" }, { "uint32_t", "opt" }, { "void *", "aoptval" }, { "uint32_t *", "aoptlen" } } },
|
||||
{ 509, "nexus_set_opt", 4, { { "int", "ctl" }, { "uint32_t", "opt" }, { "const void *", "aoptval" }, { "uint32_t", "optlen" } } },
|
||||
{ 510, "channel_open", 2, { { "struct ch_init *", "init" }, { "uint32_t", "init_len" } } },
|
||||
{ 511, "channel_get_info", 3, { { "int", "c" }, { "struct ch_info *", "cinfo" }, { "uint32_t", "cinfolen" } } },
|
||||
{ 512, "channel_sync", 3, { { "int", "c" }, { "int", "mode" }, { "int", "flags" } } },
|
||||
{ 513, "channel_get_opt", 4, { { "int", "c" }, { "uint32_t", "opt" }, { "void *", "aoptval" }, { "uint32_t *", "aoptlen" } } },
|
||||
{ 514, "channel_set_opt", 4, { { "int", "c" }, { "uint32_t", "opt" }, { "const void *", "aoptval" }, { "uint32_t", "optlen" } } },
|
||||
{ 515, "ulock_wait", 4, { { "uint32_t", "operation" }, { "void *", "addr" }, { "uint64_t", "value" }, { "uint32_t", "timeout" } } },
|
||||
{ 516, "ulock_wake", 3, { { "uint32_t", "operation" }, { "void *", "addr" }, { "uint64_t", "wake_value" } } },
|
||||
{ 517, "fclonefileat", 4, { { "int", "src_fd" }, { "int", "dst_dirfd" }, { "user_addr_t", "dst" }, { "uint32_t", "flags" } } },
|
||||
{ 518, "fs_snapshot", 6, { { "uint32_t", "op" }, { "int", "dirfd" }, { "user_addr_t", "name1" }, { "user_addr_t", "name2" }, { "user_addr_t", "data" }, { "uint32_t", "flags" } } },
|
||||
{ 520, "terminate_with_payload", 7, { { "int", "pid" }, { "uint32_t", "reason_namespace" }, { "uint64_t", "reason_code" }, { "void *", "payload" }, { "uint32_t", "payload_size" }, { "const char *", "reason_string" }, { "uint64_t", "reason_flags" } } },
|
||||
{ 521, "abort_with_payload", 6, { { "uint32_t", "reason_namespace" }, { "uint64_t", "reason_code" }, { "void *", "payload" }, { "uint32_t", "payload_size" }, { "const char *", "reason_string" }, { "uint64_t", "reason_flags" } } },
|
||||
{ 523, "necp_session_action", 6, { { "int", "necp_fd" }, { "uint32_t", "action" }, { "uint8_t *", "in_buffer" }, { "size_t", "in_buffer_length" }, { "uint8_t *", "out_buffer" }, { "size_t", "out_buffer_length" } } },
|
||||
{ 524, "setattrlistat", 6, { { "int", "fd" }, { "const char *", "path" }, { "struct attrlist *", "alist" }, { "void *", "attribute_buffer" }, { "size_t", "buffer_size" }, { "uint32_t", "options" } } },
|
||||
{ 525, "net_qos_guideline", 2, { { "struct net_qos_param *", "param" }, { "uint32_t", "param_len" } } },
|
||||
{ 526, "fmount", 4, { { "const char *", "type" }, { "int", "fd" }, { "int", "flags" }, { "void *", "data" } } },
|
||||
{ 527, "ntp_adjtime", 1, { { "struct timex *", "tp" } } },
|
||||
{ 528, "ntp_gettime", 1, { { "struct ntptimeval *", "ntvp" } } },
|
||||
{ 529, "os_fault_with_payload", 6, { { "uint32_t", "reason_namespace" }, { "uint64_t", "reason_code" }, { "void *", "payload" }, { "uint32_t", "payload_size" }, { "const char *", "reason_string" }, { "uint64_t", "reason_flags" } } },
|
||||
{ 530, "kqueue_workloop_ctl", 4, { { "user_addr_t", "cmd" }, { "uint64_t", "options" }, { "user_addr_t", "addr" }, { "size_t", "sz" } } },
|
||||
{ 531, "mach_bridge_remote_time", 1, { { "uint64_t", "local_timestamp" } } },
|
||||
{ 532, "coalition_ledger", 4, { { "uint32_t", "operation" }, { "uint64_t *", "cid" }, { "void *", "buffer" }, { "size_t *", "bufsize" } } },
|
||||
{ 533, "log_data", 4, { { "unsigned int", "tag" }, { "unsigned int", "flags" }, { "void *", "buffer" }, { "unsigned int", "size" } } },
|
||||
{ 534, "memorystatus_available_memory", 0, {} },
|
||||
{ 535, "objc_bp_assist_cfg_np", 2, { { "uint64_t", "adr" }, { "uint64_t", "ctl" } } },
|
||||
{ 536, "shared_region_map_and_slide_2_np", 4, { { "uint32_t", "files_count" }, { "const struct shared_file_np *", "files" }, { "uint32_t", "mappings_count" }, { "const shared_file_mapping_slide_np_ut *", "mappings_u" } } },
|
||||
{ 537, "pivot_root", 2, { { "const char *", "new_rootfs_path_before" }, { "const char *", "old_rootfs_path_after" } } },
|
||||
{ 538, "task_inspect_for_pid", 3, { { "mach_port_name_t", "target_tport" }, { "int", "pid" }, { "mach_port_name_t *", "t" } } },
|
||||
{ 539, "task_read_for_pid", 3, { { "mach_port_name_t", "target_tport" }, { "int", "pid" }, { "mach_port_name_t *", "t" } } },
|
||||
{ 540, "preadv", 4, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "int", "iovcnt" }, { "off_t", "offset" } } },
|
||||
{ 541, "pwritev", 4, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "int", "iovcnt" }, { "off_t", "offset" } } },
|
||||
{ 542, "preadv_nocancel", 4, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "int", "iovcnt" }, { "off_t", "offset" } } },
|
||||
{ 543, "pwritev_nocancel", 4, { { "int", "fd" }, { "struct iovec *", "iovp" }, { "int", "iovcnt" }, { "off_t", "offset" } } },
|
||||
{ 544, "ulock_wait2", 5, { { "uint32_t", "operation" }, { "void *", "addr" }, { "uint64_t", "value" }, { "uint64_t", "timeout" }, { "uint64_t", "value2" } } },
|
||||
{ 545, "proc_info_extended_id", 8, { { "int32_t", "callnum" }, { "int32_t", "pid" }, { "uint32_t", "flavor" }, { "uint32_t", "flags" }, { "uint64_t", "ext_id" }, { "uint64_t", "arg" }, { "user_addr_t", "buffer" }, { "int32_t", "buffersize" } } },
|
||||
{ 546, "tracker_action", 3, { { "int", "action" }, { "char *", "buffer" }, { "size_t", "buffer_size" } } },
|
||||
{ 547, "debug_syscall_reject", 1, { { "uint64_t", "packed_selectors" } } },
|
||||
{ 548, "debug_syscall_reject_config", 3, { { "uint64_t", "packed_selectors1" }, { "uint64_t", "packed_selectors2" }, { "uint64_t", "flags" } } },
|
||||
{ 549, "graftdmg", 4, { { "int", "dmg_fd" }, { "const char *", "mountdir" }, { "uint32_t", "graft_type" }, { "graftdmg_args_un *", "gda" } } },
|
||||
{ 550, "map_with_linking_np", 4, { { "void *", "regions" }, { "uint32_t", "region_count" }, { "void *", "link_info" }, { "uint32_t", "link_info_size" } } },
|
||||
{ 551, "freadlink", 3, { { "int", "fd" }, { "user_addr_t", "buf" }, { "user_size_t", "bufsize" } } },
|
||||
{ 552, "record_system_event", 4, { { "uint32_t", "type" }, { "uint32_t", "subsystem" }, { "const char *", "event" }, { "const char *", "payload" } } },
|
||||
{ 553, "mkfifoat", 3, { { "int", "fd" }, { "char *", "path" }, { "int", "mode" } } },
|
||||
{ 554, "mknodat", 4, { { "int", "fd" }, { "char *", "path" }, { "int", "mode" }, { "int", "dev" } } },
|
||||
{ 555, "ungraftdmg", 2, { { "const char *", "mountdir" }, { "uint64_t", "flags" } } },
|
||||
{ 556, "coalition_policy_set", 3, { { "uint64_t", "cid" }, { "uint32_t", "flavor" }, { "uint32_t", "value" } } },
|
||||
{ 557, "coalition_policy_get", 2, { { "uint64_t", "cid" }, { "uint32_t", "flavor" } } },
|
||||
};
|
||||
|
||||
FridaXnuSyscallSignature *
|
||||
frida_get_xnu_mach_traps (int * len)
|
||||
{
|
||||
if (len != NULL)
|
||||
*len = G_N_ELEMENTS (frida_xnu_mach_traps);
|
||||
return (FridaXnuSyscallSignature *) frida_xnu_mach_traps;
|
||||
}
|
||||
|
||||
FridaXnuSyscallSignature *
|
||||
frida_get_xnu_bsd_syscalls (int * len)
|
||||
{
|
||||
if (len != NULL)
|
||||
*len = G_N_ELEMENTS (frida_xnu_bsd_syscalls);
|
||||
return (FridaXnuSyscallSignature *) frida_xnu_bsd_syscalls;
|
||||
}
|
||||
@@ -0,0 +1,545 @@
|
||||
// AUTO-GENERATED FILE. DO NOT EDIT.
|
||||
|
||||
namespace Frida {
|
||||
public extern unowned XnuSyscallSignature[] get_xnu_mach_traps ();
|
||||
public extern unowned XnuSyscallSignature[] get_xnu_bsd_syscalls ();
|
||||
|
||||
public struct XnuSyscallSignature {
|
||||
public int nr;
|
||||
public string name;
|
||||
public uint8 nargs;
|
||||
public XnuSyscallArg args[9];
|
||||
}
|
||||
|
||||
public struct XnuSyscallArg {
|
||||
public string? type;
|
||||
public string? name;
|
||||
}
|
||||
|
||||
/**
|
||||
* The XNU Mach trap numbers, as used on Apple platforms.
|
||||
*/
|
||||
public enum XnuMachTrap {
|
||||
MACH_VM_ALLOCATE = -10,
|
||||
MACH_VM_PURGABLE_CONTROL = -11,
|
||||
MACH_VM_DEALLOCATE = -12,
|
||||
TASK_DYLD_PROCESS_INFO_NOTIFY_GET = -13,
|
||||
MACH_VM_PROTECT = -14,
|
||||
MACH_VM_MAP = -15,
|
||||
MACH_PORT_ALLOCATE = -16,
|
||||
MACH_PORT_DEALLOCATE = -18,
|
||||
MACH_PORT_MOD_REFS = -19,
|
||||
MACH_PORT_MOVE_MEMBER = -20,
|
||||
MACH_PORT_INSERT_RIGHT = -21,
|
||||
MACH_PORT_INSERT_MEMBER = -22,
|
||||
MACH_PORT_EXTRACT_MEMBER = -23,
|
||||
MACH_PORT_CONSTRUCT = -24,
|
||||
MACH_PORT_DESTRUCT = -25,
|
||||
MACH_REPLY_PORT = -26,
|
||||
THREAD_SELF = -27,
|
||||
TASK_SELF = -28,
|
||||
HOST_SELF = -29,
|
||||
MACH_MSG = -31,
|
||||
MACH_MSG_OVERWRITE = -32,
|
||||
SEMAPHORE_SIGNAL = -33,
|
||||
SEMAPHORE_SIGNAL_ALL = -34,
|
||||
SEMAPHORE_SIGNAL_THREAD = -35,
|
||||
SEMAPHORE_WAIT = -36,
|
||||
SEMAPHORE_WAIT_SIGNAL = -37,
|
||||
SEMAPHORE_TIMEDWAIT = -38,
|
||||
SEMAPHORE_TIMEDWAIT_SIGNAL = -39,
|
||||
MACH_PORT_GET_ATTRIBUTES = -40,
|
||||
MACH_PORT_GUARD = -41,
|
||||
MACH_PORT_UNGUARD = -42,
|
||||
MACH_GENERATE_ACTIVITY_ID = -43,
|
||||
TASK_NAME_FOR_PID = -44,
|
||||
TASK_FOR_PID = -45,
|
||||
PID_FOR_TASK = -46,
|
||||
MACH_MSG2 = -47,
|
||||
MACX_SWAPON = -48,
|
||||
MACX_SWAPOFF = -49,
|
||||
THREAD_GET_SPECIAL_REPLY_PORT = -50,
|
||||
MACX_TRIGGERS = -51,
|
||||
MACX_BACKING_STORE_SUSPEND = -52,
|
||||
MACX_BACKING_STORE_RECOVERY = -53,
|
||||
PFZ_EXIT = -58,
|
||||
SWTCH_PRI = -59,
|
||||
SWTCH = -60,
|
||||
THREAD_SWITCH = -61,
|
||||
CLOCK_SLEEP = -62,
|
||||
MACH_VM_RECLAIM_UPDATE_KERNEL_ACCOUNTING = -63,
|
||||
HOST_CREATE_MACH_VOUCHER = -70,
|
||||
MACH_VOUCHER_EXTRACT_ATTR_RECIPE = -72,
|
||||
MACH_PORT_TYPE = -76,
|
||||
MACH_PORT_REQUEST_NOTIFICATION = -77,
|
||||
EXCLAVES_CTL = -88,
|
||||
MACH_TIMEBASE_INFO = -89,
|
||||
MACH_WAIT_UNTIL = -90,
|
||||
MK_TIMER_CREATE = -91,
|
||||
MK_TIMER_DESTROY = -92,
|
||||
MK_TIMER_ARM = -93,
|
||||
MK_TIMER_CANCEL = -94,
|
||||
MK_TIMER_ARM_LEEWAY = -95,
|
||||
DEBUG_CONTROL_PORT_FOR_PID = -96,
|
||||
IOKIT_USER_CLIENT = -100,
|
||||
}
|
||||
|
||||
/**
|
||||
* The XNU BSD syscall numbers, as used on Apple platforms.
|
||||
*/
|
||||
public enum XnuBsdSyscall {
|
||||
EXIT = 1,
|
||||
FORK = 2,
|
||||
READ = 3,
|
||||
WRITE = 4,
|
||||
OPEN = 5,
|
||||
CLOSE = 6,
|
||||
WAIT4 = 7,
|
||||
LINK = 9,
|
||||
UNLINK = 10,
|
||||
CHDIR = 12,
|
||||
FCHDIR = 13,
|
||||
MKNOD = 14,
|
||||
CHMOD = 15,
|
||||
CHOWN = 16,
|
||||
GETFSSTAT = 18,
|
||||
GETPID = 20,
|
||||
SETUID = 23,
|
||||
GETUID = 24,
|
||||
GETEUID = 25,
|
||||
PTRACE = 26,
|
||||
RECVMSG = 27,
|
||||
SENDMSG = 28,
|
||||
RECVFROM = 29,
|
||||
ACCEPT = 30,
|
||||
GETPEERNAME = 31,
|
||||
GETSOCKNAME = 32,
|
||||
ACCESS = 33,
|
||||
CHFLAGS = 34,
|
||||
FCHFLAGS = 35,
|
||||
SYNC = 36,
|
||||
KILL = 37,
|
||||
CROSSARCH_TRAP = 38,
|
||||
GETPPID = 39,
|
||||
DUP = 41,
|
||||
PIPE = 42,
|
||||
GETEGID = 43,
|
||||
SIGACTION = 46,
|
||||
GETGID = 47,
|
||||
SIGPROCMASK = 48,
|
||||
GETLOGIN = 49,
|
||||
SETLOGIN = 50,
|
||||
ACCT = 51,
|
||||
SIGPENDING = 52,
|
||||
SIGALTSTACK = 53,
|
||||
IOCTL = 54,
|
||||
REBOOT = 55,
|
||||
REVOKE = 56,
|
||||
SYMLINK = 57,
|
||||
READLINK = 58,
|
||||
EXECVE = 59,
|
||||
UMASK = 60,
|
||||
CHROOT = 61,
|
||||
MSYNC = 65,
|
||||
VFORK = 66,
|
||||
OSLOG_COPROC_REG = 67,
|
||||
OSLOG_COPROC = 68,
|
||||
MUNMAP = 73,
|
||||
MPROTECT = 74,
|
||||
MADVISE = 75,
|
||||
MINCORE = 78,
|
||||
GETGROUPS = 79,
|
||||
SETGROUPS = 80,
|
||||
GETPGRP = 81,
|
||||
SETPGID = 82,
|
||||
SETITIMER = 83,
|
||||
SWAPON = 85,
|
||||
GETITIMER = 86,
|
||||
GETDTABLESIZE = 89,
|
||||
DUP2 = 90,
|
||||
FCNTL = 92,
|
||||
SELECT = 93,
|
||||
FSYNC = 95,
|
||||
SETPRIORITY = 96,
|
||||
SOCKET = 97,
|
||||
CONNECT = 98,
|
||||
GETPRIORITY = 100,
|
||||
BIND = 104,
|
||||
SETSOCKOPT = 105,
|
||||
LISTEN = 106,
|
||||
SIGSUSPEND = 111,
|
||||
GETTIMEOFDAY = 116,
|
||||
GETRUSAGE = 117,
|
||||
GETSOCKOPT = 118,
|
||||
READV = 120,
|
||||
WRITEV = 121,
|
||||
SETTIMEOFDAY = 122,
|
||||
FCHOWN = 123,
|
||||
FCHMOD = 124,
|
||||
SETREUID = 126,
|
||||
SETREGID = 127,
|
||||
RENAME = 128,
|
||||
FLOCK = 131,
|
||||
MKFIFO = 132,
|
||||
SENDTO = 133,
|
||||
SHUTDOWN = 134,
|
||||
SOCKETPAIR = 135,
|
||||
MKDIR = 136,
|
||||
RMDIR = 137,
|
||||
UTIMES = 138,
|
||||
FUTIMES = 139,
|
||||
ADJTIME = 140,
|
||||
GETHOSTUUID = 142,
|
||||
SETSID = 147,
|
||||
GETPGID = 151,
|
||||
SETPRIVEXEC = 152,
|
||||
PREAD = 153,
|
||||
PWRITE = 154,
|
||||
NFSSVC = 155,
|
||||
STATFS = 157,
|
||||
FSTATFS = 158,
|
||||
UNMOUNT = 159,
|
||||
GETFH = 161,
|
||||
FUNMOUNT = 164,
|
||||
QUOTACTL = 165,
|
||||
MOUNT = 167,
|
||||
CSOPS = 169,
|
||||
CSOPS_AUDITTOKEN = 170,
|
||||
WAITID = 173,
|
||||
KDEBUG_TYPEFILTER = 177,
|
||||
KDEBUG_TRACE_STRING = 178,
|
||||
KDEBUG_TRACE64 = 179,
|
||||
KDEBUG_TRACE = 180,
|
||||
SETGID = 181,
|
||||
SETEGID = 182,
|
||||
SETEUID = 183,
|
||||
SIGRETURN = 184,
|
||||
PANIC_WITH_DATA = 185,
|
||||
THREAD_SELFCOUNTS = 186,
|
||||
FDATASYNC = 187,
|
||||
STAT = 188,
|
||||
FSTAT = 189,
|
||||
LSTAT = 190,
|
||||
PATHCONF = 191,
|
||||
FPATHCONF = 192,
|
||||
GETRLIMIT = 194,
|
||||
SETRLIMIT = 195,
|
||||
GETDIRENTRIES = 196,
|
||||
MMAP = 197,
|
||||
LSEEK = 199,
|
||||
TRUNCATE = 200,
|
||||
FTRUNCATE = 201,
|
||||
SYSCTL = 202,
|
||||
MLOCK = 203,
|
||||
MUNLOCK = 204,
|
||||
UNDELETE = 205,
|
||||
OPEN_DPROTECTED_NP = 216,
|
||||
FSGETPATH_EXT = 217,
|
||||
OPENAT_DPROTECTED_NP = 218,
|
||||
GETATTRLIST = 220,
|
||||
SETATTRLIST = 221,
|
||||
GETDIRENTRIESATTR = 222,
|
||||
EXCHANGEDATA = 223,
|
||||
SEARCHFS = 225,
|
||||
DELETE = 226,
|
||||
COPYFILE = 227,
|
||||
FGETATTRLIST = 228,
|
||||
FSETATTRLIST = 229,
|
||||
POLL = 230,
|
||||
GETXATTR = 234,
|
||||
FGETXATTR = 235,
|
||||
SETXATTR = 236,
|
||||
FSETXATTR = 237,
|
||||
REMOVEXATTR = 238,
|
||||
FREMOVEXATTR = 239,
|
||||
LISTXATTR = 240,
|
||||
FLISTXATTR = 241,
|
||||
FSCTL = 242,
|
||||
INITGROUPS = 243,
|
||||
POSIX_SPAWN = 244,
|
||||
FFSCTL = 245,
|
||||
FHOPEN = 248,
|
||||
MINHERIT = 250,
|
||||
SEMSYS = 251,
|
||||
MSGSYS = 252,
|
||||
SHMSYS = 253,
|
||||
SEMCTL = 254,
|
||||
SEMGET = 255,
|
||||
SEMOP = 256,
|
||||
MSGCTL = 258,
|
||||
MSGGET = 259,
|
||||
MSGSND = 260,
|
||||
MSGRCV = 261,
|
||||
SHMAT = 262,
|
||||
SHMCTL = 263,
|
||||
SHMDT = 264,
|
||||
SHMGET = 265,
|
||||
SHM_OPEN = 266,
|
||||
SHM_UNLINK = 267,
|
||||
SEM_OPEN = 268,
|
||||
SEM_CLOSE = 269,
|
||||
SEM_UNLINK = 270,
|
||||
SEM_WAIT = 271,
|
||||
SEM_TRYWAIT = 272,
|
||||
SEM_POST = 273,
|
||||
SYSCTLBYNAME = 274,
|
||||
OPEN_EXTENDED = 277,
|
||||
UMASK_EXTENDED = 278,
|
||||
STAT_EXTENDED = 279,
|
||||
LSTAT_EXTENDED = 280,
|
||||
FSTAT_EXTENDED = 281,
|
||||
CHMOD_EXTENDED = 282,
|
||||
FCHMOD_EXTENDED = 283,
|
||||
ACCESS_EXTENDED = 284,
|
||||
SETTID = 285,
|
||||
GETTID = 286,
|
||||
SETSGROUPS = 287,
|
||||
GETSGROUPS = 288,
|
||||
SETWGROUPS = 289,
|
||||
GETWGROUPS = 290,
|
||||
MKFIFO_EXTENDED = 291,
|
||||
MKDIR_EXTENDED = 292,
|
||||
IDENTITYSVC = 293,
|
||||
SHARED_REGION_CHECK_NP = 294,
|
||||
VM_PRESSURE_MONITOR = 296,
|
||||
PSYNCH_RW_LONGRDLOCK = 297,
|
||||
PSYNCH_RW_YIELDWRLOCK = 298,
|
||||
PSYNCH_RW_DOWNGRADE = 299,
|
||||
PSYNCH_RW_UPGRADE = 300,
|
||||
PSYNCH_MUTEXWAIT = 301,
|
||||
PSYNCH_MUTEXDROP = 302,
|
||||
PSYNCH_CVBROAD = 303,
|
||||
PSYNCH_CVSIGNAL = 304,
|
||||
PSYNCH_CVWAIT = 305,
|
||||
PSYNCH_RW_RDLOCK = 306,
|
||||
PSYNCH_RW_WRLOCK = 307,
|
||||
PSYNCH_RW_UNLOCK = 308,
|
||||
PSYNCH_RW_UNLOCK2 = 309,
|
||||
GETSID = 310,
|
||||
SETTID_WITH_PID = 311,
|
||||
PSYNCH_CVCLRPREPOST = 312,
|
||||
AIO_FSYNC = 313,
|
||||
AIO_RETURN = 314,
|
||||
AIO_SUSPEND = 315,
|
||||
AIO_CANCEL = 316,
|
||||
AIO_ERROR = 317,
|
||||
AIO_READ = 318,
|
||||
AIO_WRITE = 319,
|
||||
LIO_LISTIO = 320,
|
||||
IOPOLICYSYS = 322,
|
||||
PROCESS_POLICY = 323,
|
||||
MLOCKALL = 324,
|
||||
MUNLOCKALL = 325,
|
||||
ISSETUGID = 327,
|
||||
PTHREAD_KILL = 328,
|
||||
PTHREAD_SIGMASK = 329,
|
||||
SIGWAIT = 330,
|
||||
DISABLE_THREADSIGNAL = 331,
|
||||
PTHREAD_MARKCANCEL = 332,
|
||||
PTHREAD_CANCELED = 333,
|
||||
SEMWAIT_SIGNAL = 334,
|
||||
PROC_INFO = 336,
|
||||
SENDFILE = 337,
|
||||
STAT64 = 338,
|
||||
FSTAT64 = 339,
|
||||
LSTAT64 = 340,
|
||||
STAT64_EXTENDED = 341,
|
||||
LSTAT64_EXTENDED = 342,
|
||||
FSTAT64_EXTENDED = 343,
|
||||
GETDIRENTRIES64 = 344,
|
||||
STATFS64 = 345,
|
||||
FSTATFS64 = 346,
|
||||
GETFSSTAT64 = 347,
|
||||
PTHREAD_CHDIR = 348,
|
||||
PTHREAD_FCHDIR = 349,
|
||||
AUDIT = 350,
|
||||
AUDITON = 351,
|
||||
GETAUID = 353,
|
||||
SETAUID = 354,
|
||||
GETAUDIT_ADDR = 357,
|
||||
SETAUDIT_ADDR = 358,
|
||||
AUDITCTL = 359,
|
||||
BSDTHREAD_CREATE = 360,
|
||||
BSDTHREAD_TERMINATE = 361,
|
||||
KQUEUE = 362,
|
||||
KEVENT = 363,
|
||||
LCHOWN = 364,
|
||||
BSDTHREAD_REGISTER = 366,
|
||||
WORKQ_OPEN = 367,
|
||||
WORKQ_KERNRETURN = 368,
|
||||
KEVENT64 = 369,
|
||||
THREAD_SELFID = 372,
|
||||
LEDGER = 373,
|
||||
KEVENT_QOS = 374,
|
||||
KEVENT_ID = 375,
|
||||
MAC_EXECVE = 380,
|
||||
MAC_SYSCALL = 381,
|
||||
MAC_GET_FILE = 382,
|
||||
MAC_SET_FILE = 383,
|
||||
MAC_GET_LINK = 384,
|
||||
MAC_SET_LINK = 385,
|
||||
MAC_GET_PROC = 386,
|
||||
MAC_SET_PROC = 387,
|
||||
MAC_GET_FD = 388,
|
||||
MAC_SET_FD = 389,
|
||||
MAC_GET_PID = 390,
|
||||
PSELECT = 394,
|
||||
PSELECT_NOCANCEL = 395,
|
||||
READ_NOCANCEL = 396,
|
||||
WRITE_NOCANCEL = 397,
|
||||
OPEN_NOCANCEL = 398,
|
||||
CLOSE_NOCANCEL = 399,
|
||||
WAIT4_NOCANCEL = 400,
|
||||
RECVMSG_NOCANCEL = 401,
|
||||
SENDMSG_NOCANCEL = 402,
|
||||
RECVFROM_NOCANCEL = 403,
|
||||
ACCEPT_NOCANCEL = 404,
|
||||
MSYNC_NOCANCEL = 405,
|
||||
FCNTL_NOCANCEL = 406,
|
||||
SELECT_NOCANCEL = 407,
|
||||
FSYNC_NOCANCEL = 408,
|
||||
CONNECT_NOCANCEL = 409,
|
||||
SIGSUSPEND_NOCANCEL = 410,
|
||||
READV_NOCANCEL = 411,
|
||||
WRITEV_NOCANCEL = 412,
|
||||
SENDTO_NOCANCEL = 413,
|
||||
PREAD_NOCANCEL = 414,
|
||||
PWRITE_NOCANCEL = 415,
|
||||
WAITID_NOCANCEL = 416,
|
||||
POLL_NOCANCEL = 417,
|
||||
MSGSND_NOCANCEL = 418,
|
||||
MSGRCV_NOCANCEL = 419,
|
||||
SEM_WAIT_NOCANCEL = 420,
|
||||
AIO_SUSPEND_NOCANCEL = 421,
|
||||
SIGWAIT_NOCANCEL = 422,
|
||||
SEMWAIT_SIGNAL_NOCANCEL = 423,
|
||||
MAC_MOUNT = 424,
|
||||
MAC_GET_MOUNT = 425,
|
||||
MAC_GETFSSTAT = 426,
|
||||
FSGETPATH = 427,
|
||||
AUDIT_SESSION_SELF = 428,
|
||||
AUDIT_SESSION_JOIN = 429,
|
||||
FILEPORT_MAKEPORT = 430,
|
||||
FILEPORT_MAKEFD = 431,
|
||||
AUDIT_SESSION_PORT = 432,
|
||||
PID_SUSPEND = 433,
|
||||
PID_RESUME = 434,
|
||||
PID_HIBERNATE = 435,
|
||||
PID_SHUTDOWN_SOCKETS = 436,
|
||||
KAS_INFO = 439,
|
||||
MEMORYSTATUS_CONTROL = 440,
|
||||
GUARDED_OPEN_NP = 441,
|
||||
GUARDED_CLOSE_NP = 442,
|
||||
GUARDED_KQUEUE_NP = 443,
|
||||
CHANGE_FDGUARD_NP = 444,
|
||||
USRCTL = 445,
|
||||
PROC_RLIMIT_CONTROL = 446,
|
||||
CONNECTX = 447,
|
||||
DISCONNECTX = 448,
|
||||
PEELOFF = 449,
|
||||
SOCKET_DELEGATE = 450,
|
||||
TELEMETRY = 451,
|
||||
PROC_UUID_POLICY = 452,
|
||||
MEMORYSTATUS_GET_LEVEL = 453,
|
||||
SYSTEM_OVERRIDE = 454,
|
||||
VFS_PURGE = 455,
|
||||
SFI_CTL = 456,
|
||||
SFI_PIDCTL = 457,
|
||||
COALITION = 458,
|
||||
COALITION_INFO = 459,
|
||||
NECP_MATCH_POLICY = 460,
|
||||
GETATTRLISTBULK = 461,
|
||||
CLONEFILEAT = 462,
|
||||
OPENAT = 463,
|
||||
OPENAT_NOCANCEL = 464,
|
||||
RENAMEAT = 465,
|
||||
FACCESSAT = 466,
|
||||
FCHMODAT = 467,
|
||||
FCHOWNAT = 468,
|
||||
FSTATAT = 469,
|
||||
FSTATAT64 = 470,
|
||||
LINKAT = 471,
|
||||
UNLINKAT = 472,
|
||||
READLINKAT = 473,
|
||||
SYMLINKAT = 474,
|
||||
MKDIRAT = 475,
|
||||
GETATTRLISTAT = 476,
|
||||
PROC_TRACE_LOG = 477,
|
||||
BSDTHREAD_CTL = 478,
|
||||
OPENBYID_NP = 479,
|
||||
RECVMSG_X = 480,
|
||||
SENDMSG_X = 481,
|
||||
THREAD_SELFUSAGE = 482,
|
||||
CSRCTL = 483,
|
||||
GUARDED_OPEN_DPROTECTED_NP = 484,
|
||||
GUARDED_WRITE_NP = 485,
|
||||
GUARDED_PWRITE_NP = 486,
|
||||
GUARDED_WRITEV_NP = 487,
|
||||
RENAMEATX_NP = 488,
|
||||
MREMAP_ENCRYPTED = 489,
|
||||
NETAGENT_TRIGGER = 490,
|
||||
STACK_SNAPSHOT_WITH_CONFIG = 491,
|
||||
MICROSTACKSHOT = 492,
|
||||
GRAB_PGO_DATA = 493,
|
||||
PERSONA = 494,
|
||||
MACH_EVENTLINK_SIGNAL = 496,
|
||||
MACH_EVENTLINK_WAIT_UNTIL = 497,
|
||||
MACH_EVENTLINK_SIGNAL_WAIT_UNTIL = 498,
|
||||
WORK_INTERVAL_CTL = 499,
|
||||
GETENTROPY = 500,
|
||||
NECP_CLIENT_ACTION = 502,
|
||||
NEXUS_OPEN = 503,
|
||||
NEXUS_REGISTER = 504,
|
||||
NEXUS_DEREGISTER = 505,
|
||||
NEXUS_CREATE = 506,
|
||||
NEXUS_DESTROY = 507,
|
||||
NEXUS_GET_OPT = 508,
|
||||
NEXUS_SET_OPT = 509,
|
||||
CHANNEL_OPEN = 510,
|
||||
CHANNEL_GET_INFO = 511,
|
||||
CHANNEL_SYNC = 512,
|
||||
CHANNEL_GET_OPT = 513,
|
||||
CHANNEL_SET_OPT = 514,
|
||||
ULOCK_WAIT = 515,
|
||||
ULOCK_WAKE = 516,
|
||||
FCLONEFILEAT = 517,
|
||||
FS_SNAPSHOT = 518,
|
||||
TERMINATE_WITH_PAYLOAD = 520,
|
||||
ABORT_WITH_PAYLOAD = 521,
|
||||
NECP_SESSION_ACTION = 523,
|
||||
SETATTRLISTAT = 524,
|
||||
NET_QOS_GUIDELINE = 525,
|
||||
FMOUNT = 526,
|
||||
NTP_ADJTIME = 527,
|
||||
NTP_GETTIME = 528,
|
||||
OS_FAULT_WITH_PAYLOAD = 529,
|
||||
KQUEUE_WORKLOOP_CTL = 530,
|
||||
MACH_BRIDGE_REMOTE_TIME = 531,
|
||||
COALITION_LEDGER = 532,
|
||||
LOG_DATA = 533,
|
||||
MEMORYSTATUS_AVAILABLE_MEMORY = 534,
|
||||
OBJC_BP_ASSIST_CFG_NP = 535,
|
||||
SHARED_REGION_MAP_AND_SLIDE_2_NP = 536,
|
||||
PIVOT_ROOT = 537,
|
||||
TASK_INSPECT_FOR_PID = 538,
|
||||
TASK_READ_FOR_PID = 539,
|
||||
PREADV = 540,
|
||||
PWRITEV = 541,
|
||||
PREADV_NOCANCEL = 542,
|
||||
PWRITEV_NOCANCEL = 543,
|
||||
ULOCK_WAIT2 = 544,
|
||||
PROC_INFO_EXTENDED_ID = 545,
|
||||
TRACKER_ACTION = 546,
|
||||
DEBUG_SYSCALL_REJECT = 547,
|
||||
DEBUG_SYSCALL_REJECT_CONFIG = 548,
|
||||
GRAFTDMG = 549,
|
||||
MAP_WITH_LINKING_NP = 550,
|
||||
FREADLINK = 551,
|
||||
RECORD_SYSTEM_EVENT = 552,
|
||||
MKFIFOAT = 553,
|
||||
MKNODAT = 554,
|
||||
UNGRAFTDMG = 555,
|
||||
COALITION_POLICY_SET = 556,
|
||||
COALITION_POLICY_GET = 557,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
FRIDA_1.0 {
|
||||
local:
|
||||
*;
|
||||
};
|
||||
|
||||
GLIBC_2.2.5 {
|
||||
local:
|
||||
fopen*;
|
||||
fseeko*;
|
||||
ftello*;
|
||||
} FRIDA_1.0;
|
||||
@@ -144,10 +144,8 @@ frida_gadget_environment_ensure_debugger_breakpoints_only (void)
|
||||
|
||||
interceptor = gum_interceptor_obtain ();
|
||||
|
||||
gum_interceptor_attach (interceptor, exceptions_set, frida_dont_steal_my_breakpoints,
|
||||
NULL, GUM_ATTACH_FLAGS_NONE);
|
||||
gum_interceptor_attach (interceptor, exceptions_swap, frida_dont_steal_my_breakpoints,
|
||||
NULL, GUM_ATTACH_FLAGS_NONE);
|
||||
gum_interceptor_attach (interceptor, exceptions_set, frida_dont_steal_my_breakpoints, NULL);
|
||||
gum_interceptor_attach (interceptor, exceptions_swap, frida_dont_steal_my_breakpoints, NULL);
|
||||
|
||||
g_object_unref (interceptor);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
# include <signal.h>
|
||||
#endif
|
||||
#include <gumjs/gumscriptbackend.h>
|
||||
#ifdef HAVE_GIOOPENSSL
|
||||
#if defined (HAVE_GIOAPPLE)
|
||||
# include <gioapple.h>
|
||||
#elif defined (HAVE_GIOOPENSSL)
|
||||
# include <gioopenssl.h>
|
||||
#endif
|
||||
|
||||
@@ -91,7 +93,9 @@ frida_gadget_environment_init (void)
|
||||
|
||||
g_thread_set_garbage_handler (_frida_gadget_on_pending_thread_garbage, NULL);
|
||||
|
||||
#ifdef HAVE_GIOOPENSSL
|
||||
#if defined (HAVE_GIOAPPLE)
|
||||
g_io_module_apple_register ();
|
||||
#elif defined (HAVE_GIOOPENSSL)
|
||||
g_io_module_openssl_register ();
|
||||
#endif
|
||||
|
||||
@@ -118,6 +122,8 @@ frida_gadget_environment_deinit (void)
|
||||
|
||||
g_assert (worker_loop != NULL);
|
||||
|
||||
frida_libc_shim_prepare_to_deinit ();
|
||||
|
||||
source = g_idle_source_new ();
|
||||
g_source_set_priority (source, G_PRIORITY_LOW);
|
||||
g_source_set_callback (source, stop_worker_loop, NULL, NULL);
|
||||
|
||||
@@ -1810,6 +1810,11 @@ namespace Frida.Gadget {
|
||||
throw new Error.NOT_SUPPORTED ("Not possible when embedded");
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("Not possible when embedded");
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("Not possible when embedded");
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ elif host_os_family == 'darwin'
|
||||
extra_link_args += '-Wl,-exported_symbols_list,' + meson.current_source_dir() / symlist
|
||||
extra_link_depends += symlist
|
||||
else
|
||||
symscript = 'frida-gadget.version'
|
||||
symscript = (libc == 'glibc') ? 'frida-gadget-glibc.version' : 'frida-gadget.version'
|
||||
extra_link_args += '-Wl,--version-script,' + meson.current_source_dir() / symscript
|
||||
extra_link_depends += symscript
|
||||
endif
|
||||
|
||||
+4
-2
@@ -4,10 +4,12 @@ endif
|
||||
subdir('base')
|
||||
subdir('netif')
|
||||
subdir('pipe')
|
||||
subdir('payload')
|
||||
if have_in_tree_payloads
|
||||
subdir('payload')
|
||||
endif
|
||||
if have_local_backend
|
||||
subdir('agent')
|
||||
endif
|
||||
if build_gadget
|
||||
if have_in_tree_payloads and build_gadget
|
||||
subdir('gadget')
|
||||
endif
|
||||
@@ -179,6 +179,18 @@ namespace Frida {
|
||||
yield script_engine.load_script (script_id);
|
||||
}
|
||||
|
||||
public async void interrupt_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
|
||||
script_engine.interrupt_script (script_id);
|
||||
}
|
||||
|
||||
public async void terminate_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
|
||||
yield script_engine.terminate_script (script_id);
|
||||
}
|
||||
|
||||
public async void eternalize_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
|
||||
|
||||
@@ -65,7 +65,9 @@ namespace Frida {
|
||||
if (runtime != null) {
|
||||
var set_argv0 = (void *) runtime.find_export_by_name ("_Z27android_os_Process_setArgV0P7_JNIEnvP8_jobjectP8_jstring");
|
||||
if (set_argv0 != null) {
|
||||
interceptor.attach (set_argv0, listener, (void *) HookId.SET_ARGV0);
|
||||
var set_argv0_options = Gum.AttachOptions ();
|
||||
set_argv0_options.listener_function_data = (void *) HookId.SET_ARGV0;
|
||||
interceptor.attach (set_argv0, listener, set_argv0_options);
|
||||
child_recovery_behavior = DEFERRED_UNTIL_SET_ARGV0;
|
||||
}
|
||||
}
|
||||
@@ -73,8 +75,11 @@ namespace Frida {
|
||||
var selinux = Gum.Process.find_module_by_name ("libselinux.so");
|
||||
if (selinux != null) {
|
||||
var setcontext = (void *) selinux.find_export_by_name ("selinux_android_setcontext");
|
||||
if (setcontext != null)
|
||||
interceptor.attach (setcontext, listener, (void *) HookId.SET_CTX);
|
||||
if (setcontext != null) {
|
||||
var setcontext_options = Gum.AttachOptions ();
|
||||
setcontext_options.listener_function_data = (void *) HookId.SET_CTX;
|
||||
interceptor.attach (setcontext, listener, setcontext_options);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (FileError e) {
|
||||
@@ -82,7 +87,9 @@ namespace Frida {
|
||||
}
|
||||
#endif
|
||||
|
||||
interceptor.attach (fork_impl, listener, (void *) HookId.FORK);
|
||||
var fork_options = Gum.AttachOptions ();
|
||||
fork_options.listener_function_data = (void *) HookId.FORK;
|
||||
interceptor.attach (fork_impl, listener, fork_options);
|
||||
interceptor.replace (vfork_impl, fork_impl);
|
||||
}
|
||||
|
||||
|
||||
+404
-30
@@ -10,6 +10,7 @@
|
||||
# include <assert.h>
|
||||
# include <fcntl.h>
|
||||
# include <unistd.h>
|
||||
# include <wchar.h>
|
||||
#endif
|
||||
#if defined (HAVE_DARWIN)
|
||||
# include <sys/attr.h>
|
||||
@@ -23,23 +24,50 @@
|
||||
# include <xlocale.h>
|
||||
#endif
|
||||
|
||||
#undef __srget
|
||||
#undef feof
|
||||
#undef ferror
|
||||
#undef fgetwc
|
||||
#undef fopen
|
||||
#undef fopen64
|
||||
#undef fputwc
|
||||
#undef fseek
|
||||
#undef fseeko
|
||||
#undef fseeko64
|
||||
#undef ftell
|
||||
#undef ftello
|
||||
#undef ftello64
|
||||
#undef getc
|
||||
#undef getc_unlocked
|
||||
#undef getwc
|
||||
#undef memcpy
|
||||
#undef putc
|
||||
#undef putchar
|
||||
#undef putwc
|
||||
#undef setbuf
|
||||
#undef setbuffer
|
||||
#undef setlinebuf
|
||||
#undef snprintf
|
||||
#undef sprintf
|
||||
#undef stderr
|
||||
#undef stdin
|
||||
#undef stdout
|
||||
#undef ungetc
|
||||
#undef ungetwc
|
||||
#undef vsnprintf
|
||||
|
||||
#ifdef HAVE_MUSL
|
||||
# define FRIDA_STDIO_OPAQUE_FILE 1
|
||||
#endif
|
||||
|
||||
static gboolean frida_deinit_expected = FALSE;
|
||||
|
||||
void
|
||||
frida_libc_shim_prepare_to_deinit (void)
|
||||
{
|
||||
frida_deinit_expected = TRUE;
|
||||
}
|
||||
|
||||
#if defined (HAVE_WINDOWS) || defined (HAVE_ASAN)
|
||||
|
||||
void
|
||||
@@ -70,6 +98,9 @@ __attribute__ ((destructor)) static
|
||||
void
|
||||
frida_libc_shim_deinit (void)
|
||||
{
|
||||
if (!frida_deinit_expected)
|
||||
return;
|
||||
|
||||
# ifdef HAVE_ASAN
|
||||
gum_deinit ();
|
||||
# else
|
||||
@@ -134,16 +165,18 @@ struct _FridaFile
|
||||
int err;
|
||||
|
||||
guint8 * rbuf;
|
||||
gboolean owns_rbuf;
|
||||
size_t rcap;
|
||||
size_t rpos;
|
||||
size_t rlen;
|
||||
|
||||
guint8 * wbuf;
|
||||
gboolean owns_wbuf;
|
||||
size_t wcap;
|
||||
size_t wlen;
|
||||
|
||||
int ungot;
|
||||
gboolean has_ungot;
|
||||
guint8 ungot_buf[8];
|
||||
guint ungot_len;
|
||||
};
|
||||
|
||||
struct _FridaFileHandle
|
||||
@@ -171,6 +204,10 @@ struct _FridaDir
|
||||
|
||||
#endif
|
||||
|
||||
static FILE * frida_fopen_impl (const char * pathname, const char * mode);
|
||||
static int frida_fseek_impl (FILE * stream, off_t offset, int whence);
|
||||
static off_t frida_ftell_impl (FILE * stream);
|
||||
|
||||
static void frida_stdio_register_stream (FILE * stream);
|
||||
static void frida_stdio_unregister_stream (FILE * stream);
|
||||
#ifdef HAVE_FRIDA_DIR
|
||||
@@ -193,6 +230,8 @@ static void frida_parse_fopen_mode (const char * mode, int * oflags);
|
||||
|
||||
static int frida_write_formatted_to_fd (int fd, const char * format, va_list args);
|
||||
|
||||
static gboolean frida_utf8_expected_len (guint8 first, guint * out_len);
|
||||
|
||||
#ifdef HAVE_FRIDA_DIR
|
||||
static FridaDir * frida_dir_get_impl (DIR * dirp);
|
||||
static DIR * frida_dir_wrap (FridaDir * impl);
|
||||
@@ -317,6 +356,9 @@ frida_libc_shim_deinit (void)
|
||||
|
||||
fflush (NULL);
|
||||
|
||||
if (!frida_deinit_expected)
|
||||
return;
|
||||
|
||||
G_LOCK (frida_stdio);
|
||||
|
||||
g_hash_table_iter_init (&iter, frida_streams);
|
||||
@@ -639,8 +681,34 @@ asprintf_l (char ** ret, locale_t loc, const char * format, ...)
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_GLIBC
|
||||
|
||||
G_GNUC_INTERNAL FILE *
|
||||
frida_fopen_glibc_225 (const char * pathname, const char * mode)
|
||||
{
|
||||
return frida_fopen_impl (pathname, mode);
|
||||
}
|
||||
__asm__ (".symver frida_fopen_glibc_225,fopen@@GLIBC_2.2.5");
|
||||
|
||||
G_GNUC_INTERNAL FILE *
|
||||
frida_fopen64_glibc_225 (const char * pathname, const char * mode)
|
||||
{
|
||||
return frida_fopen_impl (pathname, mode);
|
||||
}
|
||||
__asm__ (".symver frida_fopen64_glibc_225,fopen64@@GLIBC_2.2.5");
|
||||
|
||||
#else
|
||||
|
||||
G_GNUC_INTERNAL FILE *
|
||||
fopen (const char * pathname, const char * mode)
|
||||
{
|
||||
return frida_fopen_impl (pathname, mode);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static FILE *
|
||||
frida_fopen_impl (const char * pathname, const char * mode)
|
||||
{
|
||||
FILE * result;
|
||||
int oflags, fd;
|
||||
@@ -714,8 +782,17 @@ setvbuf (FILE * stream, char * buf, int mode, size_t size)
|
||||
|
||||
if (mode == _IONBF)
|
||||
{
|
||||
g_clear_pointer (&f->rbuf, g_free);
|
||||
g_clear_pointer (&f->wbuf, g_free);
|
||||
if (f->owns_rbuf)
|
||||
g_clear_pointer (&f->rbuf, g_free);
|
||||
else
|
||||
f->rbuf = NULL;
|
||||
f->owns_rbuf = FALSE;
|
||||
|
||||
if (f->owns_wbuf)
|
||||
g_clear_pointer (&f->wbuf, g_free);
|
||||
else
|
||||
f->wbuf = NULL;
|
||||
f->owns_wbuf = FALSE;
|
||||
|
||||
f->rcap = 0;
|
||||
f->wcap = 0;
|
||||
@@ -727,13 +804,40 @@ setvbuf (FILE * stream, char * buf, int mode, size_t size)
|
||||
}
|
||||
|
||||
if (size == 0)
|
||||
size = FRIDA_STDIO_BUFSIZE;
|
||||
size = BUFSIZ;
|
||||
|
||||
if (!f->owns_rbuf)
|
||||
{
|
||||
f->rbuf = NULL;
|
||||
f->rcap = 0;
|
||||
}
|
||||
|
||||
f->rbuf = g_realloc (f->rbuf, size);
|
||||
f->wbuf = g_realloc (f->wbuf, size);
|
||||
|
||||
f->owns_rbuf = TRUE;
|
||||
f->rcap = size;
|
||||
f->wcap = size;
|
||||
|
||||
if (buf != NULL)
|
||||
{
|
||||
if (f->owns_wbuf)
|
||||
g_clear_pointer (&f->wbuf, g_free);
|
||||
|
||||
f->wbuf = (guint8 *) buf;
|
||||
f->owns_wbuf = FALSE;
|
||||
f->wcap = size;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!f->owns_wbuf)
|
||||
{
|
||||
f->wbuf = NULL;
|
||||
f->wcap = 0;
|
||||
}
|
||||
|
||||
f->wbuf = g_realloc (f->wbuf, size);
|
||||
f->owns_wbuf = TRUE;
|
||||
f->wcap = size;
|
||||
}
|
||||
|
||||
f->rpos = 0;
|
||||
f->rlen = 0;
|
||||
f->wlen = 0;
|
||||
@@ -741,6 +845,39 @@ setvbuf (FILE * stream, char * buf, int mode, size_t size)
|
||||
return 0;
|
||||
}
|
||||
|
||||
G_GNUC_INTERNAL void
|
||||
setbuf (FILE * stream, char * buf)
|
||||
{
|
||||
if (buf == NULL)
|
||||
(void) setvbuf (stream, NULL, _IONBF, 0);
|
||||
else
|
||||
(void) setvbuf (stream, buf, _IOFBF, BUFSIZ);
|
||||
}
|
||||
|
||||
#if defined (HAVE_DARWIN) || defined (HAVE_ANDROID) || defined (HAVE_FREEBSD)
|
||||
G_GNUC_INTERNAL int
|
||||
setlinebuf (FILE * stream)
|
||||
{
|
||||
return setvbuf (stream, NULL, _IOLBF, 0);
|
||||
}
|
||||
#else
|
||||
G_GNUC_INTERNAL void
|
||||
setlinebuf (FILE * stream)
|
||||
{
|
||||
setvbuf (stream, NULL, _IOLBF, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
G_GNUC_INTERNAL void
|
||||
#if defined (HAVE_DARWIN) || defined (HAVE_ANDROID) || defined (HAVE_FREEBSD)
|
||||
setbuffer (FILE * stream, char * buf, int size)
|
||||
#else
|
||||
setbuffer (FILE * stream, char * buf, size_t size)
|
||||
#endif
|
||||
{
|
||||
(void) setvbuf (stream, buf, (buf != NULL) ? _IOFBF : _IONBF, size);
|
||||
}
|
||||
|
||||
G_GNUC_INTERNAL int
|
||||
fflush (FILE * stream)
|
||||
{
|
||||
@@ -801,11 +938,8 @@ getc_unlocked (FILE * stream)
|
||||
|
||||
f = frida_file_get_impl (stream);
|
||||
|
||||
if (f->has_ungot)
|
||||
{
|
||||
f->has_ungot = FALSE;
|
||||
return f->ungot;
|
||||
}
|
||||
if (f->ungot_len != 0)
|
||||
return f->ungot_buf[--f->ungot_len];
|
||||
|
||||
if (f->buf_mode == _IONBF)
|
||||
{
|
||||
@@ -846,6 +980,64 @@ getc (FILE * stream)
|
||||
return getc_unlocked (stream);
|
||||
}
|
||||
|
||||
G_GNUC_INTERNAL int
|
||||
__srget (FILE * stream)
|
||||
{
|
||||
return getc_unlocked (stream);
|
||||
}
|
||||
|
||||
G_GNUC_INTERNAL wint_t
|
||||
getwc (FILE * stream)
|
||||
{
|
||||
return fgetwc (stream);
|
||||
}
|
||||
|
||||
G_GNUC_INTERNAL wint_t
|
||||
fgetwc (FILE * stream)
|
||||
{
|
||||
char bytes[4];
|
||||
guint need, have;
|
||||
int c;
|
||||
gunichar ch;
|
||||
|
||||
c = getc_unlocked (stream);
|
||||
if (c == EOF)
|
||||
return WEOF;
|
||||
|
||||
bytes[0] = c;
|
||||
|
||||
if (!frida_utf8_expected_len (bytes[0], &need))
|
||||
{
|
||||
errno = EILSEQ;
|
||||
frida_file_get_impl (stream)->err = errno;
|
||||
return WEOF;
|
||||
}
|
||||
|
||||
have = 1;
|
||||
while (have != need)
|
||||
{
|
||||
c = getc_unlocked (stream);
|
||||
if (c == EOF)
|
||||
{
|
||||
errno = EILSEQ;
|
||||
frida_file_get_impl (stream)->err = errno;
|
||||
return WEOF;
|
||||
}
|
||||
|
||||
bytes[have++] = c;
|
||||
}
|
||||
|
||||
ch = g_utf8_get_char_validated (bytes, (gssize) need);
|
||||
if (ch == (gunichar) -1 || ch == (gunichar) -2)
|
||||
{
|
||||
errno = EILSEQ;
|
||||
frida_file_get_impl (stream)->err = errno;
|
||||
return WEOF;
|
||||
}
|
||||
|
||||
return (wint_t) ch;
|
||||
}
|
||||
|
||||
G_GNUC_INTERNAL int
|
||||
ungetc (int c, FILE * stream)
|
||||
{
|
||||
@@ -856,16 +1048,46 @@ ungetc (int c, FILE * stream)
|
||||
if (c == EOF)
|
||||
return EOF;
|
||||
|
||||
if (f->has_ungot)
|
||||
if (f->ungot_len == G_N_ELEMENTS (f->ungot_buf))
|
||||
return EOF;
|
||||
|
||||
f->ungot = c;
|
||||
f->has_ungot = TRUE;
|
||||
f->ungot_buf[f->ungot_len++] = c;
|
||||
f->eof = FALSE;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
G_GNUC_INTERNAL wint_t
|
||||
ungetwc (wint_t wc, FILE * stream)
|
||||
{
|
||||
FridaFile * f;
|
||||
char tmp[4];
|
||||
int len;
|
||||
int i;
|
||||
|
||||
if (wc == WEOF)
|
||||
return WEOF;
|
||||
|
||||
f = frida_file_get_impl (stream);
|
||||
|
||||
len = g_unichar_to_utf8 ((gunichar) wc, tmp);
|
||||
if (len <= 0 || len > 4)
|
||||
{
|
||||
errno = EILSEQ;
|
||||
f->err = errno;
|
||||
return WEOF;
|
||||
}
|
||||
|
||||
if (f->ungot_len + len > G_N_ELEMENTS (f->ungot_buf))
|
||||
return WEOF;
|
||||
|
||||
for (i = 0; i != len; i++)
|
||||
f->ungot_buf[f->ungot_len++] = tmp[i];
|
||||
|
||||
f->eof = FALSE;
|
||||
return wc;
|
||||
}
|
||||
|
||||
G_GNUC_INTERNAL size_t
|
||||
fread (void * ptr, size_t size, size_t nmemb, FILE * stream)
|
||||
{
|
||||
@@ -884,10 +1106,9 @@ fread (void * ptr, size_t size, size_t nmemb, FILE * stream)
|
||||
|
||||
while (got != want)
|
||||
{
|
||||
if (f->has_ungot)
|
||||
if (f->ungot_len != 0)
|
||||
{
|
||||
out[got++] = (guint8) f->ungot;
|
||||
f->has_ungot = FALSE;
|
||||
out[got++] = f->ungot_buf[--f->ungot_len];
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1006,6 +1227,12 @@ fwrite (const void * ptr, size_t size, size_t nmemb, FILE * stream)
|
||||
return off / size;
|
||||
}
|
||||
|
||||
G_GNUC_INTERNAL int
|
||||
putc (int c, FILE * stream)
|
||||
{
|
||||
return fputc (c, stream);
|
||||
}
|
||||
|
||||
G_GNUC_INTERNAL int
|
||||
fputc (int c, FILE * stream)
|
||||
{
|
||||
@@ -1019,6 +1246,37 @@ fputc (int c, FILE * stream)
|
||||
return ch;
|
||||
}
|
||||
|
||||
G_GNUC_INTERNAL wint_t
|
||||
putwc (wchar_t wc, FILE * stream)
|
||||
{
|
||||
return fputwc (wc, stream);
|
||||
}
|
||||
|
||||
G_GNUC_INTERNAL wint_t
|
||||
fputwc (wchar_t wc, FILE * stream)
|
||||
{
|
||||
char tmp[4];
|
||||
int len;
|
||||
size_t n;
|
||||
|
||||
if (wc == WEOF)
|
||||
return WEOF;
|
||||
|
||||
len = g_unichar_to_utf8 ((gunichar) wc, tmp);
|
||||
if (len <= 0 || len > 4)
|
||||
{
|
||||
errno = EILSEQ;
|
||||
frida_file_get_impl (stream)->err = errno;
|
||||
return WEOF;
|
||||
}
|
||||
|
||||
n = fwrite (tmp, 1, len, stream);
|
||||
if (n != len)
|
||||
return WEOF;
|
||||
|
||||
return (wint_t) wc;
|
||||
}
|
||||
|
||||
G_GNUC_INTERNAL int
|
||||
fputs (const char * s, FILE * stream)
|
||||
{
|
||||
@@ -1062,6 +1320,84 @@ fgets (char * s, int size, FILE * stream)
|
||||
|
||||
G_GNUC_INTERNAL int
|
||||
fseek (FILE * stream, long offset, int whence)
|
||||
{
|
||||
return frida_fseek_impl (stream, (off_t) offset, whence);
|
||||
}
|
||||
|
||||
G_GNUC_INTERNAL long
|
||||
ftell (FILE * stream)
|
||||
{
|
||||
off_t pos = frida_ftell_impl (stream);
|
||||
if (pos == (off_t) -1)
|
||||
return -1;
|
||||
|
||||
return (long) pos;
|
||||
}
|
||||
|
||||
#ifdef HAVE_GLIBC
|
||||
|
||||
G_GNUC_INTERNAL int
|
||||
frida_fseeko_glibc_225 (FILE * stream, off_t offset, int whence)
|
||||
{
|
||||
return frida_fseek_impl (stream, offset, whence);
|
||||
}
|
||||
__asm__ (".symver frida_fseeko_glibc_225,fseeko@@GLIBC_2.2.5");
|
||||
|
||||
G_GNUC_INTERNAL int
|
||||
frida_fseeko64_glibc_225 (FILE * stream, off_t offset, int whence)
|
||||
{
|
||||
return frida_fseek_impl (stream, offset, whence);
|
||||
}
|
||||
__asm__ (".symver frida_fseeko64_glibc_225,fseeko64@@GLIBC_2.2.5");
|
||||
|
||||
G_GNUC_INTERNAL off_t
|
||||
frida_ftello_glibc_225 (FILE * stream)
|
||||
{
|
||||
return frida_ftell_impl (stream);
|
||||
}
|
||||
__asm__ (".symver frida_ftello_glibc_225,ftello@@GLIBC_2.2.5");
|
||||
|
||||
G_GNUC_INTERNAL off_t
|
||||
frida_ftello64_glibc_225 (FILE * stream)
|
||||
{
|
||||
return frida_ftell_impl (stream);
|
||||
}
|
||||
__asm__ (".symver frida_ftello64_glibc_225,ftello64@@GLIBC_2.2.5");
|
||||
|
||||
#else
|
||||
|
||||
G_GNUC_INTERNAL int
|
||||
fseeko (FILE * stream, off_t offset, int whence)
|
||||
{
|
||||
return frida_fseek_impl (stream, offset, whence);
|
||||
}
|
||||
|
||||
G_GNUC_INTERNAL off_t
|
||||
ftello (FILE * stream)
|
||||
{
|
||||
return frida_ftell_impl (stream);
|
||||
}
|
||||
|
||||
# ifndef HAVE_QNX
|
||||
|
||||
G_GNUC_INTERNAL int
|
||||
fseeko64 (FILE * stream, off_t offset, int whence)
|
||||
{
|
||||
return frida_fseek_impl (stream, offset, whence);
|
||||
}
|
||||
|
||||
G_GNUC_INTERNAL off_t
|
||||
ftello64 (FILE * stream)
|
||||
{
|
||||
return frida_ftell_impl (stream);
|
||||
}
|
||||
|
||||
# endif
|
||||
|
||||
#endif
|
||||
|
||||
static int
|
||||
frida_fseek_impl (FILE * stream, off_t offset, int whence)
|
||||
{
|
||||
FridaFile * f;
|
||||
off_t r;
|
||||
@@ -1073,10 +1409,10 @@ fseek (FILE * stream, long offset, int whence)
|
||||
|
||||
f->rpos = 0;
|
||||
f->rlen = 0;
|
||||
f->has_ungot = FALSE;
|
||||
f->ungot_len = 0;
|
||||
f->eof = FALSE;
|
||||
|
||||
r = frida_lseek_nointr (f->fd, (off_t) offset, whence);
|
||||
r = frida_lseek_nointr (f->fd, offset, whence);
|
||||
if (r == (off_t) -1)
|
||||
{
|
||||
f->err = errno;
|
||||
@@ -1086,8 +1422,8 @@ fseek (FILE * stream, long offset, int whence)
|
||||
return 0;
|
||||
}
|
||||
|
||||
G_GNUC_INTERNAL long
|
||||
ftell (FILE * stream)
|
||||
static off_t
|
||||
frida_ftell_impl (FILE * stream)
|
||||
{
|
||||
FridaFile * f;
|
||||
off_t pos;
|
||||
@@ -1104,10 +1440,9 @@ ftell (FILE * stream)
|
||||
if (f->buf_mode != _IONBF)
|
||||
pos -= (off_t) (f->rlen - f->rpos);
|
||||
|
||||
if (f->has_ungot)
|
||||
pos -= 1;
|
||||
pos -= f->ungot_len;
|
||||
|
||||
return (long) pos;
|
||||
return pos;
|
||||
}
|
||||
|
||||
G_GNUC_INTERNAL void
|
||||
@@ -1718,10 +2053,12 @@ frida_file_new (int fd, gboolean close_fd, int buf_mode)
|
||||
if (buf_mode != _IONBF)
|
||||
{
|
||||
f->rcap = FRIDA_STDIO_BUFSIZE;
|
||||
f->wcap = FRIDA_STDIO_BUFSIZE;
|
||||
|
||||
f->rbuf = g_malloc (f->rcap);
|
||||
f->owns_rbuf = TRUE;
|
||||
|
||||
f->wcap = FRIDA_STDIO_BUFSIZE;
|
||||
f->wbuf = g_malloc (f->wcap);
|
||||
f->owns_wbuf = TRUE;
|
||||
}
|
||||
|
||||
return f;
|
||||
@@ -1733,8 +2070,15 @@ frida_file_free (FridaFile * f)
|
||||
if (f == NULL)
|
||||
return;
|
||||
|
||||
g_clear_pointer (&f->rbuf, g_free);
|
||||
g_clear_pointer (&f->wbuf, g_free);
|
||||
if (f->owns_rbuf)
|
||||
g_clear_pointer (&f->rbuf, g_free);
|
||||
else
|
||||
f->rbuf = NULL;
|
||||
|
||||
if (f->owns_wbuf)
|
||||
g_clear_pointer (&f->wbuf, g_free);
|
||||
else
|
||||
f->wbuf = NULL;
|
||||
|
||||
g_free (f);
|
||||
}
|
||||
@@ -1866,6 +2210,36 @@ beach:
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
frida_utf8_expected_len (guint8 first, guint * out_len)
|
||||
{
|
||||
if (first < 0x80)
|
||||
{
|
||||
*out_len = 1;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if ((first & 0xe0) == 0xc0)
|
||||
{
|
||||
*out_len = 2;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if ((first & 0xf0) == 0xe0)
|
||||
{
|
||||
*out_len = 3;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if ((first & 0xf8) == 0xf0)
|
||||
{
|
||||
*out_len = 4;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#ifdef HAVE_FRIDA_DIR
|
||||
|
||||
static FridaDir *
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
namespace Frida {
|
||||
public extern void libc_shim_prepare_to_deinit ();
|
||||
#if DARWIN
|
||||
public extern void libc_shim_deinit ();
|
||||
#endif
|
||||
|
||||
@@ -166,6 +166,14 @@ namespace Frida {
|
||||
yield get_instance (script_id).load ();
|
||||
}
|
||||
|
||||
public void interrupt_script (AgentScriptId script_id) throws Error {
|
||||
get_instance (script_id).interrupt ();
|
||||
}
|
||||
|
||||
public async void terminate_script (AgentScriptId script_id) throws Error {
|
||||
yield get_instance (script_id).terminate ();
|
||||
}
|
||||
|
||||
public Gum.Script eternalize_script (AgentScriptId script_id) throws Error {
|
||||
var instance = get_instance (script_id);
|
||||
|
||||
@@ -354,6 +362,16 @@ namespace Frida {
|
||||
load_request.resolve (true);
|
||||
}
|
||||
|
||||
public void interrupt () {
|
||||
script.interrupt ();
|
||||
}
|
||||
|
||||
public async void terminate () {
|
||||
script.interrupt ();
|
||||
|
||||
yield close ();
|
||||
}
|
||||
|
||||
private Promise<bool> unload () {
|
||||
var request = new Promise<bool> ();
|
||||
|
||||
|
||||
@@ -59,7 +59,9 @@ namespace Frida {
|
||||
|
||||
interceptor.attach ((void *) posix_spawn, this);
|
||||
|
||||
interceptor.replace (execve, (void *) replacement_execve, this);
|
||||
var replace_options = Gum.ReplaceOptions ();
|
||||
replace_options.replacement_data = this;
|
||||
interceptor.replace (execve, (void *) replacement_execve, null, replace_options);
|
||||
#else
|
||||
Gum.Address execve = 0;
|
||||
#if ANDROID
|
||||
|
||||
@@ -31,9 +31,11 @@ namespace Frida {
|
||||
thread_suspend = (ThreadSuspendFunc) kernel.find_export_by_name ("thread_suspend");
|
||||
thread_resume = (ThreadResumeFunc) kernel.find_export_by_name ("thread_resume");
|
||||
|
||||
interceptor.replace ((void *) task_threads, (void *) replacement_task_threads, this);
|
||||
interceptor.replace ((void *) thread_suspend, (void *) replacement_thread_suspend, this);
|
||||
interceptor.replace ((void *) thread_resume, (void *) replacement_thread_resume, this);
|
||||
var replace_options = Gum.ReplaceOptions ();
|
||||
replace_options.replacement_data = this;
|
||||
interceptor.replace ((void *) task_threads, (void *) replacement_task_threads, null, replace_options);
|
||||
interceptor.replace ((void *) thread_suspend, (void *) replacement_thread_suspend, null, replace_options);
|
||||
interceptor.replace ((void *) thread_resume, (void *) replacement_thread_resume, null, replace_options);
|
||||
}
|
||||
|
||||
public override void dispose () {
|
||||
|
||||
@@ -2,29 +2,14 @@
|
||||
|
||||
#ifdef HAVE_DARWIN
|
||||
|
||||
#include <capstone.h>
|
||||
#include <gum/gumdarwin.h>
|
||||
#include <gum/gummemory.h>
|
||||
#include <ptrauth.h>
|
||||
|
||||
#define FRIDA_MH_MAGIC_64 0xfeedfacf
|
||||
#define FRIDA_LIBUNWIND_PATH "/usr/lib/system/libunwind.dylib"
|
||||
#define FRIDA_UNWIND_CURSOR_VTABLE_OFFSET_SET_INFO 0x68
|
||||
#define FRIDA_UNWIND_CURSOR_VTABLE_OFFSET_GET_REG 0x18
|
||||
#define FRIDA_FP_TO_SP(fp) (fp + 0x10)
|
||||
#ifdef HAVE_ARM64
|
||||
# define FRIDA_UNWIND_CURSOR_unwindInfoMissing 0x268
|
||||
# define FRIDA_UNWAARCH64_X29 29
|
||||
# define FRIDA_STRIP_MASK 0x0000007fffffffffULL
|
||||
#else
|
||||
# define FRIDA_UNWIND_CURSOR_unwindInfoMissing 0x100
|
||||
# define FRIDA_UNWX86_64_RBP 6
|
||||
#endif
|
||||
|
||||
typedef struct _FridaFillInfoContext FridaFillInfoContext;
|
||||
typedef struct _FridaDyldUnwindSections FridaDyldUnwindSections;
|
||||
typedef struct _FridaCreateArgs FridaCreateArgs;
|
||||
typedef struct _FridaUnwindHookState FridaUnwindHookState;
|
||||
|
||||
struct _FridaFillInfoContext
|
||||
{
|
||||
@@ -47,37 +32,9 @@ struct _FridaCreateArgs
|
||||
GumAddress range_end;
|
||||
};
|
||||
|
||||
struct _FridaUnwindHookState
|
||||
{
|
||||
gpointer vtable;
|
||||
gssize shift;
|
||||
gpointer * set_info_slot;
|
||||
gpointer set_info_original;
|
||||
void (* set_info) (gpointer cursor, gint is_return_address);
|
||||
gpointer (* get_reg) (gpointer cursor, gint reg);
|
||||
GumInterceptor * interceptor;
|
||||
};
|
||||
|
||||
#if __has_feature (ptrauth_calls)
|
||||
# define FRIDA_RESIGN_PTR(x) GSIZE_TO_POINTER (gum_sign_code_address (gum_strip_code_address (GUM_ADDRESS (x))))
|
||||
#else
|
||||
# define FRIDA_RESIGN_PTR(x) (x)
|
||||
#endif
|
||||
|
||||
static FridaDyldUnwindSections * frida_get_cached_sections (GumAddress range_start, GumAddress range_end);
|
||||
static FridaDyldUnwindSections * frida_create_cached_sections (FridaCreateArgs * args);
|
||||
static gboolean frida_fill_info (const GumDarwinSectionDetails * details, FridaFillInfoContext * ctx);
|
||||
static void frida_unwind_cursor_set_info_replacement (gpointer cursor, gint is_return_address);
|
||||
static gpointer frida_find_vtable (void);
|
||||
static gboolean frida_compute_vtable_shift (gpointer vtable, gssize * shift);
|
||||
#ifdef HAVE_ARM64
|
||||
static gboolean frida_find_bss_range (const GumSectionDetails * details, GumMemoryRange * range);
|
||||
#else
|
||||
static gboolean frida_is_empty_function (GumAddress address);
|
||||
static gboolean frida_has_first_match (GumAddress address, gsize size, gboolean * matches);
|
||||
#endif
|
||||
|
||||
static FridaUnwindHookState * state = NULL;
|
||||
|
||||
void
|
||||
_frida_unwind_sitter_fill_unwind_sections (GumAddress invader_start, GumAddress invader_end, void * info)
|
||||
@@ -92,63 +49,6 @@ _frida_unwind_sitter_fill_unwind_sections (GumAddress invader_start, GumAddress
|
||||
memcpy (unwind_sections, cached, sizeof (FridaDyldUnwindSections));
|
||||
}
|
||||
|
||||
void
|
||||
_frida_unwind_sitter_hook_libunwind (void)
|
||||
{
|
||||
#if GLIB_SIZEOF_VOID_P == 8
|
||||
gpointer * set_info_slot;
|
||||
gpointer get_reg_impl;
|
||||
|
||||
if (state != NULL)
|
||||
return;
|
||||
|
||||
state = g_slice_new0 (FridaUnwindHookState);
|
||||
if (state == NULL)
|
||||
return;
|
||||
|
||||
state->vtable = frida_find_vtable ();
|
||||
if (state->vtable == NULL)
|
||||
goto unsupported_version;
|
||||
|
||||
if (!frida_compute_vtable_shift (state->vtable, &state->shift))
|
||||
goto unsupported_version;
|
||||
|
||||
set_info_slot = (gpointer *) (GUM_ADDRESS (state->vtable) + FRIDA_UNWIND_CURSOR_VTABLE_OFFSET_SET_INFO + state->shift);
|
||||
get_reg_impl = *(gpointer *) (GUM_ADDRESS (state->vtable) + FRIDA_UNWIND_CURSOR_VTABLE_OFFSET_GET_REG + state->shift);
|
||||
|
||||
state->set_info_slot = set_info_slot;
|
||||
state->set_info_original = *set_info_slot;
|
||||
state->set_info = FRIDA_RESIGN_PTR (state->set_info_original);
|
||||
state->get_reg = FRIDA_RESIGN_PTR (get_reg_impl);
|
||||
|
||||
state->interceptor = gum_interceptor_obtain ();
|
||||
|
||||
if (gum_interceptor_replace (state->interceptor, state->set_info_original, frida_unwind_cursor_set_info_replacement, NULL, NULL)
|
||||
!= GUM_REPLACE_OK)
|
||||
goto unsupported_version;
|
||||
|
||||
return;
|
||||
|
||||
unsupported_version:
|
||||
g_slice_free (FridaUnwindHookState, state);
|
||||
state = NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
_frida_unwind_sitter_unhook_libunwind (void)
|
||||
{
|
||||
if (state == NULL)
|
||||
return;
|
||||
|
||||
gum_interceptor_revert (state->interceptor, state->set_info_original);
|
||||
|
||||
g_object_unref (state->interceptor);
|
||||
|
||||
g_slice_free (FridaUnwindHookState, state);
|
||||
state = NULL;
|
||||
}
|
||||
|
||||
static FridaDyldUnwindSections *
|
||||
frida_get_cached_sections (GumAddress range_start, GumAddress range_end)
|
||||
{
|
||||
@@ -187,7 +87,8 @@ frida_create_cached_sections (FridaCreateArgs * args)
|
||||
cached_sections = g_slice_new0 (FridaDyldUnwindSections);
|
||||
cached_sections->mh = header;
|
||||
|
||||
module = gum_darwin_module_new_from_memory ("Frida", mach_task_self (), GPOINTER_TO_SIZE (header), GUM_DARWIN_MODULE_FLAGS_NONE, NULL);
|
||||
module = gum_darwin_module_new_from_memory ("Frida", mach_task_self (), GPOINTER_TO_SIZE (header),
|
||||
GUM_DARWIN_MODULE_FLAGS_NONE, NULL);
|
||||
if (module == NULL)
|
||||
return cached_sections;
|
||||
|
||||
@@ -222,308 +123,4 @@ frida_fill_info (const GumDarwinSectionDetails * details, FridaFillInfoContext *
|
||||
return ctx->missing_info > 0;
|
||||
}
|
||||
|
||||
static void
|
||||
frida_unwind_cursor_set_info_replacement (gpointer self, gint is_return_address)
|
||||
{
|
||||
gboolean missing_info;
|
||||
GumAddress fp, stored_pc;
|
||||
gpointer * stored_pc_slot;
|
||||
#if defined (HAVE_ARM64) && !__has_feature (ptrauth_calls)
|
||||
gboolean was_signed = FALSE;
|
||||
#endif
|
||||
|
||||
if (state == NULL)
|
||||
return;
|
||||
|
||||
state->set_info (self, is_return_address);
|
||||
|
||||
#ifdef HAVE_ARM64
|
||||
fp = GUM_ADDRESS (state->get_reg (self, FRIDA_UNWAARCH64_X29));
|
||||
#else
|
||||
fp = GUM_ADDRESS (state->get_reg (self, FRIDA_UNWX86_64_RBP));
|
||||
#endif
|
||||
if (fp == 0 || fp == -1)
|
||||
return;
|
||||
|
||||
missing_info = *((guint8 *) self + FRIDA_UNWIND_CURSOR_unwindInfoMissing);
|
||||
|
||||
stored_pc_slot = GSIZE_TO_POINTER (fp + GLIB_SIZEOF_VOID_P);
|
||||
stored_pc = GUM_ADDRESS (*stored_pc_slot);
|
||||
#if __has_feature (ptrauth_calls)
|
||||
stored_pc = gum_strip_code_address (stored_pc);
|
||||
#elif defined (HAVE_ARM64)
|
||||
was_signed = (stored_pc & ~FRIDA_STRIP_MASK) != 0ULL;
|
||||
if (was_signed)
|
||||
stored_pc &= FRIDA_STRIP_MASK;
|
||||
#endif
|
||||
|
||||
if (!missing_info)
|
||||
{
|
||||
GumAddress translated;
|
||||
|
||||
translated = GUM_ADDRESS (gum_invocation_stack_translate (gum_interceptor_get_current_stack (), GSIZE_TO_POINTER (stored_pc)));
|
||||
if (translated != stored_pc)
|
||||
{
|
||||
#if __has_feature (ptrauth_calls)
|
||||
*stored_pc_slot = ptrauth_sign_unauthenticated (
|
||||
ptrauth_strip (GSIZE_TO_POINTER (translated), ptrauth_key_asia), ptrauth_key_asib, FRIDA_FP_TO_SP (fp));
|
||||
#elif defined (HAVE_ARM64)
|
||||
if (was_signed)
|
||||
{
|
||||
GumAddress resigned;
|
||||
|
||||
asm volatile (
|
||||
"mov x17, %1\n\t"
|
||||
"mov x16, %2\n\t"
|
||||
".byte 0x5f, 0x21, 0x03, 0xd5\n\t" /* pacib1716 */
|
||||
"mov %0, x17\n\t"
|
||||
: "=r" (resigned)
|
||||
: "r" (translated & FRIDA_STRIP_MASK),
|
||||
"r" (FRIDA_FP_TO_SP (fp))
|
||||
: "x16", "x17"
|
||||
);
|
||||
|
||||
*stored_pc_slot = GSIZE_TO_POINTER (resigned);
|
||||
}
|
||||
else
|
||||
{
|
||||
*stored_pc_slot = GSIZE_TO_POINTER (translated);
|
||||
}
|
||||
#else
|
||||
*stored_pc_slot = GSIZE_TO_POINTER (translated);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static gpointer
|
||||
frida_find_vtable (void)
|
||||
{
|
||||
GumAddress result = 0;
|
||||
GumModule * libunwind;
|
||||
GumAddress export;
|
||||
uint64_t address;
|
||||
G_GNUC_UNUSED cs_err err;
|
||||
csh capstone;
|
||||
cs_insn * insn = NULL;
|
||||
const uint8_t * code;
|
||||
size_t size;
|
||||
const size_t max_size = 2048;
|
||||
|
||||
libunwind = gum_process_find_module_by_name (FRIDA_LIBUNWIND_PATH);
|
||||
if (libunwind == NULL)
|
||||
goto beach;
|
||||
|
||||
export = gum_module_find_export_by_name (libunwind, "unw_init_local");
|
||||
if (export == 0)
|
||||
export = gum_module_find_export_by_name (libunwind, "_Unwind_RaiseException");
|
||||
if (export == 0)
|
||||
goto beach;
|
||||
export = gum_strip_code_address (export);
|
||||
address = export;
|
||||
|
||||
#ifdef HAVE_ARM64
|
||||
cs_arch_register_arm64 ();
|
||||
err = cs_open (CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN, &capstone);
|
||||
#else
|
||||
cs_arch_register_x86 ();
|
||||
err = cs_open (CS_ARCH_X86, CS_MODE_64, &capstone);
|
||||
#endif
|
||||
g_assert (err == CS_ERR_OK);
|
||||
|
||||
err = cs_option (capstone, CS_OPT_DETAIL, CS_OPT_ON);
|
||||
g_assert (err == CS_ERR_OK);
|
||||
|
||||
insn = cs_malloc (capstone);
|
||||
code = GSIZE_TO_POINTER (export);
|
||||
size = max_size;
|
||||
|
||||
#ifdef HAVE_ARM64
|
||||
{
|
||||
GumAddress last_adrp;
|
||||
guint last_adrp_reg;
|
||||
GumMemoryRange bss_range;
|
||||
|
||||
bss_range.base_address = 0;
|
||||
gum_module_enumerate_sections (libunwind, (GumFoundSectionFunc) frida_find_bss_range, &bss_range);
|
||||
|
||||
while (cs_disasm_iter (capstone, &code, &size, &address, insn))
|
||||
{
|
||||
if (insn->id == ARM64_INS_RET || insn->id == ARM64_INS_RETAA || insn->id == ARM64_INS_RETAB)
|
||||
break;
|
||||
if (insn->id == ARM64_INS_ADRP)
|
||||
{
|
||||
if (result != 0)
|
||||
break;
|
||||
last_adrp = (GumAddress) insn->detail->arm64.operands[1].imm;
|
||||
last_adrp_reg = insn->detail->arm64.operands[0].reg;
|
||||
}
|
||||
else if (insn->id == ARM64_INS_ADD && insn->detail->arm64.operands[0].reg == last_adrp_reg)
|
||||
{
|
||||
GumAddress candidate;
|
||||
gboolean is_bss;
|
||||
|
||||
candidate = last_adrp + (GumAddress) insn->detail->arm64.operands[2].imm;
|
||||
|
||||
is_bss = bss_range.base_address != 0 &&
|
||||
bss_range.base_address <= candidate &&
|
||||
candidate < bss_range.base_address + bss_range.size;
|
||||
if (!is_bss)
|
||||
{
|
||||
if (result == 0)
|
||||
{
|
||||
result = candidate;
|
||||
last_adrp = candidate;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (result != 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
while (cs_disasm_iter (capstone, &code, &size, &address, insn))
|
||||
{
|
||||
if (insn->id == X86_INS_RET)
|
||||
break;
|
||||
if (insn->id == X86_INS_LEA)
|
||||
{
|
||||
const cs_x86_op * op = &insn->detail->x86.operands[1];
|
||||
if (op->type == X86_OP_MEM && op->mem.base == X86_REG_RIP)
|
||||
{
|
||||
result = address + op->mem.disp * op->mem.scale;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (insn != NULL)
|
||||
cs_free (insn, 1);
|
||||
cs_close (&capstone);
|
||||
|
||||
beach:
|
||||
g_clear_object (&libunwind);
|
||||
|
||||
return GSIZE_TO_POINTER (result);
|
||||
}
|
||||
|
||||
#ifdef HAVE_ARM64
|
||||
|
||||
static gboolean
|
||||
frida_find_bss_range (const GumSectionDetails * details, GumMemoryRange * range)
|
||||
{
|
||||
if (strcmp (details->name, "__bss") == 0)
|
||||
{
|
||||
range->base_address = details->address;
|
||||
range->size = details->size;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
frida_compute_vtable_shift (gpointer vtable, gssize * shift)
|
||||
{
|
||||
gboolean result = FALSE;
|
||||
G_GNUC_UNUSED cs_err err;
|
||||
csh capstone;
|
||||
cs_insn * insn = NULL;
|
||||
const uint8_t * code;
|
||||
uint64_t address;
|
||||
size_t size = 4;
|
||||
|
||||
cs_arch_register_arm64 ();
|
||||
err = cs_open (CS_ARCH_ARM64, CS_MODE_LITTLE_ENDIAN, &capstone);
|
||||
g_assert (err == CS_ERR_OK);
|
||||
|
||||
insn = cs_malloc (capstone);
|
||||
code = gum_strip_code_pointer (*(gpointer *) vtable);
|
||||
address = GPOINTER_TO_SIZE (code);
|
||||
|
||||
if (cs_disasm_iter (capstone, &code, &size, &address, insn))
|
||||
{
|
||||
if (insn->id == ARM64_INS_RET || insn->id == ARM64_INS_RETAA || insn->id == ARM64_INS_RETAB)
|
||||
*shift = 0;
|
||||
else
|
||||
*shift = -2 * GLIB_SIZEOF_VOID_P;
|
||||
|
||||
result = TRUE;
|
||||
}
|
||||
|
||||
if (insn != NULL)
|
||||
cs_free (insn, 1);
|
||||
cs_close (&capstone);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static gboolean
|
||||
frida_compute_vtable_shift (gpointer vtable, gssize * shift)
|
||||
{
|
||||
GumAddress cursor = GPOINTER_TO_SIZE (vtable);
|
||||
GumAddress error = cursor + 16 * GLIB_SIZEOF_VOID_P;
|
||||
|
||||
while (cursor < error && *(gpointer *) GSIZE_TO_POINTER (cursor) == NULL)
|
||||
cursor += GLIB_SIZEOF_VOID_P;
|
||||
if (cursor == error)
|
||||
return FALSE;
|
||||
|
||||
if (frida_is_empty_function (GUM_ADDRESS (*(gpointer *) GSIZE_TO_POINTER (cursor))) &&
|
||||
frida_is_empty_function (GUM_ADDRESS (*(gpointer *) GSIZE_TO_POINTER (cursor + GLIB_SIZEOF_VOID_P))))
|
||||
{
|
||||
*shift = cursor - GPOINTER_TO_SIZE (vtable);
|
||||
}
|
||||
else
|
||||
{
|
||||
*shift = cursor - GPOINTER_TO_SIZE (vtable) - 2 * GLIB_SIZEOF_VOID_P;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
frida_is_empty_function (GumAddress address)
|
||||
{
|
||||
gboolean matches = FALSE;
|
||||
GumMemoryRange range;
|
||||
GumMatchPattern * pattern;
|
||||
|
||||
range.base_address = address;
|
||||
range.size = 6;
|
||||
|
||||
/*
|
||||
* 55 push rbp
|
||||
* 4889e5 mov rbp, rsp
|
||||
* 5d pop rbp
|
||||
* c3 ret
|
||||
*/
|
||||
pattern = gum_match_pattern_new_from_string ("55 48 89 e5 5d c3");
|
||||
|
||||
gum_memory_scan (&range, pattern, (GumMemoryScanMatchFunc) frida_has_first_match, &matches);
|
||||
|
||||
gum_match_pattern_unref (pattern);
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
frida_has_first_match (GumAddress address, gsize size, gboolean * matches)
|
||||
{
|
||||
*matches = TRUE;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,65 +1,41 @@
|
||||
namespace Frida {
|
||||
#if DARWIN
|
||||
public sealed class UnwindSitter : Object {
|
||||
public sealed class UnwindSitter : Object, Gum.UnwindSectionsProvider {
|
||||
public weak ProcessInvader invader {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private DyldFindUnwindSectionsFunc dyld_find_unwind_sections;
|
||||
public unowned Gum.MemoryRange? range {
|
||||
get { return _range; }
|
||||
}
|
||||
|
||||
private const string LIBDYLD = "/usr/lib/system/libdyld.dylib";
|
||||
|
||||
[CCode (has_target = false)]
|
||||
private delegate int DyldFindUnwindSectionsFunc (void * addr, void * info);
|
||||
private Gum.MemoryRange? _range;
|
||||
private Gum.UnwindBroker broker;
|
||||
|
||||
public UnwindSitter (ProcessInvader invader) {
|
||||
Object (invader: invader);
|
||||
}
|
||||
|
||||
construct {
|
||||
var interceptor = Gum.Interceptor.obtain ();
|
||||
_range = invader.get_memory_range ();
|
||||
|
||||
dyld_find_unwind_sections = (DyldFindUnwindSectionsFunc)
|
||||
Gum.Process.find_module_by_name (LIBDYLD).find_export_by_name ("_dyld_find_unwind_sections");
|
||||
|
||||
interceptor.replace ((void *) dyld_find_unwind_sections, (void *) replacement_dyld_find_unwind_sections, this);
|
||||
|
||||
_hook_libunwind ();
|
||||
broker = Gum.UnwindBroker.obtain ();
|
||||
broker.add_sections_provider (this);
|
||||
}
|
||||
|
||||
public override void dispose () {
|
||||
var interceptor = Gum.Interceptor.obtain ();
|
||||
|
||||
_unhook_libunwind ();
|
||||
interceptor.revert ((void *) dyld_find_unwind_sections);
|
||||
broker.remove_sections_provider (this);
|
||||
|
||||
base.dispose ();
|
||||
}
|
||||
|
||||
private static int replacement_dyld_find_unwind_sections (void * addr, void * info) {
|
||||
unowned Gum.InvocationContext context = Gum.Interceptor.get_current_invocation ();
|
||||
unowned UnwindSitter sitter = (UnwindSitter) context.get_replacement_data ();
|
||||
|
||||
Gum.MemoryRange range = sitter.invader.get_memory_range ();
|
||||
var range_end = range.base_address + range.size;
|
||||
|
||||
var address = Gum.Address.from_pointer (addr);
|
||||
#if ARM64
|
||||
address &= 0x7ffffffffULL;
|
||||
#endif
|
||||
var is_ours = address >= range.base_address && address < range_end;
|
||||
if (!is_ours)
|
||||
return sitter.dyld_find_unwind_sections (addr, info);
|
||||
|
||||
_fill_unwind_sections (range.base_address, range_end, info);
|
||||
|
||||
return 1;
|
||||
public bool fill (Gum.Address address, void * info) {
|
||||
_fill_unwind_sections (_range.base_address, _range.base_address + _range.size, info);
|
||||
return true;
|
||||
}
|
||||
|
||||
public extern static void _fill_unwind_sections (Gum.Address invader_start, Gum.Address invader_end, void * info);
|
||||
public extern static void _hook_libunwind ();
|
||||
public extern static void _unhook_libunwind ();
|
||||
}
|
||||
#else
|
||||
public sealed class UnwindSitter : Object {
|
||||
|
||||
+301
-78
@@ -130,6 +130,10 @@ if host_os_family == 'darwin'
|
||||
add_languages('objc', 'objcpp', native: false)
|
||||
endif
|
||||
|
||||
if meson.is_cross_build()
|
||||
add_languages('c', 'cpp', 'vala', native: true)
|
||||
endif
|
||||
|
||||
python = import('python').find_installation()
|
||||
|
||||
if cc.get_argument_syntax() == 'msvc'
|
||||
@@ -224,15 +228,21 @@ helper_name = 'frida-helper' + exe_suffix
|
||||
agent_name = 'frida-agent' + shlib_suffix
|
||||
gadget_name = 'frida-gadget' + shlib_suffix
|
||||
|
||||
root_asset_dir = get_option('libdir') / 'frida'
|
||||
frida_libdir_name = f'frida-@api_version@'
|
||||
root_asset_dir = get_option('libdir') / frida_libdir_name
|
||||
if host_os_family == 'darwin'
|
||||
asset_dir = root_asset_dir
|
||||
default_asset_path_template = get_option('prefix') / get_option('libdir') / 'frida'
|
||||
default_asset_path_template = get_option('prefix') / get_option('libdir') / frida_libdir_name
|
||||
elif host_os_family == 'windows'
|
||||
asset_dir = root_asset_dir / host_abi
|
||||
asset_dir_modern = root_asset_dir / 'x86_64'
|
||||
asset_dir_legacy = root_asset_dir / 'x86'
|
||||
default_asset_path_template = get_option('prefix') / get_option('libdir') / frida_libdir_name / '<arch>'
|
||||
else
|
||||
asset_dir = root_asset_dir / host_cpu_mode
|
||||
asset_dir_modern = root_asset_dir / '64'
|
||||
asset_dir_legacy = root_asset_dir / '32'
|
||||
default_asset_path_template = get_option('prefix') / get_option('libdir') / 'frida' / '<arch>'
|
||||
default_asset_path_template = get_option('prefix') / get_option('libdir') / frida_libdir_name / '<arch>'
|
||||
endif
|
||||
|
||||
asset_path_template = get_option('asset_path_template')
|
||||
@@ -240,11 +250,22 @@ if asset_path_template == ''
|
||||
asset_path_template = default_asset_path_template
|
||||
endif
|
||||
|
||||
if get_option('assets') == 'embedded'
|
||||
is_shared = get_option('default_library') == 'shared'
|
||||
|
||||
assets_mode = get_option('assets')
|
||||
if assets_mode == 'auto'
|
||||
assets_mode = is_shared ? 'installed' : 'embedded'
|
||||
endif
|
||||
|
||||
cdata.set_quoted('FRIDA_LIBDIR', get_option('libdir'))
|
||||
cdata.set_quoted('FRIDA_LIBDIR_NAME', frida_libdir_name)
|
||||
|
||||
if assets_mode == 'embedded'
|
||||
cdata.set('HAVE_EMBEDDED_ASSETS', 1)
|
||||
vala_flags += '--define=HAVE_EMBEDDED_ASSETS'
|
||||
else
|
||||
cdata.set_quoted('FRIDA_HELPER_PATH', asset_path_template / helper_name)
|
||||
cdata.set_quoted('FRIDA_AGENT_PATH', asset_path_template / agent_name)
|
||||
cdata.set_quoted('FRIDA_HELPER_NAME', helper_name)
|
||||
cdata.set_quoted('FRIDA_AGENT_NAME', agent_name)
|
||||
endif
|
||||
|
||||
cdata.set('HAVE_' + host_os_family.to_upper(), 1)
|
||||
@@ -302,21 +323,19 @@ if host_os == 'linux'
|
||||
endif
|
||||
cdata.set('HAVE_' + libc.to_upper(), 1)
|
||||
vala_flags += '--define=' + libc.to_upper()
|
||||
elif host_os == 'android'
|
||||
libc = 'bionic'
|
||||
else
|
||||
libc = 'other'
|
||||
endif
|
||||
|
||||
if get_option('b_sanitize') == 'address'
|
||||
cdata.set('HAVE_ASAN', 1)
|
||||
endif
|
||||
|
||||
gum_dep = dependency('frida-gum-1.0', default_options: [
|
||||
f'frida_version=@frida_version@',
|
||||
'gumjs=enabled',
|
||||
])
|
||||
gumjs_dep = dependency('frida-gumjs-1.0')
|
||||
gumjs_inspector_dep = dependency('frida-gumjs-inspector-1.0')
|
||||
|
||||
glib_version_req = '>=2.74'
|
||||
glib_options = [
|
||||
'default_library=static',
|
||||
'cocoa=disabled',
|
||||
'selinux=disabled',
|
||||
'xattr=false',
|
||||
@@ -332,24 +351,58 @@ if optimize_for_prod
|
||||
]
|
||||
endif
|
||||
|
||||
glib_dep = dependency('glib-2.0', version: glib_version_req, default_options: glib_options)
|
||||
if glib_dep.type_name() == 'internal'
|
||||
have_shared_glib = get_option('default_library') != 'static'
|
||||
else
|
||||
glib_flavor_src = '''
|
||||
#include <glib.h>
|
||||
|
||||
#ifdef GLIB_STATIC_COMPILATION
|
||||
# error GLib is static
|
||||
#endif
|
||||
'''
|
||||
have_shared_glib = cc.compiles(glib_flavor_src, name: 'compiling with shared GLib', dependencies: [glib_dep])
|
||||
endif
|
||||
have_frida_glib = glib_dep.type_name() == 'internal' or cc.has_function('g_thread_set_callbacks', dependencies: [glib_dep])
|
||||
if have_frida_glib
|
||||
cdata.set('HAVE_FRIDA_GLIB', 1)
|
||||
vala_flags += '--define=HAVE_FRIDA_GLIB'
|
||||
endif
|
||||
|
||||
have_in_tree_payloads = not have_shared_glib
|
||||
gum_dep = dependency('frida-gum-1.0', default_options: [
|
||||
'default_library=static',
|
||||
f'frida_version=@frida_version@',
|
||||
'gumjs=' + (have_in_tree_payloads ? 'enabled' : 'disabled'),
|
||||
'inspector=enabled',
|
||||
])
|
||||
if have_in_tree_payloads
|
||||
gumjs_dep = dependency('frida-gumjs-1.0', default_options: ['default_library=static'])
|
||||
endif
|
||||
gumjs_inspector_dep = dependency('frida-gumjs-inspector-1.0', default_options: ['default_library=static'])
|
||||
|
||||
gee_options = [
|
||||
'default_library=static',
|
||||
'disable-introspection=true',
|
||||
]
|
||||
if optimize_for_prod
|
||||
gee_options += 'disable-internal-asserts=true'
|
||||
endif
|
||||
|
||||
glib_dep = dependency('glib-2.0', version: glib_version_req, default_options: glib_options)
|
||||
gobject_dep = dependency('gobject-2.0')
|
||||
gmodule_dep = dependency('gmodule-2.0')
|
||||
gio_dep = dependency('gio-2.0')
|
||||
gee_dep = dependency('gee-0.8', default_options: gee_options)
|
||||
json_glib_dep = dependency('json-glib-1.0', version: '>=1.8', default_options: [
|
||||
'default_library=static',
|
||||
'introspection=disabled',
|
||||
'gtk_doc=disabled',
|
||||
'tests=false',
|
||||
])
|
||||
libsoup_dep = dependency('libsoup-3.0', default_options: [
|
||||
'default_library=static',
|
||||
'sqlite:default_library=static',
|
||||
'gssapi=disabled',
|
||||
'ntlm=disabled',
|
||||
'brotli=disabled',
|
||||
@@ -361,19 +414,48 @@ libsoup_dep = dependency('libsoup-3.0', default_options: [
|
||||
'tests=false',
|
||||
'sysprof=disabled',
|
||||
])
|
||||
brotlidec_dep = dependency('libbrotlidec')
|
||||
if libsoup_dep.type_name() == 'internal' \
|
||||
or cc.has_function('soup_websocket_connection_pause_input', dependencies: [libsoup_dep])
|
||||
vala_flags += '--define=HAVE_SOUP_WEBSOCKET_BACKPRESSURE'
|
||||
endif
|
||||
brotlidec_dep = dependency('libbrotlidec', default_options: ['default_library=static'])
|
||||
capstone_dep = dependency('capstone', default_options: ['default_library=static'])
|
||||
if capstone_dep.type_name() != 'internal' \
|
||||
and not cc.has_function('cs_arch_register_x86', dependencies: [capstone_dep])
|
||||
add_project_arguments(
|
||||
'-Dcs_arch_register_x86()=',
|
||||
'-Dcs_arch_register_arm()=',
|
||||
'-Dcs_arch_register_arm64()=',
|
||||
'-Dcs_arch_register_mips()=',
|
||||
language: ['c', 'objc'],
|
||||
)
|
||||
endif
|
||||
quickjs_dep = dependency('quickjs', required: get_option('barebone_backend'), default_options: [
|
||||
'default_library=static',
|
||||
'libc=false',
|
||||
'bignum=true',
|
||||
'atomics=disabled',
|
||||
'stack_check=disabled',
|
||||
])
|
||||
lzfse_dep = dependency('lzfse', required: get_option('barebone_backend'))
|
||||
lzfse_dep = dependency('lzfse', required: false, default_options: ['default_library=static'])
|
||||
lzfse_from_system = false
|
||||
if not lzfse_dep.found() and not meson.is_cross_build()
|
||||
if cc.has_header_symbol('lzfse.h', 'lzfse_decode_buffer')
|
||||
lzfse_lib = cc.find_library('lzfse', required: false)
|
||||
if lzfse_lib.found()
|
||||
lzfse_dep = declare_dependency(dependencies: lzfse_lib)
|
||||
lzfse_from_system = true
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
if not lzfse_dep.found()
|
||||
lzfse_dep = dependency('lzfse', required: get_option('barebone_backend'), default_options: ['default_library=static'])
|
||||
endif
|
||||
|
||||
native_glib_dep = dependency('glib-2.0', version: glib_version_req, native: true, default_options: glib_options)
|
||||
native_gio_dep = dependency('gio-2.0', native: true)
|
||||
native_gee_dep = dependency('gee-0.8', native: true, default_options: gee_options)
|
||||
native_brotlienc_dep = dependency('libbrotlienc', native: true)
|
||||
native_brotlienc_dep = dependency('libbrotlienc', native: true, default_options: ['default_library=static'])
|
||||
|
||||
if host_os_family == 'windows'
|
||||
gio_windows_dep = dependency('gio-windows-2.0')
|
||||
@@ -381,8 +463,10 @@ else
|
||||
gio_unix_dep = dependency('gio-unix-2.0')
|
||||
endif
|
||||
|
||||
if host_os_family == 'linux'
|
||||
libbpf_dep = dependency('libbpf')
|
||||
have_local_backend = get_option('local_backend').allowed()
|
||||
|
||||
if host_os_family == 'linux' and have_local_backend
|
||||
libbpf_dep = dependency('libbpf', default_options: ['default_library=static'])
|
||||
endif
|
||||
|
||||
if host_os == 'android'
|
||||
@@ -430,7 +514,6 @@ else
|
||||
codesign = ''
|
||||
endif
|
||||
|
||||
have_local_backend = get_option('local_backend').allowed()
|
||||
have_simmy_backend = get_option('simmy_backend') \
|
||||
.disable_auto_if(host_os != 'macos') \
|
||||
.require(host_os == 'macos', error_message: 'Simmy backend is only available on macOS') \
|
||||
@@ -449,6 +532,7 @@ have_barebone_backend = get_option('barebone_backend') \
|
||||
compiler_backend_opt = get_option('compiler_backend') \
|
||||
.disable_auto_if(host_os == 'watchos')
|
||||
have_compiler_backend = false
|
||||
compiler_backend_mode = ''
|
||||
if compiler_backend_opt.allowed()
|
||||
go = find_program('go', native: true, required: false)
|
||||
if go.found()
|
||||
@@ -459,9 +543,13 @@ if compiler_backend_opt.allowed()
|
||||
files('tools' / 'cgo' / 'detect.py'),
|
||||
go,
|
||||
host_os,
|
||||
host_os_family,
|
||||
host_abi,
|
||||
libc,
|
||||
exe_suffix,
|
||||
shlib_suffix,
|
||||
cc.get_id(),
|
||||
assets_mode,
|
||||
'>>>', cc.cmd_array(), '<<<',
|
||||
'>>>', ar, '<<<',
|
||||
'>>>', nm, '<<<',
|
||||
@@ -474,12 +562,29 @@ if compiler_backend_opt.allowed()
|
||||
status = lines[0]
|
||||
if status == 'ok'
|
||||
have_compiler_backend = true
|
||||
tokens = lines[1].split(' ')
|
||||
go_mode = tokens[0]
|
||||
go_config = tokens[1]
|
||||
compiler_backend_static_compilation = go_mode == 'c-archive'
|
||||
if compiler_backend_static_compilation
|
||||
vala_flags += '--define=COMPILER_BACKEND_STATIC_COMPILATION'
|
||||
cdata.set('HAVE_COMPILER_BACKEND', 1)
|
||||
compiler_backend_mode = lines[1]
|
||||
go_config = lines[2]
|
||||
|
||||
if compiler_backend_mode == 'c-archive'
|
||||
vala_flags += '--define=COMPILER_BACKEND_LINKED'
|
||||
cdata.set('HAVE_COMPILER_BACKEND_LINKED', 1)
|
||||
elif compiler_backend_mode == 'c-shared'
|
||||
if assets_mode == 'installed'
|
||||
vala_flags += '--define=COMPILER_BACKEND_INSTALLED_LIBRARY'
|
||||
cdata.set_quoted('FRIDA_COMPILER_BACKEND_NAME', 'frida-compiler-backend' + shlib_suffix)
|
||||
else
|
||||
vala_flags += '--define=COMPILER_BACKEND_EMBEDDED_LIBRARY'
|
||||
endif
|
||||
elif compiler_backend_mode == 'pie'
|
||||
if assets_mode == 'installed'
|
||||
vala_flags += '--define=COMPILER_BACKEND_INSTALLED_EXECUTABLE'
|
||||
cdata.set_quoted('FRIDA_COMPILER_BACKEND_NAME', 'frida-compiler-backend' + exe_suffix)
|
||||
else
|
||||
vala_flags += '--define=COMPILER_BACKEND_EMBEDDED_EXECUTABLE'
|
||||
endif
|
||||
else
|
||||
error('Unsupported compiler backend mode: ' + compiler_backend_mode)
|
||||
endif
|
||||
else
|
||||
error_message = lines[1]
|
||||
@@ -548,8 +653,10 @@ endif
|
||||
gi_dep = dependency('gobject-introspection-1.0', required: false)
|
||||
if gi_dep.found()
|
||||
girdir = gi_dep.get_variable('girdir')
|
||||
typelibdir = gi_dep.get_variable('typelibdir')
|
||||
else
|
||||
girdir = get_option('prefix') / get_option('datadir') / 'gir-1.0'
|
||||
typelibdir = get_option('prefix') / get_option('libdir') / 'girepository-1.0'
|
||||
endif
|
||||
|
||||
vapidir = get_option('prefix') / get_option('datadir') / 'vala' / 'vapi'
|
||||
@@ -558,12 +665,15 @@ gum_vala_args = [
|
||||
'--pkg=frida-gum-1.0',
|
||||
'--vapidir=' + gum_dep.get_variable('frida_vapidir'),
|
||||
]
|
||||
gumjs_vala_args = ['--pkg=frida-gumjs-1.0', '--pkg=gio-2.0', gum_vala_args]
|
||||
gumjs_inspector_vala_args = ['--pkg=frida-gumjs-inspector-1.0', gum_vala_args]
|
||||
|
||||
have_v8 = gumjs_dep.get_variable('gumjs_v8') == 'enabled'
|
||||
if have_v8
|
||||
vala_flags += ['--define=HAVE_V8']
|
||||
if have_in_tree_payloads
|
||||
gumjs_vala_args = ['--pkg=frida-gumjs-1.0', '--pkg=gio-2.0', gum_vala_args]
|
||||
|
||||
have_v8 = gumjs_dep.get_variable('gumjs_v8') == 'enabled'
|
||||
if have_v8
|
||||
vala_flags += ['--define=HAVE_V8']
|
||||
endif
|
||||
endif
|
||||
|
||||
backend_deps_private = []
|
||||
@@ -571,6 +681,7 @@ backend_reqs_private = []
|
||||
backend_libs_private = []
|
||||
|
||||
openssl_options = [
|
||||
'default_library=static',
|
||||
'cli=disabled',
|
||||
]
|
||||
if host_os_family == 'windows' and cc.get_argument_syntax() != 'msvc'
|
||||
@@ -583,25 +694,48 @@ openssl_dep = dependency('openssl',
|
||||
)
|
||||
|
||||
if have_fruity_backend
|
||||
foreach dep : ['libnghttp2', 'libngtcp2', 'libngtcp2_crypto_quictls', 'libusb-1.0']
|
||||
backend_deps_private += dependency(dep)
|
||||
backend_reqs_private += dep
|
||||
endforeach
|
||||
backend_deps_private += dependency('libnghttp2', default_options: ['default_library=static'])
|
||||
backend_reqs_private += 'libnghttp2'
|
||||
|
||||
lwip_options = [
|
||||
'ipv4=disabled',
|
||||
'ipv6=enabled',
|
||||
'dns=disabled',
|
||||
'arp=disabled',
|
||||
'ethernet=enabled',
|
||||
]
|
||||
if optimize_for_prod
|
||||
lwip_options += 'lwip_debug=disabled'
|
||||
else
|
||||
lwip_options += 'lwip_debug=enabled'
|
||||
if host_os != 'macos'
|
||||
backend_deps_private += dependency('libngtcp2', default_options: ['default_library=static'])
|
||||
backend_reqs_private += 'libngtcp2'
|
||||
|
||||
ngtcp2_crypto_quictls_dep = dependency('libngtcp2_crypto_quictls', required: false, default_options: ['default_library=static'])
|
||||
if ngtcp2_crypto_quictls_dep.found()
|
||||
backend_deps_private += ngtcp2_crypto_quictls_dep
|
||||
backend_reqs_private += 'libngtcp2_crypto_quictls'
|
||||
else
|
||||
backend_deps_private += dependency('libngtcp2_crypto_ossl', default_options: ['default_library=static'])
|
||||
backend_reqs_private += 'libngtcp2_crypto_ossl'
|
||||
vala_flags += '--define=HAVE_NGTCP2_CRYPTO_OSSL'
|
||||
endif
|
||||
|
||||
if have_frida_glib
|
||||
libusb_dep = dependency('libusb-1.0', default_options: ['default_library=static'])
|
||||
else
|
||||
subproject('libusb', default_options: ['default_library=static'])
|
||||
libusb_dep = dependency('libusb-1.0', default_options: ['default_library=static'])
|
||||
endif
|
||||
backend_deps_private += libusb_dep
|
||||
backend_reqs_private += 'libusb-1.0'
|
||||
|
||||
lwip_options = [
|
||||
'default_library=static',
|
||||
'ipv4=disabled',
|
||||
'ipv6=enabled',
|
||||
'dns=disabled',
|
||||
'arp=disabled',
|
||||
'ethernet=enabled',
|
||||
]
|
||||
if optimize_for_prod
|
||||
lwip_options += 'lwip_debug=disabled'
|
||||
else
|
||||
lwip_options += 'lwip_debug=enabled'
|
||||
endif
|
||||
backend_deps_private += dependency('lwip', default_options: lwip_options)
|
||||
backend_reqs_private += 'lwip'
|
||||
endif
|
||||
backend_deps_private += dependency('lwip', default_options: lwip_options)
|
||||
backend_reqs_private += 'lwip'
|
||||
endif
|
||||
|
||||
if quickjs_dep.found()
|
||||
@@ -611,10 +745,14 @@ endif
|
||||
|
||||
if lzfse_dep.found()
|
||||
backend_deps_private += lzfse_dep
|
||||
backend_reqs_private += 'lzfse'
|
||||
if lzfse_from_system
|
||||
backend_libs_private += '-llzfse'
|
||||
else
|
||||
backend_reqs_private += 'lzfse'
|
||||
endif
|
||||
endif
|
||||
|
||||
if host_os_family == 'linux'
|
||||
if host_os_family == 'linux' and have_local_backend
|
||||
backend_deps_private += libbpf_dep
|
||||
backend_reqs_private += 'libbpf'
|
||||
endif
|
||||
@@ -625,43 +763,93 @@ endif
|
||||
|
||||
if host_os == 'android'
|
||||
termux_elf_cleaner = find_program('termux-elf-cleaner', native: true)
|
||||
libselinux_dep = dependency('libselinux', version: '>=3.0')
|
||||
libsepol_dep = dependency('libsepol', version: '>=3.0')
|
||||
libselinux_dep = dependency('libselinux', version: '>=3.0', default_options: ['default_library=static'])
|
||||
libsepol_dep = dependency('libsepol', version: '>=3.0', default_options: ['default_library=static'])
|
||||
backend_deps_private += [libselinux_dep, libsepol_dep]
|
||||
backend_reqs_private += ['libselinux', 'libsepol']
|
||||
else
|
||||
termux_elf_cleaner = []
|
||||
endif
|
||||
|
||||
tls_provider_dep = dependency('gioopenssl', required: get_option('connectivity'), default_options: [
|
||||
'gnutls=disabled',
|
||||
'openssl=enabled',
|
||||
'libproxy=disabled',
|
||||
'gnome_proxy=disabled',
|
||||
'tests=false',
|
||||
])
|
||||
tls_provider_vala_args = []
|
||||
if tls_provider_dep.found()
|
||||
tls_provider_vala_args += [
|
||||
'--pkg=gioopenssl',
|
||||
'--vapidir=' + tls_provider_dep.get_variable('vapidir'),
|
||||
]
|
||||
cdata.set('HAVE_GIOOPENSSL', 1)
|
||||
vala_flags += ['--define=HAVE_GIOOPENSSL']
|
||||
backend_deps_private += tls_provider_dep
|
||||
backend_reqs_private += 'gioopenssl'
|
||||
use_apple_tls = false
|
||||
if have_shared_glib
|
||||
tls_provider_dep = gio_dep
|
||||
else
|
||||
apple_tls_opt = get_option('apple_tls')
|
||||
if host_os_family == 'darwin' and not apple_tls_opt.disabled()
|
||||
apple_tls_deployment_check = '''
|
||||
#include <Availability.h>
|
||||
|
||||
#if defined (__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 150000
|
||||
# error macOS deployment target below 15.0
|
||||
#endif
|
||||
#if defined (__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED < 180000
|
||||
# error iOS deployment target below 18.0
|
||||
#endif
|
||||
#if defined (__TV_OS_VERSION_MIN_REQUIRED) && __TV_OS_VERSION_MIN_REQUIRED < 180000
|
||||
# error tvOS deployment target below 18.0
|
||||
#endif
|
||||
#if defined (__WATCH_OS_VERSION_MIN_REQUIRED) && __WATCH_OS_VERSION_MIN_REQUIRED < 110000
|
||||
# error watchOS deployment target below 11.0
|
||||
#endif
|
||||
'''
|
||||
use_apple_tls = cc.compiles(apple_tls_deployment_check, name: 'Apple TLS backend deployment target')
|
||||
if apple_tls_opt.enabled() and not use_apple_tls
|
||||
error('apple_tls explicitly enabled but deployment target is too old')
|
||||
endif
|
||||
elif apple_tls_opt.enabled()
|
||||
error('apple_tls is only supported on Darwin hosts')
|
||||
endif
|
||||
if use_apple_tls
|
||||
tls_provider_name = 'gioapple'
|
||||
tls_provider_define_name = 'HAVE_GIOAPPLE'
|
||||
tls_provider_backend_options = [
|
||||
'gnutls=disabled',
|
||||
'openssl=disabled',
|
||||
'apple=enabled',
|
||||
]
|
||||
else
|
||||
tls_provider_name = 'gioopenssl'
|
||||
tls_provider_define_name = 'HAVE_GIOOPENSSL'
|
||||
tls_provider_backend_options = [
|
||||
'gnutls=disabled',
|
||||
'openssl=enabled',
|
||||
'apple=disabled',
|
||||
]
|
||||
endif
|
||||
tls_provider_dep = dependency(tls_provider_name,
|
||||
required: get_option('connectivity'),
|
||||
default_options: tls_provider_backend_options + [
|
||||
'libproxy=disabled',
|
||||
'gnome_proxy=disabled',
|
||||
'tests=false',
|
||||
],
|
||||
)
|
||||
if tls_provider_dep.found()
|
||||
tls_provider_vala_args += [
|
||||
'--pkg=' + tls_provider_name,
|
||||
'--vapidir=' + tls_provider_dep.get_variable('vapidir'),
|
||||
]
|
||||
cdata.set(tls_provider_define_name, 1)
|
||||
vala_flags += ['--define=' + tls_provider_define_name]
|
||||
backend_deps_private += tls_provider_dep
|
||||
backend_reqs_private += tls_provider_name
|
||||
endif
|
||||
endif
|
||||
|
||||
nice_dep = dependency('nice', required: get_option('connectivity'), default_options: [
|
||||
'default_library=static',
|
||||
'gupnp=disabled',
|
||||
'gstreamer=disabled',
|
||||
'crypto-library=openssl',
|
||||
'crypto-library=auto',
|
||||
'examples=disabled',
|
||||
'tests=disabled',
|
||||
'introspection=disabled',
|
||||
])
|
||||
if nice_dep.found()
|
||||
usrsctp_dep = dependency('usrsctp', default_options: [
|
||||
'default_library=static',
|
||||
'sctp_inet=false',
|
||||
'sctp_inet6=false',
|
||||
'sctp_build_programs=false',
|
||||
@@ -669,8 +857,12 @@ if nice_dep.found()
|
||||
|
||||
cdata.set('HAVE_NICE', 1)
|
||||
vala_flags += ['--define=HAVE_NICE']
|
||||
backend_deps_private += [nice_dep, openssl_dep, usrsctp_dep]
|
||||
backend_reqs_private += ['nice', 'openssl', 'usrsctp']
|
||||
backend_deps_private += [nice_dep, usrsctp_dep]
|
||||
backend_reqs_private += ['nice', 'usrsctp']
|
||||
if host_os != 'macos'
|
||||
backend_deps_private += openssl_dep
|
||||
backend_reqs_private += 'openssl'
|
||||
endif
|
||||
|
||||
if host_os_family in ['darwin', 'freebsd', 'qnx']
|
||||
cdata.set('HAVE_SCONN_LEN', 1)
|
||||
@@ -680,10 +872,10 @@ else
|
||||
endif
|
||||
|
||||
if host_os_family == 'windows'
|
||||
backend_libs_private += ['-lsetupapi']
|
||||
backend_libs_private += ['-lsetupapi', '-lshlwapi']
|
||||
endif
|
||||
if host_os_family == 'darwin'
|
||||
backend_libs_private += ['-Wl,-framework,Foundation', '-lbsm']
|
||||
backend_libs_private += ['-Wl,-framework,Foundation', '-Wl,-framework,CoreFoundation', '-lbsm']
|
||||
endif
|
||||
if host_os == 'macos'
|
||||
backend_libs_private += ['-Wl,-framework,AppKit']
|
||||
@@ -753,6 +945,19 @@ post_process = [
|
||||
'@INPUT0@',
|
||||
]
|
||||
|
||||
executable_no_exports_link_args = []
|
||||
executable_no_exports_link_depends = []
|
||||
if host_os_family == 'darwin'
|
||||
script = meson.project_source_root() / 'tools' / 'executable-no-exports.symbols'
|
||||
executable_no_exports_link_args = ['-Wl,-exported_symbols_list,' + script]
|
||||
executable_no_exports_link_depends = [script]
|
||||
elif host_os_family != 'windows'
|
||||
script_name = (host_os == 'freebsd') ? 'executable-no-exports-freebsd.version' : 'executable-no-exports.version'
|
||||
script = meson.project_source_root() / 'tools' / script_name
|
||||
executable_no_exports_link_args = ['-Wl,--version-script,' + script]
|
||||
executable_no_exports_link_depends = [script]
|
||||
endif
|
||||
|
||||
compile_js_agent = files('tools' / 'compile-js-agent.py')
|
||||
|
||||
mapper_opt = get_option('mapper')
|
||||
@@ -783,11 +988,20 @@ if cc.get_id() == 'gcc' and cc.compiles('#if __GNUC__ >= 5\n#error NOTNEEDED\n#e
|
||||
add_project_arguments('-w', language: 'c')
|
||||
endif
|
||||
|
||||
vapidir = meson.current_source_dir() / 'vapi'
|
||||
local_vapidir = meson.current_source_dir() / 'vapi'
|
||||
vala_flags += [
|
||||
'--vapidir=' + vapidir,
|
||||
'--vapidir=' + local_vapidir,
|
||||
'--pkg', 'config',
|
||||
]
|
||||
if gee_dep.type_name() not in ['internal', 'cmake']
|
||||
gee_prefix = gee_dep.get_variable('prefix', default_value: '')
|
||||
if gee_prefix != ''
|
||||
gee_vapidir = gee_prefix / 'share' / 'vala' / 'vapi'
|
||||
if fs.is_dir(gee_vapidir)
|
||||
vala_flags += '--vapidir=' + gee_vapidir
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
|
||||
vala_flags += '--define=' + host_os_family.to_upper()
|
||||
if host_os != host_os_family
|
||||
@@ -799,13 +1013,22 @@ if host_abi != host_arch
|
||||
endif
|
||||
vala_flags += '--define=' + host_machine.endian().to_upper() + '_ENDIAN'
|
||||
add_project_arguments(vala_flags, language: 'vala')
|
||||
add_project_arguments('-I' + vapidir, language: 'c')
|
||||
add_project_arguments('-I' + local_vapidir, language: 'c')
|
||||
|
||||
native_vala_flags = [
|
||||
'--vapidir=' + meson.current_source_dir() / 'vapi',
|
||||
'--vapidir=' + local_vapidir,
|
||||
]
|
||||
if native_gee_dep.type_name() not in ['internal', 'cmake']
|
||||
native_gee_prefix = native_gee_dep.get_variable('prefix', default_value: '')
|
||||
if native_gee_prefix != ''
|
||||
native_gee_vapidir = native_gee_prefix / 'share' / 'vala' / 'vapi'
|
||||
if fs.is_dir(native_gee_vapidir)
|
||||
native_vala_flags += '--vapidir=' + native_gee_vapidir
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
add_project_arguments(native_vala_flags, language: 'vala', native: true)
|
||||
add_project_arguments('-I' + vapidir, language: 'c', native: true)
|
||||
add_project_arguments('-I' + local_vapidir, language: 'c', native: true)
|
||||
|
||||
subdir('compat')
|
||||
subdir('tools')
|
||||
@@ -840,7 +1063,7 @@ summary(
|
||||
summary(
|
||||
{
|
||||
'compat': compat_summary,
|
||||
'assets': get_option('assets'),
|
||||
'assets': assets_mode,
|
||||
'mapper': have_mapper,
|
||||
'connectivity': tls_provider_dep.found(),
|
||||
'gadget': build_gadget,
|
||||
|
||||
+8
-2
@@ -48,8 +48,8 @@ option('compiler_backend',
|
||||
|
||||
option('assets',
|
||||
type: 'combo',
|
||||
choices: ['embedded', 'installed'],
|
||||
value: 'embedded',
|
||||
choices: ['auto', 'embedded', 'installed'],
|
||||
value: 'auto',
|
||||
description: 'Whether assets such as frida-agent should be embedded vs installed and loaded at runtime'
|
||||
)
|
||||
|
||||
@@ -143,6 +143,12 @@ option('connectivity',
|
||||
description: 'Internet connectivity features (TLS and ICE support)'
|
||||
)
|
||||
|
||||
option('apple_tls',
|
||||
type: 'feature',
|
||||
value: 'auto',
|
||||
description: 'Use the Apple TLS backend (requires recent deployment targets); auto-detected when on a Darwin host'
|
||||
)
|
||||
|
||||
option('mapper',
|
||||
type: 'feature',
|
||||
value: 'auto',
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# No exported symbols.
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
global:
|
||||
# FreeBSD needs these two:
|
||||
__progname;
|
||||
environ;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
+2
-14
@@ -2,18 +2,6 @@ portal_sources = [
|
||||
'portal.vala',
|
||||
]
|
||||
|
||||
extra_link_args = []
|
||||
extra_link_depends = []
|
||||
if host_os_family == 'darwin'
|
||||
symlist = 'frida-portal.symbols'
|
||||
extra_link_args += ['-Wl,-exported_symbols_list,' + meson.current_source_dir() / symlist]
|
||||
extra_link_depends += [symlist]
|
||||
elif host_os_family != 'windows'
|
||||
symscript = 'frida-portal.version'
|
||||
extra_link_args += ['-Wl,--version-script,' + meson.current_source_dir() / symscript]
|
||||
extra_link_depends += [symscript]
|
||||
endif
|
||||
|
||||
extra_deps = []
|
||||
if tls_provider_dep.found()
|
||||
extra_deps += tls_provider_dep
|
||||
@@ -21,8 +9,8 @@ endif
|
||||
|
||||
raw_portal = executable('frida-portal-raw', portal_sources,
|
||||
vala_args: [core_vala_args, tls_provider_vala_args, '--pkg=posix'],
|
||||
link_args: extra_link_args,
|
||||
link_depends: extra_link_depends,
|
||||
link_args: executable_no_exports_link_args,
|
||||
link_depends: executable_no_exports_link_depends,
|
||||
dependencies: [core_dep] + extra_deps,
|
||||
)
|
||||
|
||||
|
||||
+3
-1
@@ -39,7 +39,9 @@ namespace Frida.Portal {
|
||||
};
|
||||
|
||||
private static int main (string[] args) {
|
||||
#if HAVE_GIOOPENSSL
|
||||
#if HAVE_GIOAPPLE
|
||||
GIOApple.register ();
|
||||
#elif HAVE_GIOOPENSSL
|
||||
GIOOpenSSL.register ();
|
||||
#endif
|
||||
|
||||
|
||||
+1
-1
Submodule releng updated: 7ed66fe687...c9cc18138b.
@@ -0,0 +1,11 @@
|
||||
{
|
||||
global:
|
||||
# FreeBSD needs these two:
|
||||
__progname;
|
||||
environ;
|
||||
# Allow users to load the server as a shared library with dlopen() and call main() directly
|
||||
main;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
@@ -1 +1,2 @@
|
||||
# No exported symbols.
|
||||
# Allow users to load the server as a shared library with dlopen() and call main() directly
|
||||
_main
|
||||
@@ -1,8 +1,5 @@
|
||||
{
|
||||
global:
|
||||
# FreeBSD needs these two:
|
||||
__progname;
|
||||
environ;
|
||||
# Allow users to load the server as a shared library with dlopen() and call main() directly
|
||||
main;
|
||||
|
||||
|
||||
+8
-3
@@ -19,7 +19,7 @@ if host_os_family == 'darwin'
|
||||
extra_link_args += ['-Wl,-exported_symbols_list,' + meson.current_source_dir() / symlist]
|
||||
extra_link_depends += [symlist]
|
||||
elif host_os_family != 'windows'
|
||||
symscript = 'frida-server.version'
|
||||
symscript = (host_os == 'freebsd') ? 'frida-server-freebsd.version' : 'frida-server.version'
|
||||
extra_link_args += ['-Wl,--version-script,' + meson.current_source_dir() / symscript]
|
||||
extra_link_depends += [symscript]
|
||||
endif
|
||||
@@ -29,15 +29,20 @@ if host_os == 'macos'
|
||||
extra_link_depends += [server_plist]
|
||||
endif
|
||||
|
||||
server_deps = [core_dep]
|
||||
if host_os_family == 'darwin'
|
||||
server_deps += dependency('appleframeworks', modules: ['CoreFoundation'])
|
||||
endif
|
||||
|
||||
raw_server = executable('frida-server-raw', server_sources,
|
||||
vala_args: [core_vala_args, '--pkg=posix'],
|
||||
link_args: extra_link_args,
|
||||
link_depends: extra_link_depends,
|
||||
dependencies: [core_dep],
|
||||
dependencies: server_deps,
|
||||
)
|
||||
|
||||
server_name = 'frida-server' + exe_suffix
|
||||
need_universal_server = get_option('assets') == 'installed' and server_compat != ''
|
||||
need_universal_server = assets_mode == 'installed' and server_compat != ''
|
||||
|
||||
server = custom_target('frida-server',
|
||||
input: [raw_server, 'frida-server.xcent'],
|
||||
|
||||
+2
-4
@@ -104,9 +104,8 @@ namespace Frida.Server {
|
||||
return true;
|
||||
});
|
||||
int prefix_pos = program_path.last_index_of (Config.FRIDA_PREFIX + "/");
|
||||
if (prefix_pos != -1 && prefix_pos != 0) {
|
||||
options.sysroot = program_path[:prefix_pos];
|
||||
}
|
||||
if (prefix_pos != -1 && prefix_pos != 0)
|
||||
TemporaryDirectory.use_sysroot (program_path[:prefix_pos]);
|
||||
#endif
|
||||
|
||||
PolicySoftenerFlavor softener_flavor = SYSTEM;
|
||||
@@ -199,7 +198,6 @@ namespace Frida.Server {
|
||||
private static int run_application (string? device_id, EndpointParameters endpoint_params, ControlServiceOptions options,
|
||||
ReadyHandler on_ready) {
|
||||
TemporaryDirectory.always_use ((directory != null) ? directory : DEFAULT_DIRECTORY);
|
||||
TemporaryDirectory.use_sysroot (options.sysroot);
|
||||
|
||||
application = new Application (device_id, endpoint_params, options);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ helper.dex: build/frida-helper.jar
|
||||
$<
|
||||
mv build/classes.dex $@
|
||||
|
||||
build/frida-helper.jar: re/frida/Helper.java
|
||||
build/frida-helper.jar: re/frida/Helper.java re/frida/HelperBackend.java
|
||||
javac \
|
||||
-cp .:$(android_jar) \
|
||||
-bootclasspath $(android_jar) \
|
||||
|
||||
@@ -1,66 +1,12 @@
|
||||
namespace Frida {
|
||||
public sealed class AndroidHelperClient : Object, AsyncInitable {
|
||||
public signal void closed ();
|
||||
|
||||
public IOStream stream {
|
||||
public sealed class AndroidHelperClient : Object {
|
||||
public AndroidHelperTransport transport {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private BufferedInputStream input;
|
||||
private OutputStream output;
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
private State state = CLOSED;
|
||||
private Gee.Queue<Request> pending_requests = new Gee.ArrayQueue<Request> ();
|
||||
|
||||
private ByteArray pending_output = new ByteArray ();
|
||||
private bool writing = false;
|
||||
|
||||
private enum State {
|
||||
CLOSED,
|
||||
OPEN
|
||||
}
|
||||
|
||||
private const uint32 MAX_RESPONSE_SIZE = 100 * 1024 * 1024;
|
||||
|
||||
public AndroidHelperClient (IOStream stream) {
|
||||
Object (stream: stream);
|
||||
}
|
||||
|
||||
construct {
|
||||
input = (BufferedInputStream) Object.new (typeof (BufferedInputStream),
|
||||
"base-stream", stream.get_input_stream (),
|
||||
"close-base-stream", false,
|
||||
"buffer-size", 128 * 1024);
|
||||
output = stream.get_output_stream ();
|
||||
|
||||
state = OPEN;
|
||||
|
||||
process_incoming_responses.begin ();
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
io_cancellable.cancel ();
|
||||
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (close.callback);
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
yield;
|
||||
|
||||
try {
|
||||
yield stream.close_async (Priority.DEFAULT, cancellable);
|
||||
} catch (IOError e) {
|
||||
}
|
||||
|
||||
ensure_closed ();
|
||||
}
|
||||
|
||||
private void ensure_closed () {
|
||||
if (state == CLOSED)
|
||||
return;
|
||||
state = CLOSED;
|
||||
closed ();
|
||||
public AndroidHelperClient (AndroidHelperTransport transport) {
|
||||
Object (transport: transport);
|
||||
}
|
||||
|
||||
public async HostApplicationInfo get_frontmost_application (FrontmostQueryOptions options,
|
||||
@@ -72,12 +18,12 @@ namespace Frida {
|
||||
.add_string_value (options.scope.to_nick ())
|
||||
.end_array ();
|
||||
|
||||
Json.Node response = yield request (stanza.get_root (), cancellable);
|
||||
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
|
||||
|
||||
if (response.is_null ())
|
||||
return HostApplicationInfo.empty ();
|
||||
|
||||
Json.Reader reader = new Json.Reader (response);
|
||||
Json.Reader reader = make_json_reader_taking_node ((owned) response);
|
||||
|
||||
string? identifier = null;
|
||||
string? name = null;
|
||||
@@ -127,9 +73,9 @@ namespace Frida {
|
||||
.add_string_value (options.scope.to_nick ())
|
||||
.end_array ();
|
||||
|
||||
Json.Node response = yield request (stanza.get_root (), cancellable);
|
||||
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
|
||||
|
||||
Json.Reader reader = new Json.Reader (response);
|
||||
Json.Reader reader = make_json_reader_taking_node ((owned) response);
|
||||
|
||||
int num_apps = reader.count_elements ();
|
||||
if (num_apps == -1)
|
||||
@@ -193,9 +139,9 @@ namespace Frida {
|
||||
.add_string_value (options.scope.to_nick ())
|
||||
.end_array ();
|
||||
|
||||
Json.Node response = yield request (stanza.get_root (), cancellable);
|
||||
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
|
||||
|
||||
Json.Reader reader = new Json.Reader (response);
|
||||
Json.Reader reader = make_json_reader_taking_node ((owned) response);
|
||||
|
||||
int num_processes = reader.count_elements ();
|
||||
if (num_processes == -1)
|
||||
@@ -247,9 +193,9 @@ namespace Frida {
|
||||
.add_int_value (uid)
|
||||
.end_array ();
|
||||
|
||||
Json.Node response = yield request (stanza.get_root (), cancellable);
|
||||
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
|
||||
|
||||
var r = new Json.Reader (response);
|
||||
var r = make_json_reader_taking_node ((owned) response);
|
||||
parse_envelope_or_throw (r);
|
||||
|
||||
r.read_element (1);
|
||||
@@ -317,9 +263,9 @@ namespace Frida {
|
||||
.add_int_value (uid)
|
||||
.end_array ();
|
||||
|
||||
Json.Node response = yield request (stanza.get_root (), cancellable);
|
||||
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
|
||||
|
||||
var r = new Json.Reader (response);
|
||||
var r = make_json_reader_taking_node ((owned) response);
|
||||
parse_envelope_or_throw (r);
|
||||
}
|
||||
|
||||
@@ -331,9 +277,9 @@ namespace Frida {
|
||||
.add_int_value ((int) pid)
|
||||
.end_array ();
|
||||
|
||||
Json.Node response = yield request (stanza.get_root (), cancellable);
|
||||
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
|
||||
|
||||
var r = new Json.Reader (response);
|
||||
var r = make_json_reader_taking_node ((owned) response);
|
||||
parse_envelope_or_throw (r);
|
||||
|
||||
r.read_element (1);
|
||||
@@ -343,6 +289,13 @@ namespace Frida {
|
||||
return r.get_boolean_value ();
|
||||
}
|
||||
|
||||
private async void request_ok (Json.Builder stanza, Cancellable? cancellable) throws Error, IOError {
|
||||
Json.Node response = yield transport.request (stanza.get_root (), cancellable);
|
||||
|
||||
var r = make_json_reader_taking_node ((owned) response);
|
||||
parse_envelope_or_throw (r);
|
||||
}
|
||||
|
||||
private void parse_envelope_or_throw (Json.Reader r) throws Error {
|
||||
r.read_element (0);
|
||||
string? kind = r.get_string_value ();
|
||||
@@ -452,6 +405,76 @@ namespace Frida {
|
||||
|
||||
throw new Error.PROTOCOL ("Unexpected JSON type: %s", type.name ());
|
||||
}
|
||||
}
|
||||
|
||||
public interface AndroidHelperTransport : Object {
|
||||
public abstract async void close (Cancellable? cancellable) throws IOError;
|
||||
public abstract async Json.Node request (Json.Node stanza, Cancellable? cancellable) throws Error, IOError;
|
||||
}
|
||||
|
||||
public sealed class AndroidHelperStreamTransport : Object, AndroidHelperTransport {
|
||||
public signal void closed ();
|
||||
|
||||
public IOStream stream {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private BufferedInputStream input;
|
||||
private OutputStream output;
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
private State state = CLOSED;
|
||||
private Gee.Queue<Request> pending_requests = new Gee.ArrayQueue<Request> ();
|
||||
|
||||
private ByteArray pending_output = new ByteArray ();
|
||||
private bool writing = false;
|
||||
|
||||
private enum State {
|
||||
CLOSED,
|
||||
OPEN
|
||||
}
|
||||
|
||||
private const uint32 MAX_RESPONSE_SIZE = 100 * 1024 * 1024;
|
||||
|
||||
public AndroidHelperStreamTransport (IOStream stream) {
|
||||
Object (stream: stream);
|
||||
}
|
||||
|
||||
construct {
|
||||
input = (BufferedInputStream) Object.new (typeof (BufferedInputStream),
|
||||
"base-stream", stream.get_input_stream (),
|
||||
"close-base-stream", false,
|
||||
"buffer-size", 128 * 1024);
|
||||
output = stream.get_output_stream ();
|
||||
|
||||
state = OPEN;
|
||||
|
||||
process_incoming_responses.begin ();
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
io_cancellable.cancel ();
|
||||
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (close.callback);
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
yield;
|
||||
|
||||
try {
|
||||
yield stream.close_async (Priority.DEFAULT, cancellable);
|
||||
} catch (IOError e) {
|
||||
}
|
||||
|
||||
ensure_closed ();
|
||||
}
|
||||
|
||||
private void ensure_closed () {
|
||||
if (state == CLOSED)
|
||||
return;
|
||||
state = CLOSED;
|
||||
closed ();
|
||||
}
|
||||
|
||||
public async Json.Node request (Json.Node stanza, Cancellable? cancellable) throws Error, IOError {
|
||||
if (state == CLOSED)
|
||||
@@ -487,13 +510,6 @@ namespace Frida {
|
||||
return r.response;
|
||||
}
|
||||
|
||||
private async void request_ok (Json.Builder stanza, Cancellable? cancellable) throws Error, IOError {
|
||||
Json.Node response = yield request (stanza.get_root (), cancellable);
|
||||
|
||||
var r = new Json.Reader (response);
|
||||
parse_envelope_or_throw (r);
|
||||
}
|
||||
|
||||
private void write_request (Json.Node request) {
|
||||
string request_str = Json.to_string (request, false);
|
||||
uint8[] request_data = request_str.data;
|
||||
|
||||
Binary file not shown.
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
+397
-39
@@ -23,57 +23,49 @@ CORE_TAG_METHOD = f"{{{CORE_NAMESPACE}}}method"
|
||||
|
||||
OBJECT_TYPE_PATTERN = re.compile(r"\bpublic\s+(sealed )?(class|interface)\s+(\w+)\b")
|
||||
|
||||
TOPLEVEL_NAMES = [
|
||||
"frida.vala",
|
||||
"package-manager.vala",
|
||||
"control-service.vala",
|
||||
"portal-service.vala",
|
||||
"file-monitor.vala",
|
||||
Path("compiler") / "compiler.vala",
|
||||
]
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Generate refined Frida API definitions")
|
||||
parser.add_argument('--output', dest='output_type', choices=['bundle', 'header', 'gir', 'vapi', 'vapi-stamp'], default='bundle')
|
||||
parser.add_argument('frida_version', metavar='frida-version', type=str)
|
||||
parser.add_argument('frida_major_version', metavar='frida-major-version', type=str)
|
||||
parser.add_argument('frida_minor_version', metavar='frida-minor-version', type=str)
|
||||
parser.add_argument('frida_micro_version', metavar='frida-micro-version', type=str)
|
||||
parser.add_argument('frida_nano_version', metavar='frida-nano-version', type=str)
|
||||
parser.add_argument('api_version', metavar='api-version', type=str)
|
||||
parser.add_argument('core_header', metavar='/path/to/frida-core.h', type=argparse.FileType('r', encoding='utf-8'))
|
||||
parser.add_argument('core_gir', metavar='/path/to/Frida-x.y.gir', type=argparse.FileType('r', encoding='utf-8'))
|
||||
parser.add_argument('core_vapi', metavar='/path/to/frida-core.vapi', type=argparse.FileType('r', encoding='utf-8'))
|
||||
parser.add_argument('base_header', metavar='/path/to/frida-base.h', type=argparse.FileType('r', encoding='utf-8'))
|
||||
parser.add_argument('base_gir', metavar='/path/to/FridaBase-x.y.gir', type=argparse.FileType('r', encoding='utf-8'))
|
||||
parser.add_argument('base_vapi', metavar='/path/to/frida-base.vapi', type=argparse.FileType('r', encoding='utf-8'))
|
||||
parser.add_argument('--output', dest='output_type', choices=['bundle', 'header', 'gir', 'vapi', 'vapi-stamp', 'symbol-maps'], default='bundle')
|
||||
parser.add_argument('output_dir', metavar='/output/dir')
|
||||
parser.add_argument('extra_args', nargs='*')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
output_type = args.output_type
|
||||
frida_version = args.frida_version
|
||||
frida_version_components = (
|
||||
args.frida_major_version,
|
||||
args.frida_minor_version,
|
||||
args.frida_micro_version,
|
||||
args.frida_nano_version,
|
||||
)
|
||||
api_version = args.api_version
|
||||
core_header = args.core_header.read()
|
||||
core_gir = args.core_gir.read()
|
||||
core_vapi = args.core_vapi.read()
|
||||
base_header = args.base_header.read()
|
||||
base_gir = args.base_gir.read()
|
||||
base_vapi = args.base_vapi.read()
|
||||
output_dir = Path(args.output_dir)
|
||||
|
||||
if output_type == 'symbol-maps':
|
||||
emit_symbol_maps_from_source(output_dir)
|
||||
return
|
||||
|
||||
extra = args.extra_args
|
||||
frida_version = extra[0]
|
||||
frida_version_components = tuple(extra[1:5])
|
||||
api_version = extra[5]
|
||||
|
||||
if output_type == 'vapi-stamp':
|
||||
(output_dir / f"frida-core-{api_version}.vapi.stamp").write_bytes(b"")
|
||||
return
|
||||
|
||||
toplevel_names = [
|
||||
"frida.vala",
|
||||
"package-manager.vala",
|
||||
"control-service.vala",
|
||||
"portal-service.vala",
|
||||
"file-monitor.vala",
|
||||
Path("compiler") / "compiler.vala",
|
||||
]
|
||||
core_header = Path(extra[6]).read_text(encoding='utf-8')
|
||||
core_gir = Path(extra[7]).read_text(encoding='utf-8')
|
||||
core_vapi = Path(extra[8]).read_text(encoding='utf-8')
|
||||
base_header = Path(extra[9]).read_text(encoding='utf-8')
|
||||
base_gir = Path(extra[10]).read_text(encoding='utf-8')
|
||||
base_vapi = Path(extra[11]).read_text(encoding='utf-8')
|
||||
|
||||
toplevel_sources = []
|
||||
src_dir = Path(__file__).parent.parent.resolve()
|
||||
for name in toplevel_names:
|
||||
for name in TOPLEVEL_NAMES:
|
||||
toplevel_sources.append((src_dir / name).read_text(encoding='utf-8'))
|
||||
toplevel_code = "\n".join(toplevel_sources)
|
||||
|
||||
@@ -97,7 +89,7 @@ def main():
|
||||
emit_header(api, output_dir)
|
||||
|
||||
if enable_gir:
|
||||
emit_gir(api, core_gir, base_gir, output_dir)
|
||||
emit_gir(api, core_gir, base_gir, output_dir, build_doc_index(src_dir))
|
||||
|
||||
if enable_vapi:
|
||||
emit_vapi(api, output_dir)
|
||||
@@ -203,7 +195,7 @@ def emit_header(api, output_dir):
|
||||
|
||||
output_header_file.write("\n\n#endif\n")
|
||||
|
||||
def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> str:
|
||||
def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path, docs: DocIndex) -> str:
|
||||
ET.register_namespace("", CORE_NAMESPACE)
|
||||
ET.register_namespace("c", C_NAMESPACE)
|
||||
ET.register_namespace("glib", GLIB_NAMESPACE)
|
||||
@@ -249,6 +241,31 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> st
|
||||
merge_and_transform_elements("interface", object_type_names)
|
||||
merge_and_transform_elements("enumeration", enum_type_names | error_type_names)
|
||||
|
||||
for source_root in [core_root, base_root]:
|
||||
for record in source_root.findall(".//record", GIR_NAMESPACES):
|
||||
owner = record.get(f"{{{GLIB_NAMESPACE}}}is-gtype-struct-for")
|
||||
if owner is not None and owner in object_type_names:
|
||||
merged_namespace.append(record)
|
||||
|
||||
known_names = object_type_names | enum_type_names | error_type_names
|
||||
referenced_names = set()
|
||||
for elem in merged_namespace.iter():
|
||||
type_name = elem.get("name")
|
||||
if type_name is None:
|
||||
continue
|
||||
for prefix in ("Frida.", "FridaBase."):
|
||||
if type_name.startswith(prefix):
|
||||
referenced_names.add(type_name[len(prefix):])
|
||||
break
|
||||
needed_names = referenced_names - known_names
|
||||
for source_root in [core_root, base_root]:
|
||||
source_namespace = source_root.find("namespace", GIR_NAMESPACES)
|
||||
for callback in source_namespace.findall("callback", GIR_NAMESPACES):
|
||||
if callback.get("name") in needed_names:
|
||||
merged_namespace.append(callback)
|
||||
|
||||
inject_documentation(merged_namespace, docs)
|
||||
|
||||
ET.indent(merged_root, space=" ")
|
||||
result = ET.tostring(merged_root,
|
||||
encoding="unicode",
|
||||
@@ -260,6 +277,328 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> st
|
||||
def filter_elements(elements: List[ET.Element], spec_set: Set[str]):
|
||||
return [elem for elem in elements if elem.get("name") in spec_set]
|
||||
|
||||
@dataclass
|
||||
class DocComment:
|
||||
body: str
|
||||
params: dict
|
||||
returns: str
|
||||
filename: str = "<generated>"
|
||||
line: int = 0
|
||||
|
||||
@dataclass
|
||||
class DocIndex:
|
||||
types: dict # type_name -> DocComment
|
||||
members: dict # (type_name, member_name) -> DocComment
|
||||
enum_members: dict # (enum_name, member_name) -> DocComment
|
||||
|
||||
DOC_TYPE_PATTERN = re.compile(r"public\s+(?:sealed\s+|abstract\s+)?(class|interface|enum|errordomain)\s+(\w+)")
|
||||
DOC_SIGNAL_PATTERN = re.compile(r"public\s+signal\s+\S.*?\b(\w+)\s*\(")
|
||||
DOC_PROPERTY_PATTERN = re.compile(r"public\s+(?:unowned\s+)?\S.*?\b(\w+)\s*\{")
|
||||
DOC_METHOD_PATTERN = re.compile(r"public\s+\S.*?\b(\w+)\s*\(")
|
||||
DOC_ENUM_MEMBER_PATTERN = re.compile(r"([A-Z][A-Z0-9_]*)\b")
|
||||
|
||||
def build_doc_index(src_dir: Path) -> DocIndex:
|
||||
repo_root = src_dir.parent
|
||||
sources = []
|
||||
for name in TOPLEVEL_NAMES:
|
||||
path = src_dir / name
|
||||
if path.exists():
|
||||
sources.append(path)
|
||||
base_dir = src_dir.parent / "lib" / "base"
|
||||
if base_dir.is_dir():
|
||||
sources.extend(sorted(base_dir.rglob("*.vala")))
|
||||
|
||||
index = DocIndex(types={}, members={}, enum_members={})
|
||||
for path in sources:
|
||||
try:
|
||||
filename = path.relative_to(repo_root).as_posix()
|
||||
except ValueError:
|
||||
filename = path.name
|
||||
_collect_docs_from_source(path.read_text(encoding="utf-8"), index, filename)
|
||||
return index
|
||||
|
||||
def _collect_docs_from_source(source: str, index: DocIndex, filename: str):
|
||||
lines = source.split("\n")
|
||||
pending = None
|
||||
current_type = None
|
||||
current_is_enum = False
|
||||
i = 0
|
||||
n = len(lines)
|
||||
while i < n:
|
||||
line = lines[i]
|
||||
stripped = line.strip()
|
||||
|
||||
if stripped.startswith("/**"):
|
||||
start_line = i + 1
|
||||
block = [line]
|
||||
while "*/" not in lines[i]:
|
||||
i += 1
|
||||
block.append(lines[i])
|
||||
pending = _parse_doc_block(block)
|
||||
pending.filename = filename
|
||||
pending.line = start_line
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if stripped == "" or stripped.startswith("//") or stripped.startswith("["):
|
||||
i += 1
|
||||
continue
|
||||
|
||||
indent = len(line) - len(line.lstrip("\t"))
|
||||
|
||||
type_match = DOC_TYPE_PATTERN.match(stripped)
|
||||
if indent == 1 and type_match is not None:
|
||||
current_type = type_match.group(2)
|
||||
current_is_enum = type_match.group(1) in ("enum", "errordomain")
|
||||
if pending is not None:
|
||||
index.types[current_type] = pending
|
||||
pending = None
|
||||
i += 1
|
||||
continue
|
||||
|
||||
if pending is not None and current_type is not None and indent >= 2:
|
||||
if current_is_enum:
|
||||
member_match = DOC_ENUM_MEMBER_PATTERN.match(stripped)
|
||||
if member_match is not None:
|
||||
index.enum_members[(current_type, member_match.group(1))] = pending
|
||||
else:
|
||||
name = _parse_member_name(stripped, current_type)
|
||||
if name is not None:
|
||||
index.members[(current_type, name)] = pending
|
||||
pending = None
|
||||
i += 1
|
||||
continue
|
||||
|
||||
pending = None
|
||||
i += 1
|
||||
|
||||
def _parse_member_name(stripped: str, type_name: str):
|
||||
if not stripped.startswith("public"):
|
||||
return None
|
||||
signal_match = DOC_SIGNAL_PATTERN.match(stripped)
|
||||
if signal_match is not None:
|
||||
return signal_match.group(1)
|
||||
brace = stripped.find("{")
|
||||
if brace != -1 and "(" not in stripped[:brace]:
|
||||
property_match = DOC_PROPERTY_PATTERN.match(stripped)
|
||||
if property_match is not None:
|
||||
return property_match.group(1)
|
||||
ctor_match = re.match(r"public\s+" + re.escape(type_name) + r"(?:\.(\w+))?\s*\(", stripped)
|
||||
if ctor_match is not None:
|
||||
return "new" if ctor_match.group(1) is None else ctor_match.group(1)
|
||||
method_match = DOC_METHOD_PATTERN.match(stripped)
|
||||
if method_match is not None:
|
||||
return method_match.group(1)
|
||||
return None
|
||||
|
||||
def _parse_doc_block(block: List[str]) -> DocComment:
|
||||
text_lines = []
|
||||
for raw in block:
|
||||
s = raw.strip()
|
||||
if s.startswith("/**"):
|
||||
s = s[3:]
|
||||
if s.endswith("*/"):
|
||||
s = s[:-2]
|
||||
s = s.strip()
|
||||
if s.startswith("*"):
|
||||
s = s[1:]
|
||||
if s.startswith(" "):
|
||||
s = s[1:]
|
||||
text_lines.append(s.rstrip())
|
||||
|
||||
# Valadoc block tags: "@param <name> <description>" and "@return <description>".
|
||||
body_lines = []
|
||||
params = {}
|
||||
returns_lines = []
|
||||
target = body_lines
|
||||
for line in text_lines:
|
||||
param_match = re.match(r"@param\s+(\w+)\s*(.*)", line)
|
||||
return_match = re.match(r"@returns?\s*(.*)", line)
|
||||
if param_match is not None:
|
||||
params[param_match.group(1)] = [param_match.group(2)]
|
||||
target = params[param_match.group(1)]
|
||||
continue
|
||||
if return_match is not None:
|
||||
returns_lines = [return_match.group(1)]
|
||||
target = returns_lines
|
||||
continue
|
||||
target.append(line)
|
||||
|
||||
def finish(lines):
|
||||
return _valadoc_to_markdown("\n".join(lines).strip())
|
||||
|
||||
return DocComment(
|
||||
body=finish(body_lines),
|
||||
params={k: finish(v) for k, v in params.items()},
|
||||
returns=finish(returns_lines),
|
||||
)
|
||||
|
||||
def _valadoc_to_markdown(text: str) -> str:
|
||||
if not text:
|
||||
return text
|
||||
# Code blocks: {{{ ... }}} -> fenced ``` blocks.
|
||||
def code_block(match):
|
||||
code = match.group(1).strip("\n")
|
||||
return "\n```\n" + code + "\n```\n"
|
||||
text = re.sub(r"\{\{\{(.*?)\}\}\}", code_block, text, flags=re.DOTALL)
|
||||
# Note: {@link ...} cross-references are resolved later, at injection time,
|
||||
# where the full symbol table is available (see inject_documentation).
|
||||
# Inline monospace: ``x`` -> `x` (valadoc) is already markdown-compatible.
|
||||
# Bold: ''x'' -> **x**.
|
||||
text = re.sub(r"''(.+?)''", r"**\1**", text)
|
||||
# Italic: //x// -> *x*, but leave protocol-relative URLs (://) alone.
|
||||
text = re.sub(r"(?<![:/])//(?!/)(.+?)//", r"*\1*", text)
|
||||
return text
|
||||
|
||||
def inject_documentation(merged_namespace: ET.Element, docs: DocIndex):
|
||||
glib_signal_tag = f"{{{GLIB_NAMESPACE}}}signal"
|
||||
callable_tags = {
|
||||
f"{{{CORE_NAMESPACE}}}method",
|
||||
f"{{{CORE_NAMESPACE}}}constructor",
|
||||
f"{{{CORE_NAMESPACE}}}function",
|
||||
f"{{{CORE_NAMESPACE}}}virtual-method",
|
||||
glib_signal_tag,
|
||||
}
|
||||
property_tag = f"{{{CORE_NAMESPACE}}}property"
|
||||
member_tag = f"{{{CORE_NAMESPACE}}}member"
|
||||
doc_tag = f"{{{CORE_NAMESPACE}}}doc"
|
||||
|
||||
# Build a symbol table so {@link ...} can be turned into real gi-docgen
|
||||
# cross-references. Keys use Vala-style names (underscores); values carry the
|
||||
# gi-docgen link kind and the GIR name (dashes for properties/signals).
|
||||
type_kinds = {} # type_name -> gi-docgen kind ("class"/"iface"/"enum"/"error")
|
||||
member_kinds = {} # (type_name, vala_member) -> (kind, gir_name)
|
||||
for type_elem in list(merged_namespace):
|
||||
type_name = type_elem.get("name")
|
||||
if type_name is None:
|
||||
continue
|
||||
tag = type_elem.tag.split("}")[-1]
|
||||
if tag == "class":
|
||||
type_kinds[type_name] = "class"
|
||||
elif tag == "interface":
|
||||
type_kinds[type_name] = "iface"
|
||||
elif tag == "enumeration":
|
||||
type_kinds[type_name] = "error" \
|
||||
if type_elem.get(f"{{{GLIB_NAMESPACE}}}error-domain") is not None \
|
||||
else "enum"
|
||||
for child in list(type_elem):
|
||||
cname = child.get("name")
|
||||
if cname is None:
|
||||
continue
|
||||
if child.tag == f"{{{CORE_NAMESPACE}}}method":
|
||||
member_kinds[(type_name, cname)] = ("method", cname)
|
||||
elif child.tag == f"{{{CORE_NAMESPACE}}}constructor":
|
||||
member_kinds[(type_name, cname)] = ("ctor", cname)
|
||||
elif child.tag == property_tag:
|
||||
member_kinds[(type_name, cname.replace("-", "_"))] = ("property", cname)
|
||||
elif child.tag == glib_signal_tag:
|
||||
member_kinds[(type_name, cname.replace("-", "_"))] = ("signal", cname)
|
||||
|
||||
def resolve_links(text: str) -> str:
|
||||
def repl(match):
|
||||
target = re.sub(r"^Frida\.", "", match.group(1).strip())
|
||||
parts = target.split(".")
|
||||
if len(parts) == 1:
|
||||
kind = type_kinds.get(parts[0])
|
||||
if kind is not None:
|
||||
return f"[{kind}@Frida.{parts[0]}]"
|
||||
elif len(parts) == 2:
|
||||
info = member_kinds.get((parts[0], parts[1]))
|
||||
if info is not None:
|
||||
kind, gir_name = info
|
||||
if kind == "method":
|
||||
return f"[method@Frida.{parts[0]}.{gir_name}]"
|
||||
if kind == "ctor":
|
||||
return f"[ctor@Frida.{parts[0]}.{gir_name}]"
|
||||
if kind == "property":
|
||||
return f"[property@Frida.{parts[0]}:{gir_name}]"
|
||||
if kind == "signal":
|
||||
return f"[signal@Frida.{parts[0]}::{gir_name}]"
|
||||
return "`" + target + "`"
|
||||
return re.sub(r"\{@link\s+([^}]+)\}", repl, text)
|
||||
|
||||
def set_doc(elem: ET.Element, text: str, filename: str = "<generated>",
|
||||
line: int = 0):
|
||||
if not text:
|
||||
return
|
||||
if elem.find(doc_tag) is not None:
|
||||
return
|
||||
doc = ET.Element(doc_tag)
|
||||
doc.set("xml:space", "preserve")
|
||||
# gi-docgen accesses filename/line unconditionally, so always emit them,
|
||||
# mirroring what g-ir-scanner produces for hand-written C.
|
||||
doc.set("filename", filename)
|
||||
doc.set("line", str(line))
|
||||
doc.text = resolve_links(text)
|
||||
elem.insert(0, doc)
|
||||
|
||||
def member_doc(type_name: str, name: str):
|
||||
comment = docs.members.get((type_name, name))
|
||||
if comment is None:
|
||||
for suffix in ("_finish", "_sync"):
|
||||
if name.endswith(suffix):
|
||||
comment = docs.members.get((type_name, name[: -len(suffix)]))
|
||||
if comment is not None:
|
||||
break
|
||||
return comment
|
||||
|
||||
def apply_callable(elem: ET.Element, comment: DocComment):
|
||||
set_doc(elem, comment.body, comment.filename, comment.line)
|
||||
if comment.returns:
|
||||
rv = elem.find(f"{{{CORE_NAMESPACE}}}return-value")
|
||||
if rv is not None:
|
||||
set_doc(rv, comment.returns, comment.filename, comment.line)
|
||||
params_elem = elem.find(f"{{{CORE_NAMESPACE}}}parameters")
|
||||
if params_elem is not None and comment.params:
|
||||
for param in params_elem.findall(f"{{{CORE_NAMESPACE}}}parameter"):
|
||||
text = comment.params.get(param.get("name"))
|
||||
if text:
|
||||
set_doc(param, text, comment.filename, comment.line)
|
||||
|
||||
for type_elem in list(merged_namespace):
|
||||
type_name = type_elem.get("name")
|
||||
if type_name is None:
|
||||
continue
|
||||
tag = type_elem.tag.split("}")[-1]
|
||||
|
||||
type_comment = docs.types.get(type_name)
|
||||
if type_comment is not None and tag in ("class", "interface", "enumeration"):
|
||||
set_doc(type_elem, type_comment.body, type_comment.filename,
|
||||
type_comment.line)
|
||||
|
||||
for child in list(type_elem):
|
||||
child_name = child.get("name")
|
||||
if child_name is None:
|
||||
continue
|
||||
if child.tag in callable_tags:
|
||||
lookup = child_name.replace("-", "_") if child.tag == glib_signal_tag \
|
||||
else child_name
|
||||
comment = member_doc(type_name, lookup)
|
||||
if comment is not None:
|
||||
apply_callable(child, comment)
|
||||
elif child.tag == f"{{{CORE_NAMESPACE}}}constructor" \
|
||||
and child_name == "new":
|
||||
# Vala's implicit default constructor; give it a sensible
|
||||
# description rather than leaving it bare.
|
||||
set_doc(child, f"Creates a new [class@Frida.{type_name}].")
|
||||
elif child.tag == property_tag:
|
||||
vala_name = child_name.replace("-", "_")
|
||||
comment = docs.members.get((type_name, vala_name))
|
||||
if comment is not None:
|
||||
set_doc(child, comment.body, comment.filename, comment.line)
|
||||
# The C API also exposes the property through getter/setter
|
||||
# methods; give them the same description.
|
||||
accessors = {"get_" + vala_name, "set_" + vala_name}
|
||||
for sibling in type_elem:
|
||||
if sibling.tag == f"{{{CORE_NAMESPACE}}}method" \
|
||||
and sibling.get("name") in accessors:
|
||||
set_doc(sibling, comment.body, comment.filename,
|
||||
comment.line)
|
||||
elif child.tag == member_tag:
|
||||
comment = docs.enum_members.get((type_name, child_name.upper()))
|
||||
if comment is not None:
|
||||
set_doc(child, comment.body, comment.filename, comment.line)
|
||||
|
||||
def emit_vapi(api, output_dir):
|
||||
with OutputFile(output_dir / f"frida-core-{api.version}.vapi") as output_vapi_file:
|
||||
output_vapi_file.write("[CCode (cheader_filename = \"frida-core.h\", cprefix = \"Frida\", lower_case_cprefix = \"frida_\")]")
|
||||
@@ -304,6 +643,17 @@ def emit_vapi(api, output_dir):
|
||||
output_deps_file.write("gobject-2.0\n")
|
||||
output_deps_file.write("gio-2.0\n")
|
||||
|
||||
def emit_symbol_maps_from_source(output_dir: Path):
|
||||
with OutputFile(output_dir / 'frida-core.version') as f:
|
||||
f.write("{\n")
|
||||
f.write(" global:\n")
|
||||
f.write(" frida_*;\n")
|
||||
f.write(" _frida_*;\n")
|
||||
f.write("\n")
|
||||
f.write(" local:\n")
|
||||
f.write(" *;\n")
|
||||
f.write("};\n")
|
||||
|
||||
def parse_api(frida_version, frida_version_components, api_version, toplevel_code, core_header, core_vapi, base_header, base_vapi):
|
||||
all_headers = core_header + "\n" + base_header
|
||||
|
||||
@@ -311,6 +661,7 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
|
||||
enum_types = []
|
||||
|
||||
base_public_types = {
|
||||
"SpawnGatingOptions": "ProcessMatchOptions",
|
||||
"FrontmostQueryOptions": "SpawnOptions",
|
||||
"ApplicationQueryOptions": "FrontmostQueryOptions",
|
||||
"ProcessQueryOptions": "ApplicationQueryOptions",
|
||||
@@ -325,6 +676,9 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
|
||||
"EndpointParameters": "PortalService",
|
||||
"AuthenticationService": "EndpointParameters",
|
||||
"StaticAuthenticationService": "AuthenticationService",
|
||||
"WebRequestHandler": "StaticAuthenticationService",
|
||||
"WebRequest": "WebRequestHandler",
|
||||
"WebResponse": "WebRequest",
|
||||
}
|
||||
internal_type_prefixes = [
|
||||
"AgentMessageKind",
|
||||
@@ -419,7 +773,7 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
|
||||
object_type.c_method_prototypes.append(method_cprototype)
|
||||
elif method_name == 'get_type':
|
||||
object_type.c_get_type = method_cprototype.replace("G_GNUC_CONST ;", "G_GNUC_CONST;")
|
||||
for d in re.finditer(r"^typedef.+?\(\*(" + object_type.c_name + r".+?)\) \(.+\);$", core_header, re.MULTILINE):
|
||||
for d in re.finditer(r"^typedef.+?\(\*(" + object_type.c_name + r".+?)\) \(.+\);$", all_headers, re.MULTILINE):
|
||||
delegate_cname = d.group(1)
|
||||
if delegate_cname not in seen_cdelegates:
|
||||
seen_cdelegates.add(delegate_cname)
|
||||
@@ -510,6 +864,8 @@ def function_is_public(name):
|
||||
"generate_certificate",
|
||||
"get_dbus_context",
|
||||
"invalidate_dbus_context",
|
||||
"make_json_reader",
|
||||
"make_json_reader_taking_node",
|
||||
"make_parameters_dict",
|
||||
"compute_system_parameters",
|
||||
"parse_control_address",
|
||||
@@ -519,6 +875,8 @@ def function_is_public(name):
|
||||
"check_kernel_version",
|
||||
"get_syscall_signatures",
|
||||
"get_compat32_syscall_signatures",
|
||||
"get_xnu_mach_traps",
|
||||
"get_xnu_bsd_syscalls",
|
||||
"make_stdio_pipes",
|
||||
"make_stdio_pipe",
|
||||
}
|
||||
|
||||
+75
-39
@@ -3,6 +3,7 @@ fs = import('fs')
|
||||
generate_api = [python, files('generate.py')]
|
||||
|
||||
generate_api_args = [
|
||||
meson.current_build_dir(),
|
||||
frida_version,
|
||||
frida_major_version.to_string(),
|
||||
frida_minor_version.to_string(),
|
||||
@@ -15,7 +16,6 @@ generate_api_args = [
|
||||
base_build_dir / 'frida-base.h',
|
||||
base_build_dir / base_gir_name,
|
||||
base_build_dir / f'frida-base-@[email protected]',
|
||||
meson.current_build_dir(),
|
||||
]
|
||||
|
||||
core_api = custom_target('frida-core-api',
|
||||
@@ -44,6 +44,24 @@ glib_gir = fs.copyfile('GLib-2.0.gir', install: install_glib_girs, install_dir:
|
||||
gobject_gir = fs.copyfile('GObject-2.0.gir', install: install_glib_girs, install_dir: girdir)
|
||||
gio_gir = fs.copyfile('Gio-2.0.gir', install: install_glib_girs, install_dir: girdir)
|
||||
|
||||
g_ir_compiler = find_program('g-ir-compiler', required: false)
|
||||
if g_ir_compiler.found()
|
||||
custom_target('frida-core-typelib',
|
||||
command: [
|
||||
g_ir_compiler,
|
||||
'--output', '@OUTPUT@',
|
||||
'--includedir=' + girdir,
|
||||
'--includedir=' + meson.current_source_dir(),
|
||||
'@INPUT@',
|
||||
],
|
||||
input: core_public_gir,
|
||||
output: f'Frida-@[email protected]',
|
||||
depends: core,
|
||||
install: true,
|
||||
install_dir: typelibdir,
|
||||
)
|
||||
endif
|
||||
|
||||
core_public_vapi_stamp = custom_target('frida-core-vapi-stamp',
|
||||
input: core_public_vapi,
|
||||
output: f'frida-core-@[email protected]',
|
||||
@@ -54,22 +72,26 @@ core_public_vapi_stamp = custom_target('frida-core-vapi-stamp',
|
||||
],
|
||||
)
|
||||
|
||||
core_public_lib = custom_target('frida-core-library',
|
||||
input: [core] + internal_libs,
|
||||
output: f'libfrida-core-@[email protected]',
|
||||
command: [
|
||||
python,
|
||||
files('merge.py'),
|
||||
lib,
|
||||
libtool,
|
||||
ar,
|
||||
meson.current_build_dir(),
|
||||
'@OUTPUT@',
|
||||
'@INPUT@'
|
||||
],
|
||||
install: true,
|
||||
install_dir: get_option('libdir'),
|
||||
)
|
||||
if is_shared
|
||||
core_public_lib = core
|
||||
else
|
||||
core_public_lib = custom_target('frida-core-library',
|
||||
input: [core] + internal_libs,
|
||||
output: f'libfrida-core-@[email protected]',
|
||||
command: [
|
||||
python,
|
||||
files('merge.py'),
|
||||
lib,
|
||||
libtool,
|
||||
ar,
|
||||
meson.current_build_dir(),
|
||||
'@OUTPUT@',
|
||||
'@INPUT@'
|
||||
],
|
||||
install: true,
|
||||
install_dir: get_option('libdir'),
|
||||
)
|
||||
endif
|
||||
|
||||
core_public_extra_headers = []
|
||||
if host_os == 'android'
|
||||
@@ -96,29 +118,43 @@ installed_variables = {
|
||||
'frida_vapidir': vapidir,
|
||||
}
|
||||
|
||||
core_public_dep = declare_dependency(
|
||||
sources: [core_public_h, core_public_gir, glib_gir, gobject_gir, gio_gir, core_public_vapi_stamp, core_public_extra_headers],
|
||||
include_directories: include_directories('.'),
|
||||
link_args: backend_libs_private,
|
||||
link_with: core_public_lib,
|
||||
dependencies: core_deps,
|
||||
variables: uninstalled_variables,
|
||||
)
|
||||
core_public_dep_kwargs = {
|
||||
'sources': [core_public_h, core_public_gir, glib_gir, gobject_gir, gio_gir, core_public_vapi_stamp] + core_public_extra_headers,
|
||||
'include_directories': include_directories('.'),
|
||||
'link_with': core_public_lib,
|
||||
'dependencies': core_deps,
|
||||
'variables': uninstalled_variables,
|
||||
}
|
||||
if not is_shared
|
||||
core_public_dep_kwargs += {
|
||||
'link_args': backend_libs_private,
|
||||
}
|
||||
endif
|
||||
core_public_dep = declare_dependency(kwargs: core_public_dep_kwargs)
|
||||
|
||||
pkg = import('pkgconfig')
|
||||
pkg.generate(
|
||||
filebase: f'frida-core-@api_version@',
|
||||
name: 'Frida Core Library',
|
||||
version: frida_version,
|
||||
description: 'Instrumentation toolkit for developers, reverse-engineers, and security researchers',
|
||||
requires: ['glib-2.0', 'gobject-2.0', 'gio-2.0', 'json-glib-1.0'],
|
||||
requires_private: ['gmodule-2.0', 'gee-0.8', 'libsoup-3.0', 'frida-gum-1.0', 'frida-gumjs-inspector-1.0', 'libbrotlidec'] + backend_reqs_private,
|
||||
subdirs: f'frida-@api_version@',
|
||||
libraries: core_public_lib,
|
||||
libraries_private: backend_libs_private,
|
||||
extra_cflags: ['-I${frida_includedir}'],
|
||||
variables: installed_variables,
|
||||
uninstalled_variables: uninstalled_variables,
|
||||
)
|
||||
pkg_kwargs = {
|
||||
'filebase': f'frida-core-@api_version@',
|
||||
'name': 'Frida Core Library',
|
||||
'version': frida_version,
|
||||
'description': 'Instrumentation toolkit for developers, reverse-engineers, and security researchers',
|
||||
'requires': ['glib-2.0', 'gobject-2.0', 'gio-2.0', 'json-glib-1.0'],
|
||||
'subdirs': f'frida-@api_version@',
|
||||
'extra_cflags': ['-I${frida_includedir}'],
|
||||
'variables': installed_variables,
|
||||
'uninstalled_variables': uninstalled_variables,
|
||||
}
|
||||
if is_shared
|
||||
pkg_kwargs += {
|
||||
'libraries': ['-L${libdir}', f'-lfrida-core-@api_version@'],
|
||||
}
|
||||
else
|
||||
pkg_kwargs += {
|
||||
'libraries': core_public_lib,
|
||||
'requires_private': ['gmodule-2.0', 'gee-0.8', 'libsoup-3.0', 'frida-gum-1.0', 'frida-gumjs-inspector-1.0', 'libbrotlidec'] + backend_reqs_private,
|
||||
'libraries_private': backend_libs_private,
|
||||
}
|
||||
endif
|
||||
pkg.generate(kwargs: pkg_kwargs)
|
||||
|
||||
meson.override_dependency(f'frida-core-@api_version@', core_public_dep)
|
||||
+288
-157
@@ -5,30 +5,37 @@ namespace Frida.Barebone {
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
private SocketConnection hostlink;
|
||||
private IOStream hostlink;
|
||||
private BufferedInputStream input;
|
||||
private OutputStream output;
|
||||
|
||||
private ByteOrder byte_order;
|
||||
private uint pointer_size;
|
||||
|
||||
private AgentConfig agent_config;
|
||||
private VsockTransportConfig? vsock_transport;
|
||||
private ImageConfig? image_config;
|
||||
private KernelRelocation? relocation;
|
||||
private uint64 kernel_base;
|
||||
private Machine machine;
|
||||
private Allocator allocator;
|
||||
|
||||
private Allocation elf_allocation;
|
||||
private Allocation config_allocation;
|
||||
private Callback mprotect_callback;
|
||||
private Callback remap_writable_pages_callback;
|
||||
|
||||
private Gee.Map<uint16, Promise<Variant>> pending_requests = new Gee.HashMap<uint16, Promise<Variant>> ();
|
||||
private uint16 next_request_id = 1;
|
||||
|
||||
private const int COMMAND_TIMEOUT_MS = 25000;
|
||||
|
||||
public static async AgentConnection open (AgentConfig agent_config, ImageConfig? image_config, Machine machine,
|
||||
Allocator allocator, Cancellable? cancellable) throws Error, IOError {
|
||||
public static async AgentConnection open (AgentConfig agent_config, ImageConfig? image_config,
|
||||
KernelRelocation? relocation, uint64 kernel_base, Machine machine, Allocator allocator,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
var connection = new AgentConnection () {
|
||||
agent_config = agent_config,
|
||||
image_config = image_config,
|
||||
relocation = relocation,
|
||||
kernel_base = kernel_base,
|
||||
machine = machine,
|
||||
allocator = allocator,
|
||||
};
|
||||
@@ -42,32 +49,48 @@ namespace Frida.Barebone {
|
||||
return connection;
|
||||
}
|
||||
|
||||
private async bool init_async (int io_priority, Cancellable? cancellable) throws Error, IOError {
|
||||
HostlinkTransportConfig? transport_config = agent_config.transport as HostlinkTransportConfig;
|
||||
if (transport_config == null)
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported transport config: only hostlink is supported for now");
|
||||
/**
|
||||
* Attaches to an agent already resident in the target, which brought its own
|
||||
* transport with it, such as the Linux kernel module exposing /dev/frida.
|
||||
*/
|
||||
public static async AgentConnection open_resident (IOStream stream, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var connection = new AgentConnection () {
|
||||
byte_order = ByteOrder.HOST,
|
||||
pointer_size = (uint) sizeof (void *),
|
||||
};
|
||||
|
||||
var qmp = yield QmpClient.open (transport_config.qmp, 0, cancellable);
|
||||
var link = yield qmp.open_hostlink (cancellable);
|
||||
hostlink = link.connection;
|
||||
input = (BufferedInputStream) Object.new (typeof (BufferedInputStream),
|
||||
"base-stream", hostlink.get_input_stream (),
|
||||
"close-base-stream", false,
|
||||
"buffer-size", 128 * 1024);
|
||||
output = hostlink.get_output_stream ();
|
||||
connection.adopt_hostlink_streams (stream);
|
||||
connection.process_incoming_messages.begin ();
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
private const uint8 TRANSPORT_KIND_VIRTIO = 0;
|
||||
private const uint8 TRANSPORT_KIND_VSOCK = 1;
|
||||
|
||||
private async bool init_async (int io_priority, Cancellable? cancellable) throws Error, IOError {
|
||||
var transport_tag = yield resolve_transport (cancellable);
|
||||
|
||||
var gdb = machine.gdb;
|
||||
ByteOrder byte_order = gdb.byte_order;
|
||||
uint pointer_size = gdb.pointer_size;
|
||||
byte_order = gdb.byte_order;
|
||||
pointer_size = gdb.pointer_size;
|
||||
|
||||
uint64 kernel_base;
|
||||
Layout layout;
|
||||
uint64 preferred_base = 0;
|
||||
if (image_config != null) {
|
||||
kernel_base = image_config.base.address;
|
||||
layout = yield Layout.load_from_symbol_source (File.new_for_path (image_config.file), kernel_base,
|
||||
byte_order, pointer_size, cancellable);
|
||||
var payload = yield Img4.parse_file (File.new_for_path (image_config.file), cancellable);
|
||||
var kernelcache = Layout.parse_kernelcache (payload.data);
|
||||
preferred_base = kernelcache.preferred_address;
|
||||
if (relocation != null) {
|
||||
layout = Layout.load_from_module (kernelcache, payload.data, preferred_base, byte_order,
|
||||
pointer_size);
|
||||
rebase_layout (layout, preferred_base, relocation);
|
||||
} else {
|
||||
layout = Layout.load_from_module (kernelcache, payload.data, kernel_base, byte_order,
|
||||
pointer_size);
|
||||
}
|
||||
} else {
|
||||
kernel_base = 0;
|
||||
layout = new Layout.empty ();
|
||||
}
|
||||
if (kernel_base == 0)
|
||||
@@ -83,9 +106,12 @@ namespace Frida.Barebone {
|
||||
foreach (var e in image_config.symbols.entries) {
|
||||
unowned string name = e.key;
|
||||
if (!symbols.has_key (name)) {
|
||||
uint32 offset = (relocation != null)
|
||||
? relocation.runtime_offset (preferred_base + e.value)
|
||||
: (uint32) e.value;
|
||||
var s = new SymbolInfo () {
|
||||
name = name,
|
||||
offset = (uint32) e.value,
|
||||
offset = offset,
|
||||
symbol_type = 0xf,
|
||||
section = 0x10, // FIXME
|
||||
};
|
||||
@@ -99,9 +125,59 @@ namespace Frida.Barebone {
|
||||
if (thread_block == null)
|
||||
throw new Error.NOT_SUPPORTED ("Missing symbol for thread_block");
|
||||
|
||||
var config_builder = new VariantBuilder (new VariantType ("(tuta(ssuuuu)ay)"));
|
||||
config_builder.add ("t", link.mmio);
|
||||
config_builder.add ("u", link.irq);
|
||||
SymbolInfo? panic = symbols["panic"];
|
||||
if (panic != null && machine is Arm64Machine)
|
||||
((Arm64Machine) machine).call_landing_zone = kernel_base + panic.offset;
|
||||
|
||||
Bytes symbol_data = hash_builder.build (byte_order);
|
||||
|
||||
Gum.ElfModule elf;
|
||||
try {
|
||||
elf = new Gum.ElfModule.from_file (agent_config.path);
|
||||
} catch (Gum.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
}
|
||||
|
||||
var raw_elf = gdb.make_buffer (new Bytes (elf.get_file_data ()));
|
||||
// Slots whose symbol is absent on this kernel are left zero; the agent's
|
||||
// xnu.rs uses Option<fn> and falls back across per-kernel name variants.
|
||||
elf.enumerate_symbols (s => {
|
||||
unowned Gum.ElfSectionDetails? sect = s.section;
|
||||
if (sect != null && sect.name == ".kernel_addrs") {
|
||||
string name = s.name[1:];
|
||||
SymbolInfo? info = symbols[name];
|
||||
if (info != null) {
|
||||
size_t file_offset = (size_t) (sect.offset + (s.address - sect.address));
|
||||
raw_elf.write_pointer (file_offset, kernel_base + info.offset);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
yield machine.enter_exception_level (1, 1000, cancellable);
|
||||
|
||||
yield run_until_thread_block (kernel_base + thread_block.offset, cancellable);
|
||||
|
||||
size_t page_size = yield machine.query_page_size (cancellable);
|
||||
|
||||
yield ((Arm64Machine) machine).learn_permission_templates (kernel_base + thread_block.offset, cancellable);
|
||||
|
||||
elf_allocation = yield inject_elf (elf, raw_elf.bytes, page_size, machine, allocator, cancellable);
|
||||
|
||||
uint64 start_address = 0;
|
||||
uint64 base_va = elf_allocation.virtual_address;
|
||||
elf.enumerate_symbols (e => {
|
||||
if (e.name == "_start")
|
||||
start_address = base_va + e.address;
|
||||
return true;
|
||||
});
|
||||
if (start_address == 0)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid agent: no _start symbol found");
|
||||
|
||||
var config_builder = new VariantBuilder (new VariantType ("((tt)yvta(ssuuuu)ay)"));
|
||||
config_builder.add ("(tt)", base_va, (uint64) elf_allocation.size);
|
||||
config_builder.add_value (transport_tag.get_child_value (0));
|
||||
config_builder.add_value (transport_tag.get_child_value (1));
|
||||
config_builder.add ("t", kernel_base);
|
||||
|
||||
config_builder.open (new VariantType ("a(ssuuuu)"));
|
||||
@@ -117,78 +193,9 @@ namespace Frida.Barebone {
|
||||
}
|
||||
config_builder.close ();
|
||||
|
||||
Bytes symbol_data = hash_builder.build (byte_order);
|
||||
config_builder.add_value (Variant.new_from_data (new VariantType ("ay"), symbol_data.get_data (), true,
|
||||
symbol_data));
|
||||
|
||||
Gum.ElfModule elf;
|
||||
try {
|
||||
elf = new Gum.ElfModule.from_file (agent_config.path);
|
||||
} catch (Gum.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
}
|
||||
|
||||
var raw_elf = gdb.make_buffer (new Bytes (elf.get_file_data ()));
|
||||
var missing_symbols = new Gee.ArrayList<string> ();
|
||||
elf.enumerate_symbols (s => {
|
||||
unowned Gum.ElfSectionDetails? sect = s.section;
|
||||
if (sect != null && sect.name == ".kernel_addrs") {
|
||||
string name = s.name[1:];
|
||||
SymbolInfo? info = symbols[name];
|
||||
if (info != null) {
|
||||
size_t file_offset = (size_t) (sect.offset + (s.address - sect.address));
|
||||
raw_elf.write_pointer (file_offset, kernel_base + info.offset);
|
||||
} else {
|
||||
missing_symbols.add (name);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (!missing_symbols.is_empty) {
|
||||
throw new Error.INVALID_ARGUMENT ("Missing symbols for: %s",
|
||||
string.joinv (", ", missing_symbols.to_array ()));
|
||||
}
|
||||
|
||||
yield machine.enter_exception_level (1, 1000, cancellable);
|
||||
|
||||
var bp = yield gdb.add_breakpoint (SOFT, kernel_base + thread_block.offset, 4, cancellable);
|
||||
GDB.Breakpoint? hit_breakpoint = null;
|
||||
do {
|
||||
var exception = yield gdb.continue_until_exception (cancellable);
|
||||
hit_breakpoint = exception.breakpoint;
|
||||
} while (hit_breakpoint != bp);
|
||||
yield bp.remove (cancellable);
|
||||
|
||||
size_t page_size = yield machine.query_page_size (cancellable);
|
||||
|
||||
elf_allocation = yield inject_elf (elf, raw_elf.bytes, page_size, machine, allocator, cancellable);
|
||||
|
||||
uint64 start_address = 0;
|
||||
uint64 remap_writable_pages_address = 0;
|
||||
uint64 mprotect_address = 0;
|
||||
uint64 base_va = elf_allocation.virtual_address;
|
||||
elf.enumerate_symbols (e => {
|
||||
if (e.name == "_start")
|
||||
start_address = base_va + e.address;
|
||||
else if (e.name == "gum_barebone_try_remap_writable_pages")
|
||||
remap_writable_pages_address = base_va + e.address;
|
||||
else if (e.name == "gum_try_mprotect")
|
||||
mprotect_address = base_va + e.address;
|
||||
else
|
||||
return true;
|
||||
return start_address == 0 || mprotect_address == 0 || remap_writable_pages_address == 0;
|
||||
});
|
||||
if (start_address == 0)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid agent: no _start symbol found");
|
||||
if (remap_writable_pages_address == 0)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid agent: no gum_barebone_try_remap_writable_pages symbol found");
|
||||
if (mprotect_address == 0)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid agent: no gum_try_mprotect symbol found");
|
||||
|
||||
remap_writable_pages_callback = yield new Callback (remap_writable_pages_address,
|
||||
new RemapWritablePagesHandler (machine), machine, cancellable);
|
||||
mprotect_callback = yield new Callback (mprotect_address, new MemoryProtectHandler (machine), machine, cancellable);
|
||||
|
||||
var config_blob = config_builder.end ().get_data_as_bytes ();
|
||||
config_allocation = yield allocator.allocate (config_blob.get_size (), 8, cancellable);
|
||||
|
||||
@@ -200,13 +207,105 @@ namespace Frida.Barebone {
|
||||
},
|
||||
cancellable);
|
||||
|
||||
yield gdb.continue (cancellable);
|
||||
// The vphone research kernel panics on any synchronous exception taken while a
|
||||
// debugger is attached, which the worker hits in the allocator during gum_init.
|
||||
var arm64 = machine as Arm64Machine;
|
||||
bool post_inject_access_uses_bridge = arm64 != null && arm64.physical_memory != null;
|
||||
if (post_inject_access_uses_bridge)
|
||||
yield gdb.detach (cancellable);
|
||||
else
|
||||
yield gdb.continue (cancellable);
|
||||
yield establish_hostlink (cancellable);
|
||||
|
||||
process_incoming_messages.begin ();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async void run_until_thread_block (uint64 address, Cancellable? cancellable) throws Error, IOError {
|
||||
var gdb = machine.gdb;
|
||||
var bp = yield gdb.add_breakpoint (SOFT, address, 4, cancellable);
|
||||
|
||||
GDB.Breakpoint? hit = null;
|
||||
do {
|
||||
var exception = yield gdb.continue_until_exception (cancellable);
|
||||
hit = exception.breakpoint;
|
||||
} while (hit != bp);
|
||||
|
||||
yield bp.remove (cancellable);
|
||||
}
|
||||
|
||||
private async Variant resolve_transport (Cancellable? cancellable) throws Error, IOError {
|
||||
if (agent_config.transport is HostlinkTransportConfig)
|
||||
return yield connect_virtio_transport ((HostlinkTransportConfig) agent_config.transport, cancellable);
|
||||
if (agent_config.transport is VsockTransportConfig) {
|
||||
var config = (VsockTransportConfig) agent_config.transport;
|
||||
vsock_transport = config;
|
||||
return new Variant.tuple ({
|
||||
new Variant.byte (TRANSPORT_KIND_VSOCK),
|
||||
new Variant.variant (new Variant.uint32 (config.port))
|
||||
});
|
||||
}
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported transport config");
|
||||
}
|
||||
|
||||
private async void establish_hostlink (Cancellable? cancellable) throws Error, IOError {
|
||||
if (vsock_transport == null)
|
||||
return;
|
||||
|
||||
#if WINDOWS
|
||||
throw new Error.NOT_SUPPORTED ("Hostlink transport is not available on this OS");
|
||||
#else
|
||||
var address = new UnixSocketAddress (vsock_transport.socket_path);
|
||||
var client = new SocketClient ();
|
||||
while (true) {
|
||||
try {
|
||||
adopt_hostlink_streams (yield client.connect_async (address, cancellable));
|
||||
return;
|
||||
} catch (GLib.Error e) {
|
||||
if (e is IOError.CANCELLED)
|
||||
throw (IOError) e;
|
||||
var source = new TimeoutSource (50);
|
||||
source.set_callback (establish_hostlink.callback);
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
yield;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private async Variant connect_virtio_transport (HostlinkTransportConfig config, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var qmp = yield QmpClient.open (config.qmp, 0, cancellable);
|
||||
var link = yield qmp.open_hostlink (cancellable);
|
||||
adopt_hostlink_streams (link.connection);
|
||||
|
||||
Variant[] virtio_cfg = { new Variant.uint64 (link.mmio), new Variant.uint32 (link.irq) };
|
||||
return new Variant.tuple ({
|
||||
new Variant.byte (TRANSPORT_KIND_VIRTIO),
|
||||
new Variant.variant (new Variant.tuple (virtio_cfg))
|
||||
});
|
||||
}
|
||||
|
||||
private void adopt_hostlink_streams (IOStream connection) {
|
||||
hostlink = connection;
|
||||
input = (BufferedInputStream) Object.new (typeof (BufferedInputStream),
|
||||
"base-stream", hostlink.get_input_stream (),
|
||||
"close-base-stream", false,
|
||||
"buffer-size", 128 * 1024);
|
||||
output = hostlink.get_output_stream ();
|
||||
}
|
||||
|
||||
private void rebase_layout (Layout layout, uint64 preferred_base, KernelRelocation reloc) throws Error {
|
||||
foreach (var s in layout.symbols)
|
||||
s.offset = reloc.runtime_offset (preferred_base + s.offset);
|
||||
foreach (var m in layout.modules) {
|
||||
m.offset = reloc.runtime_offset (preferred_base + m.offset);
|
||||
m.start_func_offset = reloc.runtime_offset (preferred_base + m.start_func_offset);
|
||||
m.stop_func_offset = reloc.runtime_offset (preferred_base + m.stop_func_offset);
|
||||
}
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
io_cancellable.cancel ();
|
||||
}
|
||||
@@ -241,16 +340,7 @@ namespace Frida.Barebone {
|
||||
private async Variant execute_command (Command command, Variant payload, Cancellable? cancellable) throws Error, IOError {
|
||||
uint16 request_id = next_request_id++;
|
||||
|
||||
var command_message = new Variant ("(yqv)", (uint8) command, request_id, payload);
|
||||
if (machine.gdb.byte_order != ByteOrder.HOST)
|
||||
command_message = command_message.byteswap ();
|
||||
var command_bytes = command_message.get_data_as_bytes ();
|
||||
|
||||
var builder = machine.gdb.make_buffer_builder ();
|
||||
Bytes frame = builder
|
||||
.append_uint32 ((uint32) command_bytes.get_size ())
|
||||
.append_bytes (command_bytes)
|
||||
.build ();
|
||||
Bytes frame = frame_message (command, request_id, payload);
|
||||
|
||||
var promise = new Promise<Variant> ();
|
||||
pending_requests[request_id] = promise;
|
||||
@@ -271,16 +361,25 @@ namespace Frida.Barebone {
|
||||
});
|
||||
timeout_source.attach (MainContext.get_thread_default ());
|
||||
|
||||
Variant response = null;
|
||||
try {
|
||||
return yield promise.future.wait_async (cancellable);
|
||||
response = yield promise.future.wait_async (cancellable);
|
||||
} finally {
|
||||
timeout_source.destroy ();
|
||||
}
|
||||
|
||||
if (!response.check_format_string ("(bv)", false))
|
||||
throw new Error.PROTOCOL ("Malformed reply from agent");
|
||||
bool succeeded;
|
||||
Variant detail;
|
||||
response.get ("(bv)", out succeeded, out detail);
|
||||
if (!succeeded)
|
||||
throw new Error.NOT_SUPPORTED ("%s", detail.get_string ());
|
||||
|
||||
return detail;
|
||||
}
|
||||
|
||||
private async void process_incoming_messages () {
|
||||
var byte_order = machine.gdb.byte_order;
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
size_t header_size = 4;
|
||||
@@ -324,6 +423,30 @@ namespace Frida.Barebone {
|
||||
payload.get ("(u&s)", out script_handle, out json);
|
||||
|
||||
script_message (AgentScriptId (script_handle), json, null);
|
||||
} else if (command_code == Command.REMAP_WRITABLE_PAGES) {
|
||||
Variant result;
|
||||
try {
|
||||
result = yield remap_writable_pages (payload, io_cancellable);
|
||||
} catch (Error e) {
|
||||
result = new Variant.uint64 (0);
|
||||
}
|
||||
yield send_reply (request_id, result);
|
||||
} else if (command_code == Command.MEMORY_PROTECT) {
|
||||
Variant result;
|
||||
try {
|
||||
result = yield protect_memory (payload, io_cancellable);
|
||||
} catch (Error e) {
|
||||
result = new Variant.boolean (false);
|
||||
}
|
||||
yield send_reply (request_id, result);
|
||||
} else if (command_code == Command.PATCH_CODE) {
|
||||
Variant result;
|
||||
try {
|
||||
result = yield patch_code (payload, io_cancellable);
|
||||
} catch (Error e) {
|
||||
result = new Variant.boolean (false);
|
||||
}
|
||||
yield send_reply (request_id, result);
|
||||
} else if (command_code == Command.REPLY) {
|
||||
Promise<Variant>? promise;
|
||||
if (pending_requests.unset (request_id, out promise))
|
||||
@@ -355,62 +478,67 @@ namespace Frida.Barebone {
|
||||
}
|
||||
}
|
||||
|
||||
private class RemapWritablePagesHandler : Object, CallbackHandler {
|
||||
public signal void output (string message);
|
||||
|
||||
public uint arity {
|
||||
get { return 2; }
|
||||
private async Variant remap_writable_pages (Variant payload, Cancellable? cancellable) throws Error, IOError {
|
||||
var arm64 = (Arm64Machine) machine;
|
||||
var physical_addresses = new Gee.ArrayList<uint64?> ();
|
||||
for (size_t i = 0; i != payload.n_children (); i++) {
|
||||
uint64 va = payload.get_child_value (i).get_uint64 ();
|
||||
physical_addresses.add (yield arm64.translate_address (va, cancellable));
|
||||
}
|
||||
|
||||
private Machine machine;
|
||||
Allocation allocation = yield machine.allocate_pages (physical_addresses, cancellable);
|
||||
|
||||
public RemapWritablePagesHandler (Machine machine) {
|
||||
this.machine = machine;
|
||||
}
|
||||
|
||||
public async uint64 handle_invocation (uint64[] args, CallFrame frame, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var pages = args[0];
|
||||
var num_pages = (uint) args[1];
|
||||
|
||||
var physical_addresses = new Gee.ArrayList<uint64?> ();
|
||||
var gdb = machine.gdb;
|
||||
var reader = new BufferReader (yield gdb.read_buffer (pages, num_pages * gdb.pointer_size, cancellable));
|
||||
for (uint i = 0; i != num_pages; i++)
|
||||
physical_addresses.add (reader.read_pointer ());
|
||||
|
||||
Allocation allocation = yield machine.allocate_pages (physical_addresses, cancellable);
|
||||
// TODO: Handle cleanup.
|
||||
|
||||
return allocation.virtual_address;
|
||||
}
|
||||
return new Variant.uint64 (allocation.virtual_address);
|
||||
}
|
||||
|
||||
private class MemoryProtectHandler : Object, CallbackHandler {
|
||||
public signal void output (string message);
|
||||
private async Variant protect_memory (Variant payload, Cancellable? cancellable) throws Error, IOError {
|
||||
uint64 address;
|
||||
uint64 size;
|
||||
uint32 prot;
|
||||
payload.get ("(ttu)", out address, out size, out prot);
|
||||
|
||||
public uint arity {
|
||||
get { return 3; }
|
||||
yield machine.protect_pages (address, (size_t) size, (Gum.PageProtection) prot, cancellable);
|
||||
|
||||
return new Variant.boolean (true);
|
||||
}
|
||||
|
||||
// CTRR/KTRR locks kernel text read-only against the guest CPU, so the agent cannot patch it
|
||||
// even through a writable alias. The physical-memory bridge writes the backing store directly,
|
||||
// which the lock does not cover, letting us land hooks in kernel and kext text.
|
||||
private async Variant patch_code (Variant payload, Cancellable? cancellable) throws Error, IOError {
|
||||
var arm64 = (Arm64Machine) machine;
|
||||
uint64 va;
|
||||
Variant bytes_value;
|
||||
payload.get ("(t@ay)", out va, out bytes_value);
|
||||
var data = (uint8[]) bytes_value.get_data_as_bytes ().get_data ();
|
||||
|
||||
size_t page_size = yield machine.query_page_size (cancellable);
|
||||
size_t offset = 0;
|
||||
while (offset < data.length) {
|
||||
uint64 page_va = va + offset;
|
||||
uint64 pa = yield arm64.translate_address (page_va, cancellable);
|
||||
size_t chunk = size_t.min (page_size - (size_t) (page_va & (page_size - 1)), data.length - offset);
|
||||
yield arm64.write_physical (pa, data[offset : offset + chunk], cancellable);
|
||||
offset += chunk;
|
||||
}
|
||||
|
||||
private Machine machine;
|
||||
return new Variant.boolean (true);
|
||||
}
|
||||
|
||||
public MemoryProtectHandler (Machine machine) {
|
||||
this.machine = machine;
|
||||
}
|
||||
private async void send_reply (uint16 request_id, Variant payload) throws GLib.Error {
|
||||
Bytes frame = frame_message (Command.REPLY, request_id, payload);
|
||||
yield output.write_all_async (frame.get_data (), Priority.DEFAULT, io_cancellable, null);
|
||||
}
|
||||
|
||||
public async uint64 handle_invocation (uint64[] args, CallFrame frame, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var address = args[0];
|
||||
var size = (size_t) args[1];
|
||||
var prot = (Gum.PageProtection) args[2];
|
||||
try {
|
||||
yield machine.protect_pages (address, size, prot, cancellable);
|
||||
return 1;
|
||||
} catch (GLib.Error e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
private Bytes frame_message (Command command, uint16 request_id, Variant payload) {
|
||||
var message = new Variant ("(yqv)", (uint8) command, request_id, payload);
|
||||
if (byte_order != ByteOrder.HOST)
|
||||
message = message.byteswap ();
|
||||
var message_bytes = message.get_data_as_bytes ();
|
||||
return new BufferBuilder (byte_order, pointer_size)
|
||||
.append_uint32 ((uint32) message_bytes.get_size ())
|
||||
.append_bytes (message_bytes)
|
||||
.build ();
|
||||
}
|
||||
|
||||
private enum Command {
|
||||
@@ -418,6 +546,9 @@ namespace Frida.Barebone {
|
||||
LOAD_SCRIPT = 2,
|
||||
DESTROY_SCRIPT = 3,
|
||||
POST_SCRIPT_MESSAGE = 4,
|
||||
REMAP_WRITABLE_PAGES = 5,
|
||||
MEMORY_PROTECT = 6,
|
||||
PATCH_CODE = 7,
|
||||
REPLY = 128,
|
||||
SCRIPT_MESSAGE = 129
|
||||
}
|
||||
|
||||
@@ -4,6 +4,22 @@ version = "1.0.0"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
name = "frida_barebone_agent"
|
||||
path = "src/lib.rs"
|
||||
crate-type = ["rlib", "staticlib"]
|
||||
|
||||
# The XNU flavour is a freestanding executable the host injects and calls into.
|
||||
# The Linux flavour is the staticlib above, linked into a .ko by linux/Kbuild.
|
||||
[[bin]]
|
||||
name = "frida-barebone-agent"
|
||||
path = "src/main.rs"
|
||||
required-features = ["xnu"]
|
||||
|
||||
[features]
|
||||
xnu = []
|
||||
linux = []
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "z"
|
||||
|
||||
@@ -21,10 +21,29 @@
|
||||
export AR_aarch64_unknown_none=aarch64-none-elf-ar
|
||||
export RANLIB_aarch64_unknown_none=aarch64-none-elf-ranlib
|
||||
|
||||
cargo build --release --features xnu
|
||||
|
||||
## Targets
|
||||
|
||||
The agent supports two kernels. One of them has to be picked; neither is a
|
||||
default, since neither follows from the host you build on:
|
||||
|
||||
- `xnu` — a freestanding ELF executable that the Barebone backend
|
||||
injects into a running kernel from the outside, over a remote stub.
|
||||
- `linux` — a static library that becomes a kernel module, loaded from the
|
||||
inside. See `linux/README.md`.
|
||||
|
||||
Everything above the `kernel` module is shared; each backend supplies the same
|
||||
set of primitives (logging, allocation, threads, waiting, time, symbols) plus its
|
||||
half of Gum's platform backend (`gum_xnu.rs` / `gum_linux.rs`).
|
||||
|
||||
## Development loop
|
||||
|
||||
export FRIDA_BAREBONE_CONFIG=$PWD/etc/xnu.json
|
||||
cargo build --release && make -C ~/src/frida-python && killall -9 qemu-system-aarch64 && sleep 2 && frida -D barebone -p 0
|
||||
cargo build --release --features xnu \
|
||||
&& make -C ~/src/frida-python \
|
||||
&& killall -9 qemu-system-aarch64 && sleep 2 \
|
||||
&& frida -D barebone -p 0
|
||||
|
||||
## Speeding up loop
|
||||
|
||||
@@ -32,6 +51,7 @@
|
||||
-- \
|
||||
-Dfrida-core:compat=disabled \
|
||||
-Dfrida-core:local_backend=disabled \
|
||||
-Dfrida-core:simmy_backend=disabled \
|
||||
-Dfrida-core:fruity_backend=disabled \
|
||||
-Dfrida-core:droidy_backend=disabled \
|
||||
-Dfrida-core:socket_backend=disabled \
|
||||
|
||||
@@ -26,6 +26,14 @@ SECTIONS {
|
||||
__ZN9IOServiceC2Ev = .; . += 8;
|
||||
__ZN8OSSymbol17withCStringNoCopyEPKc = .; . += 8;
|
||||
__ZN6OSData9withBytesEPKvj = .; . += 8;
|
||||
_kalloc_data = .; . += 8;
|
||||
_kfree_data = .; . += 8;
|
||||
_wakeup = .; . += 8;
|
||||
_sock_socket = .; . += 8;
|
||||
_sock_connect = .; . += 8;
|
||||
_sock_send = .; . += 8;
|
||||
_sock_receive = .; . += 8;
|
||||
_sock_close = .; . += 8;
|
||||
}
|
||||
.data.rel.ro : {
|
||||
*(.data.rel.ro*)
|
||||
|
||||
+33
-19
@@ -11,9 +11,13 @@ fn main() {
|
||||
let cc_str = env::var("CC_aarch64_unknown_none")
|
||||
.or_else(|_| env::var("CC"))
|
||||
.unwrap_or_else(|_| "cc".to_string());
|
||||
let cc = Path::new(&cc_str);
|
||||
let cc_include_paths = detect_gcc_include_paths(cc);
|
||||
let cc_library_paths = detect_gcc_library_paths(cc);
|
||||
// Same convention as cc-rs: the variable may carry flags, and a bare-metal
|
||||
// compiler needs them to find its libc at all.
|
||||
let mut cc_argv = cc_str.split_whitespace();
|
||||
let cc = Path::new(cc_argv.next().unwrap());
|
||||
let cc_args: Vec<&str> = cc_argv.collect();
|
||||
let cc_include_paths = detect_gcc_include_paths(cc, &cc_args);
|
||||
let cc_library_paths = detect_gcc_library_paths(cc, &cc_args);
|
||||
|
||||
let cc_clang_args: Vec<String> = cc_include_paths
|
||||
.iter()
|
||||
@@ -36,25 +40,35 @@ fn main() {
|
||||
.write_to_file(out_dir.join("bindings.rs"))
|
||||
.expect("Couldn't write bindings");
|
||||
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
devkit_dir.to_str().unwrap()
|
||||
);
|
||||
println!("cargo:rustc-link-lib=static=frida-gumjs");
|
||||
for path in cc_library_paths {
|
||||
println!("cargo:rustc-link-search=native={}", path.to_string_lossy());
|
||||
// The Linux flavour is a staticlib: kbuild owns the final link, so it — not
|
||||
// us — decides where the devkit and libc archives come from.
|
||||
if env::var("CARGO_FEATURE_XNU").is_ok() {
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
devkit_dir.to_str().unwrap()
|
||||
);
|
||||
println!("cargo:rustc-link-lib=static=frida-gumjs");
|
||||
for path in cc_library_paths {
|
||||
println!("cargo:rustc-link-search=native={}", path.to_string_lossy());
|
||||
}
|
||||
println!("cargo:rustc-link-lib=static=c");
|
||||
println!("cargo:rustc-link-lib=static=m");
|
||||
println!("cargo:rustc-link-arg=--export-dynamic");
|
||||
println!("cargo:rustc-link-arg=--emit-relocs");
|
||||
println!("cargo:rustc-link-arg=--script=agent.lds");
|
||||
println!("cargo:rustc-link-arg=--gc-sections");
|
||||
// All of the agent lives in the library crate, so nothing in the binary
|
||||
// references the entrypoint the host calls; keep the linker from
|
||||
// dropping it along with the rest of the unreferenced archive members.
|
||||
println!("cargo:rustc-link-arg=--undefined=_start");
|
||||
}
|
||||
println!("cargo:rustc-link-lib=static=c");
|
||||
println!("cargo:rustc-link-lib=static=m");
|
||||
println!("cargo:rustc-link-arg=--export-dynamic");
|
||||
println!("cargo:rustc-link-arg=--emit-relocs");
|
||||
println!("cargo:rustc-link-arg=--script=agent.lds");
|
||||
println!("cargo:rustc-link-arg=--gc-sections");
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
}
|
||||
|
||||
pub fn detect_gcc_include_paths(gcc: &Path) -> Vec<PathBuf> {
|
||||
pub fn detect_gcc_include_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
|
||||
let out = Command::new(gcc)
|
||||
.args(args)
|
||||
.args(["-xc", "-E", "-v", "-"])
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
@@ -94,8 +108,8 @@ pub fn detect_gcc_include_paths(gcc: &Path) -> Vec<PathBuf> {
|
||||
paths
|
||||
}
|
||||
|
||||
pub fn detect_gcc_library_paths(gcc: &Path) -> Vec<PathBuf> {
|
||||
let out = Command::new(gcc).arg("-print-search-dirs").output()
|
||||
pub fn detect_gcc_library_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
|
||||
let out = Command::new(gcc).args(args).arg("-print-search-dirs").output()
|
||||
.expect("Failed to execute GCC to detect library paths");
|
||||
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"agent": {
|
||||
"transport": {
|
||||
"type": "device",
|
||||
"path": "/dev/frida"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"connection": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 52494,
|
||||
"flavor": "vz",
|
||||
"pid": 49248
|
||||
},
|
||||
"allocator": {
|
||||
"mode": "target-functions",
|
||||
"alloc_function": "0xfffffe0007ab6cdc",
|
||||
"free_function": "0xfffffe0007ab74a8",
|
||||
"alloc_flags": 512
|
||||
},
|
||||
"agent": {
|
||||
"path": "/Users/oleavr/src/frida-core/src/barebone/agent/target/aarch64-unknown-none/release/frida-barebone-agent",
|
||||
"transport": {
|
||||
"type": "vsock",
|
||||
"socket_path": "/tmp/claude-501/vphone-frida-21528.sock",
|
||||
"port": 1339
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"file": "/Users/oleavr/src/vphone-cli/vm/iPhone17,3_26.1_23B85_Restore/kernelcache.research.vresearch101",
|
||||
"symbols": {
|
||||
"panic": "0x12a2814",
|
||||
"IOLog": "0xac44cc",
|
||||
"thread_block": "0xad3dac",
|
||||
"assert_wait": "0xacf1f8",
|
||||
"assert_wait_timeout": "0xacf3a0",
|
||||
"thread_wakeup": "0xad24e0",
|
||||
"kalloc_data": "0xab2cdc",
|
||||
"kfree_data": "0xab34a8",
|
||||
"sock_socket": "0x1006c88",
|
||||
"sock_connect": "0x1005714",
|
||||
"sock_send": "0x1006708",
|
||||
"sock_receive": "0x100669c",
|
||||
"sock_close": "0x1006de8",
|
||||
"mach_absolute_time": "0xc0548c",
|
||||
"absolutetime_to_nanoseconds": "0xc05a34",
|
||||
"clock_get_calendar_microtime": "0xa996c0",
|
||||
"kernel_thread_start": "0xafb660",
|
||||
"sb_evaluate_internal": "0x214dc00",
|
||||
"hook_cred_label_update_execve": "0x2156004",
|
||||
"hook_vnode_check_create": "0x213cf48",
|
||||
"hook_policy_syscall": "0x21443bc",
|
||||
"gLoadedKextSummaries": "0x24ec160",
|
||||
"proc_pid": "0xf65e5c"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
obj-m += frida-agent.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.
|
||||
frida-agent-y := frida-kmod.o frida-agent-prelinked.o
|
||||
|
||||
targets += frida-agent-prelinked.o
|
||||
|
||||
quiet_cmd_frida_agent_prelinked = COPY $@
|
||||
cmd_frida_agent_prelinked = cp $< $@
|
||||
|
||||
$(obj)/frida-agent-prelinked.o: $(src)/frida-agent-prelinked.o.bin FORCE
|
||||
$(call if_changed,frida_agent_prelinked)
|
||||
|
||||
ccflags-y += -Wno-declaration-after-statement
|
||||
@@ -0,0 +1,108 @@
|
||||
# Builds frida-agent.ko in two steps, because kbuild insists on owning the final
|
||||
# link:
|
||||
#
|
||||
# 1. Collapse the Rust staticlib plus everything it needs — the GumJS devkit and
|
||||
# the C library — into one relocatable object, and localize every symbol except
|
||||
# the entry points frida-kmod.c calls. Localizing matters: Gum and the C library
|
||||
# define names like memcpy and malloc that must not leak into the kernel's
|
||||
# namespace.
|
||||
# 2. Hand that object to kbuild as a shipped object, so vermagic, modinfo and
|
||||
# CRCs all come from the target kernel's own build system.
|
||||
#
|
||||
# See README.md for usage.
|
||||
|
||||
# KDIR is the kernel to build against. When it is a source tree that was configured
|
||||
# elsewhere — which is what Android's modules_prepare tarball gives — point KOUT at
|
||||
# the prepared tree.
|
||||
KDIR ?= /lib/modules/$(shell uname -r)/build
|
||||
ARCH ?= arm64
|
||||
RUST_TARGET ?= aarch64-unknown-none
|
||||
PROFILE ?= release
|
||||
|
||||
AGENT_DIR := $(CURDIR)/..
|
||||
AGENT_TARGET_DIR := $(AGENT_DIR)/target-linux
|
||||
AGENT_LIB := $(AGENT_TARGET_DIR)/$(RUST_TARGET)/$(PROFILE)/libfrida_barebone_agent.a
|
||||
|
||||
GUMJS_DEVKIT_DIR ?= $(error Set GUMJS_DEVKIT_DIR to the directory holding libfrida-gumjs.a)
|
||||
|
||||
# Any binutils that speak aarch64 ELF will do; nothing here is compiled with them.
|
||||
AGENT_LD ?= aarch64-none-elf-ld
|
||||
AGENT_AR ?= aarch64-none-elf-ar
|
||||
AGENT_NM ?= aarch64-none-elf-nm
|
||||
AGENT_OBJCOPY ?= aarch64-none-elf-objcopy
|
||||
|
||||
# arm64 kernels reserve x18 for the shadow call stack, so nothing we link in may use
|
||||
# it as a general-purpose register. The flag only reaches crates Cargo compiles, and
|
||||
# the toolchain ships core, alloc and compiler_builtins prebuilt without it, so those
|
||||
# three have to be rebuilt from source as well. That needs the rust-src component and
|
||||
# an unstable flag, hence RUSTC_BOOTSTRAP.
|
||||
AGENT_RUSTFLAGS ?= -Zfixed-x18
|
||||
AGENT_BUILD_STD ?= -Z build-std=core,alloc,compiler_builtins
|
||||
|
||||
# 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-arm64-softfloat SDK prefix)
|
||||
LIBM = $(FRIDA_SDK)/lib/libm.a
|
||||
LIBBUILTINS = $(FRIDA_SDK)/lib/libclang_rt.builtins.a
|
||||
|
||||
PRELINKED := frida-agent-prelinked.o.bin
|
||||
|
||||
# Rust's toolchain ships compiler_builtins compiled for the ordinary AAPCS, where a
|
||||
# double travels in d0: its __muldf3 opens with "fmov x14, d0". Everything else here
|
||||
# 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.
|
||||
AGENT_LIB_SOFTFLOAT := $(AGENT_TARGET_DIR)/libfrida-barebone-agent-softfloat.a
|
||||
|
||||
# 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
|
||||
KEEP_GLOBAL := $(foreach s,$(ENTRY_POINTS),--keep-global-symbol=$(s))
|
||||
|
||||
all: modules
|
||||
|
||||
modules: $(PRELINKED)
|
||||
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) modules
|
||||
|
||||
$(PRELINKED): $(AGENT_LIB_SOFTFLOAT) $(LIBC)
|
||||
$(AGENT_LD) -r -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) $@
|
||||
$(AGENT_AR) d $@ $$($(AGENT_NM) --print-armap $@ | sed -n 's/^__muldf3 in //p')
|
||||
$(AGENT_AR) s $@
|
||||
|
||||
$(LIBC): $(AGENT_LIB_SOFTFLOAT)
|
||||
cp $(FRIDA_SDK)/lib/libc.a $@
|
||||
$(AGENT_NM) --defined-only $(AGENT_LIB_SOFTFLOAT) \
|
||||
| awk 'NF == 3 && $$2 ~ /^[TDBR]$$/ { print $$3 }' | sort -u > $@.agent-syms
|
||||
$(AGENT_NM) --print-armap $@ | sed -n 's/^\([^ ]*\) in \(.*\)$$/\1 \2/p' | sort -u > $@.members
|
||||
$(AGENT_AR) d $@ $$(join $@.agent-syms $@.members | awk '{ print $$2 }' | sort -u)
|
||||
$(AGENT_AR) s $@
|
||||
rm -f $@.agent-syms $@.members
|
||||
|
||||
agent:
|
||||
cd $(AGENT_DIR) && RUSTC_BOOTSTRAP=1 RUSTFLAGS="$(AGENT_RUSTFLAGS)" cargo build --$(PROFILE) \
|
||||
--target $(RUST_TARGET) --features linux \
|
||||
--target-dir $(AGENT_TARGET_DIR) $(AGENT_BUILD_STD)
|
||||
|
||||
clean:
|
||||
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) clean
|
||||
rm -f $(PRELINKED) $(PRELINKED).tmp $(AGENT_LIB_SOFTFLOAT) $(LIBC)
|
||||
|
||||
.PHONY: all modules agent clean
|
||||
@@ -0,0 +1,154 @@
|
||||
# Linux kernel module
|
||||
|
||||
The same agent that the Barebone backend injects into XNU from the outside, but
|
||||
loaded from the inside as an ordinary kernel module.
|
||||
|
||||
## How it differs from the XNU flavour
|
||||
|
||||
| | XNU | Linux |
|
||||
|-------------------|--------------------------------------------------|-------------------------------------------|
|
||||
| Delivery | remote stub injects a freestanding ELF | `insmod frida-agent.ko` |
|
||||
| Kernel primitives | addresses patched into `.kernel_addrs` by the host | direct calls into `frida-kmod.c` |
|
||||
| Bootstrap config | GVariant blob written into guest memory | module parameters |
|
||||
| Transport | virtio hostlink or vsock | `/dev/frida` character device |
|
||||
| Page permissions | host rewrites descriptors over the hostlink | `set_memory_*()` |
|
||||
| Writable alias | shadow copy, committed via physical-memory bridge | `vmap()` of the same pages |
|
||||
| Symbols | table supplied by the host | kallsyms |
|
||||
|
||||
Because the Linux agent performs its own memory work, it never issues the
|
||||
`RemapWritablePages`, `MemoryProtect` or `PatchCode` host RPCs. The wire protocol
|
||||
is otherwise identical, so the host side sees the same `(yqv)` frames.
|
||||
|
||||
## Building
|
||||
|
||||
export GUMJS_DEVKIT_DIR=/path/to/frida-gum/build/bindings/gumjs/devkit
|
||||
make -C linux KDIR=/path/to/kernel/build
|
||||
|
||||
`make` builds the Rust staticlib itself, then prelinks it and hands the result to
|
||||
kbuild. `KDIR` must be the configured build tree of the kernel the module is for:
|
||||
same source commit, same config. Under `CONFIG_MODVERSIONS` anything else is rejected
|
||||
at load time, and the `module_layout` CRC covers `struct module` itself, so even
|
||||
config options that merely add fields to it — `CONFIG_DEBUG_INFO_BTF_MODULES`, for
|
||||
one — have to match.
|
||||
|
||||
The prelink half runs anywhere; the kbuild half needs a Linux host. Cross-building
|
||||
from macOS means running that step in a container holding the kernel tree.
|
||||
|
||||
### Getting a KDIR for an Android device
|
||||
|
||||
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 — for
|
||||
`6.1.145-android14-11-gc1de4747ac59-ab14219743` that is commit `c1de4747ac59` and
|
||||
build `14219743` — and three pieces keyed to them are published:
|
||||
|
||||
- `modules_prepare_outdir.tar.gz` and `kernel_aarch64_Module.symvers` from
|
||||
`ci.android.com`, under that build's `kernel_aarch64` target. The first is the
|
||||
configured build tree with `scripts/` already compiled for a Linux x86-64 host; the
|
||||
second carries the CRCs `CONFIG_MODVERSIONS` checks.
|
||||
- `kernel/common` at that commit, which a shallow single-commit fetch gets in a few
|
||||
hundred megabytes. Only its makefiles are wanted: the top-level `Makefile` in the
|
||||
prepared tree is a stub that includes the source's, so point it at wherever the
|
||||
fetch landed and build with `make -C <source> O=<outdir> M=<here>`.
|
||||
|
||||
A different GKI build of the same branch does not substitute for these: vermagic is
|
||||
compared verbatim, and it names the exact build.
|
||||
|
||||
## Loading
|
||||
|
||||
insmod frida-agent.ko
|
||||
|
||||
The module's init path returns as soon as the worker thread is running — watch
|
||||
`dmesg` for `frida: listening on /dev/frida`. `rmmod frida-agent` unwinds in the
|
||||
reverse order and does not return until the worker is off the module's text.
|
||||
|
||||
## Talking to it
|
||||
|
||||
The agent exposes `/dev/frida` and waits: one client at a time, `read`/`write` of the
|
||||
same length-prefixed frames the other transports carry, with `poll` for readiness.
|
||||
It never dials out, so there is no network namespace to get right and no ordering
|
||||
requirement between loading the module and attaching a client.
|
||||
|
||||
On Android the node is created by devtmpfs without a policy-specific label, so an
|
||||
SELinux-enforcing system needs the client's domain granted access to it before it can
|
||||
open the device. A socket-based transport was the other option considered and is a
|
||||
worse fit: `AF_VSOCK` only reaches a peer when a hypervisor is on the other side, and
|
||||
having the kernel connect out to loopback needs something already listening before
|
||||
the module loads.
|
||||
|
||||
### Reaching it from a host
|
||||
|
||||
Serve the Barebone device from a frida-server running on the target, so the `open()`
|
||||
happens next to the device node and the host talks to an ordinary frida-server:
|
||||
|
||||
FRIDA_BAREBONE_CONFIG=/data/local/tmp/linux-kmod.json \
|
||||
frida-server --device barebone -l 127.0.0.1:27042
|
||||
|
||||
`etc/linux-kmod.json` is that config. From the host, with `adb forward tcp:27042
|
||||
tcp:27042` in place:
|
||||
|
||||
frida -H 127.0.0.1:27042 -p 0
|
||||
|
||||
## What the target kernel has to provide
|
||||
|
||||
- **arm64.** The register-level pieces (`tcr_el1` for the page size, cache
|
||||
maintenance) are aarch64-only, matching the XNU flavour.
|
||||
- **`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.
|
||||
- **`CONFIG_VSOCKETS`**, plus a vsock transport for the guest.
|
||||
- **Unsigned modules.** `CONFIG_MODULE_SIG_FORCE` must be off. Android's
|
||||
`CONFIG_MODULE_SIG_PROTECT` is fine: it only demands signatures for modules on
|
||||
the GKI list.
|
||||
|
||||
## Kernel ABI constraints
|
||||
|
||||
A hardened arm64 kernel imposes an ABI on anything linked into it, and both of the
|
||||
constraints below are why this flavour is built against a soft-float SDK
|
||||
(`none-arm64-softfloat`) rather than the prebuilt `none-arm64` one.
|
||||
|
||||
- **FP/SIMD.** JavaScript numbers are doubles, so a hardfloat build would use FP
|
||||
for as long as the runtime lives. Kernels from 6.9 preserve kernel-mode FP/SIMD
|
||||
across sleeping, but before 6.9 there is no correct way to hold it:
|
||||
`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 `-mabi=aapcs-soft -mgeneral-regs-only` removes the
|
||||
question rather than answering it.
|
||||
- **Shadow call stack.** 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
|
||||
`none-arm64-softfloat` 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.
|
||||
|
||||
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.
|
||||
|
||||
## 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, where
|
||||
that stack is 16 KiB. 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.
|
||||
@@ -0,0 +1,823 @@
|
||||
/*
|
||||
* Glue between the Rust agent and the Linux kernel it is loaded into.
|
||||
*
|
||||
* Everything the agent needs from the kernel lives here rather than in Rust,
|
||||
* because all of it is version- and config-dependent: struct layouts, inline helpers
|
||||
* that are really macros, the character-device and uaccess APIs, and which of
|
||||
* kallsyms' entry points happen to be exported this release. Compiling against the
|
||||
* target kernel's own headers is what keeps that knowledge correct.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/kasan.h>
|
||||
#include <linux/kfifo.h>
|
||||
#include <linux/kprobes.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/miscdevice.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/poll.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/set_memory.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/timekeeping.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
#ifdef CONFIG_ARM64
|
||||
# include <asm/cacheflush.h>
|
||||
#endif
|
||||
|
||||
/* Mirrors GumPageProtection. */
|
||||
#define FRIDA_PAGE_WRITE (1 << 1)
|
||||
#define FRIDA_PAGE_EXECUTE (1 << 2)
|
||||
|
||||
typedef int (* FridaFoundSymbolFunc) (const char * name, u64 address, void * user_data);
|
||||
typedef int (* FridaFoundModuleFunc) (const char * name, const char * version, u64 base, u64 size,
|
||||
void * user_data);
|
||||
typedef void (* FridaThreadEntry) (void * parameter, int wait_result);
|
||||
|
||||
struct frida_thread_ctx
|
||||
{
|
||||
FridaThreadEntry entry;
|
||||
void * parameter;
|
||||
};
|
||||
|
||||
struct frida_writable_alias
|
||||
{
|
||||
struct list_head node;
|
||||
void * mapping;
|
||||
unsigned long first_page;
|
||||
unsigned int n_pages;
|
||||
};
|
||||
|
||||
struct frida_symbol_visit_ctx
|
||||
{
|
||||
FridaFoundSymbolFunc func;
|
||||
void * user_data;
|
||||
};
|
||||
|
||||
int frida_agent_start (void);
|
||||
void frida_agent_stop (void);
|
||||
|
||||
u64 frida_kmod_find_symbol (const char * name);
|
||||
void frida_kmod_wake (void);
|
||||
void frida_kmod_yield (void);
|
||||
|
||||
static void frida_resolve_kallsyms (void);
|
||||
static void frida_resolve_kernel_range (void);
|
||||
static void * frida_resolve_unexported (const char * name);
|
||||
static int frida_thread_trampoline (void * data);
|
||||
static int frida_dev_open (struct inode * inode, struct file * file);
|
||||
static int frida_dev_release (struct inode * inode, struct file * file);
|
||||
static ssize_t frida_dev_read (struct file * file, char __user * buffer, size_t size,
|
||||
loff_t * offset);
|
||||
static ssize_t frida_dev_write (struct file * file, const char __user * buffer, size_t size,
|
||||
loff_t * offset);
|
||||
static __poll_t frida_dev_poll (struct file * file, struct poll_table_struct * wait);
|
||||
static struct page * frida_page_for_virtual (unsigned long address);
|
||||
static bool frida_is_vmalloc_or_module_addr (const void * x);
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION (6, 4, 0)
|
||||
static int frida_on_each_symbol (void * data, const char * name, unsigned long address);
|
||||
#else
|
||||
static int frida_on_each_symbol (void * data, const char * name, struct module * mod,
|
||||
unsigned long address);
|
||||
#endif
|
||||
static bool frida_should_wake (unsigned int seq);
|
||||
static void frida_strip_module_suffix (char * rendered);
|
||||
|
||||
static DECLARE_WAIT_QUEUE_HEAD (frida_wq);
|
||||
static atomic_t frida_wake_seq = ATOMIC_INIT (0);
|
||||
|
||||
static u64 frida_kernel_base;
|
||||
static u64 frida_kernel_size;
|
||||
|
||||
static typeof (&kallsyms_lookup_name) frida_kallsyms_lookup_name_impl;
|
||||
static typeof (&kallsyms_on_each_symbol) frida_kallsyms_on_each_symbol_impl;
|
||||
static struct list_head * frida_modules;
|
||||
static struct mutex * frida_module_mutex;
|
||||
static typeof (&set_memory_ro) frida_set_memory_ro_impl;
|
||||
static typeof (&set_memory_rw) frida_set_memory_rw_impl;
|
||||
static typeof (&set_memory_x) frida_set_memory_x_impl;
|
||||
static typeof (&set_memory_nx) frida_set_memory_nx_impl;
|
||||
|
||||
static LIST_HEAD (frida_writable_aliases);
|
||||
static DEFINE_MUTEX (frida_writable_aliases_mutex);
|
||||
|
||||
#define FRIDA_LINK_CAPACITY (1024 * 1024)
|
||||
|
||||
#define FRIDA_WAIT_FOREVER_SLICE_US (1000 * 1000)
|
||||
|
||||
static DECLARE_KFIFO_PTR (frida_to_client, u8);
|
||||
static DECLARE_KFIFO_PTR (frida_from_client, u8);
|
||||
static DEFINE_MUTEX (frida_link_mutex);
|
||||
static DECLARE_WAIT_QUEUE_HEAD (frida_readable_wq);
|
||||
static atomic_t frida_client_count = ATOMIC_INIT (0);
|
||||
static bool frida_link_registered;
|
||||
|
||||
static const struct file_operations frida_dev_fops =
|
||||
{
|
||||
.owner = THIS_MODULE,
|
||||
.open = frida_dev_open,
|
||||
.release = frida_dev_release,
|
||||
.read = frida_dev_read,
|
||||
.write = frida_dev_write,
|
||||
.poll = frida_dev_poll,
|
||||
};
|
||||
|
||||
static struct miscdevice frida_dev =
|
||||
{
|
||||
.minor = MISC_DYNAMIC_MINOR,
|
||||
.name = "frida",
|
||||
.fops = &frida_dev_fops,
|
||||
.mode = 0600,
|
||||
};
|
||||
|
||||
static int __init
|
||||
frida_kmod_init (void)
|
||||
{
|
||||
frida_resolve_kallsyms ();
|
||||
frida_resolve_kernel_range ();
|
||||
|
||||
return frida_agent_start ();
|
||||
}
|
||||
|
||||
static void __exit
|
||||
frida_kmod_exit (void)
|
||||
{
|
||||
frida_agent_stop ();
|
||||
}
|
||||
|
||||
module_init (frida_kmod_init);
|
||||
module_exit (frida_kmod_exit);
|
||||
|
||||
MODULE_LICENSE ("GPL");
|
||||
MODULE_DESCRIPTION ("Frida instrumentation agent");
|
||||
MODULE_VERSION ("1.0.0");
|
||||
|
||||
static void
|
||||
frida_resolve_kallsyms (void)
|
||||
{
|
||||
frida_kallsyms_lookup_name_impl = frida_resolve_unexported ("kallsyms_lookup_name");
|
||||
frida_kallsyms_on_each_symbol_impl = frida_resolve_unexported ("kallsyms_on_each_symbol");
|
||||
|
||||
if (frida_kallsyms_lookup_name_impl == NULL)
|
||||
{
|
||||
printk (KERN_WARNING "frida: kallsyms_lookup_name unavailable\n");
|
||||
return;
|
||||
}
|
||||
|
||||
frida_modules = (struct list_head *) frida_kallsyms_lookup_name_impl ("modules");
|
||||
frida_module_mutex = (struct mutex *) frida_kallsyms_lookup_name_impl ("module_mutex");
|
||||
|
||||
/*
|
||||
* GKI trims its export table to the KMI symbol list, which leaves set_memory_*()
|
||||
* callable but unexported. Being absent from Module.symvers only stops the module
|
||||
* loader from resolving them, so go through kallsyms like everything else here.
|
||||
*/
|
||||
frida_set_memory_ro_impl = (void *) frida_kallsyms_lookup_name_impl ("set_memory_ro");
|
||||
frida_set_memory_rw_impl = (void *) frida_kallsyms_lookup_name_impl ("set_memory_rw");
|
||||
frida_set_memory_x_impl = (void *) frida_kallsyms_lookup_name_impl ("set_memory_x");
|
||||
frida_set_memory_nx_impl = (void *) frida_kallsyms_lookup_name_impl ("set_memory_nx");
|
||||
}
|
||||
|
||||
static void
|
||||
frida_resolve_kernel_range (void)
|
||||
{
|
||||
u64 start, end;
|
||||
|
||||
start = frida_kmod_find_symbol ("_stext");
|
||||
end = frida_kmod_find_symbol ("_end");
|
||||
if (start == 0 || end == 0)
|
||||
return;
|
||||
|
||||
frida_kernel_base = start;
|
||||
frida_kernel_size = end - start;
|
||||
}
|
||||
|
||||
/*
|
||||
* kallsyms_lookup_name() and kallsyms_on_each_symbol() stopped being exported in
|
||||
* 5.7. A kprobe registered by symbol name resolves an address without needing an
|
||||
* export, which is the standard way back in.
|
||||
*/
|
||||
static void *
|
||||
frida_resolve_unexported (const char * name)
|
||||
{
|
||||
struct kprobe kp = { .symbol_name = name };
|
||||
void * address;
|
||||
|
||||
if (register_kprobe (&kp) < 0)
|
||||
return NULL;
|
||||
address = kp.addr;
|
||||
unregister_kprobe (&kp);
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
void
|
||||
frida_kmod_log (const char * message)
|
||||
{
|
||||
printk (KERN_INFO "%s", message);
|
||||
}
|
||||
|
||||
void
|
||||
frida_kmod_panic (const char * message)
|
||||
{
|
||||
panic ("%s", message);
|
||||
}
|
||||
|
||||
int
|
||||
frida_kmod_spawn_thread (FridaThreadEntry entry,
|
||||
void * parameter)
|
||||
{
|
||||
struct frida_thread_ctx * ctx;
|
||||
struct task_struct * task;
|
||||
|
||||
ctx = kmalloc (sizeof (struct frida_thread_ctx), GFP_KERNEL);
|
||||
ctx->entry = entry;
|
||||
ctx->parameter = parameter;
|
||||
|
||||
task = kthread_run (frida_thread_trampoline, ctx, "frida-agent");
|
||||
if (IS_ERR (task))
|
||||
{
|
||||
kfree (ctx);
|
||||
return PTR_ERR (task);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __nocfi
|
||||
frida_thread_trampoline (void * data)
|
||||
{
|
||||
struct frida_thread_ctx ctx = *((struct frida_thread_ctx *) data);
|
||||
|
||||
kfree (data);
|
||||
|
||||
ctx.entry (ctx.parameter, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *
|
||||
frida_kmod_alloc (size_t size)
|
||||
{
|
||||
return kvmalloc (size, GFP_KERNEL);
|
||||
}
|
||||
|
||||
void
|
||||
frida_kmod_free (void * ptr,
|
||||
size_t size)
|
||||
{
|
||||
kvfree (ptr);
|
||||
}
|
||||
|
||||
/*
|
||||
* Code slabs have to be page-granular and live somewhere set_memory_x() accepts,
|
||||
* which rules out the slab allocator and its sub-page objects.
|
||||
*/
|
||||
void *
|
||||
frida_kmod_alloc_code (size_t size)
|
||||
{
|
||||
return __vmalloc (PAGE_ALIGN (size), GFP_KERNEL | __GFP_ZERO);
|
||||
}
|
||||
|
||||
void
|
||||
frida_kmod_free_code (void * ptr,
|
||||
size_t size)
|
||||
{
|
||||
vfree (ptr);
|
||||
}
|
||||
|
||||
void
|
||||
frida_kmod_own_range (u64 * base,
|
||||
u64 * size)
|
||||
{
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION (6, 4, 0)
|
||||
*base = (u64) (uintptr_t) THIS_MODULE->mem[MOD_TEXT].base;
|
||||
*size = THIS_MODULE->mem[MOD_TEXT].size;
|
||||
#else
|
||||
*base = (u64) (uintptr_t) THIS_MODULE->core_layout.base;
|
||||
*size = THIS_MODULE->core_layout.size;
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* The transport is a character device rather than a socket the agent dials out on:
|
||||
* userspace decides when to attach, needs no network namespace to reach us, and the
|
||||
* relay on the other side is whatever the operator points at /dev/frida. The 4-byte
|
||||
* length prefix framing is done by the agent, so this is a plain byte stream.
|
||||
*/
|
||||
int
|
||||
frida_kmod_link_open (void)
|
||||
{
|
||||
int res;
|
||||
|
||||
res = kfifo_alloc (&frida_to_client, FRIDA_LINK_CAPACITY, GFP_KERNEL);
|
||||
if (res != 0)
|
||||
return res;
|
||||
|
||||
res = kfifo_alloc (&frida_from_client, FRIDA_LINK_CAPACITY, GFP_KERNEL);
|
||||
if (res != 0)
|
||||
goto free_to_client;
|
||||
|
||||
res = misc_register (&frida_dev);
|
||||
if (res != 0)
|
||||
goto free_from_client;
|
||||
frida_link_registered = true;
|
||||
|
||||
printk (KERN_INFO "frida: listening on /dev/%s\n", frida_dev.name);
|
||||
|
||||
return 0;
|
||||
|
||||
free_from_client:
|
||||
kfifo_free (&frida_from_client);
|
||||
free_to_client:
|
||||
kfifo_free (&frida_to_client);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
int
|
||||
frida_kmod_link_send (const void * data,
|
||||
size_t size)
|
||||
{
|
||||
unsigned int written;
|
||||
|
||||
/* Nothing is listening yet, or the client cannot keep up: the agent's replies are
|
||||
* only meaningful to a client that is still there, so drop them rather than stall
|
||||
* the runtime. */
|
||||
if (atomic_read (&frida_client_count) == 0)
|
||||
return -ENOTCONN;
|
||||
|
||||
mutex_lock (&frida_link_mutex);
|
||||
written = kfifo_in (&frida_to_client, data, size);
|
||||
mutex_unlock (&frida_link_mutex);
|
||||
|
||||
if (written != size)
|
||||
return -ENOSPC;
|
||||
|
||||
wake_up_interruptible (&frida_readable_wq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
long
|
||||
frida_kmod_link_recv (void * data,
|
||||
size_t size)
|
||||
{
|
||||
unsigned int n;
|
||||
|
||||
mutex_lock (&frida_link_mutex);
|
||||
n = kfifo_out (&frida_from_client, data, size);
|
||||
mutex_unlock (&frida_link_mutex);
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
void
|
||||
frida_kmod_link_close (void)
|
||||
{
|
||||
if (!frida_link_registered)
|
||||
return;
|
||||
frida_link_registered = false;
|
||||
|
||||
misc_deregister (&frida_dev);
|
||||
|
||||
kfifo_free (&frida_from_client);
|
||||
kfifo_free (&frida_to_client);
|
||||
}
|
||||
|
||||
/* One client at a time: the protocol is a single conversation, and two readers
|
||||
* would each get half of every frame. */
|
||||
static int
|
||||
frida_dev_open (struct inode * inode,
|
||||
struct file * file)
|
||||
{
|
||||
if (atomic_cmpxchg (&frida_client_count, 0, 1) != 0)
|
||||
return -EBUSY;
|
||||
|
||||
return nonseekable_open (inode, file);
|
||||
}
|
||||
|
||||
static int
|
||||
frida_dev_release (struct inode * inode,
|
||||
struct file * file)
|
||||
{
|
||||
mutex_lock (&frida_link_mutex);
|
||||
kfifo_reset (&frida_to_client);
|
||||
kfifo_reset (&frida_from_client);
|
||||
mutex_unlock (&frida_link_mutex);
|
||||
|
||||
atomic_set (&frida_client_count, 0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
frida_dev_read (struct file * file,
|
||||
char __user * buffer,
|
||||
size_t size,
|
||||
loff_t * offset)
|
||||
{
|
||||
unsigned int copied;
|
||||
int res;
|
||||
|
||||
while (kfifo_is_empty (&frida_to_client))
|
||||
{
|
||||
if ((file->f_flags & O_NONBLOCK) != 0)
|
||||
return -EAGAIN;
|
||||
|
||||
if (wait_event_interruptible (frida_readable_wq, !kfifo_is_empty (&frida_to_client)) != 0)
|
||||
return -ERESTARTSYS;
|
||||
}
|
||||
|
||||
mutex_lock (&frida_link_mutex);
|
||||
res = kfifo_to_user (&frida_to_client, buffer, size, &copied);
|
||||
mutex_unlock (&frida_link_mutex);
|
||||
|
||||
return (res != 0) ? res : copied;
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
frida_dev_write (struct file * file,
|
||||
const char __user * buffer,
|
||||
size_t size,
|
||||
loff_t * offset)
|
||||
{
|
||||
unsigned int copied;
|
||||
int res;
|
||||
|
||||
mutex_lock (&frida_link_mutex);
|
||||
res = kfifo_from_user (&frida_from_client, buffer, size, &copied);
|
||||
mutex_unlock (&frida_link_mutex);
|
||||
|
||||
if (res != 0)
|
||||
return res;
|
||||
|
||||
frida_kmod_wake ();
|
||||
|
||||
return copied;
|
||||
}
|
||||
|
||||
static __poll_t
|
||||
frida_dev_poll (struct file * file,
|
||||
struct poll_table_struct * wait)
|
||||
{
|
||||
__poll_t events = EPOLLOUT | EPOLLWRNORM;
|
||||
|
||||
poll_wait (file, &frida_readable_wq, wait);
|
||||
|
||||
if (!kfifo_is_empty (&frida_to_client))
|
||||
events |= EPOLLIN | EPOLLRDNORM;
|
||||
|
||||
return events;
|
||||
}
|
||||
|
||||
/*
|
||||
* Two-phase wait: the caller reads the sequence number, checks its condition, and
|
||||
* only then commits to sleeping. A wakeup that lands in between bumps the sequence,
|
||||
* so the commit returns immediately instead of sleeping through it.
|
||||
*/
|
||||
unsigned int
|
||||
frida_kmod_wait_prepare (void)
|
||||
{
|
||||
return (unsigned int) atomic_read (&frida_wake_seq);
|
||||
}
|
||||
|
||||
void
|
||||
frida_kmod_wait_commit (unsigned int seq,
|
||||
s64 timeout_us)
|
||||
{
|
||||
s64 capped_timeout_us;
|
||||
long timeout;
|
||||
|
||||
/*
|
||||
* Every wait is bounded and uninterruptible. Interruptible would be wrong twice
|
||||
* over: a kernel thread with a signal pending gets it back immediately, turning
|
||||
* each sleep into a no-op and the caller's loop into a spin, and an unbounded
|
||||
* uninterruptible sleep would leave the thread unkillable. A caller asking to wait
|
||||
* forever gets a long slice instead — anything with something to say bumps the
|
||||
* sequence, so this costs latency to nobody.
|
||||
*/
|
||||
capped_timeout_us = (timeout_us < 0)
|
||||
? FRIDA_WAIT_FOREVER_SLICE_US
|
||||
: min_t (s64, timeout_us, FRIDA_WAIT_FOREVER_SLICE_US);
|
||||
timeout = max_t (long, (capped_timeout_us * HZ) / USEC_PER_SEC, 1);
|
||||
|
||||
wait_event_timeout (frida_wq, frida_should_wake (seq), timeout);
|
||||
}
|
||||
|
||||
/*
|
||||
* Queued input has to be level-triggered, not just the edge that delivered it. The
|
||||
* agent drains the fifo before arming its wait, so a write landing in between would
|
||||
* bump the sequence that the arming then reads as its baseline — and the agent would
|
||||
* sleep with a frame already waiting.
|
||||
*/
|
||||
static bool
|
||||
frida_should_wake (unsigned int seq)
|
||||
{
|
||||
if ((unsigned int) atomic_read (&frida_wake_seq) != seq)
|
||||
return true;
|
||||
|
||||
return !kfifo_is_empty (&frida_from_client);
|
||||
}
|
||||
|
||||
void
|
||||
frida_kmod_wake (void)
|
||||
{
|
||||
atomic_inc (&frida_wake_seq);
|
||||
wake_up_all (&frida_wq);
|
||||
}
|
||||
|
||||
/*
|
||||
* A kernel thread that loops without reaching the scheduler takes the machine down
|
||||
* via the hardware watchdog rather than merely running hot, which makes any such loop
|
||||
* undiagnosable. Every loop the agent runs calls this.
|
||||
*/
|
||||
void
|
||||
frida_kmod_yield (void)
|
||||
{
|
||||
cond_resched ();
|
||||
}
|
||||
|
||||
s64
|
||||
frida_kmod_monotonic_micros (void)
|
||||
{
|
||||
return ktime_to_us (ktime_get ());
|
||||
}
|
||||
|
||||
void
|
||||
frida_kmod_wall_clock_micros (unsigned int * secs,
|
||||
unsigned int * micros)
|
||||
{
|
||||
struct timespec64 ts;
|
||||
|
||||
ktime_get_real_ts64 (&ts);
|
||||
|
||||
*secs = (unsigned int) ts.tv_sec;
|
||||
*micros = (unsigned int) (ts.tv_nsec / 1000);
|
||||
}
|
||||
|
||||
/* Handed to JavaScript as a GumThreadId, so it has to fit in the 48 bits a
|
||||
* double represents exactly. */
|
||||
u64
|
||||
frida_kmod_current_thread_id (void)
|
||||
{
|
||||
return ((u64) (uintptr_t) current) & ((1ULL << 48) - 1);
|
||||
}
|
||||
|
||||
int
|
||||
frida_kmod_protect (u64 address,
|
||||
size_t size,
|
||||
unsigned int gum_prot)
|
||||
{
|
||||
unsigned long start = address & PAGE_MASK;
|
||||
unsigned long end = PAGE_ALIGN (address + size);
|
||||
int n_pages = (end - start) / PAGE_SIZE;
|
||||
|
||||
if (frida_set_memory_ro_impl == NULL)
|
||||
return 0;
|
||||
|
||||
if ((gum_prot & FRIDA_PAGE_WRITE) != 0)
|
||||
{
|
||||
if (frida_set_memory_nx_impl (start, n_pages) != 0)
|
||||
return 0;
|
||||
return frida_set_memory_rw_impl (start, n_pages) == 0;
|
||||
}
|
||||
|
||||
if (frida_set_memory_ro_impl (start, n_pages) != 0)
|
||||
return 0;
|
||||
|
||||
if ((gum_prot & FRIDA_PAGE_EXECUTE) != 0)
|
||||
return frida_set_memory_x_impl (start, n_pages) == 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* A second mapping of the same physical pages, writable regardless of what the
|
||||
* primary mapping allows. This is how the agent lands hooks in text that
|
||||
* STRICT_KERNEL_RWX has made read-only.
|
||||
*/
|
||||
void *
|
||||
frida_kmod_remap_writable (u64 first_page,
|
||||
unsigned int n_pages)
|
||||
{
|
||||
struct page ** pages;
|
||||
unsigned int i;
|
||||
void * mapping;
|
||||
struct frida_writable_alias * alias;
|
||||
|
||||
pages = kmalloc_array (n_pages, sizeof (struct page *), GFP_KERNEL);
|
||||
for (i = 0; i != n_pages; i++)
|
||||
pages[i] = frida_page_for_virtual (first_page + ((unsigned long) i * PAGE_SIZE));
|
||||
|
||||
mapping = vmap (pages, n_pages, VM_MAP, PAGE_KERNEL);
|
||||
|
||||
kfree (pages);
|
||||
|
||||
if (mapping == NULL)
|
||||
return NULL;
|
||||
|
||||
alias = kmalloc (sizeof (struct frida_writable_alias), GFP_KERNEL);
|
||||
alias->mapping = mapping;
|
||||
alias->first_page = first_page;
|
||||
alias->n_pages = n_pages;
|
||||
|
||||
mutex_lock (&frida_writable_aliases_mutex);
|
||||
list_add (&alias->node, &frida_writable_aliases);
|
||||
mutex_unlock (&frida_writable_aliases_mutex);
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
void
|
||||
frida_kmod_unmap_writable (void * mapping)
|
||||
{
|
||||
struct frida_writable_alias * alias = NULL, * candidate;
|
||||
unsigned long mapped_size;
|
||||
|
||||
mutex_lock (&frida_writable_aliases_mutex);
|
||||
list_for_each_entry (candidate, &frida_writable_aliases, node)
|
||||
{
|
||||
if (candidate->mapping == mapping)
|
||||
{
|
||||
alias = candidate;
|
||||
list_del (&alias->node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
mutex_unlock (&frida_writable_aliases_mutex);
|
||||
|
||||
mapped_size = (unsigned long) alias->n_pages * PAGE_SIZE;
|
||||
|
||||
vunmap (mapping);
|
||||
|
||||
/* flush_icache_range() inlines a reference to __icache_flags, which GKI does not
|
||||
* export; these two are what it would have called. */
|
||||
caches_clean_inval_pou (alias->first_page, alias->first_page + mapped_size);
|
||||
kick_all_cpus_sync ();
|
||||
|
||||
kfree (alias);
|
||||
}
|
||||
|
||||
static struct page *
|
||||
frida_page_for_virtual (unsigned long address)
|
||||
{
|
||||
if (frida_is_vmalloc_or_module_addr ((void *) address))
|
||||
return vmalloc_to_page ((void *) address);
|
||||
|
||||
if (virt_addr_valid ((void *) address))
|
||||
return virt_to_page ((void *) address);
|
||||
|
||||
/*
|
||||
* Kernel text and rodata live in the image mapping, which is neither the linear
|
||||
* map nor vmalloc space. __pa_symbol() is the sanctioned way out of there.
|
||||
*/
|
||||
return pfn_to_page (__phys_to_pfn (__pa_symbol (address)));
|
||||
}
|
||||
|
||||
/* The kernel's own is_vmalloc_or_module_addr() is not exported, and on arm64 the
|
||||
* module region sits below the vmalloc range rather than inside it, so testing for
|
||||
* vmalloc alone would miss it. */
|
||||
static bool
|
||||
frida_is_vmalloc_or_module_addr (const void * x)
|
||||
{
|
||||
#if defined (CONFIG_MODULES) && defined (MODULES_VADDR)
|
||||
unsigned long address = (unsigned long) kasan_reset_tag (x);
|
||||
|
||||
if (address >= MODULES_VADDR && address < MODULES_END)
|
||||
return true;
|
||||
#endif
|
||||
|
||||
return is_vmalloc_addr (x);
|
||||
}
|
||||
|
||||
u64
|
||||
frida_kmod_kernel_base (void)
|
||||
{
|
||||
return frida_kernel_base;
|
||||
}
|
||||
|
||||
u64
|
||||
frida_kmod_kernel_size (void)
|
||||
{
|
||||
return frida_kernel_size;
|
||||
}
|
||||
|
||||
/*
|
||||
* Neither the module list nor the mutex guarding it is exported, so both come
|
||||
* from kallsyms. Without them we report nothing rather than walk unsynchronised.
|
||||
*/
|
||||
void __nocfi
|
||||
frida_kmod_enumerate_modules (FridaFoundModuleFunc func,
|
||||
void * user_data)
|
||||
{
|
||||
struct module * mod;
|
||||
|
||||
if (frida_modules == NULL || frida_module_mutex == NULL)
|
||||
return;
|
||||
|
||||
mutex_lock (frida_module_mutex);
|
||||
|
||||
list_for_each_entry (mod, frida_modules, list)
|
||||
{
|
||||
u64 base, size;
|
||||
|
||||
if (mod->state != MODULE_STATE_LIVE)
|
||||
continue;
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION (6, 4, 0)
|
||||
base = (u64) (uintptr_t) mod->mem[MOD_TEXT].base;
|
||||
size = mod->mem[MOD_TEXT].size;
|
||||
#else
|
||||
base = (u64) (uintptr_t) mod->core_layout.base;
|
||||
size = mod->core_layout.size;
|
||||
#endif
|
||||
|
||||
if (!func (mod->name, "", base, size, user_data))
|
||||
break;
|
||||
}
|
||||
|
||||
mutex_unlock (frida_module_mutex);
|
||||
}
|
||||
|
||||
u64
|
||||
frida_kmod_find_symbol (const char * name)
|
||||
{
|
||||
if (frida_kallsyms_lookup_name_impl == NULL)
|
||||
return 0;
|
||||
|
||||
return frida_kallsyms_lookup_name_impl (name);
|
||||
}
|
||||
|
||||
/*
|
||||
* sprint_symbol_no_offset() is the only exported reverse lookup. It renders
|
||||
* addresses it cannot name as a hex string, which then fails to resolve back to
|
||||
* an address, so no separate check for that case is needed.
|
||||
*/
|
||||
int
|
||||
frida_kmod_symbol_name_from_address (u64 address,
|
||||
char * buffer,
|
||||
size_t size,
|
||||
u64 * symbol_address)
|
||||
{
|
||||
char rendered[KSYM_SYMBOL_LEN];
|
||||
u64 base;
|
||||
|
||||
sprint_symbol_no_offset (rendered, (unsigned long) address);
|
||||
frida_strip_module_suffix (rendered);
|
||||
|
||||
base = frida_kmod_find_symbol (rendered);
|
||||
if (base == 0)
|
||||
return 0;
|
||||
|
||||
strscpy (buffer, rendered, size);
|
||||
*symbol_address = base;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
frida_strip_module_suffix (char * rendered)
|
||||
{
|
||||
char * suffix = strchr (rendered, ' ');
|
||||
|
||||
if (suffix != NULL)
|
||||
*suffix = '\0';
|
||||
}
|
||||
|
||||
int
|
||||
frida_kmod_enumerate_symbols (FridaFoundSymbolFunc func,
|
||||
void * user_data)
|
||||
{
|
||||
struct frida_symbol_visit_ctx ctx = { func, user_data };
|
||||
|
||||
if (frida_kallsyms_on_each_symbol_impl == NULL)
|
||||
return 0;
|
||||
|
||||
frida_kallsyms_on_each_symbol_impl (frida_on_each_symbol, &ctx);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* kallsyms_on_each_symbol() stops when the callback returns non-zero. __nocfi
|
||||
* because ctx->func points into the agent, which is not CFI-instrumented. */
|
||||
static int __nocfi
|
||||
frida_on_each_symbol (void * data,
|
||||
const char * name,
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION (6, 4, 0)
|
||||
struct module * mod,
|
||||
#endif
|
||||
unsigned long address)
|
||||
{
|
||||
struct frida_symbol_visit_ctx * ctx = data;
|
||||
|
||||
return ctx->func (name, address, ctx->user_data) ? 0 : 1;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
use core::ffi::c_void;
|
||||
|
||||
use crate::bindings::{gpointer, gsize, gum_try_mprotect, GumPageProtection};
|
||||
use crate::{kernel, libc};
|
||||
|
||||
const GUM_PAGE_READ: GumPageProtection = 1;
|
||||
const GUM_PAGE_EXECUTE: GumPageProtection = 4;
|
||||
|
||||
// libffi (FFI_TRAMP_EMBEDDER) asks us to back a trampoline table: a copy of its
|
||||
// static trampoline `text` made executable, followed by a writable parameter
|
||||
// table `map_size` bytes later (the trampolines reach their parameters via that
|
||||
// fixed offset). The two halves are whole pages, so flipping the first to RX
|
||||
// leaves the parameter table writable.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn ffi_tramp_embedder_map(
|
||||
text: *const c_void,
|
||||
map_size: usize,
|
||||
code_table: *mut *mut c_void,
|
||||
parm_table: *mut *mut c_void,
|
||||
) -> i32 {
|
||||
unsafe {
|
||||
let base = kernel::alloc_code(map_size * 2);
|
||||
if base.is_null() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
core::ptr::copy_nonoverlapping(text as *const u8, base, map_size);
|
||||
libc::__clear_cache(base, base.add(map_size));
|
||||
|
||||
let made_executable =
|
||||
gum_try_mprotect(base as gpointer, map_size as gsize, GUM_PAGE_READ | GUM_PAGE_EXECUTE);
|
||||
if made_executable == 0 {
|
||||
kernel::free_code(base, map_size * 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
*code_table = base as *mut c_void;
|
||||
*parm_table = base.add(map_size) as *mut c_void;
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn ffi_tramp_embedder_unmap(
|
||||
code_table: *mut c_void,
|
||||
_parm_table: *mut c_void,
|
||||
map_size: usize,
|
||||
) {
|
||||
kernel::free_code(code_table as *mut u8, map_size * 2);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use core::ptr;
|
||||
|
||||
use crate::bindings::{g_wait_is_set, gint64, gpointer};
|
||||
use crate::xnu;
|
||||
use crate::kernel;
|
||||
|
||||
const G_WAIT_INFINITE: gint64 = -1;
|
||||
|
||||
@@ -9,38 +9,35 @@ pub static mut WAKEUP_TOKEN: u64 = 0;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_get_monotonic_time() -> gint64 {
|
||||
let abstime = xnu::mach_absolute_time();
|
||||
let time = xnu::absolutetime_to_nanoseconds(abstime);
|
||||
(time / 1000) as gint64
|
||||
kernel::monotonic_micros()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_wait_sleep(token: gpointer, timeout_us: gint64) {
|
||||
let wait_event = ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8;
|
||||
|
||||
let wait_result = if timeout_us == G_WAIT_INFINITE {
|
||||
xnu::assert_wait(wait_event, xnu::THREAD_INTERRUPTIBLE)
|
||||
let timeout = if timeout_us == G_WAIT_INFINITE {
|
||||
None
|
||||
} else {
|
||||
xnu::assert_wait_timeout(
|
||||
wait_event,
|
||||
xnu::THREAD_INTERRUPTIBLE,
|
||||
(timeout_us * 1000) as u32,
|
||||
1, // TODO
|
||||
)
|
||||
Some(timeout_us as u64)
|
||||
};
|
||||
if wait_result != xnu::THREAD_WAITING {
|
||||
panic!("assert_wait_timeout failed: {}", wait_result);
|
||||
}
|
||||
|
||||
if unsafe { g_wait_is_set(token) != 0 } {
|
||||
xnu::thread_wakeup(wait_event);
|
||||
return;
|
||||
}
|
||||
|
||||
xnu::thread_block(None);
|
||||
kernel::wait(wait_event, timeout, &mut || unsafe {
|
||||
g_wait_is_set(token) != 0
|
||||
});
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_wait_wake(_token: gpointer) {
|
||||
xnu::thread_wakeup(ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8);
|
||||
kernel::wake(ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_io_channel_unix_new(_fd: i32) -> gpointer {
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_spawn_async_with_pipes() -> i32 {
|
||||
0
|
||||
}
|
||||
@@ -8,7 +8,6 @@ use crate::bindings::GThreadFunc;
|
||||
use crate::bindings::{gpointer, gulong};
|
||||
use alloc::boxed::Box;
|
||||
use alloc::collections::BTreeMap;
|
||||
use core::arch::asm;
|
||||
use core::ffi::c_void;
|
||||
use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
|
||||
|
||||
@@ -33,9 +32,11 @@ struct RWLockImpl {
|
||||
_padding: [u8; core::mem::size_of::<GRWLock>() - core::mem::size_of::<AtomicU32>()],
|
||||
}
|
||||
|
||||
// A generation counter rather than a flag: a flag stays set once signalled, so every
|
||||
// later wait returns at once and the caller's loop spins instead of blocking.
|
||||
#[repr(C, align(8))]
|
||||
struct CondImpl {
|
||||
signal: AtomicU32, // Signal state
|
||||
generation: AtomicU32,
|
||||
_padding: [u8; core::mem::size_of::<GCond>() - core::mem::size_of::<AtomicU32>()],
|
||||
}
|
||||
|
||||
@@ -144,10 +145,9 @@ pub extern "C" fn g_rec_mutex_clear(_rec_mutex: *mut GRecMutex) {
|
||||
pub extern "C" fn g_rec_mutex_lock(rec_mutex: *mut GRecMutex) {
|
||||
unsafe {
|
||||
let impl_ = rec_mutex_impl(rec_mutex);
|
||||
let current_thread = get_current_thread_id();
|
||||
|
||||
loop {
|
||||
if rec_mutex_try_acquire(impl_, current_thread) {
|
||||
if rec_mutex_try_acquire(impl_) {
|
||||
break;
|
||||
}
|
||||
rec_mutex_wait(impl_);
|
||||
@@ -159,9 +159,8 @@ pub extern "C" fn g_rec_mutex_lock(rec_mutex: *mut GRecMutex) {
|
||||
pub extern "C" fn g_rec_mutex_trylock(rec_mutex: *mut GRecMutex) -> u32 {
|
||||
unsafe {
|
||||
let impl_ = rec_mutex_impl(rec_mutex);
|
||||
let current_thread = get_current_thread_id();
|
||||
|
||||
if rec_mutex_try_acquire(impl_, current_thread) {
|
||||
if rec_mutex_try_acquire(impl_) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
@@ -183,7 +182,7 @@ pub extern "C" fn g_rec_mutex_unlock(rec_mutex: *mut GRecMutex) {
|
||||
.is_ok()
|
||||
{
|
||||
impl_.owner.store(0, Ordering::Relaxed);
|
||||
lock_notify_all();
|
||||
wake_waiters(&impl_.count);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@@ -198,7 +197,10 @@ pub extern "C" fn g_rec_mutex_unlock(rec_mutex: *mut GRecMutex) {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl, current_thread: u64) -> bool {
|
||||
// Asking the kernel who we are costs more than the uncontended acquisition itself, and
|
||||
// every allocation takes this path, so the identity is only fetched once the lock turns
|
||||
// out to be held.
|
||||
unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl) -> bool {
|
||||
let current_count = impl_.count.load(Ordering::Relaxed);
|
||||
|
||||
if current_count == 0 {
|
||||
@@ -206,12 +208,12 @@ unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl, current_thread: u64) -
|
||||
.compare_exchange_weak(0, 1, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
{
|
||||
impl_.owner.store(current_thread, Ordering::Relaxed);
|
||||
impl_.owner.store(crate::kernel::current_thread_id(), Ordering::Relaxed);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
let current_owner = impl_.owner.load(Ordering::Relaxed);
|
||||
if current_owner == current_thread {
|
||||
if current_owner == crate::kernel::current_thread_id() {
|
||||
if impl_.count
|
||||
.compare_exchange_weak(
|
||||
current_count,
|
||||
@@ -230,7 +232,7 @@ unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl, current_thread: u64) -
|
||||
|
||||
unsafe fn rec_mutex_wait(impl_: &mut RecMutexImpl) {
|
||||
while impl_.count.load(Ordering::Relaxed) != 0 {
|
||||
unsafe { lock_wait_primitive() };
|
||||
wait_on(&impl_.count, &mut || impl_.count.load(Ordering::Relaxed) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +284,7 @@ pub extern "C" fn g_rw_lock_writer_unlock(rw_lock: *mut GRWLock) {
|
||||
unsafe {
|
||||
let impl_ = rw_lock_impl(rw_lock);
|
||||
impl_.state.store(0, Ordering::Release);
|
||||
lock_notify_all();
|
||||
wake_waiters(&impl_.state);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,7 +331,7 @@ pub extern "C" fn g_rw_lock_reader_unlock(rw_lock: *mut GRWLock) {
|
||||
.is_ok()
|
||||
{
|
||||
if current - 1 == 0 {
|
||||
lock_notify_all();
|
||||
wake_waiters(&impl_.state);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -355,13 +357,15 @@ unsafe fn rw_lock_reader_try_acquire(impl_: &mut RWLockImpl) -> bool {
|
||||
|
||||
unsafe fn rw_lock_wait_for_writers(impl_: &mut RWLockImpl) {
|
||||
while impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT != 0 {
|
||||
unsafe { lock_wait_primitive() };
|
||||
wait_on(&impl_.state, &mut || {
|
||||
impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT == 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn rw_lock_wait_for_all(impl_: &mut RWLockImpl) {
|
||||
while impl_.state.load(Ordering::Relaxed) != 0 {
|
||||
unsafe { lock_wait_primitive() };
|
||||
wait_on(&impl_.state, &mut || impl_.state.load(Ordering::Relaxed) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,7 +373,7 @@ unsafe fn rw_lock_wait_for_all(impl_: &mut RWLockImpl) {
|
||||
pub extern "C" fn g_cond_init(cond: *mut GCond) {
|
||||
unsafe {
|
||||
let impl_ = cond_impl(cond);
|
||||
impl_.signal.store(0, Ordering::Relaxed);
|
||||
impl_.generation.store(0, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -379,11 +383,17 @@ pub extern "C" fn g_cond_clear(_cond: *mut GCond) {}
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_cond_wait(cond: *mut GCond, mutex: *mut GMutex) {
|
||||
unsafe {
|
||||
// Read the generation under the mutex, so a signal that lands after the
|
||||
// unlock below is still observed as a change.
|
||||
let seen = cond_impl(cond).generation.load(Ordering::Acquire);
|
||||
|
||||
g_mutex_unlock(mutex);
|
||||
|
||||
let impl_ = cond_impl(cond);
|
||||
while impl_.signal.load(Ordering::Acquire) == 0 {
|
||||
lock_wait_primitive();
|
||||
while impl_.generation.load(Ordering::Acquire) == seen {
|
||||
wait_on(&impl_.generation, &mut || {
|
||||
impl_.generation.load(Ordering::Acquire) != seen
|
||||
});
|
||||
}
|
||||
|
||||
g_mutex_lock(mutex);
|
||||
@@ -394,8 +404,8 @@ pub extern "C" fn g_cond_wait(cond: *mut GCond, mutex: *mut GMutex) {
|
||||
pub extern "C" fn g_cond_signal(cond: *mut GCond) {
|
||||
unsafe {
|
||||
let impl_ = cond_impl(cond);
|
||||
impl_.signal.store(1, Ordering::Release);
|
||||
lock_notify_all();
|
||||
impl_.generation.fetch_add(1, Ordering::Release);
|
||||
wake_waiters(&impl_.generation);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,22 +413,37 @@ pub extern "C" fn g_cond_signal(cond: *mut GCond) {
|
||||
pub extern "C" fn g_cond_broadcast(cond: *mut GCond) {
|
||||
unsafe {
|
||||
let impl_ = cond_impl(cond);
|
||||
impl_.signal.store(1, Ordering::Release);
|
||||
lock_notify_all();
|
||||
impl_.generation.fetch_add(1, Ordering::Release);
|
||||
wake_waiters(&impl_.generation);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_cond_wait_until(cond: *mut GCond, mutex: *mut GMutex, _end_time: i64) -> u32 {
|
||||
// TODO: Implement proper timeout handling
|
||||
g_cond_wait(cond, mutex);
|
||||
1
|
||||
pub extern "C" fn g_cond_wait_until(cond: *mut GCond, mutex: *mut GMutex, end_time: i64) -> u32 {
|
||||
unsafe {
|
||||
let seen = cond_impl(cond).generation.load(Ordering::Acquire);
|
||||
|
||||
g_mutex_unlock(mutex);
|
||||
|
||||
let impl_ = cond_impl(cond);
|
||||
let mut signalled = impl_.generation.load(Ordering::Acquire) != seen;
|
||||
while !signalled && crate::kernel::monotonic_micros() < end_time {
|
||||
wait_on(&impl_.generation, &mut || {
|
||||
impl_.generation.load(Ordering::Acquire) != seen
|
||||
});
|
||||
signalled = impl_.generation.load(Ordering::Acquire) != seen;
|
||||
}
|
||||
|
||||
g_mutex_lock(mutex);
|
||||
|
||||
if signalled { 1 } else { 0 }
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_private_get(key: *mut GPrivate) -> *mut c_void {
|
||||
unsafe {
|
||||
let thread_id = get_current_thread_id();
|
||||
let thread_id = crate::kernel::current_thread_id();
|
||||
lock_acquire(&TLS_LOCK);
|
||||
|
||||
ensure_tls_storage();
|
||||
@@ -438,7 +463,7 @@ pub extern "C" fn g_private_get(key: *mut GPrivate) -> *mut c_void {
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_private_set(key: *mut GPrivate, value: *mut c_void) {
|
||||
unsafe {
|
||||
let thread_id = get_current_thread_id();
|
||||
let thread_id = crate::kernel::current_thread_id();
|
||||
lock_acquire(&TLS_LOCK);
|
||||
|
||||
ensure_tls_storage();
|
||||
@@ -468,7 +493,9 @@ pub extern "C" fn _g_system_thread_create(
|
||||
data: gpointer,
|
||||
) -> *mut GSystemThread {
|
||||
unsafe {
|
||||
let system_thread = crate::xnu::kalloc(core::mem::size_of::<SystemThreadImpl>()) as *mut SystemThreadImpl;
|
||||
crate::kernel::log("frida: glib is creating a thread\n\0");
|
||||
|
||||
let system_thread = crate::kernel::alloc(core::mem::size_of::<SystemThreadImpl>()) as *mut SystemThreadImpl;
|
||||
|
||||
g_mutex_init(&mut (*system_thread).mutex);
|
||||
g_cond_init(&mut (*system_thread).cond);
|
||||
@@ -478,12 +505,12 @@ pub extern "C" fn _g_system_thread_create(
|
||||
(*system_thread).finished.store(0, Ordering::Relaxed);
|
||||
(*system_thread).detached.store(0, Ordering::Relaxed);
|
||||
|
||||
let wrapper_data = crate::xnu::kalloc(core::mem::size_of::<ThreadWrapperData>()) as *mut ThreadWrapperData;
|
||||
let wrapper_data = crate::kernel::alloc(core::mem::size_of::<ThreadWrapperData>()) as *mut ThreadWrapperData;
|
||||
(*wrapper_data).func = func;
|
||||
(*wrapper_data).data = data;
|
||||
(*wrapper_data).system_thread = system_thread;
|
||||
|
||||
let _xnu_result = crate::xnu::kernel_thread_start(thread_wrapper, wrapper_data as *mut c_void);
|
||||
let _xnu_result = crate::kernel::spawn_thread(thread_wrapper, wrapper_data as *mut c_void);
|
||||
|
||||
system_thread as *mut GSystemThread
|
||||
}
|
||||
@@ -496,8 +523,9 @@ extern "C" fn thread_wrapper(parameter: *mut c_void, _wait_result: i32) {
|
||||
let data = (*wrapper_data).data;
|
||||
let system_thread = (*wrapper_data).system_thread;
|
||||
|
||||
let current_thread_id = get_current_thread_id();
|
||||
(*system_thread).thread_id.store(current_thread_id, Ordering::Relaxed);
|
||||
let current_thread_id = crate::kernel::current_thread_id();
|
||||
(*system_thread).thread_id.store(current_thread_id, Ordering::Release);
|
||||
wake_waiters(&(*system_thread).thread_id);
|
||||
|
||||
func.unwrap()(data);
|
||||
|
||||
@@ -537,12 +565,10 @@ pub extern "C" fn _g_system_thread_wait(thread: *mut GSystemThread) {
|
||||
unsafe {
|
||||
let system_thread = thread as *mut SystemThreadImpl;
|
||||
|
||||
loop {
|
||||
let thread_id = (*system_thread).thread_id.load(Ordering::Acquire);
|
||||
if thread_id != 0 {
|
||||
break;
|
||||
}
|
||||
core::hint::spin_loop();
|
||||
while (*system_thread).thread_id.load(Ordering::Acquire) == 0 {
|
||||
wait_on(&(*system_thread).thread_id, &mut || {
|
||||
(*system_thread).thread_id.load(Ordering::Acquire) != 0
|
||||
});
|
||||
}
|
||||
|
||||
g_mutex_lock(&mut (*system_thread).mutex);
|
||||
@@ -556,19 +582,16 @@ pub extern "C" fn _g_system_thread_wait(thread: *mut GSystemThread) {
|
||||
}
|
||||
|
||||
unsafe fn cleanup_system_thread(system_thread: *mut SystemThreadImpl) {
|
||||
crate::xnu::free(system_thread as *mut u8, core::mem::size_of::<SystemThreadImpl>());
|
||||
crate::kernel::free(system_thread as *mut u8, core::mem::size_of::<SystemThreadImpl>());
|
||||
}
|
||||
|
||||
unsafe fn cleanup_wrapper_data(wrapper_data: *mut ThreadWrapperData) {
|
||||
crate::xnu::free(wrapper_data as *mut u8, core::mem::size_of::<ThreadWrapperData>());
|
||||
crate::kernel::free(wrapper_data as *mut u8, core::mem::size_of::<ThreadWrapperData>());
|
||||
}
|
||||
|
||||
unsafe fn lock_acquire(lock: &AtomicU32) {
|
||||
loop {
|
||||
if unsafe { lock_try_acquire(lock) } {
|
||||
break;
|
||||
}
|
||||
unsafe { lock_wait(lock) };
|
||||
while !unsafe { lock_try_acquire(lock) } {
|
||||
wait_on(lock, &mut || lock.load(Ordering::Relaxed) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -579,31 +602,36 @@ unsafe fn lock_try_acquire(lock: &AtomicU32) -> bool {
|
||||
|
||||
unsafe fn lock_release(lock: &AtomicU32) {
|
||||
lock.store(0, Ordering::Release);
|
||||
unsafe { lock_notify_all() };
|
||||
wake_waiters(lock);
|
||||
}
|
||||
|
||||
unsafe fn lock_wait(lock: &AtomicU32) {
|
||||
while lock.load(Ordering::Relaxed) != 0 {
|
||||
unsafe { lock_wait_primitive() };
|
||||
}
|
||||
// Sleeps until `ready` holds or the slice expires, whichever comes first.
|
||||
//
|
||||
// These used to spin on wfe/sev. A kernel thread that spins never reaches the
|
||||
// scheduler, so a lost wakeup — or a genuine deadlock, which is how we found this —
|
||||
// starves the CPU it is on until the hardware watchdog takes the machine down. The
|
||||
// bounded sleep turns both into a stall that can be observed instead.
|
||||
//
|
||||
// The address of the word being waited on names the wait channel: XNU pairs
|
||||
// assert_wait()/thread_wakeup() on it directly, while the Linux shim has a single
|
||||
// channel and treats it as a hint. Every caller re-checks its own condition in a
|
||||
// loop, so a spurious wakeup costs nothing either way.
|
||||
fn wait_on<T>(word: &T, ready: &mut dyn FnMut() -> bool) {
|
||||
crate::kernel::wait(token_of(word), Some(WAIT_SLICE_US), ready);
|
||||
|
||||
// The wait above returns without sleeping whenever `ready` already holds, so on
|
||||
// its own it is not enough to keep a contended loop off the CPU: every caller
|
||||
// here loops, and a loop in a kernel thread that never reaches the scheduler
|
||||
// takes the machine down rather than merely running hot. Yield unconditionally.
|
||||
crate::kernel::yield_now();
|
||||
}
|
||||
|
||||
unsafe fn lock_wait_primitive() {
|
||||
unsafe {
|
||||
asm!("wfe", options(nomem, nostack));
|
||||
}
|
||||
fn wake_waiters<T>(word: &T) {
|
||||
crate::kernel::wake(token_of(word));
|
||||
}
|
||||
|
||||
unsafe fn lock_notify_all() {
|
||||
unsafe {
|
||||
asm!("sev", options(nomem, nostack));
|
||||
}
|
||||
fn token_of<T>(word: &T) -> *const u8 {
|
||||
word as *const T as *const u8
|
||||
}
|
||||
|
||||
pub unsafe fn get_current_thread_id() -> u64 {
|
||||
let thread_ptr: u64;
|
||||
unsafe {
|
||||
asm!("mrs {}, tpidr_el1", out(reg) thread_ptr, options(nomem, nostack));
|
||||
}
|
||||
thread_ptr
|
||||
}
|
||||
const WAIT_SLICE_US: u64 = 1000;
|
||||
+80
-242
@@ -1,28 +1,31 @@
|
||||
// Gum's platform backend, minus the parts that depend on which kernel we are
|
||||
// running inside: memory protection, symbols and the module registry live in
|
||||
// gum_xnu.rs and gum_linux.rs respectively.
|
||||
|
||||
use crate::{
|
||||
bindings::{
|
||||
_GInterfaceInfo, _GTypeInfo, _GumPageProtection_GUM_PAGE_EXECUTE,
|
||||
_GumRwxSupport_GUM_RWX_NONE, GArray, GObject, GObjectClass, GPrivate, GType,
|
||||
GumDebugSymbolDetails, GumExportDetails, GumExportType_GUM_EXPORT_FUNCTION,
|
||||
GumFoundExportFunc, GumMemoryRange, GumModule, GumModuleInterface, GumModuleRegistry,
|
||||
GumPageProtection, GumRwxSupport, GumThreadId, GumTlsKey, g_array_append_vals, g_array_new,
|
||||
g_free, g_object_get_type, g_object_new, g_object_unref, g_once_init_enter,
|
||||
g_once_init_leave, g_strdup, g_type_add_interface_static, g_type_class_peek_parent,
|
||||
g_type_register_static, gboolean, gchar, gconstpointer, gpointer, gsize, guint,
|
||||
gum_barebone_register_module,
|
||||
gum_barebone_try_remap_writable_pages as _gum_barebone_try_remap_writable_pages,
|
||||
gum_module_get_type, gum_mprotect, gum_query_page_size,
|
||||
_GInterfaceInfo, _GTypeInfo, GObject, GObjectClass, GPrivate, GType, GumExportDetails,
|
||||
GumExportType_GUM_EXPORT_FUNCTION, GumFoundExportFunc, GumMemoryRange, GumModule,
|
||||
GumModuleInterface, GumThreadId, GumTlsKey, g_free, g_object_get_type, g_object_new,
|
||||
g_once_init_enter, g_once_init_leave, g_strdup, g_type_add_interface_static,
|
||||
g_type_class_peek_parent, g_type_register_static, gchar, gpointer, gsize, guint,
|
||||
},
|
||||
gthread, libc, xnu,
|
||||
gthread, kernel, libc,
|
||||
};
|
||||
use alloc::boxed::Box;
|
||||
use alloc::ffi::CString;
|
||||
use alloc::format;
|
||||
use alloc::vec::Vec;
|
||||
use core::ptr;
|
||||
use core::{ffi::CStr, ptr::read_volatile};
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
use crate::gum_linux::enumerate_exports_in_range;
|
||||
#[cfg(feature = "xnu")]
|
||||
use crate::gum_xnu::enumerate_exports_in_range;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_process_get_current_thread_id() -> GumThreadId {
|
||||
unsafe { gthread::get_current_thread_id() }
|
||||
kernel::current_thread_id()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
@@ -42,83 +45,6 @@ pub extern "C" fn gum_barebone_query_page_size() -> guint {
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_query_rwx_support() -> GumRwxSupport {
|
||||
_GumRwxSupport_GUM_RWX_NONE
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_can_remap_writable() -> gboolean {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_try_remap_writable_pages(
|
||||
first_page: gpointer,
|
||||
n_pages: guint,
|
||||
) -> gpointer {
|
||||
unsafe {
|
||||
let page_size = gum_query_page_size() as usize;
|
||||
let mut physical_addrs = alloc::vec::Vec::with_capacity(n_pages as usize);
|
||||
|
||||
let mut current_page = first_page as u64;
|
||||
for _ in 0..n_pages {
|
||||
let phys_addr = xnu::ml_vtophys(current_page);
|
||||
physical_addrs.push(phys_addr as gpointer);
|
||||
current_page += page_size as u64;
|
||||
}
|
||||
|
||||
_gum_barebone_try_remap_writable_pages(
|
||||
physical_addrs.as_ptr() as *mut *const core::ffi::c_void,
|
||||
physical_addrs.len() as guint,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
#[inline(never)]
|
||||
pub extern "C" fn gum_barebone_try_remap_writable_pages(
|
||||
addrs: *const gpointer,
|
||||
n_addrs: guint,
|
||||
) -> gpointer {
|
||||
unsafe {
|
||||
read_volatile(&n_addrs);
|
||||
read_volatile(addrs)
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_try_mprotect(
|
||||
_address: gpointer,
|
||||
_size: gsize,
|
||||
_prot: GumPageProtection,
|
||||
) -> gboolean {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_allocate(
|
||||
_address: gpointer,
|
||||
size: gsize,
|
||||
_alignment: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gpointer {
|
||||
let ptr = crate::xnu::kalloc(size as usize);
|
||||
unsafe {
|
||||
core::ptr::write_bytes(ptr, 0, size as usize);
|
||||
if (prot & _GumPageProtection_GUM_PAGE_EXECUTE) != 0 {
|
||||
gum_mprotect(ptr as gpointer, size, prot);
|
||||
}
|
||||
}
|
||||
ptr as gpointer
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_free(address: gpointer, size: gsize) -> gboolean {
|
||||
crate::xnu::free(address as *mut u8, size as usize);
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_clear_cache(address: gpointer, size: gsize) {
|
||||
unsafe {
|
||||
@@ -150,40 +76,6 @@ pub extern "C" fn gum_tls_key_set_value(key: GumTlsKey, value: gpointer) {
|
||||
gthread::g_private_set(key as *mut GPrivate, value)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
|
||||
let kernel_base = crate::xnu::get_kernel_base();
|
||||
|
||||
unsafe {
|
||||
let module_infos = core::ptr::addr_of!(crate::MODULE_INFO);
|
||||
let module_infos = &*module_infos;
|
||||
|
||||
let mut i = 0;
|
||||
for module_info in module_infos.iter() {
|
||||
let module_base = kernel_base + module_info.offset as u64;
|
||||
|
||||
let module_path = if i == 0 {
|
||||
"/System/Library/Kernels/kernel"
|
||||
} else {
|
||||
&format!(
|
||||
"/System/Library/Extensions/{}.kext/{}",
|
||||
module_info.name, module_info.name
|
||||
)
|
||||
};
|
||||
let module_range = GumMemoryRange {
|
||||
base_address: module_base,
|
||||
size: module_info.size as u64,
|
||||
};
|
||||
|
||||
let module = gum_native_module_new(&module_path, &module_info.version, &module_range);
|
||||
gum_barebone_register_module(registry, module);
|
||||
g_object_unref(module as gpointer);
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct GumNativeModule {
|
||||
parent: GObject,
|
||||
@@ -231,7 +123,7 @@ fn gum_native_module_get_type() -> GType {
|
||||
interface_data: core::ptr::null_mut(),
|
||||
};
|
||||
|
||||
g_type_add_interface_static(new_type, gum_module_get_type(), &interface_info);
|
||||
g_type_add_interface_static(new_type, crate::bindings::gum_module_get_type(), &interface_info);
|
||||
|
||||
g_once_init_leave(
|
||||
core::ptr::addr_of_mut!(GUM_NATIVE_MODULE_TYPE) as *mut ::core::ffi::c_void,
|
||||
@@ -274,7 +166,11 @@ unsafe extern "C" fn gum_native_module_finalize(object: *mut GObject) {
|
||||
}
|
||||
}
|
||||
|
||||
fn gum_native_module_new(path: &str, version: &str, range: &GumMemoryRange) -> *mut GumModule {
|
||||
pub(crate) fn gum_native_module_new(
|
||||
path: &str,
|
||||
version: &str,
|
||||
range: &GumMemoryRange,
|
||||
) -> *mut GumModule {
|
||||
unsafe {
|
||||
let path_cstr = CString::new(path).unwrap();
|
||||
let version_cstr = CString::new(version).unwrap();
|
||||
@@ -325,133 +221,75 @@ unsafe extern "C" fn gum_native_module_enumerate_exports(
|
||||
) {
|
||||
unsafe {
|
||||
let module = self_ as *mut GumNativeModule;
|
||||
let module_range = &(*module).range;
|
||||
|
||||
let symbol_table = core::ptr::addr_of!(crate::SYMBOL_TABLE);
|
||||
let symbol_table = &*symbol_table;
|
||||
|
||||
const N_EXT: u8 = 0x01; // External symbol flag
|
||||
const N_TYPE: u8 = 0x0e; // Type mask
|
||||
const N_SECT: u8 = 0x0e; // Defined in section
|
||||
|
||||
let start_address = module_range.base_address;
|
||||
let end_address = module_range.base_address + module_range.size;
|
||||
|
||||
for symbol_ref in symbol_table.iter_symbols_in_range(start_address, end_address) {
|
||||
let is_external = (symbol_ref.symbol_type() & N_EXT) != 0;
|
||||
let is_defined = (symbol_ref.symbol_type() & N_TYPE) == N_SECT;
|
||||
if !is_external || !is_defined {
|
||||
continue;
|
||||
}
|
||||
|
||||
let export_type = GumExportType_GUM_EXPORT_FUNCTION;
|
||||
let range = (*module).range;
|
||||
|
||||
let mut on_export = |name: *const gchar, address: u64| {
|
||||
let export_details = GumExportDetails {
|
||||
type_: export_type,
|
||||
name: g_strdup(symbol_ref.name_ptr()),
|
||||
address: symbol_ref.address(),
|
||||
type_: GumExportType_GUM_EXPORT_FUNCTION,
|
||||
name: g_strdup(name),
|
||||
address,
|
||||
size: -1,
|
||||
};
|
||||
|
||||
let should_continue =
|
||||
func.unwrap()(&export_details as *const GumExportDetails, user_data);
|
||||
if should_continue == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
func.unwrap()(&export_details as *const GumExportDetails, user_data) != 0
|
||||
};
|
||||
|
||||
enumerate_exports_in_range(
|
||||
range.base_address,
|
||||
range.base_address + range.size,
|
||||
&mut on_export,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_symbol_details_from_address(
|
||||
address: gpointer,
|
||||
details: *mut GumDebugSymbolDetails,
|
||||
) -> gboolean {
|
||||
/// What the backends implement for `gum_native_module_enumerate_exports`: call
|
||||
/// `callback` for every exported function in `[start, end)`, stopping early when
|
||||
/// it returns false.
|
||||
pub(crate) type FoundExportCallback<'a> = dyn FnMut(*const gchar, u64) -> bool + 'a;
|
||||
|
||||
// Code slabs the agent allocates are normal writable RAM, so Gum can keep a real writable alias to
|
||||
// them across the slab's lifetime. Pre-existing kernel text is write-protected — CTRR-locked on
|
||||
// Apple silicon, STRICT_KERNEL_RWX on Linux — and can only be modified through a separate path, so
|
||||
// we treat it differently. We distinguish the two by tracking the executable ranges we hand out.
|
||||
static SLAB_LOCK: AtomicU32 = AtomicU32::new(0);
|
||||
static mut SLABS: Vec<(u64, u64)> = Vec::new();
|
||||
|
||||
fn slab_lock() {
|
||||
while SLAB_LOCK
|
||||
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
kernel::yield_now();
|
||||
}
|
||||
}
|
||||
|
||||
fn slab_unlock() {
|
||||
SLAB_LOCK.store(0, Ordering::Release);
|
||||
}
|
||||
|
||||
pub(crate) fn register_slab(start: u64, size: usize) {
|
||||
slab_lock();
|
||||
unsafe {
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
if let Some(symbol) = table.find_symbol_by_address(address as u64) {
|
||||
let name_bytes = symbol.name().as_bytes();
|
||||
let copy_len = core::cmp::min(name_bytes.len(), 2048);
|
||||
core::ptr::copy_nonoverlapping(
|
||||
name_bytes.as_ptr(),
|
||||
(*details).symbol_name.as_mut_ptr() as *mut u8,
|
||||
copy_len,
|
||||
);
|
||||
(*details).symbol_name[copy_len] = 0;
|
||||
|
||||
(*details).address = symbol.address();
|
||||
|
||||
(*details).module_name[0] = 0;
|
||||
(*details).file_name[0] = 0;
|
||||
(*details).line_number = 0;
|
||||
(*details).column = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
0
|
||||
(*core::ptr::addr_of_mut!(SLABS)).push((start, start + size as u64));
|
||||
}
|
||||
slab_unlock();
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_symbol_name_from_address(address: gpointer) -> *mut gchar {
|
||||
pub(crate) fn unregister_slab(start: u64) {
|
||||
slab_lock();
|
||||
unsafe {
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
let name_ptr = table.find_symbol_name_ptr_by_address(address as u64);
|
||||
g_strdup(name_ptr as *const gchar)
|
||||
(*core::ptr::addr_of_mut!(SLABS)).retain(|&(begin, _)| begin != start);
|
||||
}
|
||||
slab_unlock();
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_function(name: *const gchar) -> gpointer {
|
||||
unsafe {
|
||||
let target_name = CStr::from_ptr(name).to_string_lossy();
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
|
||||
if let Some(symbol) = table.find_symbol_by_name(&target_name) {
|
||||
return symbol.address() as gpointer;
|
||||
}
|
||||
|
||||
core::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_functions_named(name: *const gchar) -> *mut GArray {
|
||||
unsafe {
|
||||
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
|
||||
|
||||
let target_name = CStr::from_ptr(name).to_string_lossy();
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
|
||||
let symbols = table.find_symbols_by_name(&target_name);
|
||||
for symbol in symbols {
|
||||
let addr = symbol.address() as gpointer;
|
||||
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
|
||||
}
|
||||
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_functions_matching(pattern: *const gchar) -> *mut GArray {
|
||||
unsafe {
|
||||
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
|
||||
|
||||
let glob_pattern = CStr::from_ptr(pattern).to_string_lossy();
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
|
||||
let symbols = table.find_symbols_matching_glob(&glob_pattern);
|
||||
for symbol in symbols {
|
||||
let addr = symbol.address() as gpointer;
|
||||
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
|
||||
}
|
||||
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
|
||||
0
|
||||
pub(crate) fn is_agent_slab(address: u64) -> bool {
|
||||
slab_lock();
|
||||
let found = unsafe {
|
||||
(*core::ptr::addr_of!(SLABS))
|
||||
.iter()
|
||||
.any(|&(begin, end)| address >= begin && address < end)
|
||||
};
|
||||
slab_unlock();
|
||||
found
|
||||
}
|
||||
@@ -0,0 +1,226 @@
|
||||
// Linux half of Gum's platform backend. Everything is done locally: we are the
|
||||
// kernel, so page permissions go through set_memory_*(), writable aliases of
|
||||
// write-protected text come from vmap(), and symbols come from kallsyms.
|
||||
|
||||
use crate::{
|
||||
bindings::{
|
||||
_GumPageProtection_GUM_PAGE_EXECUTE, _GumPageProtection_GUM_PAGE_READ,
|
||||
_GumPageProtection_GUM_PAGE_WRITE, _GumRwxSupport_GUM_RWX_NONE, GArray,
|
||||
GumDebugSymbolDetails, GumMemoryRange, GumModuleRegistry, GumPageProtection, GumRwxSupport,
|
||||
g_array_append_vals, g_array_new, g_object_unref, g_pattern_spec_free, g_pattern_spec_match_string,
|
||||
g_pattern_spec_new, g_strdup, gboolean, gchar, gconstpointer, gpointer, gsize, guint,
|
||||
gum_barebone_register_module, gum_mprotect,
|
||||
},
|
||||
gum::{self, FoundExportCallback},
|
||||
kernel,
|
||||
};
|
||||
use alloc::format;
|
||||
use core::ffi::CStr;
|
||||
use core::ptr;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_query_rwx_support() -> GumRwxSupport {
|
||||
_GumRwxSupport_GUM_RWX_NONE
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_can_remap_writable() -> gboolean {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_try_remap_writable_pages(
|
||||
first_page: gpointer,
|
||||
n_pages: guint,
|
||||
) -> gpointer {
|
||||
kernel::remap_writable(first_page as u64, n_pages) as gpointer
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages: guint) {
|
||||
kernel::unmap_writable(writable as *mut core::ffi::c_void);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_try_mprotect(
|
||||
address: gpointer,
|
||||
size: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gboolean {
|
||||
if kernel::protect(address as u64, size as usize, prot as u32) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_allocate(
|
||||
_address: gpointer,
|
||||
size: gsize,
|
||||
_alignment: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gpointer {
|
||||
let ptr = kernel::alloc_code(size as usize);
|
||||
unsafe {
|
||||
core::ptr::write_bytes(ptr, 0, size as usize);
|
||||
if (prot & _GumPageProtection_GUM_PAGE_EXECUTE) != 0 {
|
||||
gum::register_slab(ptr as u64, size as usize);
|
||||
gum_mprotect(ptr as gpointer, size, prot);
|
||||
}
|
||||
}
|
||||
|
||||
ptr as gpointer
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_free(address: gpointer, size: gsize) -> gboolean {
|
||||
// vfree() wants the range writable again: the pages go back to the vmalloc
|
||||
// allocator, and leaving them RX would fault the next consumer.
|
||||
if gum::is_agent_slab(address as u64) {
|
||||
unsafe {
|
||||
gum_mprotect(
|
||||
address,
|
||||
size,
|
||||
(_GumPageProtection_GUM_PAGE_READ | _GumPageProtection_GUM_PAGE_WRITE)
|
||||
as GumPageProtection,
|
||||
);
|
||||
}
|
||||
gum::unregister_slab(address as u64);
|
||||
}
|
||||
kernel::free_code(address as *mut u8, size as usize);
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
|
||||
unsafe {
|
||||
let kernel_base = kernel::get_kernel_base();
|
||||
if kernel_base != 0 {
|
||||
register_module(registry, "/boot/vmlinux", "", kernel_base, kernel::get_kernel_size());
|
||||
}
|
||||
|
||||
for m in kernel::enumerate_modules() {
|
||||
let path = format!("/lib/modules/{}.ko", m.name);
|
||||
register_module(registry, &path, &m.version, m.base, m.size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn register_module(
|
||||
registry: *mut GumModuleRegistry,
|
||||
path: &str,
|
||||
version: &str,
|
||||
base: u64,
|
||||
size: u64,
|
||||
) {
|
||||
unsafe {
|
||||
let range = GumMemoryRange {
|
||||
base_address: base,
|
||||
size: size as gsize,
|
||||
};
|
||||
let module = gum::gum_native_module_new(path, version, &range);
|
||||
gum_barebone_register_module(registry, module);
|
||||
g_object_unref(module as gpointer);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn enumerate_exports_in_range(
|
||||
start_address: u64,
|
||||
end_address: u64,
|
||||
callback: &mut FoundExportCallback<'_>,
|
||||
) {
|
||||
kernel::enumerate_symbols(&mut |name, address| {
|
||||
if address < start_address || address >= end_address {
|
||||
return true;
|
||||
}
|
||||
callback(name.as_ptr() as *const gchar, address)
|
||||
});
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_symbol_details_from_address(
|
||||
address: gpointer,
|
||||
details: *mut GumDebugSymbolDetails,
|
||||
) -> gboolean {
|
||||
let Some((name, symbol_address)) = kernel::symbol_from_address(address as u64) else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let name_bytes = name.as_bytes();
|
||||
let copy_len = core::cmp::min(name_bytes.len(), (*details).symbol_name.len() - 1);
|
||||
core::ptr::copy_nonoverlapping(
|
||||
name_bytes.as_ptr(),
|
||||
(*details).symbol_name.as_mut_ptr() as *mut u8,
|
||||
copy_len,
|
||||
);
|
||||
(*details).symbol_name[copy_len] = 0;
|
||||
|
||||
(*details).address = symbol_address;
|
||||
|
||||
(*details).module_name[0] = 0;
|
||||
(*details).file_name[0] = 0;
|
||||
(*details).line_number = 0;
|
||||
(*details).column = 0;
|
||||
}
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_symbol_name_from_address(address: gpointer) -> *mut gchar {
|
||||
let Some((name, _)) = kernel::symbol_from_address(address as u64) else {
|
||||
return ptr::null_mut();
|
||||
};
|
||||
|
||||
let mut name = name;
|
||||
name.push('\0');
|
||||
unsafe { g_strdup(name.as_ptr() as *const gchar) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_function(name: *const gchar) -> gpointer {
|
||||
let address = kernel::find_symbol(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
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
// XNU half of Gum's platform backend. Memory permissions and kernel-text writes
|
||||
// go out over the hostlink, because the guest CPU cannot perform them itself:
|
||||
// the host owns the page tables and the physical-memory bridge.
|
||||
|
||||
use crate::{
|
||||
FridaCommand,
|
||||
bindings::{
|
||||
_GumPageProtection_GUM_PAGE_EXECUTE, _GumPageProtection_GUM_PAGE_READ,
|
||||
_GumPageProtection_GUM_PAGE_WRITE, _GumRwxSupport_GUM_RWX_NONE, GArray,
|
||||
GumDebugSymbolDetails, GumMemoryRange, GumModuleRegistry, GumPageProtection, GumRwxSupport,
|
||||
g_array_append_vals, g_array_new, g_object_unref, g_strdup, g_variant_get_boolean,
|
||||
g_variant_get_uint64, g_variant_new, g_variant_new_fixed_array, g_variant_type_free,
|
||||
g_variant_type_new, g_variant_unref, gboolean, gchar, gconstpointer, gpointer, gsize, guint,
|
||||
gum_barebone_register_module,
|
||||
gum_barebone_try_remap_writable_pages as _gum_barebone_try_remap_writable_pages,
|
||||
gum_mprotect, gum_query_page_size,
|
||||
},
|
||||
gum::{self, FoundExportCallback},
|
||||
host_rpc, kernel, libc,
|
||||
};
|
||||
use alloc::format;
|
||||
use alloc::vec::Vec;
|
||||
use core::ffi::CStr;
|
||||
use core::mem::size_of;
|
||||
use core::ptr;
|
||||
|
||||
const SHADOW_MAGIC: u64 = 0x4644_4f48_5341_4853;
|
||||
const SHADOW_HEADER: usize = 24;
|
||||
const SHADOW_MIN_ADDRESS: u64 = 0xffff_f000_0000_0000;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_query_rwx_support() -> GumRwxSupport {
|
||||
_GumRwxSupport_GUM_RWX_NONE
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_can_remap_writable() -> gboolean {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_try_remap_writable_pages(
|
||||
first_page: gpointer,
|
||||
n_pages: guint,
|
||||
) -> gpointer {
|
||||
if gum::is_agent_slab(first_page as u64) {
|
||||
return remap_agent_pages(first_page, n_pages);
|
||||
}
|
||||
shadow_kernel_pages(first_page, n_pages)
|
||||
}
|
||||
|
||||
fn remap_agent_pages(first_page: gpointer, n_pages: guint) -> gpointer {
|
||||
unsafe {
|
||||
let page_size = gum_query_page_size() as usize;
|
||||
let mut virtual_addrs = Vec::with_capacity(n_pages as usize);
|
||||
|
||||
let mut current_page = first_page as u64;
|
||||
for _ in 0..n_pages {
|
||||
virtual_addrs.push(current_page as gpointer);
|
||||
current_page += page_size as u64;
|
||||
}
|
||||
|
||||
_gum_barebone_try_remap_writable_pages(
|
||||
virtual_addrs.as_ptr() as *mut *const core::ffi::c_void,
|
||||
virtual_addrs.len() as guint,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn shadow_kernel_pages(first_page: gpointer, n_pages: guint) -> gpointer {
|
||||
unsafe {
|
||||
let total = n_pages as usize * gum_query_page_size() as usize;
|
||||
let buffer = kernel::alloc(SHADOW_HEADER + total);
|
||||
*(buffer as *mut u64) = SHADOW_MAGIC;
|
||||
*(buffer.add(8) as *mut u64) = first_page as u64;
|
||||
*(buffer.add(16) as *mut u32) = n_pages;
|
||||
|
||||
let body = buffer.add(SHADOW_HEADER);
|
||||
core::ptr::copy_nonoverlapping(first_page as *const u8, body, total);
|
||||
body as gpointer
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages: guint) {
|
||||
if (writable as u64) < SHADOW_MIN_ADDRESS {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
let buffer = (writable as *mut u8).sub(SHADOW_HEADER);
|
||||
if *(buffer as *const u64) != SHADOW_MAGIC {
|
||||
return;
|
||||
}
|
||||
let first_page = *(buffer.add(8) as *const u64);
|
||||
let n_pages = *(buffer.add(16) as *const u32);
|
||||
let total = n_pages as usize * gum_query_page_size() as usize;
|
||||
|
||||
commit_kernel_patch(first_page, writable as *const u8, total);
|
||||
libc::__clear_cache(first_page as *const u8, (first_page + total as u64) as *const u8);
|
||||
|
||||
kernel::free(buffer, SHADOW_HEADER + total);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn commit_kernel_patch(address: u64, data: *const u8, len: usize) {
|
||||
unsafe {
|
||||
let element_type = g_variant_type_new(c"y".as_ptr());
|
||||
let bytes = g_variant_new_fixed_array(element_type, data as gconstpointer, len as gsize, 1);
|
||||
g_variant_type_free(element_type);
|
||||
|
||||
let payload = g_variant_new(c"(t@ay)".as_ptr(), address, bytes);
|
||||
let reply = host_rpc(FridaCommand::PatchCode, payload);
|
||||
g_variant_unref(reply);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_try_remap_writable_pages(
|
||||
addrs: *const gpointer,
|
||||
n_addrs: guint,
|
||||
) -> gpointer {
|
||||
if !crate::transport_is_up() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
unsafe {
|
||||
let element_type = g_variant_type_new(c"t".as_ptr());
|
||||
let payload = g_variant_new_fixed_array(
|
||||
element_type,
|
||||
addrs as gconstpointer,
|
||||
n_addrs as gsize,
|
||||
size_of::<u64>() as gsize,
|
||||
);
|
||||
g_variant_type_free(element_type);
|
||||
|
||||
let reply = host_rpc(FridaCommand::RemapWritablePages, payload);
|
||||
let virtual_address = g_variant_get_uint64(reply);
|
||||
g_variant_unref(reply);
|
||||
|
||||
virtual_address as gpointer
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_try_mprotect(
|
||||
address: gpointer,
|
||||
size: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gboolean {
|
||||
if !crate::transport_is_up() {
|
||||
return 1;
|
||||
}
|
||||
unsafe {
|
||||
let payload = g_variant_new(c"(ttu)".as_ptr(), address as u64, size as u64, prot as u32);
|
||||
|
||||
let reply = host_rpc(FridaCommand::MemoryProtect, payload);
|
||||
let success = g_variant_get_boolean(reply);
|
||||
g_variant_unref(reply);
|
||||
|
||||
if success != 0 {
|
||||
flush_tlb_range(address as u64, size as u64);
|
||||
}
|
||||
|
||||
success
|
||||
}
|
||||
}
|
||||
|
||||
// The host rewrites our page-table descriptors through the physical-memory
|
||||
// bridge, which leaves this CPU's TLB holding the stale translation. Stalker
|
||||
// flips a slab page RW then RX in place, so without this the freeze never takes
|
||||
// effect and executing the page faults with a permission abort.
|
||||
unsafe fn flush_tlb_range(address: u64, size: u64) {
|
||||
unsafe {
|
||||
let page_size = gum_query_page_size() as u64;
|
||||
let start = address & !(page_size - 1);
|
||||
let end = (address + size + page_size - 1) & !(page_size - 1);
|
||||
|
||||
core::arch::asm!("dsb ish", options(nostack, preserves_flags));
|
||||
let mut va = start;
|
||||
while va < end {
|
||||
core::arch::asm!("tlbi vaae1is, {operand}", operand = in(reg) va >> 12,
|
||||
options(nostack, preserves_flags));
|
||||
va += page_size;
|
||||
}
|
||||
core::arch::asm!("dsb ish", "isb", options(nostack, preserves_flags));
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_allocate(
|
||||
_address: gpointer,
|
||||
size: gsize,
|
||||
_alignment: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gpointer {
|
||||
let ptr = kernel::alloc_code(size as usize);
|
||||
unsafe {
|
||||
core::ptr::write_bytes(ptr, 0, size as usize);
|
||||
if (prot & _GumPageProtection_GUM_PAGE_EXECUTE) != 0 {
|
||||
gum::register_slab(ptr as u64, size as usize);
|
||||
gum_mprotect(ptr as gpointer, size, prot);
|
||||
}
|
||||
}
|
||||
ptr as gpointer
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_free(address: gpointer, size: gsize) -> gboolean {
|
||||
// Executable slabs were flipped to RX in the page tables; restore RW before returning them to
|
||||
// the allocator, otherwise the reclaimed pages stay non-writable and the next consumer faults.
|
||||
if gum::is_agent_slab(address as u64) {
|
||||
unsafe {
|
||||
gum_mprotect(
|
||||
address,
|
||||
size,
|
||||
(_GumPageProtection_GUM_PAGE_READ | _GumPageProtection_GUM_PAGE_WRITE)
|
||||
as GumPageProtection,
|
||||
);
|
||||
}
|
||||
gum::unregister_slab(address as u64);
|
||||
}
|
||||
kernel::free_code(address as *mut u8, size as usize);
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
|
||||
let kernel_base = kernel::get_kernel_base();
|
||||
|
||||
unsafe {
|
||||
let module_infos = core::ptr::addr_of!(crate::MODULE_INFO);
|
||||
let module_infos = &*module_infos;
|
||||
|
||||
let mut i = 0;
|
||||
for module_info in module_infos.iter() {
|
||||
let module_base = kernel_base + module_info.offset as u64;
|
||||
|
||||
let module_path = if i == 0 {
|
||||
"/System/Library/Kernels/kernel"
|
||||
} else {
|
||||
&format!(
|
||||
"/System/Library/Extensions/{}.kext/{}",
|
||||
module_info.name, module_info.name
|
||||
)
|
||||
};
|
||||
let module_range = GumMemoryRange {
|
||||
base_address: module_base,
|
||||
size: module_info.size as u64,
|
||||
};
|
||||
|
||||
let module = gum::gum_native_module_new(&module_path, &module_info.version, &module_range);
|
||||
gum_barebone_register_module(registry, module);
|
||||
g_object_unref(module as gpointer);
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn enumerate_exports_in_range(
|
||||
start_address: u64,
|
||||
end_address: u64,
|
||||
callback: &mut FoundExportCallback<'_>,
|
||||
) {
|
||||
unsafe {
|
||||
let symbol_table = core::ptr::addr_of!(crate::SYMBOL_TABLE);
|
||||
let symbol_table = &*symbol_table;
|
||||
|
||||
const N_EXT: u8 = 0x01; // External symbol flag
|
||||
const N_TYPE: u8 = 0x0e; // Type mask
|
||||
const N_SECT: u8 = 0x0e; // Defined in section
|
||||
|
||||
for symbol_ref in symbol_table.iter_symbols_in_range(start_address, end_address) {
|
||||
let is_external = (symbol_ref.symbol_type() & N_EXT) != 0;
|
||||
let is_defined = (symbol_ref.symbol_type() & N_TYPE) == N_SECT;
|
||||
if !is_external || !is_defined {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !callback(symbol_ref.name_ptr(), symbol_ref.address()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_symbol_details_from_address(
|
||||
address: gpointer,
|
||||
details: *mut GumDebugSymbolDetails,
|
||||
) -> gboolean {
|
||||
unsafe {
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
if let Some(symbol) = table.find_symbol_by_address(address as u64) {
|
||||
let name_bytes = symbol.name().as_bytes();
|
||||
let copy_len = core::cmp::min(name_bytes.len(), 2048);
|
||||
core::ptr::copy_nonoverlapping(
|
||||
name_bytes.as_ptr(),
|
||||
(*details).symbol_name.as_mut_ptr() as *mut u8,
|
||||
copy_len,
|
||||
);
|
||||
(*details).symbol_name[copy_len] = 0;
|
||||
|
||||
(*details).address = symbol.address();
|
||||
|
||||
(*details).module_name[0] = 0;
|
||||
(*details).file_name[0] = 0;
|
||||
(*details).line_number = 0;
|
||||
(*details).column = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_symbol_name_from_address(address: gpointer) -> *mut gchar {
|
||||
unsafe {
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
let name_ptr = table.find_symbol_name_ptr_by_address(address as u64);
|
||||
g_strdup(name_ptr as *const gchar)
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_function(name: *const gchar) -> gpointer {
|
||||
unsafe {
|
||||
let target_name = CStr::from_ptr(name).to_string_lossy();
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
|
||||
if let Some(symbol) = table.find_symbol_by_name(&target_name) {
|
||||
return symbol.address() as gpointer;
|
||||
}
|
||||
|
||||
core::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_functions_named(name: *const gchar) -> *mut GArray {
|
||||
unsafe {
|
||||
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
|
||||
|
||||
let target_name = CStr::from_ptr(name).to_string_lossy();
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
|
||||
let symbols = table.find_symbols_by_name(&target_name);
|
||||
for symbol in symbols {
|
||||
let addr = symbol.address() as gpointer;
|
||||
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
|
||||
}
|
||||
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_functions_matching(pattern: *const gchar) -> *mut GArray {
|
||||
unsafe {
|
||||
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
|
||||
|
||||
let glob_pattern = CStr::from_ptr(pattern).to_string_lossy();
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
|
||||
let symbols = table.find_symbols_matching_glob(&glob_pattern);
|
||||
for symbol in symbols {
|
||||
let addr = symbol.address() as gpointer;
|
||||
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
|
||||
}
|
||||
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
|
||||
0
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
// Linux transport: /dev/frida, framed the same way as the XNU hostlinks (4-byte
|
||||
// little-endian length prefix) so the host-side (yqv) protocol is identical across
|
||||
// every transport.
|
||||
//
|
||||
// The character device itself lives in linux/frida-kmod.c and hands us a byte
|
||||
// stream; whoever opens the device decides how it reaches the host.
|
||||
|
||||
use core::cell::UnsafeCell;
|
||||
use core::ffi::{c_int, c_void};
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
struct Inner {
|
||||
rx_lenbuf: [u8; 4],
|
||||
rx_lenhave: usize,
|
||||
rx_buf: Vec<u8>,
|
||||
rx_have: usize,
|
||||
rx_need: usize,
|
||||
}
|
||||
|
||||
pub struct Hostlink {
|
||||
state: UnsafeCell<Inner>,
|
||||
on_rx: Option<fn(&[u8])>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Hostlink {}
|
||||
|
||||
impl Hostlink {
|
||||
pub fn init(on_rx: Option<fn(&[u8])>) -> Result<Self, ()> {
|
||||
if unsafe { frida_kmod_link_open() } != 0 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
Ok(Hostlink {
|
||||
state: UnsafeCell::new(Inner {
|
||||
rx_lenbuf: [0; 4],
|
||||
rx_lenhave: 0,
|
||||
rx_buf: Vec::new(),
|
||||
rx_have: 0,
|
||||
rx_need: 0,
|
||||
}),
|
||||
on_rx,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn send(&self, payload: &[u8]) {
|
||||
let header = (payload.len() as u32).to_le_bytes();
|
||||
unsafe {
|
||||
frida_kmod_link_send(header.as_ptr() as *const c_void, header.len());
|
||||
frida_kmod_link_send(payload.as_ptr() as *const c_void, payload.len());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process(&self) {
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
loop {
|
||||
if s.rx_lenhave < 4 {
|
||||
let lo = s.rx_lenhave;
|
||||
let n = recv_nonblocking(&mut s.rx_lenbuf[lo..4]);
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
s.rx_lenhave += n;
|
||||
if s.rx_lenhave < 4 {
|
||||
continue;
|
||||
}
|
||||
s.rx_need = u32::from_le_bytes(s.rx_lenbuf) as usize;
|
||||
s.rx_have = 0;
|
||||
s.rx_buf.resize(s.rx_need, 0);
|
||||
}
|
||||
|
||||
while s.rx_have < s.rx_need {
|
||||
let lo = s.rx_have;
|
||||
let hi = s.rx_need;
|
||||
let n = recv_nonblocking(&mut s.rx_buf[lo..hi]);
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
s.rx_have += n;
|
||||
}
|
||||
|
||||
// Detach the frame and reset receive state before dispatching: the callback may
|
||||
// re-enter process() (a synchronous host RPC issued while handling a command), and
|
||||
// it must start from a clean state instead of re-dispatching this same frame.
|
||||
let frame = core::mem::take(&mut s.rx_buf);
|
||||
let need = s.rx_need;
|
||||
s.rx_lenhave = 0;
|
||||
s.rx_have = 0;
|
||||
s.rx_need = 0;
|
||||
|
||||
if let Some(cb) = self.on_rx {
|
||||
cb(&frame[..need]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Hostlink {
|
||||
fn drop(&mut self) {
|
||||
unsafe { frida_kmod_link_close() };
|
||||
}
|
||||
}
|
||||
|
||||
fn recv_nonblocking(dst: &mut [u8]) -> usize {
|
||||
let n = unsafe { frida_kmod_link_recv(dst.as_mut_ptr() as *mut c_void, dst.len()) };
|
||||
if n <= 0 { 0 } else { n as usize }
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
fn frida_kmod_link_open() -> c_int;
|
||||
fn frida_kmod_link_close();
|
||||
fn frida_kmod_link_send(data: *const c_void, size: usize) -> c_int;
|
||||
fn frida_kmod_link_recv(data: *mut c_void, size: usize) -> isize;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ use core::ptr::{read_volatile, write_volatile};
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use crate::gum::gum_barebone_query_page_size;
|
||||
use crate::xnu;
|
||||
use crate::{kernel, xnu};
|
||||
|
||||
const MMIO_SIZE: u64 = 0x200;
|
||||
|
||||
@@ -107,14 +107,14 @@ struct DmaPage {
|
||||
|
||||
fn dma_page_alloc() -> DmaPage {
|
||||
let len = PAGE_SIZE.load(Ordering::Relaxed);
|
||||
let va = xnu::kalloc(len);
|
||||
let va = kernel::alloc(len);
|
||||
let pa = xnu::ml_vtophys(va as u64);
|
||||
DmaPage { va, pa }
|
||||
}
|
||||
|
||||
fn dma_page_free(p: DmaPage) {
|
||||
let len = PAGE_SIZE.load(Ordering::Relaxed);
|
||||
xnu::free(p.va, len);
|
||||
kernel::free(p.va, len);
|
||||
}
|
||||
|
||||
struct Vq {
|
||||
@@ -359,7 +359,7 @@ impl Hostlink {
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
|
||||
let total = 4 + payload.len();
|
||||
let buf = xnu::kalloc(total);
|
||||
let buf = kernel::alloc(total);
|
||||
unsafe {
|
||||
let len = payload.len() as u32;
|
||||
*buf.add(0) = (len & 0xFF) as u8;
|
||||
@@ -370,7 +370,7 @@ impl Hostlink {
|
||||
}
|
||||
let frame: &'static [u8] = unsafe { core::slice::from_raw_parts(buf, total) };
|
||||
|
||||
let node = xnu::kalloc(size_of::<TxNode>()) as *mut TxNode;
|
||||
let node = kernel::alloc(size_of::<TxNode>()) as *mut TxNode;
|
||||
unsafe {
|
||||
(*node).next = core::ptr::null_mut();
|
||||
(*node).frame = frame;
|
||||
@@ -386,7 +386,7 @@ impl Hostlink {
|
||||
s.tx_tail = node;
|
||||
}
|
||||
|
||||
xnu::thread_wakeup(s.wake_token);
|
||||
kernel::wake(s.wake_token);
|
||||
}
|
||||
|
||||
pub fn process(&self) {
|
||||
@@ -592,7 +592,7 @@ impl Hostlink {
|
||||
| ((s.rx_lenbuf[3] as usize) << 24);
|
||||
|
||||
if s.rx_buf.is_none() && len > 0 {
|
||||
let buf = xnu::kalloc(len);
|
||||
let buf = kernel::alloc(len);
|
||||
let slice: &'static mut [u8] =
|
||||
unsafe { core::slice::from_raw_parts_mut(buf, len) };
|
||||
s.rx_buf = Some(slice);
|
||||
@@ -718,8 +718,8 @@ impl Hostlink {
|
||||
self.kick(sel);
|
||||
}
|
||||
|
||||
xnu::free(frame.as_ptr() as *mut u8, frame.len());
|
||||
xnu::free(node as *mut u8, core::mem::size_of::<TxNode>());
|
||||
kernel::free(frame.as_ptr() as *mut u8, frame.len());
|
||||
kernel::free(node as *mut u8, core::mem::size_of::<TxNode>());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -732,7 +732,7 @@ impl Hostlink {
|
||||
}
|
||||
|
||||
extern "C" fn isr_wake(token: *mut c_void, _refcon: *mut c_void, _nub: *mut c_void, _src: i32) {
|
||||
xnu::thread_wakeup(token as *const u8);
|
||||
kernel::wake(token as *const u8);
|
||||
}
|
||||
|
||||
fn r32(mmio: *mut u8, off: usize) -> u32 {
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
// Same 4-byte LE length-prefix framing as hostlink_virtio so the host-side
|
||||
// (yqv) protocol is identical across transports.
|
||||
|
||||
use core::cell::UnsafeCell;
|
||||
use core::ffi::{c_int, c_void};
|
||||
use core::ptr;
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::kernel;
|
||||
|
||||
const AF_VSOCK: c_int = 40;
|
||||
const SOCK_STREAM: c_int = 1;
|
||||
const VMADDR_CID_HOST: u32 = 2;
|
||||
|
||||
const MSG_DONTWAIT: c_int = 0x80;
|
||||
|
||||
// Darwin errno values for a non-blocking connect handshake that completes in the background.
|
||||
const EWOULDBLOCK: c_int = 35;
|
||||
const EINPROGRESS: c_int = 36;
|
||||
const EISCONN: c_int = 56;
|
||||
|
||||
#[repr(C)]
|
||||
struct SockaddrVm {
|
||||
svm_len: u8,
|
||||
svm_family: u8,
|
||||
svm_reserved1: u16,
|
||||
svm_port: u32,
|
||||
svm_cid: u32,
|
||||
svm_zero: [u8; 4],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct Iovec {
|
||||
iov_base: *mut c_void,
|
||||
iov_len: usize,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct Msghdr {
|
||||
msg_name: *mut c_void,
|
||||
msg_namelen: u32,
|
||||
msg_iov: *mut Iovec,
|
||||
msg_iovlen: c_int,
|
||||
msg_control: *mut c_void,
|
||||
msg_controllen: u32,
|
||||
msg_flags: c_int,
|
||||
}
|
||||
|
||||
type SocketT = *mut c_void;
|
||||
type SockUpcall = unsafe extern "C" fn(so: SocketT, cookie: *mut c_void, waitf: c_int);
|
||||
|
||||
unsafe extern "C" {
|
||||
static _sock_socket: unsafe extern "C" fn(
|
||||
domain: c_int,
|
||||
type_: c_int,
|
||||
proto: c_int,
|
||||
callback: Option<SockUpcall>,
|
||||
cookie: *mut c_void,
|
||||
new_so: *mut SocketT,
|
||||
) -> c_int;
|
||||
static _sock_connect:
|
||||
unsafe extern "C" fn(so: SocketT, to: *const c_void, flags: c_int) -> c_int;
|
||||
static _sock_send: unsafe extern "C" fn(
|
||||
so: SocketT,
|
||||
msg: *const Msghdr,
|
||||
flags: c_int,
|
||||
sentlen: *mut usize,
|
||||
) -> c_int;
|
||||
static _sock_receive: unsafe extern "C" fn(
|
||||
so: SocketT,
|
||||
msg: *mut Msghdr,
|
||||
flags: c_int,
|
||||
recvlen: *mut usize,
|
||||
) -> c_int;
|
||||
static _sock_close: unsafe extern "C" fn(so: SocketT);
|
||||
}
|
||||
|
||||
static UPCALL_PENDING: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
struct Inner {
|
||||
so: SocketT,
|
||||
rx_lenbuf: [u8; 4],
|
||||
rx_lenhave: usize,
|
||||
rx_buf: Vec<u8>,
|
||||
rx_have: usize,
|
||||
rx_need: usize,
|
||||
}
|
||||
|
||||
pub struct Hostlink {
|
||||
state: UnsafeCell<Inner>,
|
||||
on_rx: Option<fn(&[u8])>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Hostlink {}
|
||||
|
||||
impl Hostlink {
|
||||
pub fn init(
|
||||
host_port: u32,
|
||||
on_rx: Option<fn(&[u8])>,
|
||||
wake_token: *const u8,
|
||||
) -> Result<Self, ()> {
|
||||
unsafe {
|
||||
let mut so: SocketT = ptr::null_mut();
|
||||
// The kernel invokes the sock_upcall via an authenticated branch (blraa, IA key)
|
||||
// with the sock_upcall type discriminator, so the pointer must be signed to match.
|
||||
let signed_upcall: SockUpcall =
|
||||
core::mem::transmute(crate::pac::ptrauth_sign(upcall as *const u8, 0x12f7));
|
||||
let rc = _sock_socket(
|
||||
AF_VSOCK,
|
||||
SOCK_STREAM,
|
||||
0,
|
||||
Some(signed_upcall),
|
||||
wake_token as *mut c_void,
|
||||
&mut so,
|
||||
);
|
||||
if rc != 0 || so.is_null() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let addr = SockaddrVm {
|
||||
svm_len: core::mem::size_of::<SockaddrVm>() as u8,
|
||||
svm_family: AF_VSOCK as u8,
|
||||
svm_reserved1: 0,
|
||||
svm_port: host_port,
|
||||
svm_cid: VMADDR_CID_HOST,
|
||||
svm_zero: [0; 4],
|
||||
};
|
||||
// The KPI connect is asynchronous: it returns EWOULDBLOCK/EINPROGRESS and the
|
||||
// handshake completes in the background once the host accepts. Re-calling connect to
|
||||
// poll is invalid (it corrupts the connecting state), so accept the in-progress codes
|
||||
// as success and let the main loop drive I/O once the link is up.
|
||||
let rc = _sock_connect(so, &addr as *const _ as *const c_void, 0);
|
||||
if rc != 0 && !matches!(rc, EWOULDBLOCK | EINPROGRESS | EISCONN) {
|
||||
_sock_close(so);
|
||||
return Err(());
|
||||
}
|
||||
|
||||
Ok(Hostlink {
|
||||
state: UnsafeCell::new(Inner {
|
||||
so,
|
||||
rx_lenbuf: [0; 4],
|
||||
rx_lenhave: 0,
|
||||
rx_buf: Vec::new(),
|
||||
rx_have: 0,
|
||||
rx_need: 0,
|
||||
}),
|
||||
on_rx,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send(&self, payload: &[u8]) {
|
||||
let s = unsafe { &*self.state.get() };
|
||||
unsafe {
|
||||
let len = payload.len() as u32;
|
||||
let mut hdr = [
|
||||
(len & 0xff) as u8,
|
||||
((len >> 8) & 0xff) as u8,
|
||||
((len >> 16) & 0xff) as u8,
|
||||
((len >> 24) & 0xff) as u8,
|
||||
];
|
||||
let mut iov = [
|
||||
Iovec {
|
||||
iov_base: hdr.as_mut_ptr() as *mut c_void,
|
||||
iov_len: 4,
|
||||
},
|
||||
Iovec {
|
||||
iov_base: payload.as_ptr() as *mut c_void,
|
||||
iov_len: payload.len(),
|
||||
},
|
||||
];
|
||||
let msg = Msghdr {
|
||||
msg_name: ptr::null_mut(),
|
||||
msg_namelen: 0,
|
||||
msg_iov: iov.as_mut_ptr(),
|
||||
msg_iovlen: 2,
|
||||
msg_control: ptr::null_mut(),
|
||||
msg_controllen: 0,
|
||||
msg_flags: 0,
|
||||
};
|
||||
let mut sent: usize = 0;
|
||||
let _ = _sock_send(s.so, &msg, 0, &mut sent);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process(&self) {
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
loop {
|
||||
if s.rx_lenhave < 4 {
|
||||
let lo = s.rx_lenhave;
|
||||
let n = unsafe { recv_nonblocking(s.so, &mut s.rx_lenbuf[lo..4]) };
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
s.rx_lenhave += n;
|
||||
if s.rx_lenhave < 4 {
|
||||
continue;
|
||||
}
|
||||
s.rx_need = u32::from_le_bytes(s.rx_lenbuf) as usize;
|
||||
s.rx_have = 0;
|
||||
s.rx_buf.resize(s.rx_need, 0);
|
||||
}
|
||||
|
||||
while s.rx_have < s.rx_need {
|
||||
let lo = s.rx_have;
|
||||
let hi = s.rx_need;
|
||||
let n = unsafe { recv_nonblocking(s.so, &mut s.rx_buf[lo..hi]) };
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
s.rx_have += n;
|
||||
}
|
||||
|
||||
// Detach the frame and reset receive state before dispatching: the callback may
|
||||
// re-enter process() (a synchronous host RPC issued while handling a command), and
|
||||
// it must start from a clean state instead of re-dispatching this same frame.
|
||||
let frame = core::mem::take(&mut s.rx_buf);
|
||||
let need = s.rx_need;
|
||||
s.rx_lenhave = 0;
|
||||
s.rx_have = 0;
|
||||
s.rx_need = 0;
|
||||
|
||||
if let Some(cb) = self.on_rx {
|
||||
cb(&frame[..need]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Hostlink {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let s = &*self.state.get();
|
||||
_sock_close(s.so);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn recv_nonblocking(so: SocketT, dst: &mut [u8]) -> usize {
|
||||
unsafe {
|
||||
let mut iov = Iovec {
|
||||
iov_base: dst.as_mut_ptr() as *mut c_void,
|
||||
iov_len: dst.len(),
|
||||
};
|
||||
let mut msg = Msghdr {
|
||||
msg_name: ptr::null_mut(),
|
||||
msg_namelen: 0,
|
||||
msg_iov: &mut iov,
|
||||
msg_iovlen: 1,
|
||||
msg_control: ptr::null_mut(),
|
||||
msg_controllen: 0,
|
||||
msg_flags: 0,
|
||||
};
|
||||
let mut got: usize = 0;
|
||||
let _ = _sock_receive(so, &mut msg, MSG_DONTWAIT, &mut got);
|
||||
got
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn upcall(_so: SocketT, cookie: *mut c_void, _waitf: c_int) {
|
||||
UPCALL_PENDING.fetch_add(1, Ordering::Release);
|
||||
if !cookie.is_null() {
|
||||
kernel::wake(cookie as *const u8);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Facade over the host kernel the agent is running inside of. Both backends
|
||||
// expose the same primitives, so the rest of the agent never names a specific
|
||||
// kernel.
|
||||
//
|
||||
// XNU is reached from the outside: a remote stub injects the agent and patches
|
||||
// the addresses it needs into .kernel_addrs. Linux is reached from the inside:
|
||||
// the agent is a kernel module, so its glue is an ordinary C translation unit
|
||||
// (linux/frida-kmod.c) compiled against the target kernel's headers by kbuild.
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
pub use crate::linux::*;
|
||||
#[cfg(feature = "xnu")]
|
||||
pub use crate::xnu::*;
|
||||
|
||||
use core::ffi::c_void;
|
||||
|
||||
pub type ThreadEntry = unsafe extern "C" fn(parameter: *mut c_void, wait_result: i32);
|
||||
@@ -0,0 +1,997 @@
|
||||
#![no_std]
|
||||
|
||||
#[cfg(not(any(feature = "xnu", feature = "linux")))]
|
||||
compile_error!("pick a flavour: --features xnu or --features linux");
|
||||
|
||||
#[cfg(all(feature = "xnu", feature = "linux"))]
|
||||
compile_error!("pick one flavour, not both");
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use core::alloc::{GlobalAlloc, Layout};
|
||||
use core::ffi::{CStr, c_void};
|
||||
use core::ptr;
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use bindings::{
|
||||
GAsyncResult, GBytes, GError, GMainContext, GObject, GVariant, GumMemoryRange,
|
||||
GumScript, GumScriptBackend, g_error_free, g_free, g_main_context_iteration,
|
||||
g_main_context_push_thread_default, g_memdup2, g_object_unref, g_variant_check_format_string,
|
||||
g_variant_get, g_variant_get_data, g_variant_get_size, g_variant_get_string,
|
||||
g_variant_get_uint32, g_variant_new, g_variant_new_from_data, g_variant_new_string,
|
||||
g_variant_new_tuple, g_variant_new_uint32, g_variant_type_free, g_variant_type_new,
|
||||
g_variant_unref, gchar, gpointer, gsize, gum_script_backend_create,
|
||||
gum_script_backend_create_finish, gum_script_backend_get_scheduler,
|
||||
gum_script_backend_obtain_qjs, gum_script_get_stalker, gum_script_load,
|
||||
gum_script_load_finish, gum_script_post, gum_script_scheduler_disable_background_thread,
|
||||
gum_script_scheduler_get_js_context, gum_script_set_message_handler, gum_script_unload,
|
||||
gum_script_unload_finish, gum_stalker_exclude,
|
||||
};
|
||||
|
||||
mod ffi;
|
||||
mod glib;
|
||||
mod gthread;
|
||||
mod gum;
|
||||
mod libc;
|
||||
|
||||
pub mod kernel;
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
mod gum_linux;
|
||||
#[cfg(feature = "linux")]
|
||||
mod hostlink_chardev;
|
||||
#[cfg(feature = "linux")]
|
||||
mod linux;
|
||||
|
||||
#[cfg(feature = "xnu")]
|
||||
mod gum_xnu;
|
||||
#[cfg(feature = "xnu")]
|
||||
mod hostlink_virtio;
|
||||
#[cfg(feature = "xnu")]
|
||||
mod hostlink_vsock;
|
||||
#[cfg(feature = "xnu")]
|
||||
mod pac;
|
||||
#[cfg(feature = "xnu")]
|
||||
mod symbols;
|
||||
#[cfg(feature = "xnu")]
|
||||
mod xnu;
|
||||
|
||||
mod bindings {
|
||||
#![allow(
|
||||
dead_code,
|
||||
improper_ctypes,
|
||||
non_camel_case_types,
|
||||
non_snake_case,
|
||||
non_upper_case_globals,
|
||||
unused_imports
|
||||
)]
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum FridaCommand {
|
||||
CreateScript = 1,
|
||||
LoadScript = 2,
|
||||
DestroyScript = 3,
|
||||
PostScriptMessage = 4,
|
||||
RemapWritablePages = 5,
|
||||
MemoryProtect = 6,
|
||||
PatchCode = 7,
|
||||
|
||||
Reply = 128,
|
||||
ScriptMessage = 129,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for FridaCommand {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
FridaCommand::CreateScript => write!(f, "CreateScript"),
|
||||
FridaCommand::LoadScript => write!(f, "LoadScript"),
|
||||
FridaCommand::DestroyScript => write!(f, "DestroyScript"),
|
||||
FridaCommand::PostScriptMessage => write!(f, "PostScriptMessage"),
|
||||
FridaCommand::RemapWritablePages => write!(f, "RemapWritablePages"),
|
||||
FridaCommand::MemoryProtect => write!(f, "MemoryProtect"),
|
||||
FridaCommand::PatchCode => write!(f, "PatchCode"),
|
||||
FridaCommand::Reply => write!(f, "Reply"),
|
||||
FridaCommand::ScriptMessage => write!(f, "ScriptMessage"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HandlerResponse {
|
||||
variant: *mut GVariant,
|
||||
}
|
||||
|
||||
impl HandlerResponse {
|
||||
fn success(variant: *mut GVariant) -> Self {
|
||||
Self {
|
||||
variant: unsafe { g_variant_new(c"(bv)".as_ptr(), 1, variant) },
|
||||
}
|
||||
}
|
||||
|
||||
fn success_empty() -> Self {
|
||||
Self::success(unsafe { g_variant_new_tuple(ptr::null(), 0) })
|
||||
}
|
||||
|
||||
fn error(message: &str) -> Self {
|
||||
let mut c_message = String::from(message);
|
||||
c_message.push('\0');
|
||||
|
||||
Self {
|
||||
variant: unsafe {
|
||||
g_variant_new(
|
||||
c"(bv)".as_ptr(),
|
||||
0,
|
||||
g_variant_new_string(c_message.as_ptr()),
|
||||
)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu")]
|
||||
mod entrypoint_xnu {
|
||||
use super::*;
|
||||
use crate::symbols::SymbolTable;
|
||||
|
||||
static mut CONFIG_DATA: &'static [u8] = &[];
|
||||
pub static mut MODULE_INFO: Vec<ModuleInfo> = Vec::new();
|
||||
pub static mut SYMBOL_TABLE: SymbolTable = SymbolTable::empty();
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ModuleInfo {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub offset: u32,
|
||||
pub size: u32,
|
||||
pub start_func_offset: u32,
|
||||
pub stop_func_offset: u32,
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn _start(config_data: *const u8, config_size: usize) {
|
||||
unsafe {
|
||||
kernel::log("frida: _start entry\n\0");
|
||||
CONFIG_DATA = core::slice::from_raw_parts(config_data, config_size);
|
||||
kernel::log("frida: _start config stored\n\0");
|
||||
|
||||
let _r = kernel::spawn_thread(worker, 12345usize as *mut c_void);
|
||||
}
|
||||
}
|
||||
|
||||
// KNOWN ISSUE (vphone research kernel): the worker panics during gum_init_embedded with
|
||||
// "debug exceptions enabled in kernel mode". The worker itself is healthy — it runs with
|
||||
// PSTATE.D=1 (debug masked) throughout. The panic is the research kernel's synchronous-
|
||||
// exception handler reacting to ANY exception the agent takes in kernel mode while the VZ
|
||||
// debug stub is active. Two confirmed sources: (1) the host's breakpoint-based Callbacks
|
||||
// patch BRK at gum_try_mprotect/remap, and the BRK is a debug exception; (2) a stray jump
|
||||
// into the agent's data region during GObject init (instruction abort) — looks like a
|
||||
// misapplied relocation for a function pointer stored in .data/.rodata.
|
||||
unsafe extern "C" fn worker(_parameter: *mut c_void, _wait_result: i32) {
|
||||
unsafe {
|
||||
kernel::log("frida: worker entry\n\0");
|
||||
init_gum();
|
||||
|
||||
let (transport_config, kernel_base, module_info, symbol_table, own_range) =
|
||||
parse_config(core::ptr::addr_of!(CONFIG_DATA).read());
|
||||
kernel::set_kernel_base(kernel_base);
|
||||
MODULE_INFO = module_info;
|
||||
SYMBOL_TABLE = symbol_table;
|
||||
OWN_RANGE = own_range;
|
||||
kernel::log("frida: config parsed, init transport\n\0");
|
||||
|
||||
let wake_token = ptr::addr_of_mut!(glib::WAKEUP_TOKEN) as *const u8;
|
||||
let transport = match transport_config {
|
||||
TransportConfig::Virtio { mmio, irq } => Transport::Virtio(
|
||||
hostlink_virtio::Hostlink::init(
|
||||
mmio,
|
||||
irq,
|
||||
Some(on_frame_from_host),
|
||||
wake_token,
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
TransportConfig::Vsock { host_port } => Transport::Vsock(
|
||||
hostlink_vsock::Hostlink::init(host_port, Some(on_frame_from_host), wake_token)
|
||||
.unwrap(),
|
||||
),
|
||||
};
|
||||
transport_set(transport);
|
||||
kernel::log("frida: transport up, entering main loop\n\0");
|
||||
|
||||
run_main_loop(adopt_js_context());
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn parse_config(
|
||||
config: &[u8],
|
||||
) -> (
|
||||
TransportConfig,
|
||||
u64,
|
||||
Vec<ModuleInfo>,
|
||||
SymbolTable,
|
||||
GumMemoryRange,
|
||||
) {
|
||||
use crate::bindings::{
|
||||
GVariantIter, g_variant_get_byte, g_variant_get_child_value, g_variant_get_uint64,
|
||||
g_variant_get_variant, g_variant_iter_init, g_variant_iter_next,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let type_string = c"((tt)yvta(ssuuuu)ay)".as_ptr() as *const gchar;
|
||||
let variant_type = g_variant_type_new(type_string);
|
||||
|
||||
let root_variant = g_variant_new_from_data(
|
||||
variant_type,
|
||||
config.as_ptr() as *const c_void,
|
||||
config.len() as gsize,
|
||||
1,
|
||||
None,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
|
||||
let transport_kind_variant = g_variant_get_child_value(root_variant, 1);
|
||||
let transport_kind = g_variant_get_byte(transport_kind_variant);
|
||||
|
||||
let transport_cfg_outer = g_variant_get_child_value(root_variant, 2);
|
||||
let transport_cfg_inner = g_variant_get_variant(transport_cfg_outer);
|
||||
let transport_config = match transport_kind {
|
||||
0 => {
|
||||
let mmio_variant = g_variant_get_child_value(transport_cfg_inner, 0);
|
||||
let mmio = g_variant_get_uint64(mmio_variant);
|
||||
let irq_variant = g_variant_get_child_value(transport_cfg_inner, 1);
|
||||
let irq = g_variant_get_uint32(irq_variant);
|
||||
g_variant_unref(irq_variant);
|
||||
g_variant_unref(mmio_variant);
|
||||
TransportConfig::Virtio { mmio, irq }
|
||||
}
|
||||
1 => {
|
||||
let host_port = g_variant_get_uint32(transport_cfg_inner);
|
||||
TransportConfig::Vsock { host_port }
|
||||
}
|
||||
_ => panic!("Unsupported transport kind: {}", transport_kind),
|
||||
};
|
||||
g_variant_unref(transport_cfg_inner);
|
||||
g_variant_unref(transport_cfg_outer);
|
||||
g_variant_unref(transport_kind_variant);
|
||||
|
||||
let kernel_base_variant = g_variant_get_child_value(root_variant, 3);
|
||||
let kernel_base = g_variant_get_uint64(kernel_base_variant);
|
||||
kernel::set_kernel_base(kernel_base);
|
||||
|
||||
let module_info_variant = g_variant_get_child_value(root_variant, 4);
|
||||
let mut iter: GVariantIter = core::mem::zeroed();
|
||||
g_variant_iter_init(&mut iter as *mut GVariantIter, module_info_variant);
|
||||
|
||||
let mut module_info: Vec<ModuleInfo> = Vec::new();
|
||||
let mut raw_name: *mut gchar = ptr::null_mut();
|
||||
let mut raw_version: *mut gchar = ptr::null_mut();
|
||||
let mut offset: u32 = 0;
|
||||
let mut size: u32 = 0;
|
||||
let mut start_func_offset: u32 = 0;
|
||||
let mut stop_func_offset: u32 = 0;
|
||||
while g_variant_iter_next(
|
||||
&mut iter as *mut GVariantIter,
|
||||
c"(&s&suuuu)".as_ptr(),
|
||||
&mut raw_name,
|
||||
&mut raw_version,
|
||||
&mut offset,
|
||||
&mut size,
|
||||
&mut start_func_offset,
|
||||
&mut stop_func_offset,
|
||||
) != 0
|
||||
{
|
||||
module_info.push(ModuleInfo {
|
||||
name: String::from(CStr::from_ptr(raw_name).to_str().unwrap()),
|
||||
version: String::from(CStr::from_ptr(raw_version).to_str().unwrap()),
|
||||
offset,
|
||||
size,
|
||||
start_func_offset,
|
||||
stop_func_offset,
|
||||
});
|
||||
}
|
||||
|
||||
let symbol_array_variant = g_variant_get_child_value(root_variant, 5);
|
||||
let symbol_data_ptr = g_variant_get_data(symbol_array_variant) as *const u8;
|
||||
let symbol_data_size = g_variant_get_size(symbol_array_variant) as usize;
|
||||
let symbol_table = SymbolTable::new(core::slice::from_raw_parts(
|
||||
symbol_data_ptr,
|
||||
symbol_data_size,
|
||||
));
|
||||
|
||||
let own_range_variant = g_variant_get_child_value(root_variant, 0);
|
||||
let mut own_base: u64 = 0;
|
||||
let mut own_size: u64 = 0;
|
||||
g_variant_get(
|
||||
own_range_variant,
|
||||
c"(tt)".as_ptr(),
|
||||
&mut own_base,
|
||||
&mut own_size,
|
||||
);
|
||||
let own_range = GumMemoryRange {
|
||||
base_address: own_base,
|
||||
size: own_size as gsize,
|
||||
};
|
||||
|
||||
g_variant_unref(own_range_variant);
|
||||
g_variant_unref(symbol_array_variant);
|
||||
g_variant_unref(module_info_variant);
|
||||
g_variant_unref(kernel_base_variant);
|
||||
g_variant_unref(root_variant);
|
||||
g_variant_type_free(variant_type);
|
||||
|
||||
(
|
||||
transport_config,
|
||||
kernel_base,
|
||||
module_info,
|
||||
symbol_table,
|
||||
own_range,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[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() -> c_int {
|
||||
kernel::log("frida: agent starting\n\0");
|
||||
|
||||
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");
|
||||
|
||||
init_gum();
|
||||
|
||||
let (own_base, own_size) = kernel::own_range();
|
||||
OWN_RANGE = GumMemoryRange {
|
||||
base_address: own_base,
|
||||
size: own_size as gsize,
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
kernel::log("frida: transport up, entering main loop\n\0");
|
||||
|
||||
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")]
|
||||
pub use entrypoint_xnu::{MODULE_INFO, ModuleInfo, SYMBOL_TABLE, _start};
|
||||
|
||||
pub enum Transport {
|
||||
#[cfg(feature = "xnu")]
|
||||
Virtio(hostlink_virtio::Hostlink),
|
||||
#[cfg(feature = "xnu")]
|
||||
Vsock(hostlink_vsock::Hostlink),
|
||||
#[cfg(feature = "linux")]
|
||||
CharDevice(hostlink_chardev::Hostlink),
|
||||
}
|
||||
|
||||
impl Transport {
|
||||
pub fn send(&self, payload: &[u8]) {
|
||||
match self {
|
||||
#[cfg(feature = "xnu")]
|
||||
Transport::Virtio(h) => h.send(payload),
|
||||
#[cfg(feature = "xnu")]
|
||||
Transport::Vsock(h) => h.send(payload),
|
||||
#[cfg(feature = "linux")]
|
||||
Transport::CharDevice(h) => h.send(payload),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process(&self) {
|
||||
match self {
|
||||
#[cfg(feature = "xnu")]
|
||||
Transport::Virtio(h) => h.process(),
|
||||
#[cfg(feature = "xnu")]
|
||||
Transport::Vsock(h) => h.process(),
|
||||
#[cfg(feature = "linux")]
|
||||
Transport::CharDevice(h) => h.process(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu")]
|
||||
pub enum TransportConfig {
|
||||
Virtio { mmio: u64, irq: u32 },
|
||||
Vsock { host_port: u32 },
|
||||
}
|
||||
|
||||
static mut TRANSPORT_DRIVER: *mut Transport = core::ptr::null_mut();
|
||||
|
||||
#[inline(always)]
|
||||
fn transport_set(driver: Transport) {
|
||||
unsafe {
|
||||
let boxed = Box::into_raw(Box::new(driver));
|
||||
TRANSPORT_DRIVER = boxed;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn transport_get_unchecked() -> &'static Transport {
|
||||
unsafe {
|
||||
debug_assert!(!TRANSPORT_DRIVER.is_null());
|
||||
&*TRANSPORT_DRIVER
|
||||
}
|
||||
}
|
||||
|
||||
// Only the XNU backend has to cope with Gum asking for memory work before the
|
||||
// hostlink that performs it exists.
|
||||
#[cfg(feature = "xnu")]
|
||||
#[inline(always)]
|
||||
fn transport_is_up() -> bool {
|
||||
unsafe { !TRANSPORT_DRIVER.is_null() }
|
||||
}
|
||||
|
||||
static mut SCRIPTS: BTreeMap<u32, *mut GumScript> = BTreeMap::new();
|
||||
static NEXT_SCRIPT_ID: AtomicU32 = AtomicU32::new(1);
|
||||
static mut OWN_RANGE: GumMemoryRange = GumMemoryRange {
|
||||
base_address: 0,
|
||||
size: 0,
|
||||
};
|
||||
|
||||
static NEXT_REQUEST_ID: AtomicU32 = AtomicU32::new(1);
|
||||
static mut PENDING_REPLIES: BTreeMap<u16, *mut GVariant> = BTreeMap::new();
|
||||
|
||||
unsafe fn init_gum() {
|
||||
unsafe {
|
||||
bindings::g_set_panic_handler(Some(frida_panic_handler), ptr::null_mut());
|
||||
bindings::gum_init_embedded();
|
||||
kernel::log("frida: gum_init_embedded done\n\0");
|
||||
bindings::g_log_set_default_handler(Some(frida_log_handler), ptr::null_mut());
|
||||
|
||||
gum_script_scheduler_disable_background_thread(gum_script_backend_get_scheduler());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
fn on_frame_from_host(frame: &[u8]) {
|
||||
if let Some(variant) = deserialize_message(&frame) {
|
||||
process_incoming_message(variant);
|
||||
unsafe {
|
||||
g_variant_unref(variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn adopt_js_context() -> *mut GMainContext {
|
||||
unsafe {
|
||||
let context = gum_script_scheduler_get_js_context(gum_script_backend_get_scheduler());
|
||||
|
||||
// Acquires the context as well, which is what lets this thread run the jobs the
|
||||
// asynchronous script calls queue on it. Being the thread default is what makes
|
||||
// a script created here deliver its messages here too.
|
||||
g_main_context_push_thread_default(context);
|
||||
|
||||
context
|
||||
}
|
||||
}
|
||||
|
||||
const IDLE_SLICE_US: u64 = 50_000;
|
||||
|
||||
fn run_main_loop(main_context: *mut GMainContext) {
|
||||
unsafe {
|
||||
loop {
|
||||
transport_get_unchecked().process();
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
if entrypoint_linux::stop_requested() {
|
||||
return;
|
||||
}
|
||||
|
||||
g_main_context_iteration(main_context, 0);
|
||||
|
||||
kernel::wait(
|
||||
ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8,
|
||||
Some(IDLE_SLICE_US),
|
||||
&mut || false,
|
||||
);
|
||||
|
||||
kernel::yield_now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
fn destroy_all_scripts(main_context: *mut GMainContext) {
|
||||
unsafe {
|
||||
let scripts = core::mem::take(core::ptr::addr_of_mut!(SCRIPTS).as_mut().unwrap());
|
||||
for (_, script) in scripts {
|
||||
UNLOADS_IN_FLIGHT.fetch_add(1, Ordering::Relaxed);
|
||||
gum_script_unload(script, ptr::null_mut(), Some(on_script_unloaded), ptr::null_mut());
|
||||
}
|
||||
|
||||
while UNLOADS_IN_FLIGHT.load(Ordering::Relaxed) != 0 {
|
||||
g_main_context_iteration(main_context, 0);
|
||||
kernel::yield_now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
unsafe extern "C" fn on_script_unloaded(
|
||||
source_object: *mut GObject,
|
||||
result: *mut GAsyncResult,
|
||||
_user_data: gpointer,
|
||||
) {
|
||||
unsafe {
|
||||
let script = source_object as *mut GumScript;
|
||||
|
||||
gum_script_unload_finish(script, result);
|
||||
g_object_unref(script as *mut c_void);
|
||||
|
||||
UNLOADS_IN_FLIGHT.fetch_sub(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
static UNLOADS_IN_FLIGHT: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
fn transport_teardown() {
|
||||
unsafe {
|
||||
let driver = TRANSPORT_DRIVER;
|
||||
TRANSPORT_DRIVER = ptr::null_mut();
|
||||
drop(Box::from_raw(driver));
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn serialize_message(variant: *mut GVariant) -> Option<Vec<u8>> {
|
||||
unsafe {
|
||||
let size = g_variant_get_size(variant) as usize;
|
||||
if size == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let data_ptr = g_variant_get_data(variant) as *const u8;
|
||||
let mut result = Vec::with_capacity(size);
|
||||
result.resize(size, 0);
|
||||
core::ptr::copy_nonoverlapping(data_ptr, result.as_mut_ptr(), size);
|
||||
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_message(data: &[u8]) -> Option<*mut GVariant> {
|
||||
unsafe {
|
||||
if data.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let variant_type = g_variant_type_new(c"(yqv)".as_ptr());
|
||||
let data_copy = g_memdup2(data.as_ptr() as *const c_void, data.len() as u64);
|
||||
let variant = g_variant_new_from_data(
|
||||
variant_type,
|
||||
data_copy,
|
||||
data.len() as u64,
|
||||
0,
|
||||
Some(g_free),
|
||||
data_copy,
|
||||
);
|
||||
g_variant_type_free(variant_type);
|
||||
|
||||
if variant.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(variant)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_incoming_message(variant: *mut GVariant) {
|
||||
{
|
||||
let mut cmd_value: u8 = 0;
|
||||
let mut request_id: u16 = 0;
|
||||
let mut payload_variant: *mut GVariant = ptr::null_mut();
|
||||
|
||||
let cmd = unsafe {
|
||||
g_variant_get(
|
||||
variant,
|
||||
c"(yqv)".as_ptr(),
|
||||
&mut cmd_value,
|
||||
&mut request_id,
|
||||
&mut payload_variant,
|
||||
);
|
||||
|
||||
core::mem::transmute::<u8, FridaCommand>(cmd_value)
|
||||
};
|
||||
|
||||
if cmd == FridaCommand::Reply {
|
||||
unsafe {
|
||||
core::ptr::addr_of_mut!(PENDING_REPLIES)
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.insert(request_id, payload_variant);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let response = match cmd {
|
||||
FridaCommand::CreateScript => handle_create_script(payload_variant, request_id),
|
||||
FridaCommand::LoadScript => handle_load_script(payload_variant, request_id),
|
||||
FridaCommand::DestroyScript => handle_destroy_script(payload_variant, request_id),
|
||||
FridaCommand::PostScriptMessage => Some(handle_post_script_message(payload_variant)),
|
||||
_ => Some(HandlerResponse::error("Unknown command")),
|
||||
};
|
||||
|
||||
if let Some(response) = response {
|
||||
send_command_reply(request_id, response);
|
||||
}
|
||||
|
||||
unsafe { g_variant_unref(payload_variant) };
|
||||
}
|
||||
}
|
||||
|
||||
fn send_command_reply(request_id: u16, response: HandlerResponse) {
|
||||
unsafe {
|
||||
let message = g_variant_new(
|
||||
c"(yqv)".as_ptr(),
|
||||
FridaCommand::Reply as u8 as u32,
|
||||
request_id as u32,
|
||||
response.variant,
|
||||
);
|
||||
|
||||
if let Some(serialized) = serialize_message(message) {
|
||||
transport_get_unchecked().send(&serialized);
|
||||
}
|
||||
|
||||
g_variant_unref(message);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn host_rpc(command: FridaCommand, payload: *mut GVariant) -> *mut GVariant {
|
||||
unsafe {
|
||||
let request_id = NEXT_REQUEST_ID.fetch_add(1, Ordering::Relaxed) as u16;
|
||||
let message = g_variant_new(
|
||||
c"(yqv)".as_ptr(),
|
||||
command as u8 as u32,
|
||||
request_id as u32,
|
||||
payload,
|
||||
);
|
||||
let transport = transport_get_unchecked();
|
||||
transport.send(&serialize_message(message).unwrap());
|
||||
g_variant_unref(message);
|
||||
|
||||
let wait_event = ptr::addr_of_mut!(glib::WAKEUP_TOKEN) as *const u8;
|
||||
loop {
|
||||
let mut reply: Option<*mut GVariant> = None;
|
||||
kernel::wait(wait_event, None, &mut || {
|
||||
transport.process();
|
||||
reply = take_pending_reply(request_id);
|
||||
reply.is_some()
|
||||
});
|
||||
if let Some(reply) = reply {
|
||||
return reply;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn take_pending_reply(request_id: u16) -> Option<*mut GVariant> {
|
||||
unsafe {
|
||||
core::ptr::addr_of_mut!(PENDING_REPLIES)
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.remove(&request_id)
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_create_script(payload_variant: *mut GVariant, request_id: u16) -> Option<HandlerResponse> {
|
||||
unsafe {
|
||||
if g_variant_check_format_string(payload_variant, c"s".as_ptr(), 0) == 0 {
|
||||
return Some(HandlerResponse::error("Invalid payload format: expected string"));
|
||||
}
|
||||
let source = g_variant_get_string(payload_variant, core::ptr::null_mut());
|
||||
|
||||
gum_script_backend_create(
|
||||
gum_script_backend_obtain_qjs(),
|
||||
c"agent.js".as_ptr(),
|
||||
source,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
Some(on_script_created),
|
||||
Box::into_raw(Box::new(request_id)) as *mut c_void,
|
||||
);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_script_created(
|
||||
source_object: *mut GObject,
|
||||
result: *mut GAsyncResult,
|
||||
user_data: gpointer,
|
||||
) {
|
||||
unsafe {
|
||||
let request_id = *Box::from_raw(user_data as *mut u16);
|
||||
|
||||
let mut error: *mut GError = ptr::null_mut();
|
||||
let script = gum_script_backend_create_finish(
|
||||
source_object as *mut GumScriptBackend,
|
||||
result,
|
||||
&mut error,
|
||||
);
|
||||
|
||||
if !error.is_null() {
|
||||
let message = String::from(CStr::from_ptr((*error).message).to_str().unwrap());
|
||||
g_error_free(error);
|
||||
send_command_reply(request_id, HandlerResponse::error(&message));
|
||||
return;
|
||||
}
|
||||
|
||||
exclude_own_range_from_stalker(script);
|
||||
|
||||
let script_id = NEXT_SCRIPT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
gum_script_set_message_handler(
|
||||
script,
|
||||
Some(frida_message_handler),
|
||||
Box::into_raw(Box::new(script_id)) as *mut c_void,
|
||||
None,
|
||||
);
|
||||
|
||||
core::ptr::addr_of_mut!(SCRIPTS)
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.insert(script_id, script);
|
||||
|
||||
send_command_reply(request_id, HandlerResponse::success(g_variant_new_uint32(script_id)));
|
||||
}
|
||||
}
|
||||
|
||||
// Stalker must not instrument our own runtime: when it follows the current
|
||||
// thread into the hostlink transport it would deadlock on the locks that the
|
||||
// RPC it depends on is holding. On XNU the host injected the image, so it tells
|
||||
// us our range as part of the config; on Linux we ask the module loader.
|
||||
unsafe fn exclude_own_range_from_stalker(script: *mut GumScript) {
|
||||
unsafe {
|
||||
gum_stalker_exclude(gum_script_get_stalker(script), core::ptr::addr_of!(OWN_RANGE));
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_message_handler(
|
||||
message: *const gchar,
|
||||
_data: *mut GBytes,
|
||||
user_data: gpointer,
|
||||
) {
|
||||
unsafe {
|
||||
let script_id = *(user_data as *const u32);
|
||||
|
||||
let message_variant = g_variant_new(
|
||||
c"(yqv)".as_ptr(),
|
||||
FridaCommand::ScriptMessage as u8 as u32,
|
||||
0u32,
|
||||
g_variant_new(c"(us)".as_ptr(), script_id, message),
|
||||
);
|
||||
|
||||
if let Some(serialized) = serialize_message(message_variant) {
|
||||
transport_get_unchecked().send(&serialized);
|
||||
}
|
||||
|
||||
g_variant_unref(message_variant);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_load_script(payload_variant: *mut GVariant, request_id: u16) -> Option<HandlerResponse> {
|
||||
unsafe {
|
||||
if g_variant_check_format_string(payload_variant, c"u".as_ptr(), 0) == 0 {
|
||||
return Some(HandlerResponse::error("Invalid payload format: expected uint32"));
|
||||
}
|
||||
let script_id = g_variant_get_uint32(payload_variant);
|
||||
|
||||
let Some(script) = get_script_by_id(script_id) else {
|
||||
return Some(HandlerResponse::error(&format!("Script with ID {} not found", script_id)));
|
||||
};
|
||||
|
||||
gum_script_load(
|
||||
script,
|
||||
ptr::null_mut(),
|
||||
Some(on_script_loaded),
|
||||
Box::into_raw(Box::new(request_id)) as *mut c_void,
|
||||
);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_script_loaded(
|
||||
source_object: *mut GObject,
|
||||
result: *mut GAsyncResult,
|
||||
user_data: gpointer,
|
||||
) {
|
||||
unsafe {
|
||||
let request_id = *Box::from_raw(user_data as *mut u16);
|
||||
|
||||
gum_script_load_finish(source_object as *mut GumScript, result);
|
||||
|
||||
send_command_reply(request_id, HandlerResponse::success_empty());
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_destroy_script(payload_variant: *mut GVariant, request_id: u16) -> Option<HandlerResponse> {
|
||||
unsafe {
|
||||
if g_variant_check_format_string(payload_variant, c"u".as_ptr(), 0) == 0 {
|
||||
return Some(HandlerResponse::error("Invalid payload format: expected uint32"));
|
||||
}
|
||||
let script_id = g_variant_get_uint32(payload_variant);
|
||||
|
||||
let scripts = core::ptr::addr_of_mut!(SCRIPTS).as_mut().unwrap();
|
||||
let Some(script) = scripts.remove(&script_id) else {
|
||||
return Some(HandlerResponse::error(&format!("Script with ID {} not found", script_id)));
|
||||
};
|
||||
|
||||
gum_script_unload(
|
||||
script,
|
||||
ptr::null_mut(),
|
||||
Some(on_script_destroyed),
|
||||
Box::into_raw(Box::new(request_id)) as *mut c_void,
|
||||
);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_script_destroyed(
|
||||
source_object: *mut GObject,
|
||||
result: *mut GAsyncResult,
|
||||
user_data: gpointer,
|
||||
) {
|
||||
unsafe {
|
||||
let request_id = *Box::from_raw(user_data as *mut u16);
|
||||
let script = source_object as *mut GumScript;
|
||||
|
||||
gum_script_unload_finish(script, result);
|
||||
g_object_unref(script as *mut c_void);
|
||||
|
||||
send_command_reply(request_id, HandlerResponse::success_empty());
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_post_script_message(payload_variant: *mut GVariant) -> HandlerResponse {
|
||||
unsafe {
|
||||
if g_variant_check_format_string(payload_variant, c"(us)".as_ptr(), 0) == 0 {
|
||||
return HandlerResponse::error("Invalid payload format: expected (uint32, string)");
|
||||
}
|
||||
let mut script_id: u32 = 0;
|
||||
let mut message: *const gchar = ptr::null();
|
||||
g_variant_get(
|
||||
payload_variant,
|
||||
c"(u&s)".as_ptr(),
|
||||
&mut script_id,
|
||||
&mut message,
|
||||
);
|
||||
|
||||
let Some(script) = get_script_by_id(script_id) else {
|
||||
return HandlerResponse::error(&format!("Script with ID {} not found", script_id));
|
||||
};
|
||||
|
||||
gum_script_post(script, message, ptr::null_mut());
|
||||
|
||||
HandlerResponse::success_empty()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn get_script_by_id(script_id: u32) -> Option<*mut GumScript> {
|
||||
unsafe {
|
||||
let scripts = core::ptr::addr_of_mut!(SCRIPTS).as_mut().unwrap();
|
||||
scripts.get(&script_id).copied()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_panic_handler(message: *const u8, _user_data: *mut c_void) {
|
||||
let msg = unsafe { CStr::from_ptr(message).to_str().unwrap() };
|
||||
panic!("[Frida] {}", msg);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_log_handler(
|
||||
_log_domain: *const core::ffi::c_char,
|
||||
_log_level: i32,
|
||||
message: *const core::ffi::c_char,
|
||||
_user_data: *mut c_void,
|
||||
) {
|
||||
let msg = unsafe { CStr::from_ptr(message).to_str().unwrap() };
|
||||
kprintln!("[Frida] {}", msg);
|
||||
}
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(info: &core::panic::PanicInfo<'_>) -> ! {
|
||||
let mut s = format!("{}", info);
|
||||
s.push('\0');
|
||||
kernel::panic(s.as_str());
|
||||
#[allow(unreachable_code)]
|
||||
loop {}
|
||||
}
|
||||
|
||||
pub struct KernelAllocator;
|
||||
|
||||
unsafe impl GlobalAlloc for KernelAllocator {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
kernel::alloc(layout.size())
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||
kernel::free(ptr, layout.size());
|
||||
}
|
||||
}
|
||||
|
||||
#[global_allocator]
|
||||
static GLOBAL: KernelAllocator = KernelAllocator;
|
||||
extern crate alloc;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! kprintln {
|
||||
($($arg:tt)*) => {{
|
||||
use core::fmt::Write;
|
||||
let mut buf = alloc::string::String::new();
|
||||
write!(&mut buf, $($arg)*).unwrap();
|
||||
buf.push('\n');
|
||||
buf.push('\0');
|
||||
$crate::kernel::log(&buf)
|
||||
}};
|
||||
}
|
||||
+540
-83
@@ -1,35 +1,64 @@
|
||||
// The C library's lower edge. Nothing here does real work: the agent has no
|
||||
// filesystem and never exits, so these exist to satisfy references and to give
|
||||
// malloc some memory to hand out.
|
||||
//
|
||||
// The two flavours link against different C libraries, and they disagree on what
|
||||
// this layer is called. newlib asks for the underscore-prefixed syscall names;
|
||||
// picolibc, which the soft-float flavour needs because newlib has no soft-float
|
||||
// build for arm64, calls them without the prefix. The bodies are shared and the
|
||||
// exported names live in the two `exports` modules at the end.
|
||||
|
||||
use core::ptr;
|
||||
use core::sync::atomic::Ordering;
|
||||
|
||||
use crate::bindings::{GMutex, GRecMutex};
|
||||
use crate::gthread;
|
||||
|
||||
static mut MALLOC_LOCK: GRecMutex = unsafe { core::mem::zeroed() };
|
||||
// Serialises the C library. Which hooks the library reaches for differs: newlib asks
|
||||
// for __malloc_lock/__malloc_unlock, picolibc for the retargetable lock API below,
|
||||
// whose default implementation is an empty function — so on picolibc malloc runs
|
||||
// unlocked unless we supply these, and any second thread corrupts the heap.
|
||||
static mut LIBC_LOCK: GRecMutex = unsafe { core::mem::zeroed() };
|
||||
|
||||
fn libc_lock() {
|
||||
gthread::g_rec_mutex_lock(ptr::addr_of_mut!(LIBC_LOCK));
|
||||
}
|
||||
|
||||
fn libc_unlock() {
|
||||
gthread::g_rec_mutex_unlock(ptr::addr_of_mut!(LIBC_LOCK));
|
||||
offer_preemption_point();
|
||||
}
|
||||
|
||||
fn offer_preemption_point() {
|
||||
let total = ALLOCATIONS_TOTAL.fetch_add(1, Ordering::Relaxed) + 1;
|
||||
if total % ALLOCATIONS_PER_YIELD == 0 {
|
||||
crate::kernel::yield_now();
|
||||
}
|
||||
}
|
||||
|
||||
static ALLOCATIONS_TOTAL: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
|
||||
const ALLOCATIONS_PER_YIELD: u32 = 1024;
|
||||
|
||||
// GLib's g_get_num_processors() probes this; report a single CPU so the
|
||||
// parallel scanners fall back to their inline path.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __malloc_lock(_reent: *mut core::ffi::c_void) {
|
||||
gthread::g_rec_mutex_lock(ptr::addr_of_mut!(MALLOC_LOCK));
|
||||
pub extern "C" fn sysconf(_name: i32) -> isize {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __malloc_unlock(_reent: *mut core::ffi::c_void) {
|
||||
gthread::g_rec_mutex_unlock(ptr::addr_of_mut!(MALLOC_LOCK));
|
||||
pub extern "C" fn __clear_cache(_start: *const u8, _end: *const u8) {
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"ic iallu", // Invalidate all instruction caches
|
||||
"dsb sy", // Data synchronization barrier
|
||||
"isb", // Instruction synchronization barrier
|
||||
options(nomem, nostack),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _fini() {
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _exit(_status: i32) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _kill(_pid: i32, _sig: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _sbrk(incr: isize) -> *mut u8 {
|
||||
fn sbrk_impl(incr: isize) -> *mut u8 {
|
||||
const HEAP_SIZE: usize = 32 * 1024 * 1024; // 32 MB
|
||||
|
||||
static mut HEAP_START: *mut u8 = ptr::null_mut();
|
||||
@@ -40,7 +69,7 @@ pub extern "C" fn _sbrk(incr: isize) -> *mut u8 {
|
||||
gthread::g_mutex_lock(ptr::addr_of_mut!(HEAP_MUTEX));
|
||||
|
||||
if HEAP_START.is_null() {
|
||||
HEAP_START = crate::xnu::kalloc(HEAP_SIZE);
|
||||
HEAP_START = crate::kernel::alloc(HEAP_SIZE);
|
||||
if HEAP_START.is_null() {
|
||||
gthread::g_mutex_unlock(ptr::addr_of_mut!(HEAP_MUTEX));
|
||||
panic!("Failed to allocate 32 MB heap");
|
||||
@@ -49,7 +78,8 @@ pub extern "C" fn _sbrk(incr: isize) -> *mut u8 {
|
||||
}
|
||||
|
||||
let prev_heap_current = HEAP_CURRENT;
|
||||
let new_heap_current = HEAP_CURRENT.add(incr as usize);
|
||||
// Signed: the allocator hands memory back by asking for a negative increment.
|
||||
let new_heap_current = HEAP_CURRENT.offset(incr);
|
||||
|
||||
if new_heap_current > HEAP_START.add(HEAP_SIZE) {
|
||||
gthread::g_mutex_unlock(ptr::addr_of_mut!(HEAP_MUTEX));
|
||||
@@ -65,20 +95,14 @@ pub extern "C" fn _sbrk(incr: isize) -> *mut u8 {
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _getpid() -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct Timeval {
|
||||
tv_sec: i64,
|
||||
tv_usec: i64,
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _gettimeofday(tp: *mut core::ffi::c_void, _tzp: *mut core::ffi::c_void) -> i32 {
|
||||
let (secs, microsecs) = crate::xnu::clock_get_calendar_microtime();
|
||||
fn gettimeofday_impl(tp: *mut core::ffi::c_void) -> i32 {
|
||||
let (secs, microsecs) = crate::kernel::wall_clock_micros();
|
||||
let timeval = Timeval {
|
||||
tv_sec: secs as i64,
|
||||
tv_usec: microsecs as i64,
|
||||
@@ -93,61 +117,494 @@ pub extern "C" fn _gettimeofday(tp: *mut core::ffi::c_void, _tzp: *mut core::ffi
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _isatty(_fd: i32) -> i32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _open(_path: *const u8, _flags: i32, _mode: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _close(_fd: i32) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _read(_fd: i32, _buf: *mut u8, _count: usize) -> isize {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _write(_fd: i32, _buf: *const u8, count: usize) -> isize {
|
||||
fn write_impl(count: usize) -> isize {
|
||||
count as isize
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _lseek(_fd: i32, _offset: isize, _whence: i32) -> isize {
|
||||
0
|
||||
}
|
||||
#[cfg(feature = "xnu")]
|
||||
mod exports {
|
||||
use super::*;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _fstat(_fd: i32, _st: *mut core::ffi::c_void) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _link(_old: *const u8, _new: *const u8) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _unlink(_path: *const u8) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __clear_cache(_start: *const u8, _end: *const u8) {
|
||||
// On AArch64, we need to flush the instruction cache
|
||||
// This is a simplified implementation for the kernel environment
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"ic iallu", // Invalidate all instruction caches
|
||||
"dsb sy", // Data synchronization barrier
|
||||
"isb", // Instruction synchronization barrier
|
||||
options(nomem, nostack),
|
||||
);
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __malloc_lock(_reent: *mut core::ffi::c_void) {
|
||||
libc_lock();
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __malloc_unlock(_reent: *mut core::ffi::c_void) {
|
||||
libc_unlock();
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _fini() {}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _exit(_status: i32) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _kill(_pid: i32, _sig: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _sbrk(incr: isize) -> *mut u8 {
|
||||
sbrk_impl(incr)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _getpid() -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _gettimeofday(
|
||||
tp: *mut core::ffi::c_void,
|
||||
_tzp: *mut core::ffi::c_void,
|
||||
) -> i32 {
|
||||
gettimeofday_impl(tp)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _isatty(_fd: i32) -> i32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _open(_path: *const u8, _flags: i32, _mode: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _close(_fd: i32) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _read(_fd: i32, _buf: *mut u8, _count: usize) -> isize {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _write(_fd: i32, _buf: *const u8, count: usize) -> isize {
|
||||
write_impl(count)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _lseek(_fd: i32, _offset: isize, _whence: i32) -> isize {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _fstat(_fd: i32, _st: *mut core::ffi::c_void) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _stat(_path: *const u8, _st: *mut core::ffi::c_void) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _link(_old: *const u8, _new: *const u8) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _unlink(_path: *const u8) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn creat(_path: *const u8, _mode: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn access(_path: *const u8, _mode: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn chmod(_path: *const u8, _mode: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn chdir(_path: *const u8) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn rmdir(_path: *const u8) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gethostname(_name: *mut u8, _len: usize) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
// newlib wires these up for exit(), which we never reach: the agent runs until
|
||||
// the kernel goes down or the module is unloaded.
|
||||
#[unsafe(no_mangle)]
|
||||
pub static __libc_fini: Option<extern "C" fn()> = None;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static __fini_array_start: [extern "C" fn(); 0] = [];
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static __fini_array_end: [extern "C" fn(); 0] = [];
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
mod exports {
|
||||
use super::*;
|
||||
|
||||
// The C library's own allocator sits on a fixed sbrk heap and carries its own
|
||||
// locking; inside a kernel module the kernel's allocator is already there, already
|
||||
// safe to share between threads, and not capped at a size we picked up front.
|
||||
// Every C allocation goes here instead.
|
||||
//
|
||||
// The sixteen bytes before the returned pointer hold the block the kernel gave us
|
||||
// and its total size, so a pointer that was moved forward to satisfy an alignment
|
||||
// request frees exactly like one that was not.
|
||||
use core::ffi::c_void;
|
||||
|
||||
const HEAP_HEADER_SIZE: usize = 16;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn malloc(size: usize) -> *mut c_void {
|
||||
let total = size + HEAP_HEADER_SIZE;
|
||||
let block = crate::kernel::alloc(total);
|
||||
if block.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
offer_preemption_point();
|
||||
|
||||
unsafe { attach_header(block, total, block.add(HEAP_HEADER_SIZE)) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn posix_memalign(
|
||||
result: *mut *mut c_void,
|
||||
alignment: usize,
|
||||
size: usize,
|
||||
) -> i32 {
|
||||
let total = size + alignment + HEAP_HEADER_SIZE;
|
||||
let block = crate::kernel::alloc(total);
|
||||
if block.is_null() {
|
||||
return 12; // ENOMEM
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let lowest = block.add(HEAP_HEADER_SIZE) as usize;
|
||||
let aligned = (lowest + alignment - 1) & !(alignment - 1);
|
||||
*result = attach_header(block, total, aligned as *mut u8);
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn free(p: *mut c_void) {
|
||||
if p.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let (block, total) = header_of(p);
|
||||
crate::kernel::free(block, total);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn malloc_usable_size(p: *mut c_void) -> usize {
|
||||
unsafe { usable_size(p) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn calloc(count: usize, size: usize) -> *mut c_void {
|
||||
let total = count * size;
|
||||
let p = malloc(total);
|
||||
if !p.is_null() {
|
||||
unsafe { ptr::write_bytes(p as *mut u8, 0, total) };
|
||||
}
|
||||
p
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn realloc(p: *mut c_void, size: usize) -> *mut c_void {
|
||||
if p.is_null() {
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
let previous_size = unsafe { usable_size(p) };
|
||||
let grown = malloc(size);
|
||||
if !grown.is_null() {
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(p as *const u8, grown as *mut u8, previous_size.min(size))
|
||||
};
|
||||
free(p);
|
||||
}
|
||||
grown
|
||||
}
|
||||
|
||||
unsafe fn attach_header(block: *mut u8, total: usize, p: *mut u8) -> *mut c_void {
|
||||
unsafe {
|
||||
let header = p.sub(HEAP_HEADER_SIZE) as *mut usize;
|
||||
*header = block as usize;
|
||||
*header.add(1) = total;
|
||||
}
|
||||
p as *mut c_void
|
||||
}
|
||||
|
||||
unsafe fn header_of(p: *mut c_void) -> (*mut u8, usize) {
|
||||
unsafe {
|
||||
let header = (p as *mut u8).sub(HEAP_HEADER_SIZE) as *const usize;
|
||||
(*header as *mut u8, *header.add(1))
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn usable_size(p: *mut c_void) -> usize {
|
||||
unsafe {
|
||||
let (block, total) = header_of(p);
|
||||
total - (p as usize - block as usize)
|
||||
}
|
||||
}
|
||||
|
||||
// One recursive mutex covers every lock the C library asks for. They are coarse
|
||||
// and few, and over-serialising them is safe where getting it wrong is not.
|
||||
type Lock = *mut core::ffi::c_void;
|
||||
|
||||
// The lock object itself, which we define so the C library's own no-op lock
|
||||
// implementation is never pulled out of its archive. Its contents are never
|
||||
// read: the address is just a token, and one mutex covers them all.
|
||||
// Nothing here references it, so without #[used] it is dropped before the linker
|
||||
// ever sees it, and the archive member arrives after all to supply it.
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[unsafe(no_mangle)]
|
||||
#[used]
|
||||
pub static mut __lock___libc_recursive_mutex: u8 = 0;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __retarget_lock_init(_lock: *mut Lock) {}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __retarget_lock_init_recursive(_lock: *mut Lock) {}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __retarget_lock_close(_lock: Lock) {}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __retarget_lock_close_recursive(_lock: Lock) {}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __retarget_lock_acquire(_lock: Lock) {
|
||||
libc_lock();
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __retarget_lock_acquire_recursive(_lock: Lock) {
|
||||
libc_lock();
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __retarget_lock_release(_lock: Lock) {
|
||||
libc_unlock();
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __retarget_lock_release_recursive(_lock: Lock) {
|
||||
libc_unlock();
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn exit(_status: i32) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _exit(_status: i32) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn kill(_pid: i32, _sig: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn sbrk(incr: isize) -> *mut u8 {
|
||||
sbrk_impl(incr)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn getpid() -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gettimeofday(
|
||||
tp: *mut core::ffi::c_void,
|
||||
_tzp: *mut core::ffi::c_void,
|
||||
) -> i32 {
|
||||
gettimeofday_impl(tp)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn isatty(_fd: i32) -> i32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn open(_path: *const u8, _flags: i32, _mode: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn close(_fd: i32) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn read(_fd: i32, _buf: *mut u8, _count: usize) -> isize {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn write(_fd: i32, _buf: *const u8, count: usize) -> isize {
|
||||
write_impl(count)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn lseek(_fd: i32, _offset: isize, _whence: i32) -> isize {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn fstat(_fd: i32, _st: *mut core::ffi::c_void) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn stat(_path: *const u8, _st: *mut core::ffi::c_void) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn unlink(_path: *const u8) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn rename(_old: *const u8, _new: *const u8) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn access(_path: *const u8, _mode: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn chmod(_path: *const u8, _mode: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn chdir(_path: *const u8) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn rmdir(_path: *const u8) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gethostname(_name: *mut u8, _len: usize) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Timespec {
|
||||
tv_sec: i64,
|
||||
tv_nsec: i64,
|
||||
}
|
||||
|
||||
const CLOCK_MONOTONIC: i32 = 1;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn clock_gettime(clock_id: i32, tp: *mut Timespec) -> i32 {
|
||||
let (secs, nsecs) = if clock_id == CLOCK_MONOTONIC {
|
||||
let micros = crate::kernel::monotonic_micros();
|
||||
(micros / 1_000_000, (micros % 1_000_000) * 1000)
|
||||
} else {
|
||||
let (secs, micros) = crate::kernel::wall_clock_micros();
|
||||
(secs as i64, (micros as i64) * 1000)
|
||||
};
|
||||
|
||||
unsafe {
|
||||
(*tp).tv_sec = secs;
|
||||
(*tp).tv_nsec = nsecs;
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
// picolibc consults this for errno values it does not know itself.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _user_strerror(_errnum: i32) -> *const core::ffi::c_char {
|
||||
ptr::null()
|
||||
}
|
||||
|
||||
// No dynamic loader here. Gum calls dladdr() to describe an address and already
|
||||
// falls back to printing it raw when the lookup fails.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn dladdr(_address: *const core::ffi::c_void, _info: *mut core::ffi::c_void) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn dlopen(_file: *const u8, _mode: i32) -> *mut core::ffi::c_void {
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn dlsym(
|
||||
_handle: *mut core::ffi::c_void,
|
||||
_name: *const u8,
|
||||
) -> *mut core::ffi::c_void {
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn dlclose(_handle: *mut core::ffi::c_void) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn dlerror() -> *mut core::ffi::c_char {
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
// picolibc declares the standard streams and leaves defining them to the
|
||||
// application. The agent has no I/O — its logging goes to the kernel's own log —
|
||||
// so nothing should ever reach for one of these.
|
||||
#[repr(transparent)]
|
||||
pub struct Stream(*mut core::ffi::c_void);
|
||||
|
||||
unsafe impl Sync for Stream {}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static stdin: Stream = Stream(ptr::null_mut());
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static stdout: Stream = Stream(ptr::null_mut());
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static stderr: Stream = Stream(ptr::null_mut());
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
// Linux backend. Unlike XNU — where a remote stub injects the agent and patches
|
||||
// resolved kernel addresses into .kernel_addrs — here the agent is loaded from
|
||||
// the inside as a kernel module, so every primitive is an ordinary call into
|
||||
// linux/frida-kmod.c. That shim is compiled by kbuild against the target
|
||||
// kernel's headers, which is what keeps us out of the business of guessing
|
||||
// struct layouts, config-dependent macros and per-version symbol names.
|
||||
|
||||
use core::ffi::{CStr, c_char, c_int, c_void};
|
||||
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::kernel::ThreadEntry;
|
||||
|
||||
pub fn log(msg: &str) {
|
||||
unsafe { frida_kmod_log(msg.as_ptr() as *const c_char) };
|
||||
}
|
||||
|
||||
pub fn panic(msg: &str) -> ! {
|
||||
unsafe { frida_kmod_panic(msg.as_ptr() as *const c_char) }
|
||||
}
|
||||
|
||||
pub fn spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> isize {
|
||||
unsafe { frida_kmod_spawn_thread(entry, parameter) as isize }
|
||||
}
|
||||
|
||||
pub fn alloc(size: usize) -> *mut u8 {
|
||||
unsafe { frida_kmod_alloc(size) }
|
||||
}
|
||||
|
||||
pub fn free(ptr: *mut u8, size: usize) {
|
||||
unsafe { frida_kmod_free(ptr, size) };
|
||||
}
|
||||
|
||||
pub fn alloc_code(size: usize) -> *mut u8 {
|
||||
unsafe { frida_kmod_alloc_code(size) }
|
||||
}
|
||||
|
||||
pub fn free_code(ptr: *mut u8, size: usize) {
|
||||
unsafe { frida_kmod_free_code(ptr, size) };
|
||||
}
|
||||
|
||||
pub fn own_range() -> (u64, u64) {
|
||||
let mut base: u64 = 0;
|
||||
let mut size: u64 = 0;
|
||||
unsafe { frida_kmod_own_range(&mut base, &mut size) };
|
||||
(base, size)
|
||||
}
|
||||
|
||||
// The sequence number is taken before `check` runs, so a wakeup racing with the
|
||||
// condition we are about to observe still makes the commit return immediately.
|
||||
pub fn wait(_token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut() -> bool) {
|
||||
let seq = unsafe { frida_kmod_wait_prepare() };
|
||||
|
||||
if check() {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe { frida_kmod_wait_commit(seq, timeout_us.map_or(-1, |us| us as i64)) };
|
||||
}
|
||||
|
||||
pub fn wake(_token: *const u8) {
|
||||
unsafe { frida_kmod_wake() };
|
||||
}
|
||||
|
||||
/// Gives the scheduler a chance to run something else. A kernel thread that loops
|
||||
/// without doing this is a hard lockup waiting to happen.
|
||||
pub fn yield_now() {
|
||||
unsafe { frida_kmod_yield() };
|
||||
}
|
||||
|
||||
pub fn monotonic_micros() -> i64 {
|
||||
unsafe { frida_kmod_monotonic_micros() }
|
||||
}
|
||||
|
||||
pub fn wall_clock_micros() -> (u32, u32) {
|
||||
let mut secs: u32 = 0;
|
||||
let mut micros: u32 = 0;
|
||||
unsafe { frida_kmod_wall_clock_micros(&mut secs, &mut micros) };
|
||||
(secs, micros)
|
||||
}
|
||||
|
||||
pub fn current_thread_id() -> u64 {
|
||||
unsafe { frida_kmod_current_thread_id() }
|
||||
}
|
||||
|
||||
pub fn protect(address: u64, size: usize, gum_prot: u32) -> bool {
|
||||
unsafe { frida_kmod_protect(address, size, gum_prot) != 0 }
|
||||
}
|
||||
|
||||
pub fn remap_writable(first_page: u64, n_pages: u32) -> *mut c_void {
|
||||
unsafe { frida_kmod_remap_writable(first_page, n_pages) }
|
||||
}
|
||||
|
||||
/// Tears down an alias handed out by [`remap_writable`] and flushes the icache
|
||||
/// over the range it aliased; the shim remembers which range that was.
|
||||
pub fn unmap_writable(mapping: *mut c_void) {
|
||||
unsafe { frida_kmod_unmap_writable(mapping) };
|
||||
}
|
||||
|
||||
pub fn get_kernel_base() -> u64 {
|
||||
unsafe { frida_kmod_kernel_base() }
|
||||
}
|
||||
|
||||
pub fn get_kernel_size() -> u64 {
|
||||
unsafe { frida_kmod_kernel_size() }
|
||||
}
|
||||
|
||||
pub fn enumerate_modules() -> Vec<LoadedModule> {
|
||||
unsafe extern "C" fn on_module(
|
||||
name: *const c_char,
|
||||
version: *const c_char,
|
||||
base: u64,
|
||||
size: u64,
|
||||
user_data: *mut c_void,
|
||||
) -> c_int {
|
||||
unsafe {
|
||||
let modules = &mut *(user_data as *mut Vec<LoadedModule>);
|
||||
modules.push(LoadedModule {
|
||||
name: String::from(CStr::from_ptr(name).to_str().unwrap_or("?")),
|
||||
version: String::from(CStr::from_ptr(version).to_str().unwrap_or("")),
|
||||
base,
|
||||
size,
|
||||
});
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
let mut modules: Vec<LoadedModule> = Vec::new();
|
||||
unsafe {
|
||||
frida_kmod_enumerate_modules(on_module, &mut modules as *mut _ as *mut c_void);
|
||||
}
|
||||
modules
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LoadedModule {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub base: u64,
|
||||
pub size: u64,
|
||||
}
|
||||
|
||||
pub fn find_symbol(name: &CStr) -> u64 {
|
||||
unsafe { frida_kmod_find_symbol(name.as_ptr()) }
|
||||
}
|
||||
|
||||
/// Resolves `address` to `(name, symbol_address)`, or `None` when it falls
|
||||
/// outside every known symbol.
|
||||
pub fn symbol_from_address(address: u64) -> Option<(String, u64)> {
|
||||
const NAME_CAPACITY: usize = 256;
|
||||
|
||||
let mut buffer = [0u8; NAME_CAPACITY];
|
||||
let mut symbol_address: u64 = 0;
|
||||
let found = unsafe {
|
||||
frida_kmod_symbol_name_from_address(
|
||||
address,
|
||||
buffer.as_mut_ptr() as *mut c_char,
|
||||
NAME_CAPACITY,
|
||||
&mut symbol_address,
|
||||
)
|
||||
};
|
||||
if found == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = CStr::from_bytes_until_nul(&buffer).unwrap();
|
||||
Some((String::from(name.to_str().unwrap()), symbol_address))
|
||||
}
|
||||
|
||||
/// Visits every symbol the running kernel knows about. Returns false when
|
||||
/// kallsyms is unavailable, which is the case on kernels built without
|
||||
/// CONFIG_KALLSYMS or when the shim could not resolve its entry points.
|
||||
pub fn enumerate_symbols(callback: &mut dyn FnMut(&CStr, u64) -> bool) -> bool {
|
||||
unsafe extern "C" fn on_symbol(
|
||||
name: *const c_char,
|
||||
address: u64,
|
||||
user_data: *mut c_void,
|
||||
) -> c_int {
|
||||
unsafe {
|
||||
let callback = &mut *(user_data as *mut &mut dyn FnMut(&CStr, u64) -> bool);
|
||||
if callback(CStr::from_ptr(name), address) { 1 } else { 0 }
|
||||
}
|
||||
}
|
||||
|
||||
let mut boxed: &mut dyn FnMut(&CStr, u64) -> bool = callback;
|
||||
unsafe { frida_kmod_enumerate_symbols(on_symbol, &mut boxed as *mut _ as *mut c_void) != 0 }
|
||||
}
|
||||
|
||||
type FoundSymbolFunc =
|
||||
unsafe extern "C" fn(name: *const c_char, address: u64, user_data: *mut c_void) -> c_int;
|
||||
type FoundModuleFunc = unsafe extern "C" fn(
|
||||
name: *const c_char,
|
||||
version: *const c_char,
|
||||
base: u64,
|
||||
size: u64,
|
||||
user_data: *mut c_void,
|
||||
) -> c_int;
|
||||
|
||||
unsafe extern "C" {
|
||||
fn frida_kmod_log(message: *const c_char);
|
||||
fn frida_kmod_panic(message: *const c_char) -> !;
|
||||
fn frida_kmod_spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> c_int;
|
||||
fn frida_kmod_alloc(size: usize) -> *mut u8;
|
||||
fn frida_kmod_free(ptr: *mut u8, size: usize);
|
||||
fn frida_kmod_alloc_code(size: usize) -> *mut u8;
|
||||
fn frida_kmod_free_code(ptr: *mut u8, size: usize);
|
||||
fn frida_kmod_own_range(base: *mut u64, size: *mut u64);
|
||||
fn frida_kmod_wait_prepare() -> u32;
|
||||
fn frida_kmod_wait_commit(seq: u32, timeout_us: i64);
|
||||
fn frida_kmod_wake();
|
||||
fn frida_kmod_yield();
|
||||
fn frida_kmod_monotonic_micros() -> i64;
|
||||
fn frida_kmod_wall_clock_micros(secs: *mut u32, micros: *mut u32);
|
||||
fn frida_kmod_current_thread_id() -> u64;
|
||||
fn frida_kmod_protect(address: u64, size: usize, gum_prot: u32) -> c_int;
|
||||
fn frida_kmod_remap_writable(first_page: u64, n_pages: u32) -> *mut c_void;
|
||||
fn frida_kmod_unmap_writable(mapping: *mut c_void);
|
||||
fn frida_kmod_kernel_base() -> u64;
|
||||
fn frida_kmod_kernel_size() -> u64;
|
||||
fn frida_kmod_enumerate_modules(func: FoundModuleFunc, user_data: *mut c_void);
|
||||
fn frida_kmod_find_symbol(name: *const c_char) -> u64;
|
||||
fn frida_kmod_symbol_name_from_address(
|
||||
address: u64,
|
||||
buffer: *mut c_char,
|
||||
size: usize,
|
||||
symbol_address: *mut u64,
|
||||
) -> c_int;
|
||||
fn frida_kmod_enumerate_symbols(func: FoundSymbolFunc, user_data: *mut c_void) -> c_int;
|
||||
}
|
||||
+10
-542
@@ -1,544 +1,12 @@
|
||||
#![no_main]
|
||||
// The XNU flavour ships as a freestanding ELF executable: the host injects the
|
||||
// image and calls its `_start`. All of the agent lives in the library crate;
|
||||
// this target exists only to produce that link. build.rs passes `--undefined
|
||||
// _start` so the entrypoint survives --gc-sections.
|
||||
//
|
||||
// The Linux flavour is built as a static library instead and linked into a
|
||||
// kernel module by kbuild — see linux/Kbuild.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use core::alloc::{GlobalAlloc, Layout};
|
||||
use core::ffi::{CStr, c_void};
|
||||
use core::ptr;
|
||||
use core::ptr::null_mut;
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use bindings::{
|
||||
GBytes, GCancellable, GError, GVariant, GVariantIter, GumScript, g_error_free, g_free,
|
||||
g_main_context_default, g_main_context_iteration, g_memdup2, g_object_unref,
|
||||
g_variant_check_format_string, g_variant_get, g_variant_get_child_value, g_variant_get_data,
|
||||
g_variant_get_size, g_variant_get_string, g_variant_get_uint32, g_variant_get_uint64,
|
||||
g_variant_iter_init, g_variant_iter_next, g_variant_new, g_variant_new_from_data,
|
||||
g_variant_new_string, g_variant_new_tuple, g_variant_new_uint32, g_variant_type_free,
|
||||
g_variant_type_new, g_variant_unref, gchar, gpointer, gsize, gum_script_backend_create_sync,
|
||||
gum_script_backend_obtain_qjs, gum_script_load_sync, gum_script_post,
|
||||
gum_script_set_message_handler, gum_script_unload_sync,
|
||||
};
|
||||
use hostlink_virtio::Hostlink;
|
||||
use symbols::SymbolTable;
|
||||
|
||||
mod glib;
|
||||
mod gthread;
|
||||
mod gum;
|
||||
mod hostlink_virtio;
|
||||
|
||||
mod libc;
|
||||
mod pac;
|
||||
mod symbols;
|
||||
mod xnu;
|
||||
|
||||
mod bindings {
|
||||
#![allow(
|
||||
dead_code,
|
||||
improper_ctypes,
|
||||
non_camel_case_types,
|
||||
non_snake_case,
|
||||
non_upper_case_globals,
|
||||
unused_imports
|
||||
)]
|
||||
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
|
||||
}
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum FridaCommand {
|
||||
CreateScript = 1,
|
||||
LoadScript = 2,
|
||||
DestroyScript = 3,
|
||||
PostScriptMessage = 4,
|
||||
|
||||
Reply = 128,
|
||||
ScriptMessage = 129,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for FridaCommand {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
FridaCommand::CreateScript => write!(f, "CreateScript"),
|
||||
FridaCommand::LoadScript => write!(f, "LoadScript"),
|
||||
FridaCommand::DestroyScript => write!(f, "DestroyScript"),
|
||||
FridaCommand::PostScriptMessage => write!(f, "PostScriptMessage"),
|
||||
FridaCommand::Reply => write!(f, "Reply"),
|
||||
FridaCommand::ScriptMessage => write!(f, "ScriptMessage"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct HandlerResponse {
|
||||
variant: *mut GVariant,
|
||||
}
|
||||
|
||||
impl HandlerResponse {
|
||||
fn success(variant: *mut GVariant) -> Self {
|
||||
Self { variant }
|
||||
}
|
||||
|
||||
fn success_empty() -> Self {
|
||||
let variant = unsafe { g_variant_new_tuple(ptr::null(), 0) };
|
||||
Self { variant }
|
||||
}
|
||||
|
||||
fn error(message: &str) -> Self {
|
||||
let mut c_message = String::from(message);
|
||||
c_message.push('\0');
|
||||
let error_variant = unsafe { g_variant_new_string(c_message.as_ptr()) };
|
||||
|
||||
Self {
|
||||
variant: error_variant,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static mut CONFIG_DATA: &'static [u8] = &[];
|
||||
pub static mut MODULE_INFO: Vec<ModuleInfo> = Vec::new();
|
||||
pub static mut SYMBOL_TABLE: SymbolTable = SymbolTable::empty();
|
||||
|
||||
static mut TRANSPORT_DRIVER: *mut Hostlink = core::ptr::null_mut();
|
||||
|
||||
#[inline(always)]
|
||||
fn transport_set(driver: Hostlink) {
|
||||
unsafe {
|
||||
let boxed = Box::into_raw(Box::new(driver));
|
||||
TRANSPORT_DRIVER = boxed;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn transport_get_unchecked() -> &'static Hostlink {
|
||||
unsafe {
|
||||
debug_assert!(!TRANSPORT_DRIVER.is_null());
|
||||
&*TRANSPORT_DRIVER
|
||||
}
|
||||
}
|
||||
|
||||
static mut SCRIPTS: BTreeMap<u32, *mut GumScript> = BTreeMap::new();
|
||||
static NEXT_SCRIPT_ID: AtomicU32 = AtomicU32::new(1);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ModuleInfo {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub offset: u32,
|
||||
pub size: u32,
|
||||
pub start_func_offset: u32,
|
||||
pub stop_func_offset: u32,
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn _start(config_data: *const u8, config_size: usize) {
|
||||
unsafe {
|
||||
CONFIG_DATA = core::slice::from_raw_parts(config_data, config_size);
|
||||
|
||||
xnu::kernel_thread_start(frida_agent_worker, 12345usize as *mut core::ffi::c_void);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_agent_worker(_parameter: *mut core::ffi::c_void, _wait_result: i32) {
|
||||
unsafe {
|
||||
bindings::g_set_panic_handler(Some(frida_panic_handler), ptr::null_mut());
|
||||
bindings::gum_init_embedded();
|
||||
bindings::g_log_set_default_handler(Some(frida_log_handler), ptr::null_mut());
|
||||
|
||||
let (mmio, irq, kernel_base, module_info, symbol_table) =
|
||||
parse_config(core::ptr::addr_of!(CONFIG_DATA).read());
|
||||
xnu::set_kernel_base(kernel_base);
|
||||
MODULE_INFO = module_info;
|
||||
SYMBOL_TABLE = symbol_table;
|
||||
|
||||
transport_set(
|
||||
Hostlink::init(
|
||||
mmio,
|
||||
irq,
|
||||
Some(on_frame_from_host),
|
||||
ptr::addr_of_mut!(glib::WAKEUP_TOKEN) as *const u8,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
let main_context = g_main_context_default();
|
||||
|
||||
loop {
|
||||
transport_get_unchecked().process();
|
||||
g_main_context_iteration(main_context, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_frame_from_host(frame: &[u8]) {
|
||||
if let Some(variant) = deserialize_message(&frame) {
|
||||
process_incoming_message(variant);
|
||||
unsafe {
|
||||
g_variant_unref(variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn parse_config(config: &[u8]) -> (u64, u32, u64, Vec<ModuleInfo>, SymbolTable) {
|
||||
unsafe {
|
||||
let type_string = c"(tuta(ssuuuu)ay)".as_ptr() as *const gchar;
|
||||
let variant_type = g_variant_type_new(type_string);
|
||||
|
||||
let root_variant = g_variant_new_from_data(
|
||||
variant_type,
|
||||
config.as_ptr() as *const core::ffi::c_void,
|
||||
config.len() as gsize,
|
||||
1,
|
||||
None,
|
||||
ptr::null_mut(),
|
||||
);
|
||||
|
||||
let hostlink_mmio_variant = g_variant_get_child_value(root_variant, 0);
|
||||
let hostlink_mmio = g_variant_get_uint64(hostlink_mmio_variant);
|
||||
|
||||
let hostlink_irq_variant = g_variant_get_child_value(root_variant, 1);
|
||||
let hostlink_irq = g_variant_get_uint32(hostlink_irq_variant);
|
||||
|
||||
let kernel_base_variant = g_variant_get_child_value(root_variant, 2);
|
||||
let kernel_base = g_variant_get_uint64(kernel_base_variant);
|
||||
xnu::set_kernel_base(kernel_base);
|
||||
|
||||
let module_info_variant = g_variant_get_child_value(root_variant, 3);
|
||||
let mut iter: GVariantIter = core::mem::zeroed();
|
||||
g_variant_iter_init(&mut iter as *mut GVariantIter, module_info_variant);
|
||||
|
||||
let mut module_info: Vec<ModuleInfo> = Vec::new();
|
||||
let mut raw_name: *mut gchar = null_mut();
|
||||
let mut raw_version: *mut gchar = null_mut();
|
||||
let mut offset: u32 = 0;
|
||||
let mut size: u32 = 0;
|
||||
let mut start_func_offset: u32 = 0;
|
||||
let mut stop_func_offset: u32 = 0;
|
||||
while g_variant_iter_next(
|
||||
&mut iter as *mut GVariantIter,
|
||||
c"(&s&suuuu)".as_ptr(),
|
||||
&mut raw_name,
|
||||
&mut raw_version,
|
||||
&mut offset,
|
||||
&mut size,
|
||||
&mut start_func_offset,
|
||||
&mut stop_func_offset,
|
||||
) != 0
|
||||
{
|
||||
module_info.push(ModuleInfo {
|
||||
name: String::from(CStr::from_ptr(raw_name).to_str().unwrap()),
|
||||
version: String::from(CStr::from_ptr(raw_version).to_str().unwrap()),
|
||||
offset,
|
||||
size,
|
||||
start_func_offset,
|
||||
stop_func_offset,
|
||||
});
|
||||
}
|
||||
|
||||
let symbol_array_variant = g_variant_get_child_value(root_variant, 4);
|
||||
let symbol_data_ptr = g_variant_get_data(symbol_array_variant) as *const u8;
|
||||
let symbol_data_size = g_variant_get_size(symbol_array_variant) as usize;
|
||||
let symbol_table = SymbolTable::new(core::slice::from_raw_parts(
|
||||
symbol_data_ptr,
|
||||
symbol_data_size,
|
||||
));
|
||||
|
||||
g_variant_unref(symbol_array_variant);
|
||||
g_variant_unref(module_info_variant);
|
||||
g_variant_unref(kernel_base_variant);
|
||||
g_variant_unref(hostlink_irq_variant);
|
||||
g_variant_unref(hostlink_mmio_variant);
|
||||
g_variant_unref(root_variant);
|
||||
g_variant_type_free(variant_type);
|
||||
|
||||
(
|
||||
hostlink_mmio,
|
||||
hostlink_irq,
|
||||
kernel_base,
|
||||
module_info,
|
||||
symbol_table,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn serialize_message(variant: *mut GVariant) -> Option<Vec<u8>> {
|
||||
unsafe {
|
||||
let size = g_variant_get_size(variant) as usize;
|
||||
if size == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let data_ptr = g_variant_get_data(variant) as *const u8;
|
||||
let mut result = Vec::with_capacity(size);
|
||||
result.resize(size, 0);
|
||||
core::ptr::copy_nonoverlapping(data_ptr, result.as_mut_ptr(), size);
|
||||
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_message(data: &[u8]) -> Option<*mut GVariant> {
|
||||
unsafe {
|
||||
if data.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let variant_type = g_variant_type_new(c"(yqv)".as_ptr());
|
||||
let data_copy = g_memdup2(data.as_ptr() as *const c_void, data.len() as u64);
|
||||
let variant = g_variant_new_from_data(
|
||||
variant_type,
|
||||
data_copy,
|
||||
data.len() as u64,
|
||||
0,
|
||||
Some(g_free),
|
||||
data_copy,
|
||||
);
|
||||
g_variant_type_free(variant_type);
|
||||
|
||||
if variant.is_null() {
|
||||
None
|
||||
} else {
|
||||
Some(variant)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_incoming_message(variant: *mut GVariant) {
|
||||
{
|
||||
let mut cmd_value: u8 = 0;
|
||||
let mut request_id: u16 = 0;
|
||||
let mut payload_variant: *mut GVariant = ptr::null_mut();
|
||||
|
||||
let cmd = unsafe {
|
||||
g_variant_get(
|
||||
variant,
|
||||
c"(yqv)".as_ptr(),
|
||||
&mut cmd_value,
|
||||
&mut request_id,
|
||||
&mut payload_variant,
|
||||
);
|
||||
|
||||
core::mem::transmute::<u8, FridaCommand>(cmd_value)
|
||||
};
|
||||
|
||||
let response = match cmd {
|
||||
FridaCommand::CreateScript => handle_create_script(payload_variant),
|
||||
FridaCommand::LoadScript => handle_load_script(payload_variant),
|
||||
FridaCommand::DestroyScript => handle_destroy_script(payload_variant),
|
||||
FridaCommand::PostScriptMessage => handle_post_script_message(payload_variant),
|
||||
_ => HandlerResponse::error("Unknown command"),
|
||||
};
|
||||
|
||||
send_command_reply(request_id, response);
|
||||
|
||||
unsafe { g_variant_unref(payload_variant) };
|
||||
}
|
||||
}
|
||||
|
||||
fn send_command_reply(request_id: u16, response: HandlerResponse) {
|
||||
unsafe {
|
||||
let message = g_variant_new(
|
||||
c"(yqv)".as_ptr(),
|
||||
FridaCommand::Reply as u8 as u32,
|
||||
request_id as u32,
|
||||
response.variant,
|
||||
);
|
||||
|
||||
if let Some(serialized) = serialize_message(message) {
|
||||
transport_get_unchecked().send(&serialized);
|
||||
}
|
||||
|
||||
g_variant_unref(message);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_create_script(payload_variant: *mut GVariant) -> HandlerResponse {
|
||||
unsafe {
|
||||
if g_variant_check_format_string(payload_variant, c"s".as_ptr(), 0) == 0 {
|
||||
return HandlerResponse::error("Invalid payload format: expected string");
|
||||
}
|
||||
let source = g_variant_get_string(payload_variant, core::ptr::null_mut());
|
||||
|
||||
let backend = gum_script_backend_obtain_qjs();
|
||||
let cancellable: *mut GCancellable = ptr::null_mut();
|
||||
let mut error: *mut GError = ptr::null_mut();
|
||||
|
||||
let script = gum_script_backend_create_sync(
|
||||
backend,
|
||||
c"agent.js".as_ptr(),
|
||||
source,
|
||||
ptr::null_mut(),
|
||||
cancellable,
|
||||
&mut error,
|
||||
);
|
||||
|
||||
if !error.is_null() {
|
||||
let error_msg = CStr::from_ptr((*error).message).to_str().unwrap();
|
||||
let error_string = String::from(error_msg);
|
||||
g_error_free(error);
|
||||
return HandlerResponse::error(&error_string);
|
||||
}
|
||||
|
||||
let script_id = NEXT_SCRIPT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
let script_id_ptr = Box::into_raw(Box::new(script_id));
|
||||
gum_script_set_message_handler(
|
||||
script,
|
||||
Some(frida_message_handler),
|
||||
script_id_ptr as *mut c_void,
|
||||
None,
|
||||
);
|
||||
|
||||
core::ptr::addr_of_mut!(SCRIPTS)
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.insert(script_id, script);
|
||||
|
||||
HandlerResponse::success(g_variant_new_uint32(script_id))
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_message_handler(
|
||||
message: *const gchar,
|
||||
_data: *mut GBytes,
|
||||
user_data: gpointer,
|
||||
) {
|
||||
unsafe {
|
||||
let script_id = *(user_data as *const u32);
|
||||
|
||||
let message_variant = g_variant_new(
|
||||
c"(yqv)".as_ptr(),
|
||||
FridaCommand::ScriptMessage as u8 as u32,
|
||||
0u32,
|
||||
g_variant_new(c"(us)".as_ptr(), script_id, message),
|
||||
);
|
||||
|
||||
if let Some(serialized) = serialize_message(message_variant) {
|
||||
transport_get_unchecked().send(&serialized);
|
||||
}
|
||||
|
||||
g_variant_unref(message_variant);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_load_script(payload_variant: *mut GVariant) -> HandlerResponse {
|
||||
unsafe {
|
||||
if g_variant_check_format_string(payload_variant, c"u".as_ptr(), 0) == 0 {
|
||||
return HandlerResponse::error("Invalid payload format: expected uint32");
|
||||
}
|
||||
let script_id = g_variant_get_uint32(payload_variant);
|
||||
|
||||
let Some(script) = get_script_by_id(script_id) else {
|
||||
return HandlerResponse::error(&format!("Script with ID {} not found", script_id));
|
||||
};
|
||||
|
||||
gum_script_load_sync(script, ptr::null_mut());
|
||||
|
||||
HandlerResponse::success_empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_destroy_script(payload_variant: *mut GVariant) -> HandlerResponse {
|
||||
unsafe {
|
||||
if g_variant_check_format_string(payload_variant, c"u".as_ptr(), 0) == 0 {
|
||||
return HandlerResponse::error("Invalid payload format: expected uint32");
|
||||
}
|
||||
let script_id = g_variant_get_uint32(payload_variant);
|
||||
|
||||
let scripts = core::ptr::addr_of_mut!(SCRIPTS).as_mut().unwrap();
|
||||
let Some(script) = scripts.remove(&script_id) else {
|
||||
return HandlerResponse::error(&format!("Script with ID {} not found", script_id));
|
||||
};
|
||||
|
||||
gum_script_unload_sync(script, ptr::null_mut());
|
||||
g_object_unref(script as *mut c_void);
|
||||
|
||||
HandlerResponse::success_empty()
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_post_script_message(payload_variant: *mut GVariant) -> HandlerResponse {
|
||||
unsafe {
|
||||
if g_variant_check_format_string(payload_variant, c"(us)".as_ptr(), 0) == 0 {
|
||||
return HandlerResponse::error("Invalid payload format: expected (uint32, string)");
|
||||
}
|
||||
let mut script_id: u32 = 0;
|
||||
let mut message: *const gchar = ptr::null();
|
||||
g_variant_get(
|
||||
payload_variant,
|
||||
c"(u&s)".as_ptr(),
|
||||
&mut script_id,
|
||||
&mut message,
|
||||
);
|
||||
|
||||
let Some(script) = get_script_by_id(script_id) else {
|
||||
return HandlerResponse::error(&format!("Script with ID {} not found", script_id));
|
||||
};
|
||||
|
||||
gum_script_post(script, message, ptr::null_mut());
|
||||
|
||||
HandlerResponse::success_empty()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn get_script_by_id(script_id: u32) -> Option<*mut GumScript> {
|
||||
unsafe {
|
||||
let scripts = core::ptr::addr_of_mut!(SCRIPTS).as_mut().unwrap();
|
||||
scripts.get(&script_id).copied()
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_panic_handler(message: *const u8, _user_data: *mut core::ffi::c_void) {
|
||||
let msg = unsafe { core::ffi::CStr::from_ptr(message).to_str().unwrap() };
|
||||
panic!("[Frida] {}", msg);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_log_handler(
|
||||
_log_domain: *const core::ffi::c_char,
|
||||
_log_level: i32,
|
||||
message: *const core::ffi::c_char,
|
||||
_user_data: *mut core::ffi::c_void,
|
||||
) {
|
||||
let msg = unsafe { core::ffi::CStr::from_ptr(message).to_str().unwrap() };
|
||||
kprintln!("[Frida] {}", msg);
|
||||
}
|
||||
|
||||
#[panic_handler]
|
||||
fn panic(info: &core::panic::PanicInfo<'_>) -> ! {
|
||||
let mut s = format!("{}", info);
|
||||
s.push('\0');
|
||||
xnu::panic(s.as_str());
|
||||
loop {}
|
||||
}
|
||||
|
||||
pub struct XnuAllocator;
|
||||
|
||||
unsafe impl GlobalAlloc for XnuAllocator {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
xnu::kalloc(layout.size())
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||
xnu::free(ptr, layout.size());
|
||||
}
|
||||
}
|
||||
|
||||
#[global_allocator]
|
||||
static GLOBAL: XnuAllocator = XnuAllocator;
|
||||
extern crate alloc;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! kprintln {
|
||||
($($arg:tt)*) => {{
|
||||
use core::fmt::Write;
|
||||
let mut buf = alloc::string::String::new();
|
||||
write!(&mut buf, $($arg)*).unwrap();
|
||||
buf.push('\n');
|
||||
buf.push('\0');
|
||||
crate::xnu::io_log(&buf)
|
||||
}};
|
||||
}
|
||||
use frida_barebone_agent as _;
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::bindings::{
|
||||
GPatternSpec, g_pattern_spec_free, g_pattern_spec_match_string, g_pattern_spec_new,
|
||||
};
|
||||
use crate::xnu::get_kernel_base;
|
||||
use crate::kernel::get_kernel_base;
|
||||
use alloc::ffi::CString;
|
||||
use core::cmp::Ordering;
|
||||
use core::ffi::{CStr, c_char};
|
||||
|
||||
+145
-46
@@ -1,7 +1,83 @@
|
||||
use core::ffi::c_void;
|
||||
use core::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
static KERNEL_BASE: AtomicU64 = AtomicU64::new(0);
|
||||
use crate::kernel::ThreadEntry;
|
||||
|
||||
pub fn log(msg: &str) {
|
||||
unsafe {
|
||||
_IOLog(msg.as_ptr());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn panic(msg: &str) {
|
||||
unsafe { _panic(msg.as_ptr()) };
|
||||
}
|
||||
|
||||
pub fn spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> isize {
|
||||
kernel_thread_start(entry, parameter)
|
||||
}
|
||||
|
||||
pub fn alloc(size: usize) -> *mut u8 {
|
||||
kalloc(size)
|
||||
}
|
||||
|
||||
// Executable slabs come out of the same allocator; the host flips their page
|
||||
// permissions for us through its physical-memory bridge.
|
||||
pub fn alloc_code(size: usize) -> *mut u8 {
|
||||
kalloc(size)
|
||||
}
|
||||
|
||||
pub fn free_code(ptr: *mut u8, size: usize) {
|
||||
free(ptr, size);
|
||||
}
|
||||
|
||||
// Arms the wait, gives `check` a chance to observe the condition, and only then
|
||||
// commits to sleeping — the three-phase Mach protocol, which is race-free
|
||||
// because a wakeup landing after assert_wait() cancels the pending block.
|
||||
pub fn wait(token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut() -> bool) {
|
||||
let wait_result = match timeout_us {
|
||||
None => assert_wait(token, THREAD_INTERRUPTIBLE),
|
||||
Some(us) => assert_wait_timeout(token, THREAD_INTERRUPTIBLE, (us * 1000) as u32, 1),
|
||||
};
|
||||
if wait_result != THREAD_WAITING {
|
||||
panic!("assert_wait failed: {}", wait_result);
|
||||
}
|
||||
|
||||
if check() {
|
||||
thread_wakeup(token);
|
||||
return;
|
||||
}
|
||||
|
||||
thread_block(None);
|
||||
}
|
||||
|
||||
pub fn wake(token: *const u8) {
|
||||
thread_wakeup(token);
|
||||
}
|
||||
|
||||
// XNU reschedules a kernel thread that stays runnable, so there is nothing this has
|
||||
// to do here. It exists because Linux does not forgive a loop that never yields.
|
||||
pub fn yield_now() {}
|
||||
|
||||
pub fn monotonic_micros() -> i64 {
|
||||
(absolutetime_to_nanoseconds(mach_absolute_time()) / 1000) as i64
|
||||
}
|
||||
|
||||
pub fn wall_clock_micros() -> (u32, u32) {
|
||||
clock_get_calendar_microtime()
|
||||
}
|
||||
|
||||
// The thread pointer is exposed to JavaScript as a GumThreadId, so it must fit
|
||||
// in a double without losing precision; the low bits keep it unique per thread.
|
||||
const JS_SAFE_THREAD_ID_MASK: u64 = (1 << 48) - 1;
|
||||
|
||||
pub fn current_thread_id() -> u64 {
|
||||
let thread_ptr: u64;
|
||||
unsafe {
|
||||
core::arch::asm!("mrs {}, tpidr_el1", out(reg) thread_ptr, options(nomem, nostack));
|
||||
}
|
||||
thread_ptr & JS_SAFE_THREAD_ID_MASK
|
||||
}
|
||||
|
||||
pub fn get_kernel_base() -> u64 {
|
||||
KERNEL_BASE.load(Ordering::Relaxed)
|
||||
@@ -11,55 +87,30 @@ pub fn set_kernel_base(base: u64) {
|
||||
KERNEL_BASE.store(base, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
type ContinuationFn = unsafe extern "C" fn(_parameter: *mut c_void, _wait_result: i32);
|
||||
static KERNEL_BASE: AtomicU64 = AtomicU64::new(0);
|
||||
|
||||
unsafe extern "C" {
|
||||
static _panic: unsafe extern "C" fn(*const u8);
|
||||
static _IOLog: unsafe extern "C" fn(*const u8, ...);
|
||||
static _kalloc: unsafe extern "C" fn(usize) -> *mut u8;
|
||||
static _kfree: unsafe extern "C" fn(*mut u8, usize) -> *mut u8;
|
||||
static _kernel_thread_start:
|
||||
unsafe extern "C" fn(*const (), *mut c_void, *mut *mut c_void) -> isize;
|
||||
static _assert_wait: unsafe extern "C" fn(*const u8, u32) -> i32;
|
||||
static _assert_wait_timeout: unsafe extern "C" fn(*const u8, u32, u32, u32) -> i32;
|
||||
static _thread_block: unsafe extern "C" fn(Option<ContinuationFn>) -> i32;
|
||||
static _thread_wakeup: unsafe extern "C" fn(*const u8) -> i32;
|
||||
static _mach_absolute_time: unsafe extern "C" fn() -> u64;
|
||||
static _absolutetime_to_nanoseconds: unsafe extern "C" fn(u64, *mut u64);
|
||||
static _clock_get_calendar_microtime: unsafe extern "C" fn(*mut u32, *mut u32);
|
||||
static _ml_io_map: unsafe extern "C" fn(u64, u64) -> *mut c_void;
|
||||
static _ml_vtophys: unsafe extern "C" fn(u64) -> u64;
|
||||
static __ZN9IOService11getPlatformEv: unsafe extern "C" fn() -> *mut c_void;
|
||||
static __ZN9IOServiceC2Ev: unsafe extern "C" fn(*mut core::ffi::c_void);
|
||||
static __ZN8OSSymbol17withCStringNoCopyEPKc:
|
||||
unsafe extern "C" fn(*const core::ffi::c_char) -> *const OSSymbol;
|
||||
static __ZN6OSData9withBytesEPKvj:
|
||||
unsafe extern "C" fn(*const core::ffi::c_void, u32) -> *mut OSData;
|
||||
}
|
||||
|
||||
const IO_SERVICE_VTABLE_LENGTH: isize = 168;
|
||||
|
||||
const VT_LOOKUP_IC: isize = IO_SERVICE_VTABLE_LENGTH + 25; // IOPlatformExpert
|
||||
const VT_REGISTER_INT: isize = IO_SERVICE_VTABLE_LENGTH + 0; // IOInterruptController
|
||||
const VT_ENABLE_INT: isize = IO_SERVICE_VTABLE_LENGTH + 3; // IOInterruptController
|
||||
|
||||
pub fn panic(msg: &str) {
|
||||
unsafe { _panic(msg.as_ptr()) };
|
||||
}
|
||||
|
||||
pub fn io_log(msg: &str) {
|
||||
unsafe {
|
||||
_IOLog(msg.as_ptr());
|
||||
}
|
||||
}
|
||||
// Z_WAITOK (modern XNU): block until allocation succeeds.
|
||||
const Z_WAITOK: u32 = 0x0200;
|
||||
|
||||
pub fn kalloc(size: usize) -> *mut u8 {
|
||||
unsafe { _kalloc(size) }
|
||||
unsafe {
|
||||
if let Some(f) = _kalloc_data {
|
||||
f(size, Z_WAITOK)
|
||||
} else if let Some(f) = _kalloc {
|
||||
f(size)
|
||||
} else {
|
||||
core::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn free(ptr: *mut u8, size: usize) {
|
||||
unsafe {
|
||||
_kfree(ptr, size);
|
||||
if let Some(f) = _kfree_data {
|
||||
f(ptr, size);
|
||||
} else if let Some(f) = _kfree {
|
||||
f(ptr, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,8 +127,8 @@ pub fn kernel_thread_start(continuation: ContinuationFn, thread_parameter: *mut
|
||||
};
|
||||
}
|
||||
|
||||
pub const THREAD_INTERRUPTIBLE: u32 = 1;
|
||||
pub const THREAD_WAITING: i32 = -1;
|
||||
const THREAD_INTERRUPTIBLE: u32 = 1;
|
||||
const THREAD_WAITING: i32 = -1;
|
||||
|
||||
pub fn assert_wait(event: *const u8, interruptible: u32) -> i32 {
|
||||
unsafe { _assert_wait(event, interruptible) }
|
||||
@@ -97,7 +148,16 @@ pub fn thread_block(continuation: Option<ContinuationFn>) -> i32 {
|
||||
}
|
||||
|
||||
pub fn thread_wakeup(event: *const u8) -> i32 {
|
||||
unsafe { _thread_wakeup(event) }
|
||||
unsafe {
|
||||
if let Some(f) = _thread_wakeup {
|
||||
f(event)
|
||||
} else if let Some(f) = _wakeup {
|
||||
f(event);
|
||||
0
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn mach_absolute_time() -> u64 {
|
||||
@@ -237,3 +297,42 @@ where
|
||||
let entry_ptr = unsafe { crate::pac::ptrauth_strip_data(entry as *const u8) };
|
||||
unsafe { core::mem::transmute_copy::<*const u8, T>(&entry_ptr) }
|
||||
}
|
||||
|
||||
const IO_SERVICE_VTABLE_LENGTH: isize = 168;
|
||||
|
||||
const VT_LOOKUP_IC: isize = IO_SERVICE_VTABLE_LENGTH + 25; // IOPlatformExpert
|
||||
const VT_REGISTER_INT: isize = IO_SERVICE_VTABLE_LENGTH + 0; // IOInterruptController
|
||||
const VT_ENABLE_INT: isize = IO_SERVICE_VTABLE_LENGTH + 3; // IOInterruptController
|
||||
|
||||
type ContinuationFn = ThreadEntry;
|
||||
|
||||
unsafe extern "C" {
|
||||
static _panic: unsafe extern "C" fn(*const u8);
|
||||
static _IOLog: unsafe extern "C" fn(*const u8, ...);
|
||||
// QEMU XNU exports plain kalloc/kfree; iOS XNU exports kalloc_data/kfree_data
|
||||
// (modern data-typed allocator KPIs). The host fills in whichever pair the
|
||||
// running kernel provides; the other slot stays null.
|
||||
static _kalloc: Option<unsafe extern "C" fn(usize) -> *mut u8>;
|
||||
static _kfree: Option<unsafe extern "C" fn(*mut u8, usize) -> *mut u8>;
|
||||
static _kalloc_data: Option<unsafe extern "C" fn(usize, u32) -> *mut u8>;
|
||||
static _kfree_data: Option<unsafe extern "C" fn(*mut u8, usize)>;
|
||||
static _kernel_thread_start:
|
||||
unsafe extern "C" fn(*const (), *mut c_void, *mut *mut c_void) -> isize;
|
||||
static _assert_wait: unsafe extern "C" fn(*const u8, u32) -> i32;
|
||||
static _assert_wait_timeout: unsafe extern "C" fn(*const u8, u32, u32, u32) -> i32;
|
||||
static _thread_block: unsafe extern "C" fn(Option<ContinuationFn>) -> i32;
|
||||
// QEMU XNU exports thread_wakeup; iOS XNU exports the BSD-style wakeup wrapper.
|
||||
static _thread_wakeup: Option<unsafe extern "C" fn(*const u8) -> i32>;
|
||||
static _wakeup: Option<unsafe extern "C" fn(*const u8)>;
|
||||
static _mach_absolute_time: unsafe extern "C" fn() -> u64;
|
||||
static _absolutetime_to_nanoseconds: unsafe extern "C" fn(u64, *mut u64);
|
||||
static _clock_get_calendar_microtime: unsafe extern "C" fn(*mut u32, *mut u32);
|
||||
static _ml_io_map: unsafe extern "C" fn(u64, u64) -> *mut c_void;
|
||||
static _ml_vtophys: unsafe extern "C" fn(u64) -> u64;
|
||||
static __ZN9IOService11getPlatformEv: unsafe extern "C" fn() -> *mut c_void;
|
||||
static __ZN9IOServiceC2Ev: unsafe extern "C" fn(*mut core::ffi::c_void);
|
||||
static __ZN8OSSymbol17withCStringNoCopyEPKc:
|
||||
unsafe extern "C" fn(*const core::ffi::c_char) -> *const OSSymbol;
|
||||
static __ZN6OSData9withBytesEPKvj:
|
||||
unsafe extern "C" fn(*const core::ffi::c_void, u32) -> *mut OSData;
|
||||
}
|
||||
@@ -118,7 +118,8 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async Allocation allocate (size_t size, size_t alignment, Cancellable? cancellable) throws Error, IOError {
|
||||
uint64 address = yield machine.invoke (config.alloc_function.address, { size }, cancellable);
|
||||
uint64 address = yield machine.invoke (config.alloc_function.address, { size, config.alloc_flags },
|
||||
cancellable);
|
||||
|
||||
// TODO: Handle alignment.
|
||||
|
||||
|
||||
@@ -14,6 +14,24 @@ namespace Frida.Barebone {
|
||||
get { return "tiny"; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Return-detection landing zone for invoke(). Defaults to the hijacked thread's own pc, but
|
||||
* that address is typically a hot scheduler routine (thread_block) which every other thread
|
||||
* also trips, drowning out our thread's return. Point this at a function that is never called
|
||||
* during normal operation (e.g. panic) so only our returning thread stops here.
|
||||
*/
|
||||
public uint64 call_landing_zone = 0;
|
||||
|
||||
public PhysicalMemory? physical_memory;
|
||||
|
||||
// The kernel's MMU registers (TTBR1, TCR) are fixed, so cache them from the first read
|
||||
// (taken while the target is stopped) to serve later page-table work over the bridge
|
||||
// without halting the running agent.
|
||||
private MMUParameters? cached_mmu_parameters;
|
||||
|
||||
private uint64 code_template_descriptor;
|
||||
private bool code_template_known = false;
|
||||
|
||||
private enum AddressingMode {
|
||||
VIRTUAL,
|
||||
PHYSICAL
|
||||
@@ -37,7 +55,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async size_t query_page_size (Cancellable? cancellable) throws Error, IOError {
|
||||
MMUParameters p = yield MMUParameters.load (gdb, cancellable);
|
||||
MMUParameters p = yield load_mmu_parameters (cancellable);
|
||||
|
||||
return p.granule;
|
||||
}
|
||||
@@ -84,7 +102,7 @@ namespace Frida.Barebone {
|
||||
private async void collect_ranges_in_table (uint64 table_address, uint level, uint64 upper_bits, MMUParameters p,
|
||||
Gee.List<RangeDetails> ranges, Cancellable? cancellable) throws Error, IOError {
|
||||
uint max_entries = compute_max_entries (level, p);
|
||||
Buffer entries = yield gdb.read_buffer (table_address, max_entries * Descriptor.SIZE, cancellable);
|
||||
Buffer entries = yield read_physical_buffer (table_address, max_entries * Descriptor.SIZE, cancellable);
|
||||
uint shift = address_shift_at_level (level, p.granule);
|
||||
for (uint i = 0; i != max_entries; i++) {
|
||||
uint64 raw_descriptor = entries.read_uint64 (i * Descriptor.SIZE);
|
||||
@@ -165,9 +183,13 @@ namespace Frida.Barebone {
|
||||
|
||||
public async Allocation allocate_pages (Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
MMUParameters p = yield MMUParameters.load (gdb, cancellable);
|
||||
MMUParameters p = yield load_mmu_parameters (cancellable);
|
||||
|
||||
yield set_addressing_mode (gdb, PHYSICAL, cancellable);
|
||||
// The bridge reads and writes guest physical memory directly, so the stub's
|
||||
// physical-addressing mode (which requires a halted target) is unnecessary.
|
||||
bool needs_stub_addressing = physical_memory == null;
|
||||
if (needs_stub_addressing)
|
||||
yield set_addressing_mode (gdb, PHYSICAL, cancellable);
|
||||
try {
|
||||
Allocation? allocation = yield maybe_insert_descriptor_in_table (physical_addresses, p.tt1, p.first_level,
|
||||
p.upper_bits, p, cancellable);
|
||||
@@ -176,7 +198,8 @@ namespace Frida.Barebone {
|
||||
|
||||
return allocation;
|
||||
} finally {
|
||||
set_addressing_mode.begin (gdb, VIRTUAL, null);
|
||||
if (needs_stub_addressing)
|
||||
set_addressing_mode.begin (gdb, VIRTUAL, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +219,7 @@ namespace Frida.Barebone {
|
||||
uint64 chunk_base_address = table_address + (chunk_offset * Descriptor.SIZE);
|
||||
uint chunk_size = uint.min (chunk_max_size, max_entries - chunk_offset);
|
||||
|
||||
Buffer descriptors = yield gdb.read_buffer (chunk_base_address, chunk_size * Descriptor.SIZE, cancellable);
|
||||
Buffer descriptors = yield read_physical_buffer (chunk_base_address, chunk_size * Descriptor.SIZE, cancellable);
|
||||
for (uint i = 0; i != chunk_size && num_available_slots != num_pages; i++) {
|
||||
uint buffer_offset = i * Descriptor.SIZE;
|
||||
uint64 raw_descriptor = descriptors.read_uint64 (buffer_offset);
|
||||
@@ -238,7 +261,7 @@ namespace Frida.Barebone {
|
||||
return null;
|
||||
|
||||
Gum.PageProtection page_prot = Gum.PageProtection.READ | Gum.PageProtection.WRITE;
|
||||
if (!p.sprr_enabled)
|
||||
if (!p.sprr_enabled && !code_template_known)
|
||||
page_prot |= Gum.PageProtection.EXECUTE;
|
||||
|
||||
var builder = gdb.make_buffer_builder ();
|
||||
@@ -248,18 +271,118 @@ namespace Frida.Barebone {
|
||||
builder.append_uint64 (new_descriptor);
|
||||
}
|
||||
Bytes new_descriptors = builder.build ();
|
||||
Bytes old_descriptors = yield gdb.read_byte_array (first_available_slot, new_descriptors.get_size (), cancellable);
|
||||
yield gdb.write_byte_array (first_available_slot, new_descriptors, cancellable);
|
||||
Bytes old_descriptors = (yield read_physical_buffer (first_available_slot, new_descriptors.get_size (),
|
||||
cancellable)).bytes;
|
||||
yield write_physical_buffer (first_available_slot, new_descriptors, cancellable);
|
||||
|
||||
size_t size = num_pages * p.granule;
|
||||
return new DescriptorAllocation (first_available_va, size, first_available_slot, old_descriptors, gdb);
|
||||
}
|
||||
|
||||
// The VZ kernel GDB stub does not expose the SPRR registers, so we cannot read the
|
||||
// permission-remapping table that turns a descriptor's AP/XN bits into real access rights.
|
||||
// Granting execute is the case that genuinely needs the right SPRR index, so we sample a
|
||||
// known kernel-code page and replay its exact attribute encoding onto agent text; the
|
||||
// read-only/read-write encodings the AP bits already give us continue to work as-is.
|
||||
public async void learn_permission_templates (uint64 code_va, Cancellable? cancellable) throws Error, IOError {
|
||||
code_template_descriptor = yield read_level3_descriptor (code_va, cancellable);
|
||||
code_template_known = true;
|
||||
}
|
||||
|
||||
public async uint64 translate_address (uint64 va, Cancellable? cancellable) throws Error, IOError {
|
||||
MMUParameters p = yield load_mmu_parameters (cancellable);
|
||||
uint64 descriptor = yield read_level3_descriptor (va, cancellable);
|
||||
uint64 page_mask = (1ULL << inpage_bits_for_granule (p.granule)) - 1;
|
||||
return (descriptor & INT48_MASK & ~page_mask) | (va & page_mask);
|
||||
}
|
||||
|
||||
public override async void write_virtual (uint64 va, uint8[] data, Cancellable? cancellable) throws Error, IOError {
|
||||
if (physical_memory == null) {
|
||||
yield gdb.write_byte_array (va, new Bytes (data), cancellable);
|
||||
return;
|
||||
}
|
||||
|
||||
MMUParameters p = yield MMUParameters.load (gdb, cancellable);
|
||||
size_t offset = 0;
|
||||
while (offset < data.length) {
|
||||
uint64 cur_va = va + offset;
|
||||
uint64 pa = yield translate_address (cur_va, cancellable);
|
||||
size_t chunk = size_t.min ((size_t) (p.granule - (cur_va & (p.granule - 1))), data.length - offset);
|
||||
physical_memory.write (pa, data[offset : offset + chunk]);
|
||||
offset += chunk;
|
||||
}
|
||||
}
|
||||
|
||||
private async Buffer read_physical_buffer (uint64 pa, size_t size, Cancellable? cancellable) throws Error, IOError {
|
||||
if (physical_memory != null && physical_memory.contains (pa))
|
||||
return gdb.make_buffer (new Bytes (physical_memory.read (pa, size)));
|
||||
return yield gdb.read_buffer (pa, size, cancellable);
|
||||
}
|
||||
|
||||
private async void write_physical_buffer (uint64 pa, Bytes data, Cancellable? cancellable) throws Error, IOError {
|
||||
if (physical_memory != null && physical_memory.contains (pa)) {
|
||||
physical_memory.write (pa, data.get_data ());
|
||||
return;
|
||||
}
|
||||
yield gdb.write_byte_array (pa, data, cancellable);
|
||||
}
|
||||
|
||||
public async uint8[] read_physical (uint64 pa, size_t size, Cancellable? cancellable) throws Error, IOError {
|
||||
if (physical_memory != null && physical_memory.contains (pa))
|
||||
return physical_memory.read (pa, size);
|
||||
return yield read_physical_via_stub (pa, size, cancellable);
|
||||
}
|
||||
|
||||
public async void write_physical (uint64 pa, uint8[] data, Cancellable? cancellable) throws Error, IOError {
|
||||
if (physical_memory != null && physical_memory.contains (pa)) {
|
||||
physical_memory.write (pa, data);
|
||||
return;
|
||||
}
|
||||
yield set_addressing_mode (gdb, PHYSICAL, cancellable);
|
||||
try {
|
||||
yield gdb.write_byte_array (pa, new Bytes (data), cancellable);
|
||||
} finally {
|
||||
set_addressing_mode.begin (gdb, VIRTUAL, null);
|
||||
}
|
||||
}
|
||||
|
||||
public async uint8[] read_physical_via_stub (uint64 pa, size_t size, Cancellable? cancellable) throws Error, IOError {
|
||||
yield set_addressing_mode (gdb, PHYSICAL, cancellable);
|
||||
try {
|
||||
return (yield gdb.read_byte_array (pa, size, cancellable)).get_data ();
|
||||
} finally {
|
||||
set_addressing_mode.begin (gdb, VIRTUAL, null);
|
||||
}
|
||||
}
|
||||
|
||||
public async uint64 read_level3_descriptor (uint64 va, Cancellable? cancellable) throws Error, IOError {
|
||||
MMUParameters p = yield load_mmu_parameters (cancellable);
|
||||
bool needs_stub_addressing = physical_memory == null;
|
||||
if (needs_stub_addressing)
|
||||
yield set_addressing_mode (gdb, PHYSICAL, cancellable);
|
||||
try {
|
||||
var cache = new TableWalkCache ();
|
||||
uint64 table_pa = yield find_level3_table (va, p, cache, cancellable);
|
||||
uint index_bits = num_address_bits_at_level (3, p);
|
||||
uint index_shift = inpage_bits_for_granule (p.granule);
|
||||
uint index_mask = (1U << index_bits) - 1;
|
||||
uint64 index = (va >> index_shift) & index_mask;
|
||||
uint64 slot_pa = table_pa + (index * Descriptor.SIZE);
|
||||
var buf = yield read_physical_buffer (slot_pa, Descriptor.SIZE, cancellable);
|
||||
return buf.read_uint64 (0);
|
||||
} finally {
|
||||
if (needs_stub_addressing)
|
||||
set_addressing_mode.begin (gdb, VIRTUAL, null);
|
||||
}
|
||||
}
|
||||
|
||||
public async void protect_pages (uint64 virtual_address, size_t size, Gum.PageProtection prot, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
MMUParameters p = yield MMUParameters.load (gdb, cancellable);
|
||||
MMUParameters p = yield load_mmu_parameters (cancellable);
|
||||
|
||||
yield set_addressing_mode (gdb, PHYSICAL, cancellable);
|
||||
bool needs_stub_addressing = physical_memory == null;
|
||||
if (needs_stub_addressing)
|
||||
yield set_addressing_mode (gdb, PHYSICAL, cancellable);
|
||||
try {
|
||||
uint64 page_mask = p.granule - 1;
|
||||
uint64 aligned_va = virtual_address & ~page_mask;
|
||||
@@ -268,10 +391,17 @@ namespace Frida.Barebone {
|
||||
|
||||
yield perform_protect_pages (aligned_va, num_pages, prot, p, cancellable);
|
||||
} finally {
|
||||
set_addressing_mode.begin (gdb, VIRTUAL, null);
|
||||
if (needs_stub_addressing)
|
||||
set_addressing_mode.begin (gdb, VIRTUAL, null);
|
||||
}
|
||||
}
|
||||
|
||||
private async MMUParameters load_mmu_parameters (Cancellable? cancellable) throws Error, IOError {
|
||||
if (cached_mmu_parameters == null)
|
||||
cached_mmu_parameters = yield MMUParameters.load (gdb, cancellable);
|
||||
return cached_mmu_parameters;
|
||||
}
|
||||
|
||||
private async void perform_protect_pages (uint64 start_va, uint num_pages, Gum.PageProtection prot, MMUParameters p,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
var table_cache = new TableWalkCache ();
|
||||
@@ -313,7 +443,7 @@ namespace Frida.Barebone {
|
||||
uint64 first_index = (current_va >> index_shift) & index_mask;
|
||||
uint64 first_slot_pa = table_pa + (first_index * Descriptor.SIZE);
|
||||
|
||||
Buffer descriptors = yield gdb.read_buffer (first_slot_pa, batch_size * Descriptor.SIZE, cancellable);
|
||||
Buffer descriptors = yield read_physical_buffer (first_slot_pa, batch_size * Descriptor.SIZE, cancellable);
|
||||
|
||||
bool any_changed = false;
|
||||
for (uint i = 0; i != batch_size; i++) {
|
||||
@@ -327,7 +457,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
if (any_changed)
|
||||
yield gdb.write_byte_array (first_slot_pa, descriptors.bytes, cancellable);
|
||||
yield write_physical_buffer (first_slot_pa, descriptors.bytes, cancellable);
|
||||
}
|
||||
|
||||
private async uint64 find_level3_table (uint64 va, MMUParameters p, TableWalkCache cache, Cancellable? cancellable)
|
||||
@@ -352,7 +482,7 @@ namespace Frida.Barebone {
|
||||
uint index = (uint) ((va >> shift) & (entries - 1));
|
||||
uint64 slot_pa = table_pa + ((uint64) index * Descriptor.SIZE);
|
||||
|
||||
Buffer d_buf = yield gdb.read_buffer (slot_pa, Descriptor.SIZE, cancellable);
|
||||
Buffer d_buf = yield read_physical_buffer (slot_pa, Descriptor.SIZE, cancellable);
|
||||
uint64 raw = d_buf.read_uint64 (0);
|
||||
Descriptor desc = Descriptor.parse (raw, level, p.granule);
|
||||
|
||||
@@ -375,7 +505,7 @@ namespace Frida.Barebone {
|
||||
uint index = (uint) ((va >> shift) & (entries - 1));
|
||||
uint64 slot_pa = table_pa + (index * Descriptor.SIZE);
|
||||
|
||||
Buffer d_buf = yield gdb.read_buffer (slot_pa, Descriptor.SIZE, cancellable);
|
||||
Buffer d_buf = yield read_physical_buffer (slot_pa, Descriptor.SIZE, cancellable);
|
||||
uint64 raw_desc = d_buf.read_uint64 (0);
|
||||
Descriptor desc = Descriptor.parse (raw_desc, level, p.granule);
|
||||
if (desc.kind != TABLE)
|
||||
@@ -384,7 +514,7 @@ namespace Frida.Barebone {
|
||||
if ((raw_desc & TABLE_PXN_BIT) != 0) {
|
||||
uint64 new_desc = raw_desc & ~TABLE_PXN_BIT;
|
||||
d_buf.write_uint64 (0, new_desc);
|
||||
yield gdb.write_byte_array (slot_pa, d_buf.bytes, cancellable);
|
||||
yield write_physical_buffer (slot_pa, d_buf.bytes, cancellable);
|
||||
}
|
||||
|
||||
table_pa = desc.target_address;
|
||||
@@ -557,7 +687,7 @@ namespace Frida.Barebone {
|
||||
Gum.ElfArm64Relocation type = (Gum.ElfArm64Relocation) r.type;
|
||||
switch (type) {
|
||||
case ABS64:
|
||||
relocated.write_uint64 ((size_t) r.address, base_va + r.symbol.address + r.addend);
|
||||
relocated.write_uint64 ((size_t) r.address, base_va + relocated.read_uint64 ((size_t) r.address));
|
||||
break;
|
||||
case PREL32:
|
||||
var diff = (int64) (base_va + r.symbol.address + r.addend) - (int64) (base_va + r.address);
|
||||
@@ -585,8 +715,11 @@ namespace Frida.Barebone {
|
||||
|
||||
regs["pc"] = impl;
|
||||
|
||||
uint64 landing_zone = saved_regs["pc"].get_uint64 ();
|
||||
uint64 landing_zone = (call_landing_zone != 0) ? call_landing_zone : saved_regs["pc"].get_uint64 ();
|
||||
// The VZ stub exposes "lr" and "x30" as distinct registers; the live link register is
|
||||
// "lr", so the return address must be written there for the callee's RET to land here.
|
||||
regs["x30"] = landing_zone;
|
||||
regs["lr"] = landing_zone;
|
||||
|
||||
uint64 sp = saved_regs["sp"].get_uint64 ();
|
||||
sp -= RED_ZONE_SIZE;
|
||||
@@ -597,17 +730,21 @@ namespace Frida.Barebone {
|
||||
|
||||
yield thread.write_registers (regs, cancellable);
|
||||
|
||||
// The callee may reschedule (e.g. kernel_thread_start) onto another core, and the
|
||||
// stub identifies threads by core; matching the original core's id would strand the
|
||||
// call and clobber that core's now-unrelated thread. Nothing else reaches the landing
|
||||
// zone, so any thread stopping there is ours.
|
||||
GDB.Breakpoint bp = yield gdb.add_breakpoint (SOFT, landing_zone, 4, cancellable);
|
||||
GDB.Exception ex = null;
|
||||
do {
|
||||
ex = yield gdb.continue_until_exception (cancellable);
|
||||
} while (ex.breakpoint != bp || ex.thread.id != thread.id);
|
||||
// TODO: Improve GDB.Client to guarantee a single GDB.Thread instance per ID.
|
||||
} while (ex.breakpoint != bp);
|
||||
yield bp.remove (cancellable);
|
||||
|
||||
uint64 retval = yield thread.read_register ("x0", cancellable);
|
||||
GDB.Thread landed = ex.thread;
|
||||
uint64 retval = yield landed.read_register ("x0", cancellable);
|
||||
|
||||
yield thread.write_registers (saved_regs, cancellable);
|
||||
yield landed.write_registers (saved_regs, cancellable);
|
||||
|
||||
if (was_running)
|
||||
yield gdb.continue (cancellable);
|
||||
@@ -630,15 +767,16 @@ namespace Frida.Barebone {
|
||||
|
||||
yield thread.write_registers (regs, cancellable);
|
||||
|
||||
// As in invoke(): the executed code may migrate our thread to another core,
|
||||
// so match on the end breakpoint alone and finish on the core it landed on.
|
||||
GDB.Breakpoint bp = yield gdb.add_breakpoint (SOFT, end, 4, cancellable);
|
||||
GDB.Exception ex = null;
|
||||
do {
|
||||
ex = yield gdb.continue_until_exception (cancellable);
|
||||
} while (ex.breakpoint != bp || ex.thread.id != thread.id);
|
||||
// TODO: Improve GDB.Client to guarantee a single GDB.Thread instance per ID.
|
||||
} while (ex.breakpoint != bp);
|
||||
yield bp.remove (cancellable);
|
||||
|
||||
yield thread.write_registers (saved_regs, cancellable);
|
||||
yield ex.thread.write_registers (saved_regs, cancellable);
|
||||
|
||||
if (was_running)
|
||||
yield gdb.continue (cancellable);
|
||||
@@ -916,8 +1054,11 @@ namespace Frida.Barebone {
|
||||
private static async void set_addressing_mode (GDB.Client gdb, AddressingMode mode, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
Gee.Set<string> features = gdb.features;
|
||||
string enabled = (mode == PHYSICAL) ? "1" : "0";
|
||||
if ("qemu-phy-mem-mode" in features)
|
||||
yield gdb.execute_simple ("Qqemu.PhyMemMode:" + ((mode == PHYSICAL) ? "1" : "0"), cancellable);
|
||||
yield gdb.execute_simple ("Qqemu.PhyMemMode:" + enabled, cancellable);
|
||||
else if ("vf-phy-mem-mode" in features)
|
||||
yield gdb.execute_simple ("Qvf.PhyMemMode:" + enabled, cancellable);
|
||||
else
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported GDB remote stub; please file a bug");
|
||||
}
|
||||
@@ -1276,7 +1417,11 @@ namespace Frida.Barebone {
|
||||
return prot;
|
||||
}
|
||||
|
||||
private static uint64 apply_protection_bits (uint64 descriptor, Gum.PageProtection prot, MMUParameters p) throws Error {
|
||||
private uint64 apply_protection_bits (uint64 descriptor, Gum.PageProtection prot, MMUParameters p) throws Error {
|
||||
bool want_exec = (prot & Gum.PageProtection.EXECUTE) != 0;
|
||||
if (want_exec && code_template_known)
|
||||
return apply_code_template (descriptor, p);
|
||||
|
||||
uint64 result = descriptor;
|
||||
|
||||
if (p.sprr_enabled) {
|
||||
@@ -1289,6 +1434,13 @@ namespace Frida.Barebone {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Keep the page's own output address but adopt every attribute bit (the AP/XN SPRR index,
|
||||
// memory type, shareability) from a sampled kernel-code page so the MMU grants EL1 execute.
|
||||
private uint64 apply_code_template (uint64 descriptor, MMUParameters p) {
|
||||
uint64 output_address_mask = INT48_MASK & ~((1ULL << inpage_bits_for_granule (p.granule)) - 1);
|
||||
return (descriptor & output_address_mask) | (code_template_descriptor & ~output_address_mask);
|
||||
}
|
||||
|
||||
private static uint64 apply_non_sprr_protection_bits (uint64 descriptor, Gum.PageProtection prot) {
|
||||
uint64 result = descriptor;
|
||||
|
||||
|
||||
@@ -14,15 +14,21 @@ namespace Frida {
|
||||
|
||||
public sealed class BareboneHostSessionProvider : Object, HostSessionProvider {
|
||||
public string id {
|
||||
get { return "barebone"; }
|
||||
get {
|
||||
return "barebone";
|
||||
}
|
||||
}
|
||||
|
||||
public string name {
|
||||
get { return "GDB Remote Stub"; }
|
||||
get {
|
||||
return "GDB Remote Stub";
|
||||
}
|
||||
}
|
||||
|
||||
public Variant? icon {
|
||||
get { return null; }
|
||||
get {
|
||||
return _icon;
|
||||
}
|
||||
}
|
||||
|
||||
public HostSessionProviderKind kind {
|
||||
@@ -31,8 +37,13 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private static Variant _icon;
|
||||
private BareboneHostSession? host_session;
|
||||
|
||||
static construct {
|
||||
_icon = make_provider_icon (Frida.Data.Icons.get_barebone_png_blob ().data);
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
if (host_session != null) {
|
||||
yield host_session.close (cancellable);
|
||||
@@ -60,6 +71,15 @@ namespace Frida {
|
||||
config = new Barebone.Config ();
|
||||
}
|
||||
|
||||
Barebone.AgentConfig? resident_agent = config.agent;
|
||||
if (resident_agent != null && resident_agent.transport is Barebone.DeviceTransportConfig) {
|
||||
host_session = yield attach_to_resident_agent (
|
||||
(Barebone.DeviceTransportConfig) resident_agent.transport, cancellable);
|
||||
host_session.agent_session_detached.connect (on_agent_session_detached);
|
||||
|
||||
return host_session;
|
||||
}
|
||||
|
||||
SocketConnectable connectable;
|
||||
try {
|
||||
Barebone.ConnectionConfig c = config.connection;
|
||||
@@ -80,7 +100,11 @@ namespace Frida {
|
||||
throw new Error.TRANSPORT ("The specified GDB remote stub cannot be reached: %s", e.message);
|
||||
}
|
||||
|
||||
var gdb = yield GDB.Client.open (stream, cancellable);
|
||||
GDB.Client gdb;
|
||||
if (config.connection.flavor == Barebone.StubFlavor.VZ)
|
||||
gdb = yield Barebone.VzStubClient.open (stream, cancellable);
|
||||
else
|
||||
gdb = yield GDB.Client.open (stream, cancellable);
|
||||
|
||||
Barebone.Machine machine;
|
||||
switch (gdb.arch) {
|
||||
@@ -101,6 +125,14 @@ namespace Frida {
|
||||
break;
|
||||
}
|
||||
|
||||
// Read the daemon's guest-RAM map only once the stub has halted the VM: reading the
|
||||
// high-VA backing while the vCPUs run blocks mach_vm_read. The map is dynamic (the daemon
|
||||
// reclaims idle RAM), so VzPhysicalMemory re-reads it on demand as the agent maps pages.
|
||||
#if DARWIN
|
||||
if (config.connection.flavor == VZ && machine is Barebone.Arm64Machine)
|
||||
((Barebone.Arm64Machine) machine).physical_memory = Barebone.VzPhysicalMemory.open (config.connection.pid);
|
||||
#endif
|
||||
|
||||
size_t page_size;
|
||||
try {
|
||||
page_size = yield machine.query_page_size (cancellable);
|
||||
@@ -108,6 +140,23 @@ namespace Frida {
|
||||
page_size = 0;
|
||||
}
|
||||
|
||||
// Resolve the kernel's runtime layout before building the allocator: on a scattered
|
||||
// SPTM kernel collection the config-supplied addresses are static and must be translated.
|
||||
Barebone.KernelRelocation? relocation = null;
|
||||
uint64 kernel_base = 0;
|
||||
Barebone.ImageConfig? image = config.image;
|
||||
if (image != null) {
|
||||
if (image.base != null) {
|
||||
kernel_base = image.base.address;
|
||||
} else {
|
||||
var payload = yield Barebone.Img4.parse_file (File.new_for_path (image.file), cancellable);
|
||||
uint64? summaries = image.symbols["gLoadedKextSummaries"];
|
||||
relocation = yield Barebone.KernelRelocation.compute (machine, payload.data,
|
||||
summaries ?? 0, cancellable);
|
||||
kernel_base = relocation.reference_base;
|
||||
}
|
||||
}
|
||||
|
||||
Barebone.Allocator allocator;
|
||||
Barebone.AllocatorConfig? ac = config.allocator;
|
||||
if (ac == null) {
|
||||
@@ -116,8 +165,14 @@ namespace Frida {
|
||||
allocator = new Barebone.PhysicalAllocator (machine, page_size,
|
||||
(Barebone.PhysicalAllocatorConfig) ac);
|
||||
} else if (ac is Barebone.TargetFunctionsAllocatorConfig) {
|
||||
allocator = new Barebone.TargetFunctionsAllocator (machine, page_size,
|
||||
(Barebone.TargetFunctionsAllocatorConfig) ac);
|
||||
var tfa = (Barebone.TargetFunctionsAllocatorConfig) ac;
|
||||
if (relocation != null) {
|
||||
tfa.alloc_function = new Barebone.NonNullMemoryAddress ("allocator.alloc_function",
|
||||
relocation.translate (tfa.alloc_function.address));
|
||||
tfa.free_function = new Barebone.NonNullMemoryAddress ("allocator.free_function",
|
||||
relocation.translate (tfa.free_function.address));
|
||||
}
|
||||
allocator = new Barebone.TargetFunctionsAllocator (machine, page_size, tfa);
|
||||
} else {
|
||||
assert_not_reached ();
|
||||
}
|
||||
@@ -125,8 +180,8 @@ namespace Frida {
|
||||
Barebone.AgentConnection? agent_connection = null;
|
||||
Barebone.AgentConfig? agent_config = config.agent;
|
||||
if (agent_config != null) {
|
||||
agent_connection = yield Barebone.AgentConnection.open (agent_config, config.image, machine, allocator,
|
||||
cancellable);
|
||||
agent_connection = yield Barebone.AgentConnection.open (agent_config, config.image, relocation,
|
||||
kernel_base, machine, allocator, cancellable);
|
||||
}
|
||||
|
||||
var interceptor = new Barebone.Interceptor (machine, allocator);
|
||||
@@ -139,6 +194,24 @@ namespace Frida {
|
||||
return host_session;
|
||||
}
|
||||
|
||||
private async BareboneHostSession attach_to_resident_agent (Barebone.DeviceTransportConfig transport,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
#if WINDOWS
|
||||
throw new Error.NOT_SUPPORTED ("Resident agents are not available on this OS");
|
||||
#else
|
||||
int fd = Posix.open (transport.path, Posix.O_RDWR);
|
||||
if (fd == -1)
|
||||
throw new Error.TRANSPORT ("Unable to open %s: %s", transport.path, Posix.strerror (Posix.errno));
|
||||
|
||||
// Both halves are the same descriptor, so exactly one of them may close it.
|
||||
var stream = new SimpleIOStream (new UnixInputStream (fd, true), new UnixOutputStream (fd, false));
|
||||
|
||||
var connection = yield Barebone.AgentConnection.open_resident (stream, cancellable);
|
||||
|
||||
return new BareboneHostSession (connection, null);
|
||||
#endif
|
||||
}
|
||||
|
||||
public async void destroy (HostSession session, Cancellable? cancellable) throws Error, IOError {
|
||||
if (session != host_session)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid host session");
|
||||
@@ -190,7 +263,8 @@ namespace Frida {
|
||||
construct;
|
||||
}
|
||||
|
||||
public Barebone.Services services {
|
||||
/** Absent for a resident agent: every service here is built on a debugger stub. */
|
||||
public Barebone.Services? services {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
@@ -198,7 +272,7 @@ namespace Frida {
|
||||
private Gee.Map<AgentSessionId?, BareboneAgentSession> agent_sessions =
|
||||
new Gee.HashMap<AgentSessionId?, BareboneAgentSession> (AgentSessionId.hash, AgentSessionId.equal);
|
||||
|
||||
public BareboneHostSession (Barebone.AgentConnection? connection, Barebone.Services services) {
|
||||
public BareboneHostSession (Barebone.AgentConnection? connection, Barebone.Services? services) {
|
||||
Object (connection: connection, services: services);
|
||||
}
|
||||
|
||||
@@ -240,6 +314,11 @@ namespace Frida {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
@@ -282,10 +361,13 @@ namespace Frida {
|
||||
MainContext dbus_context = yield get_dbus_context ();
|
||||
|
||||
BareboneAgentSession session;
|
||||
if (connection != null)
|
||||
if (connection != null) {
|
||||
session = new RemoteBareboneAgentSession (connection, session_id, opts.persist_timeout, dbus_context);
|
||||
else
|
||||
} else {
|
||||
if (services == null)
|
||||
throw new Error.NOT_SUPPORTED ("Barebone target has neither an agent nor a debugger stub");
|
||||
session = new LocalBareboneAgentSession (services, session_id, opts.persist_timeout, dbus_context);
|
||||
}
|
||||
agent_sessions[session_id] = session;
|
||||
session.closed.connect (on_agent_session_closed);
|
||||
|
||||
@@ -597,6 +679,14 @@ namespace Frida {
|
||||
|
||||
public abstract async void load_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError;
|
||||
|
||||
public async void interrupt_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void terminate_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async void eternalize_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
+104
-7
@@ -116,7 +116,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ConnectionConfig : Object {
|
||||
public sealed class ConnectionConfig : Object, Json.Serializable {
|
||||
public string host {
|
||||
get;
|
||||
set;
|
||||
@@ -128,6 +128,47 @@ namespace Frida.Barebone {
|
||||
set;
|
||||
default = 3333;
|
||||
}
|
||||
|
||||
public uint pid {
|
||||
get;
|
||||
set;
|
||||
default = 0;
|
||||
}
|
||||
|
||||
public StubFlavor flavor {
|
||||
get;
|
||||
set;
|
||||
default = GDB_REMOTE;
|
||||
}
|
||||
|
||||
public bool deserialize_property (string property_name, out Value value, ParamSpec pspec, Json.Node property_node) {
|
||||
if (property_name == "flavor") {
|
||||
var v = Value (typeof (StubFlavor));
|
||||
v.set_enum (parse_stub_flavor (property_node.get_string ()));
|
||||
value = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
value = Value (pspec.value_type);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static StubFlavor parse_stub_flavor (string? name) {
|
||||
switch (name) {
|
||||
case "vz": return StubFlavor.VZ;
|
||||
default: return StubFlavor.GDB_REMOTE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects which GDB-remote dialect to speak. GDB_REMOTE drives the generic client
|
||||
* (QEMU, Corellium, debugserver); VZ drives the Apple Virtualization.framework kernel
|
||||
* stub, whose lldb-flavoured quirks the generic client cannot handle.
|
||||
*/
|
||||
public enum StubFlavor {
|
||||
GDB_REMOTE,
|
||||
VZ
|
||||
}
|
||||
|
||||
public abstract class AllocatorConfig : Object {
|
||||
@@ -174,6 +215,14 @@ namespace Frida.Barebone {
|
||||
set;
|
||||
}
|
||||
|
||||
/** Extra second argument passed to alloc_function. Modern XNU data allocators are
|
||||
* kalloc_data_external(size, flags); QEMU's kalloc(size) ignores it. Defaults to zero. */
|
||||
public uint64 alloc_flags {
|
||||
get;
|
||||
set;
|
||||
default = 0;
|
||||
}
|
||||
|
||||
public override void check () throws Error {
|
||||
if (alloc_function == null)
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'allocator.alloc_function' is missing");
|
||||
@@ -212,12 +261,12 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public void check () throws Error {
|
||||
if (path == null)
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'agent.path' is missing");
|
||||
|
||||
if (transport == null)
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'agent.transport' is missing");
|
||||
transport.check ();
|
||||
|
||||
if (path == null && !(transport is DeviceTransportConfig))
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'agent.path' is missing");
|
||||
}
|
||||
|
||||
public bool deserialize_property (string property_name, out Value value, ParamSpec pspec, Json.Node property_node) {
|
||||
@@ -229,6 +278,12 @@ namespace Frida.Barebone {
|
||||
case "hostlink":
|
||||
t = typeof (HostlinkTransportConfig);
|
||||
break;
|
||||
case "vsock":
|
||||
t = typeof (VsockTransportConfig);
|
||||
break;
|
||||
case "device":
|
||||
t = typeof (DeviceTransportConfig);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -272,6 +327,49 @@ namespace Frida.Barebone {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vsock transport. The agent inside the guest kernel connects out to the host
|
||||
* over AF_VSOCK on `port`. The host can't speak vsock directly from frida-core
|
||||
* (the hypervisor proxies it), so a UNIX-socket bridge is provided by the
|
||||
* embedder (e.g. vphone-cli); frida-core just reads/writes that socket.
|
||||
*/
|
||||
public sealed class VsockTransportConfig : TransportConfig {
|
||||
/** Path to a UNIX socket the embedder has bridged to the guest's hostlink endpoint. */
|
||||
public string socket_path {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/** Vsock port the guest agent will connect to (advertised to the agent via its config). */
|
||||
public uint port {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override void check () throws Error {
|
||||
if (socket_path == null)
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'agent.transport.socket_path' is missing");
|
||||
if (port == 0)
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'agent.transport.port' is missing");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An agent already resident in the target, reached through a device node it
|
||||
* exposes. Nothing is injected, so there is no image and no stub.
|
||||
*/
|
||||
public sealed class DeviceTransportConfig : TransportConfig {
|
||||
public string path {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
public override void check () throws Error {
|
||||
if (path == null)
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'agent.transport.path' is missing");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ImageConfig : Object, Json.Serializable {
|
||||
public string file {
|
||||
get;
|
||||
@@ -293,9 +391,8 @@ namespace Frida.Barebone {
|
||||
if (file == null)
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'image.file' is missing");
|
||||
|
||||
if (@base == null)
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'image.base' is missing");
|
||||
@base.check ();
|
||||
if (@base != null)
|
||||
@base.check ();
|
||||
|
||||
if (symbols == null)
|
||||
throw new Error.NOT_SUPPORTED ("Config for 'image.symbols' is invalid");
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace Frida.Barebone {
|
||||
uint64 base_va = allocation.virtual_address;
|
||||
|
||||
Bytes relocated_image = machine.relocate (elf, raw_elf, base_va);
|
||||
yield machine.gdb.write_byte_array (base_va, relocated_image, cancellable);
|
||||
yield machine.write_virtual (base_va, relocated_image.get_data (), cancellable);
|
||||
|
||||
yield machine.protect_pages (base_va + text_base, text_size, READ | EXECUTE, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
/**
|
||||
* Translates static kernelcache addresses to their runtime locations.
|
||||
*
|
||||
* The on-device kernel collection slides each fileset entry independently (the SPTM loader
|
||||
* scatters com.apple.kernel and every kext to unrelated runtime addresses), so a single KASLR
|
||||
* slide cannot map a static address to its runtime location. Each entry's in-memory Mach-O
|
||||
* header carries the rebased per-segment vmaddrs; we pair them with the static segment table
|
||||
* (same header, same order) to translate any static address, kernel or kext.
|
||||
*/
|
||||
public sealed class KernelRelocation : Object {
|
||||
public uint64 reference_base {
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
private Gee.List<Entry> entries = new Gee.ArrayList<Entry> ();
|
||||
|
||||
private const uint64 COLLECTION_WINDOW = 0x8000000;
|
||||
private const uint64 SUMMARY_HEADER_SIZE = 0x10;
|
||||
private const uint64 SUMMARY_SIZE = 0x88;
|
||||
private const size_t SUMMARY_LOAD_ADDRESS = 0x50;
|
||||
private const uint32 MH_MAGIC_64 = 0xfeedfacfU;
|
||||
private const uint32 MH_FILESET = 0xc;
|
||||
private const uint32 CPU_TYPE_ARM64 = 0x0100000cU;
|
||||
private const uint32 LC_SEGMENT_64 = 0x19;
|
||||
private const uint32 LC_FILESET_ENTRY = 0x80000035U;
|
||||
|
||||
public static async KernelRelocation compute (Machine machine, Bytes kernelcache_blob,
|
||||
uint64 loaded_kext_summaries, Cancellable? cancellable) throws Error, IOError {
|
||||
var gdb = machine.gdb;
|
||||
Buffer image = gdb.make_buffer (kernelcache_blob);
|
||||
Gee.List<FilesetEntry> static_entries = parse_fileset_entries (image);
|
||||
|
||||
Gee.List<SegmentInfo> static_segments = parse_segments (image, 0);
|
||||
uint64 preferred_base = static_segments[0].vmaddr;
|
||||
|
||||
uint64 collection_header = yield find_collection_header (machine, cancellable);
|
||||
Buffer collection = yield read_mach_header (gdb, collection_header, cancellable);
|
||||
var header_locator = new KernelRelocation ();
|
||||
pair_segments (header_locator, static_segments, parse_segments (collection, 0));
|
||||
|
||||
var reloc = new KernelRelocation ();
|
||||
FilesetEntry kernel = find_entry (static_entries, "com.apple.kernel");
|
||||
yield add_fileset_entry (reloc, gdb, image, kernel, header_locator.translate (kernel.vmaddr), cancellable);
|
||||
reloc.reference_base = reloc.translate (kernel.vmaddr);
|
||||
|
||||
if (loaded_kext_summaries != 0) {
|
||||
var runtime_headers = yield read_loaded_kext_headers (gdb, reloc,
|
||||
preferred_base + loaded_kext_summaries, cancellable);
|
||||
foreach (var entry in static_entries) {
|
||||
uint64? runtime_header = runtime_headers[entry.name];
|
||||
if (runtime_header != null)
|
||||
yield add_fileset_entry (reloc, gdb, image, entry, runtime_header, cancellable);
|
||||
}
|
||||
}
|
||||
return reloc;
|
||||
}
|
||||
|
||||
private static void pair_segments (KernelRelocation reloc, Gee.List<SegmentInfo> static_segments,
|
||||
Gee.List<SegmentInfo> runtime_segments) {
|
||||
for (int i = 0; i != static_segments.size; i++) {
|
||||
reloc.entries.add (new Entry () {
|
||||
static_base = static_segments[i].vmaddr,
|
||||
size = static_segments[i].vmsize,
|
||||
runtime_base = runtime_segments[i].vmaddr
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static FilesetEntry find_entry (Gee.List<FilesetEntry> entries, string name) throws Error {
|
||||
foreach (var entry in entries) {
|
||||
if (entry.name == name)
|
||||
return entry;
|
||||
}
|
||||
throw new Error.NOT_SUPPORTED ("Kernelcache is missing the %s fileset entry", name);
|
||||
}
|
||||
|
||||
private static async void add_fileset_entry (KernelRelocation reloc, GDB.Client gdb, Buffer image,
|
||||
FilesetEntry entry, uint64 runtime_header, Cancellable? cancellable) throws Error, IOError {
|
||||
Buffer runtime_image = yield read_mach_header (gdb, runtime_header, cancellable);
|
||||
pair_segments (reloc, parse_segments (image, (size_t) entry.fileoff), parse_segments (runtime_image, 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* The runtime kext metadata embedded in the collection header is unusable: the loader clobbers the
|
||||
* fileset-entry vmaddrs and repacks the kext mach-headers away from their static positions. The
|
||||
* gLoadedKextSummaries array, on the other hand, is purpose-built for debuggers — it pairs each
|
||||
* loaded kext's bundle id with the runtime address of its mach-header, giving us a reliable
|
||||
* name -> runtime-header map from which to translate that kext's independently scattered segments.
|
||||
*/
|
||||
private static async Gee.Map<string, uint64?> read_loaded_kext_headers (GDB.Client gdb,
|
||||
KernelRelocation reloc, uint64 loaded_kext_summaries, Cancellable? cancellable) throws Error, IOError {
|
||||
uint64 list = (yield gdb.read_buffer (reloc.translate (loaded_kext_summaries), 8, cancellable))
|
||||
.read_uint64 (0);
|
||||
uint32 count = (yield gdb.read_buffer (list, 16, cancellable)).read_uint32 (8);
|
||||
|
||||
var result = new Gee.HashMap<string, uint64?> ();
|
||||
for (uint32 i = 0; i != count; i++) {
|
||||
Buffer summary = yield gdb.read_buffer (list + SUMMARY_HEADER_SIZE + (uint64) i * SUMMARY_SIZE,
|
||||
(size_t) SUMMARY_SIZE, cancellable);
|
||||
result[read_lc_string (summary, 0)] = summary.read_uint64 (SUMMARY_LOAD_ADDRESS);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public uint64 translate (uint64 static_address) throws Error {
|
||||
foreach (var e in entries) {
|
||||
if (static_address >= e.static_base && static_address < e.static_base + e.size)
|
||||
return e.runtime_base + (static_address - e.static_base);
|
||||
}
|
||||
throw new Error.INVALID_ARGUMENT ("Address 0x%" + uint64.FORMAT_MODIFIER + "x is outside the kernelcache",
|
||||
static_address);
|
||||
}
|
||||
|
||||
public uint32 runtime_offset (uint64 static_address) throws Error {
|
||||
return (uint32) (translate (static_address) - reference_base);
|
||||
}
|
||||
|
||||
private static async Buffer read_mach_header (GDB.Client gdb, uint64 address, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
Buffer head = yield gdb.read_buffer (address, 32, cancellable);
|
||||
return yield gdb.read_buffer (address, 32 + head.read_uint32 (20), cancellable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads of unmapped memory wedge the VZ stub indefinitely, so the collection header cannot be
|
||||
* located by blind-scanning down from a kernel pointer: scattered segments leave unmapped gaps
|
||||
* in between. We instead walk the kernel's own page tables for the readable ranges and probe
|
||||
* only the first 16 bytes of each — every byte touched is guaranteed mapped.
|
||||
*/
|
||||
private static async uint64 find_collection_header (Machine machine, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var gdb = machine.gdb;
|
||||
|
||||
uint64 vbar = yield gdb.exception.thread.read_register ("vbar_el1", cancellable);
|
||||
|
||||
var range_bases = new Gee.ArrayList<uint64?> ();
|
||||
var range_sizes = new Gee.ArrayList<uint64?> ();
|
||||
yield machine.enumerate_ranges (Gum.PageProtection.READ, details => {
|
||||
if (details.base_va < vbar - COLLECTION_WINDOW || details.base_va > vbar + COLLECTION_WINDOW)
|
||||
return true;
|
||||
range_bases.add (details.base_va);
|
||||
range_sizes.add (details.size);
|
||||
return true;
|
||||
}, cancellable);
|
||||
|
||||
for (int i = 0; i != range_bases.size; i++) {
|
||||
uint64 range_base = range_bases[i];
|
||||
if (range_sizes[i] < 16)
|
||||
continue;
|
||||
Buffer head = yield gdb.read_buffer (range_base, 16, cancellable);
|
||||
if (head.read_uint32 (0) == MH_MAGIC_64 && head.read_uint32 (4) == CPU_TYPE_ARM64
|
||||
&& head.read_uint32 (12) == MH_FILESET)
|
||||
return range_base;
|
||||
}
|
||||
throw new Error.NOT_SUPPORTED ("Unable to locate the runtime kernel collection header");
|
||||
}
|
||||
|
||||
private static Gee.List<FilesetEntry> parse_fileset_entries (Buffer image) throws Error {
|
||||
var result = new Gee.ArrayList<FilesetEntry> ();
|
||||
uint32 ncmds = image.read_uint32 (16);
|
||||
size_t off = 32;
|
||||
for (uint32 i = 0; i != ncmds; i++) {
|
||||
uint32 cmd = image.read_uint32 (off);
|
||||
uint32 cmdsize = image.read_uint32 (off + 4);
|
||||
if (cmd == LC_FILESET_ENTRY) {
|
||||
result.add (new FilesetEntry () {
|
||||
name = read_lc_string (image, off + image.read_uint32 (off + 24)),
|
||||
vmaddr = image.read_uint64 (off + 8),
|
||||
fileoff = image.read_uint64 (off + 16)
|
||||
});
|
||||
}
|
||||
off += cmdsize;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Gee.List<SegmentInfo> parse_segments (Buffer image, size_t header_offset) throws Error {
|
||||
var result = new Gee.ArrayList<SegmentInfo> ();
|
||||
uint32 ncmds = image.read_uint32 (header_offset + 16);
|
||||
size_t off = header_offset + 32;
|
||||
for (uint32 i = 0; i != ncmds; i++) {
|
||||
uint32 cmd = image.read_uint32 (off);
|
||||
uint32 cmdsize = image.read_uint32 (off + 4);
|
||||
if (cmd == LC_SEGMENT_64) {
|
||||
result.add (new SegmentInfo () {
|
||||
vmaddr = image.read_uint64 (off + 24),
|
||||
vmsize = image.read_uint64 (off + 32)
|
||||
});
|
||||
}
|
||||
off += cmdsize;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string read_lc_string (Buffer image, size_t offset) throws Error {
|
||||
var builder = new StringBuilder ();
|
||||
for (size_t i = 0; ; i++) {
|
||||
uint8 c = image.read_uint8 (offset + i);
|
||||
if (c == 0)
|
||||
break;
|
||||
builder.append_c ((char) c);
|
||||
}
|
||||
return builder.str;
|
||||
}
|
||||
|
||||
private class FilesetEntry {
|
||||
public string name;
|
||||
public uint64 vmaddr;
|
||||
public uint64 fileoff;
|
||||
}
|
||||
|
||||
private class SegmentInfo {
|
||||
public uint64 vmaddr;
|
||||
public uint64 vmsize;
|
||||
}
|
||||
|
||||
private class Entry {
|
||||
public uint64 static_base;
|
||||
public uint64 size;
|
||||
public uint64 runtime_base;
|
||||
}
|
||||
}
|
||||
}
|
||||
+89
-13
@@ -12,6 +12,10 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private const uint64 CHAINED_PTR_TARGET_MASK = (1 << 30) - 1;
|
||||
private const uint32 MH_MAGIC_64 = 0xfeedfacfU;
|
||||
private const uint32 MH_FILESET = 0xc;
|
||||
private const uint32 LC_SEGMENT_64 = 0x19;
|
||||
private const uint32 LC_FILESET_ENTRY = 0x80000035U;
|
||||
|
||||
private Layout (Gee.List<ModuleInfo> modules, Gee.List<SymbolInfo> symbols) {
|
||||
Object (modules: modules, symbols: symbols);
|
||||
@@ -21,19 +25,16 @@ namespace Frida.Barebone {
|
||||
Object (modules: new Gee.ArrayList<ModuleInfo> (), symbols: new Gee.ArrayList<SymbolInfo> ());
|
||||
}
|
||||
|
||||
public static async Layout load_from_symbol_source (File symbol_source, uint64 kernel_base, ByteOrder byte_order,
|
||||
uint pointer_size, Cancellable? cancellable) throws Error, IOError {
|
||||
var payload = yield Img4.parse_file (symbol_source, cancellable);
|
||||
|
||||
Bytes kerncache = payload.data;
|
||||
|
||||
Gum.DarwinModule mod;
|
||||
public static Gum.DarwinModule parse_kernelcache (Bytes kerncache) throws Error {
|
||||
try {
|
||||
mod = new Gum.DarwinModule.from_blob (kerncache, ARM64, Gum.PtrauthSupport.SUPPORTED);
|
||||
return new Gum.DarwinModule.from_blob (kerncache, ARM64, Gum.PtrauthSupport.SUPPORTED);
|
||||
} catch (Gum.Error e) {
|
||||
throw new Error.NOT_SUPPORTED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public static Layout load_from_module (Gum.DarwinModule mod, Bytes kerncache, uint64 kernel_base, ByteOrder byte_order,
|
||||
uint pointer_size) throws Error {
|
||||
var blob = new Blob (kerncache, byte_order, pointer_size);
|
||||
|
||||
var modules = compute_module_list (mod, blob, kernel_base);
|
||||
@@ -43,6 +44,10 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private static Gee.List<ModuleInfo> compute_module_list (Gum.DarwinModule mod, Blob blob, uint64 kernel_base) throws Error {
|
||||
var fileset = compute_fileset_module_list (blob, mod.source_version);
|
||||
if (fileset != null)
|
||||
return fileset;
|
||||
|
||||
Buffer? kmod_info = null;
|
||||
Buffer? kmod_start = null;
|
||||
Error? pending_error = null;
|
||||
@@ -62,10 +67,16 @@ namespace Frida.Barebone {
|
||||
});
|
||||
if (pending_error != null)
|
||||
throw pending_error;
|
||||
if (kmod_info == null)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to find __kmod_info");
|
||||
if (kmod_start == null)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to find __kmod_start");
|
||||
if (kmod_info == null || kmod_start == null) {
|
||||
var only_kernel = new Gee.ArrayList<ModuleInfo> ();
|
||||
only_kernel.add (new ModuleInfo () {
|
||||
name = "mach_kernel",
|
||||
version = mod.source_version ?? "",
|
||||
offset = 0,
|
||||
size = (uint32) blob.size,
|
||||
});
|
||||
return only_kernel;
|
||||
}
|
||||
|
||||
var kexts = new Gee.ArrayList<ModuleInfo> ();
|
||||
var info_pointers = new BufferReader (kmod_info);
|
||||
@@ -118,6 +129,66 @@ namespace Frida.Barebone {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static Gee.List<ModuleInfo>? compute_fileset_module_list (Blob blob, string? kernel_version) throws Error {
|
||||
var header = blob.slice (0, 32, "Mach-O header");
|
||||
if (header.read_uint32 (0) != MH_MAGIC_64 || header.read_uint32 (12) != MH_FILESET)
|
||||
return null;
|
||||
|
||||
var image = blob.slice (0, 32 + header.read_uint32 (20), "Mach-O load commands");
|
||||
|
||||
var entries = new Gee.ArrayList<FilesetEntry> ();
|
||||
uint64 image_end = 0;
|
||||
uint32 ncmds = image.read_uint32 (16);
|
||||
size_t off = 32;
|
||||
for (uint32 i = 0; i != ncmds; i++) {
|
||||
uint32 cmd = image.read_uint32 (off);
|
||||
uint32 cmdsize = image.read_uint32 (off + 4);
|
||||
if (cmd == LC_FILESET_ENTRY) {
|
||||
entries.add (new FilesetEntry () {
|
||||
name = read_lc_string (image, off + image.read_uint32 (off + 24)),
|
||||
vmaddr = image.read_uint64 (off + 8)
|
||||
});
|
||||
} else if (cmd == LC_SEGMENT_64) {
|
||||
image_end = uint64.max (image_end,
|
||||
image.read_uint64 (off + 24) + image.read_uint64 (off + 32));
|
||||
}
|
||||
off += cmdsize;
|
||||
}
|
||||
|
||||
entries.sort ((a, b) => {
|
||||
if (a.vmaddr < b.vmaddr)
|
||||
return -1;
|
||||
return (a.vmaddr > b.vmaddr) ? 1 : 0;
|
||||
});
|
||||
|
||||
uint64 kernel_vmaddr = entries[0].vmaddr;
|
||||
var modules = new Gee.ArrayList<ModuleInfo> ();
|
||||
int n = entries.size;
|
||||
for (int i = 0; i != n; i++) {
|
||||
var entry = entries[i];
|
||||
uint64 end = (i + 1 != n) ? entries[i + 1].vmaddr : image_end;
|
||||
bool is_kernel = i == 0;
|
||||
modules.add (new ModuleInfo () {
|
||||
name = is_kernel ? "mach_kernel" : entry.name,
|
||||
version = is_kernel ? (kernel_version ?? "") : "",
|
||||
offset = (uint32) (entry.vmaddr - kernel_vmaddr),
|
||||
size = (uint32) (end - entry.vmaddr)
|
||||
});
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
private static string read_lc_string (Buffer image, size_t offset) throws Error {
|
||||
var builder = new StringBuilder ();
|
||||
for (size_t i = 0; ; i++) {
|
||||
uint8 c = image.read_uint8 (offset + i);
|
||||
if (c == 0)
|
||||
break;
|
||||
builder.append_c ((char) c);
|
||||
}
|
||||
return builder.str;
|
||||
}
|
||||
|
||||
private static size_t chained_pointer_to_vm_offset (uint64 val, uint64 kernel_base) {
|
||||
var target = (size_t) (val & CHAINED_PTR_TARGET_MASK);
|
||||
|
||||
@@ -153,6 +224,11 @@ namespace Frida.Barebone {
|
||||
public uint32 stop_func_offset;
|
||||
}
|
||||
|
||||
private class FilesetEntry {
|
||||
public string name;
|
||||
public uint64 vmaddr;
|
||||
}
|
||||
|
||||
public class SymbolInfo {
|
||||
public string name;
|
||||
public uint32 offset;
|
||||
@@ -163,7 +239,7 @@ namespace Frida.Barebone {
|
||||
|
||||
private class Blob {
|
||||
private Bytes bytes;
|
||||
private size_t size;
|
||||
public size_t size;
|
||||
public ByteOrder byte_order;
|
||||
public uint pointer_size;
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public interface PhysicalMemory : Object {
|
||||
public abstract uint8[] read (uint64 pa, size_t size) throws Error;
|
||||
public abstract void write (uint64 pa, uint8[] data) throws Error;
|
||||
public abstract bool contains (uint64 pa);
|
||||
}
|
||||
|
||||
public interface Machine : Object {
|
||||
public abstract GDB.Client gdb {
|
||||
get;
|
||||
@@ -16,6 +22,10 @@ namespace Frida.Barebone {
|
||||
|
||||
public abstract async size_t query_page_size (Cancellable? cancellable) throws Error, IOError;
|
||||
|
||||
public virtual async void write_virtual (uint64 va, uint8[] data, Cancellable? cancellable) throws Error, IOError {
|
||||
yield gdb.write_byte_array (va, new Bytes (data), cancellable);
|
||||
}
|
||||
|
||||
public abstract async uint query_exception_level (Cancellable? cancellable) throws Error, IOError;
|
||||
|
||||
public async void enter_exception_level (uint level, uint timeout, Cancellable? cancellable) throws Error, IOError {
|
||||
|
||||
Loaded 100 of 277 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user