mirror of
https://github.com/frida/frida-core.git
synced 2026-11-08 17:12:49 -10:00
Compare commits
356
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,3 +1,4 @@
|
||||
/.cache/
|
||||
/build/
|
||||
/deps/
|
||||
/src/android-helper/build/
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+218
-57
@@ -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()
|
||||
@@ -192,16 +203,21 @@ def setup(role: Role,
|
||||
|
||||
if host_os == "windows" and host_arch in {"x86_64", "x86"} and have_toolchain:
|
||||
group = OutputGroup(other_label, other_triplet, extra_environ)
|
||||
outputs[group] = [
|
||||
Output(identifier=f"helper_{other_kind}",
|
||||
name=HELPER_FILE_WINDOWS.name,
|
||||
file=HELPER_FILE_WINDOWS,
|
||||
target=HELPER_TARGET),
|
||||
Output(identifier=f"agent_{other_kind}",
|
||||
name=AGENT_FILE_WINDOWS.name,
|
||||
file=AGENT_FILE_WINDOWS,
|
||||
target=AGENT_TARGET),
|
||||
]
|
||||
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=f"gadget_{other_kind}",
|
||||
@@ -218,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}",
|
||||
@@ -245,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",
|
||||
@@ -265,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",
|
||||
@@ -295,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",
|
||||
@@ -321,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:
|
||||
@@ -360,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]]
|
||||
|
||||
@@ -440,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)
|
||||
@@ -484,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]:
|
||||
@@ -521,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"}:
|
||||
@@ -593,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")
|
||||
@@ -643,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"
|
||||
@@ -670,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
|
||||
|
||||
|
||||
+49
-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
|
||||
@@ -271,6 +292,8 @@ namespace Frida.Agent {
|
||||
|
||||
start.begin ((owned) padder);
|
||||
|
||||
start_alive_task_dumper ();
|
||||
|
||||
main_loop.run ();
|
||||
|
||||
main_context.pop_thread_default ();
|
||||
@@ -282,30 +305,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 +339,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;
|
||||
@@ -342,6 +364,16 @@ namespace Frida.Agent {
|
||||
padder = null;
|
||||
}
|
||||
|
||||
// TEMPORARY DEBUG HACK: dump alive GTasks every 30 seconds.
|
||||
private void start_alive_task_dumper () {
|
||||
var source = new TimeoutSource.seconds (30);
|
||||
source.set_callback (() => {
|
||||
GLib.info ("%s", g_task_describe_alive_tasks ());
|
||||
return Source.CONTINUE;
|
||||
});
|
||||
source.attach (main_context);
|
||||
}
|
||||
|
||||
private void keep_running_eternalized () {
|
||||
agent_gthread = new Thread<bool> ("frida-eternal-agent", () => {
|
||||
var ignore_scope = new ThreadIgnoreScope (FRIDA_THREAD);
|
||||
@@ -904,6 +936,8 @@ namespace Frida.Agent {
|
||||
private void on_session_closed (BaseAgentSession base_session) {
|
||||
LiveAgentSession session = (LiveAgentSession) base_session;
|
||||
|
||||
GLib.info ("detach-trace: Agent emitting provider closed id=%s", session.id.handle);
|
||||
|
||||
closed (session.id);
|
||||
|
||||
unregister_session (session);
|
||||
@@ -1704,4 +1738,8 @@ namespace Frida.Agent {
|
||||
return repeat;
|
||||
});
|
||||
}
|
||||
|
||||
// TEMPORARY DEBUG HACK: only present when GLib is built with G_ENABLE_DEBUG.
|
||||
[CCode (cname = "g_task_describe_alive_tasks")]
|
||||
private extern string g_task_describe_alive_tasks ();
|
||||
}
|
||||
+119
-96
@@ -1,104 +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
|
||||
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,
|
||||
)
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
-10
@@ -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[128 * 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 {
|
||||
@@ -396,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);
|
||||
@@ -522,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 {
|
||||
@@ -536,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
|
||||
}
|
||||
+15
-10
@@ -1,4 +1,5 @@
|
||||
base_sources = [
|
||||
'asset-location.vala',
|
||||
'session.vala',
|
||||
'session-glue.c',
|
||||
'channel.vala',
|
||||
@@ -47,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',
|
||||
@@ -75,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
|
||||
|
||||
@@ -112,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],
|
||||
)
|
||||
|
||||
@@ -124,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;
|
||||
}
|
||||
}
|
||||
+606
-1
@@ -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;
|
||||
@@ -356,6 +361,8 @@ namespace Frida {
|
||||
|
||||
private async void teardown_peer_connection_and_emit_closed () {
|
||||
schedule_on_frida_thread (() => {
|
||||
GLib.info ("detach-trace: AgentMessageTransmitter %p emitting closed, nice_agent=%p",
|
||||
this, nice_agent);
|
||||
if (nice_agent != null)
|
||||
close_nice_resources_and_emit_closed.begin ();
|
||||
else
|
||||
@@ -593,6 +600,7 @@ namespace Frida {
|
||||
|
||||
private async void teardown_peer_connection_and_emit_closed () {
|
||||
schedule_on_frida_thread (() => {
|
||||
GLib.info ("detach-trace: AgentMessageTransmitter %p emitting closed", this);
|
||||
closed ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
@@ -755,10 +763,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 +825,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 +909,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 +993,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 +1055,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 +1155,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 +1213,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 +1334,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 +1370,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 +1386,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 +1451,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 +1467,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 +1532,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 +1558,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 +1689,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 +1798,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 +1844,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 +1962,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 +2093,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 +2194,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 +2262,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 +2304,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 +2379,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 +2394,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 +2460,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 +2531,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
|
||||
}
|
||||
|
||||
|
||||
+239
-11
@@ -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,8 +1163,11 @@ namespace Frida {
|
||||
Memory.copy (buffer, recv_queue.data, n);
|
||||
recv_queue.remove_range (0, (uint) n);
|
||||
|
||||
if (recv_queue.len == 0)
|
||||
if (recv_queue.len == 0) {
|
||||
#if HAVE_SOUP_WEBSOCKET_BACKPRESSURE
|
||||
websocket.resume_input ();
|
||||
#endif
|
||||
}
|
||||
|
||||
update_pending_io ();
|
||||
} else {
|
||||
@@ -1009,7 +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;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,9 @@ namespace Frida {
|
||||
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,
|
||||
@@ -81,6 +84,9 @@ namespace Frida {
|
||||
IOKIT_USER_CLIENT = -100,
|
||||
}
|
||||
|
||||
/**
|
||||
* The XNU BSD syscall numbers, as used on Apple platforms.
|
||||
*/
|
||||
public enum XnuBsdSyscall {
|
||||
EXIT = 1,
|
||||
FORK = 2,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
+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
|
||||
@@ -57,6 +57,8 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
GLib.info ("detach-trace: BaseAgentSession.close() id=%s close_request=%p", id.handle, close_request);
|
||||
|
||||
while (close_request != null) {
|
||||
try {
|
||||
yield close_request.future.wait_async (cancellable);
|
||||
@@ -85,6 +87,8 @@ namespace Frida {
|
||||
|
||||
yield transmitter.close (cancellable);
|
||||
|
||||
GLib.info ("detach-trace: BaseAgentSession.close() done id=%s", id.handle);
|
||||
|
||||
close_request.resolve (true);
|
||||
}
|
||||
|
||||
@@ -179,6 +183,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 ();
|
||||
|
||||
@@ -264,6 +280,8 @@ namespace Frida {
|
||||
transmitter.new_candidates.disconnect (on_transmitter_new_candidates);
|
||||
transmitter.candidate_gathering_done.disconnect (on_transmitter_candidate_gathering_done);
|
||||
|
||||
GLib.info ("detach-trace: BaseAgentSession emitting closed id=%s", id.handle);
|
||||
|
||||
closed ();
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+100
-10
@@ -24,6 +24,7 @@
|
||||
# include <xlocale.h>
|
||||
#endif
|
||||
|
||||
#undef __srget
|
||||
#undef feof
|
||||
#undef ferror
|
||||
#undef fgetwc
|
||||
@@ -43,6 +44,9 @@
|
||||
#undef putc
|
||||
#undef putchar
|
||||
#undef putwc
|
||||
#undef setbuf
|
||||
#undef setbuffer
|
||||
#undef setlinebuf
|
||||
#undef snprintf
|
||||
#undef sprintf
|
||||
#undef stderr
|
||||
@@ -161,11 +165,13 @@ 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;
|
||||
|
||||
@@ -776,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;
|
||||
@@ -789,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;
|
||||
@@ -803,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)
|
||||
{
|
||||
@@ -905,6 +980,12 @@ 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)
|
||||
{
|
||||
@@ -1972,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;
|
||||
@@ -1987,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);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
+297
-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)
|
||||
@@ -312,15 +333,9 @@ 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',
|
||||
@@ -336,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',
|
||||
@@ -365,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')
|
||||
@@ -385,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'
|
||||
@@ -434,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') \
|
||||
@@ -453,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()
|
||||
@@ -463,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, '<<<',
|
||||
@@ -478,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]
|
||||
@@ -552,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'
|
||||
@@ -562,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 = []
|
||||
@@ -575,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'
|
||||
@@ -587,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()
|
||||
@@ -615,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
|
||||
@@ -629,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',
|
||||
@@ -673,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)
|
||||
@@ -684,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']
|
||||
@@ -757,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')
|
||||
@@ -787,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
|
||||
@@ -803,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')
|
||||
@@ -844,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: 852c527081...abb8238970.
@@ -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'],
|
||||
|
||||
+89
-8
@@ -12,6 +12,7 @@ namespace Frida.Server {
|
||||
private static string? directory = null;
|
||||
#if !WINDOWS && !TVOS
|
||||
private static bool daemonize = false;
|
||||
private static string? output_path = null;
|
||||
#endif
|
||||
private static string? softener_flavor_str = null;
|
||||
private static bool enable_preload = true;
|
||||
@@ -42,6 +43,7 @@ namespace Frida.Server {
|
||||
{ "directory", 'd', 0, OptionArg.STRING, ref directory, "Store binaries in DIRECTORY", "DIRECTORY" },
|
||||
#if !WINDOWS && !TVOS
|
||||
{ "daemonize", 'D', 0, OptionArg.NONE, ref daemonize, "Detach and become a daemon", null },
|
||||
{ "output", 'o', 0, OptionArg.FILENAME, ref output_path, "Redirect output to FILE", "FILE" },
|
||||
#endif
|
||||
{ "policy-softener", 0, 0, OptionArg.STRING, ref softener_flavor_str, "Select policy softener", "system|internal" },
|
||||
{ "disable-preload", 'P', OptionFlags.REVERSE, OptionArg.NONE, ref enable_preload, "Disable preload optimization", null },
|
||||
@@ -78,6 +80,13 @@ namespace Frida.Server {
|
||||
|
||||
Environment.set_verbose_logging_enabled (verbose);
|
||||
|
||||
#if !WINDOWS && !TVOS
|
||||
if (output_path != null && !daemonize) {
|
||||
if (!redirect_output_to (output_path))
|
||||
return 6;
|
||||
}
|
||||
#endif
|
||||
|
||||
EndpointParameters endpoint_params;
|
||||
try {
|
||||
endpoint_params = new EndpointParameters (listen_address, 0, parse_certificate (certpath), origin,
|
||||
@@ -104,9 +113,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;
|
||||
@@ -157,12 +165,15 @@ namespace Frida.Server {
|
||||
Posix.setsid ();
|
||||
|
||||
var null_in = Posix.open ("/dev/null", Posix.O_RDONLY);
|
||||
var null_out = Posix.open ("/dev/null", Posix.O_WRONLY);
|
||||
Posix.dup2 (null_in, Posix.STDIN_FILENO);
|
||||
Posix.dup2 (null_out, Posix.STDOUT_FILENO);
|
||||
Posix.dup2 (null_out, Posix.STDERR_FILENO);
|
||||
Posix.close (null_in);
|
||||
Posix.close (null_out);
|
||||
|
||||
if (output_path == null || !redirect_output_to (output_path)) {
|
||||
var null_out = Posix.open ("/dev/null", Posix.O_WRONLY);
|
||||
Posix.dup2 (null_out, Posix.STDOUT_FILENO);
|
||||
Posix.dup2 (null_out, Posix.STDERR_FILENO);
|
||||
Posix.close (null_out);
|
||||
}
|
||||
}
|
||||
|
||||
var status = new uint8[1];
|
||||
@@ -199,7 +210,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);
|
||||
|
||||
@@ -220,6 +230,22 @@ namespace Frida.Server {
|
||||
return application.run ();
|
||||
}
|
||||
|
||||
#if !WINDOWS && !TVOS
|
||||
private static bool redirect_output_to (string path) {
|
||||
int fd = Posix.open (path, Posix.O_WRONLY | Posix.O_CREAT | Posix.O_APPEND, 0644);
|
||||
if (fd == -1) {
|
||||
printerr ("Unable to open “%s”: %s\n", path, Posix.strerror (Posix.errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
Posix.dup2 (fd, Posix.STDOUT_FILENO);
|
||||
Posix.dup2 (fd, Posix.STDERR_FILENO);
|
||||
Posix.close (fd);
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace Environment {
|
||||
public extern void init ();
|
||||
public extern void set_verbose_logging_enabled (bool enabled);
|
||||
@@ -276,6 +302,15 @@ namespace Frida.Server {
|
||||
return false;
|
||||
});
|
||||
|
||||
#if !WINDOWS && !TVOS
|
||||
if (!daemonize)
|
||||
process_commands.begin ();
|
||||
#elif !WINDOWS
|
||||
process_commands.begin ();
|
||||
#endif
|
||||
|
||||
start_alive_task_dumper ();
|
||||
|
||||
exit_code = 0;
|
||||
|
||||
loop.run ();
|
||||
@@ -283,6 +318,45 @@ namespace Frida.Server {
|
||||
return exit_code;
|
||||
}
|
||||
|
||||
#if !WINDOWS
|
||||
// TEMPORARY DEBUG HACK: type “t” + Enter to dump alive GTasks.
|
||||
private async void process_commands () {
|
||||
var input = new DataInputStream (new UnixInputStream (Posix.STDIN_FILENO, false));
|
||||
|
||||
while (true) {
|
||||
string? line;
|
||||
try {
|
||||
line = yield input.read_line_async (Priority.DEFAULT, io_cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
return;
|
||||
}
|
||||
if (line == null)
|
||||
return;
|
||||
|
||||
switch (line.strip ()) {
|
||||
case "t":
|
||||
g_task_print_alive_tasks ();
|
||||
break;
|
||||
case "":
|
||||
break;
|
||||
default:
|
||||
printerr ("Unknown command; try “t”\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// TEMPORARY DEBUG HACK: dump alive GTasks every 30 seconds.
|
||||
private void start_alive_task_dumper () {
|
||||
var source = new TimeoutSource.seconds (30);
|
||||
source.set_callback (() => {
|
||||
GLib.info ("%s", g_task_describe_alive_tasks ());
|
||||
return Source.CONTINUE;
|
||||
});
|
||||
source.attach (MainContext.default ());
|
||||
}
|
||||
|
||||
private async void start () {
|
||||
try {
|
||||
if (device_id != null && device_id != "local") {
|
||||
@@ -357,4 +431,11 @@ namespace Frida.Server {
|
||||
|
||||
return new TlsCertificate.from_file (path);
|
||||
}
|
||||
|
||||
// TEMPORARY DEBUG HACK: only present when GLib is built with G_ENABLE_DEBUG.
|
||||
[CCode (cname = "g_task_print_alive_tasks")]
|
||||
private extern void g_task_print_alive_tasks ();
|
||||
|
||||
[CCode (cname = "g_task_describe_alive_tasks")]
|
||||
private extern string g_task_describe_alive_tasks ();
|
||||
}
|
||||
@@ -23,7 +23,7 @@ namespace Frida {
|
||||
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;
|
||||
@@ -75,7 +75,7 @@ namespace Frida {
|
||||
|
||||
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)
|
||||
@@ -141,7 +141,7 @@ namespace Frida {
|
||||
|
||||
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)
|
||||
@@ -195,7 +195,7 @@ namespace Frida {
|
||||
|
||||
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);
|
||||
@@ -265,7 +265,7 @@ namespace Frida {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -279,7 +279,7 @@ namespace Frida {
|
||||
|
||||
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);
|
||||
@@ -292,7 +292,7 @@ namespace Frida {
|
||||
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 = new Json.Reader (response);
|
||||
var r = make_json_reader_taking_node ((owned) response);
|
||||
parse_envelope_or_throw (r);
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -100,6 +100,8 @@ public class HelperBackend {
|
||||
public HelperBackend() {
|
||||
Looper.prepareMainLooper();
|
||||
|
||||
preInitVendorExtensions();
|
||||
|
||||
Context context;
|
||||
try {
|
||||
Class<?> ActivityThread = Class.forName("android.app.ActivityThread");
|
||||
@@ -193,6 +195,23 @@ public class HelperBackend {
|
||||
}
|
||||
}
|
||||
|
||||
private static void preInitVendorExtensions() {
|
||||
// Nothing OS: AppOpsManager.<clinit> chains into NtAppOpsManager.<clinit>, which
|
||||
// calls NtExtFactory.getOrCreate(...). The factory dispatches through a static
|
||||
// INtExtFactory singleton populated by NtExtFactory.init() — normally invoked
|
||||
// from RuntimeInit.commonInit() during process bring-up, which our helper
|
||||
// bypasses. Invoke it ourselves so the static initializer chain doesn't NPE.
|
||||
invokeStaticVoidIfPresent("com.nothing.NtExtFactory", "init");
|
||||
}
|
||||
|
||||
private static void invokeStaticVoidIfPresent(String className, String methodName) {
|
||||
try {
|
||||
Class.forName(className).getMethod(methodName).invoke(null);
|
||||
} catch (ClassNotFoundException e) {
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean tryInitActivityStartApi() {
|
||||
try {
|
||||
Class<?> ActivityTaskManager = Class.forName("android.app.ActivityTaskManager");
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+395
-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",
|
||||
|
||||
+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)
|
||||
+253
-151
@@ -10,25 +10,29 @@ namespace Frida.Barebone {
|
||||
private OutputStream output;
|
||||
|
||||
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 +46,31 @@ 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");
|
||||
private const uint8 TRANSPORT_KIND_VIRTIO = 0;
|
||||
private const uint8 TRANSPORT_KIND_VSOCK = 1;
|
||||
|
||||
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 ();
|
||||
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;
|
||||
|
||||
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 +86,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 +105,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 +173,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 +187,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 (SocketConnection 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 +320,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;
|
||||
@@ -324,6 +394,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 +449,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 (machine.gdb.byte_order != ByteOrder.HOST)
|
||||
message = message.byteswap ();
|
||||
var message_bytes = message.get_data_as_bytes ();
|
||||
return machine.gdb.make_buffer_builder ()
|
||||
.append_uint32 ((uint32) message_bytes.get_size ())
|
||||
.append_bytes (message_bytes)
|
||||
.build ();
|
||||
}
|
||||
|
||||
private enum Command {
|
||||
@@ -418,6 +517,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
|
||||
}
|
||||
|
||||
@@ -32,6 +32,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*)
|
||||
|
||||
@@ -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,52 @@
|
||||
use core::ffi::c_void;
|
||||
|
||||
use crate::bindings::{gpointer, gsize, GumPageProtection};
|
||||
use crate::{gum, libc, xnu};
|
||||
|
||||
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
|
||||
// over the hostlink 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 = xnu::kalloc(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::gum_try_mprotect(base as gpointer, map_size as gsize, GUM_PAGE_READ | GUM_PAGE_EXECUTE);
|
||||
if made_executable == 0 {
|
||||
xnu::free(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,
|
||||
) {
|
||||
unsafe {
|
||||
xnu::free(code_table as *mut u8, map_size * 2);
|
||||
}
|
||||
}
|
||||
@@ -600,10 +600,14 @@ unsafe fn lock_notify_all() {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 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
|
||||
thread_ptr & JS_SAFE_THREAD_ID_MASK
|
||||
}
|
||||
+191
-16
@@ -1,24 +1,81 @@
|
||||
use crate::{
|
||||
bindings::{
|
||||
_GInterfaceInfo, _GTypeInfo, _GumPageProtection_GUM_PAGE_EXECUTE,
|
||||
_GumPageProtection_GUM_PAGE_READ, _GumPageProtection_GUM_PAGE_WRITE,
|
||||
_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,
|
||||
g_type_register_static, g_variant_get_boolean, g_variant_get_uint64, g_variant_new,
|
||||
g_variant_new_fixed_array, g_variant_type_free, g_variant_type_new, 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,
|
||||
g_variant_unref, gum_module_get_type, gum_mprotect, gum_query_page_size,
|
||||
},
|
||||
gthread, libc, xnu,
|
||||
gthread, host_rpc, libc, FridaCommand,
|
||||
};
|
||||
use alloc::boxed::Box;
|
||||
use alloc::ffi::CString;
|
||||
use alloc::format;
|
||||
use alloc::vec::Vec;
|
||||
use core::ffi::CStr;
|
||||
use core::mem::size_of;
|
||||
use core::ptr;
|
||||
use core::{ffi::CStr, ptr::read_volatile};
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
// 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/kext text is CTRR-locked and can only be
|
||||
// modified through the host's physical-memory bridge, so we shadow it and commit on dispose. 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();
|
||||
|
||||
const SHADOW_MAGIC: u64 = 0x4644_4f48_5341_4853;
|
||||
const SHADOW_HEADER: usize = 24;
|
||||
const SHADOW_MIN_ADDRESS: u64 = 0xffff_f000_0000_0000;
|
||||
|
||||
fn slab_lock() {
|
||||
while SLAB_LOCK
|
||||
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
}
|
||||
|
||||
fn slab_unlock() {
|
||||
SLAB_LOCK.store(0, Ordering::Release);
|
||||
}
|
||||
|
||||
fn register_slab(start: u64, size: usize) {
|
||||
slab_lock();
|
||||
unsafe {
|
||||
(*core::ptr::addr_of_mut!(SLABS)).push((start, start + size as u64));
|
||||
}
|
||||
slab_unlock();
|
||||
}
|
||||
|
||||
fn unregister_slab(start: u64) {
|
||||
slab_lock();
|
||||
unsafe {
|
||||
(*core::ptr::addr_of_mut!(SLABS)).retain(|&(begin, _)| begin != start);
|
||||
}
|
||||
slab_unlock();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_process_get_current_thread_id() -> GumThreadId {
|
||||
@@ -57,43 +114,146 @@ pub extern "C" fn gum_memory_try_remap_writable_pages(
|
||||
first_page: gpointer,
|
||||
n_pages: guint,
|
||||
) -> gpointer {
|
||||
if 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 physical_addrs = alloc::vec::Vec::with_capacity(n_pages 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 {
|
||||
let phys_addr = xnu::ml_vtophys(current_page);
|
||||
physical_addrs.push(phys_addr as gpointer);
|
||||
virtual_addrs.push(current_page 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,
|
||||
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 = crate::xnu::kalloc(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);
|
||||
|
||||
crate::xnu::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)]
|
||||
#[inline(never)]
|
||||
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 {
|
||||
read_volatile(&n_addrs);
|
||||
read_volatile(addrs)
|
||||
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,
|
||||
address: gpointer,
|
||||
size: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gboolean {
|
||||
1
|
||||
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)]
|
||||
@@ -107,6 +267,7 @@ pub extern "C" fn gum_memory_allocate(
|
||||
unsafe {
|
||||
core::ptr::write_bytes(ptr, 0, size as usize);
|
||||
if (prot & _GumPageProtection_GUM_PAGE_EXECUTE) != 0 {
|
||||
register_slab(ptr as u64, size as usize);
|
||||
gum_mprotect(ptr as gpointer, size, prot);
|
||||
}
|
||||
}
|
||||
@@ -115,6 +276,19 @@ pub extern "C" fn gum_memory_allocate(
|
||||
|
||||
#[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 is_agent_slab(address as u64) {
|
||||
unsafe {
|
||||
gum_mprotect(
|
||||
address,
|
||||
size,
|
||||
(_GumPageProtection_GUM_PAGE_READ | _GumPageProtection_GUM_PAGE_WRITE)
|
||||
as GumPageProtection,
|
||||
);
|
||||
}
|
||||
unregister_slab(address as u64);
|
||||
}
|
||||
crate::xnu::free(address as *mut u8, size as usize);
|
||||
1
|
||||
}
|
||||
@@ -350,6 +524,7 @@ unsafe extern "C" fn gum_native_module_enumerate_exports(
|
||||
type_: export_type,
|
||||
name: g_strdup(symbol_ref.name_ptr()),
|
||||
address: symbol_ref.address(),
|
||||
size: -1,
|
||||
};
|
||||
|
||||
let should_continue =
|
||||
|
||||
@@ -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::xnu;
|
||||
|
||||
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() {
|
||||
xnu::thread_wakeup(cookie as *const u8);
|
||||
}
|
||||
}
|
||||
@@ -138,6 +138,13 @@ pub extern "C" fn _unlink(_path: *const u8) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
// 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 sysconf(_name: i32) -> isize {
|
||||
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
|
||||
|
||||
+189
-40
@@ -15,21 +15,23 @@ 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,
|
||||
g_variant_check_format_string, g_variant_get, g_variant_get_byte, 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_get_variant, 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_get_stalker, gum_script_load_sync, gum_script_post, gum_script_set_message_handler,
|
||||
gum_script_unload_sync, gum_stalker_exclude, GumMemoryRange,
|
||||
};
|
||||
use hostlink_virtio::Hostlink;
|
||||
use symbols::SymbolTable;
|
||||
|
||||
mod ffi;
|
||||
mod glib;
|
||||
mod gthread;
|
||||
mod gum;
|
||||
mod hostlink_virtio;
|
||||
mod hostlink_vsock;
|
||||
|
||||
mod libc;
|
||||
mod pac;
|
||||
@@ -55,6 +57,9 @@ pub enum FridaCommand {
|
||||
LoadScript = 2,
|
||||
DestroyScript = 3,
|
||||
PostScriptMessage = 4,
|
||||
RemapWritablePages = 5,
|
||||
MemoryProtect = 6,
|
||||
PatchCode = 7,
|
||||
|
||||
Reply = 128,
|
||||
ScriptMessage = 129,
|
||||
@@ -67,6 +72,9 @@ impl core::fmt::Display for FridaCommand {
|
||||
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"),
|
||||
}
|
||||
@@ -103,10 +111,36 @@ 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();
|
||||
pub enum Transport {
|
||||
Virtio(hostlink_virtio::Hostlink),
|
||||
Vsock(hostlink_vsock::Hostlink),
|
||||
}
|
||||
|
||||
impl Transport {
|
||||
pub fn send(&self, payload: &[u8]) {
|
||||
match self {
|
||||
Transport::Virtio(h) => h.send(payload),
|
||||
Transport::Vsock(h) => h.send(payload),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process(&self) {
|
||||
match self {
|
||||
Transport::Virtio(h) => h.process(),
|
||||
Transport::Vsock(h) => h.process(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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: Hostlink) {
|
||||
fn transport_set(driver: Transport) {
|
||||
unsafe {
|
||||
let boxed = Box::into_raw(Box::new(driver));
|
||||
TRANSPORT_DRIVER = boxed;
|
||||
@@ -114,15 +148,27 @@ fn transport_set(driver: Hostlink) {
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn transport_get_unchecked() -> &'static Hostlink {
|
||||
fn transport_get_unchecked() -> &'static Transport {
|
||||
unsafe {
|
||||
debug_assert!(!TRANSPORT_DRIVER.is_null());
|
||||
&*TRANSPORT_DRIVER
|
||||
}
|
||||
}
|
||||
|
||||
#[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();
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ModuleInfo {
|
||||
@@ -137,33 +183,51 @@ pub struct ModuleInfo {
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn _start(config_data: *const u8, config_size: usize) {
|
||||
unsafe {
|
||||
xnu::io_log("frida: _start entry\n\0");
|
||||
CONFIG_DATA = core::slice::from_raw_parts(config_data, config_size);
|
||||
xnu::io_log("frida: _start config stored\n\0");
|
||||
|
||||
xnu::kernel_thread_start(frida_agent_worker, 12345usize as *mut core::ffi::c_void);
|
||||
let _r = xnu::kernel_thread_start(frida_agent_worker, 12345usize as *mut core::ffi::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 frida_agent_worker(_parameter: *mut core::ffi::c_void, _wait_result: i32) {
|
||||
unsafe {
|
||||
xnu::io_log("frida: worker entry\n\0");
|
||||
bindings::g_set_panic_handler(Some(frida_panic_handler), ptr::null_mut());
|
||||
bindings::gum_init_embedded();
|
||||
xnu::io_log("frida: gum_init_embedded done\n\0");
|
||||
bindings::g_log_set_default_handler(Some(frida_log_handler), ptr::null_mut());
|
||||
|
||||
let (mmio, irq, kernel_base, module_info, symbol_table) =
|
||||
let (transport_config, kernel_base, module_info, symbol_table, own_range) =
|
||||
parse_config(core::ptr::addr_of!(CONFIG_DATA).read());
|
||||
xnu::set_kernel_base(kernel_base);
|
||||
MODULE_INFO = module_info;
|
||||
SYMBOL_TABLE = symbol_table;
|
||||
OWN_RANGE = own_range;
|
||||
xnu::io_log("frida: config parsed, init transport\n\0");
|
||||
|
||||
transport_set(
|
||||
Hostlink::init(
|
||||
mmio,
|
||||
irq,
|
||||
Some(on_frame_from_host),
|
||||
ptr::addr_of_mut!(glib::WAKEUP_TOKEN) as *const u8,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
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);
|
||||
xnu::io_log("frida: transport up, entering main loop\n\0");
|
||||
|
||||
let main_context = g_main_context_default();
|
||||
|
||||
@@ -183,9 +247,11 @@ fn on_frame_from_host(frame: &[u8]) {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn parse_config(config: &[u8]) -> (u64, u32, u64, Vec<ModuleInfo>, SymbolTable) {
|
||||
unsafe fn parse_config(
|
||||
config: &[u8],
|
||||
) -> (TransportConfig, u64, Vec<ModuleInfo>, SymbolTable, GumMemoryRange) {
|
||||
unsafe {
|
||||
let type_string = c"(tuta(ssuuuu)ay)".as_ptr() as *const gchar;
|
||||
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(
|
||||
@@ -197,17 +263,36 @@ unsafe fn parse_config(config: &[u8]) -> (u64, u32, u64, Vec<ModuleInfo>, Symbol
|
||||
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 transport_kind_variant = g_variant_get_child_value(root_variant, 1);
|
||||
let transport_kind = g_variant_get_byte(transport_kind_variant);
|
||||
|
||||
let hostlink_irq_variant = g_variant_get_child_value(root_variant, 1);
|
||||
let hostlink_irq = g_variant_get_uint32(hostlink_irq_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, 2);
|
||||
let kernel_base_variant = g_variant_get_child_value(root_variant, 3);
|
||||
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 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);
|
||||
|
||||
@@ -239,7 +324,7 @@ unsafe fn parse_config(config: &[u8]) -> (u64, u32, u64, Vec<ModuleInfo>, Symbol
|
||||
});
|
||||
}
|
||||
|
||||
let symbol_array_variant = g_variant_get_child_value(root_variant, 4);
|
||||
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(
|
||||
@@ -247,21 +332,28 @@ unsafe fn parse_config(config: &[u8]) -> (u64, u32, u64, Vec<ModuleInfo>, Symbol
|
||||
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(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,
|
||||
)
|
||||
(transport_config, kernel_base, module_info, symbol_table, own_range)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,6 +417,16 @@ fn process_incoming_message(variant: *mut GVariant) {
|
||||
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),
|
||||
FridaCommand::LoadScript => handle_load_script(payload_variant),
|
||||
@@ -356,6 +458,41 @@ fn send_command_reply(request_id: u16, response: HandlerResponse) {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
xnu::assert_wait(wait_event, xnu::THREAD_INTERRUPTIBLE);
|
||||
transport.process();
|
||||
if let Some(reply) = take_pending_reply(request_id) {
|
||||
xnu::thread_wakeup(wait_event);
|
||||
return reply;
|
||||
}
|
||||
xnu::thread_block(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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) -> HandlerResponse {
|
||||
unsafe {
|
||||
if g_variant_check_format_string(payload_variant, c"s".as_ptr(), 0) == 0 {
|
||||
@@ -383,6 +520,8 @@ fn handle_create_script(payload_variant: *mut GVariant) -> HandlerResponse {
|
||||
return HandlerResponse::error(&error_string);
|
||||
}
|
||||
|
||||
exclude_own_range_from_stalker(script);
|
||||
|
||||
let script_id = NEXT_SCRIPT_ID.fetch_add(1, Ordering::Relaxed);
|
||||
|
||||
let script_id_ptr = Box::into_raw(Box::new(script_id));
|
||||
@@ -402,6 +541,16 @@ fn handle_create_script(payload_variant: *mut GVariant) -> HandlerResponse {
|
||||
}
|
||||
}
|
||||
|
||||
// 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. The host injected the image, so it tells us our
|
||||
// range as part of the config.
|
||||
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,
|
||||
|
||||
@@ -16,14 +16,21 @@ type ContinuationFn = unsafe extern "C" fn(_parameter: *mut c_void, _wait_result
|
||||
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;
|
||||
// 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;
|
||||
static _thread_wakeup: unsafe extern "C" fn(*const u8) -> 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);
|
||||
@@ -53,13 +60,28 @@ pub fn io_log(msg: &str) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +119,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 {
|
||||
|
||||
@@ -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);
|
||||
@@ -80,7 +91,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 +116,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 +131,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 +156,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 +171,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);
|
||||
@@ -240,6 +286,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 ();
|
||||
}
|
||||
@@ -597,6 +648,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 ();
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
@@ -229,6 +278,9 @@ namespace Frida.Barebone {
|
||||
case "hostlink":
|
||||
t = typeof (HostlinkTransportConfig);
|
||||
break;
|
||||
case "vsock":
|
||||
t = typeof (VsockTransportConfig);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@@ -272,6 +324,33 @@ 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");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ImageConfig : Object, Json.Serializable {
|
||||
public string file {
|
||||
get;
|
||||
@@ -293,9 +372,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 {
|
||||
|
||||
@@ -16,12 +16,18 @@ backend_sources += files(
|
||||
'img4.vala',
|
||||
'lzfse.vala',
|
||||
'qmp-client.vala',
|
||||
'kernel-relocation.vala',
|
||||
'vz-stub.vala',
|
||||
'arch-x86' / 'machine.vala',
|
||||
'arch-x86_64' / 'machine.vala',
|
||||
'arch-arm' / 'machine.vala',
|
||||
'arch-arm64' / 'machine.vala',
|
||||
'arch-unknown' / 'machine.vala',
|
||||
)
|
||||
if host_os_family == 'darwin'
|
||||
backend_sources += files('vz-physical-memory.vala')
|
||||
backend_vala_args += '--pkg=frida-gum-darwin-1.0'
|
||||
endif
|
||||
barebone_script_runtime = custom_target('frida-barebone-script-runtime',
|
||||
input: [
|
||||
'package.json',
|
||||
|
||||
Generated
+4
-4
@@ -8,7 +8,7 @@
|
||||
"name": "frida-barebone-script-runtime",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@types/frida-gum": "^19.0.1",
|
||||
"@types/frida-gum": "^19.10.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
@@ -439,9 +439,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/frida-gum": {
|
||||
"version": "19.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.0.1.tgz",
|
||||
"integrity": "sha512-mp8BpWWkQFJH86ZN5uXZuDiA7KY/+jDknrw0a06x0/+F5ucPwctHrCuV6iT+LLCp/GyICc36ssedF33B1WoLsw==",
|
||||
"version": "19.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.10.0.tgz",
|
||||
"integrity": "sha512-GBKAzwmamtNPBXEcIve1yJ6BB9ZgyW9al3+f06S+lDAvennOx9GfFQ9RDY9kP5nJrkiiRHLzgbDFjQ/+g+iw1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"watch": "tsc -w"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/frida-gum": "^19.0.1",
|
||||
"@types/frida-gum": "^19.10.0",
|
||||
"esbuild": "^0.25.5",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
|
||||
@@ -400,8 +400,8 @@ namespace Frida.Barebone {
|
||||
close_request.resolve (true);
|
||||
}
|
||||
|
||||
private void handle_message (Json.Node message) throws Error {
|
||||
var r = new Json.Reader (message);
|
||||
private void handle_message (owned Json.Node message) throws Error {
|
||||
var r = make_json_reader_taking_node ((owned) message);
|
||||
|
||||
bool is_response = r.read_member ("id");
|
||||
uint id = 0;
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
/**
|
||||
* Direct host access to the guest's physical RAM, bypassing the kernel GDB stub.
|
||||
*
|
||||
* Apple's Virtualization daemon maps the whole guest RAM into its own address space and
|
||||
* records the layout in the Hv::Vm singleton's vector<HvCore::SharedRamEntry>. We acquire
|
||||
* the daemon's task port, find Hv::Vm by its vtable, walk that vector to build a guest-PA
|
||||
* -> host-VA map, and then read and write guest physical memory with a single mach_vm call
|
||||
* instead of thousands of RSP packets. The stub is left to do what only it can: drive the
|
||||
* vCPUs.
|
||||
*/
|
||||
public sealed class VzPhysicalMemory : Object, PhysicalMemory {
|
||||
private Gum.DarwinPort task;
|
||||
private uint64 instance_address;
|
||||
private Gee.ArrayList<SharedRamEntry?> entries = new Gee.ArrayList<SharedRamEntry?> ();
|
||||
|
||||
private const uint64 VPTR_MASK = 0x00007ffffffffff8;
|
||||
private const uint64 PTR_MASK = 0x00ffffffffffffff;
|
||||
private const uint64 GUEST_RAM_CHUNK_SIZE = 64 * 1024 * 1024;
|
||||
private const uint64 MAX_HEAP_ADDRESS = 0x400000000;
|
||||
private const uint64 SCAN_WINDOW = 16 * 1024 * 1024;
|
||||
private const uint64 SHARED_RAM_VECTOR_OFFSET = 0x30;
|
||||
private const uint64 SHARED_RAM_ENTRY_SIZE = 32;
|
||||
private const uint64 MAX_SHARED_RAM_ENTRIES = 1024;
|
||||
private const uint64 MIN_HOST_VA = 0x10000000;
|
||||
private const uint64 MAPPING_ALIGNMENT = 0x4000;
|
||||
private const uint MAX_DISCOVERY_ATTEMPTS = 10;
|
||||
|
||||
public static VzPhysicalMemory open (uint pid) throws Error {
|
||||
var memory = new VzPhysicalMemory ();
|
||||
|
||||
if (Gum.Darwin.task_for_pid (Gum.Darwin.mach_task_self (), (int) pid, out memory.task) != Gum.Darwin.Status.SUCCESS) {
|
||||
throw new Error.PERMISSION_DENIED (
|
||||
"Unable to acquire the task port for the Virtualization daemon (pid %u); " +
|
||||
"frida-core needs root or the com.apple.system-task-ports entitlement", pid);
|
||||
}
|
||||
|
||||
for (uint attempt = 0; true; attempt++) {
|
||||
try {
|
||||
memory.discover ();
|
||||
return memory;
|
||||
} catch (Error e) {
|
||||
if (attempt == MAX_DISCOVERY_ATTEMPTS - 1)
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void discover () throws Error {
|
||||
uint64 vptr = (resolve_hv_vm_vtable () + 16) & VPTR_MASK;
|
||||
foreach (uint64 instance in find_vptr_candidates (vptr)) {
|
||||
if (try_load_shared_ram (instance))
|
||||
return;
|
||||
}
|
||||
throw new Error.NOT_SUPPORTED ("Unable to locate a populated Hv::Vm guest-RAM map in the daemon");
|
||||
}
|
||||
|
||||
private bool try_load_shared_ram (uint64 instance) {
|
||||
var loaded = read_shared_ram (instance);
|
||||
if (loaded == null)
|
||||
return false;
|
||||
instance_address = instance;
|
||||
entries = loaded;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Validate the vector by shape, not size: the daemon reclaims guest RAM while the VM is idle
|
||||
// so a real Hv::Vm can momentarily expose only the low-memory entry. Page-aligned sizes and
|
||||
// plausible host VAs are what separate the real map from a random vtable-shaped false match.
|
||||
// Non-throwing on purpose: a candidate vptr may point at anything, so every failed daemon read
|
||||
// just means "not this one" — surfacing it as an exception only invites error-propagation faults.
|
||||
private Gee.ArrayList<SharedRamEntry?>? read_shared_ram (uint64 instance) {
|
||||
uint64 begin, end;
|
||||
if (!try_read_u64 (instance + SHARED_RAM_VECTOR_OFFSET, out begin))
|
||||
return null;
|
||||
if (!try_read_u64 (instance + SHARED_RAM_VECTOR_OFFSET + 8, out end))
|
||||
return null;
|
||||
begin &= PTR_MASK;
|
||||
end &= PTR_MASK;
|
||||
if (end <= begin || (end - begin) % SHARED_RAM_ENTRY_SIZE != 0)
|
||||
return null;
|
||||
if ((end - begin) / SHARED_RAM_ENTRY_SIZE > MAX_SHARED_RAM_ENTRIES)
|
||||
return null;
|
||||
|
||||
var result = new Gee.ArrayList<SharedRamEntry?> ();
|
||||
for (uint64 cursor = begin; cursor < end; cursor += SHARED_RAM_ENTRY_SIZE) {
|
||||
uint64 host_va, guest_pa, size;
|
||||
if (!try_read_u64 (cursor, out host_va))
|
||||
return null;
|
||||
if (!try_read_u64 (cursor + 8, out guest_pa))
|
||||
return null;
|
||||
if (!try_read_u64 (cursor + 16, out size))
|
||||
return null;
|
||||
var entry = SharedRamEntry () { host_va = host_va & PTR_MASK, guest_pa = guest_pa, size = size };
|
||||
if (entry.size == 0 || entry.size % MAPPING_ALIGNMENT != 0)
|
||||
return null;
|
||||
// Device/MMIO regions appear with no host backing; keep only the host-backed RAM ranges.
|
||||
if (entry.host_va == 0)
|
||||
continue;
|
||||
if (entry.host_va < MIN_HOST_VA)
|
||||
return null;
|
||||
result.add (entry);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void refresh () {
|
||||
var loaded = read_shared_ram (instance_address);
|
||||
if (loaded != null)
|
||||
entries = loaded;
|
||||
}
|
||||
|
||||
private static uint64 resolve_hv_vm_vtable () throws Error {
|
||||
Gum.Module module;
|
||||
try {
|
||||
module = Gum.Module.load ("/System/Library/Frameworks/Hypervisor.framework/Hypervisor");
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.NOT_SUPPORTED ("Unable to load Hypervisor.framework: %s", e.message);
|
||||
}
|
||||
|
||||
Gum.Address vtable = module.find_symbol_by_name ("_ZTVN2Hv2VmE");
|
||||
if (vtable == 0)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to resolve the Hv::Vm vtable symbol");
|
||||
return vtable;
|
||||
}
|
||||
|
||||
private Gee.List<uint64?> find_vptr_candidates (uint64 vptr) {
|
||||
var candidates = new Gee.ArrayList<uint64?> ();
|
||||
Gum.Darwin.enumerate_ranges (task, READ, details => {
|
||||
// Hv::Vm lives in the daemon's regular malloc heap; the high guest-RAM-mapped regions
|
||||
// up at tens of GB block mach_vm_read while the VM is running, so stay below them.
|
||||
if (details.range.base_address < MAX_HEAP_ADDRESS && details.range.size < GUEST_RAM_CHUNK_SIZE)
|
||||
scan_span_for_vptr (details.range.base_address, details.range.base_address + details.range.size, vptr, candidates);
|
||||
return true;
|
||||
});
|
||||
return candidates;
|
||||
}
|
||||
|
||||
// find_pointers scans this process's own memory, so mirror each daemon window into a local
|
||||
// buffer and let gum's tiled scanner sweep it for the masked vptr, then map hits back to the
|
||||
// daemon address.
|
||||
private void scan_span_for_vptr (uint64 start, uint64 end, uint64 vptr, Gee.List<uint64?> candidates) {
|
||||
size_t[] values = { (size_t) vptr };
|
||||
for (uint64 pos = start; pos < end; pos += SCAN_WINDOW) {
|
||||
size_t length = (size_t) uint64.min (SCAN_WINDOW, end - pos);
|
||||
uint8[]? window = Gum.Darwin.read (task, pos, length);
|
||||
if (window == null)
|
||||
continue;
|
||||
Gum.Address window_base = (Gum.Address) (uintptr) (void *) window;
|
||||
Gum.MemoryRange[] ranges = { { window_base, window.length } };
|
||||
var matches = Gum.Memory.find_pointers (ranges, values, (size_t) VPTR_MASK);
|
||||
for (uint i = 0; i != matches.length; i++)
|
||||
candidates.add (pos + (matches.index (i).address - window_base));
|
||||
}
|
||||
}
|
||||
|
||||
public uint8[] read (uint64 pa, size_t size) throws Error {
|
||||
SharedRamEntry entry = entry_for (pa, size);
|
||||
uint8[]? data = Gum.Darwin.read (task, entry.host_va + (pa - entry.guest_pa), size);
|
||||
if (data == null)
|
||||
throw new Error.INVALID_ARGUMENT ("Unable to read guest physical memory at PA 0x%" + uint64.FORMAT_MODIFIER + "x", pa);
|
||||
return data;
|
||||
}
|
||||
|
||||
public void write (uint64 pa, uint8[] data) throws Error {
|
||||
SharedRamEntry entry = entry_for (pa, data.length);
|
||||
if (!Gum.Darwin.write (task, entry.host_va + (pa - entry.guest_pa), data))
|
||||
throw new Error.INVALID_ARGUMENT ("Unable to write guest physical memory at PA 0x%" + uint64.FORMAT_MODIFIER + "x", pa);
|
||||
}
|
||||
|
||||
public bool contains (uint64 pa) {
|
||||
if (!is_backed (pa))
|
||||
refresh ();
|
||||
return is_backed (pa);
|
||||
}
|
||||
|
||||
private bool is_backed (uint64 pa) {
|
||||
foreach (var entry in entries) {
|
||||
if (pa >= entry.guest_pa && pa < entry.guest_pa + entry.size)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private SharedRamEntry entry_for (uint64 pa, size_t length) throws Error {
|
||||
SharedRamEntry? entry = covering_entry (pa, length);
|
||||
if (entry == null) {
|
||||
refresh ();
|
||||
entry = covering_entry (pa, length);
|
||||
}
|
||||
if (entry == null)
|
||||
throw new Error.INVALID_ARGUMENT ("Guest PA 0x%" + uint64.FORMAT_MODIFIER + "x is not backed by guest RAM", pa);
|
||||
return entry;
|
||||
}
|
||||
|
||||
private SharedRamEntry? covering_entry (uint64 pa, size_t length) {
|
||||
foreach (var entry in entries) {
|
||||
if (pa >= entry.guest_pa && pa + length <= entry.guest_pa + entry.size)
|
||||
return entry;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private bool try_read_u64 (uint64 address, out uint64 value) {
|
||||
value = 0;
|
||||
uint8[]? buffer = Gum.Darwin.read (task, address, 8);
|
||||
if (buffer == null || buffer.length < 8)
|
||||
return false;
|
||||
uint64 v = 0;
|
||||
for (int i = 7; i >= 0; i--)
|
||||
v = (v << 8) | buffer[i];
|
||||
value = v;
|
||||
return true;
|
||||
}
|
||||
|
||||
private struct SharedRamEntry {
|
||||
public uint64 host_va;
|
||||
public uint64 guest_pa;
|
||||
public uint64 size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
/**
|
||||
* GDB-remote client for the kernel debug stub exposed by Apple's
|
||||
* Virtualization.framework (the stub behind com.apple.Virtualization.VirtualMachine).
|
||||
*
|
||||
* It speaks an lldb debugserver-flavoured RSP dialect with a few quirks that the
|
||||
* generic client cannot cope with: it stays silent until a CPU is halted, implements
|
||||
* neither `qSupported` nor `qXfer:features:read` (registers are discovered through
|
||||
* `qRegisterInfo`), reads memory with the binary `x` packet, and toggles
|
||||
* physical-vs-virtual addressing through the `vf.` vendor namespace instead of `qemu.`.
|
||||
*/
|
||||
public sealed class VzStubClient : GDB.Client {
|
||||
private const size_t MAX_BYTES_PER_READ = 0x800;
|
||||
|
||||
private const uint HALT_TIMEOUT_MSEC = 1000;
|
||||
|
||||
private VzStubClient (IOStream stream) {
|
||||
Object (stream: stream);
|
||||
}
|
||||
|
||||
public static new async VzStubClient open (IOStream stream, Cancellable? cancellable = null)
|
||||
throws Error, IOError {
|
||||
var client = new VzStubClient (stream);
|
||||
|
||||
try {
|
||||
yield client.init_async (Priority.DEFAULT, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
protected override async void prepare_connection (Cancellable? cancellable) throws Error, IOError {
|
||||
yield halt (cancellable, HALT_TIMEOUT_MSEC);
|
||||
yield start_no_ack_mode (cancellable);
|
||||
}
|
||||
|
||||
protected override async void detect_vendor_features (Cancellable? cancellable) throws Error, IOError {
|
||||
var vcont = yield query_simple ("vCont?", cancellable);
|
||||
unowned string payload = vcont.payload;
|
||||
if (payload.length > 0 && payload[0] != 'E')
|
||||
supported_features.add ("vcont");
|
||||
|
||||
string phy_mem_mode = yield query_property ("vf.PhyMemMode", cancellable);
|
||||
if (phy_mem_mode.length == 1)
|
||||
supported_features.add ("vf-phy-mem-mode");
|
||||
}
|
||||
|
||||
protected override async void enable_extensions (Cancellable? cancellable) throws Error, IOError {
|
||||
yield execute_simple ("QThreadSuffixSupported", cancellable);
|
||||
supported_features.add ("thread-suffix");
|
||||
yield execute_simple ("QListThreadsInStopReply", cancellable);
|
||||
|
||||
yield discover_target (cancellable);
|
||||
}
|
||||
|
||||
private async void discover_target (Cancellable? cancellable) throws Error, IOError {
|
||||
string info = yield query_property ("HostInfo", cancellable);
|
||||
var host = GDB.Client.PropertyDictionary.parse (info);
|
||||
arch = arch_from_cpu_type (GDB.Protocol.parse_uint (host.get_string ("cputype"), 10));
|
||||
pointer_size = GDB.Protocol.parse_uint (host.get_string ("ptrsize"), 10);
|
||||
byte_order = (host.get_string ("endian") == "little") ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN;
|
||||
|
||||
set_max_packet_size (0x4000);
|
||||
|
||||
// Force per-register access: the bulk G packet writes every register by position,
|
||||
// which would clobber the system registers (PAC keys, ...) that must be preserved.
|
||||
bulk_registers = false;
|
||||
|
||||
var regs = new Gee.ArrayList<GDB.Client.Register> ();
|
||||
for (uint n = 0; ; n++) {
|
||||
var response = yield query_simple ("qRegisterInfo%x".printf (n), cancellable);
|
||||
unowned string payload = response.payload;
|
||||
if (payload.length == 0 || payload[0] == 'E')
|
||||
break;
|
||||
|
||||
var descriptor = GDB.Client.PropertyDictionary.parse (payload);
|
||||
string name = descriptor.get_string ("name");
|
||||
string? altname = descriptor.has ("alt-name") ? descriptor.get_string ("alt-name") : null;
|
||||
uint bitsize = GDB.Protocol.parse_uint (descriptor.get_string ("bitsize"), 10);
|
||||
// Writing system registers back would clobber live state such as the PAC keys
|
||||
// (apiakey*, apdakey*, ...), breaking kernel-wide pointer authentication, so mark
|
||||
// only the general-purpose set as writable.
|
||||
bool writable = descriptor.has ("set") && descriptor.get_string ("set") == "General Purpose Registers";
|
||||
regs.add (new GDB.Client.Register (name, altname, n, bitsize, writable));
|
||||
}
|
||||
install_registers (regs);
|
||||
}
|
||||
|
||||
private static GDB.TargetArch arch_from_cpu_type (uint cpu_type) {
|
||||
switch (cpu_type) {
|
||||
case 0x00000007: return IA32;
|
||||
case 0x01000007: return X64;
|
||||
case 0x0000000c: return ARM;
|
||||
case 0x0100000c: return ARM64;
|
||||
default: return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
public override async Bytes read_byte_array (uint64 address, size_t size, Cancellable? cancellable = null)
|
||||
throws Error, IOError {
|
||||
var result = new uint8[size];
|
||||
|
||||
size_t offset = 0;
|
||||
while (offset != size) {
|
||||
size_t chunk_size = size_t.min (size - offset, MAX_BYTES_PER_READ);
|
||||
|
||||
var request = make_packet_builder_sized (32)
|
||||
.append_c ('x')
|
||||
.append_address (address + offset)
|
||||
.append_c (',')
|
||||
.append_size (chunk_size)
|
||||
.build ();
|
||||
var response = yield query (request, cancellable);
|
||||
|
||||
Bytes chunk = response.payload_bytes;
|
||||
if (chunk.get_size () != chunk_size) {
|
||||
throw new Error.INVALID_ARGUMENT (
|
||||
"Unable to read from 0x%" + uint64.FORMAT_MODIFIER + "x: invalid address", address);
|
||||
}
|
||||
|
||||
Memory.copy ((uint8 *) result + offset, chunk.get_data (), chunk_size);
|
||||
|
||||
offset += chunk_size;
|
||||
}
|
||||
|
||||
return new Bytes.take ((owned) result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
#ifdef _MSC_VER
|
||||
#if defined (_MSC_VER) && defined (HAVE_COMPILER_BACKEND_LINKED)
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
|
||||
@@ -1,98 +1,11 @@
|
||||
package main
|
||||
|
||||
/*
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
FRIDA_OUTPUT_UNESCAPED,
|
||||
FRIDA_OUTPUT_HEX_BYTES,
|
||||
FRIDA_OUTPUT_C_STRING,
|
||||
} FridaOutputFormat;
|
||||
|
||||
typedef enum {
|
||||
FRIDA_BUNDLE_ESM,
|
||||
FRIDA_BUNDLE_IIFE,
|
||||
} FridaBundleFormat;
|
||||
|
||||
typedef void (* FridaBuildCompleteFunc) (char * bundle, char * error_message, void * user_data);
|
||||
typedef void (* FridaWatchReadyFunc) (uintptr_t session_handle, char * error_message, void * user_data);
|
||||
typedef void (* FridaStartingFunc) (void * user_data);
|
||||
typedef void (* FridaFinishedFunc) (void * user_data);
|
||||
typedef void (* FridaOutputFunc) (char * bundle, void * user_data);
|
||||
typedef void (* FridaDiagnosticFunc) (char * category, int code, char * path, int line, int character, char * text,
|
||||
void * user_data);
|
||||
typedef void (* FridaDestroyFunc) (void * user_data);
|
||||
|
||||
static inline void
|
||||
invoke_build_complete_func (FridaBuildCompleteFunc fn,
|
||||
char * bundle,
|
||||
char * error_message,
|
||||
void * user_data)
|
||||
{
|
||||
fn (bundle, error_message, user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_watch_ready_func (FridaWatchReadyFunc fn,
|
||||
uintptr_t session_handle,
|
||||
char * error_message,
|
||||
void * user_data)
|
||||
{
|
||||
fn (session_handle, error_message, user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_starting_func (FridaStartingFunc fn,
|
||||
void * user_data)
|
||||
{
|
||||
fn (user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_finished_func (FridaFinishedFunc fn,
|
||||
void * user_data)
|
||||
{
|
||||
fn (user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_output_func (FridaOutputFunc fn,
|
||||
char * bundle,
|
||||
void * user_data)
|
||||
{
|
||||
fn (bundle, user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_diagnostic_func (FridaDiagnosticFunc fn,
|
||||
char * category,
|
||||
int code,
|
||||
char * path,
|
||||
int line,
|
||||
int character,
|
||||
char * text,
|
||||
void * user_data)
|
||||
{
|
||||
fn (category, code, path, line, character, text, user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_destroy_func (FridaDestroyFunc fn,
|
||||
void * user_data)
|
||||
{
|
||||
fn (user_data);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime/cgo"
|
||||
"strings"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
esbuild "github.com/evanw/esbuild/pkg/api"
|
||||
tsscanner "github.com/frida/typescript-go/pkg/scanner"
|
||||
@@ -110,21 +23,6 @@ type BuildOptions struct {
|
||||
Externals []string
|
||||
}
|
||||
|
||||
type OutputFormat C.FridaOutputFormat
|
||||
|
||||
const (
|
||||
OutputFormatUnescaped OutputFormat = OutputFormat(C.FRIDA_OUTPUT_UNESCAPED)
|
||||
OutputFormatHexBytes OutputFormat = OutputFormat(C.FRIDA_OUTPUT_HEX_BYTES)
|
||||
OutputFormatCString OutputFormat = OutputFormat(C.FRIDA_OUTPUT_C_STRING)
|
||||
)
|
||||
|
||||
type BundleFormat C.FridaBundleFormat
|
||||
|
||||
const (
|
||||
BundleFormatESM BundleFormat = BundleFormat(C.FRIDA_BUNDLE_ESM)
|
||||
BundleFormatIIFE BundleFormat = BundleFormat(C.FRIDA_BUNDLE_IIFE)
|
||||
)
|
||||
|
||||
type Diagnostic struct {
|
||||
category string
|
||||
code int
|
||||
@@ -146,153 +44,6 @@ type BuildEndCallback func()
|
||||
type BuildOutputCallback func(bundle string)
|
||||
type BuildDiagnosticCallback func(d Diagnostic)
|
||||
|
||||
//export _frida_compiler_backend_build
|
||||
func _frida_compiler_backend_build(cProjectRoot, cEntrypoint *C.char, outputFormat C.FridaOutputFormat, bundleFormat C.FridaBundleFormat,
|
||||
disableTypeCheck, sourceMap, compress uintptr, cPlatform *C.char, cExternals **C.char, numExternals C.int,
|
||||
onCompleteFn C.FridaBuildCompleteFunc, onCompleteData unsafe.Pointer, onCompleteDataDestroy C.FridaDestroyFunc,
|
||||
onDiagnosticFn C.FridaDiagnosticFunc, onDiagnosticData unsafe.Pointer) {
|
||||
options := BuildOptions{
|
||||
ProjectRoot: C.GoString(cProjectRoot),
|
||||
Entrypoint: C.GoString(cEntrypoint),
|
||||
OutputFormat: OutputFormat(outputFormat),
|
||||
BundleFormat: BundleFormat(bundleFormat),
|
||||
DisableTypeCheck: disableTypeCheck != 0,
|
||||
SourceMap: sourceMap != 0,
|
||||
Compress: compress != 0,
|
||||
Platform: platformFromFrida(C.GoString(cPlatform)),
|
||||
Externals: parseCExternals(cExternals, numExternals),
|
||||
}
|
||||
onComplete := NewCDelegate(onCompleteFn, onCompleteData, onCompleteDataDestroy)
|
||||
onDiagnostic := NewCDelegate(onDiagnosticFn, onDiagnosticData, nil)
|
||||
|
||||
go func() {
|
||||
defer onComplete.Dispose()
|
||||
defer onDiagnostic.Dispose()
|
||||
|
||||
onDiagnostic := makeCBuildDiagnosticCallback(onDiagnostic)
|
||||
|
||||
bundle, err := build(options, onDiagnostic)
|
||||
|
||||
var cBundle, cErrorMessage *C.char
|
||||
if err == nil {
|
||||
cBundle = C.CString(bundle)
|
||||
} else {
|
||||
cErrorMessage = C.CString(err.Error())
|
||||
}
|
||||
C.invoke_build_complete_func(onComplete.Func, cBundle, cErrorMessage, onComplete.Data)
|
||||
}()
|
||||
}
|
||||
|
||||
//export _frida_compiler_backend_watch
|
||||
func _frida_compiler_backend_watch(cProjectRoot, cEntrypoint *C.char, outputFormat C.FridaOutputFormat, bundleFormat C.FridaBundleFormat,
|
||||
disableTypeCheck, sourceMap, compress uintptr, cPlatform *C.char, cExternals **C.char, numExternals C.int,
|
||||
onReadyFn C.FridaWatchReadyFunc, onReadyData unsafe.Pointer, onReadyDataDestroy C.FridaDestroyFunc,
|
||||
onStartingFn C.FridaStartingFunc, onStartingData unsafe.Pointer,
|
||||
onFinishedFn C.FridaFinishedFunc, onFinishedData unsafe.Pointer,
|
||||
onOutputFn C.FridaOutputFunc, onOutputData unsafe.Pointer,
|
||||
onDiagnosticFn C.FridaDiagnosticFunc, onDiagnosticData unsafe.Pointer) {
|
||||
options := BuildOptions{
|
||||
ProjectRoot: C.GoString(cProjectRoot),
|
||||
Entrypoint: C.GoString(cEntrypoint),
|
||||
OutputFormat: OutputFormat(outputFormat),
|
||||
BundleFormat: BundleFormat(bundleFormat),
|
||||
DisableTypeCheck: disableTypeCheck != 0,
|
||||
SourceMap: sourceMap != 0,
|
||||
Compress: compress != 0,
|
||||
Platform: platformFromFrida(C.GoString(cPlatform)),
|
||||
Externals: parseCExternals(cExternals, numExternals),
|
||||
}
|
||||
onReady := NewCDelegate(onReadyFn, onReadyData, onReadyDataDestroy)
|
||||
onStarting := NewCDelegate(onStartingFn, onStartingData, nil)
|
||||
onFinished := NewCDelegate(onFinishedFn, onFinishedData, nil)
|
||||
onOutput := NewCDelegate(onOutputFn, onOutputData, nil)
|
||||
onDiagnostic := NewCDelegate(onDiagnosticFn, onDiagnosticData, nil)
|
||||
|
||||
go func() {
|
||||
defer onReady.Dispose()
|
||||
|
||||
onDispose := func() {
|
||||
onStarting.Dispose()
|
||||
onFinished.Dispose()
|
||||
onOutput.Dispose()
|
||||
onDiagnostic.Dispose()
|
||||
}
|
||||
|
||||
callbacks := BuildEventCallbacks{
|
||||
OnStart: func() {
|
||||
C.invoke_starting_func(onStarting.Func, onStarting.Data)
|
||||
},
|
||||
OnEnd: func() {
|
||||
C.invoke_starting_func(onFinished.Func, onFinished.Data)
|
||||
},
|
||||
OnOutput: func(bundle string) {
|
||||
C.invoke_output_func(onOutput.Func, C.CString(bundle), onOutput.Data)
|
||||
},
|
||||
OnDiagnostic: makeCBuildDiagnosticCallback(onDiagnostic),
|
||||
}
|
||||
|
||||
session, err := NewWatchSession(options, onDispose, callbacks)
|
||||
|
||||
var cSession C.uintptr_t
|
||||
var cErrorMessage *C.char
|
||||
if err == nil {
|
||||
cSession = C.uintptr_t(cgo.NewHandle(session))
|
||||
} else {
|
||||
cErrorMessage = C.CString(err.Error())
|
||||
}
|
||||
C.invoke_watch_ready_func(onReady.Func, cSession, cErrorMessage, onReady.Data)
|
||||
}()
|
||||
}
|
||||
|
||||
//export _frida_compiler_backend_watch_session_dispose
|
||||
func _frida_compiler_backend_watch_session_dispose(h uintptr) {
|
||||
handle := cgo.Handle(h)
|
||||
session := handle.Value().(*WatchSession)
|
||||
session.Dispose()
|
||||
handle.Delete()
|
||||
}
|
||||
|
||||
func platformFromFrida(s string) esbuild.Platform {
|
||||
switch s {
|
||||
case "gum":
|
||||
return esbuild.PlatformNode
|
||||
case "browser":
|
||||
return esbuild.PlatformBrowser
|
||||
case "neutral":
|
||||
return esbuild.PlatformNeutral
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown platform: %q", s))
|
||||
}
|
||||
}
|
||||
|
||||
func parseCExternals(cExternals **C.char, numExternals C.int) []string {
|
||||
if numExternals == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
n := int(numExternals)
|
||||
cArray := (*[1 << 20]*C.char)(unsafe.Pointer(cExternals))[:n:n]
|
||||
|
||||
externals := make([]string, n)
|
||||
for i := 0; i < n; i++ {
|
||||
externals[i] = C.GoString(cArray[i])
|
||||
}
|
||||
|
||||
return externals
|
||||
}
|
||||
|
||||
func makeCBuildDiagnosticCallback(onDiagnostic *CDelegate[C.FridaDiagnosticFunc]) BuildDiagnosticCallback {
|
||||
return func(d Diagnostic) {
|
||||
var cPath *C.char
|
||||
if d.path != "" {
|
||||
cPath = C.CString(d.path)
|
||||
}
|
||||
|
||||
C.invoke_diagnostic_func(onDiagnostic.Func, C.CString(d.category), C.int(d.code), cPath, C.int(d.line),
|
||||
C.int(d.character), C.CString(d.text), onDiagnostic.Data)
|
||||
}
|
||||
}
|
||||
|
||||
func build(options BuildOptions, onDiagnostic BuildDiagnosticCallback) (bundle string, err error) {
|
||||
callbacks := BuildEventCallbacks{
|
||||
OnOutput: func(b string) {
|
||||
@@ -828,25 +579,3 @@ func makeTypeScriptPlugin(compiler *TSCompiler) esbuild.Plugin {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type CDelegate[F any] struct {
|
||||
Func F
|
||||
null F
|
||||
Data unsafe.Pointer
|
||||
dataDestroy C.FridaDestroyFunc
|
||||
}
|
||||
|
||||
func NewCDelegate[F any](function F, data unsafe.Pointer, dataDestroy C.FridaDestroyFunc) *CDelegate[F] {
|
||||
return &CDelegate[F]{Func: function, Data: data, dataDestroy: dataDestroy}
|
||||
}
|
||||
|
||||
func (d *CDelegate[F]) Dispose() {
|
||||
if d.dataDestroy != nil {
|
||||
C.invoke_destroy_func(d.dataDestroy, d.Data)
|
||||
}
|
||||
d.Func = d.null
|
||||
d.Data = nil
|
||||
d.dataDestroy = nil
|
||||
}
|
||||
|
||||
func main() {}
|
||||
@@ -0,0 +1 @@
|
||||
__frida_compiler_backend_*
|
||||
@@ -1,4 +1,3 @@
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
@@ -10,7 +9,7 @@ from typing import List
|
||||
|
||||
|
||||
def main(argv):
|
||||
go_config = json.loads(base64.b64decode(argv[1]).decode("utf-8"))
|
||||
go_config = json.loads(Path(argv[1]).read_text(encoding="utf-8"))
|
||||
output_dir, priv_dir, go, npm, *inputs = [Path(d).resolve() for d in argv[2:]]
|
||||
|
||||
try:
|
||||
@@ -60,28 +59,36 @@ def build_backend(
|
||||
mode = config["mode"]
|
||||
|
||||
backend_stem = "frida-compiler-backend"
|
||||
if mode == "c-shared":
|
||||
if mode != "c-archive":
|
||||
backend_stem += "-raw"
|
||||
|
||||
backend_a = priv_dir / f"{backend_stem}.a"
|
||||
backend_shlib = priv_dir / (f"{backend_stem}" + config["shlib_suffix"])
|
||||
if mode == "c-archive":
|
||||
backend_binary = priv_dir / f"{backend_stem}.a"
|
||||
elif mode == "c-shared":
|
||||
backend_binary = priv_dir / (f"{backend_stem}" + config["shlib_suffix"])
|
||||
else:
|
||||
backend_binary = priv_dir / (f"{backend_stem}" + config["exe_suffix"])
|
||||
backend_h = priv_dir / f"{backend_stem}.h"
|
||||
|
||||
extra_go_args = config["extra_go_args"].copy()
|
||||
|
||||
if mode == "c-shared":
|
||||
version_script = priv_dir / "backend.version"
|
||||
extra_go_args.append(f"-ldflags=-linkmode=external -extldflags=-Wl,--version-script={version_script}")
|
||||
if config["os_family"] == "darwin":
|
||||
symbols_list = priv_dir / "backend.symbols"
|
||||
extra_go_args.append(f"-ldflags=-linkmode=external -extldflags=-Wl,-exported_symbols_list,{symbols_list}")
|
||||
elif config["os_family"] != "windows":
|
||||
version_script = priv_dir / "backend.version"
|
||||
extra_go_args.append(f"-ldflags=-linkmode=external -extldflags=-Wl,--version-script={version_script}")
|
||||
|
||||
run(
|
||||
go,
|
||||
"build",
|
||||
f"-buildmode={mode}",
|
||||
"-o",
|
||||
backend_a.name if mode == "c-archive" else backend_shlib.name,
|
||||
backend_binary.name,
|
||||
"-buildvcs=false",
|
||||
*extra_go_args,
|
||||
*go_sources,
|
||||
".",
|
||||
)
|
||||
|
||||
if mode == "c-archive":
|
||||
@@ -89,7 +96,7 @@ def build_backend(
|
||||
symbol_dest.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
src_dir = base_dir / "symbol-replacer"
|
||||
for name in ("main.go", "trie.go", "go.mod"):
|
||||
for name in ("main.go", "trie.go", "stringtables.go", "go.mod"):
|
||||
shutil.copy(src_dir / name, symbol_dest)
|
||||
|
||||
env_copy = config["env"].copy()
|
||||
@@ -109,7 +116,7 @@ def build_backend(
|
||||
)
|
||||
|
||||
run(priv_dir / "symbol-replacer" / symbol_replacer_name,
|
||||
backend_a.name, *config["nm"], *config["ranlib"], config["env"]["CC"])
|
||||
backend_binary.name, *config["nm"], *config["ranlib"], config["env"]["CC"])
|
||||
|
||||
if (mingw := config.get("mingw")) is not None and (abi := config["abi"]) in {
|
||||
"x86",
|
||||
@@ -143,17 +150,16 @@ def build_backend(
|
||||
|
||||
extra_objects = []
|
||||
if abi == "x86_64":
|
||||
run(*ar, "x", backend_a.name, "go.o")
|
||||
run(*ar, "x", backend_binary.name, "go.o")
|
||||
sort_pdata_in_object(Path(priv_dir) / "go.o")
|
||||
extra_objects.append("go.o")
|
||||
|
||||
run(*ar, "rs", backend_a.name, *libgcc_objects, *gwex_objects, *extra_objects)
|
||||
run(*ar, "rs", backend_binary.name, *libgcc_objects, *gwex_objects, *extra_objects)
|
||||
|
||||
shutil.copy(priv_dir / backend_a.name, output_dir / backend_a.name)
|
||||
else:
|
||||
shutil.copy(priv_dir / backend_shlib.name, output_dir / backend_shlib.name)
|
||||
shutil.copy(priv_dir / backend_binary.name, output_dir / backend_binary.name)
|
||||
|
||||
shutil.copy(priv_dir / backend_h.name, output_dir / backend_h.name)
|
||||
if mode in {"c-archive", "c-shared"}:
|
||||
shutil.copy(priv_dir / backend_h.name, output_dir / backend_h.name)
|
||||
|
||||
|
||||
# Work around the Go toolchain's almost-MSVC-compatible object files
|
||||
|
||||
+798
-23
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,261 @@
|
||||
//go:build !frida_compiler_backend_executable
|
||||
|
||||
package main
|
||||
|
||||
/*
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef enum {
|
||||
FRIDA_OUTPUT_UNESCAPED,
|
||||
FRIDA_OUTPUT_HEX_BYTES,
|
||||
FRIDA_OUTPUT_C_STRING,
|
||||
} FridaOutputFormat;
|
||||
|
||||
typedef enum {
|
||||
FRIDA_BUNDLE_ESM,
|
||||
FRIDA_BUNDLE_IIFE,
|
||||
} FridaBundleFormat;
|
||||
|
||||
typedef void (* FridaBuildCompleteFunc) (char * bundle, char * error_message, void * user_data);
|
||||
typedef void (* FridaWatchReadyFunc) (uintptr_t session_handle, char * error_message, void * user_data);
|
||||
typedef void (* FridaStartingFunc) (void * user_data);
|
||||
typedef void (* FridaFinishedFunc) (void * user_data);
|
||||
typedef void (* FridaOutputFunc) (char * bundle, void * user_data);
|
||||
typedef void (* FridaDiagnosticFunc) (char * category, int code, char * path, int line, int character, char * text,
|
||||
void * user_data);
|
||||
typedef void (* FridaDestroyFunc) (void * user_data);
|
||||
|
||||
static inline void
|
||||
invoke_build_complete_func (FridaBuildCompleteFunc fn,
|
||||
char * bundle,
|
||||
char * error_message,
|
||||
void * user_data)
|
||||
{
|
||||
fn (bundle, error_message, user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_watch_ready_func (FridaWatchReadyFunc fn,
|
||||
uintptr_t session_handle,
|
||||
char * error_message,
|
||||
void * user_data)
|
||||
{
|
||||
fn (session_handle, error_message, user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_starting_func (FridaStartingFunc fn,
|
||||
void * user_data)
|
||||
{
|
||||
fn (user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_finished_func (FridaFinishedFunc fn,
|
||||
void * user_data)
|
||||
{
|
||||
fn (user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_output_func (FridaOutputFunc fn,
|
||||
char * bundle,
|
||||
void * user_data)
|
||||
{
|
||||
fn (bundle, user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_diagnostic_func (FridaDiagnosticFunc fn,
|
||||
char * category,
|
||||
int code,
|
||||
char * path,
|
||||
int line,
|
||||
int character,
|
||||
char * text,
|
||||
void * user_data)
|
||||
{
|
||||
fn (category, code, path, line, character, text, user_data);
|
||||
}
|
||||
|
||||
static inline void
|
||||
invoke_destroy_func (FridaDestroyFunc fn,
|
||||
void * user_data)
|
||||
{
|
||||
fn (user_data);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"runtime/cgo"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
//export _frida_compiler_backend_build
|
||||
func _frida_compiler_backend_build(cProjectRoot, cEntrypoint *C.char, outputFormat C.FridaOutputFormat, bundleFormat C.FridaBundleFormat,
|
||||
disableTypeCheck, sourceMap, compress uintptr, cPlatform *C.char, cExternals **C.char, numExternals C.int,
|
||||
onDiagnosticFn C.FridaDiagnosticFunc, onDiagnosticData unsafe.Pointer, onCompleteFn C.FridaBuildCompleteFunc,
|
||||
onCompleteData unsafe.Pointer, onCompleteDataDestroy C.FridaDestroyFunc) {
|
||||
options := BuildOptions{
|
||||
ProjectRoot: C.GoString(cProjectRoot),
|
||||
Entrypoint: C.GoString(cEntrypoint),
|
||||
OutputFormat: OutputFormat(outputFormat),
|
||||
BundleFormat: BundleFormat(bundleFormat),
|
||||
DisableTypeCheck: disableTypeCheck != 0,
|
||||
SourceMap: sourceMap != 0,
|
||||
Compress: compress != 0,
|
||||
Platform: platformFromFrida(C.GoString(cPlatform)),
|
||||
Externals: parseCExternals(cExternals, numExternals),
|
||||
}
|
||||
onDiagnostic := NewCDelegate(onDiagnosticFn, onDiagnosticData, nil)
|
||||
onComplete := NewCDelegate(onCompleteFn, onCompleteData, onCompleteDataDestroy)
|
||||
|
||||
go func() {
|
||||
defer onComplete.Dispose()
|
||||
defer onDiagnostic.Dispose()
|
||||
|
||||
onDiagnostic := makeCBuildDiagnosticCallback(onDiagnostic)
|
||||
|
||||
bundle, err := build(options, onDiagnostic)
|
||||
|
||||
var cBundle, cErrorMessage *C.char
|
||||
if err == nil {
|
||||
cBundle = C.CString(bundle)
|
||||
} else {
|
||||
cErrorMessage = C.CString(err.Error())
|
||||
}
|
||||
C.invoke_build_complete_func(onComplete.Func, cBundle, cErrorMessage, onComplete.Data)
|
||||
C.free(unsafe.Pointer(cBundle))
|
||||
C.free(unsafe.Pointer(cErrorMessage))
|
||||
}()
|
||||
}
|
||||
|
||||
//export _frida_compiler_backend_watch
|
||||
func _frida_compiler_backend_watch(cProjectRoot, cEntrypoint *C.char, outputFormat C.FridaOutputFormat, bundleFormat C.FridaBundleFormat,
|
||||
disableTypeCheck, sourceMap, compress uintptr, cPlatform *C.char, cExternals **C.char, numExternals C.int,
|
||||
onStartingFn C.FridaStartingFunc, onStartingData unsafe.Pointer,
|
||||
onFinishedFn C.FridaFinishedFunc, onFinishedData unsafe.Pointer,
|
||||
onOutputFn C.FridaOutputFunc, onOutputData unsafe.Pointer,
|
||||
onDiagnosticFn C.FridaDiagnosticFunc, onDiagnosticData unsafe.Pointer,
|
||||
onReadyFn C.FridaWatchReadyFunc, onReadyData unsafe.Pointer, onReadyDataDestroy C.FridaDestroyFunc) {
|
||||
options := BuildOptions{
|
||||
ProjectRoot: C.GoString(cProjectRoot),
|
||||
Entrypoint: C.GoString(cEntrypoint),
|
||||
OutputFormat: OutputFormat(outputFormat),
|
||||
BundleFormat: BundleFormat(bundleFormat),
|
||||
DisableTypeCheck: disableTypeCheck != 0,
|
||||
SourceMap: sourceMap != 0,
|
||||
Compress: compress != 0,
|
||||
Platform: platformFromFrida(C.GoString(cPlatform)),
|
||||
Externals: parseCExternals(cExternals, numExternals),
|
||||
}
|
||||
onStarting := NewCDelegate(onStartingFn, onStartingData, nil)
|
||||
onFinished := NewCDelegate(onFinishedFn, onFinishedData, nil)
|
||||
onOutput := NewCDelegate(onOutputFn, onOutputData, nil)
|
||||
onDiagnostic := NewCDelegate(onDiagnosticFn, onDiagnosticData, nil)
|
||||
onReady := NewCDelegate(onReadyFn, onReadyData, onReadyDataDestroy)
|
||||
|
||||
go func() {
|
||||
defer onReady.Dispose()
|
||||
|
||||
onDispose := func() {
|
||||
onStarting.Dispose()
|
||||
onFinished.Dispose()
|
||||
onOutput.Dispose()
|
||||
onDiagnostic.Dispose()
|
||||
}
|
||||
|
||||
callbacks := BuildEventCallbacks{
|
||||
OnStart: func() {
|
||||
C.invoke_starting_func(onStarting.Func, onStarting.Data)
|
||||
},
|
||||
OnEnd: func() {
|
||||
C.invoke_finished_func(onFinished.Func, onFinished.Data)
|
||||
},
|
||||
OnOutput: func(bundle string) {
|
||||
cBundle := C.CString(bundle)
|
||||
C.invoke_output_func(onOutput.Func, cBundle, onOutput.Data)
|
||||
C.free(unsafe.Pointer(cBundle))
|
||||
},
|
||||
OnDiagnostic: makeCBuildDiagnosticCallback(onDiagnostic),
|
||||
}
|
||||
|
||||
session, err := NewWatchSession(options, onDispose, callbacks)
|
||||
|
||||
var cSession C.uintptr_t
|
||||
var cErrorMessage *C.char
|
||||
if err == nil {
|
||||
cSession = C.uintptr_t(cgo.NewHandle(session))
|
||||
} else {
|
||||
cErrorMessage = C.CString(err.Error())
|
||||
}
|
||||
C.invoke_watch_ready_func(onReady.Func, cSession, cErrorMessage, onReady.Data)
|
||||
C.free(unsafe.Pointer(cErrorMessage))
|
||||
}()
|
||||
}
|
||||
|
||||
//export _frida_compiler_backend_watch_session_dispose
|
||||
func _frida_compiler_backend_watch_session_dispose(h uintptr) {
|
||||
handle := cgo.Handle(h)
|
||||
session := handle.Value().(*WatchSession)
|
||||
session.Dispose()
|
||||
handle.Delete()
|
||||
}
|
||||
|
||||
func parseCExternals(cExternals **C.char, numExternals C.int) []string {
|
||||
if numExternals == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
n := int(numExternals)
|
||||
cArray := (*[1 << 20]*C.char)(unsafe.Pointer(cExternals))[:n:n]
|
||||
|
||||
externals := make([]string, n)
|
||||
for i := 0; i < n; i++ {
|
||||
externals[i] = C.GoString(cArray[i])
|
||||
}
|
||||
|
||||
return externals
|
||||
}
|
||||
|
||||
func makeCBuildDiagnosticCallback(onDiagnostic *CDelegate[C.FridaDiagnosticFunc]) BuildDiagnosticCallback {
|
||||
return func(d Diagnostic) {
|
||||
var cPath *C.char
|
||||
if d.path != "" {
|
||||
cPath = C.CString(d.path)
|
||||
}
|
||||
cCategory := C.CString(d.category)
|
||||
cText := C.CString(d.text)
|
||||
|
||||
C.invoke_diagnostic_func(onDiagnostic.Func, cCategory, C.int(d.code), cPath, C.int(d.line),
|
||||
C.int(d.character), cText, onDiagnostic.Data)
|
||||
|
||||
C.free(unsafe.Pointer(cCategory))
|
||||
C.free(unsafe.Pointer(cPath))
|
||||
C.free(unsafe.Pointer(cText))
|
||||
}
|
||||
}
|
||||
|
||||
type CDelegate[F any] struct {
|
||||
Func F
|
||||
null F
|
||||
Data unsafe.Pointer
|
||||
dataDestroy C.FridaDestroyFunc
|
||||
}
|
||||
|
||||
func NewCDelegate[F any](function F, data unsafe.Pointer, dataDestroy C.FridaDestroyFunc) *CDelegate[F] {
|
||||
return &CDelegate[F]{Func: function, Data: data, dataDestroy: dataDestroy}
|
||||
}
|
||||
|
||||
func (d *CDelegate[F]) Dispose() {
|
||||
if d.dataDestroy != nil {
|
||||
C.invoke_destroy_func(d.dataDestroy, d.Data)
|
||||
}
|
||||
d.Func = d.null
|
||||
d.Data = nil
|
||||
d.dataDestroy = nil
|
||||
}
|
||||
|
||||
func main() {}
|
||||
@@ -0,0 +1,263 @@
|
||||
//go:build frida_compiler_backend_executable
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type BackendRequest struct {
|
||||
Type string `json:"type"`
|
||||
ID uint `json:"id,omitempty"`
|
||||
SessionID uint `json:"session_id,omitempty"`
|
||||
ProjectRoot string `json:"project_root,omitempty"`
|
||||
Entrypoint string `json:"entrypoint,omitempty"`
|
||||
OutputFormat string `json:"output_format,omitempty"`
|
||||
BundleFormat string `json:"bundle_format,omitempty"`
|
||||
DisableTypeCheck bool `json:"disable_type_check,omitempty"`
|
||||
SourceMap bool `json:"source_map,omitempty"`
|
||||
Compress bool `json:"compress,omitempty"`
|
||||
Platform string `json:"platform,omitempty"`
|
||||
Externals []string `json:"externals,omitempty"`
|
||||
}
|
||||
|
||||
type BackendEvent struct {
|
||||
Type string `json:"type"`
|
||||
ID uint `json:"id,omitempty"`
|
||||
SessionID uint `json:"session_id,omitempty"`
|
||||
|
||||
Bundle string `json:"bundle,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
|
||||
Category string `json:"category,omitempty"`
|
||||
Code int `json:"code,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Line int `json:"line,omitempty"`
|
||||
Character int `json:"character,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := run(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run() error {
|
||||
reader := bufio.NewReaderSize(os.Stdin, 128*1024)
|
||||
writer := bufio.NewWriterSize(os.Stdout, 128*1024)
|
||||
|
||||
var outputMu sync.Mutex
|
||||
var sessionsMu sync.Mutex
|
||||
sessions := make(map[uint]*WatchSession)
|
||||
|
||||
emit := func(ev BackendEvent) {
|
||||
outputMu.Lock()
|
||||
defer outputMu.Unlock()
|
||||
|
||||
if err := writeMessage(writer, ev); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
var req BackendRequest
|
||||
if err := readMessage(reader, &req); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
switch req.Type {
|
||||
case "build":
|
||||
go func(req BackendRequest) {
|
||||
options, err := buildOptionsFromRequest(req)
|
||||
if err != nil {
|
||||
emit(BackendEvent{
|
||||
Type: "build:complete",
|
||||
ID: req.ID,
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
onDiagnostic := func(d Diagnostic) {
|
||||
ev := BackendEvent{
|
||||
Type: "build:diagnostic",
|
||||
ID: req.ID,
|
||||
Category: d.category,
|
||||
Code: d.code,
|
||||
Path: d.path,
|
||||
Line: d.line,
|
||||
Character: d.character,
|
||||
Text: d.text,
|
||||
}
|
||||
emit(ev)
|
||||
}
|
||||
|
||||
bundle, err := build(options, onDiagnostic)
|
||||
|
||||
ev := BackendEvent{
|
||||
Type: "build:complete",
|
||||
ID: req.ID,
|
||||
}
|
||||
if err != nil {
|
||||
ev.Error = err.Error()
|
||||
} else {
|
||||
ev.Bundle = bundle
|
||||
}
|
||||
emit(ev)
|
||||
}(req)
|
||||
|
||||
case "watch":
|
||||
go func(req BackendRequest) {
|
||||
options, err := buildOptionsFromRequest(req)
|
||||
if err != nil {
|
||||
emit(BackendEvent{
|
||||
Type: "watch:ready",
|
||||
SessionID: req.SessionID,
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
callbacks := BuildEventCallbacks{
|
||||
OnStart: func() {
|
||||
emit(BackendEvent{
|
||||
Type: "watch:starting",
|
||||
SessionID: req.SessionID,
|
||||
})
|
||||
},
|
||||
OnEnd: func() {
|
||||
emit(BackendEvent{
|
||||
Type: "watch:finished",
|
||||
SessionID: req.SessionID,
|
||||
})
|
||||
},
|
||||
OnOutput: func(bundle string) {
|
||||
emit(BackendEvent{
|
||||
Type: "watch:output",
|
||||
SessionID: req.SessionID,
|
||||
Bundle: bundle,
|
||||
})
|
||||
},
|
||||
OnDiagnostic: func(d Diagnostic) {
|
||||
emit(BackendEvent{
|
||||
Type: "watch:diagnostic",
|
||||
SessionID: req.SessionID,
|
||||
Category: d.category,
|
||||
Code: d.code,
|
||||
Path: d.path,
|
||||
Line: d.line,
|
||||
Character: d.character,
|
||||
Text: d.text,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
var session *WatchSession
|
||||
onDispose := func() {
|
||||
sessionsMu.Lock()
|
||||
delete(sessions, req.SessionID)
|
||||
sessionsMu.Unlock()
|
||||
}
|
||||
|
||||
session, err = NewWatchSession(options, onDispose, callbacks)
|
||||
if err != nil {
|
||||
emit(BackendEvent{
|
||||
Type: "watch:ready",
|
||||
SessionID: req.SessionID,
|
||||
Error: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
sessionsMu.Lock()
|
||||
sessions[req.SessionID] = session
|
||||
sessionsMu.Unlock()
|
||||
|
||||
emit(BackendEvent{
|
||||
Type: "watch:ready",
|
||||
SessionID: req.SessionID,
|
||||
})
|
||||
}(req)
|
||||
|
||||
case "dispose":
|
||||
sessionsMu.Lock()
|
||||
session := sessions[req.SessionID]
|
||||
delete(sessions, req.SessionID)
|
||||
sessionsMu.Unlock()
|
||||
|
||||
if session != nil {
|
||||
session.Dispose()
|
||||
}
|
||||
|
||||
default:
|
||||
return fmt.Errorf("unsupported request type: %q", req.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func buildOptionsFromRequest(req BackendRequest) (BuildOptions, error) {
|
||||
outputFormat, err := outputFormatFromNick(req.OutputFormat)
|
||||
if err != nil {
|
||||
return BuildOptions{}, err
|
||||
}
|
||||
|
||||
bundleFormat, err := bundleFormatFromNick(req.BundleFormat)
|
||||
if err != nil {
|
||||
return BuildOptions{}, err
|
||||
}
|
||||
|
||||
return BuildOptions{
|
||||
ProjectRoot: req.ProjectRoot,
|
||||
Entrypoint: req.Entrypoint,
|
||||
OutputFormat: outputFormat,
|
||||
BundleFormat: bundleFormat,
|
||||
DisableTypeCheck: req.DisableTypeCheck,
|
||||
SourceMap: req.SourceMap,
|
||||
Compress: req.Compress,
|
||||
Platform: platformFromFrida(req.Platform),
|
||||
Externals: req.Externals,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func readMessage(r io.Reader, out any) error {
|
||||
var size uint32
|
||||
if err := binary.Read(r, binary.BigEndian, &size); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buf := make([]byte, size)
|
||||
if _, err := io.ReadFull(r, buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.Unmarshal(buf, out)
|
||||
}
|
||||
|
||||
func writeMessage(w *bufio.Writer, msg any) error {
|
||||
buf, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := binary.Write(w, binary.BigEndian, uint32(len(buf))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := w.Write(buf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return w.Flush()
|
||||
}
|
||||
+67
-32
@@ -4,23 +4,40 @@ compiler_sources = files(
|
||||
)
|
||||
|
||||
if have_compiler_backend
|
||||
output_suffix = compiler_backend_static_compilation ? '' : '-raw'
|
||||
if compiler_backend_mode == 'c-archive'
|
||||
output_files = [
|
||||
'frida-compiler-backend.a',
|
||||
'frida-compiler-backend.h',
|
||||
]
|
||||
elif compiler_backend_mode == 'c-shared'
|
||||
output_files = [
|
||||
'frida-compiler-backend-raw' + shlib_suffix,
|
||||
'frida-compiler-backend-raw.h',
|
||||
]
|
||||
elif compiler_backend_mode == 'pie'
|
||||
output_files = [
|
||||
'frida-compiler-backend-raw' + exe_suffix,
|
||||
]
|
||||
endif
|
||||
|
||||
compiler_backend = custom_target('frida-compiler-backend',
|
||||
input: [
|
||||
'go.mod',
|
||||
'go.sum',
|
||||
'frontend_capi.go',
|
||||
'frontend_stdio.go',
|
||||
'backend.go',
|
||||
'protocol_types.go',
|
||||
'backend.version',
|
||||
'backend.symbols',
|
||||
'tscompiler.go',
|
||||
'tsconfig.go',
|
||||
'shims.go',
|
||||
'package.json',
|
||||
'package-lock.json',
|
||||
],
|
||||
output: [
|
||||
'frida-compiler-backend' + output_suffix + (compiler_backend_static_compilation ? '.a' : shlib_suffix),
|
||||
'frida-compiler-backend' + output_suffix + '.h',
|
||||
],
|
||||
output: output_files,
|
||||
depend_files: go_config,
|
||||
command: [
|
||||
python,
|
||||
files('build-backend.py'),
|
||||
@@ -32,35 +49,53 @@ if have_compiler_backend
|
||||
'@INPUT@',
|
||||
],
|
||||
)
|
||||
if compiler_backend_static_compilation
|
||||
|
||||
if compiler_backend_mode == 'c-archive'
|
||||
backend_libs += compiler_backend[0]
|
||||
else
|
||||
filename = 'frida-compiler-backend' + shlib_suffix
|
||||
identity = (host_os_family == 'darwin') ? 'FridaCompilerBackend' : filename
|
||||
compiler_backend_shlib = custom_target('frida-compiler-backend-shlib',
|
||||
input: compiler_backend[0],
|
||||
output: filename,
|
||||
command: post_process + ['shared-library', identity, 'elf-cleaner:off'],
|
||||
)
|
||||
if compiler_backend_mode == 'c-shared'
|
||||
processed_filename = 'frida-compiler-backend' + shlib_suffix
|
||||
identity = (host_os_family == 'darwin') ? 'FridaCompilerBackend' : processed_filename
|
||||
compiler_backend_binary = custom_target('frida-compiler-backend-binary',
|
||||
input: compiler_backend[0],
|
||||
output: processed_filename,
|
||||
command: post_process + ['shared-library', identity, 'elf-cleaner:off'],
|
||||
install: assets_mode == 'installed',
|
||||
install_dir: get_option('libdir') / frida_libdir_name / 'plugins',
|
||||
)
|
||||
elif compiler_backend_mode == 'pie'
|
||||
processed_filename = 'frida-compiler-backend' + exe_suffix
|
||||
compiler_backend_binary = custom_target('frida-compiler-backend-binary',
|
||||
input: compiler_backend[0],
|
||||
output: processed_filename,
|
||||
command: post_process + ['executable', processed_filename],
|
||||
install: assets_mode == 'installed',
|
||||
install_dir: get_option('libdir') / frida_libdir_name / 'plugins',
|
||||
)
|
||||
endif
|
||||
|
||||
compiler_data = custom_target('frida-data-compiler',
|
||||
input: [
|
||||
'compiler.resources',
|
||||
compiler_backend_shlib,
|
||||
],
|
||||
output: [
|
||||
'frida-data-compiler.vapi',
|
||||
'frida-data-compiler.h',
|
||||
'frida-data-compiler.c',
|
||||
'frida-data-compiler-blob' + resource_blob_extension,
|
||||
],
|
||||
command: [
|
||||
resource_compiler_cmd_array,
|
||||
'-c', '@INPUT0@',
|
||||
'-o', meson.current_build_dir() / 'frida-data-compiler',
|
||||
'@INPUT1@',
|
||||
],
|
||||
)
|
||||
backend_sources += compiler_data
|
||||
if assets_mode == 'installed'
|
||||
core_extra_link_depends += compiler_backend_binary
|
||||
else
|
||||
compiler_data = custom_target('frida-data-compiler',
|
||||
input: [
|
||||
'compiler.resources',
|
||||
compiler_backend_binary,
|
||||
],
|
||||
output: [
|
||||
'frida-data-compiler.vapi',
|
||||
'frida-data-compiler.h',
|
||||
'frida-data-compiler.c',
|
||||
'frida-data-compiler-blob' + resource_blob_extension,
|
||||
],
|
||||
command: [
|
||||
resource_compiler_cmd_array,
|
||||
'-c', '@INPUT0@',
|
||||
'-o', meson.current_build_dir() / 'frida-data-compiler',
|
||||
'@INPUT1@',
|
||||
],
|
||||
)
|
||||
backend_sources += compiler_data
|
||||
endif
|
||||
endif
|
||||
endif
|
||||
Generated
+4
-4
@@ -33,7 +33,7 @@
|
||||
"@frida/url": "^1.0.2",
|
||||
"@frida/util": "^1.0.3",
|
||||
"@frida/vm": "^2.0.0",
|
||||
"@types/frida-gum": "19.0.2",
|
||||
"@types/frida-gum": "^19.10.0",
|
||||
"@types/node": "18.19.103",
|
||||
"frida-fs": "7.0.0"
|
||||
}
|
||||
@@ -248,9 +248,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/frida-gum": {
|
||||
"version": "19.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.0.2.tgz",
|
||||
"integrity": "sha512-oh/f1r8dIMDWBoziXP3geIJ8ga/siU32MuifBztGsDglOKbHrhS0E3z0Dab0MIe3UFzC4Bvz/GRHfin2LDE8BQ==",
|
||||
"version": "19.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.10.0.tgz",
|
||||
"integrity": "sha512-GBKAzwmamtNPBXEcIve1yJ6BB9ZgyW9al3+f06S+lDAvennOx9GfFQ9RDY9kP5nJrkiiRHLzgbDFjQ/+g+iw1Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"@frida/url": "^1.0.2",
|
||||
"@frida/util": "^1.0.3",
|
||||
"@frida/vm": "^2.0.0",
|
||||
"@types/frida-gum": "19.0.2",
|
||||
"@types/frida-gum": "^19.10.0",
|
||||
"@types/node": "18.19.103",
|
||||
"frida-fs": "7.0.0"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
esbuild "github.com/evanw/esbuild/pkg/api"
|
||||
)
|
||||
|
||||
type OutputFormat int
|
||||
|
||||
const (
|
||||
OutputFormatUnescaped OutputFormat = iota
|
||||
OutputFormatHexBytes
|
||||
OutputFormatCString
|
||||
)
|
||||
|
||||
func outputFormatFromNick(s string) (OutputFormat, error) {
|
||||
switch s {
|
||||
case "unescaped":
|
||||
return OutputFormatUnescaped, nil
|
||||
case "hex-bytes":
|
||||
return OutputFormatHexBytes, nil
|
||||
case "c-string":
|
||||
return OutputFormatCString, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown output format: %q", s)
|
||||
}
|
||||
}
|
||||
|
||||
type BundleFormat int
|
||||
|
||||
const (
|
||||
BundleFormatESM BundleFormat = iota
|
||||
BundleFormatIIFE
|
||||
)
|
||||
|
||||
func bundleFormatFromNick(s string) (BundleFormat, error) {
|
||||
switch s {
|
||||
case "esm":
|
||||
return BundleFormatESM, nil
|
||||
case "iife":
|
||||
return BundleFormatIIFE, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unknown bundle format: %q", s)
|
||||
}
|
||||
}
|
||||
|
||||
func platformFromFrida(s string) esbuild.Platform {
|
||||
switch s {
|
||||
case "gum":
|
||||
return esbuild.PlatformNode
|
||||
case "browser":
|
||||
return esbuild.PlatformBrowser
|
||||
case "neutral":
|
||||
return esbuild.PlatformNeutral
|
||||
default:
|
||||
panic(fmt.Sprintf("Unknown platform: %q", s))
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
module github.com/frida/frida-symbol-replacer
|
||||
|
||||
go 1.24.4
|
||||
go 1.24.3
|
||||
@@ -71,17 +71,7 @@ func main() {
|
||||
}
|
||||
|
||||
data, _ := io.ReadAll(f)
|
||||
modifiedData := make([]byte, len(data))
|
||||
|
||||
for i := 0; i < len(data); {
|
||||
if l, replacement, ok := t.search(data, i); ok {
|
||||
copy(modifiedData[i:], replacement)
|
||||
i += l
|
||||
} else {
|
||||
modifiedData[i] = data[i]
|
||||
i++
|
||||
}
|
||||
}
|
||||
modifiedData := renameSymbolsWithinStringTables(data, t)
|
||||
|
||||
f.Truncate(0)
|
||||
f.Seek(0, 0)
|
||||
@@ -95,6 +85,24 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func renameSymbolsWithinStringTables(data []byte, t *trie) []byte {
|
||||
renamed := make([]byte, len(data))
|
||||
copy(renamed, data)
|
||||
|
||||
for _, r := range symbolStringTableRanges(data) {
|
||||
for i := r.lo; i < r.hi; {
|
||||
if length, replacement, ok := t.search(data, i); ok && i+length <= r.hi {
|
||||
copy(renamed[i:], replacement)
|
||||
i += length
|
||||
} else {
|
||||
i++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return renamed
|
||||
}
|
||||
|
||||
func flipAlpha(s []byte) []byte {
|
||||
dt := make([]byte, len(s))
|
||||
copy(dt, s)
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"debug/elf"
|
||||
"debug/pe"
|
||||
"encoding/binary"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const coffSymbolSize = 18
|
||||
|
||||
// debug/macho cannot read the symbol table of the object the Go linker emits
|
||||
// on Darwin, so the Mach-O load commands are parsed directly.
|
||||
var machoMagic64 = []byte{0xcf, 0xfa, 0xed, 0xfe}
|
||||
|
||||
const (
|
||||
machoHeaderSize = 32
|
||||
machoSymtabCommand = 0x2
|
||||
)
|
||||
|
||||
type span struct {
|
||||
lo, hi int
|
||||
}
|
||||
|
||||
type member struct {
|
||||
name string
|
||||
span
|
||||
}
|
||||
|
||||
func symbolStringTableRanges(data []byte) []span {
|
||||
var ranges []span
|
||||
for _, m := range archiveMembers(data) {
|
||||
// The archive symbol index maps names to members and is what the linker
|
||||
// consults; ranlib does not rebuild it for every format, so its names
|
||||
// are renamed wholesale. It holds only names and offsets, never the
|
||||
// runtime data that must be left intact inside objects.
|
||||
if isArchiveSymbolIndex(m.name) {
|
||||
ranges = append(ranges, m.span)
|
||||
continue
|
||||
}
|
||||
object := data[m.lo:m.hi]
|
||||
for _, table := range objectStringTableRanges(object) {
|
||||
ranges = append(ranges, span{m.lo + table.lo, m.lo + table.hi})
|
||||
}
|
||||
}
|
||||
return ranges
|
||||
}
|
||||
|
||||
func archiveMembers(data []byte) []member {
|
||||
var members []member
|
||||
pos := len("!<arch>\n")
|
||||
for pos+60 <= len(data) {
|
||||
header := data[pos : pos+60]
|
||||
|
||||
size, _ := strconv.Atoi(string(bytes.TrimSpace(header[48:58])))
|
||||
|
||||
body := pos + 60
|
||||
end := body + size
|
||||
|
||||
lo := body
|
||||
name := string(bytes.TrimRight(header[0:16], " "))
|
||||
if longNameLength, ok := bsdLongNameLength(name); ok {
|
||||
lo += longNameLength
|
||||
name = string(data[body : body+longNameLength])
|
||||
}
|
||||
|
||||
members = append(members, member{name, span{lo, end}})
|
||||
|
||||
pos = end + end%2
|
||||
}
|
||||
return members
|
||||
}
|
||||
|
||||
func bsdLongNameLength(name string) (int, bool) {
|
||||
if !strings.HasPrefix(name, "#1/") {
|
||||
return 0, false
|
||||
}
|
||||
length, _ := strconv.Atoi(name[3:])
|
||||
return length, true
|
||||
}
|
||||
|
||||
func isArchiveSymbolIndex(name string) bool {
|
||||
return name == "/" || name == "//" || strings.HasPrefix(name, "__.SYMDEF")
|
||||
}
|
||||
|
||||
func objectStringTableRanges(object []byte) []span {
|
||||
if f, err := elf.NewFile(bytes.NewReader(object)); err == nil {
|
||||
return elfStringTableRanges(f)
|
||||
}
|
||||
|
||||
if bytes.HasPrefix(object, machoMagic64) {
|
||||
return machoStringTableRanges(object)
|
||||
}
|
||||
|
||||
if f, err := pe.NewFile(bytes.NewReader(object)); err == nil {
|
||||
return coffStringTableRanges(f)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func elfStringTableRanges(f *elf.File) []span {
|
||||
var ranges []span
|
||||
for _, s := range f.Sections {
|
||||
if s.Type == elf.SHT_STRTAB {
|
||||
ranges = append(ranges, span{int(s.Offset), int(s.Offset + s.Size)})
|
||||
}
|
||||
}
|
||||
return ranges
|
||||
}
|
||||
|
||||
func machoStringTableRanges(object []byte) []span {
|
||||
commandCount := binary.LittleEndian.Uint32(object[16:])
|
||||
offset := machoHeaderSize
|
||||
for i := uint32(0); i < commandCount; i++ {
|
||||
command := binary.LittleEndian.Uint32(object[offset:])
|
||||
commandSize := binary.LittleEndian.Uint32(object[offset+4:])
|
||||
if command == machoSymtabCommand {
|
||||
lo := int(binary.LittleEndian.Uint32(object[offset+16:]))
|
||||
size := int(binary.LittleEndian.Uint32(object[offset+20:]))
|
||||
return []span{{lo, lo + size}}
|
||||
}
|
||||
offset += int(commandSize)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// COFF keeps names longer than eight bytes in the string table and stores
|
||||
// shorter ones inline in the symbol record, where an eight-byte name has no
|
||||
// terminator. Both must be renamed, and the inline ranges are exactly the
|
||||
// record's name field so a match cannot reach into the record's other fields.
|
||||
func coffStringTableRanges(f *pe.File) []span {
|
||||
symbolTable := int(f.FileHeader.PointerToSymbolTable)
|
||||
symbolCount := int(f.FileHeader.NumberOfSymbols)
|
||||
|
||||
stringTable := symbolTable + symbolCount*coffSymbolSize
|
||||
ranges := []span{{stringTable + 4, stringTable + 4 + len(f.StringTable)}}
|
||||
|
||||
for i := 0; i < symbolCount; {
|
||||
symbol := f.COFFSymbols[i]
|
||||
if storedInline(symbol.Name) {
|
||||
name := symbolTable + i*coffSymbolSize
|
||||
ranges = append(ranges, span{name, name + 8})
|
||||
}
|
||||
i += 1 + int(symbol.NumberOfAuxSymbols)
|
||||
}
|
||||
return ranges
|
||||
}
|
||||
|
||||
func storedInline(name [8]uint8) bool {
|
||||
return name[0] != 0 || name[1] != 0 || name[2] != 0 || name[3] != 0
|
||||
}
|
||||
+99
-25
@@ -1,10 +1,20 @@
|
||||
namespace Frida {
|
||||
/**
|
||||
* Hosts a Frida control endpoint that frida-server-style clients can connect
|
||||
* to, exposing the local system over the network.
|
||||
*/
|
||||
public sealed class ControlService : Object {
|
||||
/**
|
||||
* The endpoint the service listens on.
|
||||
*/
|
||||
public EndpointParameters endpoint_params {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
/**
|
||||
* The options the service was configured with.
|
||||
*/
|
||||
public ControlServiceOptions options {
|
||||
get;
|
||||
construct;
|
||||
@@ -39,6 +49,12 @@ namespace Frida {
|
||||
STOPPING
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a control service listening on the given endpoint.
|
||||
*
|
||||
* @param endpoint_params the endpoint to listen on
|
||||
* @param options service options, or null for the defaults
|
||||
*/
|
||||
public ControlService (EndpointParameters endpoint_params, ControlServiceOptions? options = null) throws Error {
|
||||
#if HAVE_LOCAL_BACKEND
|
||||
ControlServiceOptions opts = (options != null) ? options : new ControlServiceOptions ();
|
||||
@@ -49,8 +65,7 @@ namespace Frida {
|
||||
session = new WindowsHostSession (new WindowsHelperProcess (tempdir), tempdir);
|
||||
#endif
|
||||
#if DARWIN
|
||||
session = new DarwinHostSession (new DarwinHelperBackend (), new TemporaryDirectory (),
|
||||
opts.sysroot, opts.report_crashes);
|
||||
session = new DarwinHostSession (new DarwinHelperBackend (), new TemporaryDirectory (), opts.report_crashes);
|
||||
#endif
|
||||
#if LINUX
|
||||
var tempdir = new TemporaryDirectory ();
|
||||
@@ -74,6 +89,14 @@ namespace Frida {
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a control service exposing a specific device rather than the
|
||||
* local system.
|
||||
*
|
||||
* @param device the device to expose
|
||||
* @param endpoint_params the endpoint to listen on
|
||||
* @param options service options, or null for the defaults
|
||||
*/
|
||||
public async ControlService.with_device (Device device, EndpointParameters endpoint_params,
|
||||
ControlServiceOptions? options = null, Cancellable? cancellable = null) throws Error, IOError {
|
||||
ControlServiceOptions opts = (options != null) ? options : new ControlServiceOptions ();
|
||||
@@ -99,6 +122,7 @@ namespace Frida {
|
||||
|
||||
private void assign_session (HostSession session, HostSessionProvider provider) {
|
||||
host_session = session;
|
||||
host_session.spawn_gating_disabled.connect (on_spawn_gating_disabled);
|
||||
host_session.spawn_added.connect (notify_spawn_added);
|
||||
host_session.child_added.connect (notify_child_added);
|
||||
host_session.child_removed.connect (notify_child_removed);
|
||||
@@ -112,6 +136,9 @@ namespace Frida {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the service, binding the endpoint and accepting clients.
|
||||
*/
|
||||
public async void start (Cancellable? cancellable = null) throws Error, IOError {
|
||||
if (state != STOPPED)
|
||||
throw new Error.INVALID_OPERATION ("Invalid operation");
|
||||
@@ -147,6 +174,9 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the service, disconnecting clients and releasing the endpoint.
|
||||
*/
|
||||
public async void stop (Cancellable? cancellable = null) throws Error, IOError {
|
||||
if (state != STARTED)
|
||||
throw new Error.INVALID_OPERATION ("Invalid operation");
|
||||
@@ -360,18 +390,30 @@ namespace Frida {
|
||||
public void kick_authentication_channel (AuthenticationChannel channel) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
channel.connection.close.begin (io_cancellable);
|
||||
perform_kick.begin (channel);
|
||||
return false;
|
||||
});
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
}
|
||||
|
||||
private async void perform_kick (AuthenticationChannel channel) {
|
||||
try {
|
||||
yield channel.connection.flush (io_cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
try {
|
||||
yield channel.connection.close (io_cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
public async void teardown_control_channel (ControlChannel channel) {
|
||||
yield parent.teardown_control_channel (channel);
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating (ControlChannel requester) throws GLib.Error {
|
||||
yield parent.enable_spawn_gating (requester);
|
||||
public async void enable_spawn_gating (HashTable<string, Variant> options, ControlChannel requester)
|
||||
throws GLib.Error {
|
||||
yield parent.enable_spawn_gating (options, requester);
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (ControlChannel requester) throws GLib.Error {
|
||||
@@ -509,20 +551,27 @@ namespace Frida {
|
||||
return channels.iterator ();
|
||||
}
|
||||
|
||||
private async void enable_spawn_gating (ControlChannel requester) throws GLib.Error {
|
||||
private async void enable_spawn_gating (HashTable<string, Variant> options, ControlChannel requester)
|
||||
throws GLib.Error {
|
||||
bool is_first = spawn_gaters.is_empty;
|
||||
spawn_gaters.add (requester);
|
||||
foreach (var spawn in pending_spawn.values)
|
||||
spawn.pending_approvers.add (requester);
|
||||
|
||||
if (is_first)
|
||||
yield host_session.enable_spawn_gating (io_cancellable);
|
||||
yield host_session.enable_spawn_gating_with_options (options, io_cancellable);
|
||||
}
|
||||
|
||||
private async void disable_spawn_gating (ControlChannel requester) throws GLib.Error {
|
||||
if (spawn_gaters.remove (requester)) {
|
||||
foreach (uint pid in pending_spawn.keys.to_array ())
|
||||
host_session.resume.begin (pid, io_cancellable);
|
||||
foreach (uint pid in pending_spawn.keys.to_array ()) {
|
||||
PendingSpawn spawn = pending_spawn[pid];
|
||||
var approvers = spawn.pending_approvers;
|
||||
if (approvers.remove (requester) && approvers.is_empty) {
|
||||
forget_pending_spawn (pid, spawn);
|
||||
host_session.resume.begin (pid, io_cancellable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (spawn_gaters.is_empty)
|
||||
@@ -547,11 +596,8 @@ namespace Frida {
|
||||
var approvers = spawn.pending_approvers;
|
||||
approvers.remove (requester);
|
||||
if (approvers.is_empty) {
|
||||
pending_spawn.unset (pid);
|
||||
|
||||
forget_pending_spawn (pid, spawn);
|
||||
yield host_session.resume (pid, io_cancellable);
|
||||
|
||||
notify_spawn_removed (spawn.info);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -658,11 +704,29 @@ namespace Frida {
|
||||
return id;
|
||||
}
|
||||
|
||||
// Backend disabled gating and already resumed everything; drop our mirror, tell the gaters.
|
||||
private void on_spawn_gating_disabled (SpawnGatingDisabledReason reason) {
|
||||
foreach (var spawn in pending_spawn.values.to_array ())
|
||||
notify_spawn_removed (spawn.info);
|
||||
pending_spawn.clear ();
|
||||
|
||||
foreach (ControlChannel channel in spawn_gaters)
|
||||
channel.spawn_gating_disabled (reason);
|
||||
spawn_gaters.clear ();
|
||||
}
|
||||
|
||||
private void notify_spawn_added (HostSpawnInfo info) {
|
||||
pending_spawn[info.pid] = new PendingSpawn (info.pid, info.identifier, spawn_gaters.iterator ());
|
||||
|
||||
foreach (ControlChannel channel in spawn_gaters)
|
||||
channel.spawn_added (info);
|
||||
}
|
||||
|
||||
private void forget_pending_spawn (uint pid, PendingSpawn spawn) {
|
||||
pending_spawn.unset (pid);
|
||||
notify_spawn_removed (spawn.info);
|
||||
}
|
||||
|
||||
private void notify_spawn_removed (HostSpawnInfo info) {
|
||||
foreach (ControlChannel channel in spawn_gaters)
|
||||
channel.spawn_removed (info);
|
||||
@@ -698,12 +762,13 @@ namespace Frida {
|
||||
|
||||
private void on_agent_session_detached (AgentSessionId id, SessionDetachReason reason, CrashInfo crash) {
|
||||
AgentSessionEntry entry;
|
||||
if (agent_sessions.unset (id, out entry)) {
|
||||
ControlChannel? controller = entry.controller;
|
||||
if (controller != null) {
|
||||
controller.agent_sessions.remove (id);
|
||||
controller.agent_session_detached (id, reason, crash);
|
||||
}
|
||||
bool entry_found = agent_sessions.unset (id, out entry);
|
||||
ControlChannel? controller = entry_found ? entry.controller : null;
|
||||
GLib.info ("detach-trace: ControlService session detached id=%s reason=%d entry_found=%s controller=%p",
|
||||
id.handle, reason, entry_found.to_string (), controller);
|
||||
if (controller != null) {
|
||||
controller.agent_sessions.remove (id);
|
||||
controller.agent_session_detached (id, reason, crash);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -897,7 +962,12 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating (Cancellable? cancellable) throws GLib.Error {
|
||||
yield parent.enable_spawn_gating (this);
|
||||
yield parent.enable_spawn_gating (make_parameters_dict (), this);
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws GLib.Error {
|
||||
yield parent.enable_spawn_gating (options, this);
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws GLib.Error {
|
||||
@@ -1125,18 +1195,22 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for a {@link ControlService}.
|
||||
*/
|
||||
public sealed class ControlServiceOptions : Object {
|
||||
public string? sysroot {
|
||||
get;
|
||||
set;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to preload the agent for faster first attach.
|
||||
*/
|
||||
public bool enable_preload {
|
||||
get;
|
||||
set;
|
||||
default = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to capture and report crashes in instrumented processes.
|
||||
*/
|
||||
public bool report_crashes {
|
||||
get;
|
||||
set;
|
||||
|
||||
@@ -177,6 +177,13 @@ function applyJailbreakQuirks() {
|
||||
if (inserterResume !== null) {
|
||||
pidsToIgnore = new Set();
|
||||
instrumentInserter(inserterResume);
|
||||
return;
|
||||
}
|
||||
|
||||
const ellekitGate = findEllekitShouldEnableTweaks();
|
||||
if (ellekitGate !== null) {
|
||||
pidsToIgnore = new Set();
|
||||
instrumentEllekitInjector(ellekitGate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,6 +244,18 @@ function instrumentInserter(at) {
|
||||
}, 'int', ['uint', 'uint', 'uint', 'uint']));
|
||||
}
|
||||
|
||||
function instrumentEllekitInjector(shouldEnableTweaks) {
|
||||
const retType = 'int';
|
||||
const argTypes = ['uint'];
|
||||
const original = new NativeFunction(shouldEnableTweaks, retType, argTypes);
|
||||
Interceptor.replace(shouldEnableTweaks, new NativeCallback(pid => {
|
||||
if (pidsToIgnore.delete(pid))
|
||||
return 0;
|
||||
|
||||
return original(pid);
|
||||
}, retType, argTypes));
|
||||
}
|
||||
|
||||
function findJbdCallImpl() {
|
||||
const impl = Module.findGlobalExportByName('jbd_call');
|
||||
if (impl !== null)
|
||||
@@ -330,3 +349,20 @@ function findInserterResume() {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function findEllekitShouldEnableTweaks() {
|
||||
const impl = Module.findGlobalExportByName('should_enable_tweaks');
|
||||
if (impl !== null)
|
||||
return impl;
|
||||
|
||||
for (const name of ['libinjector.dylib', 'TweakLoader.dylib']) {
|
||||
const mod = Process.findModuleByName(name);
|
||||
if (mod !== null) {
|
||||
const exp = mod.findExportByName('should_enable_tweaks');
|
||||
if (exp !== null)
|
||||
return exp;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <sys/sysctl.h>
|
||||
|
||||
#ifdef HAVE_MACOS
|
||||
# import <AppKit/AppKit.h>
|
||||
|
||||
typedef struct _FridaMacModel FridaMacModel;
|
||||
|
||||
@@ -62,3 +63,51 @@ _frida_darwin_host_session_provider_try_extract_icon (void)
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
gchar *
|
||||
_frida_darwin_host_session_path_for_application_identifier (const gchar * identifier, GError ** error)
|
||||
{
|
||||
#ifdef HAVE_MACOS
|
||||
gchar * result = NULL;
|
||||
NSAutoreleasePool * pool;
|
||||
NSString * bundle_id;
|
||||
NSURL * bundle_url, * executable_url;
|
||||
|
||||
pool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
bundle_id = [NSString stringWithUTF8String:identifier];
|
||||
|
||||
bundle_url = [[NSWorkspace sharedWorkspace] URLForApplicationWithBundleIdentifier:bundle_id];
|
||||
if (bundle_url == nil)
|
||||
goto not_found;
|
||||
|
||||
executable_url = [NSBundle bundleWithURL:bundle_url].executableURL;
|
||||
if (executable_url == nil)
|
||||
goto not_found;
|
||||
|
||||
result = g_strdup (executable_url.path.UTF8String);
|
||||
goto beach;
|
||||
|
||||
not_found:
|
||||
{
|
||||
g_set_error (error,
|
||||
FRIDA_ERROR,
|
||||
FRIDA_ERROR_INVALID_ARGUMENT,
|
||||
"Unable to find application with identifier '%s'",
|
||||
identifier);
|
||||
goto beach;
|
||||
}
|
||||
beach:
|
||||
{
|
||||
[pool release];
|
||||
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
g_set_error (error,
|
||||
FRIDA_ERROR,
|
||||
FRIDA_ERROR_NOT_SUPPORTED,
|
||||
"Not supported on this platform");
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
@@ -29,11 +29,6 @@ namespace Frida {
|
||||
construct;
|
||||
}
|
||||
|
||||
public string? sysroot {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public bool report_crashes {
|
||||
get;
|
||||
construct;
|
||||
@@ -44,17 +39,7 @@ namespace Frida {
|
||||
#if HAVE_EMBEDDED_ASSETS
|
||||
return null;
|
||||
#else
|
||||
unowned string path = Config.FRIDA_AGENT_PATH;
|
||||
# if IOS || TVOS
|
||||
unowned string? cryptex_path = Environment.get_variable ("CRYPTEX_MOUNT_PATH");
|
||||
if (cryptex_path != null)
|
||||
return cryptex_path + path;
|
||||
# endif
|
||||
unowned string? root_path = sysroot;
|
||||
if (root_path != null)
|
||||
return root_path + path;
|
||||
|
||||
return path;
|
||||
return Frida.agent_path;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -70,18 +55,17 @@ namespace Frida {
|
||||
private ApplicationEnumerator application_enumerator = new ApplicationEnumerator ();
|
||||
private ProcessEnumerator process_enumerator = new ProcessEnumerator ();
|
||||
|
||||
public DarwinHostSession (owned DarwinHelper helper, owned TemporaryDirectory tempdir, owned string? sysroot = null,
|
||||
bool report_crashes = true) {
|
||||
public DarwinHostSession (owned DarwinHelper helper, owned TemporaryDirectory tempdir, bool report_crashes = true) {
|
||||
Object (
|
||||
helper: helper,
|
||||
tempdir: tempdir,
|
||||
sysroot: sysroot,
|
||||
report_crashes: report_crashes
|
||||
);
|
||||
}
|
||||
|
||||
construct {
|
||||
helper.output.connect (on_output);
|
||||
helper.gating_cancelled.connect (on_spawn_gating_cancelled);
|
||||
helper.spawn_added.connect (on_spawn_added);
|
||||
helper.spawn_removed.connect (on_spawn_removed);
|
||||
|
||||
@@ -92,11 +76,12 @@ namespace Frida {
|
||||
|
||||
#if HAVE_EMBEDDED_ASSETS
|
||||
var blob = Frida.Data.Agent.get_frida_agent_dylib_blob ();
|
||||
agent = new AgentResource (blob.name, new Bytes.static (blob.data), tempdir);
|
||||
agent = new AgentResource (blob.name, copy_to_aligned_pages (blob.data), tempdir);
|
||||
#endif
|
||||
|
||||
#if IOS || TVOS
|
||||
fruit_controller = new FruitController (this, io_cancellable);
|
||||
fruit_controller.gating_cancelled.connect (on_spawn_gating_cancelled);
|
||||
fruit_controller.spawn_added.connect (on_spawn_added);
|
||||
fruit_controller.spawn_removed.connect (on_spawn_removed);
|
||||
fruit_controller.process_crashed.connect (on_process_crashed);
|
||||
@@ -108,6 +93,7 @@ namespace Frida {
|
||||
|
||||
#if IOS || TVOS
|
||||
yield fruit_controller.close (cancellable);
|
||||
fruit_controller.gating_cancelled.disconnect (on_spawn_gating_cancelled);
|
||||
fruit_controller.spawn_added.disconnect (on_spawn_added);
|
||||
fruit_controller.spawn_removed.disconnect (on_spawn_removed);
|
||||
fruit_controller.process_crashed.disconnect (on_process_crashed);
|
||||
@@ -130,6 +116,7 @@ namespace Frida {
|
||||
|
||||
yield helper.close (cancellable);
|
||||
helper.output.disconnect (on_output);
|
||||
helper.gating_cancelled.disconnect (on_spawn_gating_cancelled);
|
||||
helper.spawn_added.disconnect (on_spawn_added);
|
||||
helper.spawn_removed.disconnect (on_spawn_removed);
|
||||
|
||||
@@ -199,11 +186,17 @@ namespace Frida {
|
||||
return yield process_enumerator.enumerate_processes (ProcessQueryOptions._deserialize (options));
|
||||
}
|
||||
|
||||
public override async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
public override async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
var scope = SpawnGatingOptions._deserialize (options).scope;
|
||||
#if IOS || TVOS
|
||||
if (scope == PROCESSES)
|
||||
throw new Error.NOT_SUPPORTED (
|
||||
"Process-level gating is unavailable on this OS; use the 'applications' scope");
|
||||
|
||||
yield fruit_controller.enable_spawn_gating (cancellable);
|
||||
#else
|
||||
yield helper.enable_spawn_gating (cancellable);
|
||||
yield helper.enable_spawn_gating (scope, cancellable);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -213,6 +206,8 @@ namespace Frida {
|
||||
#else
|
||||
yield helper.disable_spawn_gating (cancellable);
|
||||
#endif
|
||||
|
||||
spawn_gating_disabled (APPLICATION_REQUESTED);
|
||||
}
|
||||
|
||||
public override async HostSpawnInfo[] enumerate_pending_spawn (Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -225,12 +220,26 @@ namespace Frida {
|
||||
|
||||
public override async uint spawn (string program, HostSpawnOptions options, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
#if IOS || TVOS
|
||||
if (!program.has_prefix ("/"))
|
||||
return yield fruit_controller.spawn (program, options, cancellable);
|
||||
string path = program;
|
||||
|
||||
#if MACOS
|
||||
if (!path.has_prefix ("/"))
|
||||
path = _path_for_application_identifier (path);
|
||||
#elif IOS || TVOS
|
||||
if (!path.has_prefix ("/"))
|
||||
return yield fruit_controller.spawn (path, options, cancellable);
|
||||
#endif
|
||||
|
||||
return yield helper.spawn (program, options, cancellable);
|
||||
UnixInputStream? stdin_stream = null;
|
||||
UnixOutputStream? stdout_stream = null;
|
||||
UnixOutputStream? stderr_stream = null;
|
||||
if (options.stdio == INHERIT) {
|
||||
stdin_stream = new UnixInputStream (0, false);
|
||||
stdout_stream = new UnixOutputStream (1, false);
|
||||
stderr_stream = new UnixOutputStream (2, false);
|
||||
}
|
||||
|
||||
return yield helper.spawn (path, options, stdin_stream, stdout_stream, stderr_stream, cancellable);
|
||||
}
|
||||
|
||||
protected override async void await_exec_transition (uint pid, Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -290,6 +299,10 @@ namespace Frida {
|
||||
output (pid, fd, data);
|
||||
}
|
||||
|
||||
private void on_spawn_gating_cancelled () {
|
||||
spawn_gating_disabled (WATCHDOG_TIMEOUT);
|
||||
}
|
||||
|
||||
private void on_spawn_added (HostSpawnInfo info) {
|
||||
spawn_added (info);
|
||||
}
|
||||
@@ -333,10 +346,39 @@ namespace Frida {
|
||||
|
||||
base.on_uninjected (id);
|
||||
}
|
||||
|
||||
public extern static string _path_for_application_identifier (string identifier) throws Error;
|
||||
|
||||
#if HAVE_EMBEDDED_ASSETS
|
||||
private static Bytes copy_to_aligned_pages (uint8[] data) {
|
||||
size_t page_size = Gum.query_page_size ();
|
||||
size_t size = data.length;
|
||||
uint8 * pages = (uint8 *) Gum.memory_allocate (null, size, page_size, READ | WRITE);
|
||||
Memory.copy (pages, data, size);
|
||||
unowned uint8[] view = (uint8[]) pages;
|
||||
view.length = (int) size;
|
||||
return Bytes.new_with_owner<AlignedPages> (view, new AlignedPages (pages, size));
|
||||
}
|
||||
|
||||
private class AlignedPages {
|
||||
private uint8 * pages;
|
||||
private size_t size;
|
||||
|
||||
public AlignedPages (uint8 * pages, size_t size) {
|
||||
this.pages = pages;
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
~AlignedPages () {
|
||||
Gum.memory_free (pages, size);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if IOS || TVOS
|
||||
private sealed class FruitController : Object, MappedAgentContainer {
|
||||
public signal void gating_cancelled ();
|
||||
public signal void spawn_added (HostSpawnInfo info);
|
||||
public signal void spawn_removed (HostSpawnInfo info);
|
||||
public signal void process_crashed (CrashInfo crash);
|
||||
@@ -361,6 +403,7 @@ namespace Frida {
|
||||
private bool spawn_gating_enabled = false;
|
||||
private Gee.HashMap<string, Promise<uint>> spawn_requests = new Gee.HashMap<string, Promise<uint>> ();
|
||||
private Gee.HashMap<uint, HostSpawnInfo?> pending_spawn = new Gee.HashMap<uint, HostSpawnInfo?> ();
|
||||
private SpawnGatingWatchdog watchdog = new SpawnGatingWatchdog ();
|
||||
|
||||
private CrashReporterState crash_reporter_state;
|
||||
private Gee.HashMap<uint, ReportCrashAgent> crash_agents = new Gee.HashMap<uint, ReportCrashAgent> ();
|
||||
@@ -399,6 +442,8 @@ namespace Frida {
|
||||
|
||||
helper.process_resumed.connect (on_process_resumed);
|
||||
helper.process_killed.connect (on_process_killed);
|
||||
|
||||
watchdog.expired.connect (on_watchdog_expired);
|
||||
}
|
||||
|
||||
~FruitController () {
|
||||
@@ -432,6 +477,8 @@ namespace Frida {
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
spawn_gating_enabled = false;
|
||||
|
||||
watchdog.clear ();
|
||||
|
||||
yield launchd_agent.disable_spawn_gating (cancellable);
|
||||
|
||||
var pending = pending_spawn.values.to_array ();
|
||||
@@ -496,6 +543,7 @@ namespace Frida {
|
||||
HostSpawnInfo? info;
|
||||
if (!pending_spawn.unset (pid, out info))
|
||||
return false;
|
||||
watchdog.cancel (pid);
|
||||
spawn_removed (info);
|
||||
|
||||
yield helper.resume (pid, cancellable);
|
||||
@@ -628,6 +676,11 @@ namespace Frida {
|
||||
launchd_agent.claim_process.begin (pid, io_cancellable);
|
||||
}
|
||||
|
||||
private void on_watchdog_expired () {
|
||||
disable_spawn_gating.begin (io_cancellable);
|
||||
gating_cancelled ();
|
||||
}
|
||||
|
||||
private async void handle_spawn (HostSpawnInfo info) {
|
||||
try {
|
||||
var pid = info.pid;
|
||||
@@ -646,6 +699,7 @@ namespace Frida {
|
||||
}
|
||||
|
||||
pending_spawn[pid] = info;
|
||||
watchdog.arm (pid);
|
||||
spawn_added (info);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
@@ -1053,7 +1107,7 @@ namespace Frida {
|
||||
|
||||
var parameters = make_parameters_dict ();
|
||||
try {
|
||||
var header = new Json.Reader (Json.from_string (raw_header));
|
||||
var header = make_json_reader (raw_header);
|
||||
foreach (string member in header.list_members ()) {
|
||||
header.read_member (member);
|
||||
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <gio/gunixinputstream.h>
|
||||
#include <gio/gunixoutputstream.h>
|
||||
#include <glib-unix.h>
|
||||
#include <spawn.h>
|
||||
#include <stdio.h>
|
||||
@@ -793,7 +795,8 @@ _frida_darwin_helper_backend_schedule_on_dispatch_queue (FridaDarwinHelperBacken
|
||||
|
||||
guint
|
||||
_frida_darwin_helper_backend_spawn (FridaDarwinHelperBackend * self, const gchar * path,
|
||||
FridaHostSpawnOptions * options, FridaStdioPipes ** pipes, GError ** error)
|
||||
FridaHostSpawnOptions * options, GUnixInputStream * stdin_stream, GUnixOutputStream * stdout_stream,
|
||||
GUnixOutputStream * stderr_stream, FridaStdioPipes ** pipes, GError ** error)
|
||||
{
|
||||
pid_t pid;
|
||||
FridaSpawnInstance * instance;
|
||||
@@ -809,7 +812,7 @@ _frida_darwin_helper_backend_spawn (FridaDarwinHelperBackend * self, const gchar
|
||||
FridaAslr aslr = FRIDA_ASLR_AUTO;
|
||||
GVariant * aslr_value;
|
||||
gchar * old_cwd = NULL;
|
||||
int result, spawn_errno;
|
||||
int result;
|
||||
|
||||
*pipes = NULL;
|
||||
|
||||
@@ -830,9 +833,18 @@ _frida_darwin_helper_backend_spawn (FridaDarwinHelperBackend * self, const gchar
|
||||
if (stdio_error != NULL)
|
||||
goto propagate_stdio_error;
|
||||
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, (in_fd != NULL) ? in_fd->handle : 0, 0);
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, (out_fd != NULL) ? out_fd->handle : 1, 1);
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, (err_fd != NULL) ? err_fd->handle : 2, 2);
|
||||
if (stdin_stream != NULL)
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, g_unix_input_stream_get_fd (stdin_stream), 0);
|
||||
else if (in_fd != NULL)
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, in_fd->handle, 0);
|
||||
if (stdout_stream != NULL)
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, g_unix_output_stream_get_fd (stdout_stream), 1);
|
||||
else if (out_fd != NULL)
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, out_fd->handle, 1);
|
||||
if (stderr_stream != NULL)
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, g_unix_output_stream_get_fd (stderr_stream), 2);
|
||||
else if (err_fd != NULL)
|
||||
posix_spawn_file_actions_adddup2 (&file_actions, err_fd->handle, 2);
|
||||
|
||||
aslr_value = g_hash_table_lookup (options->aux, "aslr");
|
||||
if (aslr_value != NULL && !frida_parse_aslr_option (aslr_value, &aslr, error))
|
||||
@@ -850,7 +862,6 @@ _frida_darwin_helper_backend_spawn (FridaDarwinHelperBackend * self, const gchar
|
||||
}
|
||||
|
||||
result = posix_spawn (&pid, path, &file_actions, &attributes, argv, envp);
|
||||
spawn_errno = errno;
|
||||
|
||||
if (old_cwd != NULL)
|
||||
chdir (old_cwd);
|
||||
@@ -885,7 +896,7 @@ chdir_failed:
|
||||
}
|
||||
spawn_failed:
|
||||
{
|
||||
if (spawn_errno == EAGAIN)
|
||||
if (result == EAGAIN)
|
||||
{
|
||||
g_set_error (error,
|
||||
FRIDA_ERROR,
|
||||
@@ -899,7 +910,7 @@ spawn_failed:
|
||||
FRIDA_ERROR,
|
||||
FRIDA_ERROR_NOT_SUPPORTED,
|
||||
"Unable to spawn executable at '%s': %s",
|
||||
path, g_strerror (spawn_errno));
|
||||
path, g_strerror (result));
|
||||
}
|
||||
|
||||
goto any_failure;
|
||||
@@ -2125,7 +2136,11 @@ _frida_darwin_helper_backend_prepare_spawn_instance_for_injection (FridaDarwinHe
|
||||
|
||||
previous_ports = &instance->previous_ports;
|
||||
kr = thread_swap_exception_ports (child_thread,
|
||||
EXC_MASK_ALL,
|
||||
#if __has_feature (ptrauth_calls)
|
||||
EXC_MASK_BAD_ACCESS | EXC_MASK_BREAKPOINT | EXC_MASK_CRASH,
|
||||
#else
|
||||
EXC_MASK_BREAKPOINT | EXC_MASK_CRASH,
|
||||
#endif
|
||||
instance->server_port,
|
||||
EXCEPTION_DEFAULT,
|
||||
state_flavor,
|
||||
@@ -2867,6 +2882,9 @@ frida_spawn_instance_on_server_recv (void * context)
|
||||
thread_state_flavor_t state_flavor = GUM_DARWIN_THREAD_STATE_FLAVOR;
|
||||
mach_msg_type_number_t state_count = GUM_DARWIN_THREAD_STATE_COUNT;
|
||||
GumDarwinUnifiedThreadState state;
|
||||
#if __has_feature (ptrauth_calls)
|
||||
gboolean pc_may_need_fixup_due_to_ptrauth_failure;
|
||||
#endif
|
||||
guint i, current_bp_index;
|
||||
FridaBreakpoint * breakpoint = NULL;
|
||||
gboolean carry_on, pc_changed;
|
||||
@@ -2895,7 +2913,6 @@ frida_spawn_instance_on_server_recv (void * context)
|
||||
#if __has_feature (ptrauth_calls)
|
||||
{
|
||||
const GumAddress ret_gadget = frida_spawn_instance_get_ret_gadget_address (self);
|
||||
gboolean pc_may_need_fixup_due_to_ptrauth_failure;
|
||||
|
||||
pc_may_need_fixup_due_to_ptrauth_failure = gum_strip_code_address (GUM_ADDRESS (state.ts_64.__opaque_pc)) == ret_gadget;
|
||||
if (pc_may_need_fixup_due_to_ptrauth_failure)
|
||||
@@ -2918,7 +2935,16 @@ frida_spawn_instance_on_server_recv (void * context)
|
||||
#endif
|
||||
|
||||
if (request->exception != EXC_BREAKPOINT)
|
||||
goto unexpected_exception;
|
||||
{
|
||||
#if __has_feature (ptrauth_calls)
|
||||
if (!pc_may_need_fixup_due_to_ptrauth_failure)
|
||||
#endif
|
||||
{
|
||||
if (request->exception == EXC_CRASH)
|
||||
goto unexpected_exception;
|
||||
goto forward_to_system;
|
||||
}
|
||||
}
|
||||
|
||||
if (self->single_stepping >= 0)
|
||||
{
|
||||
@@ -3072,6 +3098,27 @@ unexpected_exception:
|
||||
g_error_free (error);
|
||||
g_string_free (message, TRUE);
|
||||
|
||||
return;
|
||||
}
|
||||
forward_to_system:
|
||||
{
|
||||
__Reply__exception_raise_t response;
|
||||
mach_msg_header_t * header;
|
||||
|
||||
bzero (&response, sizeof (response));
|
||||
header = &response.Head;
|
||||
header->msgh_bits = MACH_MSGH_BITS (MACH_MSG_TYPE_MOVE_SEND_ONCE, 0);
|
||||
header->msgh_size = sizeof (response);
|
||||
header->msgh_remote_port = request->Head.msgh_remote_port;
|
||||
header->msgh_local_port = MACH_PORT_NULL;
|
||||
header->msgh_reserved = 0;
|
||||
header->msgh_id = request->Head.msgh_id + 100;
|
||||
response.NDR = NDR_record;
|
||||
response.RetCode = KERN_FAILURE;
|
||||
kr = mach_msg_send (header);
|
||||
if (kr == KERN_SUCCESS)
|
||||
request->Head.msgh_remote_port = MACH_PORT_NULL;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ namespace Frida {
|
||||
|
||||
dtrace_agent = DTraceAgent.try_open ();
|
||||
if (dtrace_agent != null) {
|
||||
dtrace_agent.gating_cancelled.connect (on_dtrace_agent_gating_cancelled);
|
||||
dtrace_agent.spawn_added.connect (on_dtrace_agent_spawn_added);
|
||||
dtrace_agent.spawn_removed.connect (on_dtrace_agent_spawn_removed);
|
||||
}
|
||||
@@ -97,6 +98,7 @@ namespace Frida {
|
||||
yield;
|
||||
|
||||
if (dtrace_agent != null) {
|
||||
dtrace_agent.gating_cancelled.disconnect (on_dtrace_agent_gating_cancelled);
|
||||
dtrace_agent.spawn_added.disconnect (on_dtrace_agent_spawn_added);
|
||||
dtrace_agent.spawn_removed.disconnect (on_dtrace_agent_spawn_removed);
|
||||
yield dtrace_agent.close (cancellable);
|
||||
@@ -107,8 +109,8 @@ namespace Frida {
|
||||
public async void preload (Cancellable? cancellable) throws Error, IOError {
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
get_dtrace_agent ().enable_spawn_gating ();
|
||||
public async void enable_spawn_gating (SpawnGatingScope scope, Cancellable? cancellable) throws Error, IOError {
|
||||
get_dtrace_agent ().enable_spawn_gating (scope);
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -126,12 +128,13 @@ namespace Frida {
|
||||
return dtrace_agent;
|
||||
}
|
||||
|
||||
public async uint spawn (string path, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint spawn (string path, HostSpawnOptions options, UnixInputStream? stdin_stream,
|
||||
UnixOutputStream? stdout_stream, UnixOutputStream? stderr_stream, Cancellable? cancellable) throws Error, IOError {
|
||||
if (!FileUtils.test (path, EXISTS))
|
||||
throw new Error.EXECUTABLE_NOT_FOUND ("Unable to find executable at '%s'", path);
|
||||
|
||||
StdioPipes? pipes;
|
||||
var child_pid = _spawn (path, options, out pipes);
|
||||
var child_pid = _spawn (path, options, stdin_stream, stdout_stream, stderr_stream, out pipes);
|
||||
|
||||
ChildWatch.add ((Pid) child_pid, on_child_dead);
|
||||
|
||||
@@ -629,6 +632,10 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private void on_dtrace_agent_gating_cancelled () {
|
||||
gating_cancelled ();
|
||||
}
|
||||
|
||||
private void on_dtrace_agent_spawn_added (HostSpawnInfo info) {
|
||||
spawn_added (info);
|
||||
}
|
||||
@@ -664,7 +671,8 @@ namespace Frida {
|
||||
protected extern void _destroy_dispatch_context ();
|
||||
protected extern void _schedule_on_dispatch_queue (DispatchWorker worker);
|
||||
|
||||
protected extern uint _spawn (string path, HostSpawnOptions options, out StdioPipes? pipes) throws Error;
|
||||
protected extern uint _spawn (string path, HostSpawnOptions options, UnixInputStream? stdin_stream,
|
||||
UnixOutputStream? stdout_stream, UnixOutputStream? stderr_stream, out StdioPipes? pipes) throws Error;
|
||||
protected extern static void _launch (string identifier, HostSpawnOptions options, LaunchCompletionHandler on_complete);
|
||||
protected extern static bool _is_suspended (uint task) throws Error;
|
||||
public extern static void resume_process (uint task) throws Error;
|
||||
@@ -688,15 +696,22 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public sealed class DTraceAgent : Object {
|
||||
public signal void gating_cancelled ();
|
||||
public signal void spawn_added (HostSpawnInfo info);
|
||||
public signal void spawn_removed (HostSpawnInfo info);
|
||||
|
||||
private Subprocess? dtrace;
|
||||
private DataInputStream input;
|
||||
private SpawnGatingScope scope;
|
||||
private Gee.HashMap<uint, HostSpawnInfo?> pending_spawn = new Gee.HashMap<uint, HostSpawnInfo?> ();
|
||||
private SpawnGatingWatchdog watchdog = new SpawnGatingWatchdog ();
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
// Present in every path inside an app bundle, so APPLICATIONS gates the whole app
|
||||
// (helpers, XPC services, ...), not just its entry point.
|
||||
private const string APP_BUNDLE_MARKER = ".app/";
|
||||
|
||||
public static DTraceAgent? try_open () {
|
||||
if (Posix.getuid () != 0)
|
||||
return null;
|
||||
@@ -704,6 +719,15 @@ namespace Frida {
|
||||
return new DTraceAgent ();
|
||||
}
|
||||
|
||||
construct {
|
||||
watchdog.expired.connect (on_watchdog_expired);
|
||||
}
|
||||
|
||||
private void on_watchdog_expired () {
|
||||
disable_spawn_gating.begin (io_cancellable);
|
||||
gating_cancelled ();
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
if (dtrace != null) {
|
||||
try {
|
||||
@@ -714,10 +738,12 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public void enable_spawn_gating () throws Error {
|
||||
public void enable_spawn_gating (SpawnGatingScope scope) throws Error {
|
||||
if (dtrace != null)
|
||||
throw new Error.INVALID_OPERATION ("Already enabled");
|
||||
|
||||
this.scope = scope;
|
||||
|
||||
string? predicate = Environment.get_variable ("FRIDA_DTRACE_PREDICATE");
|
||||
string predicate_clause;
|
||||
if (predicate != null)
|
||||
@@ -776,6 +802,7 @@ namespace Frida {
|
||||
spawn_removed (e.value);
|
||||
}
|
||||
pending_spawn.clear ();
|
||||
watchdog.clear ();
|
||||
}
|
||||
|
||||
public HostSpawnInfo[] enumerate_pending_spawn () {
|
||||
@@ -788,8 +815,10 @@ namespace Frida {
|
||||
|
||||
public void on_resume (uint pid) {
|
||||
HostSpawnInfo? info;
|
||||
if (pending_spawn.unset (pid, out info))
|
||||
if (pending_spawn.unset (pid, out info)) {
|
||||
watchdog.cancel (pid);
|
||||
spawn_removed (info);
|
||||
}
|
||||
}
|
||||
|
||||
private async void process_incoming_messages () {
|
||||
@@ -837,8 +866,14 @@ namespace Frida {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (scope != PROCESSES && !path.contains (APP_BUNDLE_MARKER)) {
|
||||
DarwinHelperBackend.resume_without_validation (pid);
|
||||
continue;
|
||||
}
|
||||
|
||||
var info = HostSpawnInfo (pid, path);
|
||||
pending_spawn[pid] = info;
|
||||
watchdog.arm (pid);
|
||||
spawn_added (info);
|
||||
}
|
||||
} catch (IOError e) {
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Frida {
|
||||
public async void preload (Cancellable? cancellable) throws Error, IOError {
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void enable_spawn_gating (SpawnGatingScope scope, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
@@ -35,7 +35,8 @@ namespace Frida {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
public async uint spawn (string path, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint spawn (string path, HostSpawnOptions options, UnixInputStream? stdin_stream,
|
||||
UnixOutputStream? stdout_stream, UnixOutputStream? stderr_stream, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
}
|
||||
|
||||
|
||||
@@ -56,10 +56,10 @@ namespace Frida {
|
||||
yield obtain (cancellable);
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void enable_spawn_gating (SpawnGatingScope scope, Cancellable? cancellable) throws Error, IOError {
|
||||
var helper = yield obtain (cancellable);
|
||||
try {
|
||||
yield helper.enable_spawn_gating (cancellable);
|
||||
yield helper.enable_spawn_gating (scope, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_helper_error (e);
|
||||
}
|
||||
@@ -83,9 +83,12 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public async uint spawn (string path, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint spawn (string path, HostSpawnOptions options, UnixInputStream? stdin_stream,
|
||||
UnixOutputStream? stdout_stream, UnixOutputStream? stderr_stream, Cancellable? cancellable) throws Error, IOError {
|
||||
var helper = yield obtain (cancellable);
|
||||
try {
|
||||
if (stdin_stream != null)
|
||||
return yield helper.spawn_with_stdio (path, options, stdin_stream, stdout_stream, stderr_stream, cancellable);
|
||||
return yield helper.spawn (path, options, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_helper_error (e);
|
||||
@@ -306,99 +309,95 @@ namespace Frida {
|
||||
}
|
||||
|
||||
private async DarwinRemoteHelper launch_helper (Cancellable? cancellable) throws Error, IOError {
|
||||
string? pending_socket_path = null;
|
||||
Pid pending_pid = 0;
|
||||
IOStream? pending_stream = null;
|
||||
DBusConnection pending_connection = null;
|
||||
DarwinRemoteHelper pending_proxy = null;
|
||||
|
||||
SocketService? service = null;
|
||||
TimeoutSource? timeout_source = null;
|
||||
bool timed_out = false;
|
||||
bool child_exited = false;
|
||||
int child_status = 0;
|
||||
ChildExitMonitor? exit_monitor = null;
|
||||
ulong exit_handler = 0;
|
||||
|
||||
DBusConnection? established = null;
|
||||
GLib.Error? handshake_error = null;
|
||||
bool handshake_done = false;
|
||||
bool waiting = false;
|
||||
|
||||
try {
|
||||
string tempdir;
|
||||
HelperFile helper_file = get_resource_store ().helper;
|
||||
string helper_path = helper_file.path;
|
||||
if (helper_file is TemporaryHelperFile)
|
||||
tempdir = Path.get_dirname (helper_path);
|
||||
else
|
||||
tempdir = Environment.get_tmp_dir ();
|
||||
|
||||
pending_socket_path = Path.build_filename (tempdir, Uuid.string_random ());
|
||||
string socket_address = "unix:path=" + pending_socket_path;
|
||||
int sv[2];
|
||||
if (Posix.socketpair (Posix.AF_UNIX, Posix.SOCK_STREAM, 0, sv) != 0)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to allocate socketpair: %s", strerror (errno));
|
||||
var parent_fd = new FileDescriptor (sv[0]);
|
||||
var peer_fd = new FileDescriptor (sv[1]);
|
||||
|
||||
service = new SocketService ();
|
||||
SocketAddress effective_address;
|
||||
service.add_address (new UnixSocketAddress.with_type (pending_socket_path, -1, PATH),
|
||||
SocketType.STREAM, SocketProtocol.DEFAULT, null, out effective_address);
|
||||
service.start ();
|
||||
|
||||
var main_context = MainContext.get_thread_default ();
|
||||
|
||||
var idle_source = new IdleSource ();
|
||||
idle_source.set_callback (() => {
|
||||
launch_helper.callback ();
|
||||
return false;
|
||||
});
|
||||
idle_source.attach (main_context);
|
||||
yield;
|
||||
|
||||
var incoming_handler = service.incoming.connect ((c) => {
|
||||
pending_stream = c;
|
||||
launch_helper.callback ();
|
||||
return true;
|
||||
});
|
||||
|
||||
timeout_source = new TimeoutSource.seconds (10);
|
||||
timeout_source.set_callback (() => {
|
||||
timed_out = true;
|
||||
launch_helper.callback ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
timeout_source.attach (main_context);
|
||||
Socket parent_socket = new Socket.from_fd (parent_fd.steal ());
|
||||
pending_stream = SocketConnection.factory_create_connection (parent_socket);
|
||||
|
||||
string socket_address = "socket-fd:%d".printf (peer_fd.handle);
|
||||
string[] argv = { helper_path, socket_address };
|
||||
|
||||
GLib.SpawnFlags flags =
|
||||
GLib.SpawnFlags.LEAVE_DESCRIPTORS_OPEN |
|
||||
GLib.SpawnFlags.DO_NOT_REAP_CHILD |
|
||||
GLib.SpawnFlags.STDOUT_TO_DEV_NULL |
|
||||
GLib.SpawnFlags.STDERR_TO_DEV_NULL |
|
||||
/* GLib.SpawnFlags.STDIN_FROM_DEV_NULL */ 2048 |
|
||||
/* GLib.SpawnFlags.CLOEXEC_PIPES */ 256;
|
||||
|
||||
GLib.Process.spawn_async (null, argv, null, flags, null, out pending_pid);
|
||||
|
||||
var exit_monitor = new ChildExitMonitor (pending_pid, main_context);
|
||||
var exit_handler = exit_monitor.exited.connect ((status) => {
|
||||
child_exited = true;
|
||||
child_status = status;
|
||||
launch_helper.callback ();
|
||||
});
|
||||
peer_fd = null;
|
||||
|
||||
while (pending_stream == null && !timed_out && !child_exited)
|
||||
yield;
|
||||
GLib.SourceFunc resume = launch_helper.callback;
|
||||
|
||||
exit_monitor.disconnect (exit_handler);
|
||||
exit_monitor.stop ();
|
||||
|
||||
timeout_source.destroy ();
|
||||
timeout_source = null;
|
||||
|
||||
service.disconnect (incoming_handler);
|
||||
service.stop ();
|
||||
service = null;
|
||||
|
||||
if (pending_stream == null) {
|
||||
if (child_exited)
|
||||
throw new Error.PROCESS_NOT_FOUND ("Helper exited during launch (status=%d)", child_status);
|
||||
if (timed_out)
|
||||
throw new Error.TIMED_OUT ("Unexpectedly timed out while spawning helper");
|
||||
var handshake_cancellable = new Cancellable ();
|
||||
ulong cancel_chain = 0;
|
||||
if (cancellable != null) {
|
||||
cancel_chain = cancellable.cancelled.connect (() => {
|
||||
handshake_cancellable.cancel ();
|
||||
});
|
||||
}
|
||||
|
||||
pending_connection = yield new DBusConnection (pending_stream, ServerGuid.HOST_SESSION_SERVICE,
|
||||
AUTHENTICATION_SERVER | AUTHENTICATION_ALLOW_ANONYMOUS, null, cancellable);
|
||||
var main_context = MainContext.get_thread_default ();
|
||||
exit_monitor = new ChildExitMonitor (pending_pid, main_context);
|
||||
exit_handler = exit_monitor.exited.connect ((status) => {
|
||||
child_exited = true;
|
||||
child_status = status;
|
||||
handshake_cancellable.cancel ();
|
||||
});
|
||||
|
||||
establish_dbus_connection.begin (pending_stream, handshake_cancellable, (obj, res) => {
|
||||
try {
|
||||
established = establish_dbus_connection.end (res);
|
||||
} catch (GLib.Error e) {
|
||||
handshake_error = e;
|
||||
}
|
||||
handshake_done = true;
|
||||
if (waiting)
|
||||
resume ();
|
||||
});
|
||||
|
||||
waiting = true;
|
||||
while (!handshake_done)
|
||||
yield;
|
||||
waiting = false;
|
||||
|
||||
if (cancel_chain != 0)
|
||||
cancellable.disconnect (cancel_chain);
|
||||
|
||||
if (handshake_error != null && !child_exited)
|
||||
exit_monitor.poll ();
|
||||
|
||||
if (child_exited)
|
||||
throw new Error.PROCESS_NOT_FOUND ("Helper exited during launch (status=%d)", child_status);
|
||||
if (handshake_error != null)
|
||||
throw handshake_error;
|
||||
|
||||
pending_connection = established;
|
||||
pending_proxy = yield pending_connection.get_proxy (null, ObjectPath.HELPER, DO_NOT_LOAD_PROPERTIES,
|
||||
cancellable);
|
||||
|
||||
@@ -410,18 +409,14 @@ namespace Frida {
|
||||
reap_later.begin (pending_pid);
|
||||
}
|
||||
|
||||
if (timeout_source != null)
|
||||
timeout_source.destroy ();
|
||||
|
||||
if (service != null)
|
||||
service.stop ();
|
||||
|
||||
if (e is Error)
|
||||
throw (Error) e;
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
} finally {
|
||||
if (pending_socket_path != null)
|
||||
Posix.unlink (pending_socket_path);
|
||||
if (exit_monitor != null) {
|
||||
exit_monitor.disconnect (exit_handler);
|
||||
exit_monitor.stop ();
|
||||
}
|
||||
}
|
||||
|
||||
process_pid = pending_pid;
|
||||
@@ -431,6 +426,7 @@ namespace Frida {
|
||||
|
||||
proxy = pending_proxy;
|
||||
proxy.output.connect (on_output);
|
||||
proxy.gating_cancelled.connect (on_gating_cancelled);
|
||||
proxy.spawn_added.connect (on_spawn_added);
|
||||
proxy.spawn_removed.connect (on_spawn_removed);
|
||||
proxy.injected.connect (on_injected);
|
||||
@@ -441,10 +437,16 @@ namespace Frida {
|
||||
return proxy;
|
||||
}
|
||||
|
||||
private async DBusConnection establish_dbus_connection (IOStream stream, Cancellable? cancellable) throws GLib.Error {
|
||||
return yield new DBusConnection (stream, ServerGuid.HOST_SESSION_SERVICE,
|
||||
AUTHENTICATION_SERVER | AUTHENTICATION_ALLOW_ANONYMOUS, null, cancellable);
|
||||
}
|
||||
|
||||
private void on_connection_closed (bool remote_peer_vanished, GLib.Error? error) {
|
||||
obtain_request = null;
|
||||
|
||||
proxy.output.disconnect (on_output);
|
||||
proxy.gating_cancelled.disconnect (on_gating_cancelled);
|
||||
proxy.spawn_added.disconnect (on_spawn_added);
|
||||
proxy.spawn_removed.disconnect (on_spawn_removed);
|
||||
proxy.injected.disconnect (on_injected);
|
||||
@@ -466,6 +468,10 @@ namespace Frida {
|
||||
output (pid, fd, data);
|
||||
}
|
||||
|
||||
private void on_gating_cancelled () {
|
||||
gating_cancelled ();
|
||||
}
|
||||
|
||||
private void on_spawn_added (HostSpawnInfo info) {
|
||||
spawn_added (info);
|
||||
}
|
||||
@@ -497,11 +503,14 @@ namespace Frida {
|
||||
private set;
|
||||
}
|
||||
|
||||
#if HAVE_EMBEDDED_ASSETS && MACOS && ARM64
|
||||
private TemporaryDirectory tempdir;
|
||||
|
||||
#if MACOS && ARM64
|
||||
private bool thinned = false;
|
||||
#endif
|
||||
|
||||
public ResourceStore (TemporaryDirectory tempdir) throws Error {
|
||||
this.tempdir = tempdir;
|
||||
#if HAVE_EMBEDDED_ASSETS
|
||||
FileUtils.chmod (tempdir.path, 0755);
|
||||
|
||||
@@ -512,27 +521,27 @@ namespace Frida {
|
||||
tempdir));
|
||||
FileUtils.chmod (helper.path, 0700);
|
||||
#else
|
||||
helper = new InstalledHelperFile.for_path (Config.FRIDA_HELPER_PATH);
|
||||
helper = new InstalledHelperFile.for_path (Frida.helper_path);
|
||||
#endif
|
||||
}
|
||||
|
||||
~ResourceStore () {
|
||||
#if HAVE_EMBEDDED_ASSETS
|
||||
((TemporaryHelperFile) helper).file.destroy ();
|
||||
#endif
|
||||
if (helper is TemporaryHelperFile)
|
||||
((TemporaryHelperFile) helper).file.destroy ();
|
||||
}
|
||||
|
||||
public bool maybe_thin_helper_to_basic_abi () {
|
||||
#if HAVE_EMBEDDED_ASSETS && MACOS && ARM64
|
||||
#if MACOS && ARM64
|
||||
if (thinned)
|
||||
return false;
|
||||
|
||||
var blob = Frida.Data.Helper.get_frida_helper_blob ();
|
||||
|
||||
var input = new DataInputStream (new MemoryInputStream.from_data (blob.data, null));
|
||||
input.byte_order = BIG_ENDIAN;
|
||||
|
||||
try {
|
||||
uint8[] universal_data;
|
||||
FileUtils.get_data (helper.path, out universal_data);
|
||||
|
||||
var input = new DataInputStream (new MemoryInputStream.from_data (universal_data, null));
|
||||
input.byte_order = BIG_ENDIAN;
|
||||
|
||||
const uint32 fat_magic = 0xcafebabeU;
|
||||
var magic = input.read_uint32 ();
|
||||
if (magic != fat_magic)
|
||||
@@ -564,9 +573,15 @@ namespace Frida {
|
||||
if (arm64e_offset == 0 || arm64_offset == 0)
|
||||
return false;
|
||||
|
||||
FileUtils.set_data (helper.path, blob.data[arm64_offset:arm64_offset + arm64_size]);
|
||||
FileUtils.chmod (helper.path, 0700);
|
||||
var thin_file = new TemporaryFile.from_stream ("frida-helper-arm64",
|
||||
new MemoryInputStream.from_data (universal_data[arm64_offset:arm64_offset + arm64_size], null),
|
||||
tempdir);
|
||||
FileUtils.chmod (thin_file.path, 0700);
|
||||
|
||||
if (helper is TemporaryHelperFile)
|
||||
((TemporaryHelperFile) helper).file.destroy ();
|
||||
|
||||
helper = new TemporaryHelperFile (thin_file);
|
||||
thinned = true;
|
||||
|
||||
return true;
|
||||
@@ -604,6 +619,19 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
public bool poll () {
|
||||
if (source == null)
|
||||
return false;
|
||||
|
||||
int status;
|
||||
if (!try_reap (pid, out status))
|
||||
return false;
|
||||
|
||||
stop ();
|
||||
exited (status);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void arm () {
|
||||
source = new TimeoutSource (interval_ms);
|
||||
source.set_callback (() => {
|
||||
|
||||
@@ -44,6 +44,7 @@ namespace Frida {
|
||||
construct {
|
||||
backend.idle.connect (on_backend_idle);
|
||||
backend.output.connect (on_backend_output);
|
||||
backend.gating_cancelled.connect (on_backend_gating_cancelled);
|
||||
backend.spawn_added.connect (on_backend_spawn_added);
|
||||
backend.spawn_removed.connect (on_backend_spawn_removed);
|
||||
backend.injected.connect (on_backend_injected);
|
||||
@@ -93,6 +94,7 @@ namespace Frida {
|
||||
}
|
||||
backend.idle.disconnect (on_backend_idle);
|
||||
backend.output.disconnect (on_backend_output);
|
||||
backend.gating_cancelled.disconnect (on_backend_gating_cancelled);
|
||||
backend.spawn_added.disconnect (on_backend_spawn_added);
|
||||
backend.spawn_removed.disconnect (on_backend_spawn_removed);
|
||||
backend.injected.disconnect (on_backend_injected);
|
||||
@@ -111,7 +113,11 @@ namespace Frida {
|
||||
|
||||
private async void start () {
|
||||
try {
|
||||
connection = yield new DBusConnection.for_address (parent_address,
|
||||
int fd = int.parse (parent_address.substring ("socket-fd:".length));
|
||||
var sock = new Socket.from_fd (fd);
|
||||
IOStream stream = SocketConnection.factory_create_connection (sock);
|
||||
|
||||
connection = yield new DBusConnection (stream, null,
|
||||
AUTHENTICATION_CLIENT | DELAY_MESSAGE_PROCESSING);
|
||||
connection.on_closed.connect (on_connection_closed);
|
||||
|
||||
@@ -143,8 +149,8 @@ namespace Frida {
|
||||
shutdown.begin ();
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
yield backend.enable_spawn_gating (cancellable);
|
||||
public async void enable_spawn_gating (SpawnGatingScope scope, Cancellable? cancellable) throws Error, IOError {
|
||||
yield backend.enable_spawn_gating (scope, cancellable);
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -156,7 +162,12 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public async uint spawn (string path, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError {
|
||||
return yield backend.spawn (path, options, cancellable);
|
||||
return yield backend.spawn (path, options, null, null, null, cancellable);
|
||||
}
|
||||
|
||||
public async uint spawn_with_stdio (string path, HostSpawnOptions options, UnixInputStream stdin_stream,
|
||||
UnixOutputStream stdout_stream, UnixOutputStream stderr_stream, Cancellable? cancellable) throws Error, IOError {
|
||||
return yield backend.spawn (path, options, stdin_stream, stdout_stream, stderr_stream, cancellable);
|
||||
}
|
||||
|
||||
public async void launch (string identifier, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -233,6 +244,10 @@ namespace Frida {
|
||||
output (pid, fd, data);
|
||||
}
|
||||
|
||||
private void on_backend_gating_cancelled () {
|
||||
gating_cancelled ();
|
||||
}
|
||||
|
||||
private void on_backend_spawn_added (HostSpawnInfo info) {
|
||||
spawn_added (info);
|
||||
}
|
||||
|
||||
Loaded 100 of 211 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user