mirror of
https://github.com/frida/frida-core.git
synced 2026-11-12 12:50:24 -10:00
Compare commits
134
Commits
No files matched your search
@@ -0,0 +1,19 @@
|
||||
name: Set up LLVM
|
||||
description: Install the LLVM toolchain and put its tools on the path
|
||||
inputs:
|
||||
version:
|
||||
required: false
|
||||
default: "21"
|
||||
description: The LLVM major version to install
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install LLVM
|
||||
shell: bash
|
||||
run: |
|
||||
curl -sSL https://apt.llvm.org/llvm.sh -o llvm.sh
|
||||
chmod +x llvm.sh
|
||||
sudo ./llvm.sh $VERSION
|
||||
echo "/usr/lib/llvm-$VERSION/bin" >> $GITHUB_PATH
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
@@ -0,0 +1,245 @@
|
||||
name: Barebone agent
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'src/barebone/agent/**'
|
||||
- '.github/workflows/barebone-agent.yml'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'src/barebone/agent/**'
|
||||
- '.github/workflows/barebone-agent.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
gum-none:
|
||||
strategy:
|
||||
matrix:
|
||||
machine:
|
||||
- none-x86-softfloat_pic
|
||||
- none-x86_64-softfloat_pic
|
||||
- none-x86_64-softfloat_pic_msabi
|
||||
- none-arm-softfloat_pic
|
||||
- none-arm64-softfloat_pic
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
FRIDA_DEPS: ${{ github.workspace }}/deps
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: 24
|
||||
- name: Set up C toolchain
|
||||
uses: ./.github/actions/setup-llvm
|
||||
- name: Grab frida-gum
|
||||
run: |
|
||||
python releng/meson/meson.py subprojects download frida-gum
|
||||
git -C subprojects/frida-gum submodule update --init --recursive --depth 1
|
||||
- name: Configure Gum
|
||||
working-directory: subprojects/frida-gum
|
||||
run: >-
|
||||
./configure
|
||||
--host=${{ matrix.machine }}
|
||||
--enable-gumjs
|
||||
--with-devkits=gumjs
|
||||
--with-devkit-symbol-scope=original
|
||||
env:
|
||||
CC: clang
|
||||
CXX: clang++
|
||||
- name: Compile
|
||||
working-directory: subprojects/frida-gum
|
||||
run: make
|
||||
- name: Upload GumJS devkit
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: frida-gumjs-devkit-${{ matrix.machine }}
|
||||
path: subprojects/frida-gum/build/bindings/gumjs/devkit/
|
||||
- name: Upload SDK
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: sdk-${{ matrix.machine }}
|
||||
path: ${{ env.FRIDA_DEPS }}/sdk-${{ matrix.machine }}/
|
||||
|
||||
gum-macos-kernel:
|
||||
runs-on: macos-latest
|
||||
env:
|
||||
FRIDA_DEPS: ${{ github.workspace }}/deps
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Grab frida-gum
|
||||
run: |
|
||||
python3 releng/meson/meson.py subprojects download frida-gum
|
||||
git -C subprojects/frida-gum submodule update --init --recursive --depth 1
|
||||
- name: Configure Gum
|
||||
working-directory: subprojects/frida-gum
|
||||
run: >-
|
||||
./configure
|
||||
--host=macos-arm64e-kernel
|
||||
--enable-gumjs
|
||||
--with-devkits=gumjs
|
||||
--with-devkit-symbol-scope=original
|
||||
- name: Compile
|
||||
working-directory: subprojects/frida-gum
|
||||
run: make
|
||||
- name: Upload GumJS devkit
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: frida-gumjs-devkit-macos-arm64e-kernel
|
||||
path: subprojects/frida-gum/build/bindings/gumjs/devkit/
|
||||
- name: Upload SDK
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: sdk-macos-arm64e-kernel
|
||||
path: ${{ env.FRIDA_DEPS }}/sdk-macos-arm64e-kernel/
|
||||
|
||||
blob:
|
||||
needs: gum-none
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- flavor: win9x
|
||||
feature: win9x
|
||||
arch: x86
|
||||
machine: none-x86-softfloat_pic
|
||||
rust_target: i686-unknown-none-pic
|
||||
- flavor: winnt
|
||||
feature: winnt
|
||||
arch: x86
|
||||
machine: none-x86-softfloat_pic
|
||||
rust_target: i686-unknown-none-pic
|
||||
- flavor: winnt
|
||||
feature: winnt
|
||||
arch: x86_64
|
||||
machine: none-x86_64-softfloat_pic_msabi
|
||||
rust_target: x86_64-unknown-none-pic
|
||||
- flavor: winnt
|
||||
feature: winnt
|
||||
arch: arm64
|
||||
machine: none-arm64-softfloat_pic
|
||||
rust_target: aarch64-unknown-none-pic
|
||||
- flavor: xnu
|
||||
feature: xnu
|
||||
arch: arm64
|
||||
machine: none-arm64-softfloat_pic
|
||||
rust_target: aarch64-unknown-none-pic
|
||||
- flavor: linux
|
||||
feature: linux-injected
|
||||
arch: x86
|
||||
machine: none-x86-softfloat_pic
|
||||
rust_target: i686-unknown-none-pic
|
||||
- flavor: linux
|
||||
feature: linux-injected
|
||||
arch: x86_64
|
||||
machine: none-x86_64-softfloat_pic
|
||||
rust_target: x86_64-unknown-none-pic
|
||||
- flavor: linux
|
||||
feature: linux-injected
|
||||
arch: arm
|
||||
machine: none-arm-softfloat_pic
|
||||
rust_target: armv7a-unknown-none-pic
|
||||
- flavor: linux
|
||||
feature: linux-injected
|
||||
arch: arm64
|
||||
machine: none-arm64-softfloat_pic
|
||||
rust_target: aarch64-unknown-none-pic
|
||||
fail-fast: false
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SDK: ${{ github.workspace }}/sdk-${{ matrix.machine }}
|
||||
GUMJS_DEVKIT_DIR: ${{ github.workspace }}/gumjs-devkit
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- name: Detect version
|
||||
run: |
|
||||
version=$(releng/frida_version.py)
|
||||
test -n "$version"
|
||||
echo "FRIDA_VERSION=$version" >> $GITHUB_ENV
|
||||
- name: Set up C toolchain
|
||||
uses: ./.github/actions/setup-llvm
|
||||
- name: Set up Rust
|
||||
run: rustup component add rust-src
|
||||
- name: Download SDK
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: sdk-${{ matrix.machine }}
|
||||
path: ${{ env.SDK }}
|
||||
- name: Download GumJS devkit
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: frida-gumjs-devkit-${{ matrix.machine }}
|
||||
path: ${{ env.GUMJS_DEVKIT_DIR }}
|
||||
- name: Build agent
|
||||
working-directory: src/barebone/agent
|
||||
run: >-
|
||||
cargo build --release
|
||||
--features ${{ matrix.feature }}
|
||||
--target ./${{ matrix.rust_target }}.json -Z json-target-spec
|
||||
-Z build-std=core,alloc,compiler_builtins
|
||||
--target-dir target-${{ matrix.flavor }}
|
||||
env:
|
||||
RUSTC_BOOTSTRAP: 1
|
||||
CC: >-
|
||||
clang
|
||||
-I${{ env.SDK }}/include
|
||||
-I${{ env.SDK }}/include/glib-2.0
|
||||
-I${{ env.SDK }}/lib/glib-2.0/include
|
||||
-I${{ env.SDK }}/include/capstone
|
||||
-I${{ env.SDK }}/include/json-glib-1.0
|
||||
RUSTFLAGS: -L native=${{ env.SDK }}/lib
|
||||
- name: Upload agent
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: frida-barebone-agent-${{ matrix.flavor }}-${{ matrix.arch }}
|
||||
path: src/barebone/agent/target-${{ matrix.flavor }}/${{ matrix.rust_target }}/release/frida-barebone-agent
|
||||
|
||||
kext:
|
||||
needs: gum-macos-kernel
|
||||
runs-on: macos-latest
|
||||
env:
|
||||
FRIDA_SDK: ${{ github.workspace }}/sdk-macos-arm64e-kernel
|
||||
GUMJS_DEVKIT_DIR: ${{ github.workspace }}/gumjs-devkit
|
||||
steps:
|
||||
- name: Check out repo
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- name: Detect version
|
||||
run: |
|
||||
version=$(releng/frida_version.py)
|
||||
test -n "$version"
|
||||
echo "FRIDA_VERSION=$version" >> $GITHUB_ENV
|
||||
- name: Set up Rust
|
||||
run: rustup component add rust-src
|
||||
- name: Download SDK
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: sdk-macos-arm64e-kernel
|
||||
path: ${{ env.FRIDA_SDK }}
|
||||
- name: Download GumJS devkit
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: frida-gumjs-devkit-macos-arm64e-kernel
|
||||
path: ${{ env.GUMJS_DEVKIT_DIR }}
|
||||
- name: Build agent
|
||||
working-directory: src/barebone/agent/macos
|
||||
run: make
|
||||
- name: Upload agent
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: frida-barebone-agent-xnu-kext-arm64e
|
||||
path: src/barebone/agent/macos/frida-agent.kext
|
||||
@@ -2,9 +2,13 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- 'src/barebone/agent/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- 'src/barebone/agent/**'
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
@@ -451,7 +455,10 @@ jobs:
|
||||
cd build
|
||||
cp -a tests/frida-tests tests/labrats lib/agent/frida-agent.so /tmp/pkg/
|
||||
tar -C /tmp/pkg -cf /tmp/runner.tar .
|
||||
/opt/sabrelite/run.sh /tmp/runner.tar /opt/frida/frida-tests
|
||||
/opt/sabrelite/run.sh /tmp/runner.tar \
|
||||
"cp /opt/frida/frida-tests /opt/frida/frida-agent.so /tmp/ && \
|
||||
ln -sP /opt/frida/labrats /tmp/labrats && \
|
||||
/tmp/frida-tests"
|
||||
|
||||
# This job is used to check that the Linux helpers are built correctly for all
|
||||
# supported architectures. We do this because the binary artifacts are checked
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
/src/barebone/agent/linux/*.mod
|
||||
/src/barebone/agent/linux/*.mod.c
|
||||
/src/barebone/agent/linux/*.o
|
||||
/src/barebone/agent/linux/*.o.bin
|
||||
/src/barebone/agent/linux/frida-agent.elf
|
||||
/src/barebone/agent/linux/.*.cmd
|
||||
/src/barebone/agent/linux/Module.symvers
|
||||
/src/barebone/agent/linux/modules.order
|
||||
|
||||
@@ -43,7 +43,7 @@ platform-dependent (e.g. `linux-x86_64`).
|
||||
## Build Requirements
|
||||
|
||||
- **Vala compiler**: Must be the Frida-optimized fork from
|
||||
https://github.com/frida/vala (checks for `-frida` suffix
|
||||
https://github.com/frida-forks/vala (checks for `-frida` suffix
|
||||
in version)
|
||||
- **C/C++ standards**: gnu99/c99 for C, c++17 for C++
|
||||
- **Go >= 1.24**: Required for compiler backend
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
PYTHON ?= $(shell which python3 >/dev/null && echo python3 || echo python)
|
||||
PYTHON ?= $(shell command -v python3 >/dev/null && echo python3 || echo python)
|
||||
|
||||
all $(MAKECMDGOALS):
|
||||
@$(PYTHON) \
|
||||
@@ -8,7 +8,7 @@ all $(MAKECMDGOALS):
|
||||
$(MAKECMDGOALS)
|
||||
|
||||
git-submodules:
|
||||
@if [ ! -f releng/meson/meson.py ]; then \
|
||||
@if [ ! -f releng/meson/meson.py ] || git submodule status 2>/dev/null | grep -q '^-'; then \
|
||||
git submodule update --init --recursive --depth 1; \
|
||||
fi
|
||||
-include git-submodules
|
||||
|
||||
+9
-28
@@ -19,7 +19,7 @@ import traceback
|
||||
from typing import Any, Literal, Mapping, Optional, Sequence, Tuple
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
REPO_ROOT = Path(__file__).absolute().parent.parent
|
||||
NINJA = os.environ.get("NINJA", "ninja")
|
||||
|
||||
|
||||
@@ -510,9 +510,7 @@ def compile(privdir: Path, state: State):
|
||||
if not (workdir / "build.ninja").exists():
|
||||
if options is None:
|
||||
options = load_meson_options(top_builddir, state.role, set(subprojects.keys()))
|
||||
version_opt = next((opt for opt in options if opt.startswith("-Dfrida_version=")), None)
|
||||
if version_opt is None:
|
||||
options += [f"-Dfrida_version={state.frida_version}"]
|
||||
options += [f"-D:frida_version={state.frida_version}"]
|
||||
|
||||
host_machine = MachineSpec(state.host_os, group.arch, state.host_config, group.triplet)
|
||||
|
||||
@@ -525,7 +523,7 @@ def compile(privdir: Path, state: State):
|
||||
forwarded_options = [o for o in options if not is_stripped_compat_option(o)]
|
||||
forwarded_options += ["-Ddefault_library=static"]
|
||||
else:
|
||||
forwarded_options = [o for o in options if not is_overridden_compat_option(o)]
|
||||
forwarded_options = list(options)
|
||||
if state.apple_tls == "enabled":
|
||||
forwarded_options += ["-Dapple_tls=disabled"]
|
||||
|
||||
@@ -576,17 +574,10 @@ def compile(privdir: Path, state: State):
|
||||
|
||||
|
||||
def is_stripped_compat_option(o: str) -> bool:
|
||||
if is_overridden_compat_option(o):
|
||||
return True
|
||||
name = o.split("=", 1)[0].removeprefix("-D")
|
||||
return name in STRIPPED_COMPAT_OPTIONS or name.endswith(":default_library")
|
||||
|
||||
|
||||
def is_overridden_compat_option(o: str) -> bool:
|
||||
name = o.split("=", 1)[0].removeprefix("-D")
|
||||
return name in OVERRIDDEN_COMPAT_OPTIONS
|
||||
|
||||
|
||||
def load_meson_options(top_builddir: Path,
|
||||
role: Role,
|
||||
subprojects: set[str]) -> Sequence[str]:
|
||||
@@ -612,13 +603,14 @@ def option_should_be_forwarded(k: "OptionKey",
|
||||
from mesonbuild import options
|
||||
from mesonbuild.mesonlib import MachineChoice
|
||||
|
||||
subproject = k.subproject or ""
|
||||
our_project_id = "frida-core" if role == "subproject" else ""
|
||||
is_for_us = k.subproject == our_project_id
|
||||
is_for_child = k.subproject in subprojects
|
||||
is_for_us = subproject == our_project_id
|
||||
is_for_child = subproject in subprojects
|
||||
|
||||
if optstore.is_project_option(k):
|
||||
if is_for_us:
|
||||
if k.name == "compiler_backend":
|
||||
if k.name in {"compiler_backend", "assets"}:
|
||||
return False
|
||||
tokens = k.name.split("_")
|
||||
if tokens[0] in {"helper", "agent"} and tokens[-1] in {"modern", "legacy"}:
|
||||
@@ -639,9 +631,9 @@ def option_should_be_forwarded(k: "OptionKey",
|
||||
return False
|
||||
if not str(v.value):
|
||||
return False
|
||||
if not is_for_child and role == "subproject" and k.subproject in {"", our_project_id}:
|
||||
if not is_for_child and role == "subproject" and subproject in {"", our_project_id}:
|
||||
if not options.BUILTIN_OPTIONS[k.evolve(subproject=None, machine=MachineChoice.HOST)].yielding:
|
||||
return k.subproject == our_project_id
|
||||
return subproject == our_project_id
|
||||
return True
|
||||
|
||||
if k.get_module_prefix() == "python":
|
||||
@@ -798,17 +790,6 @@ STRIPPED_COMPAT_OPTIONS = {
|
||||
"frida-gum:inspector",
|
||||
}
|
||||
|
||||
OVERRIDDEN_COMPAT_OPTIONS = {
|
||||
"compiler_backend",
|
||||
"assets",
|
||||
"helper_modern",
|
||||
"helper_legacy",
|
||||
"agent_modern",
|
||||
"agent_legacy",
|
||||
"agent_emulated_modern",
|
||||
"agent_emulated_legacy",
|
||||
}
|
||||
|
||||
HELPER_TARGET = "frida-helper"
|
||||
HELPER_FILE_WINDOWS = Path("src") / "frida-helper.exe"
|
||||
HELPER_FILE_UNIX = Path("src") / "frida-helper"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#!/bin/sh
|
||||
|
||||
[ -z "$PYTHON" ] && PYTHON=$(which python3 >/dev/null && echo python3 || echo python)
|
||||
[ -z "$PYTHON" ] && PYTHON=$(command -v python3 >/dev/null && echo python3 || echo python)
|
||||
|
||||
cd $(dirname $0)
|
||||
|
||||
srcroot=$(pwd)
|
||||
|
||||
if [ ! -f releng/meson/meson.py ]; then
|
||||
if [ ! -f releng/meson/meson.py ] || git submodule status 2>/dev/null | grep -q '^-'; then
|
||||
git submodule update --init --recursive --depth 1 || exit $?
|
||||
fi
|
||||
|
||||
|
||||
+394
-35
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida.Agent {
|
||||
public void main (string agent_parameters, ref Frida.UnloadPolicy unload_policy, void * injector_state) {
|
||||
if (Runner.shared_instance == null)
|
||||
@@ -78,6 +80,7 @@ namespace Frida.Agent {
|
||||
private ExitMonitor? exit_monitor;
|
||||
private Gum.Interceptor interceptor;
|
||||
private Gum.Exceptor? exceptor;
|
||||
private bool code_memory_delegated = false;
|
||||
|
||||
private uint child_gating_subscriber_count = 0;
|
||||
private ForkMonitor? fork_monitor;
|
||||
@@ -100,6 +103,9 @@ namespace Frida.Agent {
|
||||
private SpawnMonitor? spawn_monitor;
|
||||
private ThreadSuspendMonitor? thread_suspend_monitor;
|
||||
private UnwindSitter? unwind_sitter;
|
||||
#if ANDROID && (ARM || ARM64)
|
||||
private NativeCloakMirror? native_cloak_mirror;
|
||||
#endif
|
||||
|
||||
private delegate void CompletionNotify ();
|
||||
|
||||
@@ -122,12 +128,37 @@ namespace Frida.Agent {
|
||||
{
|
||||
Gum.MemoryRange? mapped_range = null;
|
||||
|
||||
#if WINDOWS
|
||||
var windows_state = (WindowsInjectorState *) opaque_injector_state;
|
||||
if (windows_state != null) {
|
||||
if (windows_state.mapped_range.size != 0)
|
||||
mapped_range = windows_state.mapped_range;
|
||||
Gum.Cloak.add_range (windows_state.worker_range);
|
||||
if (windows_state.dbghelp_range.size != 0) {
|
||||
Gum.Cloak.add_range (windows_state.dbghelp_range);
|
||||
Gum.Cloak.add_range (windows_state.symsrv_range);
|
||||
Gum.Windows.use_dbghelp_modules ((void *) windows_state.dbghelp_range.base_address,
|
||||
(void *) windows_state.symsrv_range.base_address);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DARWIN
|
||||
var injector_state = (DarwinInjectorState *) opaque_injector_state;
|
||||
if (injector_state != null)
|
||||
mapped_range = injector_state.mapped_range;
|
||||
#endif
|
||||
|
||||
#if LINUX
|
||||
var linux_state = (LinuxInjectorState *) opaque_injector_state;
|
||||
if (linux_state != null && linux_state.mapped_size != 0) {
|
||||
mapped_range = Gum.MemoryRange () {
|
||||
base_address = linux_state.mapped_base,
|
||||
size = linux_state.mapped_size,
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
#if PROSPERO
|
||||
var prospero_state = (ProsperoInjectorState *) opaque_injector_state;
|
||||
if (prospero_state != null)
|
||||
@@ -199,6 +230,15 @@ namespace Frida.Agent {
|
||||
return;
|
||||
} else {
|
||||
release_shared_instance ();
|
||||
#if WINDOWS
|
||||
if (windows_state != null) {
|
||||
Gum.Cloak.remove_range (windows_state.worker_range);
|
||||
if (windows_state.dbghelp_range.size != 0) {
|
||||
Gum.Cloak.remove_range (windows_state.dbghelp_range);
|
||||
Gum.Cloak.remove_range (windows_state.symsrv_range);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
ignore_scope = null;
|
||||
@@ -285,25 +325,12 @@ namespace Frida.Agent {
|
||||
}
|
||||
|
||||
~Runner () {
|
||||
var interceptor = this.interceptor;
|
||||
interceptor.begin_transaction ();
|
||||
remove_instrumentation ();
|
||||
|
||||
disable_child_gating ();
|
||||
|
||||
exceptor = null;
|
||||
|
||||
exit_monitor = null;
|
||||
|
||||
interceptor.end_transaction ();
|
||||
|
||||
interceptor.begin_transaction ();
|
||||
|
||||
thread_suspend_monitor = null;
|
||||
unwind_sitter = null;
|
||||
if (code_memory_delegated)
|
||||
Gum.Memory.set_hooks (null);
|
||||
|
||||
invalidate_dbus_context ();
|
||||
|
||||
interceptor.end_transaction ();
|
||||
}
|
||||
|
||||
private void run (owned FileDescriptorTablePadder padder) throws Error {
|
||||
@@ -326,6 +353,9 @@ namespace Frida.Agent {
|
||||
bool enable_unwind_broker = true;
|
||||
bool enable_exit_monitor = true;
|
||||
bool enable_thread_suspend_monitor = true;
|
||||
#if ANDROID && (ARM || ARM64)
|
||||
bool in_emulated_realm = false;
|
||||
#endif
|
||||
foreach (unowned string option in tokens[1:]) {
|
||||
if (option == "eternal")
|
||||
ensure_eternalized ();
|
||||
@@ -341,38 +371,47 @@ namespace Frida.Agent {
|
||||
enable_exit_monitor = false;
|
||||
else if (option == "thread-suspend-monitor:off")
|
||||
enable_thread_suspend_monitor = false;
|
||||
#if ANDROID && (ARM || ARM64)
|
||||
else if (option == "realm:emulated")
|
||||
in_emulated_realm = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (exceptor_mode != FULL)
|
||||
Gum.Exceptor.set_mode (exceptor_mode);
|
||||
|
||||
{
|
||||
var interceptor = Gum.Interceptor.obtain ();
|
||||
interceptor.begin_transaction ();
|
||||
|
||||
if (enable_exit_monitor)
|
||||
exit_monitor = new ExitMonitor (this, main_context);
|
||||
|
||||
if (enable_thread_suspend_monitor)
|
||||
thread_suspend_monitor = new ThreadSuspendMonitor (this);
|
||||
|
||||
if (enable_unwind_broker)
|
||||
unwind_sitter = new UnwindSitter (this);
|
||||
|
||||
this.interceptor = interceptor;
|
||||
this.exceptor = Gum.Exceptor.obtain ();
|
||||
|
||||
interceptor.end_transaction ();
|
||||
}
|
||||
interceptor = Gum.Interceptor.obtain ();
|
||||
|
||||
try {
|
||||
yield setup_connection_with_transport_uri (transport_uri);
|
||||
#if ANDROID && (ARM || ARM64)
|
||||
if (in_emulated_realm)
|
||||
yield connect_to_native_realm ();
|
||||
#endif
|
||||
} catch (Error e) {
|
||||
start_error = e;
|
||||
main_loop.quit ();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!can_allocate_code ())
|
||||
delegate_code_memory_to_controller ();
|
||||
|
||||
interceptor.begin_transaction ();
|
||||
|
||||
if (enable_exit_monitor)
|
||||
exit_monitor = new ExitMonitor (this, main_context);
|
||||
|
||||
if (enable_thread_suspend_monitor)
|
||||
thread_suspend_monitor = new ThreadSuspendMonitor (this);
|
||||
|
||||
if (enable_unwind_broker)
|
||||
unwind_sitter = new UnwindSitter (this);
|
||||
|
||||
exceptor = Gum.Exceptor.obtain ();
|
||||
|
||||
interceptor.end_transaction ();
|
||||
|
||||
Gum.ScriptBackend.get_scheduler ().push_job_on_js_thread (Priority.DEFAULT, () => {
|
||||
schedule_idle (start.callback);
|
||||
});
|
||||
@@ -381,6 +420,77 @@ namespace Frida.Agent {
|
||||
padder = null;
|
||||
}
|
||||
|
||||
#if ANDROID && (ARM || ARM64)
|
||||
private async void connect_to_native_realm () throws Error {
|
||||
try {
|
||||
NativeCloak cloak = yield connection.get_proxy (null, ObjectPath.NATIVE_CLOAK, DO_NOT_LOAD_PROPERTIES,
|
||||
null);
|
||||
native_cloak_mirror = new NativeCloakMirror (cloak);
|
||||
native_cloak_mirror.activate ();
|
||||
|
||||
NativeThreadRegistry registry = yield connection.get_proxy (null, ObjectPath.NATIVE_THREAD_REGISTRY,
|
||||
DO_NOT_LOAD_PROPERTIES, null);
|
||||
Gum.ThreadRegistry.set_provider (new NativeThreadProvider (registry));
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private static bool can_allocate_code () {
|
||||
size_t page_size = Gum.query_page_size ();
|
||||
|
||||
void * page = Gum.Memory.allocate (null, page_size, page_size, READ | EXECUTE);
|
||||
if (page == null)
|
||||
return false;
|
||||
|
||||
Gum.Memory.free (page, page_size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void delegate_code_memory_to_controller () {
|
||||
var hooks = Gum.MemoryHooks () {
|
||||
allocate = allocate_via_controller,
|
||||
protect = protect_via_controller,
|
||||
user_data = this,
|
||||
};
|
||||
Gum.Memory.set_hooks (hooks);
|
||||
|
||||
code_memory_delegated = true;
|
||||
}
|
||||
|
||||
private static void * allocate_via_controller (Gum.AddressSpec? spec, size_t size, size_t alignment,
|
||||
Gum.PageProtection prot, void * user_data) {
|
||||
unowned Runner self = (Runner) user_data;
|
||||
|
||||
uint64 near_address = 0;
|
||||
uint64 max_distance = 0;
|
||||
if (spec != null) {
|
||||
near_address = (uint64) spec.near_address;
|
||||
max_distance = spec.max_distance;
|
||||
}
|
||||
|
||||
try {
|
||||
return (void *) self.controller.allocate_memory (get_process_id (), near_address, max_distance, size,
|
||||
alignment, prot, null);
|
||||
} catch (GLib.Error e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool protect_via_controller (void * address, size_t size, Gum.PageProtection prot,
|
||||
void * user_data) {
|
||||
unowned Runner self = (Runner) user_data;
|
||||
|
||||
try {
|
||||
self.controller.protect_memory (get_process_id (), (uint64) address, size, prot, null);
|
||||
return true;
|
||||
} catch (GLib.Error e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void keep_running_eternalized () {
|
||||
agent_gthread = new Thread<bool> ("frida-eternal-agent", () => {
|
||||
var ignore_scope = new ThreadIgnoreScope (FRIDA_THREAD);
|
||||
@@ -1042,6 +1152,10 @@ namespace Frida.Agent {
|
||||
if (unloading)
|
||||
throw new Error.INVALID_OPERATION ("Agent is already unloading");
|
||||
unloading = true;
|
||||
|
||||
if (code_memory_delegated && !is_eternal)
|
||||
yield remove_instrumentation_while_delegated ();
|
||||
|
||||
perform_unload.begin ();
|
||||
}
|
||||
|
||||
@@ -1087,6 +1201,34 @@ namespace Frida.Agent {
|
||||
}
|
||||
}
|
||||
|
||||
private async void remove_instrumentation_while_delegated () {
|
||||
yield close_all_sessions ();
|
||||
|
||||
remove_instrumentation ();
|
||||
}
|
||||
|
||||
private void remove_instrumentation () {
|
||||
var interceptor = this.interceptor;
|
||||
interceptor.begin_transaction ();
|
||||
|
||||
disable_child_gating ();
|
||||
|
||||
exceptor = null;
|
||||
|
||||
exit_monitor = null;
|
||||
|
||||
interceptor.end_transaction ();
|
||||
|
||||
interceptor.begin_transaction ();
|
||||
|
||||
thread_suspend_monitor = null;
|
||||
unwind_sitter = null;
|
||||
|
||||
interceptor.end_transaction ();
|
||||
|
||||
Gum.shutdown ();
|
||||
}
|
||||
|
||||
private void ensure_eternalized () {
|
||||
if (!_is_eternal) {
|
||||
_is_eternal = true;
|
||||
@@ -1295,7 +1437,7 @@ namespace Frida.Agent {
|
||||
|
||||
private void on_connection_closed (DBusConnection connection, bool remote_peer_vanished, GLib.Error? error) {
|
||||
bool closed_by_us = !remote_peer_vanished && error == null;
|
||||
if (!closed_by_us)
|
||||
if (!closed_by_us && !code_memory_delegated)
|
||||
unload.begin (null);
|
||||
|
||||
Promise<bool> operation = null;
|
||||
@@ -1367,6 +1509,8 @@ namespace Frida.Agent {
|
||||
private Socket? emulated_socket;
|
||||
private BridgeState? emulated_bridge_state;
|
||||
private Thread<void>? emulated_worker;
|
||||
private NativeCloakService? native_cloak;
|
||||
private NativeThreadRegistryService? native_thread_registry;
|
||||
|
||||
private async AgentSessionProvider? try_get_emulated_provider (Cancellable? cancellable) throws IOError {
|
||||
if (get_emulated_request == null)
|
||||
@@ -1406,6 +1550,8 @@ namespace Frida.Agent {
|
||||
emulated_agent = nb_api.load_library_ext (emulated_agent_path, RTLD_LAZY, classloader_namespace);
|
||||
} else {
|
||||
emulated_agent = nb_api.load_library (emulated_agent_path, RTLD_LAZY);
|
||||
if (emulated_agent == null && nb_api.load_library_ext != null)
|
||||
emulated_agent = nb_api.load_library_ext (emulated_agent_path, RTLD_LAZY, null);
|
||||
}
|
||||
if (emulated_agent == null)
|
||||
throw new Error.NOT_SUPPORTED ("Process is not using emulation");
|
||||
@@ -1445,6 +1591,7 @@ namespace Frida.Agent {
|
||||
* code being discarded, which seems like an edge-case the translator doesn't handle.
|
||||
*/
|
||||
parameters.append ("|exceptor:off|exit-monitor:off");
|
||||
parameters.append ("|realm:emulated");
|
||||
|
||||
emulated_bridge_state = new BridgeState (parameters.str);
|
||||
|
||||
@@ -1453,6 +1600,13 @@ namespace Frida.Agent {
|
||||
var connection = yield new DBusConnection (stream, ServerGuid.HOST_SESSION_SERVICE,
|
||||
AUTHENTICATION_SERVER | AUTHENTICATION_ALLOW_ANONYMOUS, null, cancellable);
|
||||
|
||||
native_cloak = new NativeCloakService ();
|
||||
connection.register_object (ObjectPath.NATIVE_CLOAK, (NativeCloak) native_cloak);
|
||||
|
||||
native_thread_registry = new NativeThreadRegistryService ();
|
||||
connection.register_object (ObjectPath.NATIVE_THREAD_REGISTRY,
|
||||
(NativeThreadRegistry) native_thread_registry);
|
||||
|
||||
AgentSessionProvider provider = yield connection.get_proxy (null, ObjectPath.AGENT_SESSION_PROVIDER,
|
||||
DO_NOT_LOAD_PROPERTIES, cancellable);
|
||||
|
||||
@@ -1500,6 +1654,16 @@ namespace Frida.Agent {
|
||||
emulated_worker = null;
|
||||
}
|
||||
|
||||
if (native_thread_registry != null) {
|
||||
native_thread_registry.close ();
|
||||
native_thread_registry = null;
|
||||
}
|
||||
|
||||
if (native_cloak != null) {
|
||||
native_cloak.close ();
|
||||
native_cloak = null;
|
||||
}
|
||||
|
||||
emulated_socket = null;
|
||||
|
||||
if (emulated_agent != null) {
|
||||
@@ -1660,6 +1824,201 @@ namespace Frida.Agent {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ANDROID && (X86 || X86_64)
|
||||
private sealed class NativeCloakService : Object, NativeCloak, Gum.Cloak.Observer {
|
||||
private MainContext main_context = MainContext.ref_thread_default ();
|
||||
private bool observing = false;
|
||||
|
||||
public uint[] activate (Cancellable? cancellable) {
|
||||
Gum.Cloak.add_observer (this);
|
||||
observing = true;
|
||||
|
||||
uint[] threads = {};
|
||||
Gum.Cloak.enumerate_threads (id => {
|
||||
threads += (uint) id;
|
||||
return true;
|
||||
});
|
||||
return threads;
|
||||
}
|
||||
|
||||
public void close () {
|
||||
if (!observing)
|
||||
return;
|
||||
|
||||
Gum.Cloak.remove_observer (this);
|
||||
observing = false;
|
||||
}
|
||||
|
||||
public void thread_added (Gum.ThreadId id) {
|
||||
schedule (() => {
|
||||
thread_cloaked ((uint) id);
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
public void thread_removed (Gum.ThreadId id) {
|
||||
schedule (() => {
|
||||
thread_uncloaked ((uint) id);
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
private void schedule (owned SourceFunc function) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback ((owned) function);
|
||||
source.attach (main_context);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class NativeThreadRegistryService : Object, NativeThreadRegistry {
|
||||
private unowned Gum.ThreadRegistry? registry;
|
||||
private ulong added_handler = 0;
|
||||
private ulong removed_handler = 0;
|
||||
private ulong renamed_handler = 0;
|
||||
|
||||
public NativeThreadInfo[] activate (Cancellable? cancellable) {
|
||||
registry = Gum.ThreadRegistry.obtain ();
|
||||
|
||||
NativeThreadInfo[] threads = {};
|
||||
|
||||
registry.lock ();
|
||||
|
||||
added_handler = registry.thread_added.connect (on_thread_added);
|
||||
removed_handler = registry.thread_removed.connect (on_thread_removed);
|
||||
renamed_handler = registry.thread_renamed.connect (on_thread_renamed);
|
||||
|
||||
registry.enumerate_threads (thread => {
|
||||
threads += NativeThreadInfo ((uint) thread.id, thread.name ?? "");
|
||||
return true;
|
||||
});
|
||||
|
||||
registry.unlock ();
|
||||
|
||||
return threads;
|
||||
}
|
||||
|
||||
public void deactivate (Cancellable? cancellable) {
|
||||
close ();
|
||||
}
|
||||
|
||||
public void close () {
|
||||
if (registry == null)
|
||||
return;
|
||||
|
||||
registry.disconnect (added_handler);
|
||||
registry.disconnect (removed_handler);
|
||||
registry.disconnect (renamed_handler);
|
||||
registry = null;
|
||||
}
|
||||
|
||||
private void on_thread_added (Gum.ThreadDetails thread) {
|
||||
thread_added (NativeThreadInfo ((uint) thread.id, thread.name ?? ""));
|
||||
}
|
||||
|
||||
private void on_thread_removed (Gum.ThreadDetails thread) {
|
||||
thread_removed ((uint) thread.id);
|
||||
}
|
||||
|
||||
private void on_thread_renamed (Gum.ThreadDetails thread, string? previous_name) {
|
||||
thread_renamed ((uint) thread.id, thread.name ?? "");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ANDROID && (ARM || ARM64)
|
||||
private sealed class NativeCloakMirror : Object {
|
||||
public NativeCloak native_cloak {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public NativeCloakMirror (NativeCloak native_cloak) {
|
||||
Object (native_cloak: native_cloak);
|
||||
}
|
||||
|
||||
public void activate () throws GLib.Error {
|
||||
native_cloak.thread_cloaked.connect (on_thread_cloaked);
|
||||
native_cloak.thread_uncloaked.connect (on_thread_uncloaked);
|
||||
|
||||
foreach (uint id in native_cloak.activate (null))
|
||||
on_thread_cloaked (id);
|
||||
}
|
||||
|
||||
private void on_thread_cloaked (uint id) {
|
||||
if (!Gum.Cloak.has_thread (id))
|
||||
Gum.Cloak.add_thread (id);
|
||||
}
|
||||
|
||||
private void on_thread_uncloaked (uint id) {
|
||||
Gum.Cloak.remove_thread (id);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class NativeThreadProvider : Object, Gum.ThreadProvider {
|
||||
public NativeThreadRegistry native_registry {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private unowned Gum.ThreadRegistry registry;
|
||||
private ulong added_handler = 0;
|
||||
private ulong removed_handler = 0;
|
||||
private ulong renamed_handler = 0;
|
||||
|
||||
public NativeThreadProvider (NativeThreadRegistry native_registry) {
|
||||
Object (native_registry: native_registry);
|
||||
}
|
||||
|
||||
public void activate (Gum.ThreadRegistry registry) {
|
||||
this.registry = registry;
|
||||
|
||||
added_handler = native_registry.thread_added.connect (add_thread);
|
||||
removed_handler = native_registry.thread_removed.connect (on_thread_removed);
|
||||
renamed_handler = native_registry.thread_renamed.connect (on_thread_renamed);
|
||||
|
||||
NativeThreadInfo[] threads;
|
||||
try {
|
||||
threads = native_registry.activate (null);
|
||||
} catch (GLib.Error e) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var thread in threads)
|
||||
add_thread (thread);
|
||||
}
|
||||
|
||||
public void deactivate () {
|
||||
native_registry.disconnect (added_handler);
|
||||
native_registry.disconnect (removed_handler);
|
||||
native_registry.disconnect (renamed_handler);
|
||||
|
||||
try {
|
||||
native_registry.deactivate (null);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
private void add_thread (NativeThreadInfo thread) {
|
||||
var details = Gum.ThreadDetails ();
|
||||
details.id = thread.id;
|
||||
if (thread.name != "") {
|
||||
details.name = thread.name;
|
||||
details.flags = NAME;
|
||||
}
|
||||
|
||||
registry.register (details);
|
||||
}
|
||||
|
||||
private void on_thread_removed (uint id) {
|
||||
registry.unregister (id);
|
||||
}
|
||||
|
||||
private void on_thread_renamed (uint id, string name) {
|
||||
registry.rename (id, (name != "") ? name : null);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private sealed class LiveAgentSession : BaseAgentSession {
|
||||
public uint registration_id {
|
||||
get;
|
||||
|
||||
@@ -25,6 +25,9 @@ if build_agent
|
||||
if host_os_family != 'windows'
|
||||
extra_vala_args += '--pkg=posix'
|
||||
endif
|
||||
if host_os_family == 'windows'
|
||||
extra_vala_args += '--pkg=frida-gum-windows-1.0'
|
||||
endif
|
||||
if host_os_family == 'darwin'
|
||||
extra_vala_args += '--pkg=frida-gum-darwin-1.0'
|
||||
endif
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
#include "frida-base.h"
|
||||
|
||||
GBytes *
|
||||
frida_make_bytes_with_owner (GType t_type,
|
||||
GBoxedCopyFunc t_dup_func,
|
||||
GDestroyNotify t_destroy_func,
|
||||
void * data,
|
||||
gsize size,
|
||||
gpointer owner)
|
||||
{
|
||||
return g_bytes_new_with_free_func (data, size, t_destroy_func, owner);
|
||||
}
|
||||
@@ -1,797 +0,0 @@
|
||||
namespace Frida {
|
||||
public sealed class BufferBuilder : Object {
|
||||
public ByteOrder byte_order {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public uint pointer_size {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public size_t offset {
|
||||
get {
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
|
||||
private ByteArray buffer = new ByteArray ();
|
||||
private size_t cursor = 0;
|
||||
|
||||
private uint64 base_address = 0;
|
||||
private Gee.List<LabelRef>? label_refs;
|
||||
private Gee.Map<string, uint>? label_defs;
|
||||
|
||||
public BufferBuilder (ByteOrder byte_order = HOST, uint pointer_size = (uint) sizeof (size_t)) {
|
||||
Object (
|
||||
byte_order: byte_order,
|
||||
pointer_size: pointer_size
|
||||
);
|
||||
}
|
||||
|
||||
public unowned BufferBuilder seek (size_t offset) {
|
||||
if (buffer.len < offset) {
|
||||
size_t n = offset - buffer.len;
|
||||
Memory.set (get_pointer (offset - n, n), 0, n);
|
||||
}
|
||||
cursor = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder skip (size_t n) {
|
||||
seek (cursor + n);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder align (size_t n) {
|
||||
size_t remainder = cursor % n;
|
||||
if (remainder != 0)
|
||||
skip (n - remainder);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_pointer (uint64 val) {
|
||||
write_pointer (cursor, val);
|
||||
cursor += pointer_size;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_pointer_to_label (string name) {
|
||||
if (label_refs == null)
|
||||
label_refs = new Gee.ArrayList<LabelRef> ();
|
||||
label_refs.add (new LabelRef (name, cursor));
|
||||
return skip (pointer_size);
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_pointer_to_label_if (bool present, string name) {
|
||||
if (present)
|
||||
append_pointer_to_label (name);
|
||||
else
|
||||
append_pointer (0);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_label (string name) throws Error {
|
||||
if (label_defs == null)
|
||||
label_defs = new Gee.HashMap<string, uint> ();
|
||||
if (label_defs.has_key (name))
|
||||
throw new Error.INVALID_ARGUMENT ("Label '%s' already exists", name);
|
||||
label_defs[name] = (uint) cursor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_size (uint64 val) {
|
||||
return append_pointer (val);
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_int8 (int8 val) {
|
||||
write_int8 (cursor, val);
|
||||
cursor += (uint) sizeof (int8);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_uint8 (uint8 val) {
|
||||
write_uint8 (cursor, val);
|
||||
cursor += (uint) sizeof (uint8);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_int16 (int16 val) {
|
||||
write_int16 (cursor, val);
|
||||
cursor += (uint) sizeof (int16);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_uint16 (uint16 val) {
|
||||
write_uint16 (cursor, val);
|
||||
cursor += (uint) sizeof (uint16);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_int32 (int32 val) {
|
||||
write_int32 (cursor, val);
|
||||
cursor += (uint) sizeof (int32);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_uint32 (uint32 val) {
|
||||
write_uint32 (cursor, val);
|
||||
cursor += (uint) sizeof (uint32);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_int64 (int64 val) {
|
||||
write_int64 (cursor, val);
|
||||
cursor += (uint) sizeof (int64);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_uint64 (uint64 val) {
|
||||
write_uint64 (cursor, val);
|
||||
cursor += (uint) sizeof (uint64);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_float (float val) {
|
||||
write_float (cursor, val);
|
||||
cursor += (uint) sizeof (float);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_double (double val) {
|
||||
write_double (cursor, val);
|
||||
cursor += (uint) sizeof (double);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_string (string val, StringTerminator terminator = NUL) {
|
||||
uint size = val.length;
|
||||
if (terminator == NUL)
|
||||
size++;
|
||||
Memory.copy (get_pointer (cursor, size), val, size);
|
||||
cursor += size;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_bytes (Bytes bytes) {
|
||||
return append_data (bytes.get_data ());
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_data (uint8[] data) {
|
||||
write_data (cursor, data);
|
||||
cursor += data.length;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_pointer (size_t offset, uint64 val) {
|
||||
if (pointer_size == 4)
|
||||
write_uint32 (offset, (uint32) val);
|
||||
else
|
||||
write_uint64 (offset, val);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_size (size_t offset, uint64 val) {
|
||||
return write_pointer (offset, val);
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_int8 (size_t offset, int8 val) {
|
||||
*((int8 *) get_pointer (offset, sizeof (int8))) = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_uint8 (size_t offset, uint8 val) {
|
||||
*get_pointer (offset, sizeof (uint8)) = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_int16 (size_t offset, int16 val) {
|
||||
int16 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((int16 *) get_pointer (offset, sizeof (int16))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_uint16 (size_t offset, uint16 val) {
|
||||
uint16 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((uint16 *) get_pointer (offset, sizeof (uint16))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_int32 (size_t offset, int32 val) {
|
||||
int32 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((int32 *) get_pointer (offset, sizeof (int32))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_uint32 (size_t offset, uint32 val) {
|
||||
uint32 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((uint32 *) get_pointer (offset, sizeof (uint32))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_int64 (size_t offset, int64 val) {
|
||||
int64 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((int64 *) get_pointer (offset, sizeof (int64))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_uint64 (size_t offset, uint64 val) {
|
||||
uint64 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((uint64 *) get_pointer (offset, sizeof (uint64))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_float (size_t offset, float val) {
|
||||
return write_uint32 (offset, *((uint32 *) &val));
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_double (size_t offset, double val) {
|
||||
return write_uint64 (offset, *((uint64 *) &val));
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_string (size_t offset, string val) {
|
||||
uint size = val.length + 1;
|
||||
Memory.copy (get_pointer (offset, size), val, size);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_bytes (size_t offset, Bytes bytes) {
|
||||
return write_data (offset, bytes.get_data ());
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_data (size_t offset, uint8[] data) {
|
||||
Memory.copy (get_pointer (offset, data.length), data, data.length);
|
||||
return this;
|
||||
}
|
||||
|
||||
private uint8 * get_pointer (size_t offset, size_t n) {
|
||||
size_t minimum_size = offset + n;
|
||||
if (buffer.len < minimum_size)
|
||||
buffer.set_size ((uint) minimum_size);
|
||||
|
||||
return (uint8 *) buffer.data + offset;
|
||||
}
|
||||
|
||||
public Bytes try_build (uint64 base_address = 0) throws Error {
|
||||
this.base_address = base_address;
|
||||
|
||||
if (label_refs != null) {
|
||||
foreach (LabelRef r in label_refs)
|
||||
write_pointer (r.offset, address_of (r.name));
|
||||
}
|
||||
|
||||
return ByteArray.free_to_bytes ((owned) buffer);
|
||||
}
|
||||
|
||||
public Bytes build (uint64 base_address = 0) {
|
||||
try {
|
||||
return try_build (base_address);
|
||||
} catch (Error e) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
public uint64 address_of (string label) throws Error {
|
||||
if (label_defs == null || !label_defs.has_key (label))
|
||||
throw new Error.INVALID_OPERATION ("Label '%s' not defined", label);
|
||||
size_t offset = label_defs[label];
|
||||
return base_address + offset;
|
||||
}
|
||||
|
||||
private class LabelRef {
|
||||
public string name;
|
||||
public size_t offset;
|
||||
|
||||
public LabelRef (string name, size_t offset) {
|
||||
this.name = name;
|
||||
this.offset = offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum StringTerminator {
|
||||
NONE,
|
||||
NUL
|
||||
}
|
||||
|
||||
public sealed class Buffer : Object {
|
||||
public Bytes bytes {
|
||||
get {
|
||||
if (_bytes == null)
|
||||
_bytes = new Bytes.static (_data);
|
||||
return _bytes;
|
||||
}
|
||||
}
|
||||
|
||||
public size_t size {
|
||||
get {
|
||||
return _data.length;
|
||||
}
|
||||
}
|
||||
|
||||
public ByteOrder byte_order {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public uint pointer_size {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private Bytes? _bytes;
|
||||
private unowned uint8[] _data;
|
||||
|
||||
public Buffer (Bytes bytes, ByteOrder byte_order = HOST, uint pointer_size = (uint) sizeof (size_t)) {
|
||||
Object (
|
||||
byte_order: byte_order,
|
||||
pointer_size: pointer_size
|
||||
);
|
||||
|
||||
reset_bytes (bytes);
|
||||
}
|
||||
|
||||
public Buffer.from_data (uint8[] data, ByteOrder byte_order = HOST, uint pointer_size = (uint) sizeof (size_t)) {
|
||||
Object (
|
||||
byte_order: byte_order,
|
||||
pointer_size: pointer_size
|
||||
);
|
||||
|
||||
reset_data (data);
|
||||
}
|
||||
|
||||
public void reset_bytes (Bytes bytes) {
|
||||
_bytes = bytes;
|
||||
_data = bytes.get_data ();
|
||||
}
|
||||
|
||||
public void reset_data (uint8[] data) {
|
||||
_bytes = null;
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public unowned uint8[] peek_data () {
|
||||
return _data;
|
||||
}
|
||||
|
||||
public uint64 read_pointer (size_t offset) {
|
||||
return (pointer_size == 4)
|
||||
? read_uint32 (offset)
|
||||
: read_uint64 (offset);
|
||||
}
|
||||
|
||||
public void write_pointer (size_t offset, uint64 val) {
|
||||
if (pointer_size == 4)
|
||||
write_uint32 (offset, (uint32) val);
|
||||
else
|
||||
write_uint64 (offset, val);
|
||||
}
|
||||
|
||||
public int8 read_int8 (size_t offset) {
|
||||
return *((int8 *) get_pointer (offset, sizeof (int8)));
|
||||
}
|
||||
|
||||
public unowned Buffer write_int8 (size_t offset, int8 val) {
|
||||
*((int8 *) get_pointer (offset, sizeof (int8))) = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public uint8 read_uint8 (size_t offset) {
|
||||
return *get_pointer (offset, sizeof (uint8));
|
||||
}
|
||||
|
||||
public unowned Buffer write_uint8 (size_t offset, uint8 val) {
|
||||
*((uint8 *) get_pointer (offset, sizeof (uint8))) = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int16 read_int16 (size_t offset) {
|
||||
int16 val = *((int16 *) get_pointer (offset, sizeof (int16)));
|
||||
return (byte_order == BIG_ENDIAN)
|
||||
? int16.from_big_endian (val)
|
||||
: int16.from_little_endian (val);
|
||||
}
|
||||
|
||||
public unowned Buffer write_int16 (size_t offset, int16 val) {
|
||||
int16 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((int16 *) get_pointer (offset, sizeof (int16))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public uint16 read_uint16 (size_t offset) {
|
||||
uint16 val = *((uint16 *) get_pointer (offset, sizeof (uint16)));
|
||||
return (byte_order == BIG_ENDIAN)
|
||||
? uint16.from_big_endian (val)
|
||||
: uint16.from_little_endian (val);
|
||||
}
|
||||
|
||||
public unowned Buffer write_uint16 (size_t offset, uint16 val) {
|
||||
uint16 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((uint16 *) get_pointer (offset, sizeof (uint16))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int32 read_int32 (size_t offset) {
|
||||
int32 val = *((int32 *) get_pointer (offset, sizeof (int32)));
|
||||
return (byte_order == BIG_ENDIAN)
|
||||
? int32.from_big_endian (val)
|
||||
: int32.from_little_endian (val);
|
||||
}
|
||||
|
||||
public unowned Buffer write_int32 (size_t offset, int32 val) {
|
||||
int32 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((int32 *) get_pointer (offset, sizeof (int32))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public uint32 read_uint32 (size_t offset) {
|
||||
uint32 val = *((uint32 *) get_pointer (offset, sizeof (uint32)));
|
||||
return (byte_order == BIG_ENDIAN)
|
||||
? uint32.from_big_endian (val)
|
||||
: uint32.from_little_endian (val);
|
||||
}
|
||||
|
||||
public unowned Buffer write_uint32 (size_t offset, uint32 val) {
|
||||
uint32 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((uint32 *) get_pointer (offset, sizeof (uint32))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int64 read_int64 (size_t offset) {
|
||||
int64 val = *((int64 *) get_pointer (offset, sizeof (int64)));
|
||||
return (byte_order == BIG_ENDIAN)
|
||||
? int64.from_big_endian (val)
|
||||
: int64.from_little_endian (val);
|
||||
}
|
||||
|
||||
public unowned Buffer write_int64 (size_t offset, int64 val) {
|
||||
int64 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((int64 *) get_pointer (offset, sizeof (int64))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public uint64 read_uint64 (size_t offset) {
|
||||
uint64 val = *((uint64 *) get_pointer (offset, sizeof (uint64)));
|
||||
return (byte_order == BIG_ENDIAN)
|
||||
? uint64.from_big_endian (val)
|
||||
: uint64.from_little_endian (val);
|
||||
}
|
||||
|
||||
public unowned Buffer write_uint64 (size_t offset, uint64 val) {
|
||||
uint64 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((uint64 *) get_pointer (offset, sizeof (uint64))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public float read_float (size_t offset) {
|
||||
uint32 bits = read_uint32 (offset);
|
||||
return *((float *) &bits);
|
||||
}
|
||||
|
||||
public double read_double (size_t offset) {
|
||||
uint64 bits = read_uint64 (offset);
|
||||
return *((double *) &bits);
|
||||
}
|
||||
|
||||
public unowned string read_string (size_t offset) throws Error {
|
||||
string * start = (string *) get_pointer (offset, sizeof (char));
|
||||
size_t max_length = _data.length - offset;
|
||||
string * end = memchr (start, 0, max_length);
|
||||
if (end == null)
|
||||
throw new Error.PROTOCOL ("Missing null character");
|
||||
return start;
|
||||
}
|
||||
|
||||
[CCode (cname = "memchr", cheader_filename = "string.h")]
|
||||
private extern static string * memchr (string * s, int c, size_t n);
|
||||
|
||||
public string read_fixed_string (size_t offset, size_t size) throws Error {
|
||||
string * start = (string *) get_pointer (offset, size);
|
||||
size_t max_length = size_t.min (size, _data.length - offset);
|
||||
string * end = memchr (start, 0, max_length);
|
||||
size_t n;
|
||||
if (end != null)
|
||||
n = end - start;
|
||||
else
|
||||
n = size;
|
||||
return start->substring (0, (long) n);
|
||||
}
|
||||
|
||||
public unowned Buffer write_string (size_t offset, string val) {
|
||||
uint size = val.length + 1;
|
||||
Memory.copy (get_pointer (offset, size), val, size);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Bytes read_bytes (size_t offset, size_t size) {
|
||||
if (_bytes == null)
|
||||
return make_bytes_with_owner ((uint8 *) _data + offset, _data.length - size, this);
|
||||
return _bytes[offset:offset + size];
|
||||
}
|
||||
|
||||
public unowned uint8[] read_data (size_t offset, size_t size) {
|
||||
unowned uint8 * ptr = get_pointer (offset, size);
|
||||
return ((uint8[]) ptr)[:size];
|
||||
}
|
||||
|
||||
public unowned Buffer write_bytes (size_t offset, Bytes bytes) {
|
||||
size_t size = bytes.get_size ();
|
||||
Memory.copy (get_pointer (offset, size), bytes.get_data (), size);
|
||||
return this;
|
||||
}
|
||||
|
||||
private uint8 * get_pointer (size_t offset, size_t n) {
|
||||
size_t minimum_size = offset + n;
|
||||
assert (_data.length >= minimum_size);
|
||||
return (uint8 *) _data + offset;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class BufferReader {
|
||||
public Buffer buffer {
|
||||
get;
|
||||
}
|
||||
|
||||
public size_t offset {
|
||||
get;
|
||||
}
|
||||
|
||||
public size_t available {
|
||||
get {
|
||||
return _size - _offset;
|
||||
}
|
||||
}
|
||||
|
||||
private size_t _size;
|
||||
|
||||
public BufferReader (Buffer buffer) {
|
||||
reset (buffer);
|
||||
}
|
||||
|
||||
public void reset (Buffer buffer) {
|
||||
_buffer = buffer;
|
||||
_size = buffer.size;
|
||||
_offset = 0;
|
||||
}
|
||||
|
||||
public void reset_at (Buffer buffer, size_t offset) throws Error {
|
||||
_buffer = buffer;
|
||||
_size = buffer.size;
|
||||
seek (offset);
|
||||
}
|
||||
|
||||
public unowned BufferReader seek (size_t offset) throws Error {
|
||||
if (offset > _size)
|
||||
throw new Error.PROTOCOL ("Malformed buffer: truncated");
|
||||
_offset = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferReader skip (size_t n) throws Error {
|
||||
check_available (n);
|
||||
_offset += n;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferReader align (size_t alignment) throws Error {
|
||||
size_t mask = alignment - 1;
|
||||
size_t rem = _offset & mask;
|
||||
if (rem != 0)
|
||||
skip (alignment - rem);
|
||||
return this;
|
||||
}
|
||||
|
||||
public uint64 peek_pointer () throws Error {
|
||||
var pointer_size = _buffer.pointer_size;
|
||||
check_available (pointer_size);
|
||||
return _buffer.read_pointer (_offset);
|
||||
}
|
||||
|
||||
public uint64 read_pointer () throws Error {
|
||||
var pointer_size = _buffer.pointer_size;
|
||||
check_available (pointer_size);
|
||||
var ptr = _buffer.read_pointer (_offset);
|
||||
_offset += pointer_size;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
public int8 peek_int8 () throws Error {
|
||||
check_available (sizeof (int8));
|
||||
return _buffer.read_int8 (_offset);
|
||||
}
|
||||
|
||||
public int8 read_int8 () throws Error {
|
||||
check_available (sizeof (int8));
|
||||
var val = _buffer.read_int8 (_offset);
|
||||
_offset += sizeof (int8);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint8 peek_uint8 () throws Error {
|
||||
check_available (sizeof (uint8));
|
||||
return _buffer.read_uint8 (_offset);
|
||||
}
|
||||
|
||||
public uint8 read_uint8 () throws Error {
|
||||
check_available (sizeof (uint8));
|
||||
var val = _buffer.read_uint8 (_offset);
|
||||
_offset += sizeof (uint8);
|
||||
return val;
|
||||
}
|
||||
|
||||
public int16 peek_int16 () throws Error {
|
||||
check_available (sizeof (int16));
|
||||
return _buffer.read_int16 (_offset);
|
||||
}
|
||||
|
||||
public int16 read_int16 () throws Error {
|
||||
check_available (sizeof (int16));
|
||||
var val = _buffer.read_int16 (_offset);
|
||||
_offset += sizeof (int16);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint16 peek_uint16 () throws Error {
|
||||
check_available (sizeof (uint16));
|
||||
return _buffer.read_uint16 (_offset);
|
||||
}
|
||||
|
||||
public uint16 read_uint16 () throws Error {
|
||||
check_available (sizeof (uint16));
|
||||
var val = _buffer.read_uint16 (_offset);
|
||||
_offset += sizeof (uint16);
|
||||
return val;
|
||||
}
|
||||
|
||||
public int32 peek_int32 () throws Error {
|
||||
check_available (sizeof (int32));
|
||||
return _buffer.read_int32 (_offset);
|
||||
}
|
||||
|
||||
public int32 read_int32 () throws Error {
|
||||
check_available (sizeof (int32));
|
||||
var val = _buffer.read_int32 (_offset);
|
||||
_offset += sizeof (int32);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint32 peek_uint32 () throws Error {
|
||||
check_available (sizeof (uint32));
|
||||
return _buffer.read_uint32 (_offset);
|
||||
}
|
||||
|
||||
public uint32 read_uint32 () throws Error {
|
||||
check_available (sizeof (uint32));
|
||||
var val = _buffer.read_uint32 (_offset);
|
||||
_offset += sizeof (uint32);
|
||||
return val;
|
||||
}
|
||||
|
||||
public int64 peek_int64 () throws Error {
|
||||
check_available (sizeof (int64));
|
||||
return _buffer.read_int64 (_offset);
|
||||
}
|
||||
|
||||
public int64 read_int64 () throws Error {
|
||||
check_available (sizeof (int64));
|
||||
var val = _buffer.read_int64 (_offset);
|
||||
_offset += sizeof (int64);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint64 peek_uint64 () throws Error {
|
||||
check_available (sizeof (uint64));
|
||||
return _buffer.read_uint64 (_offset);
|
||||
}
|
||||
|
||||
public uint64 read_uint64 () throws Error {
|
||||
check_available (sizeof (uint64));
|
||||
var val = _buffer.read_uint64 (_offset);
|
||||
_offset += sizeof (uint64);
|
||||
return val;
|
||||
}
|
||||
|
||||
public float peek_float () throws Error {
|
||||
check_available (sizeof (float));
|
||||
return _buffer.read_float (_offset);
|
||||
}
|
||||
|
||||
public float read_float () throws Error {
|
||||
check_available (sizeof (float));
|
||||
var val = _buffer.read_float (_offset);
|
||||
_offset += sizeof (float);
|
||||
return val;
|
||||
}
|
||||
|
||||
public double peek_double () throws Error {
|
||||
check_available (sizeof (double));
|
||||
return _buffer.read_double (_offset);
|
||||
}
|
||||
|
||||
public double read_double () throws Error {
|
||||
check_available (sizeof (double));
|
||||
var val = _buffer.read_double (_offset);
|
||||
_offset += sizeof (double);
|
||||
return val;
|
||||
}
|
||||
|
||||
public unowned string peek_string () throws Error {
|
||||
check_available (1);
|
||||
return _buffer.read_string (_offset);
|
||||
}
|
||||
|
||||
public unowned string read_string () throws Error {
|
||||
check_available (1);
|
||||
unowned string val = _buffer.read_string (_offset);
|
||||
_offset += val.length + 1;
|
||||
return val;
|
||||
}
|
||||
|
||||
public string peek_fixed_string (size_t size) throws Error {
|
||||
check_available (size);
|
||||
return _buffer.read_fixed_string (_offset, size);
|
||||
}
|
||||
|
||||
public string read_fixed_string (size_t size) throws Error {
|
||||
check_available (size);
|
||||
var val = _buffer.read_fixed_string (_offset, size);
|
||||
_offset += size;
|
||||
return val;
|
||||
}
|
||||
|
||||
public unowned uint8[] peek_data (size_t size) throws Error {
|
||||
check_available (size);
|
||||
return _buffer.read_data (_offset, size);
|
||||
}
|
||||
|
||||
public unowned uint8[] read_data (size_t size) throws Error {
|
||||
check_available (size);
|
||||
unowned uint8[] slice = _buffer.read_data (_offset, size);
|
||||
_offset += size;
|
||||
return slice;
|
||||
}
|
||||
|
||||
public Bytes read_bytes (size_t size) throws Error {
|
||||
check_available (size);
|
||||
var val = _buffer.read_bytes (_offset, size);
|
||||
_offset += size;
|
||||
return val;
|
||||
}
|
||||
|
||||
public unowned uint8[] read_remaining_data () throws Error {
|
||||
return read_data (available);
|
||||
}
|
||||
|
||||
private void check_available (size_t n) throws Error {
|
||||
if (available < n)
|
||||
throw new Error.PROTOCOL ("Malformed buffer: truncated");
|
||||
}
|
||||
}
|
||||
|
||||
public extern Bytes make_bytes_with_owner<T> (void * data, size_t size, owned T? owner = null);
|
||||
}
|
||||
+14
-2
@@ -1,9 +1,13 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public sealed class ChannelRegistry : Object {
|
||||
public signal void channel_closed (ChannelId id);
|
||||
|
||||
private Gee.Map<ChannelId?, weak IOStream> channels =
|
||||
new Gee.HashMap<ChannelId?, weak IOStream> (ChannelId.hash, ChannelId.equal);
|
||||
private Gee.Map<ChannelId?, IOStream> unlinked_channels =
|
||||
new Gee.HashMap<ChannelId?, IOStream> (ChannelId.hash, ChannelId.equal);
|
||||
|
||||
~ChannelRegistry () {
|
||||
clear ();
|
||||
@@ -16,25 +20,33 @@ namespace Frida {
|
||||
channel_stream.abandon ();
|
||||
}
|
||||
channels.clear ();
|
||||
unlinked_channels.clear ();
|
||||
}
|
||||
|
||||
public void register (ChannelId id, IOStream stream) {
|
||||
stream.set_data ("channel-registry-entry", new Entry (this, id));
|
||||
lock (channels)
|
||||
lock (channels) {
|
||||
channels[id] = stream;
|
||||
unlinked_channels[id] = stream;
|
||||
}
|
||||
}
|
||||
|
||||
public IOStream link (ChannelId id) throws Error {
|
||||
IOStream? stream;
|
||||
lock (channels)
|
||||
IOStream? unlinked_stream = null;
|
||||
lock (channels) {
|
||||
stream = channels[id];
|
||||
unlinked_channels.unset (id, out unlinked_stream);
|
||||
}
|
||||
if (stream == null)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid channel ID");
|
||||
return stream;
|
||||
}
|
||||
|
||||
public void unlink (ChannelId id) {
|
||||
IOStream? unlinked_stream = null;
|
||||
lock (channels) {
|
||||
unlinked_channels.unset (id, out unlinked_stream);
|
||||
if (!channels.unset (id))
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
// We need to tease out GDBus' private MainContext as libnice needs to know the MainContext up front :(
|
||||
public async MainContext get_dbus_context () {
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
#include "frida-darwin.h"
|
||||
|
||||
gpointer
|
||||
_frida_dispatch_retain (gpointer object)
|
||||
{
|
||||
dispatch_retain (object);
|
||||
return object;
|
||||
}
|
||||
|
||||
void
|
||||
_frida_xpc_connection_set_event_handler (xpc_connection_t connection, FridaXpcHandler handler, gpointer user_data)
|
||||
{
|
||||
xpc_connection_set_event_handler (connection, ^(xpc_object_t object)
|
||||
{
|
||||
handler (object, user_data);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
_frida_xpc_connection_send_message_with_reply (xpc_connection_t connection, xpc_object_t message, dispatch_queue_t replyq,
|
||||
FridaXpcHandler handler, gpointer user_data, GDestroyNotify notify)
|
||||
{
|
||||
xpc_connection_send_message_with_reply (connection, message, replyq, ^(xpc_object_t object)
|
||||
{
|
||||
handler (object, user_data);
|
||||
if (notify != NULL)
|
||||
notify (user_data);
|
||||
});
|
||||
}
|
||||
|
||||
gchar *
|
||||
_frida_xpc_object_to_string (xpc_object_t object)
|
||||
{
|
||||
gchar * result;
|
||||
char * str;
|
||||
|
||||
str = xpc_copy_description (object);
|
||||
result = g_strdup (str);
|
||||
free (str);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
gboolean
|
||||
_frida_xpc_dictionary_apply (xpc_object_t dict, FridaXpcDictionaryApplier applier, gpointer user_data)
|
||||
{
|
||||
return xpc_dictionary_apply (dict, ^bool (const char * key, xpc_object_t val)
|
||||
{
|
||||
return applier (key, val, user_data);
|
||||
});
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
#ifndef __FRIDA_DARWIN_H__
|
||||
#define __FRIDA_DARWIN_H__
|
||||
|
||||
#ifdef HAVE_MACOS
|
||||
|
||||
#include <glib.h>
|
||||
#include <xpc/xpc.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef void (* FridaXpcHandler) (xpc_object_t object, gpointer user_data);
|
||||
typedef gboolean (* FridaXpcDictionaryApplier) (const gchar * key, xpc_object_t val, gpointer user_data);
|
||||
|
||||
gpointer _frida_dispatch_retain (gpointer object);
|
||||
|
||||
void _frida_xpc_connection_set_event_handler (xpc_connection_t connection, FridaXpcHandler handler, gpointer user_data);
|
||||
void _frida_xpc_connection_send_message_with_reply (xpc_connection_t connection, xpc_object_t message, dispatch_queue_t replyq,
|
||||
FridaXpcHandler handler, gpointer user_data, GDestroyNotify notify);
|
||||
gchar * _frida_xpc_object_to_string (xpc_object_t object);
|
||||
gboolean _frida_xpc_dictionary_apply (xpc_object_t dict, FridaXpcDictionaryApplier applier, gpointer user_data);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -1,126 +0,0 @@
|
||||
namespace Frida.FS {
|
||||
public void mkdirp (File dir, Cancellable? cancellable = null) throws Error {
|
||||
try {
|
||||
dir.make_directory_with_parents (cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (!(e is IOError.EXISTS))
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public void rmtree (File dir, Cancellable? cancellable = null) throws Error {
|
||||
rmtree_worker (dir, 0, cancellable);
|
||||
}
|
||||
|
||||
private void rmtree_worker (File dir, uint depth, Cancellable? cancellable = null) throws Error {
|
||||
FileEnumerator enumerator;
|
||||
try {
|
||||
enumerator = dir.enumerate_children (FileAttribute.STANDARD_NAME, NOFOLLOW_SYMLINKS, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (depth == 0 && e is IOError.NOT_FOUND)
|
||||
return;
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
|
||||
try {
|
||||
FileInfo? info;
|
||||
File? child;
|
||||
while (enumerator.iterate (out info, out child, cancellable) && info != null) {
|
||||
if (info == null)
|
||||
continue;
|
||||
if (info.get_file_type () == DIRECTORY)
|
||||
rmtree (child, cancellable);
|
||||
else
|
||||
child.delete (cancellable);
|
||||
}
|
||||
|
||||
dir.delete (cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (!(e is IOError.EXISTS))
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async void rmtree_async (File dir, Cancellable? cancellable = null) throws Error, IOError {
|
||||
yield rmtree_async_worker (dir, 0, cancellable);
|
||||
}
|
||||
|
||||
private async void rmtree_async_worker (File dir, uint depth, Cancellable? cancellable = null) throws Error, IOError {
|
||||
int io_priority = Priority.DEFAULT;
|
||||
|
||||
FileEnumerator enumerator;
|
||||
try {
|
||||
enumerator = yield dir.enumerate_children_async (FileAttribute.STANDARD_NAME, NOFOLLOW_SYMLINKS, io_priority,
|
||||
cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (depth == 0 && e is IOError.NOT_FOUND)
|
||||
return;
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
var batch = yield enumerator.next_files_async (1000, io_priority, cancellable);
|
||||
if (batch.is_empty ())
|
||||
break;
|
||||
foreach (FileInfo info in batch) {
|
||||
File child = dir.get_child (info.get_name ());
|
||||
if (info.get_file_type () == DIRECTORY)
|
||||
yield rmtree_async_worker (child, depth + 1, cancellable);
|
||||
else
|
||||
yield child.delete_async (io_priority, cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
yield dir.delete_async (io_priority, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async string read_all_text (File f, Cancellable? cancellable) throws Error, IOError {
|
||||
Bytes b = yield read_all_bytes (f, cancellable);
|
||||
string text = (string) b.get_data ();
|
||||
if (!text.validate ())
|
||||
throw new Error.PROTOCOL ("Unable to read '%s': invalid UTF-8", f.get_parse_name ());
|
||||
return text;
|
||||
}
|
||||
|
||||
public async void write_all_text (File f, string t, Cancellable? cancellable) throws Error, IOError {
|
||||
yield write_all_bytes (f, new Bytes (t.data), cancellable);
|
||||
}
|
||||
|
||||
public Bytes read_all_bytes_sync (File f) throws Error {
|
||||
try {
|
||||
uint8[] contents;
|
||||
f.load_contents (null, out contents, null);
|
||||
return new Bytes.take ((owned) contents);
|
||||
} catch (GLib.Error e) {
|
||||
if (e is IOError.NOT_FOUND)
|
||||
throw new Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async Bytes read_all_bytes (File f, Cancellable? cancellable) throws Error, IOError {
|
||||
try {
|
||||
return yield f.load_bytes_async (cancellable, null);
|
||||
} catch (GLib.Error e) {
|
||||
if (e is IOError.CANCELLED)
|
||||
throw (IOError) e;
|
||||
if (e is IOError.NOT_FOUND)
|
||||
throw new Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async void write_all_bytes (File f, Bytes b, Cancellable? cancellable) throws Error, IOError {
|
||||
try {
|
||||
yield f.replace_contents_bytes_async (b, null, false, FileCreateFlags.NONE, cancellable, null);
|
||||
} catch (GLib.Error e) {
|
||||
if (e is IOError.CANCELLED)
|
||||
throw (IOError) e;
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,6 +123,15 @@ namespace Frida {
|
||||
return fd;
|
||||
}
|
||||
|
||||
public static FileDescriptor with_size (string name, size_t size) {
|
||||
assert (is_supported ());
|
||||
|
||||
var fd = new FileDescriptor (memfd_create (name, 0));
|
||||
int res = Posix.ftruncate (fd.handle, (Posix.off_t) size);
|
||||
assert (res == 0);
|
||||
return fd;
|
||||
}
|
||||
|
||||
private int memfd_create (string name, uint flags) {
|
||||
return Linux.syscall (LinuxSyscall.MEMFD_CREATE, name, flags);
|
||||
}
|
||||
|
||||
+3
-17
@@ -1,21 +1,16 @@
|
||||
base_sources = [
|
||||
'asset-location.vala',
|
||||
'promise.vala',
|
||||
'session.vala',
|
||||
'session-glue.c',
|
||||
'channel.vala',
|
||||
'service.vala',
|
||||
'stream.vala',
|
||||
'rpc.vala',
|
||||
'p2p.vala',
|
||||
'p2p-glue.c',
|
||||
'dbus.vala',
|
||||
'promise.vala',
|
||||
'fs.vala',
|
||||
'socket.vala',
|
||||
'socket-glue.c',
|
||||
'buffer.vala',
|
||||
'buffer-glue.c',
|
||||
'value.vala',
|
||||
'xnu-syscalls' / 'xnu-syscalls.vala',
|
||||
'xnu-syscalls' / 'xnu-syscalls-glue.c',
|
||||
]
|
||||
@@ -29,13 +24,6 @@ else
|
||||
extra_vala_args += '--pkg=posix'
|
||||
endif
|
||||
|
||||
if host_os == 'macos'
|
||||
base_sources += [
|
||||
'xpc.vala',
|
||||
'frida-darwin.c',
|
||||
]
|
||||
endif
|
||||
|
||||
if host_os_family == 'darwin'
|
||||
base_sources += 'corefoundation.vala'
|
||||
extra_vala_args += '--pkg=darwin-xnu'
|
||||
@@ -94,8 +82,6 @@ endif
|
||||
if host_os_family == 'darwin'
|
||||
base_vala_args += [
|
||||
'--pkg=corefoundation',
|
||||
'--pkg=darwin-gcd',
|
||||
'--pkg=darwin-xpc',
|
||||
]
|
||||
endif
|
||||
if host_os_family == 'linux'
|
||||
@@ -114,7 +100,7 @@ base = static_library('frida-base-' + api_version, base_sources,
|
||||
vala_header: 'frida-base.h',
|
||||
vala_gir: base_gir_name,
|
||||
vala_vapi: f'frida-base-@[email protected]',
|
||||
dependencies: [glib_dep, gobject_dep, gio_dep, libsoup_dep, nice_dep, usrsctp_dep, gee_dep, json_glib_dep, gum_dep, openssl_dep] + extra_deps,
|
||||
dependencies: [glib_dep, gobject_dep, gio_dep, libsoup_dep, nice_dep, usrsctp_dep, gee_dep, json_glib_dep, gum_dep, openssl_dep, efni_dep] + extra_deps,
|
||||
install: have_in_tree_payloads,
|
||||
install_dir: [true, header_install_dir, true, false],
|
||||
)
|
||||
@@ -122,7 +108,7 @@ base = static_library('frida-base-' + api_version, base_sources,
|
||||
base_dep = declare_dependency(
|
||||
include_directories: include_directories('.'),
|
||||
link_with: base,
|
||||
dependencies: [nice_dep, json_glib_dep, gum_dep] + extra_deps,
|
||||
dependencies: [nice_dep, json_glib_dep, gum_dep, efni_dep] + extra_deps,
|
||||
)
|
||||
|
||||
base_build_dir = meson.current_build_dir()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
#if HAVE_NICE
|
||||
namespace Frida {
|
||||
namespace PeerConnection {
|
||||
|
||||
+58
-124
@@ -1,19 +1,18 @@
|
||||
namespace Frida {
|
||||
public sealed class Promise<T> {
|
||||
private Impl<T> impl;
|
||||
private Efni.Promise<T> impl;
|
||||
private Future<T>? _future;
|
||||
|
||||
public Future<T> future {
|
||||
get {
|
||||
return impl;
|
||||
if (_future == null)
|
||||
_future = new FutureImpl<T> (impl.future);
|
||||
return _future;
|
||||
}
|
||||
}
|
||||
|
||||
public Promise () {
|
||||
impl = new Impl<T> ();
|
||||
}
|
||||
|
||||
~Promise () {
|
||||
impl.abandon ();
|
||||
impl = new Efni.Promise<T> ();
|
||||
}
|
||||
|
||||
public void resolve (T result) {
|
||||
@@ -24,149 +23,84 @@ namespace Frida {
|
||||
impl.reject (error);
|
||||
}
|
||||
|
||||
private class Impl<T> : Object, Future<T> {
|
||||
private class FutureImpl<T> : Object, Future<T> {
|
||||
private Efni.Future<T> inner;
|
||||
|
||||
public bool ready {
|
||||
get {
|
||||
return _ready;
|
||||
return inner.ready;
|
||||
}
|
||||
}
|
||||
private bool _ready = false;
|
||||
|
||||
public T? value {
|
||||
get {
|
||||
return _value;
|
||||
return inner.value;
|
||||
}
|
||||
}
|
||||
private T? _value;
|
||||
|
||||
public GLib.Error? error {
|
||||
get {
|
||||
return _error;
|
||||
return inner.error;
|
||||
}
|
||||
}
|
||||
private GLib.Error? _error;
|
||||
|
||||
private Gee.ArrayQueue<CompletionFuncEntry>? on_complete;
|
||||
private Gee.ArrayQueue<ThenHandlerEntry<T>>? on_then;
|
||||
public FutureImpl (Efni.Future<T> inner) {
|
||||
this.inner = inner;
|
||||
}
|
||||
|
||||
public async T wait_async (Cancellable? cancellable) throws Frida.Error, IOError {
|
||||
if (_ready)
|
||||
return get_result ();
|
||||
public async T wait_async (Cancellable? cancellable) throws Error, IOError {
|
||||
if (!inner.ready) {
|
||||
bool resumed = false;
|
||||
|
||||
var entry = new CompletionFuncEntry (wait_async.callback);
|
||||
if (on_complete == null)
|
||||
on_complete = new Gee.ArrayQueue<CompletionFuncEntry> ();
|
||||
on_complete.offer (entry);
|
||||
var cancel_source = new CancellableSource (cancellable);
|
||||
cancel_source.set_callback (() => {
|
||||
if (!resumed) {
|
||||
resumed = true;
|
||||
wait_async.callback ();
|
||||
}
|
||||
return Source.REMOVE;
|
||||
});
|
||||
cancel_source.attach (MainContext.get_thread_default ());
|
||||
|
||||
var cancel_source = new CancellableSource (cancellable);
|
||||
cancel_source.set_callback (() => {
|
||||
on_complete.remove (entry);
|
||||
wait_async.callback ();
|
||||
return false;
|
||||
});
|
||||
cancel_source.attach (MainContext.get_thread_default ());
|
||||
inner.then (f => {
|
||||
if (!resumed) {
|
||||
resumed = true;
|
||||
wait_async.callback ();
|
||||
}
|
||||
});
|
||||
|
||||
yield;
|
||||
yield;
|
||||
|
||||
cancel_source.destroy ();
|
||||
cancel_source.destroy ();
|
||||
|
||||
cancellable.set_error_if_cancelled ();
|
||||
cancellable.set_error_if_cancelled ();
|
||||
}
|
||||
|
||||
return get_result ();
|
||||
}
|
||||
|
||||
public void then (owned FutureCompletionHandler<T> handler) {
|
||||
if (_ready) {
|
||||
schedule (() => {
|
||||
handler (this);
|
||||
return Source.REMOVE;
|
||||
});
|
||||
return;
|
||||
inner.then (f => {
|
||||
handler (this);
|
||||
});
|
||||
}
|
||||
|
||||
public T get_result () throws Error, IOError {
|
||||
GLib.Error? e = inner.error;
|
||||
if (e != null) {
|
||||
if (e is Error)
|
||||
throw (Error) e;
|
||||
|
||||
if (e is Efni.Error)
|
||||
throw error_from_efni ((Efni.Error) e);
|
||||
|
||||
if (e is IOError.CANCELLED)
|
||||
throw (IOError) e;
|
||||
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
|
||||
if (on_then == null)
|
||||
on_then = new Gee.ArrayQueue<ThenHandlerEntry<T>> ();
|
||||
on_then.offer (new ThenHandlerEntry<T> ((owned) handler));
|
||||
}
|
||||
|
||||
public T get_result () throws Frida.Error, IOError {
|
||||
if (error != null) {
|
||||
if (error is Frida.Error)
|
||||
throw (Frida.Error) error;
|
||||
|
||||
if (error is IOError.CANCELLED)
|
||||
throw (IOError) error;
|
||||
|
||||
throw new Frida.Error.TRANSPORT ("%s", error.message);
|
||||
}
|
||||
|
||||
return _value;
|
||||
}
|
||||
|
||||
internal void resolve (T value) {
|
||||
assert (!_ready);
|
||||
|
||||
_value = value;
|
||||
transition_to_ready ();
|
||||
}
|
||||
|
||||
internal void reject (GLib.Error error) {
|
||||
assert (!_ready);
|
||||
|
||||
_error = error;
|
||||
transition_to_ready ();
|
||||
}
|
||||
|
||||
internal void abandon () {
|
||||
if (!_ready) {
|
||||
reject (new Frida.Error.INVALID_OPERATION ("Promise abandoned"));
|
||||
}
|
||||
}
|
||||
|
||||
internal void transition_to_ready () {
|
||||
_ready = true;
|
||||
|
||||
if (on_complete != null || on_then != null) {
|
||||
schedule (() => {
|
||||
if (on_complete != null) {
|
||||
CompletionFuncEntry? entry;
|
||||
while ((entry = on_complete.poll ()) != null)
|
||||
entry.func ();
|
||||
on_complete = null;
|
||||
}
|
||||
|
||||
if (on_then != null) {
|
||||
ThenHandlerEntry? entry;
|
||||
while ((entry = on_then.poll ()) != null)
|
||||
entry.handler (this);
|
||||
on_then = null;
|
||||
}
|
||||
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void schedule (owned SourceFunc function) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback ((owned) function);
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
}
|
||||
}
|
||||
|
||||
private class CompletionFuncEntry {
|
||||
public SourceFunc func;
|
||||
|
||||
public CompletionFuncEntry (owned SourceFunc func) {
|
||||
this.func = (owned) func;
|
||||
}
|
||||
}
|
||||
|
||||
private class ThenHandlerEntry<T> {
|
||||
public FutureCompletionHandler<T> handler;
|
||||
|
||||
public ThenHandlerEntry (owned FutureCompletionHandler<T> handler) {
|
||||
this.handler = (owned) handler;
|
||||
return inner.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,9 +109,9 @@ namespace Frida {
|
||||
public abstract bool ready { get; }
|
||||
public abstract T? value { get; }
|
||||
public abstract GLib.Error? error { get; }
|
||||
public abstract async T wait_async (Cancellable? cancellable) throws Frida.Error, IOError;
|
||||
public abstract async T wait_async (Cancellable? cancellable) throws Error, IOError;
|
||||
public abstract void then (owned FutureCompletionHandler<T> handler);
|
||||
public abstract T get_result () throws Frida.Error, IOError;
|
||||
public abstract T get_result () throws Error, IOError;
|
||||
}
|
||||
|
||||
public delegate void FutureCompletionHandler<T> (Future<T> future);
|
||||
|
||||
+91
-50
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
[DBus (name = "re.frida.HostSession17")]
|
||||
public interface HostSession : Object {
|
||||
@@ -124,8 +126,43 @@ namespace Frida {
|
||||
|
||||
public abstract async void acknowledge_spawn (HostChildInfo info, SpawnStartState start_state,
|
||||
Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
public abstract uint64 allocate_memory (uint pid, uint64 near_address, uint64 max_distance, uint64 size,
|
||||
uint64 alignment, Gum.PageProtection prot, Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract void protect_memory (uint pid, uint64 address, uint64 size, Gum.PageProtection prot,
|
||||
Cancellable? cancellable) throws GLib.Error;
|
||||
}
|
||||
|
||||
#if ANDROID
|
||||
[DBus (name = "re.frida.NativeThreadRegistry17")]
|
||||
public interface NativeThreadRegistry : Object {
|
||||
public abstract NativeThreadInfo[] activate (Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract void deactivate (Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
public signal void thread_added (NativeThreadInfo thread);
|
||||
public signal void thread_removed (uint id);
|
||||
public signal void thread_renamed (uint id, string name);
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.NativeCloak17")]
|
||||
public interface NativeCloak : Object {
|
||||
public abstract uint[] activate (Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
public signal void thread_cloaked (uint id);
|
||||
public signal void thread_uncloaked (uint id);
|
||||
}
|
||||
|
||||
public struct NativeThreadInfo {
|
||||
public uint id;
|
||||
public string name;
|
||||
|
||||
public NativeThreadInfo (uint id, string name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[DBus (name = "re.frida.AgentMessageSink17")]
|
||||
public interface AgentMessageSink : Object {
|
||||
public abstract async void post_messages (AgentMessage[] messages, uint batch_id,
|
||||
@@ -1133,6 +1170,15 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
#if WINDOWS
|
||||
public struct WindowsInjectorState {
|
||||
public Gum.MemoryRange mapped_range;
|
||||
public Gum.MemoryRange worker_range;
|
||||
public Gum.MemoryRange dbghelp_range;
|
||||
public Gum.MemoryRange symsrv_range;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DARWIN
|
||||
public struct DarwinInjectorState {
|
||||
public Gum.MemoryRange? mapped_range;
|
||||
@@ -1143,6 +1189,8 @@ namespace Frida {
|
||||
public struct LinuxInjectorState {
|
||||
public int frida_ctrlfd;
|
||||
public int agent_ctrlfd;
|
||||
public uint64 mapped_base;
|
||||
public size_t mapped_size;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1281,6 +1329,9 @@ namespace Frida {
|
||||
if (e is Frida.Error)
|
||||
throw (Frida.Error) e;
|
||||
|
||||
if (e is Efni.Error)
|
||||
throw error_from_efni ((Efni.Error) e);
|
||||
|
||||
if (e is IOError.CANCELLED)
|
||||
throw (IOError) e;
|
||||
|
||||
@@ -1294,6 +1345,9 @@ namespace Frida {
|
||||
if (e is Frida.Error)
|
||||
throw (Frida.Error) e;
|
||||
|
||||
if (e is Efni.Error)
|
||||
throw error_from_efni ((Efni.Error) e);
|
||||
|
||||
if (e is IOError.CANCELLED)
|
||||
throw (IOError) e;
|
||||
|
||||
@@ -1306,6 +1360,41 @@ namespace Frida {
|
||||
throw new Frida.Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
|
||||
namespace Marshal {
|
||||
public T enum_from_nick<T> (string nick) throws Error {
|
||||
try {
|
||||
return Efni.Marshal.enum_from_nick<T> (nick);
|
||||
} catch (Efni.Error e) {
|
||||
throw error_from_efni (e);
|
||||
}
|
||||
}
|
||||
|
||||
public string enum_to_nick<T> (int val) {
|
||||
return Efni.Marshal.enum_to_nick<T> (val);
|
||||
}
|
||||
}
|
||||
|
||||
public static Frida.Error error_from_efni (Efni.Error e) {
|
||||
unowned string m = e.message;
|
||||
if (e is Efni.Error.SERVER_NOT_RUNNING)
|
||||
return new Frida.Error.SERVER_NOT_RUNNING ("%s", m);
|
||||
if (e is Efni.Error.INVALID_ARGUMENT)
|
||||
return new Frida.Error.INVALID_ARGUMENT ("%s", m);
|
||||
if (e is Efni.Error.INVALID_OPERATION)
|
||||
return new Frida.Error.INVALID_OPERATION ("%s", m);
|
||||
if (e is Efni.Error.PERMISSION_DENIED)
|
||||
return new Frida.Error.PERMISSION_DENIED ("%s", m);
|
||||
if (e is Efni.Error.ADDRESS_IN_USE)
|
||||
return new Frida.Error.ADDRESS_IN_USE ("%s", m);
|
||||
if (e is Efni.Error.TIMED_OUT)
|
||||
return new Frida.Error.TIMED_OUT ("%s", m);
|
||||
if (e is Efni.Error.NOT_SUPPORTED)
|
||||
return new Frida.Error.NOT_SUPPORTED ("%s", m);
|
||||
if (e is Efni.Error.PROTOCOL)
|
||||
return new Frida.Error.PROTOCOL ("%s", m);
|
||||
return new Frida.Error.TRANSPORT ("%s", m);
|
||||
}
|
||||
|
||||
public struct HostApplicationInfo {
|
||||
public string identifier;
|
||||
public string name;
|
||||
@@ -2743,6 +2832,8 @@ namespace Frida {
|
||||
public const string AGENT_SESSION_PROVIDER = "/re/frida/AgentSessionProvider";
|
||||
public const string AGENT_SESSION = "/re/frida/AgentSession";
|
||||
public const string AGENT_CONTROLLER = "/re/frida/AgentController";
|
||||
public const string NATIVE_THREAD_REGISTRY = "/re/frida/NativeThreadRegistry";
|
||||
public const string NATIVE_CLOAK = "/re/frida/NativeCloak";
|
||||
public const string AGENT_MESSAGE_SINK = "/re/frida/AgentMessageSink";
|
||||
public const string GADGET_SESSION = "/re/frida/GadgetSession";
|
||||
public const string CHANNEL = "/re/frida/Channel";
|
||||
@@ -2768,54 +2859,4 @@ namespace Frida {
|
||||
return SERVICE + "/" + id.handle;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Marshal {
|
||||
public static T enum_from_nick<T> (string nick) throws Error {
|
||||
var klass = (EnumClass) typeof (T).class_ref ();
|
||||
var v = klass.get_value_by_nick (nick);
|
||||
if (v == null)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid %s", klass.get_type ().name ());
|
||||
return (T) v.value;
|
||||
}
|
||||
|
||||
public static string enum_to_nick<T> (int val) {
|
||||
var klass = (EnumClass) typeof (T).class_ref ();
|
||||
unowned EnumValue? v = klass.get_value (val);
|
||||
if (v == null)
|
||||
return "unknown-0x%x".printf (val);
|
||||
return v.value_nick;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Numeric {
|
||||
public uint int64_hash (int64? val) {
|
||||
uint64 v = (uint64) val.abs ();
|
||||
return (uint) ((v >> 32) ^ (v & 0xffffffffU));
|
||||
}
|
||||
|
||||
public bool int64_equal (int64? val_a, int64? val_b) {
|
||||
int64 a = val_a;
|
||||
int64 b = val_b;
|
||||
return a == b;
|
||||
}
|
||||
|
||||
public uint uint64_hash (uint64? val) {
|
||||
uint64 v = val;
|
||||
return (uint) ((v >> 32) ^ (v & 0xffffffffU));
|
||||
}
|
||||
|
||||
public bool uint64_equal (uint64? val_a, uint64? val_b) {
|
||||
uint64 a = val_a;
|
||||
uint64 b = val_b;
|
||||
return a == b;
|
||||
}
|
||||
|
||||
public uint bytes_hash (Bytes b) {
|
||||
return b.hash ();
|
||||
}
|
||||
|
||||
public static bool bytes_equal (Bytes a, Bytes b) {
|
||||
return a.compare (b) == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,5 @@
|
||||
#include "frida-base.h"
|
||||
|
||||
#ifdef HAVE_WINDOWS
|
||||
# include <winsock2.h>
|
||||
#else
|
||||
# include <netinet/in.h>
|
||||
# include <netinet/tcp.h>
|
||||
#endif
|
||||
|
||||
void
|
||||
frida_unix_socket_tune_buffer_sizes (gint fd)
|
||||
{
|
||||
#ifndef HAVE_WINDOWS
|
||||
/* The defaults are typically as low as 4K. */
|
||||
const int buffer_size = 256 * 1024;
|
||||
|
||||
setsockopt (fd, SOL_SOCKET, SO_RCVBUF, &buffer_size, sizeof (buffer_size));
|
||||
setsockopt (fd, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof (buffer_size));
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
frida_tcp_enable_nodelay (GSocket * socket)
|
||||
{
|
||||
g_socket_set_option (socket, IPPROTO_TCP, TCP_NODELAY, TRUE, NULL);
|
||||
}
|
||||
|
||||
const gchar *
|
||||
_frida_version_string (void)
|
||||
{
|
||||
|
||||
+27
-86
@@ -1,99 +1,22 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public const uint16 DEFAULT_CONTROL_PORT = 27042;
|
||||
public const uint16 DEFAULT_CLUSTER_PORT = 27052;
|
||||
|
||||
public SocketConnectable parse_control_address (string? address, uint16 port = 0) throws Error {
|
||||
return parse_socket_address (address, port, "127.0.0.1", DEFAULT_CONTROL_PORT);
|
||||
return parse_frida_address (address, port, DEFAULT_CONTROL_PORT);
|
||||
}
|
||||
|
||||
public SocketConnectable parse_cluster_address (string? address, uint16 port = 0) throws Error {
|
||||
return parse_socket_address (address, port, "127.0.0.1", DEFAULT_CLUSTER_PORT);
|
||||
return parse_frida_address (address, port, DEFAULT_CLUSTER_PORT);
|
||||
}
|
||||
|
||||
public SocketConnectable parse_socket_address (string? address, uint16 port, string default_address,
|
||||
uint16 default_port) throws Error {
|
||||
if (address == null)
|
||||
address = default_address;
|
||||
if (port == 0)
|
||||
port = default_port;
|
||||
|
||||
#if !WINDOWS
|
||||
if (address.has_prefix ("unix:")) {
|
||||
string path = address.substring (5);
|
||||
|
||||
UnixSocketAddressType type = UnixSocketAddress.abstract_names_supported ()
|
||||
? UnixSocketAddressType.ABSTRACT
|
||||
: UnixSocketAddressType.PATH;
|
||||
|
||||
return new UnixSocketAddress.with_type (path.to_utf8 (), type);
|
||||
}
|
||||
#endif
|
||||
|
||||
private SocketConnectable parse_frida_address (string? address, uint16 port, uint16 default_port) throws Error {
|
||||
try {
|
||||
return NetworkAddress.parse (address, port);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
namespace UnixSocket {
|
||||
public extern void tune_buffer_sizes (int fd);
|
||||
}
|
||||
|
||||
namespace Tcp {
|
||||
public extern void enable_nodelay (Socket socket);
|
||||
}
|
||||
|
||||
namespace Udp {
|
||||
public size_t recv (uint8[] data, DatagramBased source, Cancellable? cancellable, out InetSocketAddress remote_address)
|
||||
throws Error, IOError {
|
||||
var v = InputVector ();
|
||||
v.buffer = data;
|
||||
v.size = data.length;
|
||||
|
||||
InputVector[] vectors = { v };
|
||||
|
||||
var m = InputMessage ();
|
||||
remote_address = null;
|
||||
m.address = &remote_address;
|
||||
m.vectors = vectors;
|
||||
m.num_vectors = vectors.length;
|
||||
|
||||
InputMessage[] messages = { m };
|
||||
|
||||
try {
|
||||
source.receive_messages (messages, 0, 0, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
|
||||
return messages[0].bytes_received;
|
||||
}
|
||||
|
||||
public void send (uint8[] data, DatagramBased sink, Cancellable? cancellable) throws Error, IOError {
|
||||
send_to (data, null, sink, cancellable);
|
||||
}
|
||||
|
||||
public void send_to (uint8[] data, InetSocketAddress? dest_addr, DatagramBased sink, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var v = OutputVector ();
|
||||
v.buffer = data;
|
||||
v.size = data.length;
|
||||
|
||||
OutputVector[] vectors = { v };
|
||||
|
||||
var m = OutputMessage ();
|
||||
m.address = dest_addr;
|
||||
m.vectors = vectors;
|
||||
m.num_vectors = vectors.length;
|
||||
|
||||
OutputMessage[] messages = { m };
|
||||
|
||||
try {
|
||||
sink.send_messages (messages, 0, 0, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
return Efni.parse_socket_address (address, port, "127.0.0.1", default_port);
|
||||
} catch (Efni.Error e) {
|
||||
throw error_from_efni (e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1117,6 +1040,9 @@ namespace Frida {
|
||||
construct {
|
||||
websocket.max_incoming_payload_size = (256 * 1024) + 1; // XXX: There's an off-by-one error in libsoup
|
||||
|
||||
if (websocket.get_class ().find_property ("max-total-message-size") != null)
|
||||
websocket.set ("max-total-message-size", websocket.max_incoming_payload_size);
|
||||
|
||||
websocket.closed.connect (on_closed);
|
||||
websocket.message.connect (on_message);
|
||||
}
|
||||
@@ -1166,7 +1092,7 @@ namespace Frida {
|
||||
|
||||
if (recv_queue.len == 0) {
|
||||
#if HAVE_SOUP_WEBSOCKET_BACKPRESSURE
|
||||
websocket.resume_input ();
|
||||
resume_websocket_input ();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1183,6 +1109,21 @@ namespace Frida {
|
||||
return n;
|
||||
}
|
||||
|
||||
#if HAVE_SOUP_WEBSOCKET_BACKPRESSURE
|
||||
private void resume_websocket_input () {
|
||||
if (main_context.is_owner ()) {
|
||||
websocket.resume_input ();
|
||||
} else {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
websocket.resume_input ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
source.attach (main_context);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public override ssize_t write (uint8[] buffer) {
|
||||
with_state_lock (() => {
|
||||
send_queue.append (buffer);
|
||||
|
||||
@@ -1,336 +0,0 @@
|
||||
namespace Frida {
|
||||
public abstract class VirtualStream : IOStream {
|
||||
public override InputStream input_stream {
|
||||
get {
|
||||
return _input_stream;
|
||||
}
|
||||
}
|
||||
|
||||
public override OutputStream output_stream {
|
||||
get {
|
||||
return _output_stream;
|
||||
}
|
||||
}
|
||||
|
||||
public IOCondition pending_io {
|
||||
get {
|
||||
lock (state)
|
||||
return _pending_io;
|
||||
}
|
||||
}
|
||||
|
||||
protected State state = OPEN;
|
||||
|
||||
private VirtualInputStream _input_stream;
|
||||
private VirtualOutputStream _output_stream;
|
||||
|
||||
private IOCondition _pending_io;
|
||||
|
||||
private Gee.Map<unowned Source, IOCondition> sources = new Gee.HashMap<unowned Source, IOCondition> ();
|
||||
|
||||
protected Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
protected MainContext main_context;
|
||||
|
||||
protected enum State {
|
||||
CREATED,
|
||||
OPENING,
|
||||
OPEN,
|
||||
CLOSED
|
||||
}
|
||||
|
||||
protected delegate void WorkFunc ();
|
||||
|
||||
construct {
|
||||
_input_stream = new VirtualInputStream (this);
|
||||
_output_stream = new VirtualOutputStream (this);
|
||||
|
||||
main_context = MainContext.ref_thread_default ();
|
||||
|
||||
state = query_initial_state ();
|
||||
_pending_io = query_events ();
|
||||
}
|
||||
|
||||
public override void dispose () {
|
||||
_output_stream.detach ();
|
||||
_input_stream.detach ();
|
||||
|
||||
io_cancellable.cancel ();
|
||||
|
||||
base.dispose ();
|
||||
}
|
||||
|
||||
protected virtual State query_initial_state () {
|
||||
return OPEN;
|
||||
}
|
||||
|
||||
protected abstract IOCondition query_events ();
|
||||
|
||||
public override bool close (GLib.Cancellable? cancellable) throws IOError {
|
||||
_close ();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool close_async (int io_priority, GLib.Cancellable? cancellable) throws IOError {
|
||||
_close ();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void _close () {
|
||||
if (main_context.is_owner ()) {
|
||||
do_close ();
|
||||
} else {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
do_close ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
source.attach (main_context);
|
||||
}
|
||||
}
|
||||
|
||||
private void do_close () {
|
||||
if (state == CLOSED)
|
||||
return;
|
||||
|
||||
handle_close ();
|
||||
}
|
||||
|
||||
protected abstract void handle_close ();
|
||||
|
||||
public virtual void shutdown_read () throws IOError {
|
||||
}
|
||||
|
||||
public virtual void shutdown_write () throws IOError {
|
||||
}
|
||||
|
||||
public abstract ssize_t read (uint8[] buffer) throws IOError;
|
||||
|
||||
public abstract ssize_t write (uint8[] buffer) throws IOError;
|
||||
|
||||
protected void with_state_lock (WorkFunc func) {
|
||||
lock (state)
|
||||
func ();
|
||||
}
|
||||
|
||||
public void register_source (Source source, IOCondition condition) {
|
||||
lock (state)
|
||||
sources[source] = condition | IOCondition.ERR | IOCondition.HUP;
|
||||
}
|
||||
|
||||
public void unregister_source (Source source) {
|
||||
lock (state)
|
||||
sources.unset (source);
|
||||
}
|
||||
|
||||
protected virtual void update_pending_io () {
|
||||
lock (state) {
|
||||
_pending_io = query_events ();
|
||||
|
||||
foreach (var entry in sources.entries) {
|
||||
unowned Source source = entry.key;
|
||||
IOCondition c = entry.value;
|
||||
if ((_pending_io & c) != 0)
|
||||
source.set_ready_time (0);
|
||||
}
|
||||
}
|
||||
|
||||
notify_property ("pending-io");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class VirtualInputStream : InputStream, PollableInputStream {
|
||||
private weak VirtualStream stream;
|
||||
|
||||
public VirtualInputStream (VirtualStream stream) {
|
||||
Object ();
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
internal void detach () {
|
||||
stream = null;
|
||||
}
|
||||
|
||||
public override bool close (Cancellable? cancellable) throws IOError {
|
||||
if (stream != null)
|
||||
stream.shutdown_read ();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool close_async (int io_priority, Cancellable? cancellable) throws GLib.IOError {
|
||||
return close (cancellable);
|
||||
}
|
||||
|
||||
public override ssize_t read (uint8[] buffer, Cancellable? cancellable) throws IOError {
|
||||
if (stream == null)
|
||||
return 0;
|
||||
|
||||
if (!is_readable ()) {
|
||||
bool done = false;
|
||||
var mutex = Mutex ();
|
||||
var cond = Cond ();
|
||||
|
||||
ulong io_handler = stream.notify["pending-io"].connect ((obj, pspec) => {
|
||||
if (is_readable ()) {
|
||||
mutex.lock ();
|
||||
done = true;
|
||||
cond.signal ();
|
||||
mutex.unlock ();
|
||||
}
|
||||
});
|
||||
ulong cancellation_handler = 0;
|
||||
if (cancellable != null) {
|
||||
cancellation_handler = cancellable.connect (() => {
|
||||
mutex.lock ();
|
||||
done = true;
|
||||
cond.signal ();
|
||||
mutex.unlock ();
|
||||
});
|
||||
}
|
||||
|
||||
if (!is_readable ()) {
|
||||
mutex.lock ();
|
||||
while (!done)
|
||||
cond.wait (mutex);
|
||||
mutex.unlock ();
|
||||
}
|
||||
|
||||
if (cancellation_handler != 0)
|
||||
cancellable.disconnect (cancellation_handler);
|
||||
stream.disconnect (io_handler);
|
||||
|
||||
cancellable.set_error_if_cancelled ();
|
||||
}
|
||||
|
||||
return stream.read (buffer);
|
||||
}
|
||||
|
||||
public bool can_poll () {
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool is_readable () {
|
||||
if (stream == null)
|
||||
return true;
|
||||
return (stream.pending_io & IOCondition.IN) != 0;
|
||||
}
|
||||
|
||||
public PollableSource create_source (Cancellable? cancellable) {
|
||||
return new PollableSource.full (this, new VirtualIOSource (stream, IOCondition.IN), cancellable);
|
||||
}
|
||||
|
||||
public ssize_t read_nonblocking_fn (uint8[] buffer) throws GLib.Error {
|
||||
if (stream == null)
|
||||
return 0;
|
||||
return stream.read (buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class VirtualOutputStream : OutputStream, PollableOutputStream {
|
||||
private weak VirtualStream? stream;
|
||||
|
||||
public VirtualOutputStream (VirtualStream stream) {
|
||||
Object ();
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
internal void detach () {
|
||||
stream = null;
|
||||
}
|
||||
|
||||
public override bool close (Cancellable? cancellable) throws IOError {
|
||||
if (stream != null)
|
||||
stream.shutdown_write ();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool close_async (int io_priority, Cancellable? cancellable) throws GLib.IOError {
|
||||
return close (cancellable);
|
||||
}
|
||||
|
||||
public override bool flush (GLib.Cancellable? cancellable) throws GLib.Error {
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool flush_async (int io_priority, GLib.Cancellable? cancellable) throws GLib.Error {
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ssize_t write (uint8[] buffer, Cancellable? cancellable) throws IOError {
|
||||
return stream.write (buffer);
|
||||
}
|
||||
|
||||
public bool can_poll () {
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool is_writable () {
|
||||
if (stream == null)
|
||||
return false;
|
||||
return (stream.pending_io & IOCondition.OUT) != 0;
|
||||
}
|
||||
|
||||
public PollableSource create_source (Cancellable? cancellable) {
|
||||
return new PollableSource.full (this, new VirtualIOSource (stream, IOCondition.OUT), cancellable);
|
||||
}
|
||||
|
||||
public ssize_t write_nonblocking_fn (uint8[]? buffer) throws GLib.Error {
|
||||
if (stream == null)
|
||||
throw new IOError.CLOSED ("Virtual is closed");
|
||||
return stream.write (buffer);
|
||||
}
|
||||
|
||||
public PollableReturn writev_nonblocking_fn (OutputVector[] vectors, out size_t bytes_written) throws GLib.Error {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
private class VirtualIOSource : Source {
|
||||
public VirtualStream? stream;
|
||||
public IOCondition condition;
|
||||
|
||||
public VirtualIOSource (VirtualStream stream, IOCondition condition) {
|
||||
this.stream = stream;
|
||||
this.condition = condition;
|
||||
|
||||
if (stream != null)
|
||||
stream.register_source (this, condition);
|
||||
}
|
||||
|
||||
~VirtualIOSource () {
|
||||
if (stream != null)
|
||||
stream.unregister_source (this);
|
||||
}
|
||||
|
||||
protected override bool prepare (out int timeout) {
|
||||
timeout = -1;
|
||||
return is_ready ();
|
||||
}
|
||||
|
||||
protected override bool check () {
|
||||
return is_ready ();
|
||||
}
|
||||
|
||||
private bool is_ready () {
|
||||
IOCondition pending_io = (stream != null) ? stream.pending_io : IOCondition.IN;
|
||||
return (pending_io & condition) != 0;
|
||||
}
|
||||
|
||||
protected override bool dispatch (SourceFunc? callback) {
|
||||
set_ready_time (-1);
|
||||
|
||||
if (callback == null)
|
||||
return Source.REMOVE;
|
||||
|
||||
return callback ();
|
||||
}
|
||||
|
||||
protected static bool closure_callback (Closure closure) {
|
||||
var return_value = Value (typeof (bool));
|
||||
|
||||
closure.invoke (ref return_value, {});
|
||||
|
||||
return return_value.get_boolean ();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,432 +0,0 @@
|
||||
namespace Frida {
|
||||
public interface ObjectBuilder : Object {
|
||||
public abstract unowned ObjectBuilder begin_dictionary ();
|
||||
public abstract unowned ObjectBuilder set_member_name (string name);
|
||||
public abstract unowned ObjectBuilder end_dictionary ();
|
||||
|
||||
public abstract unowned ObjectBuilder begin_array ();
|
||||
public abstract unowned ObjectBuilder end_array ();
|
||||
|
||||
public abstract unowned ObjectBuilder add_null_value ();
|
||||
public abstract unowned ObjectBuilder add_bool_value (bool val);
|
||||
public abstract unowned ObjectBuilder add_int64_value (int64 val);
|
||||
public abstract unowned ObjectBuilder add_uint64_value (uint64 val);
|
||||
public abstract unowned ObjectBuilder add_data_value (Bytes val);
|
||||
public abstract unowned ObjectBuilder add_string_value (string val);
|
||||
public abstract unowned ObjectBuilder add_uuid_value (uint8[] val);
|
||||
public abstract unowned ObjectBuilder add_raw_value (Bytes val);
|
||||
|
||||
public abstract Bytes build ();
|
||||
}
|
||||
|
||||
public interface ObjectReader : Object {
|
||||
public abstract bool has_member (string name) throws Error;
|
||||
public abstract unowned ObjectReader read_member (string name) throws Error;
|
||||
public abstract unowned ObjectReader end_member ();
|
||||
|
||||
public abstract uint count_elements () throws Error;
|
||||
public abstract unowned ObjectReader read_element (uint index) throws Error;
|
||||
public abstract unowned ObjectReader end_element () throws Error;
|
||||
|
||||
public abstract bool get_bool_value () throws Error;
|
||||
public abstract uint8 get_uint8_value () throws Error;
|
||||
public abstract uint16 get_uint16_value () throws Error;
|
||||
public abstract int64 get_int64_value () throws Error;
|
||||
public abstract uint64 get_uint64_value () throws Error;
|
||||
public abstract Bytes get_data_value () throws Error;
|
||||
public abstract unowned string get_string_value () throws Error;
|
||||
public abstract unowned string get_uuid_value () throws Error;
|
||||
}
|
||||
|
||||
public sealed class VariantReader : Object, ObjectReader {
|
||||
public Variant root_object {
|
||||
get {
|
||||
return scopes.peek_head ().val;
|
||||
}
|
||||
}
|
||||
|
||||
public Variant current_object {
|
||||
get {
|
||||
return scopes.peek_tail ().val;
|
||||
}
|
||||
}
|
||||
|
||||
private Gee.Deque<Scope> scopes = new Gee.ArrayQueue<Scope> ();
|
||||
|
||||
public VariantReader (Variant v) {
|
||||
push_scope (v);
|
||||
}
|
||||
|
||||
public string[] list_members () throws Error {
|
||||
var scope = peek_scope ();
|
||||
if (scope.dict == null)
|
||||
throw new Error.PROTOCOL ("Dictionary expected, but at %s", scope.val.print (true));
|
||||
|
||||
var members = new Gee.ArrayList<string> ();
|
||||
foreach (var item in scope.val) {
|
||||
string k;
|
||||
Variant v;
|
||||
item.get ("{sv}", out k, out v);
|
||||
|
||||
members.add (k);
|
||||
}
|
||||
return members.to_array ();
|
||||
}
|
||||
|
||||
public bool has_member (string name) throws Error {
|
||||
var scope = peek_scope ();
|
||||
if (scope.dict == null)
|
||||
throw new Error.PROTOCOL ("Dictionary expected, but at %s", scope.val.print (true));
|
||||
return scope.dict.contains (name);
|
||||
}
|
||||
|
||||
public unowned ObjectReader read_member (string name) throws Error {
|
||||
var scope = peek_scope ();
|
||||
if (scope.dict == null)
|
||||
throw new Error.PROTOCOL ("Dictionary expected, but at %s", scope.val.print (true));
|
||||
|
||||
Variant? v = scope.dict.lookup_value (name, null);
|
||||
if (v == null)
|
||||
throw new Error.PROTOCOL ("Key '%s' not found in dictionary: %s", name, scope.val.print (true));
|
||||
|
||||
push_scope (v);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectReader end_member () {
|
||||
pop_scope ();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public uint count_elements () throws Error {
|
||||
var scope = peek_scope ();
|
||||
scope.check_array ();
|
||||
return (uint) scope.val.n_children ();
|
||||
}
|
||||
|
||||
public unowned ObjectReader read_element (uint index) throws Error {
|
||||
var scope = peek_scope ();
|
||||
scope.check_array ();
|
||||
push_scope (scope.val.get_child_value (index).get_variant ());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectReader end_element () throws Error {
|
||||
pop_scope ();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool get_bool_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.BOOLEAN).get_boolean ();
|
||||
}
|
||||
|
||||
public uint8 get_uint8_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.BYTE).get_byte ();
|
||||
}
|
||||
|
||||
public uint16 get_uint16_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.UINT16).get_uint16 ();
|
||||
}
|
||||
|
||||
public int64 get_int64_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.INT64).get_int64 ();
|
||||
}
|
||||
|
||||
public uint64 get_uint64_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.UINT64).get_uint64 ();
|
||||
}
|
||||
|
||||
public Bytes get_data_value () throws Error {
|
||||
return peek_scope ().get_value (new VariantType.array (VariantType.BYTE)).get_data_as_bytes ();
|
||||
}
|
||||
|
||||
public unowned string get_string_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.STRING).get_string ();
|
||||
}
|
||||
|
||||
public unowned string get_uuid_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.STRING).get_string (); // TODO: Use a tuple to avoid ambiguity.
|
||||
}
|
||||
|
||||
private void push_scope (Variant v) {
|
||||
scopes.offer_tail (new Scope (v));
|
||||
}
|
||||
|
||||
private Scope peek_scope () {
|
||||
return scopes.peek_tail ();
|
||||
}
|
||||
|
||||
private Scope pop_scope () {
|
||||
return scopes.poll_tail ();
|
||||
}
|
||||
|
||||
private class Scope {
|
||||
public Variant val;
|
||||
public VariantDict? dict;
|
||||
public bool is_array = false;
|
||||
|
||||
public Scope (Variant v) {
|
||||
val = v;
|
||||
|
||||
VariantType t = v.get_type ();
|
||||
if (t.equal (VariantType.VARDICT))
|
||||
dict = new VariantDict (v);
|
||||
else if (t.is_subtype_of (VariantType.ARRAY))
|
||||
is_array = true;
|
||||
}
|
||||
|
||||
public Variant get_value (VariantType expected_type) throws Error {
|
||||
if (!val.get_type ().equal (expected_type)) {
|
||||
throw new Error.PROTOCOL ("Expected type '%s', got '%s'",
|
||||
(string) expected_type.peek_string (),
|
||||
(string) val.get_type ().peek_string ());
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void check_array () throws Error {
|
||||
if (!is_array)
|
||||
throw new Error.PROTOCOL ("Array expected, but at %s", val.print (true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class JsonObjectBuilder : Object, ObjectBuilder {
|
||||
private Json.Builder builder = new Json.Builder ();
|
||||
private Gee.Map<string, Bytes> raw_values = new Gee.HashMap<string, Bytes> ();
|
||||
|
||||
public unowned ObjectBuilder begin_dictionary () {
|
||||
builder.begin_object ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder set_member_name (string name) {
|
||||
builder.set_member_name (name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder end_dictionary () {
|
||||
builder.end_object ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder begin_array () {
|
||||
builder.begin_array ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder end_array () {
|
||||
builder.end_array ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_null_value () {
|
||||
builder.add_null_value ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_bool_value (bool val) {
|
||||
builder.add_boolean_value (val);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_int64_value (int64 val) {
|
||||
builder.add_int_value (val);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_uint64_value (uint64 val) {
|
||||
builder.add_int_value ((int64) val);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_data_value (Bytes val) {
|
||||
builder.add_string_value (Base64.encode (val.get_data ()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_string_value (string val) {
|
||||
builder.add_string_value (val);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_uuid_value (uint8[] val) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_raw_value (Bytes val) {
|
||||
string uuid = Uuid.string_random ();
|
||||
builder.add_string_value (uuid);
|
||||
raw_values[uuid] = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Bytes build () {
|
||||
string json = Json.to_string (builder.get_root (), false);
|
||||
|
||||
foreach (var e in raw_values.entries) {
|
||||
unowned string uuid = e.key;
|
||||
Bytes val = e.value;
|
||||
|
||||
unowned string raw_str = (string) val.get_data ();
|
||||
string str = raw_str[:(long) val.get_size ()];
|
||||
|
||||
json = json.replace ("\"" + uuid + "\"", str);
|
||||
}
|
||||
|
||||
return new Bytes (json.data);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class JsonObjectReader : Object, ObjectReader {
|
||||
private Json.Reader reader;
|
||||
|
||||
public JsonObjectReader (string json) throws Error {
|
||||
try {
|
||||
reader = make_json_reader (json);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public bool has_member (string name) throws Error {
|
||||
bool result = reader.read_member (name);
|
||||
reader.end_member ();
|
||||
return result;
|
||||
}
|
||||
|
||||
public unowned ObjectReader read_member (string name) throws Error {
|
||||
if (!reader.read_member (name))
|
||||
throw_dict_access_error ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectReader end_member () {
|
||||
reader.end_member ();
|
||||
return this;
|
||||
}
|
||||
|
||||
[NoReturn]
|
||||
private void throw_dict_access_error () throws Error {
|
||||
GLib.Error e = reader.get_error ();
|
||||
reader.end_member ();
|
||||
throw new Error.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
|
||||
public uint count_elements () throws Error {
|
||||
int n = reader.count_elements ();
|
||||
if (n == -1)
|
||||
throw_array_access_error ();
|
||||
return n;
|
||||
}
|
||||
|
||||
public unowned ObjectReader read_element (uint index) throws Error {
|
||||
if (!reader.read_element (index)) {
|
||||
GLib.Error e = reader.get_error ();
|
||||
reader.end_element ();
|
||||
throw new Error.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectReader end_element () throws Error {
|
||||
reader.end_element ();
|
||||
return this;
|
||||
}
|
||||
|
||||
[NoReturn]
|
||||
private void throw_array_access_error () throws Error {
|
||||
GLib.Error e = reader.get_error ();
|
||||
reader.end_element ();
|
||||
throw new Error.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
|
||||
public bool get_bool_value () throws Error {
|
||||
bool v = reader.get_boolean_value ();
|
||||
if (!v)
|
||||
maybe_throw_value_access_error ();
|
||||
return v;
|
||||
}
|
||||
|
||||
public uint8 get_uint8_value () throws Error {
|
||||
int64 v = get_int64_value ();
|
||||
if (v < 0 || v > uint8.MAX)
|
||||
throw new Error.PROTOCOL ("Invalid uint8");
|
||||
return (uint8) v;
|
||||
}
|
||||
|
||||
public uint16 get_uint16_value () throws Error {
|
||||
int64 v = get_int64_value ();
|
||||
if (v < 0 || v > uint16.MAX)
|
||||
throw new Error.PROTOCOL ("Invalid uint16");
|
||||
return (uint16) v;
|
||||
}
|
||||
|
||||
public int64 get_int64_value () throws Error {
|
||||
int64 v = reader.get_int_value ();
|
||||
if (v == 0)
|
||||
maybe_throw_value_access_error ();
|
||||
return v;
|
||||
}
|
||||
|
||||
public uint64 get_uint64_value () throws Error {
|
||||
int64 v = get_int64_value ();
|
||||
if (v < 0)
|
||||
throw new Error.PROTOCOL ("Invalid uint64");
|
||||
return v;
|
||||
}
|
||||
|
||||
public Bytes get_data_value () throws Error {
|
||||
return new Bytes (Base64.decode (get_string_value ()));
|
||||
}
|
||||
|
||||
public unowned string get_string_value () throws Error {
|
||||
unowned string? v = reader.get_string_value ();
|
||||
if (v == null)
|
||||
maybe_throw_value_access_error ();
|
||||
return v;
|
||||
}
|
||||
|
||||
public unowned string get_uuid_value () throws Error {
|
||||
return get_string_value ();
|
||||
}
|
||||
|
||||
private void maybe_throw_value_access_error () throws Error {
|
||||
GLib.Error? e = reader.get_error ();
|
||||
if (e == null)
|
||||
return;
|
||||
reader.end_member ();
|
||||
throw new Error.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Constructs a Json.Reader for the given JSON string, working around the
|
||||
* json_node_copy() bug in unpatched json-glib (GNOME bugzilla #774684).
|
||||
*
|
||||
* Json.Reader's set_root() copies the supplied node via json_node_copy().
|
||||
* For object/array nodes that copy is a thin shell that shares the original
|
||||
* JsonObject/JsonArray via refcount, but the members inside the shared
|
||||
* container have their parent pointers set to the *original* root, not the
|
||||
* shell. If the original is then dropped, json_reader_end_member()'s walk
|
||||
* via json_node_get_parent() touches a freed node — which on builds with
|
||||
* glib checks enabled (e.g. Fedora) trips JSON_NODE_IS_VALID and aborts.
|
||||
*
|
||||
* We keep the original root alive by attaching it to the reader as object
|
||||
* data; the destroy notify drops our reference when the reader is finalized.
|
||||
*/
|
||||
public Json.Reader make_json_reader (string json) throws GLib.Error {
|
||||
return make_json_reader_taking_node (Json.from_string (json));
|
||||
}
|
||||
|
||||
public Json.Reader make_json_reader_taking_node (owned Json.Node root) {
|
||||
var reader = new Json.Reader (root);
|
||||
reader.set_data<Json.Node> ("frida-json-root", (owned) root);
|
||||
return reader;
|
||||
}
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
namespace Frida {
|
||||
public sealed class XpcClient : Object {
|
||||
public signal void message (Darwin.Xpc.Object obj);
|
||||
|
||||
public State state {
|
||||
get {
|
||||
return _state;
|
||||
}
|
||||
}
|
||||
|
||||
public Darwin.Xpc.Connection connection {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public Darwin.GCD.DispatchQueue queue {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private State _state;
|
||||
private string? close_reason;
|
||||
private MainContext main_context;
|
||||
|
||||
public enum State {
|
||||
OPEN,
|
||||
CLOSING,
|
||||
CLOSED,
|
||||
}
|
||||
|
||||
public static XpcClient make_for_mach_service (string name, Darwin.GCD.DispatchQueue queue) {
|
||||
return new XpcClient (Darwin.Xpc.Connection.create_mach_service (name, queue, 0), queue);
|
||||
}
|
||||
|
||||
public XpcClient (Darwin.Xpc.Connection connection, Darwin.GCD.DispatchQueue queue) {
|
||||
Object (connection: connection, queue: queue);
|
||||
}
|
||||
|
||||
construct {
|
||||
main_context = MainContext.ref_thread_default ();
|
||||
|
||||
connection.set_event_handler (on_event);
|
||||
connection.activate ();
|
||||
}
|
||||
|
||||
public override void dispose () {
|
||||
if (close_reason != null) {
|
||||
change_state (CLOSED);
|
||||
} else {
|
||||
change_state (CLOSING);
|
||||
this.ref ();
|
||||
connection.cancel ();
|
||||
}
|
||||
|
||||
base.dispose ();
|
||||
}
|
||||
|
||||
public async Darwin.Xpc.Object request (Darwin.Xpc.Object message, Cancellable? cancellable) throws Error, IOError {
|
||||
Darwin.Xpc.Object? reply = null;
|
||||
connection.send_message_with_reply (message, queue, r => {
|
||||
schedule_on_frida_thread (() => {
|
||||
reply = r;
|
||||
request.callback ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
});
|
||||
|
||||
var cancel_source = new CancellableSource (cancellable);
|
||||
cancel_source.set_callback (() => {
|
||||
connection.cancel ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
cancel_source.attach (main_context);
|
||||
|
||||
yield;
|
||||
|
||||
cancel_source.destroy ();
|
||||
|
||||
if (reply.type == Darwin.Xpc.Error.TYPE) {
|
||||
var e = (Darwin.Xpc.Error) reply;
|
||||
throw new Error.NOT_SUPPORTED ("%s", e.get_string (Darwin.Xpc.Error.KEY_DESCRIPTION));
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
public void post (Darwin.Xpc.Object message) throws Error, IOError {
|
||||
connection.send_message (message);
|
||||
}
|
||||
|
||||
private void change_state (State new_state) {
|
||||
_state = new_state;
|
||||
notify_property ("state");
|
||||
}
|
||||
|
||||
private void on_event (Darwin.Xpc.Object obj) {
|
||||
schedule_on_frida_thread (() => {
|
||||
if (obj.type == Darwin.Xpc.Error.TYPE) {
|
||||
var e = (Darwin.Xpc.Error) obj;
|
||||
close_reason = e.get_string (Darwin.Xpc.Error.KEY_DESCRIPTION);
|
||||
switch (state) {
|
||||
case OPEN:
|
||||
change_state (CLOSED);
|
||||
break;
|
||||
case CLOSING:
|
||||
change_state (CLOSED);
|
||||
unref ();
|
||||
break;
|
||||
case CLOSED:
|
||||
assert_not_reached ();
|
||||
}
|
||||
} else {
|
||||
message (obj);
|
||||
}
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
private void schedule_on_frida_thread (owned SourceFunc function) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback ((owned) function);
|
||||
source.attach (main_context);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class XpcObjectReader {
|
||||
public Darwin.Xpc.Object root_object {
|
||||
get {
|
||||
return scopes.peek_head ().object;
|
||||
}
|
||||
}
|
||||
|
||||
public Darwin.Xpc.Object current_object {
|
||||
get {
|
||||
return scopes.peek_tail ().object;
|
||||
}
|
||||
}
|
||||
|
||||
public delegate int TranslateErrorFunc (string domain, int code, string description);
|
||||
|
||||
private Gee.Deque<Scope> scopes = new Gee.ArrayQueue<Scope> ();
|
||||
|
||||
public XpcObjectReader (Darwin.Xpc.Object obj) {
|
||||
push_scope (obj);
|
||||
}
|
||||
|
||||
public bool has_member (string name) throws Error {
|
||||
return peek_scope ().get_dictionary ().get_value (name) != null;
|
||||
}
|
||||
|
||||
public bool try_read_member (string name) throws Error {
|
||||
var scope = peek_scope ();
|
||||
var dict = scope.get_dictionary ();
|
||||
var val = dict.get_value (name);
|
||||
if (val == null)
|
||||
return false;
|
||||
|
||||
push_scope (val);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public unowned XpcObjectReader read_member (string name) throws Error {
|
||||
var scope = peek_scope ();
|
||||
var dict = scope.get_dictionary ();
|
||||
var val = dict.get_value (name);
|
||||
if (val == null)
|
||||
throw new Error.PROTOCOL ("Key '%s' not found in dictionary: %s", name, scope.object.to_string ());
|
||||
|
||||
push_scope (val);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned XpcObjectReader end_member () {
|
||||
pop_scope ();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public size_t count_elements () throws Error {
|
||||
return peek_scope ().get_array ().count;
|
||||
}
|
||||
|
||||
public unowned XpcObjectReader read_element (size_t index) throws Error {
|
||||
push_scope (peek_scope ().get_array ().get_value (index));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned XpcObjectReader end_element () throws Error {
|
||||
pop_scope ();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool get_bool_value () throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.Bool> (Darwin.Xpc.Bool.TYPE).get_value ();
|
||||
}
|
||||
|
||||
public int64 get_int64_value () throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.Int64> (Darwin.Xpc.Int64.TYPE).get_value ();
|
||||
}
|
||||
|
||||
public uint64 get_uint64_value () throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.UInt64> (Darwin.Xpc.UInt64.TYPE).get_value ();
|
||||
}
|
||||
|
||||
public unowned uint8[] get_data_value () throws Error {
|
||||
var data = peek_scope ().get_object<Darwin.Xpc.Data> (Darwin.Xpc.Data.TYPE);
|
||||
unowned uint8[] buf = (uint8[]) data.get_bytes_ptr ();
|
||||
buf.length = (int) data.get_length ();
|
||||
return buf;
|
||||
}
|
||||
|
||||
public unowned string get_string_value () throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.String> (Darwin.Xpc.String.TYPE).get_string_ptr ();
|
||||
}
|
||||
|
||||
public unowned uint8[] get_uuid_value () throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.Uuid> (Darwin.Xpc.Uuid.TYPE).get_bytes ()[:16];
|
||||
}
|
||||
|
||||
public unowned string get_error_description () throws Error {
|
||||
var error = peek_scope ().get_object<Darwin.Xpc.Error> (Darwin.Xpc.Error.TYPE);
|
||||
return error.get_string (Darwin.Xpc.Error.KEY_DESCRIPTION);
|
||||
}
|
||||
|
||||
public void check_nserror (TranslateErrorFunc translate_error) throws Error {
|
||||
if (!has_member ("error"))
|
||||
return;
|
||||
|
||||
read_member ("error");
|
||||
|
||||
unowned string domain = read_member ("domain").get_string_value ();
|
||||
end_member ();
|
||||
|
||||
var code = (int) read_member ("code").get_int64_value ();
|
||||
end_member ();
|
||||
|
||||
string description = read_member ("userInfo").read_member ("NSLocalizedDescription").get_string_value ();
|
||||
if (description.has_suffix ("."))
|
||||
description = description[:description.length - 1];
|
||||
|
||||
throw (Error) new GLib.Error.literal (
|
||||
Quark.from_string ("frida-error-quark"),
|
||||
translate_error (domain, code, description),
|
||||
description);
|
||||
}
|
||||
|
||||
public unowned Darwin.Xpc.Object get_object_value (Darwin.Xpc.Type expected_type) throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.Object> (expected_type);
|
||||
}
|
||||
|
||||
private void push_scope (Darwin.Xpc.Object obj) {
|
||||
scopes.offer_tail (new Scope (obj));
|
||||
}
|
||||
|
||||
private Scope peek_scope () {
|
||||
return scopes.peek_tail ();
|
||||
}
|
||||
|
||||
private Scope pop_scope () {
|
||||
return scopes.poll_tail ();
|
||||
}
|
||||
|
||||
private class Scope {
|
||||
public Darwin.Xpc.Object object;
|
||||
private unowned Darwin.Xpc.Type type;
|
||||
|
||||
public Scope (Darwin.Xpc.Object obj) {
|
||||
object = obj;
|
||||
type = obj.type;
|
||||
}
|
||||
|
||||
public unowned T get_object<T> (Darwin.Xpc.Type expected_type) throws Error {
|
||||
if (type != expected_type)
|
||||
throw new Error.PROTOCOL ("Expected type '%s', got '%s'", expected_type.name, type.name);
|
||||
return object;
|
||||
}
|
||||
|
||||
public unowned Darwin.Xpc.Array get_array () throws Error {
|
||||
return get_object<Darwin.Xpc.Array> (Darwin.Xpc.Array.TYPE);
|
||||
}
|
||||
|
||||
public unowned Darwin.Xpc.Dictionary get_dictionary () throws Error {
|
||||
return get_object<Darwin.Xpc.Dictionary> (Darwin.Xpc.Dictionary.TYPE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida.Gadget {
|
||||
private sealed class Config : Object, Json.Serializable {
|
||||
public Object interaction {
|
||||
|
||||
@@ -8,7 +8,6 @@ extra_link_args = []
|
||||
if host_os in ['ios', 'tvos']
|
||||
extra_vala_args += [
|
||||
'--pkg=corefoundation',
|
||||
'--pkg=darwin-gcd',
|
||||
'--pkg=darwin-systemconfiguration',
|
||||
]
|
||||
extra_link_args += '-Wl,-framework,SystemConfiguration'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public abstract class BaseAgentSession : Object, AgentSession {
|
||||
public signal void closed ();
|
||||
|
||||
@@ -46,8 +46,24 @@ namespace Frida {
|
||||
}
|
||||
#else
|
||||
var libc = Gum.Process.get_libc_module ();
|
||||
foreach (var symbol in apis) {
|
||||
interceptor.attach ((void *) libc.find_export_by_name (symbol), listener);
|
||||
var targets = new Gum.Address[apis.length];
|
||||
for (var i = 0; i != apis.length; i++)
|
||||
targets[i] = libc.find_export_by_name (apis[i]);
|
||||
|
||||
var sizes = new uint[apis.length];
|
||||
#if ARM
|
||||
libc.enumerate_symbols (s => {
|
||||
for (var i = 0; i != targets.length; i++) {
|
||||
if (s.address == targets[i] && s.size > 0)
|
||||
sizes[i] = (uint) s.size;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
#endif
|
||||
for (var i = 0; i != apis.length; i++) {
|
||||
var options = Gum.AttachOptions ();
|
||||
options.instrumentation.redirect_space_hint = sizes[i];
|
||||
interceptor.attach ((void *) targets[i], listener, options);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public sealed class PortalClient : Object, AgentSessionProvider {
|
||||
public signal void resume ();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public extern void libc_shim_prepare_to_deinit ();
|
||||
#if DARWIN
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public sealed class ScriptEngine : Object {
|
||||
public signal void message_from_script (AgentScriptId script_id, string json, Bytes? data);
|
||||
|
||||
@@ -34,7 +34,7 @@ struct _FridaCreateArgs
|
||||
|
||||
static FridaDyldUnwindSections * frida_get_cached_sections (GumAddress range_start, GumAddress range_end);
|
||||
static FridaDyldUnwindSections * frida_create_cached_sections (FridaCreateArgs * args);
|
||||
static gboolean frida_fill_info (const GumDarwinSectionDetails * details, FridaFillInfoContext * ctx);
|
||||
static gboolean frida_fill_info (const GumMachoSectionDetails * details, FridaFillInfoContext * ctx);
|
||||
|
||||
void
|
||||
_frida_unwind_sitter_fill_unwind_sections (GumAddress invader_start, GumAddress invader_end, void * info)
|
||||
@@ -70,7 +70,7 @@ frida_create_cached_sections (FridaCreateArgs * args)
|
||||
gsize page_size;
|
||||
gpointer header;
|
||||
GumPageProtection prot;
|
||||
GumDarwinModule * module;
|
||||
GumMachoModule * module;
|
||||
FridaFillInfoContext ctx;
|
||||
|
||||
page_size = gum_query_page_size ();
|
||||
@@ -87,14 +87,14 @@ frida_create_cached_sections (FridaCreateArgs * args)
|
||||
cached_sections = g_slice_new0 (FridaDyldUnwindSections);
|
||||
cached_sections->mh = header;
|
||||
|
||||
module = gum_darwin_module_new_from_memory ("Frida", mach_task_self (), GPOINTER_TO_SIZE (header),
|
||||
GUM_DARWIN_MODULE_FLAGS_NONE, NULL);
|
||||
module = gum_macho_module_new_from_memory ("Frida", mach_task_self (), GPOINTER_TO_SIZE (header),
|
||||
GUM_MACHO_MODULE_FLAGS_NONE, NULL);
|
||||
if (module == NULL)
|
||||
return cached_sections;
|
||||
|
||||
ctx.info = cached_sections;
|
||||
ctx.missing_info = 2;
|
||||
gum_darwin_module_enumerate_sections (module, (GumFoundDarwinSectionFunc) frida_fill_info, &ctx);
|
||||
gum_macho_module_enumerate_sections (module, (GumFoundMachoSectionFunc) frida_fill_info, &ctx);
|
||||
|
||||
g_object_unref (module);
|
||||
|
||||
@@ -102,7 +102,7 @@ frida_create_cached_sections (FridaCreateArgs * args)
|
||||
}
|
||||
|
||||
static gboolean
|
||||
frida_fill_info (const GumDarwinSectionDetails * details, FridaFillInfoContext * ctx)
|
||||
frida_fill_info (const GumMachoSectionDetails * details, FridaFillInfoContext * ctx)
|
||||
{
|
||||
if (strcmp ("__TEXT", details->segment_name) != 0)
|
||||
return TRUE;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "pipe-glue.h"
|
||||
|
||||
#include <efni.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
@@ -66,7 +67,7 @@ _frida_pipe_transport_create_backend (gchar ** local_address, gchar ** remote_ad
|
||||
|
||||
fcntl (fd, F_SETFD, FD_CLOEXEC);
|
||||
setsockopt (fd, SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, sizeof (no_sigpipe));
|
||||
frida_unix_socket_tune_buffer_sizes (fd);
|
||||
efni_unix_socket_tune_buffer_sizes (fd);
|
||||
}
|
||||
|
||||
status = fileport_makeport (sockets[0], &local_wrapper);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public sealed class PipeTransport : Object {
|
||||
public string local_address {
|
||||
|
||||
+65
-49
@@ -14,7 +14,7 @@ if not meson.get_compiler('vala').version().endswith('-frida')
|
||||
Frida currently relies on features not yet upstream. Grab the Frida-optimized
|
||||
Vala sources from:
|
||||
|
||||
https://github.com/frida/vala
|
||||
https://github.com/frida-forks/vala
|
||||
|
||||
and compile them with Meson.''')
|
||||
endif
|
||||
@@ -349,9 +349,13 @@ glib_options = [
|
||||
'selinux=disabled',
|
||||
'xattr=false',
|
||||
'libmount=disabled',
|
||||
'sysprof=disabled',
|
||||
'tests=false',
|
||||
'nls=disabled',
|
||||
]
|
||||
if host_os_family in ['darwin', 'qnx', 'none'] or host_os == 'android' or libc == 'uclibc'
|
||||
glib_options += 'iconv=external'
|
||||
endif
|
||||
if optimize_for_prod
|
||||
glib_options += [
|
||||
'glib_debug=disabled',
|
||||
@@ -408,7 +412,20 @@ json_glib_dep = dependency('json-glib-1.0', version: '>=1.8', default_options: [
|
||||
'introspection=disabled',
|
||||
'gtk_doc=disabled',
|
||||
'tests=false',
|
||||
'nls=disabled',
|
||||
])
|
||||
efni_dep = dependency('efni-1.0', default_options: ['default_library=static'])
|
||||
efni_libs = []
|
||||
if efni_dep.type_name() == 'internal'
|
||||
efni_libs += subproject('efni').get_variable('efni')
|
||||
endif
|
||||
efni_vapidir = efni_dep.get_variable('vapidir', default_value: '')
|
||||
if efni_vapidir != ''
|
||||
vala_flags += '--vapidir=' + efni_vapidir
|
||||
endif
|
||||
foreach pkg : efni_dep.get_variable('vala_packages', default_value: '').split()
|
||||
vala_flags += '--pkg=' + pkg
|
||||
endforeach
|
||||
libsoup_dep = dependency('libsoup-3.0', default_options: [
|
||||
'default_library=static',
|
||||
'sqlite:default_library=static',
|
||||
@@ -429,7 +446,13 @@ if libsoup_dep.type_name() == 'internal' \
|
||||
vala_flags += '--define=HAVE_SOUP_WEBSOCKET_BACKPRESSURE'
|
||||
endif
|
||||
brotlidec_dep = dependency('libbrotlidec', default_options: ['default_library=static'])
|
||||
capstone_dep = dependency('capstone', default_options: ['default_library=static'])
|
||||
capstone_dep = dependency('capstone', default_options: [
|
||||
'default_library=static',
|
||||
'archs=all',
|
||||
'use_arch_registration=true',
|
||||
'x86_att_disable=true',
|
||||
'cli=disabled',
|
||||
])
|
||||
if capstone_dep.type_name() != 'internal' \
|
||||
and not cc.has_function('cs_arch_register_x86', dependencies: [capstone_dep])
|
||||
add_project_arguments(
|
||||
@@ -443,9 +466,9 @@ endif
|
||||
quickjs_dep = dependency('quickjs', required: get_option('barebone_backend'), default_options: [
|
||||
'default_library=static',
|
||||
'libc=false',
|
||||
'bignum=true',
|
||||
'atomics=disabled',
|
||||
'stack_check=disabled',
|
||||
'stack_check=' + ((host_os_family == 'none') ? 'enabled' : 'disabled'),
|
||||
'stack_mode=' + ((host_os_family == 'none') ? 'optimize' : 'default'),
|
||||
])
|
||||
lzfse_dep = dependency('lzfse', required: false, default_options: ['default_library=static'])
|
||||
lzfse_from_system = false
|
||||
@@ -481,6 +504,7 @@ endif
|
||||
|
||||
if host_os == 'android'
|
||||
minizip_dep = dependency('minizip', required: false, default_options: [
|
||||
'default_library=static',
|
||||
'zlib=enabled',
|
||||
'lzma=disabled',
|
||||
])
|
||||
@@ -574,7 +598,11 @@ if compiler_backend_opt.allowed()
|
||||
have_compiler_backend = true
|
||||
cdata.set('HAVE_COMPILER_BACKEND', 1)
|
||||
compiler_backend_mode = lines[1]
|
||||
go_config = lines[2]
|
||||
go_config = configure_file(
|
||||
input: 'tools' / 'cgo' / 'go-config.json.in',
|
||||
output: 'go-config.json',
|
||||
configuration: {'GO_CONFIG': lines[2]},
|
||||
)
|
||||
|
||||
if compiler_backend_mode == 'c-archive'
|
||||
vala_flags += '--define=COMPILER_BACKEND_LINKED'
|
||||
@@ -698,54 +726,37 @@ if host_os_family == 'windows' and cc.get_argument_syntax() != 'msvc'
|
||||
openssl_options += 'asm=disabled'
|
||||
endif
|
||||
openssl_dep = dependency('openssl',
|
||||
required: have_fruity_backend or get_option('connectivity').enabled(),
|
||||
required: get_option('connectivity').enabled(),
|
||||
modules: cc.get_argument_syntax() == 'msvc' ? ['OpenSSL::SSL'] : [],
|
||||
default_options: openssl_options,
|
||||
)
|
||||
|
||||
idun_libs = []
|
||||
have_idund = false
|
||||
if have_fruity_backend
|
||||
backend_deps_private += dependency('libnghttp2', default_options: ['default_library=static'])
|
||||
backend_reqs_private += 'libnghttp2'
|
||||
|
||||
if host_os != 'macos'
|
||||
backend_deps_private += dependency('libngtcp2', default_options: ['default_library=static'])
|
||||
backend_reqs_private += 'libngtcp2'
|
||||
|
||||
ngtcp2_crypto_quictls_dep = dependency('libngtcp2_crypto_quictls', required: false, default_options: ['default_library=static'])
|
||||
if ngtcp2_crypto_quictls_dep.found()
|
||||
backend_deps_private += ngtcp2_crypto_quictls_dep
|
||||
backend_reqs_private += 'libngtcp2_crypto_quictls'
|
||||
else
|
||||
backend_deps_private += dependency('libngtcp2_crypto_ossl', default_options: ['default_library=static'])
|
||||
backend_reqs_private += 'libngtcp2_crypto_ossl'
|
||||
vala_flags += '--define=HAVE_NGTCP2_CRYPTO_OSSL'
|
||||
idun_dep = dependency('idun-1.0', default_options: ['default_library=static'])
|
||||
backend_reqs_private += 'idun-1.0'
|
||||
if idun_dep.type_name() == 'internal'
|
||||
idun_sp = subproject('idun')
|
||||
idun_libs += idun_sp.get_variable('idun')
|
||||
have_idund = idun_sp.get_variable('build_daemon')
|
||||
if have_idund
|
||||
idund_raw = idun_sp.get_variable('idund')
|
||||
if assets_mode == 'embedded'
|
||||
vala_flags += '--define=HAVE_EMBEDDED_IDUND'
|
||||
else
|
||||
vala_flags += '--define=HAVE_INSTALLED_IDUND'
|
||||
cdata.set_quoted('FRIDA_IDUND_NAME', 'idund' + exe_suffix)
|
||||
endif
|
||||
endif
|
||||
|
||||
if have_frida_glib
|
||||
libusb_dep = dependency('libusb-1.0', default_options: ['default_library=static'])
|
||||
else
|
||||
subproject('libusb', default_options: ['default_library=static'])
|
||||
libusb_dep = dependency('libusb-1.0', default_options: ['default_library=static'])
|
||||
endif
|
||||
backend_deps_private += libusb_dep
|
||||
backend_reqs_private += 'libusb-1.0'
|
||||
|
||||
lwip_options = [
|
||||
'default_library=static',
|
||||
'ipv4=disabled',
|
||||
'ipv6=enabled',
|
||||
'dns=disabled',
|
||||
'arp=disabled',
|
||||
'ethernet=enabled',
|
||||
]
|
||||
if optimize_for_prod
|
||||
lwip_options += 'lwip_debug=disabled'
|
||||
else
|
||||
lwip_options += 'lwip_debug=enabled'
|
||||
endif
|
||||
backend_deps_private += dependency('lwip', default_options: lwip_options)
|
||||
backend_reqs_private += 'lwip'
|
||||
endif
|
||||
idun_vapidir = idun_dep.get_variable('vapidir', default_value: '')
|
||||
if idun_vapidir != ''
|
||||
vala_flags += '--vapidir=' + idun_vapidir
|
||||
endif
|
||||
foreach pkg : idun_dep.get_variable('vala_packages', default_value: '').split()
|
||||
vala_flags += '--pkg=' + pkg
|
||||
endforeach
|
||||
endif
|
||||
|
||||
if quickjs_dep.found()
|
||||
@@ -775,8 +786,12 @@ endif
|
||||
|
||||
if host_os == 'android'
|
||||
termux_elf_cleaner = find_program('termux-elf-cleaner', native: true)
|
||||
libselinux_dep = dependency('libselinux', version: '>=3.0', default_options: ['default_library=static'])
|
||||
libsepol_dep = dependency('libsepol', version: '>=3.0', default_options: ['default_library=static'])
|
||||
selinux_options = [
|
||||
'default_library=static',
|
||||
'regex=disabled',
|
||||
]
|
||||
libselinux_dep = dependency('libselinux', version: '>=3.0', default_options: selinux_options)
|
||||
libsepol_dep = dependency('libsepol', version: '>=3.0', default_options: selinux_options)
|
||||
backend_deps_private += [libselinux_dep, libsepol_dep]
|
||||
backend_reqs_private += ['libselinux', 'libsepol']
|
||||
else
|
||||
@@ -980,12 +995,13 @@ compile_js_agent = files('tools' / 'compile-js-agent.py')
|
||||
|
||||
mapper_opt = get_option('mapper')
|
||||
if mapper_opt.auto()
|
||||
have_mapper = host_os_family == 'darwin'
|
||||
have_mapper = host_os_family in ['windows', 'darwin', 'linux']
|
||||
else
|
||||
have_mapper = mapper_opt.enabled()
|
||||
endif
|
||||
if have_mapper
|
||||
cdata.set('HAVE_MAPPER', 1)
|
||||
vala_flags += '--define=HAVE_MAPPER'
|
||||
endif
|
||||
|
||||
configure_file(
|
||||
|
||||
+1
-1
Submodule releng updated: 4601e8a75a...fe7d31dfbc.
@@ -359,5 +359,11 @@
|
||||
<true/>
|
||||
<key>com.apple.private.amfi.can-execute-cdhash</key>
|
||||
<true/>
|
||||
<key>com.apple.private.FairPlayIOKitUserClient.access</key>
|
||||
<true/>
|
||||
<key>com.apple.security.iokit-user-client-class</key>
|
||||
<array>
|
||||
<string>com_apple_driver_FairPlayIOKitUserClient</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida.Server {
|
||||
private static Application application;
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
[CCode (gir_namespace = "FridaAdb", gir_version = "1.0")]
|
||||
namespace Frida {
|
||||
public sealed class AdbDeviceTracker : Object {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public sealed class AgentContainer : Object, AgentSessionProvider {
|
||||
public DBusConnection connection {
|
||||
@@ -9,9 +11,7 @@ namespace Frida {
|
||||
[CCode (has_target = false)]
|
||||
private delegate void AgentMainFunc (string data, ref UnloadPolicy unload_policy, void * injector_state);
|
||||
private AgentMainFunc main_impl;
|
||||
#if LINUX
|
||||
private FileDescriptor agent_ctrlfd_for_peer;
|
||||
#else
|
||||
#if !LINUX
|
||||
private PipeTransport transport;
|
||||
#endif
|
||||
private string transport_address;
|
||||
@@ -38,8 +38,7 @@ namespace Frida {
|
||||
if (Posix.socketpair (Posix.AF_UNIX, Posix.SOCK_STREAM, 0, agent_ctrlfds) != 0)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to allocate socketpair");
|
||||
var agent_ctrlfd = new FileDescriptor (agent_ctrlfds[0]);
|
||||
container.agent_ctrlfd_for_peer = new FileDescriptor (agent_ctrlfds[1]);
|
||||
container.transport_address = "";
|
||||
container.transport_address = "socket:%d".printf (agent_ctrlfds[1]);
|
||||
|
||||
try {
|
||||
Socket socket = new Socket.from_fd (agent_ctrlfd.handle);
|
||||
@@ -114,18 +113,10 @@ namespace Frida {
|
||||
|
||||
private bool run () {
|
||||
UnloadPolicy unload_policy = IMMEDIATE;
|
||||
void * injector_state = null;
|
||||
|
||||
#if LINUX
|
||||
var s = LinuxInjectorState ();
|
||||
s.frida_ctrlfd = -1;
|
||||
s.agent_ctrlfd = agent_ctrlfd_for_peer.steal ();
|
||||
injector_state = &s;
|
||||
#endif
|
||||
|
||||
string agent_parameters = transport_address + "|exit-monitor:off|thread-suspend-monitor:off";
|
||||
|
||||
main_impl (agent_parameters, ref unload_policy, injector_state);
|
||||
main_impl (agent_parameters, ref unload_policy, null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public sealed class AndroidHelperClient : Object {
|
||||
public AndroidHelperTransport transport {
|
||||
|
||||
+1
-1
@@ -158,7 +158,7 @@ if is_shared
|
||||
else
|
||||
pkg_kwargs += {
|
||||
'libraries': core_public_lib,
|
||||
'requires_private': ['gmodule-2.0', 'gee-0.8', 'libsoup-3.0', 'frida-gum-1.0', 'frida-gumjs-inspector-1.0', 'libbrotlidec'] + backend_reqs_private,
|
||||
'requires_private': ['gmodule-2.0', 'gee-0.8', 'libsoup-3.0', 'efni-1.0', 'frida-gum-1.0', 'frida-gumjs-inspector-1.0', 'libbrotlidec'] + backend_reqs_private,
|
||||
'libraries_private': backend_libs_private,
|
||||
}
|
||||
endif
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public sealed class AgentConnection : Object, AsyncInitable {
|
||||
@@ -40,9 +42,9 @@ namespace Frida.Barebone {
|
||||
private Allocation config_allocation;
|
||||
private Allocation? agent_stack;
|
||||
|
||||
private Promise<bool>? listening;
|
||||
private Efni.Promise<bool>? listening;
|
||||
|
||||
private Gee.Map<uint16, Promise<Variant>> pending_requests = new Gee.HashMap<uint16, Promise<Variant>> ();
|
||||
private Gee.Map<uint16, Efni.Promise<Variant>> pending_requests = new Gee.HashMap<uint16, Efni.Promise<Variant>> ();
|
||||
private uint16 next_request_id = 1;
|
||||
|
||||
private const size_t AGENT_STACK_SIZE = 512 * 1024;
|
||||
@@ -71,11 +73,11 @@ namespace Frida.Barebone {
|
||||
this.kernel_notes = kernel_notes;
|
||||
}
|
||||
|
||||
public async void open (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void open (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
try {
|
||||
yield init_async (Priority.DEFAULT, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_api_error (e);
|
||||
Efni.throw_api_error (e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,7 +86,7 @@ namespace Frida.Barebone {
|
||||
* transport with it, such as the Linux kernel module exposing /dev/frida.
|
||||
*/
|
||||
public static async AgentConnection open_resident (IOStream stream, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var connection = new AgentConnection.resident ();
|
||||
|
||||
connection.adopt_hostlink_streams (stream);
|
||||
@@ -105,7 +107,7 @@ namespace Frida.Barebone {
|
||||
private const uint8 TRANSPORT_KIND_PIPE_VSOCK = 4;
|
||||
private const uint8 TRANSPORT_KIND_PIPE_GOLDFISH = 5;
|
||||
|
||||
private async bool init_async (int io_priority, Cancellable? cancellable) throws Error, IOError {
|
||||
private async bool init_async (int io_priority, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
progress ("Preparing the agent", 0.0);
|
||||
|
||||
var transport_tag = yield resolve_transport (cancellable);
|
||||
@@ -173,11 +175,11 @@ namespace Frida.Barebone {
|
||||
KernelFlavor flavor;
|
||||
if (kind == XNU) {
|
||||
if (kernel_base == 0)
|
||||
throw new Error.NOT_SUPPORTED ("Missing kernel_base");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Missing kernel_base");
|
||||
flavor = new XnuKernelFlavor (machine, kernel_base, symbols);
|
||||
} else if (kind == LINUX) {
|
||||
if (kernel_base == 0)
|
||||
throw new Error.NOT_SUPPORTED ("Missing kernel_base");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Missing kernel_base");
|
||||
flavor = new LinuxKernelFlavor (machine, kernel_base, symbols);
|
||||
} else if (kind == WIN9X) {
|
||||
flavor = new Win9xKernelFlavor (machine, symbols);
|
||||
@@ -193,7 +195,7 @@ namespace Frida.Barebone {
|
||||
try {
|
||||
elf = new Gum.ElfModule.from_blob (agent_config.image);
|
||||
} catch (Gum.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
throw new Efni.Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
}
|
||||
|
||||
var raw_elf = debugger.make_buffer (new Bytes (elf.get_file_data ()));
|
||||
@@ -240,7 +242,7 @@ namespace Frida.Barebone {
|
||||
return true;
|
||||
});
|
||||
if (start_address == 0)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid agent: no _start symbol found");
|
||||
throw new Efni.Error.INVALID_ARGUMENT ("Invalid agent: no _start symbol found");
|
||||
|
||||
XnuLayout? xnu_layout = null;
|
||||
bool xnu_reads_its_own_processes = symbols.has_key ("proc_pid")
|
||||
@@ -351,14 +353,14 @@ namespace Frida.Barebone {
|
||||
return true;
|
||||
}
|
||||
|
||||
private async void wait_to_be_greeted (Cancellable? cancellable) throws Error, IOError {
|
||||
private async void wait_to_be_greeted (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (listening == null)
|
||||
return;
|
||||
|
||||
var timeout = new TimeoutSource (GREETING_TIMEOUT_MS);
|
||||
timeout.set_callback (() => {
|
||||
if (!listening.future.ready)
|
||||
listening.reject (new Error.TIMED_OUT ("The agent never announced itself"));
|
||||
listening.reject (new Efni.Error.TIMED_OUT ("The agent never announced itself"));
|
||||
return false;
|
||||
});
|
||||
timeout.attach (MainContext.get_thread_default ());
|
||||
@@ -370,7 +372,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
}
|
||||
|
||||
private async Variant resolve_transport (Cancellable? cancellable) throws Error, IOError {
|
||||
private async Variant resolve_transport (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (agent_config.transport is BareboneHostlinkTransportConfig)
|
||||
return yield connect_virtio_transport ((BareboneHostlinkTransportConfig) agent_config.transport, cancellable);
|
||||
if (agent_config.transport is BareboneVsockTransportConfig) {
|
||||
@@ -407,11 +409,11 @@ namespace Frida.Barebone {
|
||||
new Variant.variant (new Variant.string (config.socket_path))
|
||||
});
|
||||
}
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported transport config");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unsupported transport config");
|
||||
}
|
||||
|
||||
private async void connect_serial_transport (BareboneSerialTransportConfig config,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
try {
|
||||
#if WINDOWS
|
||||
adopt_hostlink_streams (WindowsNamedPipe.open_client (config.path));
|
||||
@@ -421,20 +423,20 @@ namespace Frida.Barebone {
|
||||
cancellable));
|
||||
#endif
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.TRANSPORT ("Unable to reach the serial port of the guest: %s", e.message);
|
||||
throw new Efni.Error.TRANSPORT ("Unable to reach the serial port of the guest: %s", e.message);
|
||||
}
|
||||
|
||||
listening = new Promise<bool> ();
|
||||
listening = new Efni.Promise<bool> ();
|
||||
}
|
||||
|
||||
private async void open_pipe_listener (string path, Cancellable? cancellable) throws Error, IOError {
|
||||
private async void open_pipe_listener (string path, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
FileUtils.unlink (path);
|
||||
var service = new SocketService ();
|
||||
try {
|
||||
SocketAddress effective;
|
||||
service.add_address (new UnixSocketAddress (path), STREAM, DEFAULT, null, out effective);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.TRANSPORT ("Unable to listen on %s: %s", path, e.message);
|
||||
throw new Efni.Error.TRANSPORT ("Unable to listen on %s: %s", path, e.message);
|
||||
}
|
||||
service.incoming.connect ((connection) => {
|
||||
if (pipe_stream == null) {
|
||||
@@ -451,7 +453,7 @@ namespace Frida.Barebone {
|
||||
pipe_service = service;
|
||||
}
|
||||
|
||||
private async void establish_hostlink (Cancellable? cancellable) throws Error, IOError {
|
||||
private async void establish_hostlink (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (pipe_listener_wanted) {
|
||||
while (pipe_stream == null) {
|
||||
pipe_accept_handler = establish_hostlink.callback;
|
||||
@@ -482,7 +484,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async Variant connect_virtio_transport (BareboneHostlinkTransportConfig config, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var qmp = yield QmpClient.open (config.qmp, 0, cancellable);
|
||||
var link = yield qmp.open_hostlink (config.bus, cancellable);
|
||||
this.qmp = qmp;
|
||||
@@ -520,7 +522,7 @@ namespace Frida.Barebone {
|
||||
output = hostlink.get_output_stream ();
|
||||
}
|
||||
|
||||
private void rebase_layout (Layout layout, uint64 preferred_base, KernelRelocation reloc) throws Error {
|
||||
private void rebase_layout (Layout layout, uint64 preferred_base, KernelRelocation reloc) throws Efni.Error {
|
||||
foreach (var s in layout.symbols)
|
||||
s.offset = reloc.runtime_offset (preferred_base + s.offset);
|
||||
foreach (var m in layout.modules) {
|
||||
@@ -559,7 +561,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
}
|
||||
|
||||
private async void give_the_memory_back (Cancellable? cancellable) throws Error, IOError {
|
||||
private async void give_the_memory_back (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
var timeout = new TimeoutSource (LEAVE_INTERVAL_MS);
|
||||
timeout.set_callback (give_the_memory_back.callback);
|
||||
timeout.attach (MainContext.get_thread_default ());
|
||||
@@ -583,7 +585,7 @@ namespace Frida.Barebone {
|
||||
yield flavor.settle (cancellable);
|
||||
}
|
||||
|
||||
private async void wait_for_agent_to_leave (Cancellable? cancellable) throws Error, IOError {
|
||||
private async void wait_for_agent_to_leave (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (left_flag == 0)
|
||||
return;
|
||||
|
||||
@@ -615,13 +617,13 @@ namespace Frida.Barebone {
|
||||
// same data. The process receives the code as it is, thus only the writable half needs a new
|
||||
// location, in memory that both sides can address.
|
||||
public async void place_winnt_user_agent (uint pid, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var response = yield execute_command (Command.PLACE_AGENT_IN_PROCESS,
|
||||
new Variant.uint32 (pid), cancellable);
|
||||
if (!response.is_of_type (VariantType.UINT32))
|
||||
throw new Error.PROTOCOL ("Invalid place_agent_in_process response format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid place_agent_in_process response format");
|
||||
if (response.get_uint32 () == 0)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to place the agent in the process");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unable to place the agent in the process");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -629,7 +631,7 @@ namespace Frida.Barebone {
|
||||
* copy reports having woken up in.
|
||||
*/
|
||||
public async uint inject_agent_into_process (uint pid, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
if (kernel_kind == WINNT) {
|
||||
yield place_winnt_user_agent (pid, cancellable);
|
||||
return yield start_winnt_agent_in_process (pid, cancellable);
|
||||
@@ -641,12 +643,12 @@ namespace Frida.Barebone {
|
||||
// The copy cannot answer from the thread that starts it. Thus ask again until it reports its
|
||||
// process id.
|
||||
private async uint start_winnt_agent_in_process (uint pid, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
for (uint attempt = 0; attempt != INJECT_MAX_ATTEMPTS; attempt++) {
|
||||
var response = yield execute_command (Command.START_AGENT_IN_PROCESS,
|
||||
new Variant.uint32 (pid), cancellable);
|
||||
if (!response.is_of_type (VariantType.UINT32))
|
||||
throw new Error.PROTOCOL ("Invalid start_agent_in_process response format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid start_agent_in_process response format");
|
||||
|
||||
uint reached = response.get_uint32 ();
|
||||
if (reached != 0)
|
||||
@@ -659,19 +661,19 @@ namespace Frida.Barebone {
|
||||
timeout.destroy ();
|
||||
}
|
||||
|
||||
throw new Error.TIMED_OUT ("Timed out while starting the agent in the process");
|
||||
throw new Efni.Error.TIMED_OUT ("Timed out while starting the agent in the process");
|
||||
}
|
||||
|
||||
public signal void spawn_added (uint pid, string command_line, uint holder_pid);
|
||||
|
||||
public async Application[] enumerate_applications (string[] identifiers, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var selected = new VariantBuilder (new VariantType ("as"));
|
||||
foreach (unowned string identifier in identifiers)
|
||||
selected.add ("s", identifier);
|
||||
var response = yield execute_command (Command.ENUMERATE_APPLICATIONS, selected.end (), cancellable);
|
||||
if (!response.check_format_string ("a(sss)", false))
|
||||
throw new Error.PROTOCOL ("Invalid enumerate_applications response format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid enumerate_applications response format");
|
||||
|
||||
var applications = new Application[response.n_children ()];
|
||||
for (size_t i = 0; i != applications.length; i++) {
|
||||
@@ -686,11 +688,11 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async Shortcut[] enumerate_shortcuts (uint helper_pid, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var response = yield execute_command (Command.ENUMERATE_SHORTCUTS,
|
||||
new Variant.boolean (false), cancellable, helper_pid);
|
||||
if (!response.check_format_string ("a(ssss)", false))
|
||||
throw new Error.PROTOCOL ("Invalid enumerate_shortcuts response format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid enumerate_shortcuts response format");
|
||||
|
||||
var shortcuts = new Shortcut[response.n_children ()];
|
||||
for (size_t i = 0; i != shortcuts.length; i++) {
|
||||
@@ -737,59 +739,59 @@ namespace Frida.Barebone {
|
||||
}
|
||||
}
|
||||
|
||||
public async void gate_spawns (bool on, Cancellable? cancellable) throws Error, IOError {
|
||||
public async void gate_spawns (bool on, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
yield execute_command (Command.GATE_SPAWNS, new Variant.boolean (on), cancellable);
|
||||
}
|
||||
|
||||
public async void arm_named_spawn (string name, Cancellable? cancellable) throws Error, IOError {
|
||||
public async void arm_named_spawn (string name, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
yield execute_command (Command.ARM_NAMED_SPAWN, new Variant.string (name), cancellable);
|
||||
}
|
||||
|
||||
public async void disarm_named_spawn (string name, Cancellable? cancellable) throws Error, IOError {
|
||||
public async void disarm_named_spawn (string name, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
yield execute_command (Command.DISARM_NAMED_SPAWN, new Variant.string (name), cancellable);
|
||||
}
|
||||
|
||||
public async uint spawn_process (uint helper_pid, string command_line, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
return yield ask_for_a_spawn (helper_pid, new Variant.string (command_line), cancellable);
|
||||
}
|
||||
|
||||
// A kernel that starts programs itself takes the words as they are, since a line to be
|
||||
// split again is what Windows asks for and nothing else does.
|
||||
public async uint spawn_program (string[] words, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
return yield ask_for_a_spawn (0, new Variant.strv (words), cancellable);
|
||||
}
|
||||
|
||||
private async uint ask_for_a_spawn (uint helper_pid, Variant payload, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var response = yield execute_command (Command.SPAWN_PROCESS, payload, cancellable,
|
||||
helper_pid);
|
||||
if (!response.is_of_type (VariantType.UINT32))
|
||||
throw new Error.PROTOCOL ("Invalid spawn_process response format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid spawn_process response format");
|
||||
|
||||
return response.get_uint32 ();
|
||||
}
|
||||
|
||||
public async void resume_process (uint helper_pid, uint pid, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
yield execute_command (Command.RESUME_PROCESS, new Variant.uint32 (pid), cancellable,
|
||||
helper_pid);
|
||||
}
|
||||
|
||||
public async void detach_from_process (uint pid, Cancellable? cancellable) throws Error, IOError {
|
||||
public async void detach_from_process (uint pid, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
yield execute_command (Command.DETACH_FROM_PROCESS, new Variant.uint32 (pid), cancellable);
|
||||
}
|
||||
|
||||
public async uint inject_into_process (uint pid, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
// The agent cannot answer from the context that does the work. Thus it reports the result at
|
||||
// the next request.
|
||||
for (uint attempt = 0; attempt != INJECT_MAX_ATTEMPTS; attempt++) {
|
||||
var response = yield execute_command (Command.INJECT_INTO_PROCESS,
|
||||
new Variant.uint32 (pid), cancellable);
|
||||
if (!response.is_of_type (VariantType.UINT32))
|
||||
throw new Error.PROTOCOL ("Invalid inject_into_process response format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid inject_into_process response format");
|
||||
|
||||
uint reached = response.get_uint32 ();
|
||||
if (reached != 0)
|
||||
@@ -802,11 +804,11 @@ namespace Frida.Barebone {
|
||||
timeout.destroy ();
|
||||
}
|
||||
|
||||
throw new Error.TIMED_OUT ("Timed out while injecting into process");
|
||||
throw new Efni.Error.TIMED_OUT ("Timed out while injecting into process");
|
||||
}
|
||||
|
||||
public async HostProcessInfo[] enumerate_processes (Scope scope, uint[] pids, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
bool include_icons = scope == FULL;
|
||||
var selected = new VariantBuilder (new VariantType ("au"));
|
||||
foreach (uint pid in pids)
|
||||
@@ -814,7 +816,7 @@ namespace Frida.Barebone {
|
||||
var request = new Variant.tuple ({ new Variant.boolean (include_icons), selected.end () });
|
||||
var response = yield execute_command (Command.ENUMERATE_PROCESSES, request, cancellable);
|
||||
if (!response.check_format_string ("a(usssusuxaay)", false))
|
||||
throw new Error.PROTOCOL ("Invalid enumerate_processes response format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid enumerate_processes response format");
|
||||
|
||||
var processes = new HostProcessInfo[response.n_children ()];
|
||||
for (size_t i = 0; i != processes.length; i++) {
|
||||
@@ -1016,30 +1018,30 @@ namespace Frida.Barebone {
|
||||
private const uint32 MAX_ICON_DIMENSION = 256;
|
||||
|
||||
public async AgentScriptId create_script (string source, uint destination, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var payload = new Variant ("s", source);
|
||||
var response = yield execute_command (Command.CREATE_SCRIPT, payload, cancellable, destination);
|
||||
if (!response.check_format_string ("u", false))
|
||||
throw new Error.PROTOCOL ("Invalid create_script response format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid create_script response format");
|
||||
uint32 script_handle;
|
||||
response.get ("u", out script_handle);
|
||||
return AgentScriptId (script_handle);
|
||||
}
|
||||
|
||||
public async void load_script (AgentScriptId script_id, uint destination, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var payload = new Variant ("u", script_id.handle);
|
||||
yield execute_command (Command.LOAD_SCRIPT, payload, cancellable, destination);
|
||||
}
|
||||
|
||||
public async void destroy_script (AgentScriptId script_id, uint destination, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var payload = new Variant ("u", script_id.handle);
|
||||
yield execute_command (Command.DESTROY_SCRIPT, payload, cancellable, destination);
|
||||
}
|
||||
|
||||
public async void post_script_message (AgentScriptId script_id, string message, Bytes? data, uint destination,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
var payload = new Variant ("(us@ay)", script_id.handle, message,
|
||||
new Variant.from_bytes (new VariantType ("ay"), data ?? new Bytes (new uint8[0]), true));
|
||||
yield execute_command (Command.POST_SCRIPT_MESSAGE, payload, cancellable, destination);
|
||||
@@ -1047,26 +1049,26 @@ namespace Frida.Barebone {
|
||||
|
||||
// Zero selects the agent in the kernel. A different value selects a process that has a copy.
|
||||
private async Variant execute_command (Command command, Variant payload, Cancellable? cancellable,
|
||||
uint destination = 0) throws Error, IOError {
|
||||
uint destination = 0) throws Efni.Error, IOError {
|
||||
uint16 request_id = next_request_id++;
|
||||
|
||||
Bytes frame = frame_message (command, request_id, destination, payload);
|
||||
|
||||
var promise = new Promise<Variant> ();
|
||||
var promise = new Efni.Promise<Variant> ();
|
||||
pending_requests[request_id] = promise;
|
||||
|
||||
try {
|
||||
yield write_frame (frame, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
pending_requests.unset (request_id);
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
throw new Efni.Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
|
||||
var timeout_source = new TimeoutSource (COMMAND_TIMEOUT_MS);
|
||||
timeout_source.set_callback (() => {
|
||||
Promise<Variant>? p;
|
||||
Efni.Promise<Variant>? p;
|
||||
if (pending_requests.unset (request_id, out p))
|
||||
p.reject (new Error.TIMED_OUT ("The %s command timed out", command.to_nick ()));
|
||||
p.reject (new Efni.Error.TIMED_OUT ("The %s command timed out", command.to_nick ()));
|
||||
return Source.REMOVE;
|
||||
});
|
||||
timeout_source.attach (MainContext.get_thread_default ());
|
||||
@@ -1074,29 +1076,29 @@ namespace Frida.Barebone {
|
||||
Variant response = null;
|
||||
try {
|
||||
response = yield promise.future.wait_async (cancellable);
|
||||
} catch (Error e) {
|
||||
if (!(e is Error.TIMED_OUT))
|
||||
} catch (Efni.Error e) {
|
||||
if (!(e is Efni.Error.TIMED_OUT))
|
||||
throw e;
|
||||
string? fault = yield describe_agent_fault (cancellable);
|
||||
if (fault == null)
|
||||
throw e;
|
||||
throw new Error.TIMED_OUT ("%s; %s", e.message, fault);
|
||||
throw new Efni.Error.TIMED_OUT ("%s; %s", e.message, fault);
|
||||
} finally {
|
||||
timeout_source.destroy ();
|
||||
}
|
||||
|
||||
if (!response.check_format_string ("(bv)", false))
|
||||
throw new Error.PROTOCOL ("Malformed reply from agent");
|
||||
throw new Efni.Error.PROTOCOL ("Malformed reply from agent");
|
||||
bool succeeded;
|
||||
Variant detail;
|
||||
response.get ("(bv)", out succeeded, out detail);
|
||||
if (!succeeded)
|
||||
throw new Error.NOT_SUPPORTED ("%s", detail.get_string ());
|
||||
throw new Efni.Error.NOT_SUPPORTED ("%s", detail.get_string ());
|
||||
|
||||
return detail;
|
||||
}
|
||||
|
||||
private async string? describe_agent_fault (Cancellable? cancellable) throws Error, IOError {
|
||||
private async string? describe_agent_fault (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (fault_record == 0)
|
||||
return null;
|
||||
|
||||
@@ -1143,7 +1145,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async void process_incoming_messages () {
|
||||
reading = new Promise<bool> ();
|
||||
reading = new Efni.Promise<bool> ();
|
||||
try {
|
||||
while (true) {
|
||||
size_t header_size = 4;
|
||||
@@ -1171,7 +1173,7 @@ namespace Frida.Barebone {
|
||||
if (byte_order != ByteOrder.HOST)
|
||||
message = message.byteswap ();
|
||||
if (!message.check_format_string ("(yquv)", false))
|
||||
throw new Error.PROTOCOL ("Invalid message format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid message format");
|
||||
|
||||
uint8 command_code;
|
||||
uint16 request_id;
|
||||
@@ -1184,7 +1186,7 @@ namespace Frida.Barebone {
|
||||
listening.resolve (true);
|
||||
} else if (command_code == Command.SPAWN_ADDED) {
|
||||
if (!payload.check_format_string ("(us)", false))
|
||||
throw new Error.PROTOCOL ("Invalid spawn added payload format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid spawn added payload format");
|
||||
|
||||
uint32 pid;
|
||||
unowned string command_line;
|
||||
@@ -1193,7 +1195,7 @@ namespace Frida.Barebone {
|
||||
spawn_added (pid, command_line, destination);
|
||||
} else if (command_code == Command.SCRIPT_MESSAGE) {
|
||||
if (!payload.check_format_string ("(usay)", false))
|
||||
throw new Error.PROTOCOL ("Invalid script message payload format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid script message payload format");
|
||||
|
||||
uint32 script_handle;
|
||||
unowned string json;
|
||||
@@ -1218,7 +1220,7 @@ namespace Frida.Barebone {
|
||||
result = yield protect_memory (payload, io_cancellable);
|
||||
else
|
||||
result = yield patch_code (payload, io_cancellable);
|
||||
} catch (Error e) {
|
||||
} catch (Efni.Error e) {
|
||||
result = (command_code == Command.REMAP_WRITABLE_PAGES)
|
||||
? new Variant.uint64 (0)
|
||||
: new Variant.boolean (false);
|
||||
@@ -1228,7 +1230,7 @@ namespace Frida.Barebone {
|
||||
|
||||
yield send_reply (request_id, result);
|
||||
} else if (command_code == Command.REPLY) {
|
||||
Promise<Variant>? promise;
|
||||
Efni.Promise<Variant>? promise;
|
||||
if (pending_requests.unset (request_id, out promise))
|
||||
promise.resolve (payload);
|
||||
}
|
||||
@@ -1239,9 +1241,9 @@ namespace Frida.Barebone {
|
||||
reading.resolve (true);
|
||||
}
|
||||
|
||||
private Promise<bool>? reading;
|
||||
private Efni.Promise<bool>? reading;
|
||||
|
||||
private async void fill_until_n_bytes_available (size_t minimum) throws Error, IOError {
|
||||
private async void fill_until_n_bytes_available (size_t minimum) throws Efni.Error, IOError {
|
||||
size_t available = input.get_available ();
|
||||
while (available < minimum) {
|
||||
if (input.get_buffer_size () < minimum)
|
||||
@@ -1252,17 +1254,17 @@ namespace Frida.Barebone {
|
||||
n = yield input.fill_async ((ssize_t) (input.get_buffer_size () - available), Priority.DEFAULT,
|
||||
io_cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.TRANSPORT ("Connection closed");
|
||||
throw new Efni.Error.TRANSPORT ("Connection closed");
|
||||
}
|
||||
|
||||
if (n == 0)
|
||||
throw new Error.TRANSPORT ("Connection closed");
|
||||
throw new Efni.Error.TRANSPORT ("Connection closed");
|
||||
|
||||
available += n;
|
||||
}
|
||||
}
|
||||
|
||||
private async Variant remap_writable_pages (Variant payload, Cancellable? cancellable) throws Error, IOError {
|
||||
private async Variant remap_writable_pages (Variant payload, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
var physical_addresses = new Gee.ArrayList<uint64?> ();
|
||||
for (size_t i = 0; i != payload.n_children (); i++) {
|
||||
uint64 va = payload.get_child_value (i).get_uint64 ();
|
||||
@@ -1274,7 +1276,7 @@ namespace Frida.Barebone {
|
||||
return new Variant.uint64 (allocation.virtual_address);
|
||||
}
|
||||
|
||||
private async Variant protect_memory (Variant payload, Cancellable? cancellable) throws Error, IOError {
|
||||
private async Variant protect_memory (Variant payload, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
uint64 address;
|
||||
uint64 size;
|
||||
uint32 prot;
|
||||
@@ -1289,7 +1291,7 @@ namespace Frida.Barebone {
|
||||
// CTRR/KTRR locks kernel text read-only against the guest CPU, so the agent cannot patch it
|
||||
// even through a writable alias. The physical-memory bridge writes the backing store directly,
|
||||
// which the lock does not cover, letting us land hooks in kernel and kext text.
|
||||
private async Variant patch_code (Variant payload, Cancellable? cancellable) throws Error, IOError {
|
||||
private async Variant patch_code (Variant payload, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
uint64 va;
|
||||
Variant bytes_value;
|
||||
payload.get ("(t@ay)", out va, out bytes_value);
|
||||
@@ -1319,7 +1321,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async void write_frame (Bytes frame, Cancellable? cancellable) throws GLib.Error {
|
||||
var ours = new Promise<bool> ();
|
||||
var ours = new Efni.Promise<bool> ();
|
||||
var theirs = write_turn;
|
||||
write_turn = ours;
|
||||
|
||||
@@ -1340,7 +1342,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
}
|
||||
|
||||
private Promise<bool>? write_turn;
|
||||
private Efni.Promise<bool>? write_turn;
|
||||
|
||||
private Bytes frame_message (Command command, uint16 request_id, uint destination, Variant payload) {
|
||||
var message = new Variant ("(yquv)", (uint8) command, request_id, destination, payload);
|
||||
@@ -1380,7 +1382,7 @@ namespace Frida.Barebone {
|
||||
READY = 131;
|
||||
|
||||
public string to_nick () {
|
||||
return Marshal.enum_to_nick<Command> (this);
|
||||
return Efni.Marshal.enum_to_nick<Command> (this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,15 +9,12 @@ name = "frida_barebone_agent"
|
||||
path = "src/lib.rs"
|
||||
crate-type = ["rlib", "staticlib"]
|
||||
|
||||
# The XNU flavour is a freestanding executable the host injects and calls into.
|
||||
# The Linux flavour is the staticlib above, linked into a .ko by linux/Kbuild.
|
||||
[[bin]]
|
||||
name = "frida-barebone-agent"
|
||||
path = "src/main.rs"
|
||||
required-features = ["blob"]
|
||||
|
||||
[features]
|
||||
# Flavors the host injects and calls into, as opposed to the module Linux loads.
|
||||
blob = []
|
||||
# The XNU flavour itself, and the two ways it is packaged: a blob the host injects
|
||||
# into a running kernel, and a kext that kernel loads.
|
||||
@@ -26,7 +23,6 @@ xnu = ["xnu-core", "blob"]
|
||||
xnu-kext = ["xnu-core"]
|
||||
win9x = ["blob"]
|
||||
winnt = ["blob"]
|
||||
linux = []
|
||||
linux-injected = ["blob"]
|
||||
|
||||
[profile.release]
|
||||
|
||||
@@ -1,44 +1,36 @@
|
||||
## Getting the SDK and the devkit
|
||||
|
||||
The agent is built against the bare soft-float SDK, which carries picolibc and the
|
||||
compiler runtime, and against a GumJS devkit built for the same flavor. Which flavor
|
||||
is the target's, listed under Targets below; this walks through the XNU one. No
|
||||
prebuilt bundles are published for these, so build both:
|
||||
compiler runtime, and against a GumJS devkit built for the same machine. Which machine
|
||||
is the target's, listed under Targets below; this walks through the XNU one. The SDK
|
||||
is published prebuilt, and the devkit is built from frida-gum:
|
||||
|
||||
machine=none-arm64-softfloat_nopic
|
||||
machine=none-arm64-softfloat_pic
|
||||
|
||||
releng/deps.py build --bundle=sdk --host=$machine
|
||||
mkdir -p ~/sdk-$machine
|
||||
tar -C ~/sdk-$machine -xf deps/sdk-$machine.tar.xz
|
||||
sdk=~/sdk-$machine
|
||||
releng/deps.py sync sdk $machine $sdk
|
||||
|
||||
gum=~/src/frida-gum
|
||||
mkdir -p $gum/build/$machine
|
||||
(cd $gum/build/$machine \
|
||||
&& FRIDA_DEPS=$HOME/src/frida-core/deps CC=clang CXX=clang++ AR=llvm-ar \
|
||||
RANLIB=llvm-ranlib NM=llvm-nm STRIP=llvm-strip \
|
||||
../../configure --host=$machine --enable-gumjs \
|
||||
--with-devkits=gumjs \
|
||||
&& python3 ../../releng/meson/meson.py configure -Ddevkit_symbol_scope=original \
|
||||
&& ninja)
|
||||
&& CC=clang CXX=clang++ ../../configure --host=$machine --enable-gumjs \
|
||||
--with-devkits=gumjs --with-devkit-symbol-scope=original \
|
||||
&& make)
|
||||
|
||||
## Building
|
||||
|
||||
sdk=~/sdk-$machine
|
||||
export GUMJS_DEVKIT_DIR=$gum/build/$machine/bindings/gumjs/devkit
|
||||
export CC="clang -I$sdk/include -I$sdk/include/glib-2.0 -I$sdk/lib/glib-2.0/include \
|
||||
-I$sdk/include/capstone -I$sdk/include/json-glib-1.0"
|
||||
export RUSTFLAGS="-L native=$sdk/lib"
|
||||
|
||||
RUSTC_BOOTSTRAP=1 cargo build --release --features xnu \
|
||||
--target aarch64-unknown-none-softfloat --target-dir target-xnu \
|
||||
-Z build-std=core,alloc,compiler_builtins
|
||||
--target ./aarch64-unknown-none-pic.json -Z json-target-spec \
|
||||
--target-dir target-xnu -Z build-std=core,alloc,compiler_builtins
|
||||
|
||||
Rust names no bare-metal target for the Windows guests, so the agent spells them
|
||||
out and they are asked for by path:
|
||||
|
||||
RUSTC_BOOTSTRAP=1 cargo build --release --features win9x \
|
||||
--target ./i686-unknown-none-pic.json -Z json-target-spec \
|
||||
--target-dir target-win9x -Z build-std=core,alloc,compiler_builtins
|
||||
Every flavor names its target by path, as Rust has no built-in one for a
|
||||
position-independent bare-metal image. The others differ only in feature, machine
|
||||
and target spec, listed under Targets below.
|
||||
|
||||
## Targets
|
||||
|
||||
@@ -58,7 +50,7 @@ and NT's 64-bit half is called in the Microsoft ABI.
|
||||
|
||||
| Flavor | Machine | Rust target |
|
||||
|---------|-----------------------------------|----------------------------------|
|
||||
| `xnu` | `none-arm64-softfloat_nopic` | `aarch64-unknown-none-softfloat` |
|
||||
| `xnu` | `none-arm64-softfloat_pic` | `aarch64-unknown-none-pic.json` |
|
||||
| `win9x` | `none-x86-softfloat_pic` | `i686-unknown-none-pic.json` |
|
||||
| `winnt` | `none-x86-softfloat_pic` | `i686-unknown-none-pic.json` |
|
||||
| `winnt` | `none-x86_64-softfloat_pic_msabi` | `x86_64-unknown-none-pic.json` |
|
||||
@@ -72,7 +64,9 @@ half of Gum's platform backend (`gum_xnu.rs` / `gum_windows.rs` / `gum_linux.rs`
|
||||
## Development loop
|
||||
|
||||
export FRIDA_BAREBONE_CONFIG=$PWD/etc/xnu.json
|
||||
cargo build --release --features xnu --target aarch64-unknown-none-softfloat \
|
||||
RUSTC_BOOTSTRAP=1 cargo build --release --features xnu \
|
||||
--target ./aarch64-unknown-none-pic.json -Z json-target-spec \
|
||||
--target-dir target-xnu -Z build-std=core,alloc,compiler_builtins \
|
||||
&& make -C ~/src/frida-python \
|
||||
&& killall -9 qemu-system-aarch64 && sleep 2 \
|
||||
&& frida -D barebone -p 0
|
||||
|
||||
@@ -33,8 +33,22 @@ SECTIONS {
|
||||
_set_memory_x = .; . += 4;
|
||||
_set_memory_nx = .; . += 4;
|
||||
_modules = .; . += 4;
|
||||
_module_mutex = .; . += 4;
|
||||
_mutex_lock = .; . += 4;
|
||||
_mutex_unlock = .; . += 4;
|
||||
_register_module_notifier = .; . += 4;
|
||||
_unregister_module_notifier = .; . += 4;
|
||||
_frida_kmod_link_open = .; . += 4;
|
||||
_frida_kmod_link_close = .; . += 4;
|
||||
_frida_kmod_link_send = .; . += 4;
|
||||
_frida_kmod_link_recv = .; . += 4;
|
||||
_frida_kmod_link_pending = .; . += 4;
|
||||
_frida_kmod_exit_worker = .; . += 4;
|
||||
_frida_kmod_layout = .; . += 4;
|
||||
_frida_kmod_remap_writable = .; . += 4;
|
||||
_frida_kmod_unmap_writable = .; . += 4;
|
||||
_execmem_alloc_rw = .; . += 4;
|
||||
_vmalloc_noprof = .; . += 4;
|
||||
_execmem_free = .; . += 4;
|
||||
_module_alloc = .; . += 4;
|
||||
_module_memfree = .; . += 4;
|
||||
|
||||
@@ -33,8 +33,48 @@ SECTIONS {
|
||||
_set_memory_x = .; . += 8;
|
||||
_set_memory_nx = .; . += 8;
|
||||
_modules = .; . += 8;
|
||||
_module_mutex = .; . += 8;
|
||||
_mutex_lock = .; . += 8;
|
||||
_mutex_unlock = .; . += 8;
|
||||
_register_module_notifier = .; . += 8;
|
||||
_unregister_module_notifier = .; . += 8;
|
||||
_frida_kmod_link_open = .; . += 8;
|
||||
_frida_kmod_link_close = .; . += 8;
|
||||
_frida_kmod_link_send = .; . += 8;
|
||||
_frida_kmod_link_recv = .; . += 8;
|
||||
_frida_kmod_link_pending = .; . += 8;
|
||||
_frida_kmod_exit_worker = .; . += 8;
|
||||
_frida_kmod_layout = .; . += 8;
|
||||
_frida_kmod_remap_writable = .; . += 8;
|
||||
_frida_kmod_unmap_writable = .; . += 8;
|
||||
_execmem_alloc_rw = .; . += 8;
|
||||
_vmalloc_noprof = .; . += 8;
|
||||
_queued_spin_lock_slowpath = .; . += 8;
|
||||
_queued_read_lock_slowpath = .; . += 8;
|
||||
_queued_write_lock_slowpath = .; . += 8;
|
||||
_find_get_task_by_vpid = .; . += 8;
|
||||
___put_task_struct = .; . += 8;
|
||||
_task_join_group_stop = .; . += 8;
|
||||
___cleanup_sighand = .; . += 8;
|
||||
_kmem_cache_free = .; . += 8;
|
||||
_signal_cachep = .; . += 8;
|
||||
_put_files_struct = .; . += 8;
|
||||
_switch_task_namespaces = .; . += 8;
|
||||
_free_fs_struct = .; . += 8;
|
||||
___put_cred = .; . += 8;
|
||||
__raw_spin_lock = .; . += 8;
|
||||
__raw_spin_unlock = .; . += 8;
|
||||
_capable = .; . += 8;
|
||||
_show_map = .; . += 8;
|
||||
_show_smap = .; . += 8;
|
||||
_proc_fill_cache = .; . += 8;
|
||||
_proc_pid_status = .; . += 8;
|
||||
_do_task_stat = .; . += 8;
|
||||
_release_task = .; . += 8;
|
||||
_proc_task_instantiate = .; . += 8;
|
||||
_proc_fd_instantiate = .; . += 8;
|
||||
_proc_fdinfo_instantiate = .; . += 8;
|
||||
___arm64_sys_prctl = .; . += 8;
|
||||
_execmem_free = .; . += 8;
|
||||
_module_alloc = .; . += 8;
|
||||
_module_memfree = .; . += 8;
|
||||
|
||||
@@ -32,8 +32,22 @@ SECTIONS {
|
||||
_set_memory_x = .; . += 4;
|
||||
_set_memory_nx = .; . += 4;
|
||||
_modules = .; . += 4;
|
||||
_module_mutex = .; . += 4;
|
||||
_mutex_lock = .; . += 4;
|
||||
_mutex_unlock = .; . += 4;
|
||||
_register_module_notifier = .; . += 4;
|
||||
_unregister_module_notifier = .; . += 4;
|
||||
_frida_kmod_link_open = .; . += 4;
|
||||
_frida_kmod_link_close = .; . += 4;
|
||||
_frida_kmod_link_send = .; . += 4;
|
||||
_frida_kmod_link_recv = .; . += 4;
|
||||
_frida_kmod_link_pending = .; . += 4;
|
||||
_frida_kmod_exit_worker = .; . += 4;
|
||||
_frida_kmod_layout = .; . += 4;
|
||||
_frida_kmod_remap_writable = .; . += 4;
|
||||
_frida_kmod_unmap_writable = .; . += 4;
|
||||
_execmem_alloc_rw = .; . += 4;
|
||||
_vmalloc_noprof = .; . += 4;
|
||||
_text_poke_kgdb = .; . += 4;
|
||||
_execmem_free = .; . += 4;
|
||||
_module_alloc = .; . += 4;
|
||||
@@ -55,6 +69,7 @@ SECTIONS {
|
||||
_request_threaded_irq = .; . += 4;
|
||||
_vmap = .; . += 4;
|
||||
_current_task = .; . += 4;
|
||||
_pcpu_hot = .; . += 4;
|
||||
_ioremap = .; . += 4;
|
||||
_ioremap_nocache = .; . += 4;
|
||||
___default_kernel_pte_mask = .; . += 4;
|
||||
|
||||
@@ -33,8 +33,45 @@ SECTIONS {
|
||||
_set_memory_x = .; . += 8;
|
||||
_set_memory_nx = .; . += 8;
|
||||
_modules = .; . += 8;
|
||||
_module_mutex = .; . += 8;
|
||||
_mutex_lock = .; . += 8;
|
||||
_mutex_unlock = .; . += 8;
|
||||
_register_module_notifier = .; . += 8;
|
||||
_unregister_module_notifier = .; . += 8;
|
||||
_frida_kmod_link_open = .; . += 8;
|
||||
_frida_kmod_link_close = .; . += 8;
|
||||
_frida_kmod_link_send = .; . += 8;
|
||||
_frida_kmod_link_recv = .; . += 8;
|
||||
_frida_kmod_link_pending = .; . += 8;
|
||||
_frida_kmod_exit_worker = .; . += 8;
|
||||
_frida_kmod_layout = .; . += 8;
|
||||
_frida_kmod_remap_writable = .; . += 8;
|
||||
_frida_kmod_unmap_writable = .; . += 8;
|
||||
_execmem_alloc_rw = .; . += 8;
|
||||
_vmalloc_noprof = .; . += 8;
|
||||
_find_get_task_by_vpid = .; . += 8;
|
||||
___put_task_struct = .; . += 8;
|
||||
_task_join_group_stop = .; . += 8;
|
||||
___cleanup_sighand = .; . += 8;
|
||||
_kmem_cache_free = .; . += 8;
|
||||
_signal_cachep = .; . += 8;
|
||||
_put_files_struct = .; . += 8;
|
||||
_switch_task_namespaces = .; . += 8;
|
||||
_free_fs_struct = .; . += 8;
|
||||
___put_cred = .; . += 8;
|
||||
__raw_spin_lock = .; . += 8;
|
||||
__raw_spin_unlock = .; . += 8;
|
||||
_capable = .; . += 8;
|
||||
_show_map = .; . += 8;
|
||||
_show_smap = .; . += 8;
|
||||
_proc_fill_cache = .; . += 8;
|
||||
_proc_pid_status = .; . += 8;
|
||||
_do_task_stat = .; . += 8;
|
||||
_release_task = .; . += 8;
|
||||
_proc_task_instantiate = .; . += 8;
|
||||
_proc_fd_instantiate = .; . += 8;
|
||||
_proc_fdinfo_instantiate = .; . += 8;
|
||||
___x64_sys_prctl = .; . += 8;
|
||||
_text_poke_kgdb = .; . += 8;
|
||||
_execmem_free = .; . += 8;
|
||||
_module_alloc = .; . += 8;
|
||||
@@ -59,6 +96,8 @@ SECTIONS {
|
||||
_page_offset_base = .; . += 8;
|
||||
_current_task = .; . += 8;
|
||||
_cpu_current_top_of_stack = .; . += 8;
|
||||
___preempt_count = .; . += 8;
|
||||
_pcpu_hot = .; . += 8;
|
||||
_register_die_notifier = .; . += 8;
|
||||
_unregister_die_notifier = .; . += 8;
|
||||
_ioremap = .; . += 8;
|
||||
|
||||
@@ -47,8 +47,6 @@ fn main() {
|
||||
.write_to_file(out_dir.join("bindings.rs"))
|
||||
.expect("Couldn't write bindings");
|
||||
|
||||
// The Linux flavour is a staticlib: kbuild owns the final link, so it — not
|
||||
// us — decides where the devkit and libc archives come from.
|
||||
if env::var("CARGO_FEATURE_BLOB").is_ok() {
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
@@ -89,6 +87,10 @@ fn main() {
|
||||
// references the entrypoint the host calls; keep the linker from
|
||||
// dropping it along with the rest of the unreferenced archive members.
|
||||
println!("cargo:rustc-link-arg=--undefined=_start");
|
||||
if env::var("CARGO_FEATURE_LINUX_INJECTED").is_ok() {
|
||||
println!("cargo:rustc-link-arg=--undefined=frida_agent_wake");
|
||||
println!("cargo:rustc-link-arg=--undefined=frida_agent_stop");
|
||||
}
|
||||
|
||||
let note = compile_version_note(&out_dir, cc, &cc_args, arch);
|
||||
println!("cargo:rustc-link-arg={}", note.to_string_lossy());
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"agent": {
|
||||
"type": "resident",
|
||||
"transport": {
|
||||
"type": "socket",
|
||||
"path": "/tmp/frida-guest.sock"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"agent": {
|
||||
"type": "resident",
|
||||
"transport": {
|
||||
"type": "device",
|
||||
"path": "/dev/frida"
|
||||
|
||||
@@ -2,27 +2,15 @@ MODULE_NAME ?= frida-agent
|
||||
|
||||
obj-m += $(MODULE_NAME).o
|
||||
|
||||
# frida-agent-prelinked.o is not built from source here: it is the Rust agent, the
|
||||
# GumJS devkit and the C library collapsed into a single relocatable object by the prelink
|
||||
# step in Makefile. Copy it in through if_changed rather than kbuild's _shipped rule,
|
||||
# so the .cmd file modpost expects gets written.
|
||||
$(MODULE_NAME)-y := frida-kmod.o frida-agent-prelinked.o
|
||||
$(MODULE_NAME)-y := frida-kmod.o frida-agent-image.o
|
||||
|
||||
targets += frida-agent-prelinked.o
|
||||
AFLAGS_frida-agent-image.o := -DFRIDA_AGENT_IMAGE='"$(FRIDA_AGENT_IMAGE)"'
|
||||
|
||||
quiet_cmd_frida_agent_prelinked = COPY $@
|
||||
cmd_frida_agent_prelinked = cp $< $@
|
||||
frida-header-has = $(if $(shell grep -ls '$(2)' $(srctree)/$(1)),-D$(3))
|
||||
ccflags-y += $(call frida-header-has,include/linux/sched.h,list_head[[:space:]]*thread_group;,FRIDA_HAVE_TASK_THREAD_GROUP)
|
||||
ccflags-y += $(call frida-header-has,include/linux/sched/signal.h,int[[:space:]]*quick_threads;,FRIDA_HAVE_SIGNAL_QUICK_THREADS)
|
||||
ccflags-y += $(call frida-header-has,include/linux/fs_struct.h,spinlock_t[[:space:]]*lock;,FRIDA_HAVE_FS_STRUCT_LOCK)
|
||||
ccflags-y += $(call frida-header-has,arch/$(SRCARCH)/include/asm/current.h,struct[[:space:]]*pcpu_hot[[:space:]]*{,FRIDA_HAVE_PCPU_HOT)
|
||||
ccflags-y += $(call frida-header-has,arch/$(SRCARCH)/include/asm/processor.h,vl_onexec\[ARM64_VEC_MAX\],FRIDA_HAVE_VECTOR_LENGTHS)
|
||||
|
||||
$(obj)/frida-agent-prelinked.o: $(src)/frida-agent-prelinked.o.bin FORCE
|
||||
$(call if_changed,frida_agent_prelinked)
|
||||
|
||||
# On x86 objtool validates every object going into a module and generates its ORC
|
||||
# unwind tables. It was not built to read a JavaScript runtime linked against a
|
||||
# freestanding libc: it gives up on the first Rust switch jump table it cannot
|
||||
# follow, and the tables would only ever describe frames no kernel stack trace walks
|
||||
# into. Both the blob and the module object it is linked into have to be excused,
|
||||
# since objtool runs over each.
|
||||
OBJECT_FILES_NON_STANDARD_frida-agent-prelinked.o := y
|
||||
OBJECT_FILES_NON_STANDARD_$(MODULE_NAME).o := y
|
||||
|
||||
ccflags-y += $(FRIDA_KMOD_DEFS)
|
||||
$(obj)/frida-agent-image.o: $(FRIDA_AGENT_IMAGE)
|
||||
@@ -1,14 +1,3 @@
|
||||
# Builds frida-agent.ko in two steps, because kbuild insists on owning the final
|
||||
# link:
|
||||
#
|
||||
# 1. Collapse the Rust staticlib plus everything it needs — the GumJS devkit and
|
||||
# the C library — into one relocatable object, and localize every symbol except
|
||||
# the entry points frida-kmod.c calls. Localizing matters: Gum and the C library
|
||||
# define names like memcpy and malloc that must not leak into the kernel's
|
||||
# namespace.
|
||||
# 2. Hand that object to kbuild as a shipped object, so vermagic, modinfo and
|
||||
# CRCs all come from the target kernel's own build system.
|
||||
#
|
||||
# See README.md for usage.
|
||||
|
||||
# KDIR is the kernel to build against. When it is a source tree that was configured
|
||||
@@ -17,142 +6,44 @@
|
||||
KDIR ?= /lib/modules/$(shell uname -r)/build
|
||||
PROFILE ?= release
|
||||
|
||||
# The kernel's own name for the architecture, and the one Rust knows it by. Both
|
||||
# follow from the machine this is built for, which defaults to the one building.
|
||||
ARCH ?= $(shell uname -m \
|
||||
| sed -e 's/^i[3-6]86$$/x86/' -e 's/^x86_64$$/x86/' -e 's/^aarch64$$/arm64/')
|
||||
BITS ?= $(shell uname -m \
|
||||
| sed -e 's/^i[3-6]86$$/32/' -e 's/^x86_64$$/64/' -e 's/^aarch64$$/64/')
|
||||
|
||||
ifeq ($(ARCH)-$(BITS),x86-32)
|
||||
RUST_TARGET ?= i686-unknown-none
|
||||
AGENT_TARGET_FLAGS ?= --target $(AGENT_DIR)/$(RUST_TARGET).json -Z json-target-spec
|
||||
RUST_TARGET ?= i686-unknown-none-pic
|
||||
SDK_ARCH ?= x86
|
||||
TOOLPREFIX ?=
|
||||
AGENT_LD_EMULATION ?= elf_i386
|
||||
AGENT_RUSTFLAGS ?=
|
||||
AGENT_CFLAGS ?= --target=i386-none-elf
|
||||
else ifeq ($(ARCH)-$(BITS),x86-64)
|
||||
RUST_TARGET ?= x86_64-unknown-none
|
||||
RUST_TARGET ?= x86_64-unknown-none-pic
|
||||
SDK_ARCH ?= x86_64
|
||||
TOOLPREFIX ?=
|
||||
AGENT_LD_EMULATION ?= elf_x86_64
|
||||
AGENT_RUSTFLAGS ?= -Zcf-protection=branch -Cllvm-args=--min-jump-table-entries=65536
|
||||
AGENT_CFLAGS ?= --target=x86_64-none-elf -fcf-protection=branch -fno-jump-tables
|
||||
else ifeq ($(ARCH)-$(BITS),arm64-64)
|
||||
RUST_TARGET ?= aarch64-unknown-none
|
||||
RUST_TARGET ?= aarch64-unknown-none-pic
|
||||
SDK_ARCH ?= arm64
|
||||
TOOLPREFIX ?= aarch64-none-elf-
|
||||
AGENT_RUSTFLAGS ?=
|
||||
AGENT_CFLAGS ?= --target=aarch64-none-elf -ffixed-x18
|
||||
else
|
||||
$(error Unsupported ARCH "$(ARCH)" and BITS "$(BITS)"; this flavour covers \
|
||||
$(error Unsupported ARCH "$(ARCH)" and BITS "$(BITS)"; this flavor covers \
|
||||
x86/32, x86/64 and arm64/64)
|
||||
endif
|
||||
AGENT_TARGET_FLAGS ?= --target $(RUST_TARGET)
|
||||
|
||||
AGENT_DIR := $(CURDIR)/..
|
||||
AGENT_TARGET_DIR := $(AGENT_DIR)/target-linux
|
||||
AGENT_LIB := $(AGENT_TARGET_DIR)/$(RUST_TARGET)/$(PROFILE)/libfrida_barebone_agent.a
|
||||
|
||||
FRIDA_SDK ?= $(error Set FRIDA_SDK to a none-$(SDK_ARCH)-softfloat_pic SDK prefix)
|
||||
GUMJS_DEVKIT_DIR ?= $(error Set GUMJS_DEVKIT_DIR to the directory holding libfrida-gumjs.a)
|
||||
|
||||
# Any binutils that speak the target's ELF will do; nothing here is compiled with
|
||||
# them. Building for the host's own architecture, that is the host's binutils.
|
||||
AGENT_LD ?= $(TOOLPREFIX)ld
|
||||
AGENT_AR ?= $(TOOLPREFIX)ar
|
||||
AGENT_NM ?= $(TOOLPREFIX)nm
|
||||
AGENT_OBJCOPY ?= $(TOOLPREFIX)objcopy
|
||||
|
||||
# The ABI the kernel imposes on anything linked into it. The flags only reach crates
|
||||
# Cargo compiles, and the toolchain ships core, alloc and compiler_builtins prebuilt
|
||||
# without them, so those three have to be rebuilt from source as well. That needs the
|
||||
# rust-src component and an unstable flag, hence RUSTC_BOOTSTRAP.
|
||||
#
|
||||
# On x86_64 that is endbr64 on every address-taken function, since a kernel with
|
||||
# CONFIG_X86_KERNEL_IBT faults an indirect call landing anywhere else, and switches
|
||||
# compiled as comparisons rather than jump tables, which objtool cannot follow into.
|
||||
# Soft-float, the kernel code model and the absent red zone come with the target
|
||||
# itself. On arm64
|
||||
# it is x18, which the kernel reserves for the shadow call stack, so nothing linked in
|
||||
# may use it as a general-purpose register.
|
||||
ifeq ($(ARCH)-$(BITS),x86-32)
|
||||
AGENT_RUSTFLAGS ?=
|
||||
AGENT_CFLAGS ?= --target=i386-none-elf \
|
||||
-Xclang -target-feature -Xclang +soft-float -mno-mmx -mno-sse \
|
||||
-mlong-double-64 -fno-pic --sysroot=$(FRIDA_SDK)
|
||||
else ifeq ($(ARCH)-$(BITS),x86-64)
|
||||
AGENT_RUSTFLAGS ?= -Zcf-protection=branch \
|
||||
-Cllvm-args=--min-jump-table-entries=65536 \
|
||||
-Crelocation-model=static -Zplt=yes
|
||||
AGENT_CFLAGS ?= --target=x86_64-none-elf \
|
||||
-Xclang -target-feature -Xclang +soft-float -mno-mmx -mno-sse -mno-red-zone \
|
||||
-mlong-double-64 -mcmodel=kernel -fcf-protection=branch -fno-jump-tables \
|
||||
-fno-pic --sysroot=$(FRIDA_SDK)
|
||||
else
|
||||
AGENT_RUSTFLAGS ?= -Zfixed-x18
|
||||
AGENT_CFLAGS ?= --target=aarch64-none-elf -mabi=aapcs-soft -mgeneral-regs-only \
|
||||
-ffixed-x18 -fno-pic --sysroot=$(FRIDA_SDK)
|
||||
endif
|
||||
AGENT_BUILD_STD ?= -Z build-std=core,alloc,compiler_builtins
|
||||
|
||||
# The devkit header reaches for GLib's and capstone's, which a devkit built against
|
||||
# an SDK leaves in that SDK rather than beside itself. The sysroot's own include
|
||||
# directory is spelled out because only clang's bare-metal driver, which it selects
|
||||
# for arm64 but not for x86_64, searches there on its own.
|
||||
AGENT_CFLAGS += -isystem $(FRIDA_SDK)/include \
|
||||
AGENT_CC ?= clang
|
||||
AGENT_CFLAGS += -I$(FRIDA_SDK)/include \
|
||||
-I$(FRIDA_SDK)/include/glib-2.0 \
|
||||
-I$(FRIDA_SDK)/lib/glib-2.0/include \
|
||||
-I$(FRIDA_SDK)/include/capstone \
|
||||
-I$(FRIDA_SDK)/include/json-glib-1.0
|
||||
|
||||
# The soft-float SDK, whose libc is picolibc and whose compiler runtime is
|
||||
# compiler-rt. GCC's own newlib and libgcc are hard-float and cannot be used here.
|
||||
FRIDA_SDK ?= $(error Set FRIDA_SDK to a none-$(SDK_ARCH)-softfloat_nopic SDK prefix)
|
||||
LIBM = $(FRIDA_SDK)/lib/libm.a
|
||||
LIBBUILTINS = $(FRIDA_SDK)/lib/libclang_rt.builtins.a
|
||||
|
||||
# bindgen reads the devkit header through whatever this compiler reports as its
|
||||
# include path, so it has to be one that knows where the SDK keeps its libc. It has
|
||||
# to be clang either way: -mabi=aapcs-soft landed there in 19 and GCC has it at no
|
||||
# version, and on x86_64 the soft-float ABI is reachable only as an -Xclang feature.
|
||||
AGENT_CC ?= clang
|
||||
|
||||
PRELINKED := frida-agent-prelinked.o.bin
|
||||
|
||||
AGENT_LIB_SOFTFLOAT := $(AGENT_TARGET_DIR)/libfrida-barebone-agent-softfloat.a
|
||||
|
||||
# On arm64 the toolchain's own compiler_builtins is compiled for the ordinary AAPCS,
|
||||
# where a double travels in d0: its __muldf3 opens with "fmov x14, d0". Everything
|
||||
# else there is -mabi=aapcs-soft and passes that same double in x0. Whole-archiving
|
||||
# the staticlib puts those definitions in front of the SDK's compiler-rt, which has
|
||||
# the soft-float ones, and every double the C library computes then comes back
|
||||
# garbage — quietly, since the arithmetic still runs. Drop them and let compiler-rt
|
||||
# answer instead. x86_64 needs none of this: soft-float is part of the Rust target
|
||||
# there, so the copy build-std compiles already passes doubles the way the C half
|
||||
# expects.
|
||||
ifeq ($(ARCH),arm64)
|
||||
DROP_HARDFLOAT_BUILTINS = \
|
||||
$(AGENT_AR) d $@ $$($(AGENT_NM) --print-armap $@ | sed -n 's/^__muldf3 in //p') && \
|
||||
$(AGENT_AR) s $@
|
||||
else
|
||||
DROP_HARDFLOAT_BUILTINS = :
|
||||
endif
|
||||
|
||||
# The agent puts malloc, the C library's locks and its process stubs on the kernel's
|
||||
# own facilities, so picolibc's versions are duplicate definitions rather than
|
||||
# fallbacks. Work out which members they live in instead of naming them: the set
|
||||
# follows from what the agent defines, and picolibc moves things between releases.
|
||||
LIBC := $(AGENT_TARGET_DIR)/libc-trimmed.a
|
||||
|
||||
ENTRY_POINTS := frida_agent_start frida_agent_stop \
|
||||
gum_interceptor_obtain gum_interceptor_begin_transaction \
|
||||
gum_interceptor_end_transaction gum_interceptor_replace \
|
||||
gum_interceptor_revert
|
||||
KEEP_GLOBAL := $(foreach s,$(ENTRY_POINTS),--keep-global-symbol=$(s))
|
||||
|
||||
# Two structures the reparenting path reaches into lost a member upstream, and
|
||||
# neither change announces itself as anything frida-kmod.c could test for. The
|
||||
# headers it is compiled against are the authority on both, so ask them.
|
||||
FRIDA_KMOD_DEFS := \
|
||||
$(if $(shell grep -l 'list_head[[:space:]]*thread_group;' \
|
||||
$(KDIR)/include/linux/sched.h),-DFRIDA_HAVE_TASK_THREAD_GROUP) \
|
||||
$(if $(shell grep -l 'spinlock_t[[:space:]]*lock;' \
|
||||
$(KDIR)/include/linux/fs_struct.h),-DFRIDA_HAVE_FS_STRUCT_LOCK)
|
||||
AGENT_DIR := $(CURDIR)/..
|
||||
AGENT_TARGET_DIR := $(AGENT_DIR)/target-linux-$(SDK_ARCH)
|
||||
AGENT_IMAGE := frida-agent.elf
|
||||
|
||||
all: modules
|
||||
|
||||
@@ -160,47 +51,24 @@ all: modules
|
||||
# next boot, so bring-up wants a fresh one per attempt.
|
||||
MODULE_NAME ?= frida-agent
|
||||
|
||||
modules: $(PRELINKED)
|
||||
modules: $(AGENT_IMAGE)
|
||||
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) \
|
||||
FRIDA_KMOD_DEFS="$(FRIDA_KMOD_DEFS)" MODULE_NAME=$(MODULE_NAME) modules
|
||||
MODULE_NAME=$(MODULE_NAME) FRIDA_AGENT_IMAGE=$(CURDIR)/$(AGENT_IMAGE) modules
|
||||
|
||||
$(PRELINKED): $(AGENT_LIB_SOFTFLOAT) $(LIBC) prelink.lds
|
||||
$(AGENT_LD) -r $(if $(AGENT_LD_EMULATION),-m $(AGENT_LD_EMULATION)) \
|
||||
-T prelink.lds -o $@.tmp \
|
||||
--whole-archive $(AGENT_LIB_SOFTFLOAT) --no-whole-archive \
|
||||
--start-group \
|
||||
$(GUMJS_DEVKIT_DIR)/libfrida-gumjs.a \
|
||||
$(LIBC) \
|
||||
$(LIBM) \
|
||||
$(LIBBUILTINS) \
|
||||
--end-group
|
||||
$(AGENT_OBJCOPY) $(KEEP_GLOBAL) --strip-debug \
|
||||
--remove-section=.llvmbc --remove-section=.llvmcmd $@.tmp $@
|
||||
rm -f $@.tmp
|
||||
|
||||
$(AGENT_LIB_SOFTFLOAT): agent
|
||||
cp $(AGENT_LIB) $@
|
||||
$(DROP_HARDFLOAT_BUILTINS)
|
||||
|
||||
$(LIBC): export LC_ALL = C
|
||||
$(LIBC): $(AGENT_LIB_SOFTFLOAT)
|
||||
cp $(FRIDA_SDK)/lib/libc.a $@
|
||||
$(AGENT_NM) --defined-only $(AGENT_LIB_SOFTFLOAT) \
|
||||
| awk 'NF == 3 && $$2 ~ /^[TDBR]$$/ { print $$3 }' | sort -u > $@.agent-syms
|
||||
$(AGENT_NM) --print-armap $@ | sed -n 's/^\([^ ]*\) in \(.*\)$$/\1 \2/p' | sort -u > $@.members
|
||||
$(AGENT_AR) d $@ $$(join $@.agent-syms $@.members | awk '{ print $$2 }' | sort -u)
|
||||
$(AGENT_AR) s $@
|
||||
rm -f $@.agent-syms $@.members
|
||||
$(AGENT_IMAGE): agent
|
||||
cp $(AGENT_TARGET_DIR)/$(RUST_TARGET)/$(PROFILE)/frida-barebone-agent $@
|
||||
|
||||
agent:
|
||||
cd $(AGENT_DIR) && RUSTC_BOOTSTRAP=1 RUSTFLAGS="$(AGENT_RUSTFLAGS)" \
|
||||
CC_$(subst -,_,$(RUST_TARGET))="$(AGENT_CC) $(AGENT_CFLAGS)" \
|
||||
cargo build --$(PROFILE) \
|
||||
$(AGENT_TARGET_FLAGS) --features linux \
|
||||
--target-dir $(AGENT_TARGET_DIR) $(AGENT_BUILD_STD)
|
||||
cd $(AGENT_DIR) && RUSTC_BOOTSTRAP=1 \
|
||||
RUSTFLAGS="-L native=$(FRIDA_SDK)/lib $(AGENT_RUSTFLAGS)" \
|
||||
CC="$(AGENT_CC) $(AGENT_CFLAGS)" \
|
||||
GUMJS_DEVKIT_DIR=$(GUMJS_DEVKIT_DIR) \
|
||||
cargo build --$(PROFILE) --features linux-injected \
|
||||
--target ./$(RUST_TARGET).json -Z json-target-spec \
|
||||
--target-dir $(AGENT_TARGET_DIR) -Z build-std=core,alloc,compiler_builtins
|
||||
|
||||
clean:
|
||||
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) clean
|
||||
rm -f $(PRELINKED) $(PRELINKED).tmp $(AGENT_LIB_SOFTFLOAT) $(LIBC)
|
||||
rm -f $(AGENT_IMAGE)
|
||||
|
||||
.PHONY: all modules agent clean
|
||||
+127
-217
@@ -1,291 +1,201 @@
|
||||
# Linux kernel module
|
||||
|
||||
The same agent that the Barebone backend injects into XNU from the outside, but
|
||||
loaded from the inside as an ordinary kernel module.
|
||||
The agent the Barebone backend injects into a Linux guest from the outside,
|
||||
loaded from the inside instead. The module is a loader and nothing more: it
|
||||
carries the same `linux-injected` image a hypervisor would write into guest
|
||||
memory, maps and relocates it, and starts it. Everything past that point is the
|
||||
one implementation, whichever way the agent got in: the GumJS runtime, process
|
||||
and module enumeration, cloaking, the userspace injector.
|
||||
|
||||
## How it differs from the XNU flavor
|
||||
## What the loader does
|
||||
|
||||
| | XNU | Linux |
|
||||
|-------------------|--------------------------------------------------|-------------------------------------------|
|
||||
| Delivery | remote stub injects a freestanding ELF | `insmod frida-agent.ko` |
|
||||
| Kernel primitives | addresses patched into `.kernel_addrs` by the host | direct calls into `frida-kmod.c` |
|
||||
| Bootstrap config | GVariant blob written into guest memory | module parameters |
|
||||
| Transport | virtio hostlink or vsock | `/dev/frida` character device |
|
||||
| Page permissions | host rewrites descriptors over the hostlink | `set_memory_*()` |
|
||||
| Writable alias | shadow copy, committed via physical-memory bridge | `vmap()` of the same pages |
|
||||
| Symbols | table supplied by the host | kallsyms |
|
||||
| | From the outside | From the inside (`frida-agent.ko`) |
|
||||
|--------------------|-----------------------------------------------|--------------------------------------------------|
|
||||
| Delivery | host writes the image into guest memory | `insmod`; the image is embedded in the module |
|
||||
| Relocation | host applies `.rela.dyn` | loader applies `.rela.dyn` (`.rel.dyn` on x86) |
|
||||
| `.kernel_addrs` | host fills the slots from its symbol source | loader fills them from kallsyms |
|
||||
| Struct layouts | BTF | `offsetof()` table from the module's headers, BTF behind it |
|
||||
| Symbol table | supplied by the host | built by the loader from kallsyms |
|
||||
| Transport | virtio hostlink or vsock | `/dev/frida` character device |
|
||||
| Writable alias | host rewrites page tables | `vmap()` of the same pages |
|
||||
| Lifetime | ends when the host detaches | resident until `rmmod` |
|
||||
|
||||
Because the Linux agent performs its own memory work, it never issues the
|
||||
`RemapWritablePages`, `MemoryProtect` or `PatchCode` host RPCs. The wire protocol
|
||||
is otherwise identical, so the host side sees the same `(yqv)` frames.
|
||||
The configuration the agent starts from is the same GVariant either way; the
|
||||
loader writes one naming the character device as its transport. A slot the
|
||||
loader fills that the host never does — the link functions, the layout table,
|
||||
the writable alias — is what tells the agent it was loaded from the inside.
|
||||
|
||||
## Building
|
||||
|
||||
Four inputs: this repository, a GumJS devkit built for
|
||||
`none-<arch>-softfloat_nopic`, the SDK it was built against, and the kernel the module is for. `Makefile`
|
||||
covers x86_64 and arm64, and defaults to the architecture of the machine building.
|
||||
|
||||
Building for the running kernel is a good deal shorter than cross-building for a
|
||||
device, so that walkthrough comes first; the arm64 one below it is the same steps
|
||||
with the kernel obtained from elsewhere.
|
||||
Three inputs besides this repository: a GumJS devkit for
|
||||
`none-<arch>-softfloat_pic`, the SDK it was built against, and the kernel the
|
||||
module is for. Building the image needs no kernel and runs anywhere; only the
|
||||
kbuild step needs the kernel's build tree.
|
||||
|
||||
### Toolchain
|
||||
|
||||
rustup target add x86_64-unknown-none # or aarch64-unknown-none
|
||||
rustup component add rust-src
|
||||
sudo dnf install clang binutils kernel-devel-$(uname -r)
|
||||
rustup toolchain install nightly
|
||||
rustup component add rust-src --toolchain nightly
|
||||
|
||||
clang must be 19 or newer, since that is where `-mabi=aapcs-soft` landed; GCC does
|
||||
not implement it at any version, nor does it reach x86_64's soft-float ABI. The
|
||||
binutils are for the prelink, which reads the archive map that only GNU `nm` prints —
|
||||
the LLVM one does not; cross-building wants the target's, natively the host's own
|
||||
will do. The host compiler is for Cargo's own build scripts, which run on the machine
|
||||
doing the building.
|
||||
The image links with `rust-lld`; nothing GNU is involved. clang has to be 19 or
|
||||
newer, which is where `-mabi=aapcs-soft` landed. The kbuild step wants whatever
|
||||
compiler the kernel was built with.
|
||||
|
||||
The commands below are run from the top of this repository, with `releng` checked
|
||||
out — `git submodule update --init releng` if the clone did not recurse.
|
||||
### The devkit and the SDK
|
||||
|
||||
### For the running x86_64 kernel
|
||||
version=17.26.2
|
||||
arch=arm64 # or x86_64
|
||||
base=https://github.com/frida/frida/releases/download/$version
|
||||
curl -LO $base/frida-gumjs-devkit-$version-none-$arch-softfloat_pic.tar.xz
|
||||
mkdir -p ~/gumjs-devkit
|
||||
tar -C ~/gumjs-devkit -xf frida-gumjs-devkit-$version-none-$arch-softfloat_pic.tar.xz
|
||||
|
||||
No prebuilt bundles are published for `none-x86_64-softfloat_nopic`, so both inputs are
|
||||
built locally, against the same SDK:
|
||||
releng/deps.py sync sdk none-$arch-softfloat_pic ~/sdk-none-$arch-softfloat_pic
|
||||
|
||||
releng/deps.py build --bundle=sdk --host=none-x86_64-softfloat_nopic
|
||||
mkdir -p ~/sdk-none-x86_64-softfloat_nopic
|
||||
tar -C ~/sdk-none-x86_64-softfloat_nopic -xf deps/sdk-none-x86_64-softfloat_nopic.tar.xz
|
||||
`releng/deps.toml` pins which SDK a devkit was built against, and mixing the two
|
||||
leaves symbols undefined at link time.
|
||||
|
||||
gum=subprojects/frida-gum
|
||||
mkdir -p $gum/build/none-x86_64-softfloat_nopic
|
||||
(cd $gum/build/none-x86_64-softfloat_nopic \
|
||||
&& FRIDA_DEPS=$PWD/../../../../deps ../../configure \
|
||||
--host=none-x86_64-softfloat_nopic \
|
||||
--enable-gumjs \
|
||||
--with-devkits=gumjs \
|
||||
--with-devkit-symbol-scope=original \
|
||||
&& make)
|
||||
### For the running kernel
|
||||
|
||||
make -C src/barebone/agent/linux \
|
||||
FRIDA_SDK=$HOME/sdk-none-x86_64-softfloat_nopic \
|
||||
GUMJS_DEVKIT_DIR=$PWD/$gum/build/none-x86_64-softfloat_nopic/bindings/gumjs/devkit
|
||||
FRIDA_SDK=$HOME/sdk-none-$arch-softfloat_pic \
|
||||
GUMJS_DEVKIT_DIR=$HOME/gumjs-devkit
|
||||
|
||||
Everything else — `KDIR`, the Rust target, the binutils, the kernel's own name for the
|
||||
architecture — follows from the running kernel.
|
||||
`KDIR`, the Rust target and the kernel's name for the architecture all follow
|
||||
from the running kernel. `make frida-agent.elf` stops after the image, which is
|
||||
also what the Barebone backend injects from the outside.
|
||||
|
||||
### For an arm64 device
|
||||
### For an Android device
|
||||
|
||||
Here there are published bundles to start from, and the kernel has to be assembled.
|
||||
|
||||
#### The devkit and the SDK
|
||||
|
||||
version=17.17.0
|
||||
base=https://github.com/frida/frida/releases/download/$version
|
||||
curl -LO $base/frida-gumjs-devkit-$version-none-arm64-softfloat_nopic.tar.xz
|
||||
mkdir -p ~/gumjs-devkit
|
||||
tar -C ~/gumjs-devkit -xf frida-gumjs-devkit-$version-none-arm64-softfloat_nopic.tar.xz
|
||||
|
||||
releng/deps.py sync sdk none-arm64-softfloat_nopic ~/sdk-none-arm64-softfloat_nopic
|
||||
|
||||
The SDK carries picolibc and the compiler-rt builtins the devkit expects to be linked
|
||||
against, and `releng/deps.toml` pins which one. Mixing a devkit with an SDK built for
|
||||
a different libc leaves undefined symbols at prelink time.
|
||||
|
||||
#### The kernel
|
||||
|
||||
Nothing here needs the device's own kernel to be built, but the config alone is not
|
||||
enough either. `uname -r` ends in the GKI commit and the build number:
|
||||
The image is built the same way; it is the kernel tree that takes assembling.
|
||||
`uname -r` ends in the GKI commit and the build number:
|
||||
|
||||
adb shell uname -r
|
||||
6.1.145-android14-11-gc1de4747ac59-ab14219743
|
||||
6.6.102-android15-8-g6eb5b2a8c46b-ab14739656-4k
|
||||
|
||||
which is commit `c1de4747ac59` and build `14219743`. Three pieces are keyed to them:
|
||||
which is commit `6eb5b2a8c46b` and build `14739656`. The source comes from the
|
||||
commit, the CRCs `CONFIG_MODVERSIONS` checks from what CI published for the
|
||||
build, and the config from the device itself:
|
||||
|
||||
commit=c1de4747ac59
|
||||
build=14219743
|
||||
commit=6eb5b2a8c46b
|
||||
build=14739656
|
||||
ci=https://ci.android.com/builds/submitted/$build/kernel_aarch64/latest/raw
|
||||
|
||||
mkdir -p ~/kernel-prepared ~/kernel-source
|
||||
curl -sSL $ci/modules_prepare_outdir.tar.gz | tar -xz -C ~/kernel-prepared
|
||||
curl -sSL $ci/kernel_aarch64_Module.symvers -o ~/kernel-prepared/Module.symvers
|
||||
mkdir -p ~/kernel
|
||||
curl -sSL https://android.googlesource.com/kernel/common/+archive/$commit.tar.gz \
|
||||
| tar -xz -C ~/kernel-source
|
||||
| tar -xz -C ~/kernel
|
||||
curl -sSL $ci/kernel_aarch64_Module.symvers -o ~/kernel/Module.symvers
|
||||
adb shell "su -c 'cat /proc/config.gz'" | gunzip > ~/kernel/.config
|
||||
make -C ~/kernel ARCH=arm64 LLVM=1 olddefconfig modules_prepare
|
||||
|
||||
`modules_prepare_outdir.tar.gz` is the configured build tree with `scripts/` already
|
||||
compiled as x86-64 binaries, so the kbuild half has to run on an x86-64 Linux host —
|
||||
an arm64 one gets as far as `CC [M]` and then fails to run `fixdep`. `Module.symvers`
|
||||
carries the CRCs `CONFIG_MODVERSIONS` checks. The top-level `Makefile` in that prepared tree is a stub
|
||||
that includes the source's, hence both `KDIR` and `KOUT` below.
|
||||
|
||||
A different GKI build of the same branch does not substitute for these: vermagic is
|
||||
compared verbatim, and it names the exact build.
|
||||
|
||||
#### Build
|
||||
That wants a Linux host, of either architecture, with clang and lld. With
|
||||
`CONFIG_MODVERSIONS` the kernel matches a module by its CRCs rather than by the
|
||||
release vermagic names, so the release this tree reports does not have to be
|
||||
the device's; the vendor's own modules do not carry it either.
|
||||
|
||||
make -C src/barebone/agent/linux \
|
||||
FRIDA_SDK=$HOME/sdk-none-arm64-softfloat_nopic \
|
||||
ARCH=arm64 BITS=64 \
|
||||
FRIDA_SDK=$HOME/sdk-none-arm64-softfloat_pic \
|
||||
GUMJS_DEVKIT_DIR=$HOME/gumjs-devkit \
|
||||
AGENT_LD=aarch64-linux-gnu-ld \
|
||||
AGENT_AR=aarch64-linux-gnu-ar \
|
||||
AGENT_NM=aarch64-linux-gnu-nm \
|
||||
AGENT_OBJCOPY=aarch64-linux-gnu-objcopy \
|
||||
KDIR=$HOME/kernel-source \
|
||||
KOUT=$HOME/kernel-prepared \
|
||||
KDIR=$HOME/kernel \
|
||||
LLVM=1
|
||||
|
||||
`make` builds the Rust staticlib itself, prelinks it against the devkit and the SDK,
|
||||
then hands the result to kbuild. The `AGENT_*` overrides are only needed because the
|
||||
defaults name an `aarch64-none-elf-` GNU toolchain; drop them if that is what is
|
||||
installed.
|
||||
|
||||
The prelink half runs anywhere; the kbuild half needs the x86-64 Linux host above.
|
||||
Cross-building from macOS means running that step in a container holding the kernel
|
||||
tree.
|
||||
|
||||
adb push src/barebone/agent/linux/frida-agent.ko /data/local/tmp/
|
||||
|
||||
## Loading
|
||||
|
||||
insmod frida-agent.ko
|
||||
|
||||
The module's init path returns as soon as the worker thread is running — watch
|
||||
`dmesg` for `frida: listening on /dev/frida`. `rmmod frida-agent` unwinds in the
|
||||
reverse order and does not return until the worker is off the module's text.
|
||||
Init returns once the agent's worker is running and `/dev/frida` is there.
|
||||
`rmmod frida-agent` stops the agent, waits for its worker to leave the image,
|
||||
and only then frees it.
|
||||
|
||||
## Talking to it
|
||||
|
||||
The agent exposes `/dev/frida` and waits: one client at a time, `read`/`write` of the
|
||||
same length-prefixed frames the other transports carry, with `poll` for readiness.
|
||||
It never dials out, so there is no network namespace to get right and no ordering
|
||||
requirement between loading the module and attaching a client.
|
||||
The agent waits on `/dev/frida`: one client at a time, `read`/`write` of the
|
||||
same length-prefixed frames the other transports carry, with `poll` for
|
||||
readiness. It never dials out, so there is no network namespace to get right
|
||||
and no ordering between loading the module and attaching a client.
|
||||
|
||||
On Android the node is created by devtmpfs without a policy-specific label, so an
|
||||
SELinux-enforcing system needs the client's domain granted access to it before it can
|
||||
open the device. A socket-based transport was the other option considered and is a
|
||||
worse fit: `AF_VSOCK` only reaches a peer when a hypervisor is on the other side, and
|
||||
having the kernel connect out to loopback needs something already listening before
|
||||
the module loads.
|
||||
On Android devtmpfs creates the node without a policy-specific label, so an
|
||||
SELinux-enforcing system needs the client's domain granted access to it.
|
||||
|
||||
### Reaching it from a host
|
||||
|
||||
Serve the Barebone device from a frida-server running on the target, so the `open()`
|
||||
happens next to the device node and the host talks to an ordinary frida-server:
|
||||
Serve the Barebone device from a frida-server running on the target, so the
|
||||
`open()` happens next to the device node:
|
||||
|
||||
FRIDA_BAREBONE_CONFIG=/data/local/tmp/linux-kmod.json \
|
||||
frida-server --device barebone -l 127.0.0.1:27042
|
||||
|
||||
`etc/linux-kmod.json` is that config. From the host, with `adb forward tcp:27042
|
||||
tcp:27042` in place:
|
||||
`etc/linux-kmod.json` is that config. Its `"type": "resident"` is what keeps a
|
||||
client leaving from stopping the agent. From the host, with
|
||||
`adb forward tcp:27042 tcp:27042` in place:
|
||||
|
||||
frida -H 127.0.0.1:27042 -p 0
|
||||
|
||||
## Injecting into userspace
|
||||
|
||||
Loaded on a live system the module doubles as a ptrace-free injector for ordinary
|
||||
processes — how the Linux local backend reaches its targets when the module is
|
||||
present, falling back to `ptrace` when it is not. There is no second device node;
|
||||
the channel is `prctl()` with a magic option and a token in the fifth argument,
|
||||
On x86_64 and arm64 a resident agent doubles as a ptrace-free injector for
|
||||
ordinary processes — how the Linux local backend reaches its targets when the
|
||||
agent is there, falling back to `ptrace` when it is not. The channel is
|
||||
`prctl()` with a magic option and a token in the fifth argument,
|
||||
|
||||
prctl(0x46524944, op, arg1, arg2, 0x1d5f9e6b2c7a4038)
|
||||
|
||||
and any call whose option or token does not match falls through to the real
|
||||
prctl, so probing for the module looks like a stock kernel. `op` 32 pings and
|
||||
prctl, so probing for the agent looks like a stock kernel. `op` 32 pings and
|
||||
returns the magic.
|
||||
|
||||
Allocate, free, read, write and spawn — the primitives the injector drives
|
||||
against a target pid — require `CAP_SYS_ADMIN`. A spawned thread is a real
|
||||
`CLONE_THREAD` sibling that adopts the target's mm, files, credentials,
|
||||
namespaces, fs and SysV semaphore undo list, so it is a native member of the
|
||||
group rather than a kernel thread wearing its address space.
|
||||
Allocate, free, read, write and spawn — what the injector drives against a
|
||||
target pid — require `CAP_SYS_ADMIN`. A spawned thread is a real member of the
|
||||
target's thread group that adopts its mm, files, credentials, namespaces, fs
|
||||
and SysV semaphore undo list, rather than a kernel thread wearing its address
|
||||
space.
|
||||
|
||||
Cloaking is Gum's own registry mirrored into the kernel so it holds against
|
||||
`/proc` too: the module intercepts the `maps`, `smaps`, task and status readers
|
||||
Cloaking is Gum's own registry mirrored into the kernel, so it holds against
|
||||
`/proc` too: the agent intercepts the `maps`, `smaps`, task and status readers
|
||||
and drops cloaked threads, ranges and fds while keeping the counts consistent. A
|
||||
process cloaks its own resources with the token alone — no capability — so an
|
||||
injected agent self-cloaks by forwarding Gum's updates over the same channel,
|
||||
and the loader adds the footprint the agent itself never sees. A cloaked thread
|
||||
still sees through the cloak when it reads `/proc`, so the agent is not blind to
|
||||
its own process.
|
||||
process cloaks its own resources with the token alone, so an injected agent
|
||||
self-cloaks by forwarding Gum's updates over the same channel. A cloaked thread
|
||||
still sees through the cloak when it reads `/proc`.
|
||||
|
||||
## What the target kernel has to provide
|
||||
|
||||
- **x86_64 or arm64.** The register-level pieces — the page size, cache maintenance,
|
||||
where the syscall ABI leaves prctl()'s arguments, and how a spawned thread's
|
||||
registers are set up for its return to userspace — have a half for each. The XNU
|
||||
flavor remains arm64-only.
|
||||
- **`CONFIG_KPROBES` and `CONFIG_KALLSYMS_ALL`.** A good deal of what the shim
|
||||
needs is compiled in but not exported — `kallsyms_lookup_name`,
|
||||
`kallsyms_on_each_symbol` and `set_memory_*` all are, and GKI trims its export
|
||||
table to the KMI symbol list on top of that. The shim resolves them by name
|
||||
through a kprobe, which is the standard way back in. Without kallsyms the module
|
||||
still loads but cannot look up symbols or change page permissions.
|
||||
- **x86_64 or arm64** for everything above; 32-bit x86 and ARM load the agent
|
||||
but have no injector.
|
||||
- **`CONFIG_KPROBES` and `CONFIG_KALLSYMS_ALL`.** Most of what the agent calls
|
||||
is compiled in but not exported, and GKI trims its export table to the KMI
|
||||
symbol list on top of that. The loader finds `kallsyms_lookup_name` through a
|
||||
kprobe and resolves the rest by name.
|
||||
- **Unsigned modules.** `CONFIG_MODULE_SIG_FORCE` must be off. Android's
|
||||
`CONFIG_MODULE_SIG_PROTECT` is fine: it only demands signatures for modules on
|
||||
the GKI list.
|
||||
- **No BTI on arm64.** The image has no landing pads, so a CPU implementing BTI
|
||||
under a kernel built with `CONFIG_ARM64_BTI_KERNEL` faults the first indirect
|
||||
call into it.
|
||||
|
||||
## Kernel ABI constraints
|
||||
|
||||
A hardened kernel imposes an ABI on anything linked into it, and the FP constraint
|
||||
below is why this flavor is built against a soft-float SDK
|
||||
(`none-arm64-softfloat_nopic`, `none-x86_64-softfloat_nopic`) rather than an ordinary
|
||||
bare-metal one. The remaining constraints are per-architecture.
|
||||
A hardened kernel imposes an ABI on anything running inside it, which the image
|
||||
honors whichever way it got in:
|
||||
|
||||
- **FP/SIMD.** JavaScript numbers are doubles, so a hardfloat build would use FP
|
||||
for as long as the runtime lives, and neither architecture lets a kernel thread
|
||||
hold it for free — x86 wants every stretch bracketed by `kernel_fpu_begin()`, and
|
||||
on arm64 kernels before 6.9 there is no correct way to hold it at all:
|
||||
`kernel_neon_begin()` runs with preemption disabled so we cannot sleep under it,
|
||||
and the scheduler does not save a kernel thread's FP registers — live values are
|
||||
lost on preemption and the FP state of whichever user task was interrupted is
|
||||
corrupted. Building everything soft-float removes the question rather than
|
||||
answering it: `-mabi=aapcs-soft -mgeneral-regs-only` on arm64, and on x86_64
|
||||
LLVM's `+soft-float`, which is also what Rust's own `x86_64-unknown-none` turns
|
||||
on, so the two halves agree on where a double travels. `-mno-sse` alone does not
|
||||
reach the ABI; and `long double` has to be narrowed to 64 bits with
|
||||
`-mlong-double-64`, since soft-float has no lowering for x87's 80-bit format and
|
||||
LLVM crashes rather than calling out to a builtin.
|
||||
- **Indirect branch tracking (x86_64).** With `CONFIG_X86_KERNEL_IBT` an indirect
|
||||
call landing on anything but an `endbr64` faults, so everything linked in is built
|
||||
`-fcf-protection=branch` and the Rust half `-Zcf-protection=branch`.
|
||||
- **Kernel code model and red zone (x86_64).** Modules are loaded into the top 2GB
|
||||
of the address space and their relocations are resolved as if by `-mcmodel=kernel`,
|
||||
and there is no red zone below the stack pointer for an interrupt to step on. Both
|
||||
come with the Rust target; the C half is told explicitly.
|
||||
- **objtool (x86_64).** It validates every object entering a module and builds its
|
||||
ORC unwind tables, and was not written to read a JavaScript runtime linked against
|
||||
a freestanding libc. `Kbuild` marks the prelinked blob `OBJECT_FILES_NON_STANDARD`.
|
||||
- **Shadow call stack (arm64).** With `CONFIG_SHADOW_CALL_STACK` the kernel reserves x18, so
|
||||
the SDK and Gum are built `-ffixed-x18` and the Rust half with `-Zfixed-x18`.
|
||||
Anything linked in that uses x18 as a scratch register overwrites the kernel's SCS
|
||||
pointer, and the thread then returns through whatever that address happens to hold.
|
||||
|
||||
The Rust flag only reaches crates Cargo compiles, so `core`, `alloc` and
|
||||
`compiler_builtins` — which the toolchain ships prebuilt, and which between them
|
||||
account for every x18 write that used to reach the module — are rebuilt from source
|
||||
via `-Z build-std`. That needs `rustup component add rust-src`.
|
||||
|
||||
## The soft-float libc
|
||||
|
||||
The libc is picolibc and the compiler runtime is compiler-rt, both from the
|
||||
soft-float SDK that `FRIDA_SDK` points at. `Makefile` trims a copy of its
|
||||
`libc.a` before the prelink: the agent implements `malloc`, the C library's locks and
|
||||
its process stubs over the kernel's own facilities, so picolibc's are duplicate
|
||||
definitions rather than fallbacks. The set is derived from what the agent defines
|
||||
rather than listed, since picolibc moves things between releases.
|
||||
|
||||
On arm64 those compiler-rt builtins are load-bearing in a way that is easy to miss.
|
||||
Rust's toolchain carries its own `compiler_builtins`, compiled for the ordinary AAPCS
|
||||
where a double travels in `d0`, and the prelink whole-archives the Rust staticlib — so
|
||||
without the filtering step in `Makefile` those definitions answer the C library's calls
|
||||
to `__muldf3` and friends, which pass the same double in `x0`. Nothing crashes. The
|
||||
arithmetic simply returns garbage, and the first thing to notice is GLib deciding a
|
||||
hash table need not grow. x86_64 needs none of that filtering: soft-float is part of
|
||||
the Rust target there, so the copy `-Z build-std` compiles already agrees with the C
|
||||
half.
|
||||
|
||||
## What a script may look like
|
||||
|
||||
The runtime compiles scripts on a kernel thread's stack, and QuickJS parses
|
||||
expressions by recursion, so the ceiling is nesting rather than length: roughly
|
||||
fourteen levels of parentheses or fifteen of object literals on arm64 Linux, whose
|
||||
kernel stack is 16 KiB — as is x86_64's. A script that exceeds it fails to compile rather than taking
|
||||
anything down, though the message is not always `stack overflow` — an over-deep
|
||||
`if`/`else` chain reports `expecting ';'`. Long scripts are fine; deep ones are not.
|
||||
for as long as the runtime lives, and neither architecture lets a kernel
|
||||
thread hold it for free — x86 wants every stretch bracketed by
|
||||
`kernel_fpu_begin()`, and on arm64 kernels before 6.9 there is no correct way
|
||||
to hold it at all. Building everything soft-float removes the question: the
|
||||
SDK, the devkit and the Rust target all use the soft-float ABI.
|
||||
- **Indirect branch tracking (x86_64).** With `CONFIG_X86_KERNEL_IBT` an
|
||||
indirect call landing on anything but an `endbr64` faults, so the image is
|
||||
built `-fcf-protection=branch` and `-Zcf-protection=branch`, with switches
|
||||
compiled as comparisons rather than `notrack` jump tables. The kernel seals
|
||||
the `endbr64` of every function it saw no indirect call to, so the loader
|
||||
restores it on any it hands the agent to call.
|
||||
- **Shadow call stack (arm64).** With `CONFIG_SHADOW_CALL_STACK` the kernel
|
||||
reserves x18, so the SDK, the devkit and the Rust target all leave it alone.
|
||||
`core`, `alloc` and `compiler_builtins` are rebuilt from source with
|
||||
`-Z build-std` so that holds for them too.
|
||||
- **Stack.** The runtime runs on a kernel thread's stack, and QuickJS parses
|
||||
expressions by recursion, so the ceiling is nesting rather than length: around
|
||||
fourteen levels of parentheses or fifteen of object literals on a 16 KiB
|
||||
kernel stack. A script that exceeds it fails to compile rather than taking
|
||||
anything down, though the message is not always `stack overflow`.
|
||||
@@ -0,0 +1,7 @@
|
||||
.section .rodata, "a"
|
||||
.balign 16
|
||||
.globl frida_agent_image_start
|
||||
frida_agent_image_start:
|
||||
.incbin FRIDA_AGENT_IMAGE
|
||||
.globl frida_agent_image_end
|
||||
frida_agent_image_end:
|
||||
+1110
-2209
File diff suppressed because it is too large.
Load diff
@@ -1,8 +0,0 @@
|
||||
SECTIONS
|
||||
{
|
||||
.init_array 0 : {
|
||||
_agent_init_start = .;
|
||||
*(.init_array*)
|
||||
_agent_init_end = .;
|
||||
}
|
||||
}
|
||||
@@ -11,21 +11,22 @@ how it is told about the kernel around it, and how the host reaches it.
|
||||
## Building
|
||||
|
||||
Three things, in order. First an SDK for this kernel -- picolibc, GLib, QuickJS, capstone and the
|
||||
rest, built for `macos-arm64-kernel`:
|
||||
rest, built for `macos-arm64e-kernel` and published prebuilt:
|
||||
|
||||
cd frida-core
|
||||
python3 -m releng.deps build --host macos-arm64-kernel
|
||||
releng/deps.py sync sdk macos-arm64e-kernel ~/sdk-macos-arm64e-kernel
|
||||
|
||||
That fetches the same sources every other flavour is built from, six of them pinned to a commit
|
||||
that says what a Darwin kernel is not; see below. Then a GumJS devkit against it:
|
||||
It is built from the same sources every other flavour is, six of them pinned to a commit that
|
||||
says what a Darwin kernel is not; see below. Then a GumJS devkit against it:
|
||||
|
||||
cd frida-gum
|
||||
./configure --host=macos-arm64-kernel --enable-gumjs --disable-v8 --with-devkits=gumjs
|
||||
./configure --host=macos-arm64e-kernel --enable-gumjs --with-devkits=gumjs \
|
||||
--with-devkit-symbol-scope=original
|
||||
make
|
||||
|
||||
Then the kext itself:
|
||||
|
||||
make FRIDA_SDK=.../deps/sdk-macos-arm64-kernel \
|
||||
make FRIDA_SDK=~/sdk-macos-arm64e-kernel \
|
||||
GUMJS_DEVKIT_DIR=.../frida-gum/build/bindings/gumjs/devkit
|
||||
|
||||
which builds the agent as a static library for the kernel's ABI, `frida-kext.c` against the Kernel
|
||||
|
||||
@@ -236,7 +236,7 @@ unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl) -> bool {
|
||||
|
||||
unsafe fn rec_mutex_wait(impl_: &mut RecMutexImpl) {
|
||||
while impl_.count.load(Ordering::Relaxed) != 0 {
|
||||
wait_on(&impl_.count, &mut || impl_.count.load(Ordering::Relaxed) == 0);
|
||||
wait_on(&impl_.count, None, &mut || impl_.count.load(Ordering::Relaxed) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,7 +361,7 @@ unsafe fn rw_lock_reader_try_acquire(impl_: &mut RWLockImpl) -> bool {
|
||||
|
||||
unsafe fn rw_lock_wait_for_writers(impl_: &mut RWLockImpl) {
|
||||
while impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT != 0 {
|
||||
wait_on(&impl_.state, &mut || {
|
||||
wait_on(&impl_.state, None, &mut || {
|
||||
impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT == 0
|
||||
});
|
||||
}
|
||||
@@ -369,7 +369,7 @@ unsafe fn rw_lock_wait_for_writers(impl_: &mut RWLockImpl) {
|
||||
|
||||
unsafe fn rw_lock_wait_for_all(impl_: &mut RWLockImpl) {
|
||||
while impl_.state.load(Ordering::Relaxed) != 0 {
|
||||
wait_on(&impl_.state, &mut || impl_.state.load(Ordering::Relaxed) == 0);
|
||||
wait_on(&impl_.state, None, &mut || impl_.state.load(Ordering::Relaxed) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,7 +395,7 @@ pub extern "C" fn g_cond_wait(cond: *mut GCond, mutex: *mut GMutex) {
|
||||
|
||||
let impl_ = cond_impl(cond);
|
||||
while impl_.generation.load(Ordering::Acquire) == seen {
|
||||
wait_on(&impl_.generation, &mut || {
|
||||
wait_on(&impl_.generation, None, &mut || {
|
||||
impl_.generation.load(Ordering::Acquire) != seen
|
||||
});
|
||||
}
|
||||
@@ -431,11 +431,13 @@ pub extern "C" fn g_cond_wait_until(cond: *mut GCond, mutex: *mut GMutex, end_ti
|
||||
|
||||
let impl_ = cond_impl(cond);
|
||||
let mut signalled = impl_.generation.load(Ordering::Acquire) != seen;
|
||||
while !signalled && crate::kernel::monotonic_micros() < end_time {
|
||||
wait_on(&impl_.generation, &mut || {
|
||||
let mut now = crate::kernel::monotonic_micros();
|
||||
while !signalled && now < end_time {
|
||||
wait_on(&impl_.generation, Some((end_time - now) as u64), &mut || {
|
||||
impl_.generation.load(Ordering::Acquire) != seen
|
||||
});
|
||||
signalled = impl_.generation.load(Ordering::Acquire) != seen;
|
||||
now = crate::kernel::monotonic_micros();
|
||||
}
|
||||
|
||||
g_mutex_lock(mutex);
|
||||
@@ -620,7 +622,7 @@ pub extern "C" fn _g_system_thread_wait(thread: *mut GSystemThread) {
|
||||
let system_thread = thread as *mut SystemThreadImpl;
|
||||
|
||||
while (*system_thread).thread_id.load(Ordering::Acquire) == 0 {
|
||||
wait_on(&(*system_thread).thread_id, &mut || {
|
||||
wait_on(&(*system_thread).thread_id, None, &mut || {
|
||||
(*system_thread).thread_id.load(Ordering::Acquire) != 0
|
||||
});
|
||||
}
|
||||
@@ -645,7 +647,7 @@ unsafe fn cleanup_wrapper_data(wrapper_data: *mut ThreadWrapperData) {
|
||||
|
||||
unsafe fn lock_acquire(lock: &AtomicU32) {
|
||||
while !unsafe { lock_try_acquire(lock) } {
|
||||
wait_on(lock, &mut || lock.load(Ordering::Relaxed) == 0);
|
||||
wait_on(lock, None, &mut || lock.load(Ordering::Relaxed) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -659,19 +661,11 @@ unsafe fn lock_release(lock: &AtomicU32) {
|
||||
wake_waiters(lock);
|
||||
}
|
||||
|
||||
// Sleeps until `ready` holds or the slice expires, whichever comes first.
|
||||
//
|
||||
// These used to spin on wfe/sev. A kernel thread that spins never reaches the
|
||||
// scheduler, so a lost wakeup — or a genuine deadlock, which is how we found this —
|
||||
// starves the CPU it is on until the hardware watchdog takes the machine down. The
|
||||
// bounded sleep turns both into a stall that can be observed instead.
|
||||
//
|
||||
// The address of the word being waited on names the wait channel: XNU pairs
|
||||
// assert_wait()/thread_wakeup() on it directly, while the Linux shim has a single
|
||||
// channel and treats it as a hint. Every caller re-checks its own condition in a
|
||||
// loop, so a spurious wakeup costs nothing either way.
|
||||
fn wait_on<T>(word: &T, ready: &mut dyn FnMut() -> bool) {
|
||||
crate::kernel::wait(token_of(word), Some(WAIT_SLICE_US), ready);
|
||||
// assert_wait()/thread_wakeup() on it directly. Every caller re-checks its own
|
||||
// condition in a loop, so a spurious wakeup costs nothing either way.
|
||||
fn wait_on<T>(word: &T, timeout_us: Option<u64>, ready: &mut dyn FnMut() -> bool) {
|
||||
crate::kernel::wait(token_of(word), timeout_us, ready);
|
||||
|
||||
// The wait above returns without sleeping whenever `ready` already holds, so on
|
||||
// its own it is not enough to keep a contended loop off the CPU: every caller
|
||||
@@ -688,4 +682,3 @@ fn token_of<T>(word: &T) -> *const u8 {
|
||||
word as *const T as *const u8
|
||||
}
|
||||
|
||||
const WAIT_SLICE_US: u64 = 1000;
|
||||
@@ -19,14 +19,12 @@ use alloc::vec::Vec;
|
||||
use core::ptr;
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
use crate::gum_linux::enumerate_exports_in_range;
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
#[cfg(feature = "linux-injected")]
|
||||
use crate::bindings::{
|
||||
GumFoundSymbolFunc, GumSymbolDetails, GumSymbolType_GUM_SYMBOL_FUNCTION,
|
||||
GumSymbolType_GUM_SYMBOL_OBJECT, GumSymbolType_GUM_SYMBOL_UNKNOWN, gboolean, gssize,
|
||||
};
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
#[cfg(feature = "linux-injected")]
|
||||
use crate::gum_modules::enumerate_symbols_in_range;
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
use crate::gum_windows::{enumerate_exports_in_range, enumerate_imports_in_module};
|
||||
@@ -192,7 +190,7 @@ extern "C" fn gum_native_module_iface_init(g_iface: gpointer, _iface_data: gpoin
|
||||
Some(crate::signed_to_be_called_back(gum_native_module_enumerate_imports, 0));
|
||||
}
|
||||
(*iface).find_export_by_name = Some(crate::signed_to_be_called_back(gum_native_module_find_export_by_name, 0));
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
#[cfg(feature = "linux-injected")]
|
||||
{
|
||||
(*iface).enumerate_symbols =
|
||||
Some(crate::signed_to_be_called_back(gum_native_module_enumerate_symbols, 0));
|
||||
@@ -350,7 +348,7 @@ unsafe extern "C" fn gum_native_module_enumerate_imports(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
#[cfg(feature = "linux-injected")]
|
||||
unsafe extern "C" fn gum_native_module_enumerate_symbols(
|
||||
self_: *mut GumModule,
|
||||
func: GumFoundSymbolFunc,
|
||||
@@ -388,12 +386,12 @@ unsafe extern "C" fn gum_native_module_enumerate_symbols(
|
||||
/// What the backends implement for `gum_native_module_enumerate_symbols`: call
|
||||
/// `callback` for every symbol in `[start, end)`, stopping early when it returns
|
||||
/// false.
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub(crate) type FoundSymbolCallback<'a> = dyn FnMut(*const gchar, u64, u64, u8, bool) -> bool + 'a;
|
||||
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
#[cfg(feature = "linux-injected")]
|
||||
const SYMBOL_IS_OBJECT: u8 = 1;
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
#[cfg(feature = "linux-injected")]
|
||||
const SYMBOL_IS_FUNCTION: u8 = 2;
|
||||
|
||||
/// What the backends implement for `gum_native_module_enumerate_exports`: call
|
||||
|
||||
@@ -116,6 +116,11 @@ pub extern "C" fn gum_memory_try_remap_writable_pages(
|
||||
return remap_agent_pages(first_page, n_pages);
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
if let Some(alias) = kernel::remap_writable(first_page as u64, n_pages) {
|
||||
return alias as gpointer;
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "linux-injected", not(target_arch = "arm")))]
|
||||
if writable_in_place(first_page, n_pages) {
|
||||
return first_page;
|
||||
@@ -271,6 +276,11 @@ pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
if kernel::unmap_writable(writable as *mut core::ffi::c_void) {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(executable) = aliases().remove(&(writable as u64)) {
|
||||
let size = _n_pages as usize * unsafe { gum_query_page_size() } as usize;
|
||||
libc::__clear_cache(executable as *const u8, (executable + size as u64) as *const u8);
|
||||
@@ -591,7 +601,7 @@ pub extern "C" fn gum_memory_allocate(
|
||||
#[cfg(all(not(feature = "xnu-core"), not(feature = "linux-injected")))]
|
||||
let ptr = kernel::alloc_code(size as usize);
|
||||
#[cfg(feature = "linux-injected")]
|
||||
let ptr = kernel::alloc_heap(size as usize);
|
||||
let ptr = kernel::alloc_code(size as usize);
|
||||
#[cfg(not(feature = "xnu-core"))]
|
||||
let _ = address;
|
||||
#[cfg(not(feature = "xnu-kext"))]
|
||||
|
||||
@@ -1,216 +0,0 @@
|
||||
// Linux half of Gum's platform backend. Everything is done locally: we are the
|
||||
// kernel, so page permissions go through set_memory_*(), writable aliases of
|
||||
// write-protected text come from vmap(), and symbols come from kallsyms.
|
||||
|
||||
use crate::{
|
||||
bindings::{
|
||||
_GumPageProtection_GUM_PAGE_EXECUTE, _GumPageProtection_GUM_PAGE_READ,
|
||||
_GumPageProtection_GUM_PAGE_WRITE, _GumRwxSupport_GUM_RWX_NONE, GArray,
|
||||
GumDebugSymbolDetails, GumModuleRegistry, GumPageProtection, GumRwxSupport,
|
||||
g_array_append_vals, g_array_new, g_pattern_spec_free, g_pattern_spec_match_string,
|
||||
g_pattern_spec_new, g_strdup, gboolean, gchar, gconstpointer, gpointer, gsize, guint,
|
||||
gum_mprotect,
|
||||
},
|
||||
gum::{self, FoundExportCallback},
|
||||
kernel,
|
||||
};
|
||||
use core::ffi::CStr;
|
||||
use core::ptr;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_query_platform() -> *const crate::bindings::gchar {
|
||||
c"linux".as_ptr() as *const crate::bindings::gchar
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_query_stack_size() -> gsize {
|
||||
crate::linux::stack_headroom() as gsize
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_query_rwx_support() -> GumRwxSupport {
|
||||
_GumRwxSupport_GUM_RWX_NONE
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_can_remap_writable() -> gboolean {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_try_remap_writable_pages(
|
||||
first_page: gpointer,
|
||||
n_pages: guint,
|
||||
) -> gpointer {
|
||||
kernel::remap_writable(first_page as u64, n_pages) as gpointer
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages: guint) {
|
||||
kernel::unmap_writable(writable as *mut core::ffi::c_void);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_try_mprotect(
|
||||
address: gpointer,
|
||||
size: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gboolean {
|
||||
if kernel::protect(address as u64, size as usize, prot as u32) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_allocate(
|
||||
_address: gpointer,
|
||||
size: gsize,
|
||||
_alignment: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gpointer {
|
||||
let ptr = kernel::alloc_code(size as usize);
|
||||
unsafe {
|
||||
core::ptr::write_bytes(ptr, 0, size as usize);
|
||||
if (prot & _GumPageProtection_GUM_PAGE_EXECUTE) != 0 {
|
||||
gum::register_slab(ptr as u64, size as usize);
|
||||
gum_mprotect(ptr as gpointer, size, prot);
|
||||
}
|
||||
}
|
||||
|
||||
ptr as gpointer
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_free(address: gpointer, size: gsize) -> gboolean {
|
||||
// vfree() wants the range writable again: the pages go back to the vmalloc
|
||||
// allocator, and leaving them RX would fault the next consumer.
|
||||
if gum::is_agent_slab(address as u64) {
|
||||
unsafe {
|
||||
gum_mprotect(
|
||||
address,
|
||||
size,
|
||||
(_GumPageProtection_GUM_PAGE_READ | _GumPageProtection_GUM_PAGE_WRITE)
|
||||
as GumPageProtection,
|
||||
);
|
||||
}
|
||||
gum::unregister_slab(address as u64);
|
||||
}
|
||||
kernel::free_code(address as *mut u8, size as usize);
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
|
||||
crate::gum_modules::publish(registry);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_on_registry_deactivating(_registry: *mut GumModuleRegistry) {
|
||||
crate::gum_modules::unpublish();
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn enumerate_exports_in_range(
|
||||
start_address: u64,
|
||||
end_address: u64,
|
||||
callback: &mut FoundExportCallback<'_>,
|
||||
) {
|
||||
if crate::gum_modules::enumerate_exports_in_module(start_address, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
kernel::enumerate_symbols(&mut |name, address| {
|
||||
if address < start_address || address >= end_address {
|
||||
return true;
|
||||
}
|
||||
callback(name.as_ptr() as *const gchar, address)
|
||||
});
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_symbol_details_from_address(
|
||||
address: gpointer,
|
||||
details: *mut GumDebugSymbolDetails,
|
||||
) -> gboolean {
|
||||
let Some((name, symbol_address)) = kernel::symbol_from_address(address as u64) else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let name_bytes = name.as_bytes();
|
||||
let copy_len = core::cmp::min(name_bytes.len(), (*details).symbol_name.len() - 1);
|
||||
core::ptr::copy_nonoverlapping(
|
||||
name_bytes.as_ptr(),
|
||||
(*details).symbol_name.as_mut_ptr() as *mut u8,
|
||||
copy_len,
|
||||
);
|
||||
(*details).symbol_name[copy_len] = 0;
|
||||
|
||||
(*details).address = symbol_address;
|
||||
|
||||
(*details).module_name[0] = 0;
|
||||
(*details).file_name[0] = 0;
|
||||
(*details).line_number = 0;
|
||||
(*details).column = 0;
|
||||
}
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_symbol_name_from_address(address: gpointer) -> *mut gchar {
|
||||
let Some((name, _)) = kernel::symbol_from_address(address as u64) else {
|
||||
return ptr::null_mut();
|
||||
};
|
||||
|
||||
let mut name = name;
|
||||
name.push('\0');
|
||||
unsafe { g_strdup(name.as_ptr() as *const gchar) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_function(name: *const gchar) -> gpointer {
|
||||
let address = kernel::find_function(unsafe { CStr::from_ptr(name) });
|
||||
address as gpointer
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_functions_named(name: *const gchar) -> *mut GArray {
|
||||
let wanted = unsafe { CStr::from_ptr(name) };
|
||||
|
||||
collect_addresses(&mut |candidate, _address| candidate == wanted)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_functions_matching(pattern: *const gchar) -> *mut GArray {
|
||||
unsafe {
|
||||
let spec = g_pattern_spec_new(pattern);
|
||||
let array = collect_addresses(&mut |candidate, _address| {
|
||||
g_pattern_spec_match_string(spec, candidate.as_ptr() as *const gchar) != 0
|
||||
});
|
||||
g_pattern_spec_free(spec);
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_addresses(predicate: &mut dyn FnMut(&CStr, u64) -> bool) -> *mut GArray {
|
||||
unsafe {
|
||||
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
|
||||
|
||||
kernel::enumerate_symbols(&mut |name, address| {
|
||||
if predicate(name, address) {
|
||||
let addr = address as gpointer;
|
||||
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
|
||||
}
|
||||
true
|
||||
});
|
||||
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
|
||||
0
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
// stream; whoever opens the device decides how it reaches the host.
|
||||
|
||||
use core::cell::UnsafeCell;
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
use core::ffi::{c_int, c_void};
|
||||
|
||||
use alloc::vec::Vec;
|
||||
@@ -30,6 +31,7 @@ impl Hostlink {
|
||||
if unsafe { frida_kmod_link_open() } != 0 {
|
||||
return Err(());
|
||||
}
|
||||
LINK_OPEN.store(true, Ordering::Release);
|
||||
|
||||
Ok(Hostlink {
|
||||
state: UnsafeCell::new(Inner {
|
||||
@@ -97,25 +99,55 @@ impl Hostlink {
|
||||
|
||||
impl Drop for Hostlink {
|
||||
fn drop(&mut self) {
|
||||
LINK_OPEN.store(false, Ordering::Release);
|
||||
unsafe { frida_kmod_link_close() };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
pub fn a_turn_is_wanted() -> bool {
|
||||
unsafe { frida_kmod_link_pending() }
|
||||
LINK_OPEN.load(Ordering::Acquire) && unsafe { frida_kmod_link_pending() }
|
||||
}
|
||||
|
||||
static LINK_OPEN: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
fn recv_nonblocking(dst: &mut [u8]) -> usize {
|
||||
let n = unsafe { frida_kmod_link_recv(dst.as_mut_ptr() as *mut c_void, dst.len()) };
|
||||
if n <= 0 { 0 } else { n as usize }
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
unsafe extern "C" {
|
||||
fn frida_kmod_link_open() -> c_int;
|
||||
fn frida_kmod_link_close();
|
||||
fn frida_kmod_link_send(data: *const c_void, size: usize) -> c_int;
|
||||
fn frida_kmod_link_recv(data: *mut c_void, size: usize) -> isize;
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
fn frida_kmod_link_pending() -> bool;
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "linux-injected", not(target_arch = "x86")))]
|
||||
unsafe extern "C" {
|
||||
#[link_name = "_frida_kmod_link_open"]
|
||||
static frida_kmod_link_open: unsafe extern "C" fn() -> c_int;
|
||||
#[link_name = "_frida_kmod_link_close"]
|
||||
static frida_kmod_link_close: unsafe extern "C" fn();
|
||||
#[link_name = "_frida_kmod_link_send"]
|
||||
static frida_kmod_link_send: unsafe extern "C" fn(*const c_void, usize) -> c_int;
|
||||
#[link_name = "_frida_kmod_link_recv"]
|
||||
static frida_kmod_link_recv: unsafe extern "C" fn(*mut c_void, usize) -> isize;
|
||||
#[link_name = "_frida_kmod_link_pending"]
|
||||
static frida_kmod_link_pending: unsafe extern "C" fn() -> bool;
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "linux-injected", target_arch = "x86"))]
|
||||
unsafe extern "C" {
|
||||
#[link_name = "frida_k_frida_kmod_link_open"]
|
||||
fn frida_kmod_link_open() -> c_int;
|
||||
#[link_name = "frida_k_frida_kmod_link_close"]
|
||||
fn frida_kmod_link_close();
|
||||
#[link_name = "frida_k_frida_kmod_link_send"]
|
||||
fn frida_kmod_link_send(data: *const c_void, size: usize) -> c_int;
|
||||
#[link_name = "frida_k_frida_kmod_link_recv"]
|
||||
fn frida_kmod_link_recv(data: *mut c_void, size: usize) -> isize;
|
||||
#[link_name = "frida_k_frida_kmod_link_pending"]
|
||||
fn frida_kmod_link_pending() -> bool;
|
||||
}
|
||||
@@ -1,14 +1,8 @@
|
||||
// Facade over the host kernel the agent is running inside of. Both backends
|
||||
// expose the same primitives, so the rest of the agent never names a specific
|
||||
// kernel.
|
||||
//
|
||||
// A kernel is reached from the outside -- a remote stub injects the agent and
|
||||
// patches the addresses it needs into .kernel_addrs -- or from the inside,
|
||||
// where the agent is a kernel module whose glue is an ordinary C translation
|
||||
// unit compiled against the target kernel's headers. Linux is reached either
|
||||
// way; the others only from the outside.
|
||||
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub use crate::linux::*;
|
||||
#[cfg(feature = "win9x")]
|
||||
pub use crate::win9x::*;
|
||||
|
||||
+61
-112
@@ -4,16 +4,12 @@
|
||||
feature = "xnu-core",
|
||||
feature = "win9x",
|
||||
feature = "winnt",
|
||||
feature = "linux",
|
||||
feature = "linux-injected"
|
||||
)))]
|
||||
compile_error!(
|
||||
"pick a flavor: --features xnu, --features win9x, --features winnt, --features linux or --features linux-injected"
|
||||
"pick a flavor: --features xnu, --features win9x, --features winnt or --features linux-injected"
|
||||
);
|
||||
|
||||
#[cfg(all(feature = "blob", feature = "linux"))]
|
||||
compile_error!("pick one flavour, not both");
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::format;
|
||||
@@ -58,17 +54,15 @@ mod ring;
|
||||
|
||||
pub mod kernel;
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
mod gum_linux;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
mod gum_btf;
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
#[cfg(feature = "linux-injected")]
|
||||
mod gum_modules;
|
||||
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-kext"))]
|
||||
mod hostlink_chardev;
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
|
||||
mod heap;
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
#[cfg(feature = "linux-injected")]
|
||||
mod linux;
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
@@ -269,6 +263,19 @@ mod entrypoint_blob {
|
||||
unsafe { enter(config_data, config_size) }
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn frida_agent_wake() {
|
||||
nudge_the_loop(ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8);
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn frida_agent_stop() {
|
||||
STOP_REQUESTED.store(true, Ordering::Release);
|
||||
frida_agent_wake();
|
||||
}
|
||||
|
||||
unsafe fn enter(config_data: *const u8, config_size: usize) {
|
||||
unsafe {
|
||||
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected", feature = "xnu-core"))]
|
||||
@@ -312,6 +319,12 @@ mod entrypoint_blob {
|
||||
#[cfg(feature = "linux-injected")]
|
||||
crate::gum_btf::publish();
|
||||
|
||||
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
|
||||
{
|
||||
kernel::learn_when_to_sleep();
|
||||
kernel::open_control_channel();
|
||||
}
|
||||
|
||||
let wake_token = ptr::addr_of_mut!(glib::WAKEUP_TOKEN) as *const u8;
|
||||
// Install this before the loop waits the first time, thus a copy placed in a process later
|
||||
// can receive a handle to the same event.
|
||||
@@ -342,6 +355,10 @@ mod entrypoint_blob {
|
||||
linux::hostlink_pipe_goldfish::Hostlink::init(&path, Some(on_frame_from_host), wake_token)
|
||||
.unwrap(),
|
||||
),
|
||||
#[cfg(feature = "linux-injected")]
|
||||
TransportConfig::CharDevice => Transport::CharDevice(
|
||||
hostlink_chardev::Hostlink::init(Some(on_frame_from_host)).unwrap(),
|
||||
),
|
||||
#[cfg(feature = "xnu-core")]
|
||||
TransportConfig::Vsock { host_port } => Transport::Vsock(
|
||||
hostlink_vsock::Hostlink::init(host_port, Some(on_frame_from_host), wake_token)
|
||||
@@ -361,6 +378,9 @@ mod entrypoint_blob {
|
||||
|
||||
destroy_all_scripts(context);
|
||||
|
||||
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
|
||||
kernel::close_control_channel();
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected",
|
||||
feature = "xnu-core"))]
|
||||
{
|
||||
@@ -384,6 +404,8 @@ mod entrypoint_blob {
|
||||
|
||||
(&raw mut frida_agent_left).write_volatile(1);
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
kernel::hand_back_to_the_loader();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,6 +497,12 @@ mod entrypoint_blob {
|
||||
#[cfg(not(feature = "linux-injected"))]
|
||||
{ panic!("pipe-goldfish is linux-injected's") }
|
||||
}
|
||||
6 => {
|
||||
#[cfg(feature = "linux-injected")]
|
||||
{ TransportConfig::CharDevice }
|
||||
#[cfg(not(feature = "linux-injected"))]
|
||||
{ panic!("the character device is linux-injected's") }
|
||||
}
|
||||
_ => panic!("Unsupported transport kind: {}", transport_kind),
|
||||
};
|
||||
g_variant_unref(transport_cfg_inner);
|
||||
@@ -628,7 +656,7 @@ mod entrypoint_xnu_kext {
|
||||
kernel::wake(ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8);
|
||||
kernel::wait(
|
||||
ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8,
|
||||
Some(10_000),
|
||||
None,
|
||||
&mut || WORKER_EXITED.load(Ordering::Acquire),
|
||||
);
|
||||
}
|
||||
@@ -678,94 +706,6 @@ mod entrypoint_xnu_kext {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
mod entrypoint_linux {
|
||||
use super::*;
|
||||
use core::ffi::c_int;
|
||||
use core::sync::atomic::AtomicBool;
|
||||
|
||||
static STOP_REQUESTED: AtomicBool = AtomicBool::new(false);
|
||||
static WORKER_EXITED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Called from the module's init path. Everything interesting happens on our
|
||||
/// own kernel thread: `insmod` runs with the module mutex held, and bringing
|
||||
/// up Gum plus connecting to the host both block.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn frida_agent_start(own_base: u64, own_size: u64) -> c_int {
|
||||
kernel::log("frida: agent starting\n\0");
|
||||
|
||||
unsafe {
|
||||
OWN_RANGE = GumMemoryRange {
|
||||
base_address: own_base,
|
||||
size: own_size as gsize,
|
||||
};
|
||||
}
|
||||
|
||||
if kernel::spawn_thread(worker, ptr::null_mut()) != 0 {
|
||||
kernel::log("frida: failed to spawn worker\n\0");
|
||||
return -1;
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
/// Called from the module's exit path, which must not return until the worker
|
||||
/// is off our text — the module's pages go away right after.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn frida_agent_stop() {
|
||||
kernel::log("frida: agent stopping\n\0");
|
||||
|
||||
STOP_REQUESTED.store(true, Ordering::Release);
|
||||
|
||||
while !WORKER_EXITED.load(Ordering::Acquire) {
|
||||
kernel::wake(ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8);
|
||||
kernel::wait(
|
||||
ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8,
|
||||
Some(10_000),
|
||||
&mut || WORKER_EXITED.load(Ordering::Acquire),
|
||||
);
|
||||
}
|
||||
|
||||
kernel::log("frida: agent stopped\n\0");
|
||||
}
|
||||
|
||||
pub fn stop_requested() -> bool {
|
||||
STOP_REQUESTED.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
unsafe extern "C" fn worker(_parameter: *mut c_void, _wait_result: i32) {
|
||||
unsafe {
|
||||
kernel::log("frida: worker entry\n\0");
|
||||
|
||||
crate::run_constructors();
|
||||
init_gum();
|
||||
|
||||
kernel::install_hooks();
|
||||
|
||||
match hostlink_chardev::Hostlink::init(Some(on_frame_from_host)) {
|
||||
Ok(hostlink) => transport_set(Transport::CharDevice(hostlink)),
|
||||
Err(_) => {
|
||||
kernel::log("frida: failed to connect to peer\n\0");
|
||||
WORKER_EXITED.store(true, Ordering::Release);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let main_context = adopt_js_context();
|
||||
|
||||
run_main_loop(main_context);
|
||||
|
||||
destroy_all_scripts(main_context);
|
||||
transport_teardown();
|
||||
|
||||
WORKER_EXITED.store(true, Ordering::Release);
|
||||
kernel::wake(ptr::addr_of!(glib::WAKEUP_TOKEN) as *const u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
pub use entrypoint_linux::{frida_agent_start, frida_agent_stop};
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
pub use entrypoint_xnu_kext::{frida_agent_start, frida_agent_stop, frida_agent_wake};
|
||||
#[cfg(feature = "blob")]
|
||||
@@ -782,7 +722,7 @@ pub enum Transport {
|
||||
PipeVsock(linux::hostlink_pipe_vsock::Hostlink),
|
||||
#[cfg(feature = "linux-injected")]
|
||||
PipeGoldfish(linux::hostlink_pipe_goldfish::Hostlink),
|
||||
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-kext"))]
|
||||
CharDevice(hostlink_chardev::Hostlink),
|
||||
}
|
||||
|
||||
@@ -799,7 +739,7 @@ impl Transport {
|
||||
Transport::PipeVsock(h) => h.send(payload),
|
||||
#[cfg(feature = "linux-injected")]
|
||||
Transport::PipeGoldfish(h) => h.send(payload),
|
||||
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-kext"))]
|
||||
Transport::CharDevice(h) => h.send(payload),
|
||||
}
|
||||
}
|
||||
@@ -816,7 +756,7 @@ impl Transport {
|
||||
Transport::PipeVsock(h) => h.shutdown(),
|
||||
#[cfg(feature = "linux-injected")]
|
||||
Transport::PipeGoldfish(h) => h.shutdown(),
|
||||
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-kext"))]
|
||||
Transport::CharDevice(_) => {}
|
||||
}
|
||||
}
|
||||
@@ -833,7 +773,7 @@ impl Transport {
|
||||
Transport::PipeVsock(h) => h.process(),
|
||||
#[cfg(feature = "linux-injected")]
|
||||
Transport::PipeGoldfish(h) => h.process(),
|
||||
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-kext"))]
|
||||
Transport::CharDevice(h) => h.process(),
|
||||
}
|
||||
}
|
||||
@@ -872,6 +812,8 @@ pub enum TransportConfig {
|
||||
PipeVsock { path: alloc::string::String },
|
||||
#[cfg(feature = "linux-injected")]
|
||||
PipeGoldfish { path: alloc::string::String },
|
||||
#[cfg(feature = "linux-injected")]
|
||||
CharDevice,
|
||||
}
|
||||
|
||||
static mut TRANSPORT_DRIVER: *mut Transport = core::ptr::null_mut();
|
||||
@@ -1262,11 +1204,6 @@ fn run_main_loop(main_context: *mut GMainContext) {
|
||||
#[cfg(not(any(feature = "blob", feature = "xnu-core")))]
|
||||
transport_get_unchecked().process();
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
if entrypoint_linux::stop_requested() {
|
||||
return;
|
||||
}
|
||||
|
||||
if stop_requested() {
|
||||
return;
|
||||
}
|
||||
@@ -1298,7 +1235,7 @@ fn kernel_half_has_work() -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-kext"))]
|
||||
if hostlink_chardev::a_turn_is_wanted() {
|
||||
return true;
|
||||
}
|
||||
@@ -1337,6 +1274,11 @@ fn kernel_half_has_work() -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
|
||||
if kernel::an_operation_waits() {
|
||||
return true;
|
||||
}
|
||||
|
||||
kernel::injected_arenas()
|
||||
.iter()
|
||||
.any(|arena| kernel::holds_a_frame_from_target(*arena))
|
||||
@@ -1370,6 +1312,9 @@ fn serve_the_kernel_half() {
|
||||
#[cfg(feature = "linux-injected")]
|
||||
kernel::report_what_the_copies_hit();
|
||||
|
||||
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
|
||||
kernel::carry_out_operations();
|
||||
|
||||
relay_frames_from_targets();
|
||||
}
|
||||
|
||||
@@ -1528,7 +1473,7 @@ unsafe extern "C" fn on_script_unloaded(
|
||||
|
||||
static UNLOADS_IN_FLIGHT: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
fn transport_teardown() {
|
||||
unsafe {
|
||||
let driver = TRANSPORT_DRIVER;
|
||||
@@ -2247,7 +2192,6 @@ fn text_or_empty(text: *const u8) -> *const core::ffi::c_char {
|
||||
}
|
||||
|
||||
fn send_command_reply(request_id: u16, response: HandlerResponse) {
|
||||
|
||||
unsafe {
|
||||
let message = g_variant_new(
|
||||
c"(yquv)".as_ptr(),
|
||||
@@ -2433,9 +2377,14 @@ fn handle_stop() -> HandlerResponse {
|
||||
kernel::show_our_threads_again();
|
||||
}
|
||||
|
||||
#[cfg(feature = "blob")]
|
||||
#[cfg(all(feature = "blob", not(feature = "linux-injected")))]
|
||||
STOP_REQUESTED.store(true, Ordering::Release);
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
if !kernel::loaded_from_the_inside() {
|
||||
STOP_REQUESTED.store(true, Ordering::Release);
|
||||
}
|
||||
|
||||
HandlerResponse::success(unsafe { g_variant_new_uint32(0) })
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,7 @@ pub extern "C" fn sysconf(_name: i32) -> isize {
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __clear_cache(_start: *const u8, _end: *const u8) {}
|
||||
|
||||
#[cfg(all(target_arch = "arm", not(any(feature = "linux", feature = "linux-injected"))))]
|
||||
#[cfg(all(target_arch = "arm", not(feature = "linux-injected")))]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __clear_cache(start: *const u8, end: *const u8) {
|
||||
unsafe {
|
||||
@@ -68,7 +68,7 @@ pub extern "C" fn __clear_cache(start: *const u8, end: *const u8) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "arm", any(feature = "linux", feature = "linux-injected")))]
|
||||
#[cfg(all(target_arch = "arm", feature = "linux-injected"))]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __clear_cache(start: *const u8, end: *const u8) {
|
||||
unsafe {
|
||||
|
||||
@@ -0,0 +1,402 @@
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::vec::Vec;
|
||||
use core::ffi::{c_int, c_void};
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use super::layout::field_offset;
|
||||
use super::native;
|
||||
|
||||
pub fn add_thread(tgid: i32, tid: i32) {
|
||||
let fresh = Cloak::of(tgid);
|
||||
registry().with(|cloaks| {
|
||||
let threads = &mut cloaks.entry(tgid).or_insert(fresh).threads;
|
||||
if !threads.contains(&tid) {
|
||||
threads.push(tid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn remove_thread(tgid: i32, tid: i32) {
|
||||
registry().with(|cloaks| {
|
||||
if let Some(cloak) = cloaks.get_mut(&tgid) {
|
||||
cloak.threads.retain(|t| *t != tid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn add_range(tgid: i32, base: usize, size: usize) {
|
||||
let fresh = Cloak::of(tgid);
|
||||
registry().with(|cloaks| {
|
||||
cloaks.entry(tgid).or_insert(fresh).ranges.push((base, base + size));
|
||||
});
|
||||
}
|
||||
|
||||
pub fn remove_range(tgid: i32, base: usize, size: usize) {
|
||||
registry().with(|cloaks| {
|
||||
if let Some(cloak) = cloaks.get_mut(&tgid) {
|
||||
if let Some(index) = cloak.ranges.iter().position(|r| *r == (base, base + size)) {
|
||||
cloak.ranges.swap_remove(index);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn add_fd(tgid: i32, fd: i32) {
|
||||
let fresh = Cloak::of(tgid);
|
||||
registry().with(|cloaks| {
|
||||
let fds = &mut cloaks.entry(tgid).or_insert(fresh).fds;
|
||||
if !fds.contains(&fd) {
|
||||
fds.push(fd);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn remove_fd(tgid: i32, fd: i32) {
|
||||
registry().with(|cloaks| {
|
||||
if let Some(cloak) = cloaks.get_mut(&tgid) {
|
||||
cloak.fds.retain(|f| *f != fd);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn install_hooks() {
|
||||
let Some(layout) = describe_readers() else {
|
||||
return;
|
||||
};
|
||||
unsafe { LAYOUT = Some(layout) };
|
||||
|
||||
let interceptor = unsafe { crate::bindings::gum_interceptor_obtain() };
|
||||
unsafe {
|
||||
crate::bindings::gum_interceptor_begin_transaction(interceptor);
|
||||
replace(interceptor, _show_map, frida_cb_show_map as *mut c_void, &raw mut SHOW_MAP);
|
||||
replace(interceptor, _show_smap, frida_cb_show_smap as *mut c_void, &raw mut SHOW_SMAP);
|
||||
replace(interceptor, _proc_fill_cache, frida_cb_proc_fill_cache as *mut c_void,
|
||||
&raw mut PROC_FILL_CACHE);
|
||||
replace(interceptor, _proc_pid_status, frida_cb_proc_pid_status as *mut c_void,
|
||||
&raw mut PROC_PID_STATUS);
|
||||
replace(interceptor, _do_task_stat, frida_cb_do_task_stat as *mut c_void, &raw mut DO_TASK_STAT);
|
||||
replace(interceptor, _release_task, frida_cb_release_task as *mut c_void, &raw mut RELEASE_TASK);
|
||||
crate::bindings::gum_interceptor_end_transaction(interceptor);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_hooks() {
|
||||
if unsafe { (*(&raw const LAYOUT)).is_none() } {
|
||||
return;
|
||||
}
|
||||
|
||||
let interceptor = unsafe { crate::bindings::gum_interceptor_obtain() };
|
||||
unsafe {
|
||||
crate::bindings::gum_interceptor_begin_transaction(interceptor);
|
||||
for target in [_show_map, _show_smap, _proc_fill_cache, _proc_pid_status, _do_task_stat, _release_task] {
|
||||
if target != 0 {
|
||||
crate::bindings::gum_interceptor_revert(interceptor, target as *mut c_void);
|
||||
}
|
||||
}
|
||||
crate::bindings::gum_interceptor_end_transaction(interceptor);
|
||||
}
|
||||
|
||||
registry().with(|cloaks| cloaks.clear());
|
||||
}
|
||||
|
||||
unsafe fn replace(interceptor: *mut crate::bindings::GumInterceptor, target: usize, replacement: *mut c_void,
|
||||
original: *mut usize) {
|
||||
if target == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
crate::bindings::gum_interceptor_replace(interceptor, target as *mut c_void, replacement,
|
||||
original as *mut *mut c_void, core::ptr::null_mut());
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_cb_show_map(m: *mut c_void, vma: *mut c_void) -> c_int {
|
||||
if hides_the_area(vma) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsafe { call::<ShowFn>(SHOW_MAP)(m, vma) }
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_cb_show_smap(m: *mut c_void, vma: *mut c_void) -> c_int {
|
||||
if hides_the_area(vma) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsafe { call::<ShowFn>(SHOW_SMAP)(m, vma) }
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_cb_proc_fill_cache(file: *mut c_void, ctx: *mut c_void, name: *const u8, len: u32,
|
||||
instantiate: usize, task: *mut c_void, ptr: *const c_void) -> bool {
|
||||
if hides_the_entry(instantiate, task as usize, ptr as usize) {
|
||||
return true;
|
||||
}
|
||||
|
||||
unsafe { call::<FillCacheFn>(PROC_FILL_CACHE)(file, ctx, name, len, instantiate, task, ptr) }
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_cb_proc_pid_status(m: *mut c_void, ns: *mut c_void, pid: *mut c_void,
|
||||
task: *mut c_void) -> c_int {
|
||||
let layout = layout();
|
||||
let start = read_word(m as usize + layout.seq_count);
|
||||
|
||||
let result = unsafe { call::<StatusFn>(PROC_PID_STATUS)(m, ns, pid, task) };
|
||||
|
||||
if let Some(hidden) = threads_hidden_from_the_reader(task as usize) {
|
||||
let said = said_since(m as usize, start);
|
||||
if let Some(at) = find(said, b"Threads:\t") {
|
||||
take_from_the_number_at(m as usize, start + at + b"Threads:\t".len(), hidden);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_cb_do_task_stat(m: *mut c_void, ns: *mut c_void, pid: *mut c_void,
|
||||
task: *mut c_void, whole: c_int) -> c_int {
|
||||
let layout = layout();
|
||||
let start = read_word(m as usize + layout.seq_count);
|
||||
|
||||
let result = unsafe { call::<StatFn>(DO_TASK_STAT)(m, ns, pid, task, whole) };
|
||||
|
||||
if let Some(hidden) = threads_hidden_from_the_reader(task as usize) {
|
||||
if let Some(at) = where_the_thread_count_is(said_since(m as usize, start)) {
|
||||
take_from_the_number_at(m as usize, start + at, hidden);
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_cb_release_task(task: *mut c_void) {
|
||||
let layout = layout();
|
||||
let task = task as usize;
|
||||
let tgid = read_i32(task + layout.task_tgid);
|
||||
|
||||
remove_thread(tgid, read_i32(task + layout.task_pid));
|
||||
if read_word(task + layout.task_group_leader) == task {
|
||||
registry().with(|cloaks| {
|
||||
cloaks.remove(&tgid);
|
||||
});
|
||||
}
|
||||
|
||||
unsafe { call::<ReleaseFn>(RELEASE_TASK)(task as *mut c_void) };
|
||||
}
|
||||
|
||||
fn hides_the_area(vma: *mut c_void) -> bool {
|
||||
let layout = layout();
|
||||
let vma = vma as usize;
|
||||
let mm = read_word(vma + layout.vma_mm);
|
||||
let start = read_word(vma + layout.vma_start);
|
||||
let end = read_word(vma + layout.vma_end);
|
||||
|
||||
!the_reader_is_cloaked() && registry().with(|cloaks| {
|
||||
cloaks.values().any(|cloak| {
|
||||
cloak.mm == mm && cloak.ranges.iter().any(|(s, e)| start < *e && *s < end)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn hides_the_entry(instantiate: usize, task: usize, ptr: usize) -> bool {
|
||||
if the_reader_is_cloaked() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let layout = layout();
|
||||
if instantiate == unsafe { _proc_task_instantiate } {
|
||||
let tid = read_i32(task + layout.task_pid);
|
||||
return registry().with(|cloaks| cloaks.values().any(|cloak| cloak.threads.contains(&tid)));
|
||||
}
|
||||
|
||||
if instantiate == unsafe { _proc_fd_instantiate } || instantiate == unsafe { _proc_fdinfo_instantiate } {
|
||||
let tgid = read_i32(task + layout.task_tgid);
|
||||
let fd = read_i32(ptr + layout.fd_data_fd);
|
||||
return registry().with(|cloaks| cloaks.get(&tgid).is_some_and(|cloak| cloak.fds.contains(&fd)));
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
fn threads_hidden_from_the_reader(task: usize) -> Option<i32> {
|
||||
if the_reader_is_cloaked() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let tgid = read_i32(task + layout().task_tgid);
|
||||
let hidden = registry().with(|cloaks| cloaks.get(&tgid).map_or(0, |cloak| cloak.threads.len()));
|
||||
|
||||
(hidden != 0).then_some(hidden as i32)
|
||||
}
|
||||
|
||||
// A cloaked thread is one of ours, so let it see through the cloak; RASP threads in the same
|
||||
// process are not cloaked and keep getting the censored view.
|
||||
fn the_reader_is_cloaked() -> bool {
|
||||
let tid = read_i32(native::current_task() as usize + layout().task_pid);
|
||||
|
||||
registry().with(|cloaks| cloaks.values().any(|cloak| cloak.threads.contains(&tid)))
|
||||
}
|
||||
|
||||
fn said_since(m: usize, start: usize) -> &'static [u8] {
|
||||
let layout = layout();
|
||||
let count = read_word(m + layout.seq_count);
|
||||
if start >= count || count >= read_word(m + layout.seq_size) {
|
||||
return &[];
|
||||
}
|
||||
|
||||
unsafe { core::slice::from_raw_parts((read_word(m + layout.seq_buf) + start) as *const u8, count - start) }
|
||||
}
|
||||
|
||||
fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
|
||||
haystack.windows(needle.len()).position(|window| window == needle)
|
||||
}
|
||||
|
||||
fn where_the_thread_count_is(said: &[u8]) -> Option<usize> {
|
||||
let name_end = said.iter().rposition(|b| *b == b')')?;
|
||||
|
||||
said[name_end..]
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, b)| **b == b' ')
|
||||
.nth(17)
|
||||
.map(|(at, _)| name_end + at + 1)
|
||||
}
|
||||
|
||||
fn take_from_the_number_at(m: usize, at: usize, hidden: i32) {
|
||||
let layout = layout();
|
||||
let buf = read_word(m + layout.seq_buf);
|
||||
let count = read_word(m + layout.seq_count);
|
||||
let said = unsafe { core::slice::from_raw_parts_mut(buf as *mut u8, count) };
|
||||
|
||||
let digits = said[at..].iter().take_while(|b| b.is_ascii_digit()).count();
|
||||
let value = said[at..at + digits].iter().fold(0i32, |n, b| n * 10 + i32::from(b - b'0'));
|
||||
let shown = if value > hidden { value - hidden } else { 1 };
|
||||
let rendered = alloc::format!("{shown}");
|
||||
|
||||
said.copy_within(at + digits..count, at + rendered.len());
|
||||
said[at..at + rendered.len()].copy_from_slice(rendered.as_bytes());
|
||||
write_word(m + layout.seq_count, count - digits + rendered.len());
|
||||
}
|
||||
|
||||
struct Cloak {
|
||||
mm: usize,
|
||||
threads: Vec<i32>,
|
||||
ranges: Vec<(usize, usize)>,
|
||||
fds: Vec<i32>,
|
||||
}
|
||||
|
||||
impl Cloak {
|
||||
fn of(tgid: i32) -> Self {
|
||||
let mm = super::processes::HeldTask::with_id(tgid as u32)
|
||||
.map_or(0, |task| read_word(task.address() + layout().task_mm));
|
||||
|
||||
Self { mm, threads: Vec::new(), ranges: Vec::new(), fds: Vec::new() }
|
||||
}
|
||||
}
|
||||
|
||||
struct Registry {
|
||||
busy: AtomicBool,
|
||||
cloaks: core::cell::UnsafeCell<BTreeMap<i32, Cloak>>,
|
||||
}
|
||||
|
||||
unsafe impl Sync for Registry {}
|
||||
|
||||
impl Registry {
|
||||
fn with<R>(&self, action: impl FnOnce(&mut BTreeMap<i32, Cloak>) -> R) -> R {
|
||||
native::disable_preemption();
|
||||
while self.busy.swap(true, Ordering::Acquire) {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
let result = action(unsafe { &mut *self.cloaks.get() });
|
||||
self.busy.store(false, Ordering::Release);
|
||||
native::enable_preemption();
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
fn registry() -> &'static Registry {
|
||||
®ISTRY
|
||||
}
|
||||
|
||||
static REGISTRY: Registry = Registry {
|
||||
busy: AtomicBool::new(false),
|
||||
cloaks: core::cell::UnsafeCell::new(BTreeMap::new()),
|
||||
};
|
||||
|
||||
struct ReaderLayout {
|
||||
task_pid: usize,
|
||||
task_tgid: usize,
|
||||
task_mm: usize,
|
||||
task_group_leader: usize,
|
||||
vma_mm: usize,
|
||||
vma_start: usize,
|
||||
vma_end: usize,
|
||||
seq_buf: usize,
|
||||
seq_size: usize,
|
||||
seq_count: usize,
|
||||
fd_data_fd: usize,
|
||||
}
|
||||
|
||||
fn describe_readers() -> Option<ReaderLayout> {
|
||||
Some(ReaderLayout {
|
||||
task_pid: field_offset("task_struct", "pid")?,
|
||||
task_tgid: field_offset("task_struct", "tgid")?,
|
||||
task_mm: field_offset("task_struct", "mm")?,
|
||||
task_group_leader: field_offset("task_struct", "group_leader")?,
|
||||
vma_mm: field_offset("vm_area_struct", "vm_mm")?,
|
||||
vma_start: field_offset("vm_area_struct", "vm_start")?,
|
||||
vma_end: field_offset("vm_area_struct", "vm_end")?,
|
||||
seq_buf: field_offset("seq_file", "buf")?,
|
||||
seq_size: field_offset("seq_file", "size")?,
|
||||
seq_count: field_offset("seq_file", "count")?,
|
||||
fd_data_fd: field_offset("fd_data", "fd")?,
|
||||
})
|
||||
}
|
||||
|
||||
fn layout() -> &'static ReaderLayout {
|
||||
unsafe { (*(&raw const LAYOUT)).as_ref().unwrap() }
|
||||
}
|
||||
|
||||
static mut LAYOUT: Option<ReaderLayout> = None;
|
||||
|
||||
unsafe fn call<F: Copy>(original: usize) -> F {
|
||||
unsafe { core::mem::transmute_copy(&original) }
|
||||
}
|
||||
|
||||
fn read_word(address: usize) -> usize {
|
||||
unsafe { (address as *const usize).read() }
|
||||
}
|
||||
|
||||
fn write_word(address: usize, value: usize) {
|
||||
unsafe { (address as *mut usize).write(value) };
|
||||
}
|
||||
|
||||
fn read_i32(address: usize) -> i32 {
|
||||
unsafe { (address as *const i32).read() }
|
||||
}
|
||||
|
||||
type ShowFn = unsafe extern "C" fn(*mut c_void, *mut c_void) -> c_int;
|
||||
type FillCacheFn =
|
||||
unsafe extern "C" fn(*mut c_void, *mut c_void, *const u8, u32, usize, *mut c_void, *const c_void) -> bool;
|
||||
type StatusFn = unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void, *mut c_void) -> c_int;
|
||||
type StatFn = unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void, *mut c_void, c_int) -> c_int;
|
||||
type ReleaseFn = unsafe extern "C" fn(*mut c_void);
|
||||
|
||||
static mut SHOW_MAP: usize = 0;
|
||||
static mut SHOW_SMAP: usize = 0;
|
||||
static mut PROC_FILL_CACHE: usize = 0;
|
||||
static mut PROC_PID_STATUS: usize = 0;
|
||||
static mut DO_TASK_STAT: usize = 0;
|
||||
static mut RELEASE_TASK: usize = 0;
|
||||
|
||||
unsafe extern "C" {
|
||||
static _show_map: usize;
|
||||
static _show_smap: usize;
|
||||
static _proc_fill_cache: usize;
|
||||
static _proc_pid_status: usize;
|
||||
static _do_task_stat: usize;
|
||||
static _release_task: usize;
|
||||
static _proc_task_instantiate: usize;
|
||||
static _proc_fd_instantiate: usize;
|
||||
static _proc_fdinfo_instantiate: usize;
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
use alloc::vec;
|
||||
use core::ffi::{c_int, c_long, c_void};
|
||||
use core::mem::size_of;
|
||||
use core::ptr;
|
||||
use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
|
||||
|
||||
use super::layout::field_offset;
|
||||
use super::{cloak, native, process_memory, user_threads};
|
||||
|
||||
pub fn open() {
|
||||
cloak::install_hooks();
|
||||
|
||||
let Some(arguments) = describe_arguments() else {
|
||||
return;
|
||||
};
|
||||
let target = unsafe { SYS_PRCTL_TARGET };
|
||||
if target == 0 {
|
||||
return;
|
||||
}
|
||||
unsafe { ARGUMENTS = Some(arguments) };
|
||||
|
||||
let interceptor = unsafe { crate::bindings::gum_interceptor_obtain() };
|
||||
unsafe {
|
||||
crate::bindings::gum_interceptor_begin_transaction(interceptor);
|
||||
crate::bindings::gum_interceptor_replace(interceptor, target as *mut c_void,
|
||||
frida_cb_sys_prctl as *mut c_void, (&raw mut SYS_PRCTL) as *mut *mut c_void, ptr::null_mut());
|
||||
crate::bindings::gum_interceptor_end_transaction(interceptor);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close() {
|
||||
if unsafe { (*(&raw const ARGUMENTS)).is_some() } {
|
||||
let interceptor = unsafe { crate::bindings::gum_interceptor_obtain() };
|
||||
unsafe {
|
||||
crate::bindings::gum_interceptor_begin_transaction(interceptor);
|
||||
crate::bindings::gum_interceptor_revert(interceptor, SYS_PRCTL_TARGET as *mut c_void);
|
||||
crate::bindings::gum_interceptor_end_transaction(interceptor);
|
||||
}
|
||||
}
|
||||
|
||||
cloak::remove_hooks();
|
||||
}
|
||||
|
||||
pub fn an_operation_waits() -> bool {
|
||||
!PENDING.load(Ordering::Acquire).is_null()
|
||||
}
|
||||
|
||||
pub fn carry_out_operations() {
|
||||
let operation = PENDING.swap(ptr::null_mut(), Ordering::AcqRel);
|
||||
if operation.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
(*operation).result = match (*operation).request {
|
||||
Request::Allocate { id, size, protection } => process_memory::allocate(id, size, protection) as c_long,
|
||||
Request::Release { id, address, size } => process_memory::release(id, address, size) as c_long,
|
||||
Request::StartThread { id, entry, stack, argument, tls } => {
|
||||
user_threads::start_in_process(id, entry, stack, argument, tls) as c_long
|
||||
}
|
||||
};
|
||||
(*operation).done.store(true, Ordering::Release);
|
||||
}
|
||||
|
||||
native::wake(wake_token());
|
||||
}
|
||||
|
||||
unsafe extern "C" fn frida_cb_sys_prctl(regs: *const c_void) -> c_long {
|
||||
let argument = |index: usize| read_word(regs as usize + unsafe { arguments().at[index] });
|
||||
|
||||
if argument(0) as c_int != CONTROL_MAGIC || argument(4) as u64 != CONTROL_TOKEN {
|
||||
let original: unsafe extern "C" fn(*const c_void) -> c_long = unsafe { core::mem::transmute(SYS_PRCTL) };
|
||||
return unsafe { original(regs) };
|
||||
}
|
||||
|
||||
let op = argument(1) as u32;
|
||||
let (first, second) = (argument(2), argument(3));
|
||||
|
||||
if op == OP_PING {
|
||||
return CONTROL_MAGIC as c_long;
|
||||
}
|
||||
|
||||
if op >= OP_ALLOC && !unsafe { _capable(CAP_SYS_ADMIN) } {
|
||||
return -(EPERM as c_long);
|
||||
}
|
||||
|
||||
let tgid = read_i32(native::current_task() as usize + unsafe { arguments().task_tgid });
|
||||
match op {
|
||||
OP_ADD_THREAD => cloak::add_thread(tgid, first as i32),
|
||||
OP_REMOVE_THREAD => cloak::remove_thread(tgid, first as i32),
|
||||
OP_ADD_RANGE => cloak::add_range(tgid, first, second),
|
||||
OP_REMOVE_RANGE => cloak::remove_range(tgid, first, second),
|
||||
OP_ADD_FD => cloak::add_fd(tgid, first as i32),
|
||||
OP_REMOVE_FD => cloak::remove_fd(tgid, first as i32),
|
||||
OP_ALLOC => return allocate(first),
|
||||
OP_FREE => return release(first),
|
||||
OP_WRITE => return write(first),
|
||||
OP_READ => return read(first),
|
||||
OP_SPAWN => return start_thread(first),
|
||||
OP_CLOAK_THREAD => return cloak_thread(first),
|
||||
OP_CLOAK_RANGE => return cloak_range(first),
|
||||
_ => return -(EINVAL as c_long),
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
fn allocate(arguments: usize) -> c_long {
|
||||
let Some(args) = arguments_at::<AllocArgs>(arguments) else {
|
||||
return -(EFAULT as c_long);
|
||||
};
|
||||
|
||||
hand_to_the_worker(Request::Allocate { id: args.pid, size: args.size as usize, protection: args.prot as usize })
|
||||
}
|
||||
|
||||
fn release(arguments: usize) -> c_long {
|
||||
let Some(args) = arguments_at::<FreeArgs>(arguments) else {
|
||||
return -(EFAULT as c_long);
|
||||
};
|
||||
|
||||
hand_to_the_worker(Request::Release { id: args.pid, address: args.address as usize, size: args.size as usize })
|
||||
}
|
||||
|
||||
fn write(arguments: usize) -> c_long {
|
||||
let Some(args) = arguments_at::<RwArgs>(arguments) else {
|
||||
return -(EFAULT as c_long);
|
||||
};
|
||||
|
||||
let mut bounce = vec![0u8; args.size as usize];
|
||||
if !process_memory::read_own(args.buffer as usize, &mut bounce) {
|
||||
return -(EFAULT as c_long);
|
||||
}
|
||||
|
||||
process_memory::write(args.pid, args.address as usize, &bounce) as c_long
|
||||
}
|
||||
|
||||
fn read(arguments: usize) -> c_long {
|
||||
let Some(args) = arguments_at::<RwArgs>(arguments) else {
|
||||
return -(EFAULT as c_long);
|
||||
};
|
||||
|
||||
let mut bounce = vec![0u8; args.size as usize];
|
||||
let transferred = process_memory::read(args.pid, args.address as usize, &mut bounce);
|
||||
|
||||
if !process_memory::write_own(args.buffer as usize, &bounce) {
|
||||
return -(EFAULT as c_long);
|
||||
}
|
||||
|
||||
transferred as c_long
|
||||
}
|
||||
|
||||
fn start_thread(arguments: usize) -> c_long {
|
||||
let Some(args) = arguments_at::<SpawnArgs>(arguments) else {
|
||||
return -(EFAULT as c_long);
|
||||
};
|
||||
|
||||
hand_to_the_worker(Request::StartThread {
|
||||
id: args.pid,
|
||||
entry: args.entry as usize,
|
||||
stack: args.stack as usize,
|
||||
argument: args.arg as usize,
|
||||
tls: args.tls as usize,
|
||||
})
|
||||
}
|
||||
|
||||
fn cloak_thread(arguments: usize) -> c_long {
|
||||
let Some(args) = arguments_at::<CloakThreadArgs>(arguments) else {
|
||||
return -(EFAULT as c_long);
|
||||
};
|
||||
|
||||
cloak::add_thread(args.tgid as i32, args.tid as i32);
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
fn cloak_range(arguments: usize) -> c_long {
|
||||
let Some(args) = arguments_at::<CloakRangeArgs>(arguments) else {
|
||||
return -(EFAULT as c_long);
|
||||
};
|
||||
|
||||
cloak::add_range(args.tgid as i32, args.base as usize, args.size as usize);
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
fn arguments_at<T: Default>(address: usize) -> Option<T> {
|
||||
let mut value = T::default();
|
||||
let raw = unsafe { core::slice::from_raw_parts_mut(&mut value as *mut T as *mut u8, size_of::<T>()) };
|
||||
|
||||
process_memory::read_own(address, raw).then_some(value)
|
||||
}
|
||||
|
||||
fn hand_to_the_worker(request: Request) -> c_long {
|
||||
let token = wake_token();
|
||||
|
||||
while BUSY.swap(true, Ordering::Acquire) {
|
||||
native::wait(token, None, &mut || !BUSY.load(Ordering::Acquire));
|
||||
}
|
||||
|
||||
let mut operation = Operation { request, result: 0, done: AtomicBool::new(false) };
|
||||
PENDING.store(&mut operation, Ordering::Release);
|
||||
crate::nudge_the_loop(token);
|
||||
|
||||
while !operation.done.load(Ordering::Acquire) {
|
||||
native::wait(token, None, &mut || operation.done.load(Ordering::Acquire));
|
||||
}
|
||||
|
||||
BUSY.store(false, Ordering::Release);
|
||||
native::wake(token);
|
||||
|
||||
operation.result
|
||||
}
|
||||
|
||||
fn wake_token() -> *const u8 {
|
||||
(&raw const crate::glib::WAKEUP_TOKEN) as *const u8
|
||||
}
|
||||
|
||||
struct Operation {
|
||||
request: Request,
|
||||
result: c_long,
|
||||
done: AtomicBool,
|
||||
}
|
||||
|
||||
enum Request {
|
||||
Allocate { id: u32, size: usize, protection: usize },
|
||||
Release { id: u32, address: usize, size: usize },
|
||||
StartThread { id: u32, entry: usize, stack: usize, argument: usize, tls: usize },
|
||||
}
|
||||
|
||||
static PENDING: AtomicPtr<Operation> = AtomicPtr::new(ptr::null_mut());
|
||||
static BUSY: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
struct AllocArgs {
|
||||
size: u64,
|
||||
pid: u32,
|
||||
prot: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
struct FreeArgs {
|
||||
address: u64,
|
||||
size: u64,
|
||||
pid: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
struct RwArgs {
|
||||
address: u64,
|
||||
buffer: u64,
|
||||
size: u64,
|
||||
pid: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
struct SpawnArgs {
|
||||
entry: u64,
|
||||
stack: u64,
|
||||
arg: u64,
|
||||
tls: u64,
|
||||
pid: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
struct CloakThreadArgs {
|
||||
tgid: u32,
|
||||
tid: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Default)]
|
||||
struct CloakRangeArgs {
|
||||
base: u64,
|
||||
size: u64,
|
||||
tgid: u32,
|
||||
}
|
||||
|
||||
struct ArgumentLayout {
|
||||
at: [usize; 5],
|
||||
task_tgid: usize,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn describe_arguments() -> Option<ArgumentLayout> {
|
||||
let regs = field_offset("pt_regs", "regs")?;
|
||||
|
||||
Some(ArgumentLayout {
|
||||
at: core::array::from_fn(|index| regs + index * size_of::<usize>()),
|
||||
task_tgid: field_offset("task_struct", "tgid")?,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn describe_arguments() -> Option<ArgumentLayout> {
|
||||
Some(ArgumentLayout {
|
||||
at: [
|
||||
field_offset("pt_regs", "di")?,
|
||||
field_offset("pt_regs", "si")?,
|
||||
field_offset("pt_regs", "dx")?,
|
||||
field_offset("pt_regs", "r10")?,
|
||||
field_offset("pt_regs", "r8")?,
|
||||
],
|
||||
task_tgid: field_offset("task_struct", "tgid")?,
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn arguments() -> &'static ArgumentLayout {
|
||||
unsafe { (*(&raw const ARGUMENTS)).as_ref().unwrap() }
|
||||
}
|
||||
|
||||
static mut ARGUMENTS: Option<ArgumentLayout> = None;
|
||||
static mut SYS_PRCTL: usize = 0;
|
||||
|
||||
fn read_word(address: usize) -> usize {
|
||||
unsafe { (address as *const usize).read() }
|
||||
}
|
||||
|
||||
fn read_i32(address: usize) -> i32 {
|
||||
unsafe { (address as *const i32).read() }
|
||||
}
|
||||
|
||||
const CONTROL_MAGIC: c_int = 0x4652_4944;
|
||||
const CONTROL_TOKEN: u64 = 0x1d5f_9e6b_2c7a_4038;
|
||||
|
||||
const OP_ADD_THREAD: u32 = 1;
|
||||
const OP_REMOVE_THREAD: u32 = 2;
|
||||
const OP_ADD_RANGE: u32 = 3;
|
||||
const OP_REMOVE_RANGE: u32 = 4;
|
||||
const OP_ADD_FD: u32 = 5;
|
||||
const OP_REMOVE_FD: u32 = 6;
|
||||
const OP_PING: u32 = 32;
|
||||
const OP_ALLOC: u32 = 64;
|
||||
const OP_FREE: u32 = 65;
|
||||
const OP_WRITE: u32 = 66;
|
||||
const OP_READ: u32 = 67;
|
||||
const OP_SPAWN: u32 = 68;
|
||||
const OP_CLOAK_THREAD: u32 = 69;
|
||||
const OP_CLOAK_RANGE: u32 = 70;
|
||||
|
||||
const CAP_SYS_ADMIN: c_int = 21;
|
||||
|
||||
const EPERM: c_int = 1;
|
||||
const EFAULT: c_int = 14;
|
||||
const EINVAL: c_int = 22;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use self::___arm64_sys_prctl as SYS_PRCTL_TARGET;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
use self::___x64_sys_prctl as SYS_PRCTL_TARGET;
|
||||
|
||||
unsafe extern "C" {
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static ___arm64_sys_prctl: usize;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
static ___x64_sys_prctl: usize;
|
||||
static _capable: unsafe extern "C" fn(c_int) -> bool;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use core::ffi::{c_int, c_void};
|
||||
use core::ptr;
|
||||
|
||||
use alloc::boxed::Box;
|
||||
use alloc::collections::BTreeMap;
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
@@ -63,17 +64,28 @@ fn take_what_the_copy_opened(placed: &mut Placement) {
|
||||
let copy = COPY_TASK.load(core::sync::atomic::Ordering::Acquire) as *mut c_void;
|
||||
placed.says = native::take_the_file_of(copy, says_fd);
|
||||
placed.hears = native::take_the_file_of(copy, hears_fd);
|
||||
listen_to_the_copy(placed.says);
|
||||
listen_to_the_copy(placed);
|
||||
}
|
||||
|
||||
fn listen_to_the_copy(says: *mut c_void) {
|
||||
native::spawn_thread(carry_what_the_copy_says, says);
|
||||
fn listen_to_the_copy(placed: &Placement) {
|
||||
let listener = Box::into_raw(Box::new(Listener { says: placed.says, arena: placed.arena }));
|
||||
native::spawn_thread(carry_what_the_copy_says, listener as *mut c_void);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn carry_what_the_copy_says(argument: *mut c_void, _reason: i32) {
|
||||
while native::wait_for_a_word(argument) {
|
||||
let listener = unsafe { Box::from_raw(argument as *mut Listener) };
|
||||
|
||||
while native::wait_for_a_word(listener.says) {
|
||||
crate::nudge_the_loop(crate::glib::wakeup_token());
|
||||
}
|
||||
|
||||
Arena::at(listener.arena).gone();
|
||||
crate::nudge_the_loop(crate::glib::wakeup_token());
|
||||
}
|
||||
|
||||
struct Listener {
|
||||
says: *mut c_void,
|
||||
arena: usize,
|
||||
}
|
||||
|
||||
struct Placement {
|
||||
@@ -293,10 +305,6 @@ fn give(destination: usize, source: usize, size: usize) -> Option<()> {
|
||||
unsafe { copy_to_user(destination as *mut c_void, source as *const c_void, size) };
|
||||
unsafe { close_user_access(opened) };
|
||||
if left != 0 {
|
||||
if left == size {
|
||||
} else if left < size {
|
||||
} else {
|
||||
}
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -366,23 +374,25 @@ pub unsafe extern "C" fn frida_cb_user(argument: *mut c_void) -> c_int {
|
||||
|
||||
COPY_TASK.store(native::current_task() as usize, core::sync::atomic::Ordering::Release);
|
||||
|
||||
hand_userland_to(bootstrap, hand_over_argument(stack, arena), arena);
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
pub(crate) fn hand_userland_to(pc: usize, stack: usize, first_argument: usize) {
|
||||
let Some(places) = describe_registers() else {
|
||||
return 0;
|
||||
return;
|
||||
};
|
||||
let registers = registers_of_this_task(&places);
|
||||
|
||||
let stack = hand_over_argument(stack, arena);
|
||||
|
||||
unsafe {
|
||||
((registers + places.pc) as *mut usize).write(bootstrap & !THUMB_BIT);
|
||||
((registers + places.pc) as *mut usize).write(pc & !THUMB_BIT);
|
||||
((registers + places.stack) as *mut usize).write(stack);
|
||||
((registers + places.flags) as *mut usize).write(
|
||||
USER_EXECUTION | (bootstrap & THUMB_BIT) * THUMB_STATE);
|
||||
((registers + places.first) as *mut usize).write(arena);
|
||||
USER_EXECUTION | (pc & THUMB_BIT) * THUMB_STATE);
|
||||
((registers + places.first) as *mut usize).write(first_argument);
|
||||
write_selectors(registers, &places);
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
// What a task starts with in userland is at the top of the kernel stack it was given.
|
||||
@@ -394,7 +404,7 @@ fn registers_of_this_task(places: &Places) -> usize {
|
||||
fn registers_of_task(task: usize, places: &Places) -> usize {
|
||||
let stack = unsafe { ((task + places.stack_of_task) as *const usize).read() };
|
||||
|
||||
stack + STACK_SPAN - TOP_OF_STACK_PADDING - places.size
|
||||
stack + places.stack_span - TOP_OF_STACK_PADDING - places.size
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -688,6 +698,7 @@ unsafe fn write_selectors(registers: usize, _places: &Places) {
|
||||
unsafe {
|
||||
((registers + 136) as *mut usize).write(0x33);
|
||||
((registers + 160) as *mut usize).write(0x2b);
|
||||
((registers + 120) as *mut isize).write(-1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -707,15 +718,25 @@ unsafe fn write_selectors(_registers: usize, _places: &Places) {}
|
||||
#[cfg(target_arch = "x86")]
|
||||
fn hand_over_argument(stack: usize, arena: usize) -> usize {
|
||||
let frame = [0usize, arena];
|
||||
let sp = stack - size_of::<[usize; 2]>();
|
||||
let aligned_argument = stack - 16;
|
||||
let sp = aligned_argument - size_of::<usize>();
|
||||
|
||||
match give(sp, frame.as_ptr() as usize, size_of::<[usize; 2]>()) {
|
||||
Some(()) => sp,
|
||||
None => stack,
|
||||
}
|
||||
give(sp, frame.as_ptr() as usize, size_of::<[usize; 2]>());
|
||||
|
||||
sp
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn hand_over_argument(stack: usize, _arena: usize) -> usize {
|
||||
let return_address = 0usize;
|
||||
let sp = stack - size_of::<usize>();
|
||||
|
||||
give(sp, &return_address as *const usize as usize, size_of::<usize>());
|
||||
|
||||
sp
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
fn hand_over_argument(stack: usize, _arena: usize) -> usize {
|
||||
stack
|
||||
}
|
||||
@@ -727,6 +748,7 @@ struct Places {
|
||||
pc: usize,
|
||||
flags: usize,
|
||||
stack_of_task: usize,
|
||||
stack_span: usize,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
@@ -738,6 +760,7 @@ fn describe_registers() -> Option<Places> {
|
||||
pc: field_offset("user_pt_regs", "pc")?,
|
||||
flags: field_offset("user_pt_regs", "pstate")?,
|
||||
stack_of_task: field_offset("task_struct", "stack")?,
|
||||
stack_span: struct_size("thread_union").unwrap_or(STACK_SPAN),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -750,6 +773,7 @@ fn describe_registers() -> Option<Places> {
|
||||
pc: 15 * 4,
|
||||
flags: 16 * 4,
|
||||
stack_of_task: 0,
|
||||
stack_span: STACK_SPAN,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -762,6 +786,7 @@ fn describe_registers() -> Option<Places> {
|
||||
pc: 48,
|
||||
flags: 56,
|
||||
stack_of_task: 0,
|
||||
stack_span: STACK_SPAN,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -774,6 +799,7 @@ fn describe_registers() -> Option<Places> {
|
||||
pc: 128,
|
||||
flags: 144,
|
||||
stack_of_task: 0,
|
||||
stack_span: STACK_SPAN,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -926,7 +952,7 @@ pub unsafe extern "C" fn frida_cb_exec(_data: *mut c_void, task: *mut c_void, _w
|
||||
((program as usize + at) as *const *const u8).read()
|
||||
});
|
||||
|
||||
native::wake(task as *const u8);
|
||||
native::wake(crate::glib::wakeup_token());
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
@@ -963,7 +989,7 @@ unsafe extern "C" fn frida_replaced_set_task_comm(task: *mut c_void, buf: *const
|
||||
if let Some(id) = id_of(task as usize) {
|
||||
native::send_signal(STOP, task as usize);
|
||||
super::spawn::note_pending_spawn(id, name.to_bytes());
|
||||
native::wake(ptr::null());
|
||||
native::wake(crate::glib::wakeup_token());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1056,9 +1082,8 @@ fn ask_it_to_leave(placed: &Placement) {
|
||||
arena.tell_it_to_go();
|
||||
wake_the_copy(placed);
|
||||
|
||||
let waited_on = &placed.arena as *const usize as *const u8;
|
||||
while !arena.has_gone() {
|
||||
native::wait(waited_on, Some(LEAVING_GRACE_US), &mut || arena.has_gone());
|
||||
native::wait(crate::glib::wakeup_token(), None, &mut || arena.has_gone());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1085,7 +1110,6 @@ fn take_back_what_it_was_given(placed: &Placement, memory: *mut c_void) {
|
||||
}
|
||||
}
|
||||
|
||||
const LEAVING_GRACE_US: u64 = 100_000;
|
||||
|
||||
pub fn tell_the_copy(id: u32) {
|
||||
let Some(placed) = (unsafe { placements() }).get(&id) else {
|
||||
@@ -1284,7 +1308,6 @@ const EXEC_PROBE: unsafe extern "C" fn(*mut c_void, *mut c_void, c_int, *mut c_v
|
||||
const EXEC_PROBE: unsafe extern "C" fn(*mut c_void, *mut c_void, c_int, *mut c_void) =
|
||||
frida_cb_exec;
|
||||
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
const THREAD_APPEARED_PROBE: unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void) =
|
||||
frida_kcb_thread_appeared;
|
||||
|
||||
@@ -3,6 +3,8 @@ use core::ffi::{c_int, c_void};
|
||||
use core::ffi::c_long;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
use core::ptr;
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
use crate::bindings::{
|
||||
gum_barebone_handle_exception, GumCpuContext,
|
||||
@@ -322,10 +324,15 @@ pub fn install() {
|
||||
_patch_text(addr.add(1) as *mut c_void, frida_arm_fixup as *const () as usize as u32);
|
||||
_patch_text(addr as *mut c_void, 0xe51ff004);
|
||||
}
|
||||
ACTIVATED.store(true, Ordering::Release);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub fn uninstall() {
|
||||
if !ACTIVATED.swap(false, Ordering::AcqRel) {
|
||||
return;
|
||||
}
|
||||
|
||||
let target = unsafe { _fixup_exception } as *mut u32;
|
||||
unsafe {
|
||||
_patch_text(target.add(1) as *mut c_void, PROLOGUE[1]);
|
||||
@@ -437,6 +444,9 @@ unsafe extern "C" {
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PROLOGUE: [u32; 4] = [0xd503233f, 0xa9be7bfd, 0x910003fd, 0xa90153f3];
|
||||
|
||||
#[cfg(any(target_arch = "arm", target_arch = "aarch64"))]
|
||||
static ACTIVATED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[unsafe(no_mangle)]
|
||||
static mut FRIDA_ARM64_ORIGINAL_CONT: u64 = 0;
|
||||
@@ -452,7 +462,7 @@ pub fn install() {
|
||||
|
||||
unsafe { FRIDA_ARM64_ORIGINAL_CONT = target.add(4) as u64 };
|
||||
|
||||
let jump = detour_to(frida_arm64_fixup as *const () as usize);
|
||||
let jump = redirect_to(frida_arm64_fixup as *const () as usize);
|
||||
unsafe {
|
||||
let addr = target as *mut u32;
|
||||
_aarch64_insn_patch_text_nosync(addr.add(2) as *mut c_void, jump[2]);
|
||||
@@ -460,10 +470,15 @@ pub fn install() {
|
||||
_aarch64_insn_patch_text_nosync(addr.add(1) as *mut c_void, jump[1]);
|
||||
_aarch64_insn_patch_text_nosync(addr as *mut c_void, jump[0]);
|
||||
}
|
||||
ACTIVATED.store(true, Ordering::Release);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub fn uninstall() {
|
||||
if !ACTIVATED.swap(false, Ordering::AcqRel) {
|
||||
return;
|
||||
}
|
||||
|
||||
let target = unsafe { _fixup_exception } as *mut u32;
|
||||
unsafe {
|
||||
for slot in 0..4 {
|
||||
@@ -473,7 +488,7 @@ pub fn uninstall() {
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn detour_to(destination: usize) -> [u32; 4] {
|
||||
fn redirect_to(destination: usize) -> [u32; 4] {
|
||||
[0x58000050, 0xd61f0200, destination as u32, (destination >> 32) as u32]
|
||||
}
|
||||
|
||||
@@ -542,7 +557,7 @@ unsafe fn recovered(regs: *mut c_void) -> bool {
|
||||
extern "C" fn frida_arm64_park() -> ! {
|
||||
static mut PARK_TOKEN: u8 = 0;
|
||||
loop {
|
||||
crate::kernel::wait(core::ptr::addr_of!(PARK_TOKEN), Some(1_000_000), &mut || false);
|
||||
crate::kernel::wait(core::ptr::addr_of!(PARK_TOKEN), None, &mut || false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,357 +0,0 @@
|
||||
// The agent as a kernel module: every primitive is an ordinary call into
|
||||
// linux/frida-kmod.c, compiled by kbuild against the target kernel's headers,
|
||||
// which is what keeps us out of the business of guessing struct layouts,
|
||||
// config-dependent macros and per-version symbol names.
|
||||
|
||||
use core::ffi::{CStr, c_char, c_int, c_void};
|
||||
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::{LoadedModule, ModuleEvent};
|
||||
use crate::kernel::ThreadEntry;
|
||||
|
||||
pub fn log(msg: &str) {
|
||||
unsafe { frida_kmod_log(msg.as_ptr() as *const c_char) };
|
||||
}
|
||||
|
||||
pub fn panic(msg: &str) -> ! {
|
||||
unsafe { frida_kmod_panic(msg.as_ptr() as *const c_char) }
|
||||
}
|
||||
|
||||
pub fn run_when_ready(action: fn()) {
|
||||
action();
|
||||
}
|
||||
|
||||
pub fn spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> isize {
|
||||
unsafe { frida_kmod_spawn_thread(entry, parameter) as isize }
|
||||
}
|
||||
|
||||
pub fn install_hooks() {
|
||||
unsafe { frida_kmod_install_hooks() };
|
||||
}
|
||||
|
||||
pub fn alloc(size: usize) -> *mut u8 {
|
||||
unsafe { frida_kmod_alloc(size) }
|
||||
}
|
||||
|
||||
pub fn free(ptr: *mut u8, size: usize) {
|
||||
unsafe { frida_kmod_free(ptr, size) };
|
||||
}
|
||||
|
||||
pub fn alloc_code(size: usize) -> *mut u8 {
|
||||
unsafe { frida_kmod_alloc_code(size) }
|
||||
}
|
||||
|
||||
pub fn free_code(ptr: *mut u8, size: usize) {
|
||||
unsafe { frida_kmod_free_code(ptr, size) };
|
||||
}
|
||||
|
||||
pub fn page_size() -> usize {
|
||||
crate::gum::page_size_the_kernel_runs_with()
|
||||
}
|
||||
|
||||
pub fn alloc_heap(size: usize) -> *mut u8 {
|
||||
alloc(size)
|
||||
}
|
||||
|
||||
pub fn alloc_dma(size: usize) -> *mut u8 {
|
||||
alloc_code(size)
|
||||
}
|
||||
|
||||
pub fn free_dma(ptr: *mut u8, size: usize) {
|
||||
free_code(ptr, size);
|
||||
}
|
||||
|
||||
pub fn own_range() -> (u64, u64) {
|
||||
let mut base: u64 = 0;
|
||||
let mut size: u64 = 0;
|
||||
unsafe { frida_kmod_own_range(&mut base, &mut size) };
|
||||
(base, size)
|
||||
}
|
||||
|
||||
// The sequence number is taken before `check` runs, so a wakeup racing with the
|
||||
// condition we are about to observe still makes the commit return immediately.
|
||||
pub fn wait(_token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut() -> bool) {
|
||||
let seq = unsafe { frida_kmod_wait_prepare() };
|
||||
|
||||
if check() {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe { frida_kmod_wait_commit(seq, timeout_us.map_or(-1, |us| us as i64)) };
|
||||
}
|
||||
|
||||
pub fn poke_loop_wakeup() {}
|
||||
|
||||
pub fn wake(_token: *const u8) {
|
||||
unsafe { frida_kmod_wake() };
|
||||
}
|
||||
|
||||
/// Gives the scheduler a chance to run something else. A kernel thread that loops
|
||||
/// without doing this is a hard lockup waiting to happen.
|
||||
pub fn yield_now() {
|
||||
unsafe { frida_kmod_yield() };
|
||||
}
|
||||
|
||||
pub fn monotonic_micros() -> i64 {
|
||||
unsafe { frida_kmod_monotonic_micros() }
|
||||
}
|
||||
|
||||
pub fn wall_clock_micros() -> (u32, u32) {
|
||||
let mut secs: u32 = 0;
|
||||
let mut micros: u32 = 0;
|
||||
unsafe { frida_kmod_wall_clock_micros(&mut secs, &mut micros) };
|
||||
(secs, micros)
|
||||
}
|
||||
|
||||
pub fn current_process_id() -> u32 {
|
||||
KERNEL_PROCESS
|
||||
}
|
||||
|
||||
pub fn current_thread_id() -> u64 {
|
||||
unsafe { frida_kmod_current_thread_id() }
|
||||
}
|
||||
|
||||
pub fn protect(address: u64, size: usize, gum_prot: u32) -> bool {
|
||||
unsafe { frida_kmod_protect(address, size, gum_prot) != 0 }
|
||||
}
|
||||
|
||||
pub fn remap_writable(first_page: u64, n_pages: u32) -> *mut c_void {
|
||||
unsafe { frida_kmod_remap_writable(first_page, n_pages) }
|
||||
}
|
||||
|
||||
/// Tears down an alias handed out by [`remap_writable`] and flushes the icache
|
||||
/// over the range it aliased; the shim remembers which range that was.
|
||||
pub fn unmap_writable(mapping: *mut c_void) {
|
||||
unsafe { frida_kmod_unmap_writable(mapping) };
|
||||
}
|
||||
|
||||
pub fn get_kernel_base() -> u64 {
|
||||
unsafe { frida_kmod_kernel_base() }
|
||||
}
|
||||
|
||||
pub fn get_kernel_size() -> u64 {
|
||||
unsafe { frida_kmod_kernel_size() }
|
||||
}
|
||||
|
||||
pub fn enumerate_modules() -> Vec<LoadedModule> {
|
||||
unsafe extern "C" fn on_module(
|
||||
name: *const c_char,
|
||||
version: *const c_char,
|
||||
base: u64,
|
||||
size: u64,
|
||||
user_data: *mut c_void,
|
||||
) -> c_int {
|
||||
unsafe {
|
||||
let modules = &mut *(user_data as *mut Vec<LoadedModule>);
|
||||
modules.push(LoadedModule {
|
||||
name: String::from(CStr::from_ptr(name).to_str().unwrap_or("?")),
|
||||
version: String::from(CStr::from_ptr(version).to_str().unwrap_or("")),
|
||||
base,
|
||||
size,
|
||||
});
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
let mut modules: Vec<LoadedModule> = Vec::new();
|
||||
unsafe {
|
||||
frida_kmod_enumerate_modules(on_module, &mut modules as *mut _ as *mut c_void);
|
||||
}
|
||||
modules
|
||||
}
|
||||
|
||||
pub fn watch_modules(on_event: fn(ModuleEvent, LoadedModule)) {
|
||||
unsafe extern "C" fn on_module_event(
|
||||
loaded: c_int,
|
||||
name: *const c_char,
|
||||
version: *const c_char,
|
||||
base: u64,
|
||||
size: u64,
|
||||
user_data: *mut c_void,
|
||||
) {
|
||||
unsafe {
|
||||
let on_event: fn(ModuleEvent, LoadedModule) = core::mem::transmute(user_data);
|
||||
on_event(
|
||||
if loaded != 0 {
|
||||
ModuleEvent::Loaded
|
||||
} else {
|
||||
ModuleEvent::Unloaded
|
||||
},
|
||||
LoadedModule {
|
||||
name: String::from(CStr::from_ptr(name).to_str().unwrap_or("?")),
|
||||
version: String::from(CStr::from_ptr(version).to_str().unwrap_or("")),
|
||||
base,
|
||||
size,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
frida_kmod_watch_modules(on_module_event, on_event as *mut c_void);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwatch_modules() {
|
||||
unsafe { frida_kmod_unwatch_modules() };
|
||||
}
|
||||
|
||||
pub fn enumerate_module_symbols(
|
||||
base: u64,
|
||||
on_symbol: &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool,
|
||||
) -> bool {
|
||||
unsafe extern "C" fn on_module_symbol(
|
||||
name: *const c_char,
|
||||
address: u64,
|
||||
size: u64,
|
||||
kind: u8,
|
||||
is_global: c_int,
|
||||
user_data: *mut c_void,
|
||||
) -> c_int {
|
||||
unsafe {
|
||||
let callback =
|
||||
&mut *(user_data as *mut &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool);
|
||||
if callback(name, address, size, kind, is_global != 0) { 1 } else { 0 }
|
||||
}
|
||||
}
|
||||
|
||||
let mut boxed: &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool = on_symbol;
|
||||
unsafe {
|
||||
frida_kmod_enumerate_module_symbols(base, on_module_symbol, &mut boxed as *mut _ as *mut c_void) != 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enumerate_module_exports(
|
||||
base: u64,
|
||||
on_export: &mut dyn FnMut(*const c_char, u64) -> bool,
|
||||
) -> bool {
|
||||
unsafe extern "C" fn on_module_export(
|
||||
name: *const c_char,
|
||||
address: u64,
|
||||
user_data: *mut c_void,
|
||||
) -> c_int {
|
||||
unsafe {
|
||||
let callback = &mut *(user_data as *mut &mut dyn FnMut(*const c_char, u64) -> bool);
|
||||
if callback(name, address) { 1 } else { 0 }
|
||||
}
|
||||
}
|
||||
|
||||
let mut boxed: &mut dyn FnMut(*const c_char, u64) -> bool = on_export;
|
||||
unsafe {
|
||||
frida_kmod_enumerate_module_exports(base, on_module_export, &mut boxed as *mut _ as *mut c_void) != 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_symbol(name: &CStr) -> u64 {
|
||||
unsafe { frida_kmod_find_symbol(name.as_ptr()) }
|
||||
}
|
||||
|
||||
pub fn find_function(name: &CStr) -> u64 {
|
||||
unsafe { frida_kmod_find_function(name.as_ptr()) }
|
||||
}
|
||||
|
||||
/// Resolves `address` to `(name, symbol_address)`, or `None` when it falls
|
||||
/// outside every known symbol.
|
||||
pub fn symbol_from_address(address: u64) -> Option<(String, u64)> {
|
||||
const NAME_CAPACITY: usize = 256;
|
||||
|
||||
let mut buffer = [0u8; NAME_CAPACITY];
|
||||
let mut symbol_address: u64 = 0;
|
||||
let found = unsafe {
|
||||
frida_kmod_symbol_name_from_address(
|
||||
address,
|
||||
buffer.as_mut_ptr() as *mut c_char,
|
||||
NAME_CAPACITY,
|
||||
&mut symbol_address,
|
||||
)
|
||||
};
|
||||
if found == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = CStr::from_bytes_until_nul(&buffer).unwrap();
|
||||
Some((String::from(name.to_str().unwrap()), symbol_address))
|
||||
}
|
||||
|
||||
/// Visits every symbol the running kernel knows about. Returns false when
|
||||
/// kallsyms is unavailable, which is the case on kernels built without
|
||||
/// CONFIG_KALLSYMS or when the shim could not resolve its entry points.
|
||||
pub fn enumerate_symbols(callback: &mut dyn FnMut(&CStr, u64) -> bool) -> bool {
|
||||
unsafe extern "C" fn on_symbol(
|
||||
name: *const c_char,
|
||||
address: u64,
|
||||
user_data: *mut c_void,
|
||||
) -> c_int {
|
||||
unsafe {
|
||||
let callback = &mut *(user_data as *mut &mut dyn FnMut(&CStr, u64) -> bool);
|
||||
if callback(CStr::from_ptr(name), address) { 1 } else { 0 }
|
||||
}
|
||||
}
|
||||
|
||||
let mut boxed: &mut dyn FnMut(&CStr, u64) -> bool = callback;
|
||||
unsafe { frida_kmod_enumerate_symbols(on_symbol, &mut boxed as *mut _ as *mut c_void) != 0 }
|
||||
}
|
||||
|
||||
const KERNEL_PROCESS: u32 = 0;
|
||||
|
||||
type FoundSymbolFunc =
|
||||
unsafe extern "C" fn(name: *const c_char, address: u64, user_data: *mut c_void) -> c_int;
|
||||
type ModuleEventFunc = unsafe extern "C" fn(c_int, *const c_char, *const c_char, u64, u64, *mut c_void);
|
||||
type FoundModuleSymbolFunc =
|
||||
unsafe extern "C" fn(*const c_char, u64, u64, u8, c_int, *mut c_void) -> c_int;
|
||||
type FoundModuleExportFunc = unsafe extern "C" fn(*const c_char, u64, *mut c_void) -> c_int;
|
||||
type FoundModuleFunc = unsafe extern "C" fn(
|
||||
name: *const c_char,
|
||||
version: *const c_char,
|
||||
base: u64,
|
||||
size: u64,
|
||||
user_data: *mut c_void,
|
||||
) -> c_int;
|
||||
|
||||
unsafe extern "C" {
|
||||
fn frida_kmod_log(message: *const c_char);
|
||||
fn frida_kmod_panic(message: *const c_char) -> !;
|
||||
fn frida_kmod_spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> c_int;
|
||||
fn frida_kmod_install_hooks();
|
||||
fn frida_kmod_alloc(size: usize) -> *mut u8;
|
||||
fn frida_kmod_free(ptr: *mut u8, size: usize);
|
||||
fn frida_kmod_alloc_code(size: usize) -> *mut u8;
|
||||
fn frida_kmod_free_code(ptr: *mut u8, size: usize);
|
||||
fn frida_kmod_own_range(base: *mut u64, size: *mut u64);
|
||||
fn frida_kmod_wait_prepare() -> u32;
|
||||
fn frida_kmod_wait_commit(seq: u32, timeout_us: i64);
|
||||
fn frida_kmod_wake();
|
||||
fn frida_kmod_yield();
|
||||
fn frida_kmod_monotonic_micros() -> i64;
|
||||
fn frida_kmod_wall_clock_micros(secs: *mut u32, micros: *mut u32);
|
||||
fn frida_kmod_current_thread_id() -> u64;
|
||||
fn frida_kmod_protect(address: u64, size: usize, gum_prot: u32) -> c_int;
|
||||
fn frida_kmod_remap_writable(first_page: u64, n_pages: u32) -> *mut c_void;
|
||||
fn frida_kmod_unmap_writable(mapping: *mut c_void);
|
||||
fn frida_kmod_kernel_base() -> u64;
|
||||
fn frida_kmod_kernel_size() -> u64;
|
||||
fn frida_kmod_enumerate_modules(func: FoundModuleFunc, user_data: *mut c_void);
|
||||
fn frida_kmod_watch_modules(func: ModuleEventFunc, user_data: *mut c_void);
|
||||
fn frida_kmod_unwatch_modules();
|
||||
fn frida_kmod_enumerate_module_symbols(
|
||||
base: u64,
|
||||
func: FoundModuleSymbolFunc,
|
||||
user_data: *mut c_void,
|
||||
) -> c_int;
|
||||
fn frida_kmod_enumerate_module_exports(
|
||||
base: u64,
|
||||
func: FoundModuleExportFunc,
|
||||
user_data: *mut c_void,
|
||||
) -> c_int;
|
||||
fn frida_kmod_find_symbol(name: *const c_char) -> u64;
|
||||
fn frida_kmod_find_function(name: *const c_char) -> u64;
|
||||
fn frida_kmod_symbol_name_from_address(
|
||||
address: u64,
|
||||
buffer: *mut c_char,
|
||||
size: usize,
|
||||
symbol_address: *mut u64,
|
||||
) -> c_int;
|
||||
fn frida_kmod_enumerate_symbols(func: FoundSymbolFunc, user_data: *mut c_void) -> c_int;
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use core::ffi::c_void;
|
||||
use core::ffi::{CStr, c_char, c_void};
|
||||
|
||||
pub fn field_offset(container: &str, field: &str) -> Option<usize> {
|
||||
if let Some(told) = told_by_the_loader(container, field) {
|
||||
return Some(told);
|
||||
}
|
||||
|
||||
let types = described_types()?;
|
||||
let described = types.named(container)?;
|
||||
|
||||
types.offset_of(&described, field).map(|bits| bits / 8)
|
||||
types.offset_of_path(&described, field).map(|bits| bits / 8)
|
||||
}
|
||||
|
||||
pub fn resolve_struct(container: &str) -> Option<usize> {
|
||||
@@ -96,11 +100,43 @@ pub fn types_are_described() -> bool {
|
||||
}
|
||||
|
||||
pub fn struct_size(container: &str) -> Option<usize> {
|
||||
if let Some(told) = told_by_the_loader(container, "") {
|
||||
return Some(told);
|
||||
}
|
||||
|
||||
let types = described_types()?;
|
||||
|
||||
Some(types.named(container)?.size as usize)
|
||||
}
|
||||
|
||||
fn told_by_the_loader(container: &str, field: &str) -> Option<usize> {
|
||||
let mut entry = unsafe { _frida_kmod_layout } as *const ToldLayout;
|
||||
if entry.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
while !(*entry).container.is_null() {
|
||||
let told = &*entry;
|
||||
if CStr::from_ptr(told.container).to_bytes() == container.as_bytes()
|
||||
&& CStr::from_ptr(told.field).to_bytes() == field.as_bytes()
|
||||
{
|
||||
return Some(told.value);
|
||||
}
|
||||
entry = entry.add(1);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct ToldLayout {
|
||||
container: *const c_char,
|
||||
field: *const c_char,
|
||||
value: usize,
|
||||
}
|
||||
|
||||
struct DescribedTypes {
|
||||
types: &'static [u8],
|
||||
names: &'static [u8],
|
||||
@@ -437,6 +473,25 @@ impl DescribedTypes {
|
||||
described.kind == KIND_ENUM || described.kind == KIND_WIDE_ENUM
|
||||
}
|
||||
|
||||
fn offset_of_path(&self, described: &Described, path: &str) -> Option<usize> {
|
||||
let Some((member, rest)) = path.split_once('.') else {
|
||||
return self.offset_of(described, path);
|
||||
};
|
||||
|
||||
let (placement, type_id) = self.member_named(described, member)?;
|
||||
let within = self.with_id(type_id)?;
|
||||
|
||||
Some(placement + self.offset_of_path(&within, rest)?)
|
||||
}
|
||||
|
||||
fn member_named(&self, described: &Described, wanted: &str) -> Option<(usize, u32)> {
|
||||
(0..described.members).find_map(|index| {
|
||||
let member = described.body + (index * MEMBER_SIZE);
|
||||
(self.name_of(word_at(self.types, member)) == wanted)
|
||||
.then(|| (self.placement_of(described, member), word_at(self.types, member + 4)))
|
||||
})
|
||||
}
|
||||
|
||||
// A structure the kernel lays out itself keeps its fields in a member with no name of its
|
||||
// own, so a field is looked for through those as well, and answers where it sits in the
|
||||
// structure the question was asked about.
|
||||
@@ -619,4 +674,5 @@ const MAX_DEPTH: usize = 8;
|
||||
unsafe extern "C" {
|
||||
static ___start_BTF: *const c_void;
|
||||
static ___stop_BTF: *const c_void;
|
||||
static _frida_kmod_layout: usize;
|
||||
}
|
||||
@@ -1,10 +1,3 @@
|
||||
// Linux backend. The agent reaches the kernel in one of two ways, and the rest
|
||||
// of the agent cannot tell which: loaded from the inside as a kernel module,
|
||||
// where every primitive is a call into a shim kbuild compiled against the
|
||||
// target's headers; or injected from the outside like XNU and Windows, where
|
||||
// the host patches the addresses it needs into .kernel_addrs and the memory it
|
||||
// cannot touch is reached over the hostlink.
|
||||
|
||||
use alloc::string::String;
|
||||
|
||||
pub fn stack_headroom() -> usize {
|
||||
@@ -20,8 +13,6 @@ pub(crate) const STACK_SPAN: usize = 16 * 1024;
|
||||
#[cfg(any(target_arch = "arm", target_arch = "x86"))]
|
||||
pub(crate) const STACK_SPAN: usize = 8 * 1024;
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
mod kmod;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
mod arena;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
@@ -52,9 +43,15 @@ mod threads;
|
||||
pub(crate) mod user;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
mod processes;
|
||||
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
|
||||
mod cloak;
|
||||
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
|
||||
mod control;
|
||||
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
|
||||
mod process_memory;
|
||||
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
|
||||
mod user_threads;
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
pub use self::kmod::*;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub use self::injection::*;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
@@ -63,17 +60,23 @@ pub use self::relay::*;
|
||||
pub use self::facade::*;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub use self::native::{
|
||||
alloc_dma, free_dma, get_kernel_base, get_kernel_size, install_interrupt_handler, map_io,
|
||||
map_pages,
|
||||
mmio_interrupt, pci_interrupt, release_fault_reporter, release_interrupt, run_when_ready,
|
||||
patch_text, set_kernel_base, set_protection, virt_to_phys,
|
||||
alloc_dma, free_dma, get_kernel_base, get_kernel_size, hand_back_to_the_loader,
|
||||
install_interrupt_handler, loaded_from_the_inside, map_io, map_pages, mmio_interrupt, pci_interrupt,
|
||||
release_fault_reporter, release_interrupt, remap_writable, run_when_ready, patch_text,
|
||||
set_kernel_base, set_protection, unmap_writable, virt_to_phys,
|
||||
};
|
||||
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
|
||||
pub use self::native::learn_when_to_sleep;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub use self::mapped::*;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub use self::modules::*;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub use self::processes::*;
|
||||
#[cfg(all(feature = "linux-injected", any(target_arch = "x86_64", target_arch = "aarch64")))]
|
||||
pub use self::control::{
|
||||
an_operation_waits, carry_out_operations, close as close_control_channel, open as open_control_channel,
|
||||
};
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub use self::spawn::*;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
|
||||
@@ -9,6 +9,7 @@ use crate::bindings::{g_free, gsize, gum_memory_read};
|
||||
use super::{LoadedModule, ModuleEvent};
|
||||
|
||||
pub fn enumerate_modules() -> Vec<LoadedModule> {
|
||||
let _held = ModuleListLock::take();
|
||||
let Some(layout) = module_layout() else {
|
||||
return Vec::new();
|
||||
};
|
||||
@@ -38,6 +39,7 @@ pub fn enumerate_module_symbols(
|
||||
base: u64,
|
||||
on_symbol: &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool,
|
||||
) -> bool {
|
||||
let _held = ModuleListLock::take();
|
||||
let Some(layout) = module_layout() else {
|
||||
return false;
|
||||
};
|
||||
@@ -87,6 +89,7 @@ pub fn enumerate_module_exports(
|
||||
base: u64,
|
||||
on_export: &mut dyn FnMut(*const c_char, u64) -> bool,
|
||||
) -> bool {
|
||||
let _held = ModuleListLock::take();
|
||||
let Some(layout) = module_layout() else {
|
||||
return false;
|
||||
};
|
||||
@@ -152,6 +155,52 @@ fn relative_target(at: usize) -> usize {
|
||||
at.wrapping_add_signed(offset as isize)
|
||||
}
|
||||
|
||||
struct ModuleListLock {
|
||||
mutex: *mut c_void,
|
||||
}
|
||||
|
||||
impl ModuleListLock {
|
||||
fn take() -> Self {
|
||||
let mutex = unsafe { _module_mutex } as *mut c_void;
|
||||
let lockable = !mutex.is_null() && unsafe { _mutex_lock.is_some() && _mutex_unlock.is_some() };
|
||||
if !lockable {
|
||||
return Self { mutex: ptr::null_mut() };
|
||||
}
|
||||
|
||||
unsafe { lock_mutex(mutex) };
|
||||
|
||||
Self { mutex }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ModuleListLock {
|
||||
fn drop(&mut self) {
|
||||
if !self.mutex.is_null() {
|
||||
unsafe { unlock_mutex(self.mutex) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
unsafe fn lock_mutex(mutex: *mut c_void) {
|
||||
unsafe { _mutex_lock.unwrap()(mutex) };
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
unsafe fn unlock_mutex(mutex: *mut c_void) {
|
||||
unsafe { _mutex_unlock.unwrap()(mutex) };
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
unsafe fn lock_mutex(mutex: *mut c_void) {
|
||||
unsafe { frida_k_mutex_lock(mutex) };
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
unsafe fn unlock_mutex(mutex: *mut c_void) {
|
||||
unsafe { frida_k_mutex_unlock(mutex) };
|
||||
}
|
||||
|
||||
pub fn watch_modules(on_event: fn(ModuleEvent, LoadedModule)) {
|
||||
if module_layout().is_none() {
|
||||
return;
|
||||
@@ -778,10 +827,15 @@ unsafe extern "C" {
|
||||
) -> c_int;
|
||||
fn frida_k_register_module_notifier(block: *mut c_void) -> c_int;
|
||||
fn frida_k_unregister_module_notifier(block: *mut c_void) -> c_int;
|
||||
fn frida_k_mutex_lock(mutex: *mut c_void);
|
||||
fn frida_k_mutex_unlock(mutex: *mut c_void);
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
static _modules: *const c_void;
|
||||
static _module_mutex: *const c_void;
|
||||
static _mutex_lock: Option<unsafe extern "C" fn(*mut c_void)>;
|
||||
static _mutex_unlock: Option<unsafe extern "C" fn(*mut c_void)>;
|
||||
static _register_module_notifier: Option<unsafe extern "C" fn(*mut c_void) -> c_int>;
|
||||
#[allow(dead_code)]
|
||||
static _unregister_module_notifier: Option<unsafe extern "C" fn(*mut c_void) -> c_int>;
|
||||
|
||||
@@ -1,14 +1,9 @@
|
||||
// The agent injected from the outside: the host resolves the kernel functions
|
||||
// below and patches their addresses into .kernel_addrs, the same way it does
|
||||
// for XNU and Windows. Everything the kernel would otherwise have to lend us --
|
||||
// its symbol table, its module list, the right to write to its pages -- comes
|
||||
// from the host instead, over the hostlink.
|
||||
|
||||
use core::ffi::{c_char, c_int, c_uint, c_void};
|
||||
use core::ptr;
|
||||
use core::ptr::read_volatile;
|
||||
use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
use core::sync::atomic::{AtomicI32, fence};
|
||||
#[cfg(not(target_arch = "arm"))]
|
||||
use core::sync::atomic::AtomicUsize;
|
||||
|
||||
use crate::kernel::ThreadEntry;
|
||||
@@ -47,6 +42,16 @@ pub fn release_fault_reporter() {
|
||||
super::kfault::uninstall();
|
||||
}
|
||||
|
||||
pub fn loaded_from_the_inside() -> bool {
|
||||
unsafe { _frida_kmod_exit_worker != 0 }
|
||||
}
|
||||
|
||||
pub fn hand_back_to_the_loader() {
|
||||
if loaded_from_the_inside() {
|
||||
unsafe { frida_kmod_exit_worker() };
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_signal(signal: c_int, task: usize) {
|
||||
unsafe { _send_sig(signal, task as *mut c_void, 1) };
|
||||
}
|
||||
@@ -152,6 +157,17 @@ pub(crate) fn kcfi_thunk(callback: usize, _type_ref: Option<usize>) -> usize {
|
||||
callback
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub(crate) fn kernel_thread_function(cache: &AtomicUsize, callback: KthreadFn) -> KthreadFn {
|
||||
let ref_fn = unsafe { _kthread_worker_fn.map(|f| f as usize) };
|
||||
unsafe { core::mem::transmute(kcfi_thunk_cached(cache, callback as usize, ref_fn)) }
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub(crate) fn kernel_thread_function(_cache: &AtomicUsize, callback: KthreadFn) -> KthreadFn {
|
||||
callback
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn kcfi_thunk_cached(cache: &AtomicUsize, callback: usize, type_ref: Option<usize>) -> usize {
|
||||
let existing = cache.load(Ordering::Acquire);
|
||||
@@ -212,28 +228,38 @@ pub fn spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> isize {
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub fn alloc(size: usize) -> *mut u8 {
|
||||
unsafe { frida_k_alloc(size, GFP_KERNEL as usize) }
|
||||
unsafe { frida_k_alloc(size, allocation_flags() as usize) }
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
pub fn alloc(size: usize) -> *mut u8 {
|
||||
let flags = allocation_flags();
|
||||
|
||||
unsafe {
|
||||
if let Some(f) = ___kmalloc_noprof {
|
||||
f(size, GFP_KERNEL)
|
||||
f(size, flags)
|
||||
} else if let Some(f) = _kmalloc {
|
||||
f(size, GFP_KERNEL)
|
||||
f(size, flags)
|
||||
} else {
|
||||
___kmalloc.unwrap()(size, GFP_KERNEL)
|
||||
___kmalloc.unwrap()(size, flags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn allocation_flags() -> u32 {
|
||||
if can_sleep() { GFP_KERNEL } else { GFP_ATOMIC }
|
||||
}
|
||||
|
||||
pub fn free(ptr: *mut u8, _size: usize) {
|
||||
unsafe { _kfree(ptr) };
|
||||
}
|
||||
|
||||
pub fn alloc_heap(size: usize) -> *mut u8 {
|
||||
unsafe {
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
if let Some(f) = _vmalloc_noprof {
|
||||
return f(size);
|
||||
}
|
||||
if let Some(f) = _vmalloc {
|
||||
return f(size);
|
||||
}
|
||||
@@ -242,7 +268,6 @@ pub fn alloc_heap(size: usize) -> *mut u8 {
|
||||
alloc(size)
|
||||
}
|
||||
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub fn alloc_code(size: usize) -> *mut u8 {
|
||||
unsafe {
|
||||
@@ -253,7 +278,9 @@ pub fn alloc_code(size: usize) -> *mut u8 {
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
pub fn alloc_code(size: usize) -> *mut u8 {
|
||||
unsafe {
|
||||
let code = if let Some(f) = _execmem_alloc {
|
||||
let code = if let Some(f) = _execmem_alloc_rw {
|
||||
f(EXECMEM_MODULE_TEXT, size)
|
||||
} else if let Some(f) = _execmem_alloc {
|
||||
f(EXECMEM_MODULE_TEXT, size)
|
||||
} else {
|
||||
_module_alloc.unwrap()(size)
|
||||
@@ -283,6 +310,29 @@ pub fn free_code(ptr: *mut u8, _size: usize) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
pub fn remap_writable(first_page: u64, n_pages: u32) -> Option<*mut c_void> {
|
||||
let remap = unsafe { _frida_kmod_remap_writable }?;
|
||||
let alias = unsafe { remap(first_page, n_pages) };
|
||||
|
||||
(!alias.is_null()).then_some(alias)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub fn remap_writable(_first_page: u64, _n_pages: u32) -> Option<*mut c_void> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
pub fn unmap_writable(alias: *mut c_void) -> bool {
|
||||
unsafe { _frida_kmod_unmap_writable }.is_some_and(|unmap| unsafe { unmap(alias) })
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub fn unmap_writable(_alias: *mut c_void) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
pub fn page_size() -> usize {
|
||||
crate::gum::page_size_the_kernel_runs_with()
|
||||
}
|
||||
@@ -392,13 +442,18 @@ pub fn free_dma(ptr: *mut u8, size: usize) {
|
||||
free(ptr, size);
|
||||
}
|
||||
|
||||
pub fn wait(_token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut() -> bool) {
|
||||
pub fn wait(token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut() -> bool) {
|
||||
if !can_sleep() {
|
||||
spin_until(check, timeout_us);
|
||||
return;
|
||||
}
|
||||
|
||||
let waiters = waiters();
|
||||
|
||||
let mut waiter = [0usize; WAIT_ENTRY_WORDS];
|
||||
let waiter = queue_this_thread(&mut waiter);
|
||||
let waiter = queue_this_thread(&mut waiter, token);
|
||||
|
||||
unsafe { _prepare_to_wait_event(waiters, waiter, TASK_INTERRUPTIBLE) };
|
||||
unsafe { _prepare_to_wait_event(waiters, waiter, TASK_IDLE) };
|
||||
|
||||
if !check() {
|
||||
match timeout_us {
|
||||
@@ -413,18 +468,19 @@ pub fn wait(_token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut()
|
||||
unsafe { _finish_wait(waiters, waiter) };
|
||||
}
|
||||
|
||||
pub fn wake(_token: *const u8) {
|
||||
unsafe { ___wake_up(waiters(), TASK_NORMAL, WAKE_EVERY_WAITER, ptr::null_mut()) };
|
||||
pub fn wake(token: *const u8) {
|
||||
unsafe { ___wake_up(waiters(), TASK_NORMAL, WAKE_EVERY_WAITER, token as *mut c_void) };
|
||||
}
|
||||
|
||||
pub fn poke_loop_wakeup() {}
|
||||
|
||||
fn queue_this_thread(entry: &mut [usize; WAIT_ENTRY_WORDS]) -> *mut c_void {
|
||||
fn queue_this_thread(entry: &mut [usize; WAIT_ENTRY_WORDS], token: *const u8) -> *mut c_void {
|
||||
let entry = entry.as_mut_ptr();
|
||||
|
||||
unsafe {
|
||||
entry.add(WAIT_ENTRY_THREAD).write(current_task() as usize);
|
||||
entry.add(WAIT_ENTRY_WAKE).write(_autoremove_wake_function as usize);
|
||||
entry.add(WAIT_ENTRY_WAKE).write(waking_function());
|
||||
entry.add(WAIT_ENTRY_TOKEN).write(token as usize);
|
||||
|
||||
let queued = entry.add(WAIT_ENTRY_QUEUED);
|
||||
queued.write(queued as usize);
|
||||
@@ -434,6 +490,37 @@ fn queue_this_thread(entry: &mut [usize; WAIT_ENTRY_WORDS]) -> *mut c_void {
|
||||
entry as *mut c_void
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn waking_function() -> usize {
|
||||
let ref_fn = unsafe { _autoremove_wake_function } as usize;
|
||||
kcfi_thunk_cached(&WAKE_THUNK, frida_cb_wake as *const () as usize, Some(ref_fn))
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86")))]
|
||||
fn waking_function() -> usize {
|
||||
frida_cb_wake as *const () as usize
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
fn waking_function() -> usize {
|
||||
unsafe { _autoremove_wake_function as usize }
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
unsafe extern "C" fn frida_cb_wake(entry: *mut c_void, mode: c_uint, flags: c_int, key: *mut c_void) -> c_int {
|
||||
let awaited = unsafe { (entry as *const usize).add(WAIT_ENTRY_TOKEN).read() };
|
||||
if key as usize != awaited {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let wake_this_one: unsafe extern "C" fn(*mut c_void, c_uint, c_int, *mut c_void) -> c_int =
|
||||
unsafe { core::mem::transmute(_autoremove_wake_function) };
|
||||
unsafe { wake_this_one(entry, mode, flags, key) }
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static WAKE_THUNK: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
fn waiters() -> *mut c_void {
|
||||
let queue = (&raw mut WAITERS) as *mut c_void;
|
||||
|
||||
@@ -492,7 +579,7 @@ fn pci_irq_vector() -> Option<unsafe extern "C" fn(*mut c_void, c_uint) -> c_int
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub fn mmio_interrupt(mmio_base: u64) -> Option<u32> {
|
||||
let path = alloc::format!("/virtio_mmio@{:x}\0", mmio_base);
|
||||
let node = unsafe { _of_find_node_opts_by_path(path.as_ptr() as *const c_char, ptr::null_mut()) };
|
||||
let node = unsafe { _of_find_node_opts_by_path(path.as_ptr() as *const c_char, core::ptr::null_mut()) };
|
||||
if node.is_null() {
|
||||
return None;
|
||||
}
|
||||
@@ -513,7 +600,240 @@ pub fn mmio_interrupt(_mmio_base: u64) -> Option<u32> {
|
||||
}
|
||||
|
||||
pub fn yield_now() {
|
||||
unsafe { _schedule() };
|
||||
if can_sleep() {
|
||||
unsafe { _schedule() };
|
||||
} else {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
|
||||
pub fn learn_when_to_sleep() {
|
||||
let above = |found: Option<usize>| found.map_or(usize::MAX, |at| at + 1);
|
||||
RCU_DEPTH_AT.store(above(super::layout::field_offset("task_struct", "rcu_read_lock_nesting")), Ordering::Relaxed);
|
||||
PREEMPT_COUNT_AT.store(above(where_the_preempt_count_is()), Ordering::Release);
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
|
||||
fn can_sleep() -> bool {
|
||||
let Some(places) = sleeping_places() else {
|
||||
return true;
|
||||
};
|
||||
let rcu_depth = places.rcu_read_lock_nesting
|
||||
.map_or(0, |at| unsafe { ((current_task() as usize + at) as *const i32).read_volatile() });
|
||||
|
||||
current_preempt_count(places.preempt_count) == 0 && !interrupts_masked() && rcu_depth == 0
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
|
||||
fn can_sleep() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
|
||||
fn sleeping_places() -> Option<SleepingPlaces> {
|
||||
let found = |at: usize| (at != 0 && at != usize::MAX).then(|| at - 1);
|
||||
Some(SleepingPlaces {
|
||||
preempt_count: found(PREEMPT_COUNT_AT.load(Ordering::Acquire))?,
|
||||
rcu_read_lock_nesting: found(RCU_DEPTH_AT.load(Ordering::Relaxed)),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn where_the_preempt_count_is() -> Option<usize> {
|
||||
super::layout::field_offset("task_struct", "thread_info.preempt_count")
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn where_the_preempt_count_is() -> Option<usize> {
|
||||
let standalone = unsafe { ___preempt_count };
|
||||
if standalone != 0 {
|
||||
return Some(standalone);
|
||||
}
|
||||
|
||||
hot_per_cpu_place("preempt_count")
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn current_preempt_count(at: usize) -> u32 {
|
||||
unsafe { ((current_task() as usize + at) as *const u32).read_volatile() }
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn current_preempt_count(at: usize) -> u32 {
|
||||
let count: u32;
|
||||
unsafe { core::arch::asm!("mov {:e}, gs:[{}]", out(reg) count, in(reg) at, options(nostack, readonly)) };
|
||||
|
||||
count & !PREEMPT_NEED_RESCHED
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn interrupts_masked() -> bool {
|
||||
let daif: usize;
|
||||
unsafe { core::arch::asm!("mrs {}, daif", out(reg) daif, options(nomem, nostack)) };
|
||||
|
||||
daif & DAIF_IRQ_MASKED != 0
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn interrupts_masked() -> bool {
|
||||
let flags: usize;
|
||||
unsafe { core::arch::asm!("pushfq", "pop {}", out(reg) flags, options(nomem, preserves_flags)) };
|
||||
|
||||
flags & RFLAGS_IF == 0
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
|
||||
struct SleepingPlaces {
|
||||
preempt_count: usize,
|
||||
rcu_read_lock_nesting: Option<usize>,
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
|
||||
static PREEMPT_COUNT_AT: AtomicUsize = AtomicUsize::new(0);
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
|
||||
static RCU_DEPTH_AT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
fn spin_until(check: &mut dyn FnMut() -> bool, timeout_us: Option<u64>) {
|
||||
let deadline = timeout_us.map(|us| monotonic_micros() + us as i64);
|
||||
|
||||
while !check() && deadline.is_none_or(|deadline| monotonic_micros() < deadline) {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub(crate) fn lock_spinlock(lock: usize) {
|
||||
unsafe { __raw_spin_lock.unwrap()(lock as *mut c_void) };
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub(crate) fn unlock_spinlock(lock: usize) {
|
||||
unsafe { __raw_spin_unlock.unwrap()(lock as *mut c_void) };
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub(crate) fn lock_spinlock(lock: usize) {
|
||||
if let Some(lock_it) = unsafe { __raw_spin_lock } {
|
||||
return unsafe { lock_it(lock as *mut c_void) };
|
||||
}
|
||||
|
||||
disable_preemption();
|
||||
if let Err(seen) = counter_at(lock).compare_exchange(0, SPIN_LOCKED, Ordering::Acquire, Ordering::Relaxed) {
|
||||
unsafe { _queued_spin_lock_slowpath(lock as *mut c_void, seen as u32) };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub(crate) fn unlock_spinlock(lock: usize) {
|
||||
if let Some(unlock_it) = unsafe { __raw_spin_unlock } {
|
||||
return unsafe { unlock_it(lock as *mut c_void) };
|
||||
}
|
||||
|
||||
unsafe { core::sync::atomic::AtomicU8::from_ptr(lock as *mut u8) }.store(0, Ordering::Release);
|
||||
enable_preemption();
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub(crate) fn read_lock_irqsave(lock: usize) -> usize {
|
||||
let flags = mask_interrupts();
|
||||
disable_preemption();
|
||||
if counter_at(lock).fetch_add(READER_BIAS, Ordering::Acquire) & WRITER_MASK != 0 {
|
||||
unsafe { _queued_read_lock_slowpath(lock as *mut c_void) };
|
||||
}
|
||||
flags
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub(crate) fn read_unlock_irqrestore(lock: usize, flags: usize) {
|
||||
counter_at(lock).fetch_sub(READER_BIAS, Ordering::Release);
|
||||
enable_preemption();
|
||||
restore_interrupts(flags);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub(crate) fn write_lock_irqsave(lock: usize) -> usize {
|
||||
let flags = mask_interrupts();
|
||||
disable_preemption();
|
||||
if counter_at(lock).compare_exchange(0, WRITER_LOCKED, Ordering::Acquire, Ordering::Relaxed).is_err() {
|
||||
unsafe { _queued_write_lock_slowpath(lock as *mut c_void) };
|
||||
}
|
||||
flags
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub(crate) fn write_unlock_irqrestore(lock: usize, flags: usize) {
|
||||
unsafe { core::sync::atomic::AtomicU8::from_ptr(lock as *mut u8) }.store(0, Ordering::Release);
|
||||
enable_preemption();
|
||||
restore_interrupts(flags);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub(crate) fn disable_preemption() {
|
||||
let count = preempt_count();
|
||||
unsafe { count.write_volatile(count.read_volatile() + 1) };
|
||||
core::sync::atomic::compiler_fence(Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub(crate) fn enable_preemption() {
|
||||
core::sync::atomic::compiler_fence(Ordering::SeqCst);
|
||||
let count = preempt_count();
|
||||
unsafe { count.write_volatile(count.read_volatile() - 1) };
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub(crate) fn disable_preemption() {
|
||||
let at = sleeping_places().unwrap().preempt_count;
|
||||
unsafe { core::arch::asm!("add dword ptr gs:[{}], 1", in(reg) at, options(nostack)) };
|
||||
core::sync::atomic::compiler_fence(Ordering::SeqCst);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub(crate) fn enable_preemption() {
|
||||
core::sync::atomic::compiler_fence(Ordering::SeqCst);
|
||||
let at = sleeping_places().unwrap().preempt_count;
|
||||
unsafe { core::arch::asm!("sub dword ptr gs:[{}], 1", in(reg) at, options(nostack)) };
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn preempt_count() -> *mut u32 {
|
||||
let at = sleeping_places().unwrap().preempt_count;
|
||||
(current_task() as usize + at) as *mut u32
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn mask_interrupts() -> usize {
|
||||
let flags: usize;
|
||||
unsafe {
|
||||
core::arch::asm!("mrs {}, daif", "msr daifset, #3", out(reg) flags, options(nostack));
|
||||
}
|
||||
flags
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn restore_interrupts(flags: usize) {
|
||||
unsafe { core::arch::asm!("msr daif, {}", in(reg) flags, options(nostack)) };
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
pub(crate) fn hold(counter: usize) {
|
||||
counter_at(counter).fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
pub(crate) fn let_go(counter: usize) -> bool {
|
||||
if counter_at(counter).fetch_sub(1, Ordering::Release) != 1 {
|
||||
return false;
|
||||
}
|
||||
|
||||
fence(Ordering::Acquire);
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
fn counter_at(address: usize) -> &'static AtomicI32 {
|
||||
unsafe { AtomicI32::from_ptr(address as *mut i32) }
|
||||
}
|
||||
|
||||
pub fn monotonic_micros() -> i64 {
|
||||
@@ -567,33 +887,64 @@ pub fn current_task() -> u64 {
|
||||
task
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
pub fn current_task() -> u64 {
|
||||
let task: u64;
|
||||
unsafe {
|
||||
core::arch::asm!("mov {}, gs:[{}]", out(reg) task, in(reg) _current_task, options(nostack));
|
||||
}
|
||||
task
|
||||
let place = per_cpu_place(unsafe { _current_task }, "current_task", &CURRENT_TASK_AT);
|
||||
|
||||
read_per_cpu_word(place) as u64
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn top_of_stack() -> usize {
|
||||
let top: usize;
|
||||
unsafe {
|
||||
core::arch::asm!("mov {}, gs:[{}]", out(reg) top, in(reg) _cpu_current_top_of_stack, options(nostack));
|
||||
let place = per_cpu_place(unsafe { _cpu_current_top_of_stack }, "top_of_stack", &TOP_OF_STACK_AT);
|
||||
|
||||
read_per_cpu_word(place)
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn per_cpu_place(standalone: usize, hot_member: &str, cache: &AtomicUsize) -> usize {
|
||||
let known = cache.load(Ordering::Relaxed);
|
||||
if known != 0 {
|
||||
return known;
|
||||
}
|
||||
top
|
||||
|
||||
let place = if standalone != 0 { standalone } else { hot_per_cpu_place(hot_member).unwrap() };
|
||||
cache.store(place, Ordering::Relaxed);
|
||||
|
||||
place
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn hot_per_cpu_place(member: &str) -> Option<usize> {
|
||||
let hot = unsafe { _pcpu_hot };
|
||||
if hot == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(hot + super::layout::field_offset("pcpu_hot", member)?)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn read_per_cpu_word(place: usize) -> usize {
|
||||
let value: usize;
|
||||
unsafe { core::arch::asm!("mov {}, gs:[{}]", out(reg) value, in(reg) place, options(nostack, readonly)) };
|
||||
|
||||
value
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub fn current_task() -> u64 {
|
||||
let task: u32;
|
||||
unsafe {
|
||||
core::arch::asm!("mov {}, fs:[{}]", out(reg) task, in(reg) _current_task as u32, options(nostack));
|
||||
}
|
||||
task as u64
|
||||
fn read_per_cpu_word(place: usize) -> usize {
|
||||
let value: usize;
|
||||
unsafe { core::arch::asm!("mov {}, fs:[{}]", out(reg) value, in(reg) place, options(nostack, readonly)) };
|
||||
|
||||
value
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
static CURRENT_TASK_AT: AtomicUsize = AtomicUsize::new(0);
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
static TOP_OF_STACK_AT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
pub fn get_kernel_base() -> u64 {
|
||||
KERNEL_BASE.load(Ordering::Relaxed)
|
||||
}
|
||||
@@ -814,22 +1165,38 @@ const KERNEL_PROCESS: u32 = 0;
|
||||
const JS_SAFE_THREAD_ID_MASK: u64 = (1 << 48) - 1;
|
||||
|
||||
const GFP_KERNEL: u32 = 0xcc0;
|
||||
const GFP_ATOMIC: u32 = 0x820;
|
||||
const NUMA_NO_NODE: c_int = -1;
|
||||
const EXECMEM_MODULE_TEXT: u32 = 1;
|
||||
const EXECMEM_MODULE_TEXT: u32 = 0;
|
||||
const PAGE_SIZE: usize = 4096;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const TCR_T1SZ_SHIFT: u64 = 16;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const TCR_T1SZ_MASK: u64 = 0x3f;
|
||||
const TASK_INTERRUPTIBLE: c_uint = 1;
|
||||
const TASK_IDLE: c_uint = 0x402;
|
||||
const TASK_NORMAL: c_uint = 3;
|
||||
const WAKE_EVERY_WAITER: c_int = 0;
|
||||
const WAKE_SLACK_NS: u64 = 50_000;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const SPIN_LOCKED: i32 = 1;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const READER_BIAS: i32 = 1 << 9;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const WRITER_LOCKED: i32 = 0xff;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const WRITER_MASK: i32 = 0x1ff;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const DAIF_IRQ_MASKED: usize = 1 << 7;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const PREEMPT_NEED_RESCHED: u32 = 1 << 31;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const RFLAGS_IF: usize = 1 << 9;
|
||||
const HRTIMER_MODE_REL: c_int = 1;
|
||||
const WAIT_ENTRY_WORDS: usize = 8;
|
||||
const WAIT_ENTRY_THREAD: usize = 1;
|
||||
const WAIT_ENTRY_WAKE: usize = 2;
|
||||
const WAIT_ENTRY_QUEUED: usize = 3;
|
||||
const WAIT_ENTRY_TOKEN: usize = WAIT_ENTRY_WORDS - 1;
|
||||
const WAIT_QUEUE_WORDS: usize = 16;
|
||||
const LOCK_KEY_WORDS: usize = 8;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
@@ -864,7 +1231,33 @@ struct Timespec64 {
|
||||
tv_nsec: i64,
|
||||
}
|
||||
|
||||
type KthreadFn = unsafe extern "C" fn(data: *mut c_void) -> c_int;
|
||||
pub(crate) type KthreadFn = unsafe extern "C" fn(data: *mut c_void) -> c_int;
|
||||
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
unsafe extern "C" {
|
||||
static __raw_spin_lock: Option<unsafe extern "C" fn(*mut c_void)>;
|
||||
static __raw_spin_unlock: Option<unsafe extern "C" fn(*mut c_void)>;
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
unsafe extern "C" {
|
||||
static _queued_spin_lock_slowpath: unsafe extern "C" fn(*mut c_void, u32);
|
||||
static _queued_read_lock_slowpath: unsafe extern "C" fn(*mut c_void);
|
||||
static _queued_write_lock_slowpath: unsafe extern "C" fn(*mut c_void);
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
static _frida_kmod_exit_worker: usize;
|
||||
#[cfg(target_arch = "x86")]
|
||||
#[link_name = "frida_k_frida_kmod_exit_worker"]
|
||||
fn frida_kmod_exit_worker() -> !;
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
unsafe fn frida_kmod_exit_worker() -> ! {
|
||||
let exit: unsafe extern "C" fn() -> ! = unsafe { core::mem::transmute(_frida_kmod_exit_worker) };
|
||||
unsafe { exit() }
|
||||
}
|
||||
type IrqHandlerFn = unsafe extern "C" fn(irq: c_int, cookie: *mut c_void) -> c_int;
|
||||
|
||||
unsafe extern "C" {
|
||||
@@ -900,6 +1293,14 @@ unsafe extern "C" {
|
||||
static _vmalloc: Option<unsafe extern "C" fn(usize) -> *mut u8>;
|
||||
// Executable memory moved out of the module loader in 6.12.
|
||||
static _execmem_alloc: Option<unsafe extern "C" fn(u32, usize) -> *mut u8>;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _execmem_alloc_rw: Option<unsafe extern "C" fn(u32, usize) -> *mut u8>;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _vmalloc_noprof: Option<unsafe extern "C" fn(usize) -> *mut u8>;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _frida_kmod_remap_writable: Option<unsafe extern "C" fn(u64, u32) -> *mut c_void>;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _frida_kmod_unmap_writable: Option<unsafe extern "C" fn(*mut c_void) -> bool>;
|
||||
static _set_memory_rw: Option<unsafe extern "C" fn(usize, c_int) -> c_int>;
|
||||
static _set_memory_ro: Option<unsafe extern "C" fn(usize, c_int) -> c_int>;
|
||||
static _set_memory_x: Option<unsafe extern "C" fn(usize, c_int) -> c_int>;
|
||||
@@ -971,6 +1372,10 @@ unsafe extern "C" {
|
||||
static _current_task: usize;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
static _cpu_current_top_of_stack: usize;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
static ___preempt_count: usize;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
static _pcpu_hot: usize;
|
||||
#[cfg(target_arch = "arm")]
|
||||
static ___pv_offset: *const u64;
|
||||
#[cfg(target_arch = "arm")]
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
use core::ffi::{c_int, c_void};
|
||||
|
||||
use super::processes::HeldTask;
|
||||
|
||||
pub fn allocate(id: u32, size: usize, protection: usize) -> usize {
|
||||
let Some(mm) = BorrowedMemory::of(id) else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
let address = unsafe { _vm_mmap(core::ptr::null_mut(), 0, size, protection, MAP_PRIVATE | MAP_ANONYMOUS, 0) };
|
||||
drop(mm);
|
||||
|
||||
if address >= FIRST_ERROR_ADDRESS { 0 } else { address }
|
||||
}
|
||||
|
||||
pub fn release(id: u32, address: usize, size: usize) -> c_int {
|
||||
let Some(mm) = BorrowedMemory::of(id) else {
|
||||
return -ESRCH;
|
||||
};
|
||||
|
||||
let result = unsafe { _vm_munmap(address, size) };
|
||||
drop(mm);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub fn write(id: u32, address: usize, data: &[u8]) -> usize {
|
||||
transfer(id, address, data.as_ptr() as *mut c_void, data.len(), FOLL_WRITE)
|
||||
}
|
||||
|
||||
pub fn read(id: u32, address: usize, buffer: &mut [u8]) -> usize {
|
||||
transfer(id, address, buffer.as_mut_ptr() as *mut c_void, buffer.len(), 0)
|
||||
}
|
||||
|
||||
pub fn read_own(address: usize, buffer: &mut [u8]) -> bool {
|
||||
let len = buffer.len();
|
||||
transfer_with(super::native::current_task() as usize, address, buffer.as_mut_ptr() as *mut c_void,
|
||||
len, 0) == len
|
||||
}
|
||||
|
||||
pub fn write_own(address: usize, data: &[u8]) -> bool {
|
||||
transfer_with(super::native::current_task() as usize, address, data.as_ptr() as *mut c_void,
|
||||
data.len(), FOLL_WRITE) == data.len()
|
||||
}
|
||||
|
||||
fn transfer(id: u32, address: usize, buffer: *mut c_void, size: usize, flags: u32) -> usize {
|
||||
let Some(task) = HeldTask::with_id(id) else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
transfer_with(task.address(), address, buffer, size, flags)
|
||||
}
|
||||
|
||||
fn transfer_with(task: usize, address: usize, buffer: *mut c_void, size: usize, flags: u32) -> usize {
|
||||
let transferred = unsafe { _access_process_vm(task as *mut c_void, address, buffer, size as c_int, flags) };
|
||||
|
||||
transferred.max(0) as usize
|
||||
}
|
||||
|
||||
struct BorrowedMemory(*mut c_void);
|
||||
|
||||
impl BorrowedMemory {
|
||||
fn of(id: u32) -> Option<Self> {
|
||||
let task = HeldTask::with_id(id)?;
|
||||
let mm = unsafe { _get_task_mm(task.address() as *mut c_void) };
|
||||
if mm.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
unsafe { _kthread_use_mm(mm) };
|
||||
|
||||
Some(Self(mm))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for BorrowedMemory {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
_kthread_unuse_mm(self.0);
|
||||
_mmput(self.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const MAP_PRIVATE: usize = 0x02;
|
||||
const MAP_ANONYMOUS: usize = 0x20;
|
||||
const FOLL_WRITE: u32 = 0x01;
|
||||
const FIRST_ERROR_ADDRESS: usize = usize::MAX - 4095;
|
||||
const ESRCH: c_int = 3;
|
||||
|
||||
unsafe extern "C" {
|
||||
static _get_task_mm: unsafe extern "C" fn(*mut c_void) -> *mut c_void;
|
||||
static _mmput: unsafe extern "C" fn(*mut c_void);
|
||||
static _kthread_use_mm: unsafe extern "C" fn(*mut c_void);
|
||||
static _kthread_unuse_mm: unsafe extern "C" fn(*mut c_void);
|
||||
static _vm_mmap: unsafe extern "C" fn(*mut c_void, usize, usize, usize, usize, usize) -> usize;
|
||||
static _vm_munmap: unsafe extern "C" fn(usize, usize) -> c_int;
|
||||
static _access_process_vm: unsafe extern "C" fn(*mut c_void, usize, *mut c_void, c_int, u32) -> c_int;
|
||||
}
|
||||
@@ -72,6 +72,53 @@ pub unsafe fn let_go_of(file: *mut c_void) {
|
||||
unsafe { _fput(file) };
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
pub(crate) struct HeldTask(usize);
|
||||
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
impl HeldTask {
|
||||
pub(crate) fn with_id(id: u32) -> Option<Self> {
|
||||
let task = unsafe { _find_get_task_by_vpid(id as c_int) } as usize;
|
||||
(task != 0).then(|| Self(task))
|
||||
}
|
||||
|
||||
pub(crate) fn leader(&self) -> Option<Self> {
|
||||
let group_leader = super::layout::field_offset("task_struct", "group_leader")?;
|
||||
let usage = super::layout::field_offset("task_struct", "usage")?;
|
||||
|
||||
let flags = lock_tasklist();
|
||||
let leader = unsafe { ((self.0 + group_leader) as *const usize).read() };
|
||||
super::native::hold(leader + usage);
|
||||
unlock_tasklist(flags);
|
||||
|
||||
Some(Self(leader))
|
||||
}
|
||||
|
||||
pub(crate) fn address(&self) -> usize {
|
||||
self.0
|
||||
}
|
||||
|
||||
pub(crate) fn into_address(self) -> usize {
|
||||
let task = self.0;
|
||||
core::mem::forget(self);
|
||||
task
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn from_address(task: usize) -> Self {
|
||||
Self(task)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
impl Drop for HeldTask {
|
||||
fn drop(&mut self) {
|
||||
let usage = super::layout::field_offset("task_struct", "usage").unwrap();
|
||||
if super::native::let_go(self.0 + usage) {
|
||||
unsafe { ___put_task_struct(self.0 as *mut c_void) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn task_with_id(id: u32) -> Option<usize> {
|
||||
let layout = task_layout()?;
|
||||
|
||||
@@ -573,12 +620,16 @@ fn lock_tasklist() -> usize {
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
fn lock_tasklist() -> usize {
|
||||
unsafe {
|
||||
match (__raw_read_lock_irqsave, __raw_read_unlock_irqrestore) {
|
||||
(Some(lock), Some(_)) => lock(_tasklist_lock),
|
||||
_ => {
|
||||
__raw_read_lock.unwrap()(_tasklist_lock);
|
||||
match (__raw_read_lock_irqsave, __raw_read_unlock_irqrestore, __raw_read_lock) {
|
||||
(Some(lock), Some(_), _) => lock(_tasklist_lock),
|
||||
(_, _, Some(lock)) => {
|
||||
lock(_tasklist_lock);
|
||||
0
|
||||
}
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
_ => super::native::read_lock_irqsave(_tasklist_lock as usize),
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
_ => panic!("no way to lock the task list"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -591,9 +642,13 @@ fn unlock_tasklist(flags: usize) {
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
fn unlock_tasklist(flags: usize) {
|
||||
unsafe {
|
||||
match (__raw_read_lock_irqsave, __raw_read_unlock_irqrestore) {
|
||||
(Some(_), Some(unlock)) => unlock(_tasklist_lock, flags),
|
||||
_ => __raw_read_unlock.unwrap()(_tasklist_lock),
|
||||
match (__raw_read_lock_irqsave, __raw_read_unlock_irqrestore, __raw_read_unlock) {
|
||||
(Some(_), Some(unlock), _) => unlock(_tasklist_lock, flags),
|
||||
(_, _, Some(unlock)) => unlock(_tasklist_lock),
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
_ => super::native::read_unlock_irqrestore(_tasklist_lock as usize, flags),
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
_ => panic!("no way to unlock the task list"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -602,10 +657,6 @@ const PIDTYPE_TGID: c_int = 1;
|
||||
const PIDTYPE_MAX: usize = 4;
|
||||
|
||||
pub fn cloak_task(task: *mut c_void) {
|
||||
// detach_pid's arity differs across kernels (the classic detach_pid(task, type)
|
||||
// versus the batched detach_pid(pids, task, type) paired with free_pids); calling
|
||||
// the wrong one drives a UBSAN/CFI trap on hardened kernels. Leave the thread
|
||||
// visible rather than risk it.
|
||||
if true {
|
||||
let _ = task;
|
||||
return;
|
||||
@@ -638,8 +689,17 @@ unsafe fn detach_pid(pids: *mut *mut c_void, task: *mut c_void, kind: c_int) {
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
unsafe fn detach_pid(pids: *mut *mut c_void, task: *mut c_void, kind: c_int) {
|
||||
unsafe { _detach_pid(pids, task, kind) };
|
||||
pub(crate) unsafe fn detach_pid(pids: *mut *mut c_void, task: *mut c_void, kind: c_int) {
|
||||
type ReleasingDetachPid = unsafe extern "C" fn(*mut c_void, c_int);
|
||||
|
||||
unsafe {
|
||||
if _free_pids.is_some() {
|
||||
_detach_pid(pids, task, kind);
|
||||
} else {
|
||||
let releasing: ReleasingDetachPid = core::mem::transmute(_detach_pid);
|
||||
releasing(task, kind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
@@ -648,11 +708,13 @@ unsafe fn free_pids(pids: *mut *mut c_void) {
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
unsafe fn free_pids(pids: *mut *mut c_void) {
|
||||
unsafe { _free_pids(pids) };
|
||||
pub(crate) unsafe fn free_pids(pids: *mut *mut c_void) {
|
||||
if let Some(free) = unsafe { _free_pids } {
|
||||
unsafe { free(pids) };
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn unlink(node: usize) {
|
||||
pub(crate) unsafe fn unlink(node: usize) {
|
||||
let next = unsafe { (node as *const usize).read() };
|
||||
let prev = unsafe { ((node + WORD_SIZE) as *const usize).read() };
|
||||
unsafe {
|
||||
@@ -664,34 +726,64 @@ unsafe fn unlink(node: usize) {
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
fn write_lock_tasklist() -> usize {
|
||||
pub(crate) fn write_lock_tasklist() -> usize {
|
||||
unsafe { frida_k_write_lock_tasklist() }
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
fn write_lock_tasklist() -> usize {
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
pub(crate) unsafe fn unlink_for_readers(node: usize) {
|
||||
let next = unsafe { (node as *const usize).read() };
|
||||
let prev = unsafe { ((node + WORD_SIZE) as *const usize).read() };
|
||||
unsafe {
|
||||
match (__raw_write_lock_irqsave, __raw_write_unlock_irqrestore) {
|
||||
(Some(lock), Some(_)) => lock(_tasklist_lock),
|
||||
_ => {
|
||||
__raw_write_lock.unwrap()(_tasklist_lock);
|
||||
((next + WORD_SIZE) as *mut usize).write(prev);
|
||||
(prev as *mut usize).write(next);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
pub(crate) unsafe fn append_for_readers(node: usize, head: usize) {
|
||||
let prev = unsafe { ((head + WORD_SIZE) as *const usize).read() };
|
||||
unsafe {
|
||||
(node as *mut usize).write(head);
|
||||
((node + WORD_SIZE) as *mut usize).write(prev);
|
||||
core::sync::atomic::AtomicUsize::from_ptr(prev as *mut usize)
|
||||
.store(node, core::sync::atomic::Ordering::Release);
|
||||
((head + WORD_SIZE) as *mut usize).write(node);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
pub(crate) fn write_lock_tasklist() -> usize {
|
||||
unsafe {
|
||||
match (__raw_write_lock_irqsave, __raw_write_unlock_irqrestore, __raw_write_lock) {
|
||||
(Some(lock), Some(_), _) => lock(_tasklist_lock),
|
||||
(_, _, Some(lock)) => {
|
||||
lock(_tasklist_lock);
|
||||
0
|
||||
}
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
_ => super::native::write_lock_irqsave(_tasklist_lock as usize),
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
_ => panic!("no way to lock the task list"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
fn write_unlock_tasklist(flags: usize) {
|
||||
pub(crate) fn write_unlock_tasklist(flags: usize) {
|
||||
unsafe { frida_k_write_unlock_tasklist(flags) };
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
fn write_unlock_tasklist(flags: usize) {
|
||||
pub(crate) fn write_unlock_tasklist(flags: usize) {
|
||||
unsafe {
|
||||
match (__raw_write_lock_irqsave, __raw_write_unlock_irqrestore) {
|
||||
(Some(_), Some(unlock)) => unlock(_tasklist_lock, flags),
|
||||
_ => __raw_write_unlock.unwrap()(_tasklist_lock),
|
||||
match (__raw_write_lock_irqsave, __raw_write_unlock_irqrestore, __raw_write_unlock) {
|
||||
(Some(_), Some(unlock), _) => unlock(_tasklist_lock, flags),
|
||||
(_, _, Some(unlock)) => unlock(_tasklist_lock),
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
_ => super::native::write_unlock_irqrestore(_tasklist_lock as usize, flags),
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
_ => panic!("no way to unlock the task list"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -728,7 +820,11 @@ unsafe extern "C" {
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _detach_pid: unsafe extern "C" fn(*mut *mut c_void, *mut c_void, c_int);
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _free_pids: unsafe extern "C" fn(*mut *mut c_void);
|
||||
static _free_pids: Option<unsafe extern "C" fn(*mut *mut c_void)>;
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
static _find_get_task_by_vpid: unsafe extern "C" fn(c_int) -> *mut c_void;
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
static ___put_task_struct: unsafe extern "C" fn(*mut c_void);
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static __raw_write_lock: Option<unsafe extern "C" fn(*mut c_void)>;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
|
||||
@@ -625,6 +625,24 @@ int frida_k_unregister_module_notifier (void * a0)
|
||||
return ((fn_t) _unregister_module_notifier) (a0);
|
||||
}
|
||||
|
||||
extern void * _mutex_lock;
|
||||
|
||||
void frida_k_mutex_lock (void * a0)
|
||||
{
|
||||
typedef void (__attribute__ ((regparm (3))) * fn_t) (void *);
|
||||
|
||||
((fn_t) _mutex_lock) (a0);
|
||||
}
|
||||
|
||||
extern void * _mutex_unlock;
|
||||
|
||||
void frida_k_mutex_unlock (void * a0)
|
||||
{
|
||||
typedef void (__attribute__ ((regparm (3))) * fn_t) (void *);
|
||||
|
||||
((fn_t) _mutex_unlock) (a0);
|
||||
}
|
||||
|
||||
extern int frida_cb_module_state (void * nb, unsigned long action, void * data);
|
||||
|
||||
__attribute__ ((regparm (3))) int
|
||||
@@ -658,3 +676,57 @@ frida_kcb_die (void * nb, unsigned long action, void * data)
|
||||
{
|
||||
return frida_cb_die (nb, action, data);
|
||||
}
|
||||
|
||||
extern void * _frida_kmod_link_open;
|
||||
|
||||
int frida_k_frida_kmod_link_open (void)
|
||||
{
|
||||
typedef int (__attribute__ ((regparm (3))) * fn_t) (void);
|
||||
|
||||
return ((fn_t) _frida_kmod_link_open) ();
|
||||
}
|
||||
|
||||
extern void * _frida_kmod_link_close;
|
||||
|
||||
void frida_k_frida_kmod_link_close (void)
|
||||
{
|
||||
typedef void (__attribute__ ((regparm (3))) * fn_t) (void);
|
||||
|
||||
((fn_t) _frida_kmod_link_close) ();
|
||||
}
|
||||
|
||||
extern void * _frida_kmod_link_send;
|
||||
|
||||
int frida_k_frida_kmod_link_send (const void * data, unsigned long size)
|
||||
{
|
||||
typedef int (__attribute__ ((regparm (3))) * fn_t) (const void *, unsigned long);
|
||||
|
||||
return ((fn_t) _frida_kmod_link_send) (data, size);
|
||||
}
|
||||
|
||||
extern void * _frida_kmod_link_recv;
|
||||
|
||||
long frida_k_frida_kmod_link_recv (void * data, unsigned long size)
|
||||
{
|
||||
typedef long (__attribute__ ((regparm (3))) * fn_t) (void *, unsigned long);
|
||||
|
||||
return ((fn_t) _frida_kmod_link_recv) (data, size);
|
||||
}
|
||||
|
||||
extern void * _frida_kmod_link_pending;
|
||||
|
||||
_Bool frida_k_frida_kmod_link_pending (void)
|
||||
{
|
||||
typedef _Bool (__attribute__ ((regparm (3))) * fn_t) (void);
|
||||
|
||||
return ((fn_t) _frida_kmod_link_pending) ();
|
||||
}
|
||||
|
||||
extern void * _frida_kmod_exit_worker;
|
||||
|
||||
void frida_k_frida_kmod_exit_worker (void)
|
||||
{
|
||||
typedef void (__attribute__ ((regparm (3))) * fn_t) (void);
|
||||
|
||||
((fn_t) _frida_kmod_exit_worker) ();
|
||||
}
|
||||
@@ -125,7 +125,6 @@ fn leave() -> ! {
|
||||
syscall(CLOSE, descriptor as usize, 0, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
Arena::at(unsafe { ARENA }).gone();
|
||||
syscall(EXIT_GROUP, 0, 0, 0, 0, 0, 0);
|
||||
|
||||
loop {}
|
||||
@@ -459,7 +458,8 @@ unsafe extern "C" {
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
unsafe fn start_thread(stack: usize, carried: usize) -> isize {
|
||||
let launch = stack - 8;
|
||||
let aligned_argument = stack - 16;
|
||||
let launch = aligned_argument - 4;
|
||||
unsafe {
|
||||
(launch as *mut usize).write(enter_thread as *const () as usize);
|
||||
((launch + 4) as *mut usize).write(carried);
|
||||
|
||||
@@ -0,0 +1,419 @@
|
||||
use core::ffi::{c_int, c_void};
|
||||
use core::mem::size_of;
|
||||
use core::ptr;
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use super::layout::field_offset;
|
||||
use super::native::{self, hold, let_go, lock_spinlock, unlock_spinlock};
|
||||
use super::processes::{
|
||||
HeldTask, append_for_readers, detach_pid, free_pids, unlink, unlink_for_readers,
|
||||
write_lock_tasklist, write_unlock_tasklist,
|
||||
};
|
||||
|
||||
pub fn start_in_process(id: u32, entry: usize, stack: usize, argument: usize, tls: usize) -> c_int {
|
||||
let Some(layout) = describe_thread_group() else {
|
||||
return -ENOSYS;
|
||||
};
|
||||
let Some(task) = HeldTask::with_id(id) else {
|
||||
return -ESRCH;
|
||||
};
|
||||
let Some(leader) = task.leader() else {
|
||||
return -ESRCH;
|
||||
};
|
||||
let mm = unsafe { _get_task_mm(task.address() as *mut c_void) };
|
||||
if mm.is_null() {
|
||||
return -ESRCH;
|
||||
}
|
||||
|
||||
let birth = native::alloc(size_of::<Birth>()) as *mut Birth;
|
||||
unsafe {
|
||||
birth.write(Birth { entry, stack, argument, tls, leader: leader.into_address(), layout });
|
||||
|
||||
_kthread_use_mm(mm);
|
||||
let started = _user_mode_thread(thread_entry(), birth as *mut c_void, 0);
|
||||
_kthread_unuse_mm(mm);
|
||||
_mmput(mm);
|
||||
|
||||
if started < 0 {
|
||||
drop(HeldTask::from_address((*birth).leader));
|
||||
native::free(birth as *mut u8, size_of::<Birth>());
|
||||
}
|
||||
|
||||
started
|
||||
}
|
||||
}
|
||||
|
||||
struct Birth {
|
||||
entry: usize,
|
||||
stack: usize,
|
||||
argument: usize,
|
||||
tls: usize,
|
||||
leader: usize,
|
||||
layout: ThreadGroupLayout,
|
||||
}
|
||||
|
||||
fn thread_entry() -> native::KthreadFn {
|
||||
native::kernel_thread_function(&THREAD_ENTRY, frida_cb_user_thread)
|
||||
}
|
||||
|
||||
static THREAD_ENTRY: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
unsafe extern "C" fn frida_cb_user_thread(argument: *mut c_void) -> c_int {
|
||||
let birth = unsafe { (argument as *const Birth).read() };
|
||||
native::free(argument as *mut u8, size_of::<Birth>());
|
||||
|
||||
let leader = unsafe { HeldTask::from_address(birth.leader) };
|
||||
let child = native::current_task() as usize;
|
||||
join_thread_group(child, leader.address(), &birth.layout);
|
||||
adopt_context_of(child, leader.address(), &birth.layout);
|
||||
drop(leader);
|
||||
|
||||
super::injection::hand_userland_to(birth.entry, birth.stack, birth.argument);
|
||||
use_thread_pointer(child, birth.tls, &birth.layout);
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
fn join_thread_group(child: usize, leader: usize, layout: &ThreadGroupLayout) {
|
||||
let task = &layout.task;
|
||||
let old_signal = read_word(child + task.signal);
|
||||
let old_sighand = read_word(child + task.sighand);
|
||||
let signal = read_word(leader + task.signal);
|
||||
let sighand = read_word(leader + task.sighand);
|
||||
let mut freed = [ptr::null_mut::<c_void>(); PIDTYPE_MAX];
|
||||
|
||||
let flags = write_lock_tasklist();
|
||||
|
||||
unsafe {
|
||||
detach_pid(freed.as_mut_ptr(), child as *mut c_void, PIDTYPE_SID);
|
||||
detach_pid(freed.as_mut_ptr(), child as *mut c_void, PIDTYPE_PGID);
|
||||
detach_pid(freed.as_mut_ptr(), child as *mut c_void, PIDTYPE_TGID);
|
||||
|
||||
unlink_for_readers(child + task.tasks);
|
||||
unlink(child + task.sibling);
|
||||
unlink_for_readers(child + task.thread_node);
|
||||
}
|
||||
|
||||
let parent = read_word(leader + task.real_parent);
|
||||
write_word(child + task.real_parent, parent);
|
||||
write_word(child + task.parent, parent);
|
||||
write_word(child + task.group_leader, leader);
|
||||
write_i32(child + task.tgid, read_i32(leader + task.tgid));
|
||||
write_i32(child + task.exit_signal, -1);
|
||||
write_word(child + task.signal, signal);
|
||||
write_word(child + task.sighand, sighand);
|
||||
|
||||
hold(sighand + layout.sighand.count);
|
||||
|
||||
lock_spinlock(sighand + layout.sighand.siglock);
|
||||
|
||||
write_i32(signal + layout.signal.nr_threads, read_i32(signal + layout.signal.nr_threads) + 1);
|
||||
if let Some(quick_threads) = layout.signal.quick_threads {
|
||||
write_i32(signal + quick_threads, read_i32(signal + quick_threads) + 1);
|
||||
}
|
||||
hold(signal + layout.signal.live);
|
||||
hold(signal + layout.signal.sigcnt);
|
||||
|
||||
unsafe {
|
||||
(layout.calls.task_join_group_stop)(child as *mut c_void);
|
||||
|
||||
if let Some(thread_group) = task.thread_group {
|
||||
append_for_readers(child + thread_group, leader + thread_group);
|
||||
}
|
||||
append_for_readers(child + task.thread_node, signal + layout.signal.thread_head);
|
||||
}
|
||||
|
||||
unlock_spinlock(sighand + layout.sighand.siglock);
|
||||
|
||||
write_unlock_tasklist(flags);
|
||||
|
||||
unsafe {
|
||||
free_pids(freed.as_mut_ptr());
|
||||
|
||||
(layout.calls.cleanup_sighand)(old_sighand as *mut c_void);
|
||||
if let_go(old_signal + layout.signal.sigcnt) {
|
||||
_kmem_cache_free(*_signal_cachep, old_signal as *mut c_void);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn adopt_context_of(child: usize, leader: usize, layout: &ThreadGroupLayout) {
|
||||
let task = &layout.task;
|
||||
|
||||
let files = read_word(leader + task.files);
|
||||
let old_files = read_word(child + task.files);
|
||||
hold(files + layout.files_count);
|
||||
lock_spinlock(child + task.alloc_lock);
|
||||
write_word(child + task.files, files);
|
||||
unlock_spinlock(child + task.alloc_lock);
|
||||
unsafe { (layout.calls.put_files_struct)(old_files as *mut c_void) };
|
||||
|
||||
let fs = read_word(leader + task.fs);
|
||||
let old_fs = read_word(child + task.fs);
|
||||
lock_spinlock(fs + layout.fs.lock);
|
||||
write_i32(fs + layout.fs.users, read_i32(fs + layout.fs.users) + 1);
|
||||
unlock_spinlock(fs + layout.fs.lock);
|
||||
lock_spinlock(child + task.alloc_lock);
|
||||
write_word(child + task.fs, fs);
|
||||
unlock_spinlock(child + task.alloc_lock);
|
||||
lock_spinlock(old_fs + layout.fs.lock);
|
||||
let users_left = read_i32(old_fs + layout.fs.users) - 1;
|
||||
write_i32(old_fs + layout.fs.users, users_left);
|
||||
unlock_spinlock(old_fs + layout.fs.lock);
|
||||
if users_left == 0 {
|
||||
unsafe { (layout.calls.free_fs_struct)(old_fs as *mut c_void) };
|
||||
}
|
||||
|
||||
let real_cred = read_word(leader + task.real_cred);
|
||||
let cred = read_word(leader + task.cred);
|
||||
let old_real_cred = read_word(child + task.real_cred);
|
||||
let old_cred = read_word(child + task.cred);
|
||||
hold(real_cred + layout.cred_usage);
|
||||
write_word(child + task.real_cred, real_cred);
|
||||
hold(cred + layout.cred_usage);
|
||||
unsafe { AtomicUsize::from_ptr((child + task.cred) as *mut usize).store(cred, Ordering::Release) };
|
||||
release_cred(old_cred, layout);
|
||||
release_cred(old_real_cred, layout);
|
||||
|
||||
let namespaces = read_word(leader + task.nsproxy);
|
||||
hold(namespaces + layout.nsproxy_count);
|
||||
unsafe { (layout.calls.switch_task_namespaces)(child as *mut c_void, namespaces as *mut c_void) };
|
||||
|
||||
if let Some(undo) = &layout.semaphore_undo {
|
||||
let undo_list = read_word(leader + undo.list);
|
||||
if undo_list != 0 {
|
||||
hold(undo_list + undo.refcnt);
|
||||
write_word(child + undo.list, undo_list);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
if let Some(lengths) = &layout.vector_lengths {
|
||||
adopt_vector_lengths(child, leader, lengths);
|
||||
}
|
||||
}
|
||||
|
||||
fn release_cred(cred: usize, layout: &ThreadGroupLayout) {
|
||||
if let_go(cred + layout.cred_usage) {
|
||||
unsafe { (layout.calls.put_cred)(cred as *mut c_void) };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn adopt_vector_lengths(child: usize, leader: usize, lengths: &VectorLengthLayout) {
|
||||
for array in [lengths.current, lengths.on_exec] {
|
||||
for kind in 0..VECTOR_KINDS {
|
||||
let at = array + kind * size_of::<u32>();
|
||||
write_i32(child + at, read_i32(leader + at));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn use_thread_pointer(child: usize, tls: usize, layout: &ThreadGroupLayout) {
|
||||
const MSR_FS_BASE: u32 = 0xc000_0100;
|
||||
|
||||
write_word(child + layout.task.thread_pointer, tls);
|
||||
unsafe {
|
||||
core::arch::asm!("wrmsr", in("ecx") MSR_FS_BASE, in("eax") tls as u32,
|
||||
in("edx") (tls >> 32) as u32, options(nostack));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn use_thread_pointer(child: usize, tls: usize, layout: &ThreadGroupLayout) {
|
||||
write_word(child + layout.task.thread_pointer, tls);
|
||||
unsafe { core::arch::asm!("msr tpidr_el0, {}", in(reg) tls, options(nostack)) };
|
||||
}
|
||||
|
||||
struct ThreadGroupLayout {
|
||||
calls: ThreadGroupCalls,
|
||||
task: TaskLayout,
|
||||
signal: SignalLayout,
|
||||
sighand: SighandLayout,
|
||||
fs: FsLayout,
|
||||
files_count: usize,
|
||||
cred_usage: usize,
|
||||
nsproxy_count: usize,
|
||||
semaphore_undo: Option<SemaphoreUndoLayout>,
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
vector_lengths: Option<VectorLengthLayout>,
|
||||
}
|
||||
|
||||
struct ThreadGroupCalls {
|
||||
task_join_group_stop: unsafe extern "C" fn(*mut c_void),
|
||||
cleanup_sighand: unsafe extern "C" fn(*mut c_void),
|
||||
put_files_struct: unsafe extern "C" fn(*mut c_void),
|
||||
free_fs_struct: unsafe extern "C" fn(*mut c_void),
|
||||
put_cred: unsafe extern "C" fn(*mut c_void),
|
||||
switch_task_namespaces: unsafe extern "C" fn(*mut c_void, *mut c_void),
|
||||
}
|
||||
|
||||
struct TaskLayout {
|
||||
tgid: usize,
|
||||
real_parent: usize,
|
||||
parent: usize,
|
||||
group_leader: usize,
|
||||
exit_signal: usize,
|
||||
signal: usize,
|
||||
sighand: usize,
|
||||
tasks: usize,
|
||||
sibling: usize,
|
||||
thread_node: usize,
|
||||
thread_group: Option<usize>,
|
||||
files: usize,
|
||||
fs: usize,
|
||||
cred: usize,
|
||||
real_cred: usize,
|
||||
nsproxy: usize,
|
||||
alloc_lock: usize,
|
||||
thread_pointer: usize,
|
||||
}
|
||||
|
||||
struct SignalLayout {
|
||||
nr_threads: usize,
|
||||
quick_threads: Option<usize>,
|
||||
live: usize,
|
||||
sigcnt: usize,
|
||||
thread_head: usize,
|
||||
}
|
||||
|
||||
struct SighandLayout {
|
||||
count: usize,
|
||||
siglock: usize,
|
||||
}
|
||||
|
||||
struct FsLayout {
|
||||
users: usize,
|
||||
lock: usize,
|
||||
}
|
||||
|
||||
struct SemaphoreUndoLayout {
|
||||
list: usize,
|
||||
refcnt: usize,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
struct VectorLengthLayout {
|
||||
current: usize,
|
||||
on_exec: usize,
|
||||
}
|
||||
|
||||
fn describe_thread_group() -> Option<ThreadGroupLayout> {
|
||||
Some(ThreadGroupLayout {
|
||||
calls: unsafe {
|
||||
ThreadGroupCalls {
|
||||
task_join_group_stop: _task_join_group_stop?,
|
||||
cleanup_sighand: ___cleanup_sighand?,
|
||||
put_files_struct: _put_files_struct?,
|
||||
free_fs_struct: _free_fs_struct?,
|
||||
put_cred: ___put_cred?,
|
||||
switch_task_namespaces: _switch_task_namespaces?,
|
||||
}
|
||||
},
|
||||
task: TaskLayout {
|
||||
tgid: field_offset("task_struct", "tgid")?,
|
||||
real_parent: field_offset("task_struct", "real_parent")?,
|
||||
parent: field_offset("task_struct", "parent")?,
|
||||
group_leader: field_offset("task_struct", "group_leader")?,
|
||||
exit_signal: field_offset("task_struct", "exit_signal")?,
|
||||
signal: field_offset("task_struct", "signal")?,
|
||||
sighand: field_offset("task_struct", "sighand")?,
|
||||
tasks: field_offset("task_struct", "tasks")?,
|
||||
sibling: field_offset("task_struct", "sibling")?,
|
||||
thread_node: field_offset("task_struct", "thread_node")?,
|
||||
thread_group: field_offset("task_struct", "thread_group"),
|
||||
files: field_offset("task_struct", "files")?,
|
||||
fs: field_offset("task_struct", "fs")?,
|
||||
cred: field_offset("task_struct", "cred")?,
|
||||
real_cred: field_offset("task_struct", "real_cred")?,
|
||||
nsproxy: field_offset("task_struct", "nsproxy")?,
|
||||
alloc_lock: field_offset("task_struct", "alloc_lock")?,
|
||||
thread_pointer: field_offset("task_struct", THREAD_POINTER_FIELD)?,
|
||||
},
|
||||
signal: SignalLayout {
|
||||
nr_threads: field_offset("signal_struct", "nr_threads")?,
|
||||
quick_threads: field_offset("signal_struct", "quick_threads"),
|
||||
live: field_offset("signal_struct", "live")?,
|
||||
sigcnt: field_offset("signal_struct", "sigcnt")?,
|
||||
thread_head: field_offset("signal_struct", "thread_head")?,
|
||||
},
|
||||
sighand: SighandLayout {
|
||||
count: field_offset("sighand_struct", "count")?,
|
||||
siglock: field_offset("sighand_struct", "siglock")?,
|
||||
},
|
||||
fs: FsLayout {
|
||||
users: field_offset("fs_struct", "users")?,
|
||||
lock: field_offset("fs_struct", "lock").or_else(|| field_offset("fs_struct", "seq.lock"))?,
|
||||
},
|
||||
files_count: field_offset("files_struct", "count")?,
|
||||
cred_usage: field_offset("cred", "usage")?,
|
||||
nsproxy_count: field_offset("nsproxy", "count")?,
|
||||
semaphore_undo: describe_semaphore_undo(),
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
vector_lengths: describe_vector_lengths(),
|
||||
})
|
||||
}
|
||||
|
||||
fn describe_semaphore_undo() -> Option<SemaphoreUndoLayout> {
|
||||
Some(SemaphoreUndoLayout {
|
||||
list: field_offset("task_struct", "sysvsem.undo_list")?,
|
||||
refcnt: field_offset("sem_undo_list", "refcnt")?,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn describe_vector_lengths() -> Option<VectorLengthLayout> {
|
||||
Some(VectorLengthLayout {
|
||||
current: field_offset("task_struct", "thread.vl")?,
|
||||
on_exec: field_offset("task_struct", "thread.vl_onexec")?,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
const THREAD_POINTER_FIELD: &str = "thread.fsbase";
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const THREAD_POINTER_FIELD: &str = "thread.uw.tp_value";
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const VECTOR_KINDS: usize = 2;
|
||||
|
||||
fn read_word(address: usize) -> usize {
|
||||
unsafe { (address as *const usize).read() }
|
||||
}
|
||||
|
||||
fn write_word(address: usize, value: usize) {
|
||||
unsafe { (address as *mut usize).write(value) };
|
||||
}
|
||||
|
||||
fn read_i32(address: usize) -> i32 {
|
||||
unsafe { (address as *const i32).read() }
|
||||
}
|
||||
|
||||
fn write_i32(address: usize, value: i32) {
|
||||
unsafe { (address as *mut i32).write(value) };
|
||||
}
|
||||
|
||||
const PIDTYPE_TGID: c_int = 1;
|
||||
const PIDTYPE_PGID: c_int = 2;
|
||||
const PIDTYPE_SID: c_int = 3;
|
||||
const PIDTYPE_MAX: usize = 4;
|
||||
|
||||
const ESRCH: c_int = 3;
|
||||
const ENOSYS: c_int = 38;
|
||||
|
||||
unsafe extern "C" {
|
||||
static _get_task_mm: unsafe extern "C" fn(*mut c_void) -> *mut c_void;
|
||||
static _mmput: unsafe extern "C" fn(*mut c_void);
|
||||
static _kthread_use_mm: unsafe extern "C" fn(*mut c_void);
|
||||
static _kthread_unuse_mm: unsafe extern "C" fn(*mut c_void);
|
||||
static _user_mode_thread: unsafe extern "C" fn(native::KthreadFn, *mut c_void, usize) -> c_int;
|
||||
static _task_join_group_stop: Option<unsafe extern "C" fn(*mut c_void)>;
|
||||
static ___cleanup_sighand: Option<unsafe extern "C" fn(*mut c_void)>;
|
||||
static _kmem_cache_free: unsafe extern "C" fn(*mut c_void, *mut c_void);
|
||||
static _signal_cachep: *const *mut c_void;
|
||||
static _put_files_struct: Option<unsafe extern "C" fn(*mut c_void)>;
|
||||
static _free_fs_struct: Option<unsafe extern "C" fn(*mut c_void)>;
|
||||
static ___put_cred: Option<unsafe extern "C" fn(*mut c_void)>;
|
||||
static _switch_task_namespaces: Option<unsafe extern "C" fn(*mut c_void, *mut c_void)>;
|
||||
}
|
||||
@@ -2,9 +2,6 @@
|
||||
// image and calls its `_start`. All of the agent lives in the library crate;
|
||||
// this target exists only to produce that link. build.rs passes `--undefined
|
||||
// _start` so the entrypoint survives --gc-sections.
|
||||
//
|
||||
// The Linux flavour is built as a static library instead and linked into a
|
||||
// kernel module by kbuild — see linux/Kbuild.
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
@@ -1059,7 +1059,9 @@ fn write_trampoline(trampoline: u32, entry: u32, stack_top: u32) {
|
||||
0xff, 0xd2,
|
||||
];
|
||||
let stack_bottom = stack_top + 0x40 - INJECTION_STACK_SIZE as u32;
|
||||
code[5..9].copy_from_slice(&stack_top.to_le_bytes());
|
||||
let aligned_argument = stack_top - 16;
|
||||
let sp_before_push = aligned_argument + 4;
|
||||
code[5..9].copy_from_slice(&sp_before_push.to_le_bytes());
|
||||
code[11..15].copy_from_slice(&entry.to_le_bytes());
|
||||
code[32..36].copy_from_slice(&stack_bottom.to_le_bytes());
|
||||
code[39..43].copy_from_slice(&kernel32_export(b"ExitThread").to_le_bytes());
|
||||
|
||||
@@ -3338,18 +3338,12 @@ const BRANCH_TO_STEP_TARGET: u32 = 0xd61f_0200;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const STATUS_NOT_FOUND: i32 = -1_073_741_275;
|
||||
|
||||
// The argument travels in a register here, and the stack only has to be aligned the way a call
|
||||
// would have left it.
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
fn seed_stack(_process: *mut c_void, top: u64, _argument: u64) -> u64 {
|
||||
(top - 64) & !15
|
||||
}
|
||||
|
||||
// A cdecl entry looks for its argument above the return address, thus the stack is laid out
|
||||
// before the thread runs, in the process that will read it.
|
||||
#[cfg(target_arch = "x86")]
|
||||
fn seed_stack(process: *mut c_void, top: u64, argument: u64) -> u64 {
|
||||
let sp = (top - 32) & !15;
|
||||
let aligned_argument = (top - 32) & !15;
|
||||
let sp = aligned_argument - 4;
|
||||
|
||||
unsafe {
|
||||
let mut apc_state = [0usize; APC_STATE_WORDS];
|
||||
@@ -3362,6 +3356,19 @@ fn seed_stack(process: *mut c_void, top: u64, argument: u64) -> u64 {
|
||||
sp
|
||||
}
|
||||
|
||||
// The argument travels in a register here, and the stack only has to be aligned the way a call
|
||||
// would have left it.
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn seed_stack(_process: *mut c_void, top: u64, _argument: u64) -> u64 {
|
||||
let before_call = (top - 64) & !15;
|
||||
before_call - size_of::<u64>() as u64
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn seed_stack(_process: *mut c_void, top: u64, _argument: u64) -> u64 {
|
||||
(top - 64) & !15
|
||||
}
|
||||
|
||||
fn free_in_process(process_handle: *mut c_void, address: u64) {
|
||||
let mut base = address as *mut u8;
|
||||
let mut region = 0usize;
|
||||
|
||||
@@ -3,7 +3,7 @@ use alloc::string::{String, ToString};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::bindings::{
|
||||
GumDarwinExportDetails, GumDarwinModule, GumMemoryRange, GumModuleRegistry, GumAddress,
|
||||
GumMachoExportDetails, GumMachoModule, GumMemoryRange, GumModuleRegistry, GumAddress,
|
||||
gboolean, g_object_unref, gpointer, gsize,
|
||||
};
|
||||
use crate::gum;
|
||||
@@ -33,7 +33,7 @@ pub fn enumerate_exports_in_range(from: u64, to: u64,
|
||||
|
||||
let mut asking = Asking { found, base: image.base };
|
||||
unsafe {
|
||||
crate::bindings::gum_darwin_module_enumerate_exports(module,
|
||||
crate::bindings::gum_macho_module_enumerate_exports(module,
|
||||
Some(crate::signed_to_be_called_back(note_an_export, 0)),
|
||||
&mut asking as *mut Asking<'_, '_> as gpointer);
|
||||
g_object_unref(module as gpointer);
|
||||
@@ -45,7 +45,7 @@ struct Asking<'a, 'b> {
|
||||
base: u64,
|
||||
}
|
||||
|
||||
unsafe extern "C" fn note_an_export(details: *const GumDarwinExportDetails, asking: gpointer)
|
||||
unsafe extern "C" fn note_an_export(details: *const GumMachoExportDetails, asking: gpointer)
|
||||
-> gboolean
|
||||
{
|
||||
let asking = unsafe { &mut *(asking as *mut Asking<'_, '_>) };
|
||||
@@ -146,10 +146,10 @@ fn where_the_loader_keeps_its_list() -> Option<u64> {
|
||||
(list != 0).then_some(list)
|
||||
}
|
||||
|
||||
fn read_the_image(base: u64, name: &str) -> Option<*mut GumDarwinModule> {
|
||||
fn read_the_image(base: u64, name: &str) -> Option<*mut GumMachoModule> {
|
||||
let named = alloc::ffi::CString::new(name).ok()?;
|
||||
let module = unsafe {
|
||||
crate::bindings::gum_darwin_module_new_from_memory(named.as_ptr(), NO_TASK_TO_ASK,
|
||||
crate::bindings::gum_macho_module_new_from_memory(named.as_ptr(), NO_TASK_TO_ASK,
|
||||
base as GumAddress, 0, core::ptr::null_mut())
|
||||
};
|
||||
|
||||
@@ -163,7 +163,7 @@ fn how_much_of_it_there_is(base: u64, name: &str) -> u64 {
|
||||
|
||||
let mut size = 0;
|
||||
for step in 0.. {
|
||||
let segment = unsafe { crate::bindings::gum_darwin_module_get_nth_segment(module, step) };
|
||||
let segment = unsafe { crate::bindings::gum_macho_module_get_nth_segment(module, step) };
|
||||
if segment.is_null() {
|
||||
break;
|
||||
}
|
||||
|
||||
+13
-13
@@ -8,7 +8,7 @@ namespace Frida.Barebone {
|
||||
public abstract Gum.PageProtection granted_protection ();
|
||||
|
||||
public abstract async Allocation allocate (size_t size, size_t alignment, Cancellable? cancellable)
|
||||
throws Error, IOError;
|
||||
throws Efni.Error, IOError;
|
||||
}
|
||||
|
||||
public interface Allocation : Object {
|
||||
@@ -20,11 +20,11 @@ namespace Frida.Barebone {
|
||||
get;
|
||||
}
|
||||
|
||||
public abstract async void deallocate (Cancellable? cancellable) throws Error, IOError;
|
||||
public abstract async void deallocate (Cancellable? cancellable) throws Efni.Error, IOError;
|
||||
}
|
||||
|
||||
internal async void protect_unless_already (Machine machine, Allocator allocator, uint64 address, size_t size,
|
||||
Gum.PageProtection prot, Cancellable? cancellable) throws Error, IOError {
|
||||
Gum.PageProtection prot, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if ((allocator.granted_protection () & prot) == prot)
|
||||
return;
|
||||
|
||||
@@ -55,9 +55,9 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async Allocation allocate (size_t size, size_t alignment, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
if (size > _size)
|
||||
throw new Error.NOT_SUPPORTED ("Reserved region is too small");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Reserved region is too small");
|
||||
|
||||
return new FixedAllocation (address, size);
|
||||
}
|
||||
@@ -84,7 +84,7 @@ namespace Frida.Barebone {
|
||||
this._size = size;
|
||||
}
|
||||
|
||||
public async void deallocate (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void deallocate (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,8 +105,8 @@ namespace Frida.Barebone {
|
||||
this._page_size = page_size;
|
||||
}
|
||||
|
||||
public async Allocation allocate (size_t size, size_t alignment, Cancellable? cancellable) throws Error, IOError {
|
||||
throw new Error.NOT_SUPPORTED ("To enable this feature, specify an allocator in your FRIDA_BAREBONE_CONFIG");
|
||||
public async Allocation allocate (size_t size, size_t alignment, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
throw new Efni.Error.NOT_SUPPORTED ("To enable this feature, specify an allocator in your FRIDA_BAREBONE_CONFIG");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ namespace Frida.Barebone {
|
||||
this.cursor = config.physical_base.address;
|
||||
}
|
||||
|
||||
public async Allocation allocate (size_t size, size_t alignment, Cancellable? cancellable) throws Error, IOError {
|
||||
public async Allocation allocate (size_t size, size_t alignment, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
uint64 address_pa = cursor;
|
||||
|
||||
size_t vm_size = round_size_up (size, _page_size);
|
||||
@@ -168,7 +168,7 @@ namespace Frida.Barebone {
|
||||
page_allocation = allocation;
|
||||
}
|
||||
|
||||
public async void deallocate (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void deallocate (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
// TODO: Add to freelist.
|
||||
yield page_allocation.deallocate (cancellable);
|
||||
}
|
||||
@@ -208,14 +208,14 @@ namespace Frida.Barebone {
|
||||
// A kernel allocator gives blocks with an alignment of less than a page, and the caller can
|
||||
// change the protection and write whole pages. Thus allocate enough for every page the
|
||||
// result spans, give out full pages, and keep the start of the block for the release.
|
||||
public async Allocation allocate (size_t size, size_t alignment, Cancellable? cancellable) throws Error, IOError {
|
||||
public async Allocation allocate (size_t size, size_t alignment, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
size_t aligned_size = (size + alignment - 1) & ~(alignment - 1);
|
||||
size_t padded_size = aligned_size + alignment - 1;
|
||||
|
||||
uint64 block = yield machine.invoke (alloc_function,
|
||||
resolve_arguments (alloc_arguments, padded_size, 0), cancellable);
|
||||
if (block == 0)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to allocate %zu bytes in the target", padded_size);
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unable to allocate %zu bytes in the target", padded_size);
|
||||
|
||||
uint64 address = (block + alignment - 1) & ~((uint64) alignment - 1);
|
||||
|
||||
@@ -274,7 +274,7 @@ namespace Frida.Barebone {
|
||||
this.free_arguments = free_arguments;
|
||||
}
|
||||
|
||||
public async void deallocate (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void deallocate (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
yield machine.invoke (free_function,
|
||||
resolve_arguments (free_arguments, block_size, block), cancellable);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public sealed class AndroidEmulatorStubClient : GDB.Client {
|
||||
@@ -6,19 +8,19 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public static new async AndroidEmulatorStubClient open (IOStream stream, Cancellable? cancellable = null)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var client = new AndroidEmulatorStubClient (stream);
|
||||
|
||||
try {
|
||||
yield client.init_async (Priority.DEFAULT, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_api_error (e);
|
||||
Efni.throw_api_error (e);
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
protected override async void detect_corellium (Cancellable? cancellable) throws Error, IOError {
|
||||
protected override async void detect_corellium (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public sealed class ArmMachine : Object, Machine {
|
||||
@@ -84,14 +86,14 @@ namespace Frida.Barebone {
|
||||
Object (debugger: debugger);
|
||||
}
|
||||
|
||||
public async size_t query_page_size (Cancellable? cancellable) throws Error, IOError {
|
||||
public async size_t query_page_size (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
return 4096;
|
||||
}
|
||||
|
||||
public async uint query_exception_level (Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint query_exception_level (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
DebuggerException? exception = debugger.exception;
|
||||
if (exception == null)
|
||||
throw new Error.INVALID_OPERATION ("Unable to query in current state");
|
||||
throw new Efni.Error.INVALID_OPERATION ("Unable to query in current state");
|
||||
DebuggerThread thread = exception.thread;
|
||||
|
||||
var cpsr = yield thread.read_register ("cpsr", cancellable);
|
||||
@@ -100,7 +102,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async void enumerate_ranges (Gum.PageProtection prot, FoundRangeFunc func, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
Gee.List<RangeDetails> ranges = yield collect_ranges_using_mmu (cancellable);
|
||||
foreach (RangeDetails r in coalesce_ranges (ranges)) {
|
||||
if ((r.protection & prot) != prot)
|
||||
@@ -110,7 +112,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
}
|
||||
|
||||
private async Gee.List<RangeDetails> collect_ranges_using_mmu (Cancellable? cancellable) throws Error, IOError {
|
||||
private async Gee.List<RangeDetails> collect_ranges_using_mmu (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
var result = new Gee.ArrayList<RangeDetails> ();
|
||||
|
||||
MMUParameters p = yield MMUParameters.load (debugger, cancellable);
|
||||
@@ -132,7 +134,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async void collect_ranges_in_l1 (uint64 table_address, uint first_index, uint end_index, MMUParameters p,
|
||||
Gee.List<RangeDetails> ranges, Cancellable? cancellable) throws Error, IOError {
|
||||
Gee.List<RangeDetails> ranges, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
Buffer entries = yield read_physical_buffer (table_address + (first_index * L1_ENTRY_SIZE),
|
||||
(end_index - first_index) * L1_ENTRY_SIZE, cancellable);
|
||||
|
||||
@@ -158,7 +160,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async void collect_ranges_in_l2 (uint64 table_address, uint64 base_va, MMUParameters p,
|
||||
Gee.List<RangeDetails> ranges, Cancellable? cancellable) throws Error, IOError {
|
||||
Gee.List<RangeDetails> ranges, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
Buffer entries = yield read_physical_buffer (table_address, L2_NUM_ENTRIES * L2_ENTRY_SIZE, cancellable);
|
||||
|
||||
for (uint i = 0; i != L2_NUM_ENTRIES; i++) {
|
||||
@@ -202,7 +204,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public override async void write_virtual (uint64 va, uint8[] data, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
if (kernel_page_table == 0) {
|
||||
yield debugger.write_byte_array (va, new Bytes (data), cancellable);
|
||||
return;
|
||||
@@ -229,7 +231,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async bool write_through_kernel_table (uint64 va, uint8[] data, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
size_t offset = 0;
|
||||
while (offset < data.length) {
|
||||
uint64 cur_va = va + offset;
|
||||
@@ -247,7 +249,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async void locate_kernel_page_table (MMUParameters p, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
if (kernel_page_table == 0 || kernel_page_table_pa != 0)
|
||||
return;
|
||||
|
||||
@@ -257,7 +259,7 @@ namespace Frida.Barebone {
|
||||
kernel_page_table_pa = table;
|
||||
}
|
||||
|
||||
public override async uint64 translate_address (uint64 va, Cancellable? cancellable) throws Error, IOError {
|
||||
public override async uint64 translate_address (uint64 va, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
MMUParameters p = yield MMUParameters.load (debugger, cancellable);
|
||||
|
||||
bool was_running = yield enter_physical_addressing (debugger, cancellable);
|
||||
@@ -274,13 +276,13 @@ namespace Frida.Barebone {
|
||||
throw_if_failed (failure);
|
||||
|
||||
if (pa == null)
|
||||
throw new Error.INVALID_ARGUMENT ("Address 0x%x is not mapped", (uint) va);
|
||||
throw new Efni.Error.INVALID_ARGUMENT ("Address 0x%x is not mapped", (uint) va);
|
||||
|
||||
return pa;
|
||||
}
|
||||
|
||||
private async uint64 table_describing (uint64 va, MMUParameters p, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
if (kernel_page_table_pa != 0) {
|
||||
uint64 slot = kernel_page_table_pa + (((va >> L1_SHIFT) & (L1_NUM_ENTRIES - 1)) * L1_ENTRY_SIZE);
|
||||
uint32 entry = (yield read_physical_buffer (slot, L1_ENTRY_SIZE, cancellable)).read_uint32 (0);
|
||||
@@ -292,7 +294,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async uint64? translate_in_table (uint64 table_pa, uint64 va, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
uint64 l1_slot = table_pa + (((va >> L1_SHIFT) & (L1_NUM_ENTRIES - 1)) * L1_ENTRY_SIZE);
|
||||
uint32 l1 = (yield read_physical_buffer (l1_slot, L1_ENTRY_SIZE, cancellable)).read_uint32 (0);
|
||||
|
||||
@@ -317,7 +319,7 @@ namespace Frida.Barebone {
|
||||
return (l2 & SMALL_PAGE_MASK) | (va & 0xfff);
|
||||
}
|
||||
|
||||
private async Buffer read_physical_buffer (uint64 pa, size_t size, Cancellable? cancellable) throws Error, IOError {
|
||||
private async Buffer read_physical_buffer (uint64 pa, size_t size, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
return yield debugger.read_buffer (pa, size, cancellable);
|
||||
}
|
||||
|
||||
@@ -327,15 +329,15 @@ namespace Frida.Barebone {
|
||||
public uint n;
|
||||
public bool afe;
|
||||
|
||||
public static async MMUParameters load (Debugger debugger, Cancellable? cancellable) throws Error, IOError {
|
||||
public static async MMUParameters load (Debugger debugger, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
DebuggerException? exception = debugger.exception;
|
||||
if (exception == null)
|
||||
throw new Error.INVALID_OPERATION ("Unable to query in current state");
|
||||
throw new Efni.Error.INVALID_OPERATION ("Unable to query in current state");
|
||||
DebuggerThread thread = exception.thread;
|
||||
|
||||
uint64 ttbcr = yield thread.read_register ("ttbcr", cancellable);
|
||||
if ((ttbcr & TTBCR_EAE) != 0)
|
||||
throw new Error.NOT_SUPPORTED ("LPAE translation tables are not supported; please open a PR");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("LPAE translation tables are not supported; please open a PR");
|
||||
|
||||
uint64 sctlr;
|
||||
try {
|
||||
@@ -344,7 +346,7 @@ namespace Frida.Barebone {
|
||||
sctlr = yield thread.read_register ("sctlr_el1", cancellable);
|
||||
}
|
||||
if ((sctlr & SCTLR_M) == 0)
|
||||
throw new Error.NOT_SUPPORTED ("The MMU is off");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("The MMU is off");
|
||||
|
||||
var p = new MMUParameters ();
|
||||
p.n = (uint) (ttbcr & TTBCR_N_MASK);
|
||||
@@ -367,7 +369,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async Allocation allocate_pages (Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
MMUParameters p = yield MMUParameters.load (debugger, cancellable);
|
||||
|
||||
uint num_pages = physical_addresses.size;
|
||||
@@ -387,12 +389,12 @@ namespace Frida.Barebone {
|
||||
throw_if_failed (failure);
|
||||
|
||||
if (allocation == null)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to find a free run of page table entries");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unable to find a free run of page table entries");
|
||||
|
||||
return allocation;
|
||||
}
|
||||
|
||||
private async bool scan_for_run (Run run, MMUParameters p, Cancellable? cancellable) throws Error, IOError {
|
||||
private async bool scan_for_run (Run run, MMUParameters p, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
uint boundary = p.split_index;
|
||||
|
||||
if (boundary != L1_NUM_ENTRIES) {
|
||||
@@ -410,7 +412,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async bool scan_for_run_in_l1 (Run run, uint64 table_address, uint first_index, uint end_index,
|
||||
MMUParameters p, Cancellable? cancellable) throws Error, IOError {
|
||||
MMUParameters p, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
Buffer l1 = yield read_physical_buffer (table_address + (first_index * L1_ENTRY_SIZE),
|
||||
(end_index - first_index) * L1_ENTRY_SIZE, cancellable);
|
||||
|
||||
@@ -441,7 +443,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async Allocation occupy (Run run, Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var displaced = new Gee.ArrayList<DisplacedEntries> ();
|
||||
|
||||
uint next_page = 0;
|
||||
@@ -463,7 +465,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async void protect_pages (uint64 virtual_address, size_t size, Gum.PageProtection prot,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
MMUParameters p = yield MMUParameters.load (debugger, cancellable);
|
||||
|
||||
uint64 start_va = page_start (virtual_address, SMALL_PAGE_SIZE);
|
||||
@@ -517,9 +519,9 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async uint64 skip_section (uint64 page_va, uint64 end_va, uint32 l1_entry, Gum.PageProtection prot,
|
||||
MMUParameters p, Cancellable? cancellable) throws Error, IOError {
|
||||
MMUParameters p, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if ((l1_entry & L1_KIND_MASK) != L1_KIND_SECTION) {
|
||||
throw new Error.NOT_SUPPORTED ("Unable to change protection of the unmapped page at 0x%"
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unable to change protection of the unmapped page at 0x%"
|
||||
+ uint64.FORMAT_MODIFIER + "x", page_va);
|
||||
}
|
||||
|
||||
@@ -527,7 +529,7 @@ namespace Frida.Barebone {
|
||||
| (((l1_entry >> SECTION_APX_SHIFT) & 1) << AP_HIGH_SHIFT);
|
||||
Gum.PageProtection current = protection_from_ap (ap, (l1_entry & SECTION_XN) != 0, p.afe);
|
||||
if ((current & prot) != prot) {
|
||||
throw new Error.NOT_SUPPORTED ("Unable to change protection of the section mapping at 0x%"
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unable to change protection of the section mapping at 0x%"
|
||||
+ uint64.FORMAT_MODIFIER + "x", page_va);
|
||||
}
|
||||
|
||||
@@ -561,7 +563,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async void write_physical_buffer (uint64 pa, Bytes data, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
yield debugger.write_byte_array (pa, data, cancellable);
|
||||
}
|
||||
|
||||
@@ -649,7 +651,7 @@ namespace Frida.Barebone {
|
||||
this.machine = machine;
|
||||
}
|
||||
|
||||
public async void deallocate (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void deallocate (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
bool was_running = yield enter_physical_addressing (machine.debugger, cancellable);
|
||||
GLib.Error? failure = null;
|
||||
try {
|
||||
@@ -664,11 +666,11 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async Gee.List<uint64?> scan_ranges (Gee.List<Gum.MemoryRange?> ranges, MatchPattern pattern, uint max_matches,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Not yet supported");
|
||||
}
|
||||
|
||||
public void apply_relocation (Gum.ElfRelocationDetails r, uint64 base_va, Buffer relocated) throws Error {
|
||||
public void apply_relocation (Gum.ElfRelocationDetails r, uint64 base_va, Buffer relocated) throws Efni.Error {
|
||||
Gum.ElfArmRelocation type = (Gum.ElfArmRelocation) r.type;
|
||||
size_t offset = (size_t) r.address;
|
||||
switch (type) {
|
||||
@@ -682,14 +684,14 @@ namespace Frida.Barebone {
|
||||
relocated.write_int32 (offset, (int32) diff);
|
||||
break;
|
||||
default:
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported relocation type: %s",
|
||||
Marshal.enum_to_nick<Gum.ElfArmRelocation> (type));
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unsupported relocation type: %s",
|
||||
Efni.Marshal.enum_to_nick<Gum.ElfArmRelocation> (type));
|
||||
}
|
||||
}
|
||||
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (args.length > NUM_ARGS_IN_REGS)
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported number of arguments; please open a PR");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unsupported number of arguments; please open a PR");
|
||||
|
||||
bool was_running = debugger.state != STOPPED;
|
||||
if (was_running)
|
||||
@@ -703,6 +705,8 @@ namespace Frida.Barebone {
|
||||
|
||||
uint64 landing_zone = (call_landing_zone != 0) ? call_landing_zone : saved_regs["pc"].get_uint64 ();
|
||||
|
||||
uint64 doubleword_aligned_sp = saved_regs["sp"].get_uint64 () & ~7ULL;
|
||||
regs["sp"] = doubleword_aligned_sp;
|
||||
regs["pc"] = address_from_funcptr (impl);
|
||||
regs["lr"] = landing_zone;
|
||||
regs["cpsr"] = instruction_set_of (impl, saved_regs["cpsr"].get_uint64 ());
|
||||
@@ -718,7 +722,7 @@ namespace Frida.Barebone {
|
||||
int64 deadline = get_monotonic_time () + LANDING_TIMEOUT_USEC;
|
||||
do {
|
||||
if (get_monotonic_time () >= deadline)
|
||||
throw new Error.TIMED_OUT ("Timed out waiting for the call to return");
|
||||
throw new Efni.Error.TIMED_OUT ("Timed out waiting for the call to return");
|
||||
|
||||
ex = yield debugger.continue_until_exception (cancellable);
|
||||
} while (ex.breakpoint != bp);
|
||||
@@ -741,7 +745,7 @@ namespace Frida.Barebone {
|
||||
return cpsr & ~CPSR_THUMB_BIT;
|
||||
}
|
||||
|
||||
public async CallFrame load_call_frame (DebuggerThread thread, uint arity, Cancellable? cancellable) throws Error, IOError {
|
||||
public async CallFrame load_call_frame (DebuggerThread thread, uint arity, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
var regs = yield thread.read_registers (cancellable);
|
||||
|
||||
Buffer? stack = null;
|
||||
@@ -844,7 +848,7 @@ namespace Frida.Barebone {
|
||||
stack_state = MODIFIED;
|
||||
}
|
||||
|
||||
public async void commit (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void commit (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (regs.get_data<bool> ("dirty"))
|
||||
yield thread.write_registers (regs, cancellable);
|
||||
|
||||
@@ -876,7 +880,7 @@ namespace Frida.Barebone {
|
||||
this.debugger = debugger;
|
||||
}
|
||||
|
||||
public async void destroy (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void destroy (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (state == DESTROYED)
|
||||
return;
|
||||
|
||||
@@ -892,16 +896,16 @@ namespace Frida.Barebone {
|
||||
yield debugger.resume (cancellable);
|
||||
}
|
||||
|
||||
public async void enable (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void enable (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (state == ENABLED)
|
||||
return;
|
||||
if (state != DISABLED)
|
||||
throw new Error.INVALID_OPERATION ("Invalid operation");
|
||||
throw new Efni.Error.INVALID_OPERATION ("Invalid operation");
|
||||
yield debugger.write_byte_array (target, new_target_code, cancellable);
|
||||
state = ENABLED;
|
||||
}
|
||||
|
||||
public async void disable (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void disable (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (state != ENABLED)
|
||||
return;
|
||||
yield debugger.write_byte_array (target, old_target_code, cancellable);
|
||||
@@ -918,7 +922,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async InlineHook create_inline_hook (uint64 target, uint64 handler, Allocator allocator,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
uint64 target_address = address_from_funcptr (target);
|
||||
bool target_is_thumb = (target & THUMB_BIT) != 0;
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public sealed class Arm64Machine : Object, Machine {
|
||||
@@ -74,7 +76,7 @@ namespace Frida.Barebone {
|
||||
Object (debugger: debugger);
|
||||
}
|
||||
|
||||
public async size_t query_page_size (Cancellable? cancellable) throws Error, IOError {
|
||||
public async size_t query_page_size (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if ("parallels" in debugger.features)
|
||||
return SMALLEST_GRANULE;
|
||||
|
||||
@@ -86,10 +88,10 @@ namespace Frida.Barebone {
|
||||
return p.granule;
|
||||
}
|
||||
|
||||
public async uint query_exception_level (Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint query_exception_level (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
DebuggerException? exception = debugger.exception;
|
||||
if (exception == null)
|
||||
throw new Error.INVALID_OPERATION ("Unable to query in current state");
|
||||
throw new Efni.Error.INVALID_OPERATION ("Unable to query in current state");
|
||||
DebuggerThread thread = exception.thread;
|
||||
|
||||
var cpsr = yield thread.read_register ("cpsr", cancellable);
|
||||
@@ -98,7 +100,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async void enumerate_ranges (Gum.PageProtection prot, FoundRangeFunc func, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
Gee.List<RangeDetails> ranges = ("corellium" in debugger.features)
|
||||
? yield collect_ranges_using_corellium (cancellable)
|
||||
: yield collect_ranges_using_mmu (cancellable);
|
||||
@@ -110,7 +112,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
}
|
||||
|
||||
private async Gee.List<RangeDetails> collect_ranges_using_mmu (Cancellable? cancellable) throws Error, IOError {
|
||||
private async Gee.List<RangeDetails> collect_ranges_using_mmu (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
var result = new Gee.ArrayList<RangeDetails> ();
|
||||
|
||||
MMUParameters p = yield load_mmu_parameters (cancellable);
|
||||
@@ -129,7 +131,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 {
|
||||
Gee.List<RangeDetails> ranges, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
uint max_entries = compute_max_entries (level, p);
|
||||
Buffer entries = yield read_physical_buffer (table_address, max_entries * Descriptor.SIZE, cancellable);
|
||||
uint shift = address_shift_at_level (level, p.granule);
|
||||
@@ -155,7 +157,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
}
|
||||
|
||||
private async Gee.List<RangeDetails> collect_ranges_using_corellium (Cancellable? cancellable) throws Error, IOError {
|
||||
private async Gee.List<RangeDetails> collect_ranges_using_corellium (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
var result = new Gee.ArrayList<RangeDetails> ();
|
||||
|
||||
string pt = yield debugger.run_remote_command ("pt", cancellable);
|
||||
@@ -168,7 +170,7 @@ namespace Frida.Barebone {
|
||||
|
||||
string[] range_tokens = range.split ("-");
|
||||
if (range_tokens.length != 2)
|
||||
throw new Error.PROTOCOL ("Unexpected Corellium response; please file a bug");
|
||||
throw new Efni.Error.PROTOCOL ("Unexpected Corellium response; please file a bug");
|
||||
|
||||
uint64 base_va = uint64.parse (range_tokens[0], 16);
|
||||
uint64 end_va = uint64.parse (range_tokens[1], 16) + 1;
|
||||
@@ -184,7 +186,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async Allocation allocate_pages (Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
MMUParameters p = yield load_mmu_parameters (cancellable);
|
||||
|
||||
bool was_running = yield begin_physical_addressing (cancellable);
|
||||
@@ -200,14 +202,14 @@ namespace Frida.Barebone {
|
||||
throw_if_failed (failure);
|
||||
|
||||
if (allocation == null)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to insert page table mapping; please file a bug");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unable to insert page table mapping; please file a bug");
|
||||
|
||||
return allocation;
|
||||
}
|
||||
|
||||
private async Allocation? maybe_insert_descriptor_in_table (Gee.List<uint64?> physical_addresses,
|
||||
uint64 table_address, uint level, uint64 upper_bits, MMUParameters p, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
uint max_entries = compute_max_entries (level, p);
|
||||
uint shift = address_shift_at_level (level, p.granule);
|
||||
|
||||
@@ -286,7 +288,7 @@ namespace Frida.Barebone {
|
||||
// A kernel using permission indirection reads an index out of those bits instead, leaving
|
||||
// AP to say nothing at all, so both kinds of page are granted by sampling one the kernel
|
||||
// already made that way and replaying its exact attribute encoding.
|
||||
public async void learn_permission_templates (uint64 code_va, Cancellable? cancellable) throws Error, IOError {
|
||||
public async void learn_permission_templates (uint64 code_va, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
code_template_descriptor = yield read_level3_descriptor (code_va, cancellable);
|
||||
code_template_known = true;
|
||||
|
||||
@@ -295,7 +297,7 @@ namespace Frida.Barebone {
|
||||
data_template_known = true;
|
||||
}
|
||||
|
||||
public override async uint64 translate_address (uint64 va, Cancellable? cancellable) throws Error, IOError {
|
||||
public override async uint64 translate_address (uint64 va, Cancellable? cancellable) throws Efni.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;
|
||||
@@ -303,7 +305,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public override async Bytes read_virtual (uint64 va, size_t size, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
if (physical_memory == null)
|
||||
return yield debugger.read_byte_array (va, size, cancellable);
|
||||
|
||||
@@ -314,7 +316,7 @@ namespace Frida.Barebone {
|
||||
uint64 cur_va = va + offset;
|
||||
uint64 pa = yield translate_address (cur_va, cancellable);
|
||||
if (!physical_memory.contains (pa))
|
||||
throw new Error.NOT_SUPPORTED ("Address 0x%" + uint64.FORMAT_MODIFIER + "x is a device, not memory", cur_va);
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Address 0x%" + uint64.FORMAT_MODIFIER + "x is a device, not memory", cur_va);
|
||||
size_t chunk = size_t.min ((size_t) (p.granule - (cur_va & (p.granule - 1))), size - offset);
|
||||
taken.append (physical_memory.read (pa, chunk));
|
||||
offset += chunk;
|
||||
@@ -323,7 +325,7 @@ namespace Frida.Barebone {
|
||||
return ByteArray.free_to_bytes ((owned) taken);
|
||||
}
|
||||
|
||||
public override async void write_virtual (uint64 va, uint8[] data, Cancellable? cancellable) throws Error, IOError {
|
||||
public override async void write_virtual (uint64 va, uint8[] data, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (physical_memory == null) {
|
||||
yield debugger.write_byte_array (va, new Bytes (data), cancellable);
|
||||
return;
|
||||
@@ -340,13 +342,13 @@ namespace Frida.Barebone {
|
||||
}
|
||||
}
|
||||
|
||||
private async Buffer read_physical_buffer (uint64 pa, size_t size, Cancellable? cancellable) throws Error, IOError {
|
||||
private async Buffer read_physical_buffer (uint64 pa, size_t size, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (physical_memory != null && physical_memory.contains (pa))
|
||||
return debugger.make_buffer (new Bytes (physical_memory.read (pa, size)));
|
||||
return yield debugger.read_buffer (pa, size, cancellable);
|
||||
}
|
||||
|
||||
private async void write_physical_buffer (uint64 pa, Bytes data, Cancellable? cancellable) throws Error, IOError {
|
||||
private async void write_physical_buffer (uint64 pa, Bytes data, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (physical_memory != null && physical_memory.contains (pa)) {
|
||||
physical_memory.write (pa, data.get_data ());
|
||||
return;
|
||||
@@ -354,13 +356,13 @@ namespace Frida.Barebone {
|
||||
yield debugger.write_byte_array (pa, data, cancellable);
|
||||
}
|
||||
|
||||
public async uint8[] read_physical (uint64 pa, size_t size, Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint8[] read_physical (uint64 pa, size_t size, Cancellable? cancellable) throws Efni.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 {
|
||||
public async void write_physical (uint64 pa, uint8[] data, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (physical_memory != null && physical_memory.contains (pa)) {
|
||||
physical_memory.write (pa, data);
|
||||
return;
|
||||
@@ -376,7 +378,7 @@ namespace Frida.Barebone {
|
||||
throw_if_failed (failure);
|
||||
}
|
||||
|
||||
public async uint8[] read_physical_via_stub (uint64 pa, size_t size, Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint8[] read_physical_via_stub (uint64 pa, size_t size, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
bool was_running = yield enter_physical_addressing (debugger, cancellable);
|
||||
uint8[]? data = null;
|
||||
GLib.Error? failure = null;
|
||||
@@ -391,7 +393,7 @@ namespace Frida.Barebone {
|
||||
return data;
|
||||
}
|
||||
|
||||
public async uint64 read_level3_descriptor (uint64 va, Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint64 read_level3_descriptor (uint64 va, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
MMUParameters p = yield load_mmu_parameters (cancellable);
|
||||
bool was_running = yield begin_physical_addressing (cancellable);
|
||||
uint64 descriptor = 0;
|
||||
@@ -416,12 +418,12 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public override async void protect_pages_leaving_the_flush (uint64 virtual_address, size_t size,
|
||||
Gum.PageProtection prot, Cancellable? cancellable) throws Error, IOError {
|
||||
Gum.PageProtection prot, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
yield write_page_protection (virtual_address, size, prot, cancellable);
|
||||
}
|
||||
|
||||
public async void protect_pages (uint64 virtual_address, size_t size, Gum.PageProtection prot, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
if (!mmu_registers_available) {
|
||||
yield set_pages_protection (virtual_address, size, prot, cancellable);
|
||||
return;
|
||||
@@ -436,7 +438,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async void ensure_level3_coverage (uint64 virtual_address, size_t size, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
if (code_allocator == null)
|
||||
return;
|
||||
|
||||
@@ -469,7 +471,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async Gee.List<BlockMapping> locate_blocks (uint64 start_va, uint64 end_va, MMUParameters p,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
var blocks = new Gee.ArrayList<BlockMapping> ();
|
||||
|
||||
bool was_running = yield begin_physical_addressing (cancellable);
|
||||
@@ -496,7 +498,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async BlockMapping? locate_block (uint64 va, MMUParameters p, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
uint64 table_pa = p.tt1;
|
||||
uint level = p.first_level;
|
||||
while (true) {
|
||||
@@ -522,7 +524,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async void split_block (BlockMapping block, uint64 table_pa, MMUParameters p, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
uint sub_level = block.level + 1;
|
||||
uint64 sub_size = (sub_level == 3)
|
||||
? (uint64) 1 << inpage_bits_for_granule (p.granule)
|
||||
@@ -545,7 +547,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async uint64 resolve_physical_address (uint64 va, MMUParameters p, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
uint64 table_pa = p.tt1;
|
||||
uint level = p.first_level;
|
||||
while (true) {
|
||||
@@ -565,7 +567,7 @@ namespace Frida.Barebone {
|
||||
return (raw & INT48_MASK & ~(span - 1)) | (va & (span - 1));
|
||||
}
|
||||
if (desc.kind != TABLE)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to resolve physical address for 0x%" + uint64.FORMAT_MODIFIER + "x", va);
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unable to resolve physical address for 0x%" + uint64.FORMAT_MODIFIER + "x", va);
|
||||
|
||||
table_pa = desc.target_address;
|
||||
level++;
|
||||
@@ -576,9 +578,9 @@ namespace Frida.Barebone {
|
||||
// clearing write before adding execute; the helpers take a page-aligned base and a page
|
||||
// count, and handle the TLB and cache maintenance themselves.
|
||||
private async void set_pages_protection (uint64 virtual_address, size_t size, Gum.PageProtection prot,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (set_memory_x == 0)
|
||||
throw new Error.NOT_SUPPORTED ("Kernel set_memory_* helpers are unavailable");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Kernel set_memory_* helpers are unavailable");
|
||||
|
||||
uint64 aligned = virtual_address & ~(uint64) 0xfff;
|
||||
uint num_pages = (uint) (((virtual_address + size + 0xfff) & ~(uint64) 0xfff) - aligned) / 4096;
|
||||
@@ -594,7 +596,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async void write_page_protection (uint64 virtual_address, size_t size, Gum.PageProtection prot,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
MMUParameters p = yield load_mmu_parameters (cancellable);
|
||||
|
||||
bool was_running = yield begin_physical_addressing (cancellable);
|
||||
@@ -613,7 +615,7 @@ namespace Frida.Barebone {
|
||||
throw_if_failed (failure);
|
||||
}
|
||||
|
||||
private async void flush_translations (Cancellable? cancellable) throws Error, IOError {
|
||||
private async void flush_translations (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (flush_stub == null) {
|
||||
flush_stub = yield code_allocator.allocate (code_allocator.page_size, code_allocator.page_size,
|
||||
cancellable);
|
||||
@@ -631,7 +633,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async void grant_execution (uint64 virtual_address, size_t size, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
yield ensure_level3_coverage (virtual_address, size, cancellable);
|
||||
|
||||
MMUParameters p = yield load_mmu_parameters (cancellable);
|
||||
@@ -654,7 +656,7 @@ namespace Frida.Barebone {
|
||||
|
||||
// The bridge reads and writes guest physical memory directly, so the stub's
|
||||
// physical-addressing mode, which requires a halted target, is unnecessary.
|
||||
private async bool begin_physical_addressing (Cancellable? cancellable) throws Error, IOError {
|
||||
private async bool begin_physical_addressing (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (physical_memory != null)
|
||||
return false;
|
||||
|
||||
@@ -662,21 +664,21 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async void end_physical_addressing (bool was_running, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
if (physical_memory != null)
|
||||
return;
|
||||
|
||||
yield leave_physical_addressing (debugger, was_running, cancellable);
|
||||
}
|
||||
|
||||
private async MMUParameters load_mmu_parameters (Cancellable? cancellable) throws Error, IOError {
|
||||
private async MMUParameters load_mmu_parameters (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (cached_mmu_parameters == null)
|
||||
cached_mmu_parameters = yield MMUParameters.load (debugger, 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 {
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
var table_cache = new TableWalkCache ();
|
||||
uint pages_processed = 0;
|
||||
|
||||
@@ -708,7 +710,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async void protect_pages_in_table (uint64 current_va, uint batch_size, Gum.PageProtection prot,
|
||||
uint64 table_pa, MMUParameters p, Cancellable? cancellable) throws Error, IOError {
|
||||
uint64 table_pa, MMUParameters p, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
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;
|
||||
@@ -734,7 +736,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async uint64 find_level3_table (uint64 va, MMUParameters p, TableWalkCache cache, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
TableWalkResult? cached = cache.lookup (va, p);
|
||||
if (cached != null)
|
||||
return yield walk_to_level3 (va, cached.table_pa, cached.level, p, cache, cancellable);
|
||||
@@ -743,7 +745,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async uint64 walk_to_level3 (uint64 va, uint64 start_table_pa, uint start_level, MMUParameters p,
|
||||
TableWalkCache cache, Cancellable? cancellable) throws Error, IOError {
|
||||
TableWalkCache cache, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
uint64 table_pa = start_table_pa;
|
||||
uint level = start_level;
|
||||
|
||||
@@ -760,7 +762,7 @@ namespace Frida.Barebone {
|
||||
Descriptor desc = Descriptor.parse (raw, level, p.granule);
|
||||
|
||||
if (desc.kind != TABLE)
|
||||
throw new Error.NOT_SUPPORTED ("Walk failed at level %u", level);
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Walk failed at level %u", level);
|
||||
|
||||
table_pa = desc.target_address;
|
||||
level++;
|
||||
@@ -770,7 +772,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async void clear_pxn_table_bits_for_va (uint64 va, MMUParameters p, TableWalkCache cache, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
uint64 table_pa = p.tt1;
|
||||
for (uint level = p.first_level; level != 3; level++) {
|
||||
uint entries = compute_max_entries (level, p);
|
||||
@@ -847,7 +849,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async Gee.List<uint64?> scan_ranges (Gee.List<Gum.MemoryRange?> ranges, MatchPattern pattern, uint max_matches,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
unowned uint8[] scanner_blob = Data.Barebone.get_memory_scanner_arm64_elf_blob ().data;
|
||||
|
||||
var raw_scanner = new Bytes.static (scanner_blob);
|
||||
@@ -856,7 +858,7 @@ namespace Frida.Barebone {
|
||||
try {
|
||||
scanner = new Gum.ElfModule.from_blob (raw_scanner);
|
||||
} catch (Gum.Error e) {
|
||||
throw new Error.NOT_SUPPORTED ("%s", e.message);
|
||||
throw new Efni.Error.NOT_SUPPORTED ("%s", e.message);
|
||||
}
|
||||
|
||||
size_t vm_size = (size_t) scanner.mapped_size;
|
||||
@@ -895,7 +897,7 @@ namespace Frida.Barebone {
|
||||
|
||||
RangeDetails? stack_range = writable_ranges.first_match (r => r.contains_virtual_address (old_sp));
|
||||
if (stack_range == null)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to find stack memory range");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unable to find stack memory range");
|
||||
|
||||
uint64 module_pa;
|
||||
size_t logical_page_size = 4096;
|
||||
@@ -950,7 +952,7 @@ namespace Frida.Barebone {
|
||||
DebuggerBreakpoint bp = yield debugger.add_breakpoint (SOFT, landing_zone_va, 4, cancellable);
|
||||
DebuggerException ex = yield debugger.continue_until_exception (cancellable);
|
||||
if (ex.breakpoint != bp)
|
||||
throw new Error.NOT_SUPPORTED ("Breakpoint did not trigger; please file a bug");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Breakpoint did not trigger; please file a bug");
|
||||
yield bp.remove (cancellable);
|
||||
|
||||
var num_matches = (uint) yield thread.read_register ("x0", cancellable);
|
||||
@@ -972,7 +974,7 @@ namespace Frida.Barebone {
|
||||
return matches;
|
||||
}
|
||||
|
||||
public void apply_relocation (Gum.ElfRelocationDetails r, uint64 base_va, Buffer relocated) throws Error {
|
||||
public void apply_relocation (Gum.ElfRelocationDetails r, uint64 base_va, Buffer relocated) throws Efni.Error {
|
||||
Gum.ElfArm64Relocation type = (Gum.ElfArm64Relocation) r.type;
|
||||
switch (type) {
|
||||
case ABS64:
|
||||
@@ -986,14 +988,14 @@ namespace Frida.Barebone {
|
||||
relocated.write_int32 ((size_t) r.address, (int32) diff);
|
||||
break;
|
||||
default:
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported relocation type: %s",
|
||||
Marshal.enum_to_nick<Gum.ElfArm64Relocation> (type));
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unsupported relocation type: %s",
|
||||
Efni.Marshal.enum_to_nick<Gum.ElfArm64Relocation> (type));
|
||||
}
|
||||
}
|
||||
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (args.length > 8)
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported number of arguments; please open a PR");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unsupported number of arguments; please open a PR");
|
||||
|
||||
bool was_running = debugger.state != STOPPED;
|
||||
if (was_running)
|
||||
@@ -1044,7 +1046,7 @@ namespace Frida.Barebone {
|
||||
return retval;
|
||||
}
|
||||
|
||||
public async void execute (uint64 start, uint64 end, Cancellable? cancellable) throws Error, IOError {
|
||||
public async void execute (uint64 start, uint64 end, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
bool was_running = debugger.state != STOPPED;
|
||||
if (was_running)
|
||||
yield debugger.stop (cancellable);
|
||||
@@ -1071,13 +1073,13 @@ namespace Frida.Barebone {
|
||||
// reschedule onto another core, and the stub identifies threads by core; nothing else reaches
|
||||
// these addresses (a cold landing zone, or the scheduler with a predicate), so whichever
|
||||
// thread stops there is ours.
|
||||
public async DebuggerThread run_until_pc (uint64 address, Cancellable? cancellable) throws Error, IOError {
|
||||
public async DebuggerThread run_until_pc (uint64 address, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
DebuggerBreakpoint bp = yield debugger.add_breakpoint (SOFT, address, 4, cancellable);
|
||||
DebuggerException ex = null;
|
||||
int64 deadline = get_monotonic_time () + LANDING_TIMEOUT_USEC;
|
||||
do {
|
||||
if (get_monotonic_time () >= deadline)
|
||||
throw new Error.TIMED_OUT ("Timed out waiting for the call to return");
|
||||
throw new Efni.Error.TIMED_OUT ("Timed out waiting for the call to return");
|
||||
|
||||
ex = yield debugger.continue_until_exception (cancellable);
|
||||
} while (ex.breakpoint != bp);
|
||||
@@ -1089,7 +1091,7 @@ namespace Frida.Barebone {
|
||||
// points at that core's per-processor data. Handing it the register of the core we began
|
||||
// on would make every later access reach the wrong processor.
|
||||
private async void restore_registers (DebuggerThread origin, DebuggerThread landed,
|
||||
Gee.Map<string, Variant> saved, Cancellable? cancellable) throws Error, IOError {
|
||||
Gee.Map<string, Variant> saved, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
var restored = saved;
|
||||
if (landed.id != origin.id) {
|
||||
restored = new Gee.HashMap<string, Variant> ();
|
||||
@@ -1100,7 +1102,7 @@ namespace Frida.Barebone {
|
||||
yield landed.write_registers (restored, cancellable);
|
||||
}
|
||||
|
||||
public async CallFrame load_call_frame (DebuggerThread thread, uint arity, Cancellable? cancellable) throws Error, IOError {
|
||||
public async CallFrame load_call_frame (DebuggerThread thread, uint arity, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
var regs = yield thread.read_registers (cancellable);
|
||||
|
||||
Buffer? stack = null;
|
||||
@@ -1203,7 +1205,7 @@ namespace Frida.Barebone {
|
||||
stack_state = MODIFIED;
|
||||
}
|
||||
|
||||
public async void commit (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void commit (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (regs.get_data<bool> ("dirty"))
|
||||
yield thread.write_registers (regs, cancellable);
|
||||
|
||||
@@ -1221,7 +1223,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async InlineHook create_inline_hook (uint64 target, uint64 handler, Allocator allocator, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
size_t page_size = allocator.page_size;
|
||||
var allocation = yield allocator.allocate (page_size, page_size, cancellable);
|
||||
uint64 code_va = allocation.virtual_address;
|
||||
@@ -1335,7 +1337,7 @@ namespace Frida.Barebone {
|
||||
this.debugger = debugger;
|
||||
}
|
||||
|
||||
public async void destroy (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void destroy (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (state == DESTROYED)
|
||||
return;
|
||||
|
||||
@@ -1352,16 +1354,16 @@ namespace Frida.Barebone {
|
||||
yield debugger.resume (cancellable);
|
||||
}
|
||||
|
||||
public async void enable (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void enable (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (state == ENABLED)
|
||||
return;
|
||||
if (state != DISABLED)
|
||||
throw new Error.INVALID_OPERATION ("Invalid operation");
|
||||
throw new Efni.Error.INVALID_OPERATION ("Invalid operation");
|
||||
yield debugger.write_byte_array (target, new_target_code, cancellable);
|
||||
state = ENABLED;
|
||||
}
|
||||
|
||||
public async void disable (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void disable (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (state != ENABLED)
|
||||
return;
|
||||
yield debugger.write_byte_array (target, old_target_code, cancellable);
|
||||
@@ -1379,10 +1381,10 @@ namespace Frida.Barebone {
|
||||
public bool sprr_enabled;
|
||||
public uint64 sprr_perm;
|
||||
|
||||
public static async MMUParameters load (Debugger debugger, Cancellable? cancellable) throws Error, IOError {
|
||||
public static async MMUParameters load (Debugger debugger, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
DebuggerException? exception = debugger.exception;
|
||||
if (exception == null)
|
||||
throw new Error.INVALID_OPERATION ("Unable to query in current state");
|
||||
throw new Efni.Error.INVALID_OPERATION ("Unable to query in current state");
|
||||
DebuggerThread thread = exception.thread;
|
||||
|
||||
MMURegisters regs = yield MMURegisters.read (thread, cancellable);
|
||||
@@ -1422,7 +1424,7 @@ namespace Frida.Barebone {
|
||||
public uint64 sprr_config;
|
||||
public uint64 sprr_perm;
|
||||
|
||||
public static async MMURegisters read (DebuggerThread thread, Cancellable? cancellable) throws Error, IOError {
|
||||
public static async MMURegisters read (DebuggerThread thread, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
var regs = new MMURegisters ();
|
||||
|
||||
Debugger client = thread.debugger;
|
||||
@@ -1447,14 +1449,14 @@ namespace Frida.Barebone {
|
||||
// The stub serves no system register, and the monitor command that would
|
||||
// print them aborts the VM whenever another processor is somewhere it
|
||||
// cannot be read from, so leave the caller to manage without them.
|
||||
throw new Error.NOT_SUPPORTED ("Parallels serves no system register");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Parallels serves no system register");
|
||||
} else {
|
||||
regs.tcr = yield thread.read_register ("tcr_el1", cancellable);
|
||||
regs.ttbr1 = yield thread.read_register ("ttbr1_el1", cancellable);
|
||||
try {
|
||||
regs.sprr_config = yield thread.read_register ("sprr_config_el1", cancellable);
|
||||
regs.sprr_perm = yield thread.read_register ("sprr_el1br1_el1", cancellable);
|
||||
} catch (Error e) {
|
||||
} catch (Efni.Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1587,9 +1589,9 @@ namespace Frida.Barebone {
|
||||
this.debugger = debugger;
|
||||
}
|
||||
|
||||
public async void deallocate (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void deallocate (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (old_descriptors == null)
|
||||
throw new Error.INVALID_OPERATION ("Already deallocated");
|
||||
throw new Efni.Error.INVALID_OPERATION ("Already deallocated");
|
||||
Bytes d = old_descriptors;
|
||||
old_descriptors = null;
|
||||
bool was_running = yield enter_physical_addressing (debugger, cancellable);
|
||||
@@ -1610,7 +1612,7 @@ namespace Frida.Barebone {
|
||||
64K = 65536
|
||||
}
|
||||
|
||||
private static Granule granule_from_tg1 (uint tg1) throws Error {
|
||||
private static Granule granule_from_tg1 (uint tg1) throws Efni.Error {
|
||||
switch (tg1) {
|
||||
case 1:
|
||||
return 16K;
|
||||
@@ -1619,7 +1621,7 @@ namespace Frida.Barebone {
|
||||
case 3:
|
||||
return 64K;
|
||||
default:
|
||||
throw new Error.PROTOCOL ("Invalid TG1 value");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid TG1 value");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1733,7 +1735,7 @@ namespace Frida.Barebone {
|
||||
return prot;
|
||||
}
|
||||
|
||||
private 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 Efni.Error {
|
||||
bool want_exec = (prot & Gum.PageProtection.EXECUTE) != 0;
|
||||
if (want_exec && code_template_known)
|
||||
return apply_code_template (descriptor, p);
|
||||
@@ -1786,7 +1788,7 @@ namespace Frida.Barebone {
|
||||
return result;
|
||||
}
|
||||
|
||||
private static uint find_sprr_index_for_permissions (Gum.PageProtection prot, MMUParameters p) throws Error {
|
||||
private static uint find_sprr_index_for_permissions (Gum.PageProtection prot, MMUParameters p) throws Efni.Error {
|
||||
bool want_read = (prot & Gum.PageProtection.READ) != 0;
|
||||
bool want_write = (prot & Gum.PageProtection.WRITE) != 0;
|
||||
bool want_exec = (prot & Gum.PageProtection.EXECUTE) != 0;
|
||||
@@ -1819,7 +1821,7 @@ namespace Frida.Barebone {
|
||||
return i;
|
||||
}
|
||||
|
||||
throw new Error.INVALID_ARGUMENT ("No suitable SPRR slot found for protection 0x%x", prot);
|
||||
throw new Efni.Error.INVALID_ARGUMENT ("No suitable SPRR slot found for protection 0x%x", prot);
|
||||
}
|
||||
|
||||
private static uint64 apply_sprr_index_to_descriptor (uint64 descriptor, uint sprr_index) {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public sealed class UnknownMachine : Object, Machine {
|
||||
@@ -18,43 +20,43 @@ namespace Frida.Barebone {
|
||||
Object (debugger: debugger);
|
||||
}
|
||||
|
||||
public async size_t query_page_size (Cancellable? cancellable) throws Error, IOError {
|
||||
public async size_t query_page_size (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
return 4096;
|
||||
}
|
||||
|
||||
public async uint query_exception_level (Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
public async uint query_exception_level (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Not yet supported");
|
||||
}
|
||||
|
||||
public async void enumerate_ranges (Gum.PageProtection prot, FoundRangeFunc func, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
}
|
||||
|
||||
public async Allocation allocate_pages (Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
throws Efni.Error, IOError {
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Not yet supported");
|
||||
}
|
||||
|
||||
public async void protect_pages (uint64 virtual_address, size_t size, Gum.PageProtection prot,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Not yet supported");
|
||||
}
|
||||
|
||||
public async Gee.List<uint64?> scan_ranges (Gee.List<Gum.MemoryRange?> ranges, MatchPattern pattern,
|
||||
uint max_matches, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
uint max_matches, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Not yet supported");
|
||||
}
|
||||
|
||||
public void apply_relocation (Gum.ElfRelocationDetails r, uint64 base_va, Buffer relocated) throws Error {
|
||||
throw_not_supported ();
|
||||
public void apply_relocation (Gum.ElfRelocationDetails r, uint64 base_va, Buffer relocated) throws Efni.Error {
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Not yet supported");
|
||||
}
|
||||
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Not yet supported");
|
||||
}
|
||||
|
||||
public async CallFrame load_call_frame (DebuggerThread thread, uint arity, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var regs = yield thread.read_registers (cancellable);
|
||||
|
||||
return new UnknownCallFrame (thread, regs);
|
||||
@@ -101,7 +103,7 @@ namespace Frida.Barebone {
|
||||
public void force_return () {
|
||||
}
|
||||
|
||||
public async void commit (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void commit (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (regs.get_data<bool> ("dirty"))
|
||||
yield thread.write_registers (regs, cancellable);
|
||||
}
|
||||
@@ -116,8 +118,8 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async InlineHook create_inline_hook (uint64 target, uint64 handler, Allocator allocator, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
throws Efni.Error, IOError {
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Not yet supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public sealed class IA32Machine : Object, Machine {
|
||||
@@ -31,7 +33,7 @@ namespace Frida.Barebone {
|
||||
page_tables = new X86PageTables (debugger);
|
||||
}
|
||||
|
||||
public async size_t query_page_size (Cancellable? cancellable) throws Error, IOError {
|
||||
public async size_t query_page_size (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
return 4096;
|
||||
}
|
||||
|
||||
@@ -43,10 +45,10 @@ namespace Frida.Barebone {
|
||||
private const uint64 USER_PRIVILEGE = 3;
|
||||
private const int64 LANDING_TIMEOUT_USEC = 60000000;
|
||||
|
||||
public async uint query_exception_level (Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint query_exception_level (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
DebuggerException? exception = debugger.exception;
|
||||
if (exception == null)
|
||||
throw new Error.INVALID_OPERATION ("Unable to query in current state");
|
||||
throw new Efni.Error.INVALID_OPERATION ("Unable to query in current state");
|
||||
DebuggerThread thread = exception.thread;
|
||||
|
||||
var cs = yield thread.read_register ("cs", cancellable);
|
||||
@@ -55,7 +57,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async void enumerate_ranges (Gum.PageProtection prot, FoundRangeFunc func, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
foreach (RangeDetails r in coalesce_ranges (yield page_tables.collect_ranges (cancellable))) {
|
||||
if ((r.protection & prot) != prot)
|
||||
continue;
|
||||
@@ -65,19 +67,19 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async Allocation allocate_pages (Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
return yield page_tables.map (physical_addresses, cancellable);
|
||||
}
|
||||
|
||||
public async void protect_pages (uint64 virtual_address, size_t size, Gum.PageProtection prot,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
yield page_tables.protect (virtual_address, size, prot, cancellable);
|
||||
}
|
||||
|
||||
// Read the memory and do the match here. A script asks about small ranges, thus a scanner in
|
||||
// the guest would cost more round trips than it saves.
|
||||
public async Gee.List<uint64?> scan_ranges (Gee.List<Gum.MemoryRange?> ranges, MatchPattern pattern, uint max_matches,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
var matches = new Gee.ArrayList<uint64?> ();
|
||||
|
||||
foreach (Gum.MemoryRange r in ranges) {
|
||||
@@ -90,7 +92,7 @@ namespace Frida.Barebone {
|
||||
Bytes chunk;
|
||||
try {
|
||||
chunk = yield debugger.read_byte_array (cursor, chunk_size, cancellable);
|
||||
} catch (Error e) {
|
||||
} catch (Efni.Error e) {
|
||||
cursor += chunk_size;
|
||||
continue;
|
||||
}
|
||||
@@ -113,7 +115,7 @@ namespace Frida.Barebone {
|
||||
// keep the overlap at the boundaries small.
|
||||
private const size_t SCAN_CHUNK_SIZE = 256 * 1024;
|
||||
|
||||
public void apply_relocation (Gum.ElfRelocationDetails r, uint64 base_va, Buffer relocated) throws Error {
|
||||
public void apply_relocation (Gum.ElfRelocationDetails r, uint64 base_va, Buffer relocated) throws Efni.Error {
|
||||
Gum.ElfIA32Relocation type = (Gum.ElfIA32Relocation) r.type;
|
||||
switch (type) {
|
||||
case NONE:
|
||||
@@ -132,12 +134,12 @@ namespace Frida.Barebone {
|
||||
// Both ends of the displacement move by the same amount.
|
||||
break;
|
||||
default:
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported relocation type: %s",
|
||||
Marshal.enum_to_nick<Gum.ElfIA32Relocation> (type));
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unsupported relocation type: %s",
|
||||
Efni.Marshal.enum_to_nick<Gum.ElfIA32Relocation> (type));
|
||||
}
|
||||
}
|
||||
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
bool was_running = debugger.state != STOPPED;
|
||||
if (was_running)
|
||||
yield debugger.stop (cancellable);
|
||||
@@ -178,7 +180,7 @@ namespace Frida.Barebone {
|
||||
int64 deadline = get_monotonic_time () + LANDING_TIMEOUT_USEC;
|
||||
while (true) {
|
||||
if (get_monotonic_time () >= deadline)
|
||||
throw new Error.TIMED_OUT ("Timed out waiting for the call to return");
|
||||
throw new Efni.Error.TIMED_OUT ("Timed out waiting for the call to return");
|
||||
|
||||
ex = yield debugger.continue_until_exception (cancellable);
|
||||
if (ex.breakpoint != bp)
|
||||
@@ -200,7 +202,7 @@ namespace Frida.Barebone {
|
||||
return retval;
|
||||
}
|
||||
|
||||
public async CallFrame load_call_frame (DebuggerThread thread, uint arity, Cancellable? cancellable) throws Error, IOError {
|
||||
public async CallFrame load_call_frame (DebuggerThread thread, uint arity, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
var regs = yield thread.read_registers (cancellable);
|
||||
|
||||
uint64 original_esp = regs["esp"].get_uint64 ();
|
||||
@@ -289,7 +291,7 @@ namespace Frida.Barebone {
|
||||
stack_state = MODIFIED;
|
||||
}
|
||||
|
||||
public async void commit (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void commit (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (regs.get_data<bool> ("dirty"))
|
||||
yield thread.write_registers (regs, cancellable);
|
||||
|
||||
@@ -307,11 +309,11 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async InlineHook create_inline_hook (uint64 target, uint64 handler, Allocator allocator, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
return yield X86InlineHook.create (target, handler, IA32, allocator, debugger, cancellable);
|
||||
}
|
||||
|
||||
public override async uint64 translate_address (uint64 va, Cancellable? cancellable) throws Error, IOError {
|
||||
public override async uint64 translate_address (uint64 va, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
return yield page_tables.translate (va, cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public enum CallingConvention {
|
||||
@@ -71,17 +73,17 @@ namespace Frida.Barebone {
|
||||
page_tables = new X86PageTables (debugger);
|
||||
}
|
||||
|
||||
public async size_t query_page_size (Cancellable? cancellable) throws Error, IOError {
|
||||
public async size_t query_page_size (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
return 4096;
|
||||
}
|
||||
|
||||
private const uint64 CODE_SELECTOR_PRIVILEGE = 3;
|
||||
private const uint64 USER_PRIVILEGE = 3;
|
||||
|
||||
public async uint query_exception_level (Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint query_exception_level (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
DebuggerException? exception = debugger.exception;
|
||||
if (exception == null)
|
||||
throw new Error.INVALID_OPERATION ("Unable to query in current state");
|
||||
throw new Efni.Error.INVALID_OPERATION ("Unable to query in current state");
|
||||
DebuggerThread thread = exception.thread;
|
||||
|
||||
var cs = yield thread.read_register ("cs", cancellable);
|
||||
@@ -90,7 +92,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async void enumerate_ranges (Gum.PageProtection prot, FoundRangeFunc func, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
foreach (RangeDetails r in coalesce_ranges (yield page_tables.collect_ranges (cancellable))) {
|
||||
if ((r.protection & prot) != prot)
|
||||
continue;
|
||||
@@ -100,12 +102,12 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async Allocation allocate_pages (Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
return yield page_tables.map (physical_addresses, cancellable);
|
||||
}
|
||||
|
||||
public async void protect_pages (uint64 virtual_address, size_t size, Gum.PageProtection prot,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (set_memory_x != 0) {
|
||||
yield set_pages_protection (virtual_address, size, prot, cancellable);
|
||||
return;
|
||||
@@ -115,7 +117,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async void set_pages_protection (uint64 virtual_address, size_t size, Gum.PageProtection prot,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
uint64 aligned = virtual_address & ~(uint64) 0xfff;
|
||||
uint num_pages = (uint) (((virtual_address + size + 0xfff) & ~(uint64) 0xfff) - aligned) / 4096;
|
||||
|
||||
@@ -130,11 +132,11 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async Gee.List<uint64?> scan_ranges (Gee.List<Gum.MemoryRange?> ranges, MatchPattern pattern, uint max_matches,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
throw_not_supported ();
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Not yet supported");
|
||||
}
|
||||
|
||||
public void apply_relocation (Gum.ElfRelocationDetails r, uint64 base_va, Buffer relocated) throws Error {
|
||||
public void apply_relocation (Gum.ElfRelocationDetails r, uint64 base_va, Buffer relocated) throws Efni.Error {
|
||||
Gum.ElfX64Relocation type = (Gum.ElfX64Relocation) r.type;
|
||||
switch (type) {
|
||||
case NONE:
|
||||
@@ -157,8 +159,8 @@ namespace Frida.Barebone {
|
||||
// Both ends of the displacement move by the same amount.
|
||||
break;
|
||||
default:
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported relocation type: %s",
|
||||
Marshal.enum_to_nick<Gum.ElfX64Relocation> (type));
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unsupported relocation type: %s",
|
||||
Efni.Marshal.enum_to_nick<Gum.ElfX64Relocation> (type));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,9 +169,9 @@ namespace Frida.Barebone {
|
||||
return true;
|
||||
}
|
||||
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, Cancellable? cancellable) throws Error, IOError {
|
||||
public async uint64 invoke (uint64 impl, uint64[] args, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (args.length > arg_reg_names.length)
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported number of arguments; please open a PR");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unsupported number of arguments; please open a PR");
|
||||
|
||||
bool was_running = debugger.state != STOPPED;
|
||||
if (was_running)
|
||||
@@ -206,7 +208,7 @@ namespace Frida.Barebone {
|
||||
int64 deadline = get_monotonic_time () + LANDING_TIMEOUT_USEC;
|
||||
while (true) {
|
||||
if (get_monotonic_time () >= deadline)
|
||||
throw new Error.TIMED_OUT ("Timed out waiting for the call to return");
|
||||
throw new Efni.Error.TIMED_OUT ("Timed out waiting for the call to return");
|
||||
|
||||
ex = yield debugger.continue_until_exception (cancellable);
|
||||
if (ex.breakpoint != bp)
|
||||
@@ -227,7 +229,7 @@ namespace Frida.Barebone {
|
||||
return retval;
|
||||
}
|
||||
|
||||
public async CallFrame load_call_frame (DebuggerThread thread, uint arity, Cancellable? cancellable) throws Error, IOError {
|
||||
public async CallFrame load_call_frame (DebuggerThread thread, uint arity, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
var regs = yield thread.read_registers (cancellable);
|
||||
|
||||
uint64 original_rsp = regs["rsp"].get_uint64 ();
|
||||
@@ -353,7 +355,7 @@ namespace Frida.Barebone {
|
||||
stack_state = MODIFIED;
|
||||
}
|
||||
|
||||
public async void commit (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void commit (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (regs.get_data<bool> ("dirty"))
|
||||
yield thread.write_registers (regs, cancellable);
|
||||
|
||||
@@ -371,11 +373,11 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async InlineHook create_inline_hook (uint64 target, uint64 handler, Allocator allocator, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
return yield X86InlineHook.create (target, handler, AMD64, allocator, debugger, cancellable);
|
||||
}
|
||||
|
||||
public override async uint64 translate_address (uint64 va, Cancellable? cancellable) throws Error, IOError {
|
||||
public override async uint64 translate_address (uint64 va, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
return yield page_tables.translate (va, cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public sealed class BareboneHostSessionBackend : Object, HostSessionBackend {
|
||||
private BareboneHostSessionProvider? provider;
|
||||
@@ -62,128 +64,132 @@ namespace Frida {
|
||||
|
||||
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, owned ConnectingFunc connecting,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
if (host_session != null)
|
||||
throw new Error.INVALID_OPERATION ("Already created");
|
||||
try {
|
||||
if (host_session != null)
|
||||
throw new Error.INVALID_OPERATION ("Already created");
|
||||
|
||||
BareboneConfig? config = null;
|
||||
if (options != null) {
|
||||
Value? val = options.map["config"];
|
||||
if (val != null) {
|
||||
config = (BareboneConfig) val.get_object ();
|
||||
config.check ();
|
||||
BareboneConfig? config = null;
|
||||
if (options != null) {
|
||||
Value? val = options.map["config"];
|
||||
if (val != null) {
|
||||
config = (BareboneConfig) val.get_object ();
|
||||
config.check ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unowned string? config_path = Environment.get_variable ("FRIDA_BAREBONE_CONFIG");
|
||||
if (config == null && config_path != null) {
|
||||
unowned string? config_path = Environment.get_variable ("FRIDA_BAREBONE_CONFIG");
|
||||
if (config == null && config_path != null) {
|
||||
try {
|
||||
var config_data = yield FS.read_all_text (File.new_for_path (config_path), cancellable);
|
||||
var cfg = (BareboneConfig) Json.gobject_from_data (typeof (BareboneConfig), config_data);
|
||||
cfg.check ();
|
||||
config = cfg;
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("Unable to load %s: %s", config_path, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
if (config == null)
|
||||
config = new BareboneConfig ();
|
||||
|
||||
var resident_agent = config.agent as BareboneResidentAgentConfig;
|
||||
if (resident_agent != null) {
|
||||
host_session = yield attach_to_resident_agent (resident_agent.transport, cancellable);
|
||||
host_session.agent_session_detached.connect (on_agent_session_detached);
|
||||
|
||||
return host_session;
|
||||
}
|
||||
|
||||
SocketConnectable connectable;
|
||||
try {
|
||||
var config_data = yield FS.read_all_text (File.new_for_path (config_path), cancellable);
|
||||
var cfg = (BareboneConfig) Json.gobject_from_data (typeof (BareboneConfig), config_data);
|
||||
cfg.check ();
|
||||
config = cfg;
|
||||
BareboneConnectionConfig c = config.connection;
|
||||
connectable = NetworkAddress.parse (c.host, c.port);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("Unable to load %s: %s", config_path, e.message);
|
||||
}
|
||||
}
|
||||
|
||||
if (config == null)
|
||||
config = new BareboneConfig ();
|
||||
connecting ("Connecting to the debugger", 0.0);
|
||||
|
||||
var resident_agent = config.agent as BareboneResidentAgentConfig;
|
||||
if (resident_agent != null) {
|
||||
host_session = yield attach_to_resident_agent (resident_agent.transport, cancellable);
|
||||
host_session.agent_session_detached.connect (on_agent_session_detached);
|
||||
if (config.connection.flavor == BareboneStubFlavor.VIRTUALBOX_CONSOLE) {
|
||||
var console_debugger = yield Barebone.VirtualBoxDebugger.open (config.connection.host,
|
||||
config.connection.port, cancellable);
|
||||
host_session = yield establish (config, console_debugger, (owned) connecting, cancellable);
|
||||
host_session.agent_session_detached.connect (on_agent_session_detached);
|
||||
|
||||
return host_session;
|
||||
}
|
||||
|
||||
SocketConnectable connectable;
|
||||
try {
|
||||
BareboneConnectionConfig c = config.connection;
|
||||
connectable = NetworkAddress.parse (c.host, c.port);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("Unable to load %s: %s", config_path, e.message);
|
||||
}
|
||||
|
||||
connecting ("Connecting to the debugger", 0.0);
|
||||
|
||||
if (config.connection.flavor == BareboneStubFlavor.VIRTUALBOX_CONSOLE) {
|
||||
var console_debugger = yield Barebone.VirtualBoxDebugger.open (config.connection.host,
|
||||
config.connection.port, cancellable);
|
||||
host_session = yield establish (config, console_debugger, (owned) connecting, cancellable);
|
||||
host_session.agent_session_detached.connect (on_agent_session_detached);
|
||||
|
||||
return host_session;
|
||||
}
|
||||
|
||||
IOStream stream;
|
||||
try {
|
||||
var client = new SocketClient ();
|
||||
var connection = yield client.connect_async (connectable, cancellable);
|
||||
|
||||
Tcp.enable_nodelay (connection.socket);
|
||||
|
||||
stream = connection;
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.TRANSPORT ("The specified GDB remote stub cannot be reached: %s", e.message);
|
||||
}
|
||||
|
||||
#if WINDOWS || MACOS || (LINUX && !ANDROID)
|
||||
if (config.connection.flavor == BareboneStubFlavor.ANDROID_EMULATOR) {
|
||||
connecting ("Instrumenting the emulator", 0.05);
|
||||
emulator_instrumentation = yield EmulatorInstrumentation.apply (hub, config, cancellable);
|
||||
}
|
||||
#endif
|
||||
|
||||
Debugger debugger;
|
||||
switch (config.connection.flavor) {
|
||||
case BareboneStubFlavor.VZ:
|
||||
debugger = yield Barebone.VzStubClient.open (stream, cancellable);
|
||||
break;
|
||||
case BareboneStubFlavor.PARALLELS:
|
||||
debugger = yield Barebone.ParallelsStubClient.open (stream, cancellable);
|
||||
break;
|
||||
case BareboneStubFlavor.VIRTUALBOX:
|
||||
debugger = yield Barebone.VirtualBoxStubClient.open (stream, cancellable);
|
||||
break;
|
||||
case BareboneStubFlavor.ANDROID_EMULATOR:
|
||||
debugger = yield Barebone.AndroidEmulatorStubClient.open (stream, cancellable);
|
||||
break;
|
||||
default:
|
||||
debugger = yield GDB.Client.open (stream, cancellable);
|
||||
break;
|
||||
}
|
||||
|
||||
#if WINDOWS
|
||||
if (emulator_instrumentation != null) {
|
||||
var client = debugger as GDB.Client;
|
||||
if (client != null)
|
||||
emulator_instrumentation.adopt_breakpoints (client);
|
||||
}
|
||||
#endif
|
||||
|
||||
try {
|
||||
host_session = yield establish (config, debugger, (owned) connecting, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (debugger.state == STOPPED) {
|
||||
try {
|
||||
yield debugger.resume (cancellable);
|
||||
} catch (GLib.Error ee) {
|
||||
}
|
||||
return host_session;
|
||||
}
|
||||
|
||||
IOStream stream;
|
||||
try {
|
||||
yield debugger.close (cancellable);
|
||||
} catch (IOError ee) {
|
||||
var client = new SocketClient ();
|
||||
var connection = yield client.connect_async (connectable, cancellable);
|
||||
|
||||
Tcp.enable_nodelay (connection.socket);
|
||||
|
||||
stream = connection;
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.TRANSPORT ("The specified GDB remote stub cannot be reached: %s", e.message);
|
||||
}
|
||||
|
||||
#if WINDOWS || MACOS || (LINUX && !ANDROID)
|
||||
if (config.connection.flavor == BareboneStubFlavor.ANDROID_EMULATOR) {
|
||||
connecting ("Instrumenting the emulator", 0.05);
|
||||
emulator_instrumentation = yield EmulatorInstrumentation.apply (hub, config, cancellable);
|
||||
}
|
||||
#endif
|
||||
|
||||
Debugger debugger;
|
||||
switch (config.connection.flavor) {
|
||||
case BareboneStubFlavor.VZ:
|
||||
debugger = yield Barebone.VzStubClient.open (stream, cancellable);
|
||||
break;
|
||||
case BareboneStubFlavor.PARALLELS:
|
||||
debugger = yield Barebone.ParallelsStubClient.open (stream, cancellable);
|
||||
break;
|
||||
case BareboneStubFlavor.VIRTUALBOX:
|
||||
debugger = yield Barebone.VirtualBoxStubClient.open (stream, cancellable);
|
||||
break;
|
||||
case BareboneStubFlavor.ANDROID_EMULATOR:
|
||||
debugger = yield Barebone.AndroidEmulatorStubClient.open (stream, cancellable);
|
||||
break;
|
||||
default:
|
||||
debugger = yield GDB.Client.open (stream, cancellable);
|
||||
break;
|
||||
}
|
||||
|
||||
#if WINDOWS
|
||||
if (emulator_instrumentation != null) {
|
||||
var client = debugger as GDB.Client;
|
||||
if (client != null)
|
||||
emulator_instrumentation.adopt_breakpoints (client);
|
||||
}
|
||||
#endif
|
||||
|
||||
try {
|
||||
host_session = yield establish (config, debugger, (owned) connecting, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (debugger.state == STOPPED) {
|
||||
try {
|
||||
yield debugger.resume (cancellable);
|
||||
} catch (GLib.Error ee) {
|
||||
}
|
||||
}
|
||||
try {
|
||||
yield debugger.close (cancellable);
|
||||
} catch (IOError ee) {
|
||||
}
|
||||
throw_api_error (e);
|
||||
}
|
||||
host_session.agent_session_detached.connect (on_agent_session_detached);
|
||||
|
||||
return host_session;
|
||||
} catch (Efni.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
host_session.agent_session_detached.connect (on_agent_session_detached);
|
||||
|
||||
return host_session;
|
||||
}
|
||||
|
||||
private async BareboneHostSession establish (BareboneConfig config, Debugger debugger, owned ConnectingFunc connecting,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
Cancellable? cancellable) throws Error, Efni.Error, IOError {
|
||||
// The arm64 MMU system registers (TTBR1_EL1/TCR_EL1) are exposed by some stubs and not
|
||||
// others (the Android emulator's does not); detect it from the advertised register set,
|
||||
// which selects host page-table walking versus the kernel's set_memory_* helpers.
|
||||
@@ -226,7 +232,7 @@ namespace Frida {
|
||||
size_t page_size;
|
||||
try {
|
||||
page_size = yield machine.query_page_size (cancellable);
|
||||
} catch (Error e) {
|
||||
} catch (Efni.Error e) {
|
||||
page_size = 0;
|
||||
}
|
||||
|
||||
@@ -341,7 +347,7 @@ namespace Frida {
|
||||
}
|
||||
|
||||
private async BareboneHostSession attach_to_resident_agent (BareboneResidentTransportConfig transport,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
Cancellable? cancellable) throws Error, Efni.Error, IOError {
|
||||
#if WINDOWS
|
||||
throw new Error.NOT_SUPPORTED ("Resident agents are not available on this OS");
|
||||
#else
|
||||
@@ -654,51 +660,55 @@ namespace Frida {
|
||||
|
||||
public async HostApplicationInfo[] enumerate_applications (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
if (connection == null)
|
||||
throw_not_supported ();
|
||||
try {
|
||||
if (connection == null)
|
||||
throw_not_supported ();
|
||||
|
||||
var query = ApplicationQueryOptions._deserialize (options);
|
||||
var query = ApplicationQueryOptions._deserialize (options);
|
||||
|
||||
if (android_emulator_pid != 0) {
|
||||
var helper = yield get_android_helper (cancellable);
|
||||
return yield helper.enumerate_applications (query, cancellable);
|
||||
if (android_emulator_pid != 0) {
|
||||
var helper = yield get_android_helper (cancellable);
|
||||
return yield helper.enumerate_applications (query, cancellable);
|
||||
}
|
||||
|
||||
var selected = new Gee.HashSet<string> ();
|
||||
query.enumerate_selected_identifiers (identifier => {
|
||||
selected.add (identifier);
|
||||
});
|
||||
|
||||
var applications = yield list_applications (selected.to_array (), cancellable);
|
||||
|
||||
var running = new Gee.HashMap<string, uint> ();
|
||||
foreach (HostProcessInfo p in yield connection.enumerate_processes (Scope.METADATA, {},
|
||||
cancellable)) {
|
||||
var path = p.parameters["path"];
|
||||
if (path != null)
|
||||
running[basename_of (path.get_string ()).down ()] = p.pid;
|
||||
}
|
||||
|
||||
var scope = query.scope;
|
||||
var result = new HostApplicationInfo[applications.length];
|
||||
for (int i = 0; i != applications.length; i++) {
|
||||
var app = applications[i];
|
||||
var parameters = make_parameters_dict ();
|
||||
if (scope != MINIMAL)
|
||||
parameters["path"] = app.path;
|
||||
|
||||
uint pid;
|
||||
if (!running.unset (basename_of (app.path).down (), out pid))
|
||||
pid = 0;
|
||||
|
||||
var name = (app.description != "") ? app.description : basename_of (app.path);
|
||||
result[i] = HostApplicationInfo (app.identifier, name, pid, (owned) parameters);
|
||||
}
|
||||
return result;
|
||||
} catch (Efni.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
|
||||
var selected = new Gee.HashSet<string> ();
|
||||
query.enumerate_selected_identifiers (identifier => {
|
||||
selected.add (identifier);
|
||||
});
|
||||
|
||||
var applications = yield list_applications (selected.to_array (), cancellable);
|
||||
|
||||
var running = new Gee.HashMap<string, uint> ();
|
||||
foreach (HostProcessInfo p in yield connection.enumerate_processes (Scope.METADATA, {},
|
||||
cancellable)) {
|
||||
var path = p.parameters["path"];
|
||||
if (path != null)
|
||||
running[basename_of (path.get_string ()).down ()] = p.pid;
|
||||
}
|
||||
|
||||
var scope = query.scope;
|
||||
var result = new HostApplicationInfo[applications.length];
|
||||
for (int i = 0; i != applications.length; i++) {
|
||||
var app = applications[i];
|
||||
var parameters = make_parameters_dict ();
|
||||
if (scope != MINIMAL)
|
||||
parameters["path"] = app.path;
|
||||
|
||||
uint pid;
|
||||
if (!running.unset (basename_of (app.path).down (), out pid))
|
||||
pid = 0;
|
||||
|
||||
var name = (app.description != "") ? app.description : basename_of (app.path);
|
||||
result[i] = HostApplicationInfo (app.identifier, name, pid, (owned) parameters);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Barebone.AgentConnection.Application[] list_applications (
|
||||
string[] identifiers, Cancellable? cancellable) throws Error, IOError {
|
||||
string[] identifiers, Cancellable? cancellable) throws Error, Efni.Error, IOError {
|
||||
var listed = new Gee.ArrayList<Barebone.AgentConnection.Application> ();
|
||||
listed.add_all_array (yield connection.enumerate_applications (identifiers, cancellable));
|
||||
|
||||
@@ -738,19 +748,23 @@ namespace Frida {
|
||||
|
||||
public async HostProcessInfo[] enumerate_processes (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
if (connection == null)
|
||||
throw_not_supported ();
|
||||
var query = ProcessQueryOptions._deserialize (options);
|
||||
uint[] pids = {};
|
||||
query.enumerate_selected_pids (pid => {
|
||||
pids += pid;
|
||||
});
|
||||
var processes = yield connection.enumerate_processes (query.scope, pids, cancellable);
|
||||
try {
|
||||
if (connection == null)
|
||||
throw_not_supported ();
|
||||
var query = ProcessQueryOptions._deserialize (options);
|
||||
uint[] pids = {};
|
||||
query.enumerate_selected_pids (pid => {
|
||||
pids += pid;
|
||||
});
|
||||
var processes = yield connection.enumerate_processes (query.scope, pids, cancellable);
|
||||
|
||||
if (query.scope == FULL && android_emulator_pid != 0)
|
||||
yield attach_app_icons (processes, cancellable);
|
||||
if (query.scope == FULL && android_emulator_pid != 0)
|
||||
yield attach_app_icons (processes, cancellable);
|
||||
|
||||
return processes;
|
||||
return processes;
|
||||
} catch (Efni.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
}
|
||||
|
||||
private async void attach_app_icons (HostProcessInfo[] processes, Cancellable? cancellable) throws IOError {
|
||||
@@ -759,6 +773,8 @@ namespace Frida {
|
||||
icons = yield resolve_app_icons (cancellable);
|
||||
} catch (Error e) {
|
||||
return;
|
||||
} catch (Efni.Error e) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i != processes.length; i++) {
|
||||
@@ -768,7 +784,7 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
private async Gee.HashMap<string, Variant> resolve_app_icons (Cancellable? cancellable) throws Error, IOError {
|
||||
private async Gee.HashMap<string, Variant> resolve_app_icons (Cancellable? cancellable) throws Error, Efni.Error, IOError {
|
||||
if (android_app_icons == null) {
|
||||
var helper = yield get_android_helper (cancellable);
|
||||
|
||||
@@ -788,7 +804,7 @@ namespace Frida {
|
||||
return android_app_icons;
|
||||
}
|
||||
|
||||
private async AndroidHelperClient get_android_helper (Cancellable? cancellable) throws Error, IOError {
|
||||
private async AndroidHelperClient get_android_helper (Cancellable? cancellable) throws Error, Efni.Error, IOError {
|
||||
while (android_helper_request != null) {
|
||||
try {
|
||||
return yield android_helper_request.future.wait_async (cancellable);
|
||||
@@ -854,15 +870,23 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
yield start_gating (cancellable);
|
||||
try {
|
||||
yield start_gating (cancellable);
|
||||
} catch (Efni.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
}
|
||||
|
||||
public async void enable_spawn_gating_with_options (HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
yield start_gating (cancellable);
|
||||
try {
|
||||
yield start_gating (cancellable);
|
||||
} catch (Efni.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
}
|
||||
|
||||
private async void start_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
private async void start_gating (Cancellable? cancellable) throws Error, Efni.Error, IOError {
|
||||
if (connection == null)
|
||||
throw_not_supported ();
|
||||
|
||||
@@ -872,12 +896,16 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
|
||||
if (connection == null)
|
||||
throw_not_supported ();
|
||||
try {
|
||||
if (connection == null)
|
||||
throw_not_supported ();
|
||||
|
||||
yield connection.gate_spawns (false, cancellable);
|
||||
watchdog.clear ();
|
||||
pending_spawn.clear ();
|
||||
yield connection.gate_spawns (false, cancellable);
|
||||
watchdog.clear ();
|
||||
pending_spawn.clear ();
|
||||
} catch (Efni.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
}
|
||||
|
||||
public async HostSpawnInfo[] enumerate_pending_spawn (Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -894,23 +922,27 @@ namespace Frida {
|
||||
|
||||
public async uint spawn (string program, HostSpawnOptions options, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
if (connection == null)
|
||||
throw_not_supported ();
|
||||
try {
|
||||
if (connection == null)
|
||||
throw_not_supported ();
|
||||
|
||||
if (android_emulator_pid != 0 && program[0] != '/')
|
||||
return yield spawn_android_app (program, options, cancellable);
|
||||
if (android_emulator_pid != 0 && program[0] != '/')
|
||||
return yield spawn_android_app (program, options, cancellable);
|
||||
|
||||
if (connection.spawns_by_itself)
|
||||
return yield connection.spawn_program (words_of (program, options), cancellable);
|
||||
if (connection.spawns_by_itself)
|
||||
return yield connection.spawn_program (words_of (program, options), cancellable);
|
||||
|
||||
uint helper = yield acquire_spawn_helper (cancellable);
|
||||
uint helper = yield acquire_spawn_helper (cancellable);
|
||||
|
||||
return yield connection.spawn_process (helper,
|
||||
command_line_of (yield file_of (program, cancellable), options), cancellable);
|
||||
return yield connection.spawn_process (helper,
|
||||
command_line_of (yield file_of (program, cancellable), options), cancellable);
|
||||
} catch (Efni.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
}
|
||||
|
||||
private async uint spawn_android_app (string package, HostSpawnOptions options, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Error, Efni.Error, IOError {
|
||||
var entrypoint = PackageEntrypoint.parse (package, options);
|
||||
|
||||
var helper = yield get_android_helper (cancellable);
|
||||
@@ -963,28 +995,32 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public async void resume (uint pid, Cancellable? cancellable) throws Error, IOError {
|
||||
if (connection.spawns_by_itself) {
|
||||
pending_spawn.unset (pid);
|
||||
watchdog.cancel (pid);
|
||||
try {
|
||||
if (connection.spawns_by_itself) {
|
||||
pending_spawn.unset (pid);
|
||||
watchdog.cancel (pid);
|
||||
|
||||
yield connection.resume_process (0, pid, cancellable);
|
||||
yield connection.resume_process (0, pid, cancellable);
|
||||
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
uint holder = spawn_helper_pid;
|
||||
|
||||
HeldSpawn? held = pending_spawn[pid];
|
||||
if (held != null) {
|
||||
holder = held.holder_pid;
|
||||
pending_spawn.unset (pid);
|
||||
watchdog.cancel (pid);
|
||||
}
|
||||
|
||||
if (holder == 0)
|
||||
throw new Error.INVALID_ARGUMENT ("Process %u was not spawned by us", pid);
|
||||
|
||||
yield connection.resume_process (holder, pid, cancellable);
|
||||
} catch (Efni.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
|
||||
uint holder = spawn_helper_pid;
|
||||
|
||||
HeldSpawn? held = pending_spawn[pid];
|
||||
if (held != null) {
|
||||
holder = held.holder_pid;
|
||||
pending_spawn.unset (pid);
|
||||
watchdog.cancel (pid);
|
||||
}
|
||||
|
||||
if (holder == 0)
|
||||
throw new Error.INVALID_ARGUMENT ("Process %u was not spawned by us", pid);
|
||||
|
||||
yield connection.resume_process (holder, pid, cancellable);
|
||||
}
|
||||
|
||||
public async void kill (uint pid, Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -993,34 +1029,38 @@ namespace Frida {
|
||||
|
||||
public async AgentSessionId attach (uint pid, HashTable<string, Variant> options, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
if (pid != 0) {
|
||||
if (connection == null)
|
||||
throw_not_supported ();
|
||||
try {
|
||||
if (pid != 0) {
|
||||
if (connection == null)
|
||||
throw_not_supported ();
|
||||
|
||||
yield acquire_injected_agent (pid, cancellable);
|
||||
yield acquire_injected_agent (pid, cancellable);
|
||||
}
|
||||
|
||||
var opts = SessionOptions._deserialize (options);
|
||||
if (opts.realm == EMULATED)
|
||||
throw new Error.NOT_SUPPORTED ("Emulated realm is not supported on Barebone targets");
|
||||
|
||||
var session_id = AgentSessionId.generate ();
|
||||
|
||||
MainContext dbus_context = yield get_dbus_context ();
|
||||
|
||||
BareboneAgentSession session;
|
||||
if (connection != null) {
|
||||
session = new RemoteBareboneAgentSession (connection, pid, session_id, opts.persist_timeout,
|
||||
dbus_context);
|
||||
} else {
|
||||
if (services == null)
|
||||
throw new Error.NOT_SUPPORTED ("Barebone target has neither an agent nor a debugger stub");
|
||||
session = new LocalBareboneAgentSession (services, session_id, opts.persist_timeout, dbus_context);
|
||||
}
|
||||
agent_sessions[session_id] = session;
|
||||
session.closed.connect (on_agent_session_closed);
|
||||
|
||||
return session_id;
|
||||
} catch (Efni.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
|
||||
var opts = SessionOptions._deserialize (options);
|
||||
if (opts.realm == EMULATED)
|
||||
throw new Error.NOT_SUPPORTED ("Emulated realm is not supported on Barebone targets");
|
||||
|
||||
var session_id = AgentSessionId.generate ();
|
||||
|
||||
MainContext dbus_context = yield get_dbus_context ();
|
||||
|
||||
BareboneAgentSession session;
|
||||
if (connection != null) {
|
||||
session = new RemoteBareboneAgentSession (connection, pid, session_id, opts.persist_timeout,
|
||||
dbus_context);
|
||||
} else {
|
||||
if (services == null)
|
||||
throw new Error.NOT_SUPPORTED ("Barebone target has neither an agent nor a debugger stub");
|
||||
session = new LocalBareboneAgentSession (services, session_id, opts.persist_timeout, dbus_context);
|
||||
}
|
||||
agent_sessions[session_id] = session;
|
||||
session.closed.connect (on_agent_session_closed);
|
||||
|
||||
return session_id;
|
||||
}
|
||||
|
||||
public async void reattach (AgentSessionId id, Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -1126,7 +1166,7 @@ namespace Frida {
|
||||
return (end != -1) ? line[:end] : line;
|
||||
}
|
||||
|
||||
private async uint acquire_spawn_helper (Cancellable? cancellable) throws Error, IOError {
|
||||
private async uint acquire_spawn_helper (Cancellable? cancellable) throws Error, Efni.Error, IOError {
|
||||
if (spawn_helper_pid != 0)
|
||||
return spawn_helper_pid;
|
||||
|
||||
@@ -1148,7 +1188,7 @@ namespace Frida {
|
||||
return pid;
|
||||
}
|
||||
|
||||
private async string file_of (string program, Cancellable? cancellable) throws Error, IOError {
|
||||
private async string file_of (string program, Cancellable? cancellable) throws Error, Efni.Error, IOError {
|
||||
if (connection.spawns_by_itself || program.contains ("\\") || program.contains (":"))
|
||||
return program;
|
||||
|
||||
@@ -1190,7 +1230,7 @@ namespace Frida {
|
||||
return "\"" + token + "\"";
|
||||
}
|
||||
|
||||
private async void acquire_injected_agent (uint pid, Cancellable? cancellable) throws Error, IOError {
|
||||
private async void acquire_injected_agent (uint pid, Cancellable? cancellable) throws Error, Efni.Error, IOError {
|
||||
uint users = injected_agents[pid];
|
||||
if (users != 0) {
|
||||
injected_agents[pid] = users + 1;
|
||||
@@ -1247,19 +1287,23 @@ namespace Frida {
|
||||
|
||||
public override async AgentScriptId create_script (string source, HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
try {
|
||||
check_open ();
|
||||
|
||||
var opts = ScriptOptions._deserialize (options);
|
||||
if (opts.runtime == V8)
|
||||
throw new Error.INVALID_ARGUMENT ("The V8 runtime is not supported by the Barebone backend");
|
||||
var opts = ScriptOptions._deserialize (options);
|
||||
if (opts.runtime == V8)
|
||||
throw new Error.INVALID_ARGUMENT ("The V8 runtime is not supported by the Barebone backend");
|
||||
|
||||
var id = AgentScriptId (next_script_id++);
|
||||
var id = AgentScriptId (next_script_id++);
|
||||
|
||||
var script = BareboneScript.create (id, source, services);
|
||||
scripts[id] = script;
|
||||
script.message.connect (on_message_from_script);
|
||||
var script = BareboneScript.create (id, source, services);
|
||||
scripts[id] = script;
|
||||
script.message.connect (on_message_from_script);
|
||||
|
||||
return id;
|
||||
return id;
|
||||
} catch (Efni.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
}
|
||||
|
||||
public override async void destroy_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
@@ -1347,47 +1391,63 @@ namespace Frida {
|
||||
|
||||
public override async AgentScriptId create_script (string source, HashTable<string, Variant> options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
try {
|
||||
check_open ();
|
||||
|
||||
var opts = ScriptOptions._deserialize (options);
|
||||
if (opts.runtime == V8)
|
||||
throw new Error.INVALID_ARGUMENT ("The V8 runtime is not supported by the Barebone backend");
|
||||
var opts = ScriptOptions._deserialize (options);
|
||||
if (opts.runtime == V8)
|
||||
throw new Error.INVALID_ARGUMENT ("The V8 runtime is not supported by the Barebone backend");
|
||||
|
||||
var script_id = yield connection.create_script (source, pid, cancellable);
|
||||
own_scripts.add (script_id);
|
||||
var script_id = yield connection.create_script (source, pid, cancellable);
|
||||
own_scripts.add (script_id);
|
||||
|
||||
return script_id;
|
||||
return script_id;
|
||||
} catch (Efni.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
}
|
||||
|
||||
public override async void destroy_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
try {
|
||||
check_open ();
|
||||
|
||||
yield connection.destroy_script (script_id, pid, cancellable);
|
||||
own_scripts.remove (script_id);
|
||||
yield connection.destroy_script (script_id, pid, cancellable);
|
||||
own_scripts.remove (script_id);
|
||||
} catch (Efni.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
}
|
||||
|
||||
public override async void load_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
|
||||
check_open ();
|
||||
try {
|
||||
check_open ();
|
||||
|
||||
yield connection.load_script (script_id, pid, cancellable);
|
||||
yield connection.load_script (script_id, pid, cancellable);
|
||||
} catch (Efni.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
}
|
||||
|
||||
public override async void post_messages (AgentMessage[] messages, uint batch_id, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
transmitter.check_okay_to_receive ();
|
||||
try {
|
||||
transmitter.check_okay_to_receive ();
|
||||
|
||||
foreach (var m in messages) {
|
||||
switch (m.kind) {
|
||||
case SCRIPT:
|
||||
yield connection.post_script_message (m.script_id, m.text,
|
||||
m.has_data ? new Bytes (m.data) : null, pid, cancellable);
|
||||
break;
|
||||
case DEBUGGER:
|
||||
break;
|
||||
foreach (var m in messages) {
|
||||
switch (m.kind) {
|
||||
case SCRIPT:
|
||||
yield connection.post_script_message (m.script_id, m.text,
|
||||
m.has_data ? new Bytes (m.data) : null, pid, cancellable);
|
||||
break;
|
||||
case DEBUGGER:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transmitter.notify_rx_batch_id (batch_id);
|
||||
transmitter.notify_rx_batch_id (batch_id);
|
||||
} catch (Efni.Error e) {
|
||||
throw_api_error (e);
|
||||
}
|
||||
}
|
||||
|
||||
// Each copy numbers its scripts from one, thus take only what comes from our own process.
|
||||
|
||||
@@ -23,11 +23,11 @@ namespace Frida.Barebone {
|
||||
private Callback console_log_callback;
|
||||
|
||||
public async CModule.from_blob (Bytes blob, Machine machine, Allocator allocator, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
try {
|
||||
elf = new Gum.ElfModule.from_blob (blob);
|
||||
} catch (Gum.Error e) {
|
||||
throw new Error.NOT_SUPPORTED ("%s", e.message);
|
||||
throw new Efni.Error.NOT_SUPPORTED ("%s", e.message);
|
||||
}
|
||||
|
||||
var raw_elf = new Bytes.static (elf.get_file_data ());
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public sealed class Callback : Object {
|
||||
@@ -10,7 +12,7 @@ namespace Frida.Barebone {
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
public async Callback (uint64 code, CallbackHandler handler, Machine machine, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
this.code = code;
|
||||
this.handler = handler;
|
||||
this.machine = machine;
|
||||
@@ -26,7 +28,7 @@ namespace Frida.Barebone {
|
||||
debugger.notify["state"].disconnect (on_gdb_state_changed);
|
||||
}
|
||||
|
||||
public async void destroy (Cancellable? cancellable) throws Error, IOError {
|
||||
public async void destroy (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
yield breakpoint.remove (cancellable);
|
||||
}
|
||||
|
||||
@@ -44,7 +46,7 @@ namespace Frida.Barebone {
|
||||
handle_invocation.begin (exception.thread);
|
||||
}
|
||||
|
||||
private async void handle_invocation (DebuggerThread thread) throws Error, IOError {
|
||||
private async void handle_invocation (DebuggerThread thread) throws Efni.Error, IOError {
|
||||
uint arity = handler.arity;
|
||||
|
||||
var frame = yield machine.load_call_frame (thread, arity, io_cancellable);
|
||||
@@ -69,6 +71,6 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public abstract async uint64 handle_invocation (uint64[] args, CallFrame frame, Cancellable? cancellable)
|
||||
throws Error, IOError;
|
||||
throws Efni.Error, IOError;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
private class ConsoleLogHandler : Object, CallbackHandler {
|
||||
@@ -14,7 +16,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async uint64 handle_invocation (uint64[] args, CallFrame frame, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var message = args[0];
|
||||
var len = (long) args[1];
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public async Allocation inject_elf (Gum.ElfModule elf, Bytes raw_elf, size_t page_size, Machine machine, Allocator allocator,
|
||||
owned UploadProgressFunc on_upload_progress, Cancellable? cancellable) throws Error, IOError {
|
||||
owned UploadProgressFunc on_upload_progress, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
size_t vm_size = (size_t) elf.mapped_size;
|
||||
|
||||
uint num_pages = (uint) (vm_size / page_size);
|
||||
@@ -21,7 +21,7 @@ namespace Frida.Barebone {
|
||||
return true;
|
||||
});
|
||||
if (text_size == 0)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to detect text segment");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unable to detect text segment");
|
||||
|
||||
var allocation = yield allocator.allocate (num_pages * page_size, page_size, cancellable);
|
||||
try {
|
||||
@@ -39,14 +39,14 @@ namespace Frida.Barebone {
|
||||
(num_pages * page_size) - (size_t) data_base, READ | WRITE, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
yield allocation.deallocate (cancellable);
|
||||
throw_api_error (e);
|
||||
Efni.throw_api_error (e);
|
||||
}
|
||||
|
||||
return allocation;
|
||||
}
|
||||
|
||||
private async void upload (Machine machine, uint64 va, Bytes image, owned UploadProgressFunc on_progress,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
unowned uint8[] data = image.get_data ();
|
||||
size_t slice_size = size_t.max (data.length / UPLOAD_PROGRESS_STEPS, MIN_UPLOAD_SLICE);
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace Frida.Barebone {
|
||||
private const size_t MIN_UPLOAD_SLICE = 256 * 1024;
|
||||
|
||||
private async void clear_tail_beyond_file (Machine machine, uint64 base_va, size_t written,
|
||||
size_t mapped, Cancellable? cancellable) throws Error, IOError {
|
||||
size_t mapped, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (written >= mapped)
|
||||
return;
|
||||
|
||||
|
||||
Loaded 100 of 260 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user