Compare commits

..
Author SHA1 Message Date
Ole André Vadla Ravnås a6b853d490 Add PHARO as a script runtime
ScriptInstance becomes the abstract peer of JSScriptInstance and
PharoScriptInstance, so Pharo scripts ride the same engine without
going through the GumJS interfaces.
2026-08-14 20:01:56 +02:00
Ole André Vadla Ravnås c3ba7814d3 Link the Pharo bindings when Gum has them
Gum answers frida-gumpharo-1.0 only where the bindings were built,
so ask for it and go without when it is absent.
2026-08-14 18:54:27 +02:00
416 changed files with 35669 additions and 91588 deletions

No files matched your search

-19
View File
@@ -1,19 +0,0 @@
name: Set up LLVM
description: Install the LLVM toolchain and put its tools on the path
inputs:
version:
required: false
default: "21"
description: The LLVM major version to install
runs:
using: composite
steps:
- name: Install LLVM
shell: bash
run: |
curl -sSL https://apt.llvm.org/llvm.sh -o llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh $VERSION
echo "/usr/lib/llvm-$VERSION/bin" >> $GITHUB_PATH
env:
VERSION: ${{ inputs.version }}
-245
View File
@@ -1,245 +0,0 @@
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
+9 -24
View File
@@ -2,13 +2,9 @@ name: CI
on:
push:
paths-ignore:
- 'src/barebone/agent/**'
pull_request:
branches:
- main
paths-ignore:
- 'src/barebone/agent/**'
workflow_dispatch:
inputs:
tag:
@@ -24,8 +20,8 @@ jobs:
strategy:
matrix:
include:
- { id: windows-arm64, runner: '"windows-11-arm"', sys: CLANGARM64, pkg: 'mingw-w64-clang-aarch64-clang', opts: "'-Dfrida-gum:v8=disabled'" }
- { id: windows-x86_64, runner: '"windows-latest"', sys: UCRT64, pkg: 'mingw-w64-ucrt-x86_64-gcc' }
- { id: windows-arm64, runner: '"windows-11-arm"', sys: CLANGARM64, pkg: 'mingw-w64-clang-aarch64-clang' }
- { id: windows-x86_64, runner: '"windows-latest"', sys: MINGW64, pkg: 'mingw-w64-x86_64-gcc' }
- { id: windows-x86, runner: '"windows-latest"', sys: MINGW32, pkg: 'mingw-w64-i686-gcc' }
- { id: macos-x86_64, runner: '"macos-latest"' }
- { id: macos-arm64, runner: '"macos-latest"' }
@@ -75,7 +71,7 @@ jobs:
--host=${{ matrix.id }} `
--enable-compiler-backend `
${{ env.FRIDA_CORE_OPTIONS }} `
-- --force-fallback-for=openssl ${{ matrix.opts }}
-- --force-fallback-for=openssl
.\make
env:
MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }}
@@ -455,10 +451,7 @@ 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 \
"cp /opt/frida/frida-tests /opt/frida/frida-agent.so /tmp/ && \
ln -sP /opt/frida/labrats /tmp/labrats && \
/tmp/frida-tests"
/opt/sabrelite/run.sh /tmp/runner.tar /opt/frida/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
@@ -665,7 +658,7 @@ jobs:
matrix:
include:
- { arch: x86_64, runner: windows-latest, msvc-arch: amd64, vcpkg-triplet: x64-windows-release }
- { arch: arm64, runner: windows-11-arm, msvc-arch: arm64, vcpkg-triplet: arm64-windows-release, opts: "'-Dfrida-gum:v8=disabled'" }
- { arch: arm64, runner: windows-11-arm, msvc-arch: arm64, vcpkg-triplet: arm64-windows-release }
fail-fast: false
runs-on: ${{ matrix.runner }}
timeout-minutes: 120
@@ -675,17 +668,9 @@ jobs:
with:
submodules: recursive
- name: Set up MSVC environment
run: |
$vs = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -products * -latest -property installationPath
$before = @{}
Get-ChildItem env: | ForEach-Object { $before[$_.Name] = $_.Value }
cmd /c "`"$vs\VC\Auxiliary\Build\vcvarsall.bat`" ${{ matrix.msvc-arch }} >nul && set" | ForEach-Object {
$name, $value = $_ -split '=', 2
if ($before[$name] -ne $value) {
"$name=$value" >> $env:GITHUB_ENV
}
}
- uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{ matrix.msvc-arch }}
- uses: actions/setup-python@v6
with:
@@ -736,7 +721,7 @@ jobs:
--enable-shared `
--without-prebuilds=sdk `
"--prefix=$env:RUNNER_TEMP\frida-prefix" `
-- --force-fallback-for=openssl,libnghttp2 -Dcompiler_backend=disabled -Dtests=disabled ${{ matrix.opts }}
-- --force-fallback-for=openssl,libnghttp2 -Dcompiler_backend=disabled -Dtests=disabled
- name: Compile
run: python releng/meson/meson.py compile -C build
+4 -8
View File
@@ -2,15 +2,11 @@
/build/
/deps/
/src/android-helper/build/
/src/barebone/agent/linux/*.ko
/src/barebone/agent/linux/*.mod
/src/barebone/agent/linux/*.mod.c
/src/barebone/agent/target/
/src/barebone/agent/target-linux/
/src/barebone/agent/target-win9x/
/src/barebone/agent/linux/*.o
/src/barebone/agent/linux/frida-agent.elf
/src/barebone/agent/linux/.*.cmd
/src/barebone/agent/linux/Module.symvers
/src/barebone/agent/linux/modules.order
/src/barebone/agent/target*/
/src/barebone/agent/linux/*.o.bin
/src/barebone/agent/xpacks/
/src/barebone/helpers/memory-scanner/target/
/src/barebone/helpers/target/
+1 -1
View File
@@ -43,7 +43,7 @@ platform-dependent (e.g. `linux-x86_64`).
## Build Requirements
- **Vala compiler**: Must be the Frida-optimized fork from
https://github.com/frida-forks/vala (checks for `-frida` suffix
https://github.com/frida/vala (checks for `-frida` suffix
in version)
- **C/C++ standards**: gnu99/c99 for C, c++17 for C++
- **Go >= 1.24**: Required for compiler backend
+2 -2
View File
@@ -1,4 +1,4 @@
PYTHON ?= $(shell command -v python3 >/dev/null && echo python3 || echo python)
PYTHON ?= $(shell which 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 ] || git submodule status 2>/dev/null | grep -q '^-'; then \
@if [ ! -f releng/meson/meson.py ]; then \
git submodule update --init --recursive --depth 1; \
fi
-include git-submodules
+38 -23
View File
@@ -19,7 +19,7 @@ import traceback
from typing import Any, Literal, Mapping, Optional, Sequence, Tuple
REPO_ROOT = Path(__file__).absolute().parent.parent
REPO_ROOT = Path(__file__).resolve().parent.parent
NINJA = os.environ.get("NINJA", "ninja")
@@ -510,7 +510,9 @@ 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()))
options += [f"-D:frida_version={state.frida_version}"]
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}"]
host_machine = MachineSpec(state.host_os, group.arch, state.host_config, group.triplet)
@@ -523,7 +525,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 = list(options)
forwarded_options = [o for o in options if not is_overridden_compat_option(o)]
if state.apple_tls == "enabled":
forwarded_options += ["-Dapple_tls=disabled"]
@@ -574,19 +576,24 @@ 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]:
from mesonbuild import coredata
optstore = coredata.load(top_builddir).optstore
return [f"-D{adapt_key(k, role)}={v.value}" for k, v in optstore.options.items()
if option_should_be_forwarded(k, v, optstore, role, subprojects)]
return [f"-D{adapt_key(k, role)}={v.value}" for k, v in coredata.load(top_builddir).options.items()
if option_should_be_forwarded(k, v, role, subprojects)]
def adapt_key(k: "OptionKey", role: Role) -> "OptionKey":
@@ -596,47 +603,44 @@ def adapt_key(k: "OptionKey", role: Role) -> "OptionKey":
def option_should_be_forwarded(k: "OptionKey",
v: "options.UserOption[Any]",
optstore: "options.OptionStore",
v: "coredata.UserOption[Any]",
role: Role,
subprojects: set[str]) -> bool:
from mesonbuild import options
from mesonbuild.mesonlib import MachineChoice
from mesonbuild import coredata
subproject = k.subproject or ""
our_project_id = "frida-core" if role == "subproject" else ""
is_for_us = subproject == our_project_id
is_for_child = subproject in subprojects
is_for_us = k.subproject == our_project_id
is_for_child = k.subproject in subprojects
if optstore.is_project_option(k):
if k.is_project():
if is_for_us:
if k.name in {"compiler_backend", "assets"}:
if k.name == "compiler_backend":
return False
tokens = k.name.split("_")
if tokens[0] in {"helper", "agent"} and tokens[-1] in {"modern", "legacy"}:
return False
if k.subproject and k.machine is not MachineChoice.HOST:
if k.subproject and k.machine is not coredata.MachineChoice.HOST:
return False
return is_for_us or is_for_child
if optstore.is_per_machine_option(k):
if coredata.CoreData.is_per_machine_option(k):
# The compat build generates its own machine files, so forwarding the
# parent's build-machine options would inject a foreign toolchain. In a
# native compat build that conjures a compiler-less build machine and
# breaks any native: true target.
return False
if optstore.is_builtin_option(k):
if k.is_builtin():
if k.name in {"buildtype", "genvslite"}:
return False
if not str(v.value):
return False
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 subproject == our_project_id
if not is_for_child and role == "subproject" and k.subproject in {"", our_project_id}:
if not coredata.BUILTIN_OPTIONS[k.as_root()].yielding:
return k.subproject == our_project_id
return True
if k.get_module_prefix() == "python":
if k.module == "python":
if k.name == "install_env" and v.value == "prefix":
return False
if not str(v.value):
@@ -790,6 +794,17 @@ 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"
Vendored
+2 -2
View File
@@ -1,12 +1,12 @@
#!/bin/sh
[ -z "$PYTHON" ] && PYTHON=$(command -v python3 >/dev/null && echo python3 || echo python)
[ -z "$PYTHON" ] && PYTHON=$(which python3 >/dev/null && echo python3 || echo python)
cd $(dirname $0)
srcroot=$(pwd)
if [ ! -f releng/meson/meson.py ] || git submodule status 2>/dev/null | grep -q '^-'; then
if [ ! -f releng/meson/meson.py ]; then
git submodule update --init --recursive --depth 1 || exit $?
fi
-38
View File
@@ -13,44 +13,6 @@
# include <gioapple.h>
#elif defined (HAVE_GIOOPENSSL)
# include <gioopenssl.h>
#endif
#ifdef HAVE_PROSPERO
# include "frida-prospero.h"
# include <unistd.h>
#endif
#ifdef HAVE_PROSPERO
FridaProsperoAgentArgs frida_prospero_agent_args = { .fifo_fd = -1 };
int
main (int argc, char * argv[], char * envp[])
{
FridaProsperoAgentArgs * args = &frida_prospero_agent_args;
FridaProsperoInjectorState injector_state;
guint8 hello_byte = FRIDA_PROSPERO_HELLO_BYTE;
FridaUnloadPolicy unload_policy = FRIDA_UNLOAD_POLICY_IMMEDIATE;
guint8 policy_byte;
guint32 worker_id;
injector_state.fifo_fd = args->fifo_fd;
injector_state.agent_ctrlfd = args->agent_ctrlfd;
injector_state.mapped_range = &args->mapped_range;
write (injector_state.fifo_fd, &hello_byte, sizeof (hello_byte));
frida_agent_main (GSIZE_TO_POINTER (args->agent_parameters), &unload_policy, &injector_state);
policy_byte = unload_policy;
worker_id = gum_process_get_current_thread_id ();
write (injector_state.fifo_fd, &policy_byte, sizeof (policy_byte));
write (injector_state.fifo_fd, &worker_id, sizeof (worker_id));
close (injector_state.fifo_fd);
return 0;
}
#endif
void
+35 -413
View File
@@ -1,5 +1,3 @@
using Efni;
namespace Frida.Agent {
public void main (string agent_parameters, ref Frida.UnloadPolicy unload_policy, void * injector_state) {
if (Runner.shared_instance == null)
@@ -80,7 +78,6 @@ 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;
@@ -103,9 +100,6 @@ 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 ();
@@ -128,43 +122,12 @@ 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)
mapped_range = prospero_state.mapped_range;
#endif
if (cached_agent_path == null) {
cached_agent_range = detect_own_range_and_path (mapped_range, out cached_agent_path);
Gum.Cloak.add_range (cached_agent_range);
@@ -194,19 +157,6 @@ namespace Frida.Agent {
}
#endif
#if PROSPERO
string? agent_parameters_with_transport_uri = null;
if (prospero_state != null) {
int agent_ctrlfd = prospero_state.agent_ctrlfd;
prospero_state.agent_ctrlfd = -1;
fdt_padder.move_descriptor_if_needed (ref agent_ctrlfd);
agent_parameters_with_transport_uri = "socket:%d%s".printf (agent_ctrlfd, agent_parameters);
agent_parameters = agent_parameters_with_transport_uri;
}
#endif
var ignore_scope = new ThreadIgnoreScope (FRIDA_THREAD);
shared_instance = new Runner (agent_parameters, cached_agent_path, cached_agent_range);
@@ -230,15 +180,6 @@ 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;
@@ -325,12 +266,25 @@ namespace Frida.Agent {
}
~Runner () {
remove_instrumentation ();
var interceptor = this.interceptor;
interceptor.begin_transaction ();
if (code_memory_delegated)
Gum.Memory.set_hooks (null);
disable_child_gating ();
exceptor = null;
exit_monitor = null;
interceptor.end_transaction ();
interceptor.begin_transaction ();
thread_suspend_monitor = null;
unwind_sitter = null;
invalidate_dbus_context ();
interceptor.end_transaction ();
}
private void run (owned FileDescriptorTablePadder padder) throws Error {
@@ -353,9 +307,6 @@ 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 ();
@@ -371,47 +322,38 @@ 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);
interceptor = Gum.Interceptor.obtain ();
{
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 ();
}
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);
});
@@ -420,77 +362,6 @@ 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);
@@ -1152,10 +1023,6 @@ 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 ();
}
@@ -1201,34 +1068,6 @@ 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;
@@ -1437,7 +1276,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 && !code_memory_delegated)
if (!closed_by_us)
unload.begin (null);
Promise<bool> operation = null;
@@ -1509,8 +1348,6 @@ 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)
@@ -1550,8 +1387,6 @@ 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");
@@ -1591,7 +1426,6 @@ 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);
@@ -1600,13 +1434,6 @@ 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);
@@ -1654,16 +1481,6 @@ 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) {
@@ -1824,201 +1641,6 @@ 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;
-8
View File
@@ -1,8 +0,0 @@
{
global:
frida_agent_main;
frida_prospero_agent_args;
local:
*;
};
+3 -8
View File
@@ -25,9 +25,6 @@ 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
@@ -45,8 +42,6 @@ if build_agent
symscript = 'frida-agent-glibc.version'
elif host_os == 'android'
symscript = 'frida-agent-android.version'
elif host_is_prospero
symscript = 'frida-agent-prospero.version'
else
symscript = 'frida-agent.version'
endif
@@ -59,8 +54,6 @@ if build_agent
extra_link_args += '-Wl,-Bsymbolic'
endif
extra_link_args += prospero_crt_link_args
raw_agent = shared_library('frida-agent-raw', agent_sources,
name_prefix: 'lib',
c_args: frida_component_cflags,
@@ -70,7 +63,7 @@ if build_agent
link_depends: extra_link_depends,
vs_module_defs: 'frida-agent.def',
override_options: ['b_asneeded=true'],
dependencies: [gio_dep, gee_dep, gum_dep, gumjs_dep, base_dep, pipe_dep, tls_provider_dep, payload_dep],
dependencies: [gio_dep, gee_dep, gum_dep, gumjs_dep, gumpharo_dep, base_dep, pipe_dep, tls_provider_dep, payload_dep],
)
operations = ['--move', 'constructor', 'frida_libc_shim_init', 'first']
@@ -96,6 +89,8 @@ if build_agent
install: assets_mode == 'installed' and not need_universal_agent,
install_dir: asset_dir,
)
agent_dep = declare_dependency(link_with: agent)
endif
if need_universal_agent
+12
View File
@@ -0,0 +1,12 @@
#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);
}
+797
View File
@@ -0,0 +1,797 @@
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);
}
+2 -14
View File
@@ -1,13 +1,9 @@
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 ();
@@ -20,33 +16,25 @@ 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;
IOStream? unlinked_stream = null;
lock (channels) {
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;
}
-2
View File
@@ -1,5 +1,3 @@
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 () {
+51
View File
@@ -0,0 +1,51 @@
#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);
});
}
+26
View File
@@ -0,0 +1,26 @@
#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
-42
View File
@@ -1,42 +0,0 @@
#ifndef __FRIDA_PROSPERO_H__
#define __FRIDA_PROSPERO_H__
#ifdef HAVE_PROSPERO
#include <gum/gum.h>
G_BEGIN_DECLS
#define FRIDA_PROSPERO_AGENT_ARGS_SYMBOL "frida_prospero_agent_args"
#define FRIDA_PROSPERO_HELLO_BYTE 0xff
typedef struct _FridaProsperoAgentArgs FridaProsperoAgentArgs;
struct _FridaProsperoAgentArgs
{
GumAddress agent_parameters;
gint fifo_fd;
gint agent_ctrlfd;
GumMemoryRange mapped_range;
};
typedef struct _FridaProsperoAppLaunchContext FridaProsperoAppLaunchContext;
struct _FridaProsperoAppLaunchContext
{
guint32 size;
guint32 user_id;
guint32 app_opt;
guint64 crash_report;
guint32 check_flag;
};
extern int sceUserServiceInitialize (void * params);
extern int sceUserServiceGetForegroundUser (guint32 * user_id);
extern int sceSystemServiceLaunchApp (const gchar * title_id, gchar ** argv, FridaProsperoAppLaunchContext * ctx);
G_END_DECLS
#endif
#endif
+114
View File
@@ -0,0 +1,114 @@
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 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);
}
}
}
-9
View File
@@ -123,15 +123,6 @@ 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);
}
+18 -4
View File
@@ -1,16 +1,21 @@
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',
]
@@ -18,12 +23,19 @@ base_sources = [
extra_vala_args = []
if host_os == 'windows'
base_sources += ['windows.vala', 'windows-glue.c']
base_sources += 'windows.vala'
else
base_sources += 'posix.vala'
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'
@@ -82,6 +94,8 @@ endif
if host_os_family == 'darwin'
base_vala_args += [
'--pkg=corefoundation',
'--pkg=darwin-gcd',
'--pkg=darwin-xpc',
]
endif
if host_os_family == 'linux'
@@ -100,7 +114,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, efni_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] + extra_deps,
install: have_in_tree_payloads,
install_dir: [true, header_install_dir, true, false],
)
@@ -108,7 +122,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, efni_dep] + extra_deps,
dependencies: [nice_dep, json_glib_dep, gum_dep] + extra_deps,
)
base_build_dir = meson.current_build_dir()
-2
View File
@@ -1,5 +1,3 @@
using Efni;
#if HAVE_NICE
namespace Frida {
namespace PeerConnection {
-124
View File
@@ -220,128 +220,4 @@ namespace Frida {
Posix.tcsetattr (fd, 0, tios);
}
#endif
namespace ChildProcess {
public async WaitResult wait_for_next_stop (uint pid, Cancellable? cancellable) throws Error, IOError {
var main_context = MainContext.get_thread_default ();
bool timed_out = false;
var timeout_source = new TimeoutSource.seconds (5);
timeout_source.set_callback (() => {
timed_out = true;
return Source.REMOVE;
});
timeout_source.attach (main_context);
int status = 0;
uint[] delays = { 0, 1, 2, 5, 10, 20, 50, 250 };
try {
for (uint i = 0; !timed_out && !cancellable.set_error_if_cancelled (); i++) {
int res = Posix.waitpid ((Posix.pid_t) pid, out status, Posix.WNOHANG);
if (res == -1)
throw new Error.NOT_SUPPORTED ("Unable to wait for next stop: %s", strerror (errno));
if (res != 0)
break;
uint delay_ms = (i < delays.length) ? delays[i] : delays[delays.length - 1];
var delay_source = new TimeoutSource (delay_ms);
delay_source.set_callback (wait_for_next_stop.callback);
delay_source.attach (main_context);
var cancel_source = new CancellableSource (cancellable);
cancel_source.set_callback (wait_for_next_stop.callback);
cancel_source.attach (main_context);
yield;
cancel_source.destroy ();
delay_source.destroy ();
}
} finally {
timeout_source.destroy ();
}
if (timed_out)
throw new Error.TIMED_OUT ("Unexpectedly timed out while waiting for stop from process with PID %u", pid);
return WaitResult (pid, status);
}
}
public struct WaitResult {
public uint pid;
public int status;
public WaitKind kind;
public uint exit_status;
public int term_signal;
public int stop_signal;
public WaitResult (uint pid, int status) {
this.pid = pid;
this.status = status;
kind = WaitKind.OTHER;
exit_status = 0;
term_signal = 0;
stop_signal = 0;
if (PosixStatus.is_exit (status)) {
kind = WaitKind.EXITED;
exit_status = PosixStatus.parse_exit_status (status);
} else if (PosixStatus.is_signaled (status)) {
kind = WaitKind.SIGNALED;
term_signal = PosixStatus.parse_termination_signal (status);
} else if (PosixStatus.is_stopped (status)) {
kind = WaitKind.STOPPED;
stop_signal = PosixStatus.parse_stop_signal (status);
}
}
public void check_stopped () throws Error {
switch (kind) {
case WaitKind.EXITED:
throw new Error.NOT_SUPPORTED ("Target exited with status %u", exit_status);
case WaitKind.SIGNALED:
throw new Error.NOT_SUPPORTED ("Target terminated with signal %u", term_signal);
case WaitKind.STOPPED:
return;
default:
throw new Error.NOT_SUPPORTED ("Unexpected status: 0x%08x", status);
}
}
}
public enum WaitKind {
EXITED,
SIGNALED,
STOPPED,
OTHER
}
namespace PosixStatus {
[CCode (cname = "WIFEXITED", cheader_filename = "sys/wait.h")]
private extern bool is_exit (int status);
[CCode (cname = "WIFSIGNALED", cheader_filename = "sys/wait.h")]
private extern bool is_signaled (int status);
[CCode (cname = "WIFSTOPPED", cheader_filename = "sys/wait.h")]
private extern bool is_stopped (int status);
[CCode (cname = "WEXITSTATUS", cheader_filename = "sys/wait.h")]
private extern uint parse_exit_status (int status);
[CCode (cname = "WTERMSIG", cheader_filename = "sys/wait.h")]
private extern Posix.Signal parse_termination_signal (int status);
[CCode (cname = "WSTOPSIG", cheader_filename = "sys/wait.h")]
private extern Posix.Signal parse_stop_signal (int status);
}
}
+124 -58
View File
@@ -1,18 +1,19 @@
namespace Frida {
public sealed class Promise<T> {
private Efni.Promise<T> impl;
private Future<T>? _future;
private Impl<T> impl;
public Future<T> future {
get {
if (_future == null)
_future = new FutureImpl<T> (impl.future);
return _future;
return impl;
}
}
public Promise () {
impl = new Efni.Promise<T> ();
impl = new Impl<T> ();
}
~Promise () {
impl.abandon ();
}
public void resolve (T result) {
@@ -23,84 +24,149 @@ namespace Frida {
impl.reject (error);
}
private class FutureImpl<T> : Object, Future<T> {
private Efni.Future<T> inner;
private class Impl<T> : Object, Future<T> {
public bool ready {
get {
return inner.ready;
return _ready;
}
}
private bool _ready = false;
public T? value {
get {
return inner.value;
return _value;
}
}
private T? _value;
public GLib.Error? error {
get {
return inner.error;
return _error;
}
}
private GLib.Error? _error;
public FutureImpl (Efni.Future<T> inner) {
this.inner = inner;
}
private Gee.ArrayQueue<CompletionFuncEntry>? on_complete;
private Gee.ArrayQueue<ThenHandlerEntry<T>>? on_then;
public async T wait_async (Cancellable? cancellable) throws Error, IOError {
if (!inner.ready) {
bool resumed = false;
public async T wait_async (Cancellable? cancellable) throws Frida.Error, IOError {
if (_ready)
return get_result ();
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 entry = new CompletionFuncEntry (wait_async.callback);
if (on_complete == null)
on_complete = new Gee.ArrayQueue<CompletionFuncEntry> ();
on_complete.offer (entry);
inner.then (f => {
if (!resumed) {
resumed = true;
wait_async.callback ();
}
});
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 ());
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) {
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 (_ready) {
schedule (() => {
handler (this);
return Source.REMOVE;
});
return;
}
return inner.value;
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;
}
}
}
@@ -109,9 +175,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 Error, IOError;
public abstract async T wait_async (Cancellable? cancellable) throws Frida.Error, IOError;
public abstract void then (owned FutureCompletionHandler<T> handler);
public abstract T get_result () throws Error, IOError;
public abstract T get_result () throws Frida.Error, IOError;
}
public delegate void FutureCompletionHandler<T> (Future<T> future);
+55 -100
View File
@@ -1,5 +1,3 @@
using Efni;
namespace Frida {
[DBus (name = "re.frida.HostSession17")]
public interface HostSession : Object {
@@ -126,43 +124,8 @@ 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,
@@ -1170,15 +1133,6 @@ 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;
@@ -1189,8 +1143,6 @@ namespace Frida {
public struct LinuxInjectorState {
public int frida_ctrlfd;
public int agent_ctrlfd;
public uint64 mapped_base;
public size_t mapped_size;
}
#endif
@@ -1200,14 +1152,6 @@ namespace Frida {
}
#endif
#if PROSPERO
public struct ProsperoInjectorState {
public int fifo_fd;
public int agent_ctrlfd;
public Gum.MemoryRange? mapped_range;
}
#endif
/**
* Why spawn gating was disabled on a {@link Device}.
*/
@@ -1329,9 +1273,6 @@ 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;
@@ -1345,9 +1286,6 @@ 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;
@@ -1360,41 +1298,6 @@ 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;
@@ -2371,7 +2274,11 @@ namespace Frida {
/**
* The V8 runtime.
*/
V8;
V8,
/**
* The Pharo runtime.
*/
PHARO;
public static ScriptRuntime from_nick (string nick) throws Error {
return Marshal.enum_from_nick<ScriptRuntime> (nick);
@@ -2832,8 +2739,6 @@ 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";
@@ -2859,4 +2764,54 @@ 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;
}
}
}
+25
View File
@@ -1,5 +1,30 @@
#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)
{
+87 -29
View File
@@ -1,22 +1,99 @@
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_frida_address (address, port, DEFAULT_CONTROL_PORT);
return parse_socket_address (address, port, "127.0.0.1", DEFAULT_CONTROL_PORT);
}
public SocketConnectable parse_cluster_address (string? address, uint16 port = 0) throws Error {
return parse_frida_address (address, port, DEFAULT_CLUSTER_PORT);
return parse_socket_address (address, port, "127.0.0.1", DEFAULT_CLUSTER_PORT);
}
private SocketConnectable parse_frida_address (string? address, uint16 port, uint16 default_port) throws Error {
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, -1, type);
}
#endif
try {
return Efni.parse_socket_address (address, port, "127.0.0.1", default_port);
} catch (Efni.Error e) {
throw error_from_efni (e);
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);
}
}
}
@@ -649,8 +726,7 @@ namespace Frida {
try {
remote_address = sc.get_remote_address ();
} catch (GLib.Error e) {
remove_connection (peer);
return;
assert_not_reached ();
}
incoming (peer, remote_address);
@@ -1040,9 +1116,6 @@ 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);
}
@@ -1092,7 +1165,7 @@ namespace Frida {
if (recv_queue.len == 0) {
#if HAVE_SOUP_WEBSOCKET_BACKPRESSURE
resume_websocket_input ();
websocket.resume_input ();
#endif
}
@@ -1109,21 +1182,6 @@ 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);
+336
View File
@@ -0,0 +1,336 @@
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 ();
}
}
}
+432
View File
@@ -0,0 +1,432 @@
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;
}
}
-376
View File
@@ -1,376 +0,0 @@
#include "frida-base.h"
#include <windows.h>
#define FRIDA_TYPE_WINDOWS_HANDLE_STREAM (frida_windows_handle_stream_get_type ())
#define FRIDA_TYPE_WINDOWS_HANDLE_INPUT_STREAM (frida_windows_handle_input_stream_get_type ())
#define FRIDA_TYPE_WINDOWS_HANDLE_OUTPUT_STREAM (frida_windows_handle_output_stream_get_type ())
#define FRIDA_WINDOWS_HANDLE_STREAM(o) ((FridaWindowsHandleStream *) (o))
#define FRIDA_WINDOWS_HANDLE_INPUT_STREAM(o) ((FridaWindowsHandleInputStream *) (o))
#define FRIDA_WINDOWS_HANDLE_OUTPUT_STREAM(o) ((FridaWindowsHandleOutputStream *) (o))
typedef struct _FridaWindowsHandleStream FridaWindowsHandleStream;
typedef struct _FridaWindowsHandleStreamClass FridaWindowsHandleStreamClass;
typedef struct _FridaWindowsHandleInputStream FridaWindowsHandleInputStream;
typedef struct _FridaWindowsHandleInputStreamClass FridaWindowsHandleInputStreamClass;
typedef struct _FridaWindowsHandleOutputStream FridaWindowsHandleOutputStream;
typedef struct _FridaWindowsHandleOutputStreamClass FridaWindowsHandleOutputStreamClass;
struct _FridaWindowsHandleStream
{
GIOStream parent;
HANDLE handle;
HANDLE read_complete;
HANDLE read_cancel;
HANDLE write_complete;
HANDLE write_cancel;
GInputStream * input;
GOutputStream * output;
};
struct _FridaWindowsHandleStreamClass
{
GIOStreamClass parent_class;
};
struct _FridaWindowsHandleInputStream
{
GInputStream parent;
FridaWindowsHandleStream * stream;
};
struct _FridaWindowsHandleInputStreamClass
{
GInputStreamClass parent_class;
};
struct _FridaWindowsHandleOutputStream
{
GOutputStream parent;
FridaWindowsHandleStream * stream;
};
struct _FridaWindowsHandleOutputStreamClass
{
GOutputStreamClass parent_class;
};
static GIOStream * frida_windows_handle_stream_new (HANDLE handle);
static void frida_windows_handle_stream_finalize (GObject * object);
static GInputStream * frida_windows_handle_stream_get_input_stream (GIOStream * stream);
static GOutputStream * frida_windows_handle_stream_get_output_stream (GIOStream * stream);
static gboolean frida_windows_handle_stream_close_fn (GIOStream * stream, GCancellable * cancellable, GError ** error);
static gssize frida_windows_handle_input_stream_read (GInputStream * base, void * buffer, gsize count,
GCancellable * cancellable, GError ** error);
static gboolean frida_windows_handle_input_stream_close (GInputStream * base, GCancellable * cancellable, GError ** error);
static gssize frida_windows_handle_output_stream_write (GOutputStream * base, const void * buffer, gsize count,
GCancellable * cancellable, GError ** error);
static gboolean frida_windows_handle_output_stream_close (GOutputStream * base, GCancellable * cancellable,
GError ** error);
static gboolean frida_windows_handle_stream_await (FridaWindowsHandleStream * self, HANDLE complete, HANDLE cancel,
GCancellable * cancellable, GError ** error);
static void frida_windows_handle_stream_on_cancel (GCancellable * cancellable, gpointer user_data);
G_DEFINE_TYPE (FridaWindowsHandleStream, frida_windows_handle_stream, G_TYPE_IO_STREAM)
G_DEFINE_TYPE (FridaWindowsHandleInputStream, frida_windows_handle_input_stream, G_TYPE_INPUT_STREAM)
G_DEFINE_TYPE (FridaWindowsHandleOutputStream, frida_windows_handle_output_stream, G_TYPE_OUTPUT_STREAM)
GIOStream *
frida_windows_named_pipe_open_client (const gchar * path, GError ** error)
{
HANDLE handle;
WCHAR * path_utf16;
DWORD last_error;
path_utf16 = (WCHAR *) g_utf8_to_utf16 (path, -1, NULL, NULL, NULL);
if (path_utf16 == NULL)
goto invalid_path;
handle = CreateFileW (path_utf16,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
last_error = GetLastError ();
g_free (path_utf16);
if (handle == INVALID_HANDLE_VALUE)
goto cannot_open;
return frida_windows_handle_stream_new (handle);
invalid_path:
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Invalid pipe path");
return NULL;
}
cannot_open:
{
g_set_error (error,
G_IO_ERROR,
g_io_error_from_win32_error (last_error),
"Unable to open %s (CreateFile returned 0x%08lx)",
path, last_error);
return NULL;
}
}
static GIOStream *
frida_windows_handle_stream_new (HANDLE handle)
{
FridaWindowsHandleStream * stream;
FridaWindowsHandleInputStream * input;
FridaWindowsHandleOutputStream * output;
stream = g_object_new (FRIDA_TYPE_WINDOWS_HANDLE_STREAM, NULL);
stream->handle = handle;
stream->read_complete = CreateEvent (NULL, TRUE, FALSE, NULL);
stream->read_cancel = CreateEvent (NULL, TRUE, FALSE, NULL);
stream->write_complete = CreateEvent (NULL, TRUE, FALSE, NULL);
stream->write_cancel = CreateEvent (NULL, TRUE, FALSE, NULL);
input = g_object_new (FRIDA_TYPE_WINDOWS_HANDLE_INPUT_STREAM, NULL);
input->stream = stream;
stream->input = G_INPUT_STREAM (input);
output = g_object_new (FRIDA_TYPE_WINDOWS_HANDLE_OUTPUT_STREAM, NULL);
output->stream = stream;
stream->output = G_OUTPUT_STREAM (output);
return G_IO_STREAM (stream);
}
static void
frida_windows_handle_stream_class_init (FridaWindowsHandleStreamClass * klass)
{
GObjectClass * object_class = G_OBJECT_CLASS (klass);
GIOStreamClass * stream_class = G_IO_STREAM_CLASS (klass);
object_class->finalize = frida_windows_handle_stream_finalize;
stream_class->get_input_stream = frida_windows_handle_stream_get_input_stream;
stream_class->get_output_stream = frida_windows_handle_stream_get_output_stream;
stream_class->close_fn = frida_windows_handle_stream_close_fn;
}
static void
frida_windows_handle_stream_init (FridaWindowsHandleStream * self)
{
self->handle = INVALID_HANDLE_VALUE;
}
static void
frida_windows_handle_stream_finalize (GObject * object)
{
FridaWindowsHandleStream * self = FRIDA_WINDOWS_HANDLE_STREAM (object);
g_clear_object (&self->input);
g_clear_object (&self->output);
CloseHandle (self->read_complete);
CloseHandle (self->read_cancel);
CloseHandle (self->write_complete);
CloseHandle (self->write_cancel);
if (self->handle != INVALID_HANDLE_VALUE)
CloseHandle (self->handle);
G_OBJECT_CLASS (frida_windows_handle_stream_parent_class)->finalize (object);
}
static GInputStream *
frida_windows_handle_stream_get_input_stream (GIOStream * stream)
{
return FRIDA_WINDOWS_HANDLE_STREAM (stream)->input;
}
static GOutputStream *
frida_windows_handle_stream_get_output_stream (GIOStream * stream)
{
return FRIDA_WINDOWS_HANDLE_STREAM (stream)->output;
}
static gboolean
frida_windows_handle_stream_close_fn (GIOStream * stream, GCancellable * cancellable, GError ** error)
{
FridaWindowsHandleStream * self = FRIDA_WINDOWS_HANDLE_STREAM (stream);
SetEvent (self->read_cancel);
SetEvent (self->write_cancel);
if (self->handle != INVALID_HANDLE_VALUE)
{
CancelIo (self->handle);
CloseHandle (self->handle);
self->handle = INVALID_HANDLE_VALUE;
}
return TRUE;
}
static void
frida_windows_handle_input_stream_class_init (FridaWindowsHandleInputStreamClass * klass)
{
GInputStreamClass * stream_class = G_INPUT_STREAM_CLASS (klass);
stream_class->read_fn = frida_windows_handle_input_stream_read;
stream_class->close_fn = frida_windows_handle_input_stream_close;
}
static void
frida_windows_handle_input_stream_init (FridaWindowsHandleInputStream * self)
{
}
static gssize
frida_windows_handle_input_stream_read (GInputStream * base, void * buffer, gsize count, GCancellable * cancellable,
GError ** error)
{
FridaWindowsHandleStream * stream = FRIDA_WINDOWS_HANDLE_INPUT_STREAM (base)->stream;
OVERLAPPED overlapped = { 0, };
DWORD bytes_transferred, last_error;
if (stream->handle == INVALID_HANDLE_VALUE)
return 0;
overlapped.hEvent = stream->read_complete;
if (!ReadFile (stream->handle, buffer, count, NULL, &overlapped) && GetLastError () != ERROR_IO_PENDING)
goto failure;
if (!frida_windows_handle_stream_await (stream, stream->read_complete, stream->read_cancel, cancellable, error))
return -1;
if (!GetOverlappedResult (stream->handle, &overlapped, &bytes_transferred, FALSE))
goto failure;
return bytes_transferred;
failure:
{
last_error = GetLastError ();
if (last_error == ERROR_BROKEN_PIPE || last_error == ERROR_PIPE_NOT_CONNECTED)
return 0;
g_set_error (error,
G_IO_ERROR,
g_io_error_from_win32_error (last_error),
"Error reading from pipe");
return -1;
}
}
static gboolean
frida_windows_handle_input_stream_close (GInputStream * base, GCancellable * cancellable, GError ** error)
{
return TRUE;
}
static void
frida_windows_handle_output_stream_class_init (FridaWindowsHandleOutputStreamClass * klass)
{
GOutputStreamClass * stream_class = G_OUTPUT_STREAM_CLASS (klass);
stream_class->write_fn = frida_windows_handle_output_stream_write;
stream_class->close_fn = frida_windows_handle_output_stream_close;
}
static void
frida_windows_handle_output_stream_init (FridaWindowsHandleOutputStream * self)
{
}
static gssize
frida_windows_handle_output_stream_write (GOutputStream * base, const void * buffer, gsize count,
GCancellable * cancellable, GError ** error)
{
FridaWindowsHandleStream * stream = FRIDA_WINDOWS_HANDLE_OUTPUT_STREAM (base)->stream;
OVERLAPPED overlapped = { 0, };
DWORD bytes_transferred, last_error;
if (stream->handle == INVALID_HANDLE_VALUE)
goto closed;
overlapped.hEvent = stream->write_complete;
if (!WriteFile (stream->handle, buffer, count, NULL, &overlapped) && GetLastError () != ERROR_IO_PENDING)
goto failure;
if (!frida_windows_handle_stream_await (stream, stream->write_complete, stream->write_cancel, cancellable, error))
return -1;
if (!GetOverlappedResult (stream->handle, &overlapped, &bytes_transferred, FALSE))
goto failure;
return bytes_transferred;
closed:
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, "Pipe is closed");
return -1;
}
failure:
{
last_error = GetLastError ();
g_set_error (error,
G_IO_ERROR,
g_io_error_from_win32_error (last_error),
"Error writing to pipe");
return -1;
}
}
static gboolean
frida_windows_handle_output_stream_close (GOutputStream * base, GCancellable * cancellable, GError ** error)
{
return TRUE;
}
static gboolean
frida_windows_handle_stream_await (FridaWindowsHandleStream * self, HANDLE complete, HANDLE cancel,
GCancellable * cancellable, GError ** error)
{
gulong handler_id = 0;
HANDLE events[2];
if (cancellable != NULL)
handler_id = g_cancellable_connect (cancellable, G_CALLBACK (frida_windows_handle_stream_on_cancel), cancel, NULL);
events[0] = complete;
events[1] = cancel;
WaitForMultipleObjects (G_N_ELEMENTS (events), events, FALSE, INFINITE);
if (cancellable != NULL)
{
g_cancellable_disconnect (cancellable, handler_id);
if (g_cancellable_set_error_if_cancelled (cancellable, error))
{
CancelIo (self->handle);
return FALSE;
}
}
if (WaitForSingleObject (cancel, 0) == WAIT_OBJECT_0)
{
g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, "Pipe is closed");
return FALSE;
}
return TRUE;
}
static void
frida_windows_handle_stream_on_cancel (GCancellable * cancellable, gpointer user_data)
{
HANDLE cancel = (HANDLE) user_data;
SetEvent (cancel);
}
-4
View File
@@ -1,8 +1,4 @@
namespace Frida {
namespace WindowsNamedPipe {
public extern IOStream open_client (string path) throws GLib.Error;
}
public sealed class StdioPipes : Object {
public OutputStream input {
get;
+291
View File
@@ -0,0 +1,291 @@
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);
}
}
}
}
-2
View File
@@ -1,5 +1,3 @@
using Efni;
namespace Frida.Gadget {
private sealed class Config : Object, Json.Serializable {
public Object interaction {
-4
View File
@@ -55,10 +55,6 @@ if host_os_family in ['linux', 'qnx']
extra_link_args += ['-Wl,-Bsymbolic']
endif
if host_is_prospero
extra_link_args += [prospero_crt_link_args, '-Wl,--defsym=main=0']
endif
raw_gadget = shared_library('frida-gadget-raw', gadget_sources,
name_prefix: 'lib',
vala_args: [base_vala_args, payload_vala_args, extra_vala_args],
+1
View File
@@ -8,6 +8,7 @@ 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'
-2
View File
@@ -1,5 +1,3 @@
using Efni;
namespace Frida {
public abstract class BaseAgentSession : Object, AgentSession {
public signal void closed ();
+2 -24
View File
@@ -35,37 +35,15 @@ namespace Frida {
interceptor.attach ((void *) Gum.Process.find_module_by_name ("kernel32.dll").find_export_by_name ("ExitProcess"),
listener);
#else
var libc = Gum.Process.get_libc_module ();
const string[] apis = {
"exit",
"_exit",
"abort",
};
#if PROSPERO
foreach (var symbol in apis) {
interceptor.attach ((void *) Gum.Module.find_global_export_by_name (symbol), listener);
interceptor.attach ((void *) libc.find_export_by_name (symbol), listener);
}
#else
var libc = Gum.Process.get_libc_module ();
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
}
+21 -28
View File
@@ -28,7 +28,6 @@
#undef feof
#undef ferror
#undef fgetwc
#undef fileno
#undef fopen
#undef fopen64
#undef fputwc
@@ -215,7 +214,6 @@ static void frida_stdio_unregister_stream (FILE * stream);
static void frida_stdio_register_dir (DIR * dirp);
static void frida_stdio_unregister_dir (DIR * dirp);
#endif
static void frida_stdio_ensure_registries (void);
static void frida_flush_all_streams (int * result);
static FridaFile * frida_file_get_impl (FILE * stream);
@@ -253,13 +251,7 @@ static ssize_t frida_write_nointr (int fd, const void * buf, size_t count);
static int frida_close_nointr (int fd);
static off_t frida_lseek_nointr (int fd, off_t offset, int whence);
#if defined (HAVE_QNX)
G_GNUC_INTERNAL FILE _Stdin, _Stdout, _Stderr;
G_GNUC_INTERNAL FILE * stdin = &_Stdin;
G_GNUC_INTERNAL FILE * stdout = &_Stdout;
G_GNUC_INTERNAL FILE * stderr = &_Stderr;
#elif !defined (FRIDA_STDIO_OPAQUE_FILE)
#ifndef FRIDA_STDIO_OPAQUE_FILE
G_GNUC_INTERNAL FILE __sF[3];
G_GNUC_INTERNAL FILE * stdin = &__sF[0];
@@ -309,18 +301,33 @@ frida_libc_shim_init (void)
G_LOCK (frida_stdio);
frida_stdio_ensure_registries ();
frida_streams = g_hash_table_new (g_direct_hash, g_direct_equal);
#ifdef HAVE_FRIDA_DIR
frida_dirs = g_hash_table_new (g_direct_hash, g_direct_equal);
#endif
G_UNLOCK (frida_stdio);
f0 = frida_file_new (0, FALSE, _IOFBF);
frida_file_bind_slot (stdin, f0);
#ifndef FRIDA_STDIO_OPAQUE_FILE
frida_file_bind_slot (&__sF[0], f0);
#else
frida_file_bind_slot ((FILE *) &frida_stdio[0], f0);
#endif
f1 = frida_file_new (1, FALSE, _IOLBF);
frida_file_bind_slot (stdout, f1);
#ifndef FRIDA_STDIO_OPAQUE_FILE
frida_file_bind_slot (&__sF[1], f1);
#else
frida_file_bind_slot ((FILE *) &frida_stdio[1], f1);
#endif
f2 = frida_file_new (2, FALSE, _IONBF);
frida_file_bind_slot (stderr, f2);
#ifndef FRIDA_STDIO_OPAQUE_FILE
frida_file_bind_slot (&__sF[2], f2);
#else
frida_file_bind_slot ((FILE *) &frida_stdio[2], f2);
#endif
frida_libc_shim_initialized = TRUE;
}
@@ -631,7 +638,7 @@ __vsnprintf_chk (char * string, size_t size, int flags, size_t len, const char *
return gum_vsnprintf (string, size, format, args);
}
#if defined (HAVE_XLOCALE_H) && !defined (HAVE_PROSPERO)
#ifdef HAVE_XLOCALE_H
G_GNUC_INTERNAL int
sprintf_l (char * string, locale_t loc, const char * format, ...)
@@ -1897,7 +1904,6 @@ frida_stdio_register_stream (FILE * stream)
{
G_LOCK (frida_stdio);
frida_stdio_ensure_registries ();
g_hash_table_add (frida_streams, stream);
G_UNLOCK (frida_stdio);
@@ -1920,7 +1926,6 @@ frida_stdio_register_dir (DIR * dirp)
{
G_LOCK (frida_stdio);
frida_stdio_ensure_registries ();
g_hash_table_add (frida_dirs, dirp);
G_UNLOCK (frida_stdio);
@@ -1938,18 +1943,6 @@ frida_stdio_unregister_dir (DIR * dirp)
#endif
static void
frida_stdio_ensure_registries (void)
{
if (frida_streams != NULL)
return;
frida_streams = g_hash_table_new (g_direct_hash, g_direct_equal);
#ifdef HAVE_FRIDA_DIR
frida_dirs = g_hash_table_new (g_direct_hash, g_direct_equal);
#endif
}
static void
frida_flush_all_streams (int * result)
{
+1 -1
View File
@@ -49,7 +49,7 @@ payload = static_library('frida-payload-' + api_version, payload_sources,
],
vala_header: 'frida-payload.h',
vala_vapi: f'frida-payload-@[email protected]',
dependencies: [gio_dep, gee_dep, json_glib_dep, gum_dep, gumjs_dep, base_dep],
dependencies: [gio_dep, gee_dep, json_glib_dep, gum_dep, gumjs_dep, gumpharo_dep, base_dep],
install: true,
install_dir: [true, header_install_dir, true],
)
-2
View File
@@ -1,5 +1,3 @@
using Efni;
namespace Frida {
public sealed class PortalClient : Object, AgentSessionProvider {
public signal void resume ();
-2
View File
@@ -1,5 +1,3 @@
using Efni;
namespace Frida {
public extern void libc_shim_prepare_to_deinit ();
#if DARWIN
+155 -19
View File
@@ -1,5 +1,3 @@
using Efni;
namespace Frida {
public sealed class ScriptEngine : Object {
public signal void message_from_script (AgentScriptId script_id, string json, Bytes? data);
@@ -93,7 +91,27 @@ namespace Frida {
if (name == null)
name = "script%u".printf (script_id.handle);
Gum.ScriptBackend backend = pick_backend (options.runtime);
ScriptRuntime runtime = options.runtime;
if (runtime == DEFAULT)
runtime = preferred_runtime;
#if HAVE_GUMPHARO
if (runtime == PHARO) {
if (source == null)
throw new Error.NOT_SUPPORTED ("The Pharo runtime cannot load precompiled scripts");
var instance = new PharoScriptInstance (script_id, new Gum.PharoScript (name, source));
instances[script_id] = instance;
instance.closed.connect (on_instance_closed);
instance.message.connect (on_instance_message);
instance.debug_message.connect (on_instance_debug_message);
return instance;
}
#endif
Gum.ScriptBackend backend = pick_backend (runtime);
Gum.Script script;
try {
@@ -111,7 +129,8 @@ namespace Frida {
return Source.REMOVE;
});
var instance = new ScriptInstance (script_id, script);
var js_instance = new JSScriptInstance (script_id, script);
ScriptInstance instance = js_instance;
instances[script_id] = instance;
instance.closed.connect (on_instance_closed);
@@ -177,7 +196,9 @@ namespace Frida {
}
public Gum.Script eternalize_script (AgentScriptId script_id) throws Error {
var instance = get_instance (script_id);
var instance = get_instance (script_id) as JSScriptInstance;
if (instance == null)
throw new Error.NOT_SUPPORTED ("Only JavaScript scripts may be eternalized");
var script = instance.eternalize ();
@@ -233,7 +254,7 @@ namespace Frida {
source.attach (Gum.ScriptBackend.get_scheduler ().get_js_context ());
}
public sealed class ScriptInstance : Object, RpcPeer {
public abstract class ScriptInstance : Object, RpcPeer {
public signal void closed ();
public signal void message (string json, Bytes? data);
public signal void debug_message (string message);
@@ -243,6 +264,120 @@ namespace Frida {
construct;
}
public abstract async void close ();
public abstract async void flush ();
public abstract async void load () throws Error;
public abstract void post (string json, Bytes? data) throws Error;
public abstract async void prepare_for_termination (TerminationReason reason);
public abstract void unprepare_for_termination ();
public abstract async void post_rpc_message (string json, Bytes? data, Cancellable? cancellable)
throws Error, IOError;
public virtual void interrupt () throws Error {
throw new Error.NOT_SUPPORTED ("Interrupt is not available for this runtime");
}
public virtual async void terminate () {
yield close ();
}
public virtual void enable_debugger () throws Error {
throw new Error.NOT_SUPPORTED ("Debugger is not available for this runtime");
}
public virtual void disable_debugger () {
}
public virtual void post_debug_message (string message) throws Error {
throw new Error.NOT_SUPPORTED ("Debugger is not available for this runtime");
}
}
#if HAVE_GUMPHARO
public sealed class PharoScriptInstance : ScriptInstance {
private Gum.PharoScript? script;
private State state = CREATED;
private enum State {
CREATED,
LOADED,
UNLOADED,
DESTROYED
}
private RpcClient rpc_client;
public PharoScriptInstance (AgentScriptId script_id, Gum.PharoScript script) {
Object (script_id: script_id);
this.script = script;
script.set_message_handler (on_message);
}
construct {
rpc_client = new RpcClient (this);
}
public override async void close () {
if (state == DESTROYED)
return;
if (state == LOADED) {
script.unload ();
state = UNLOADED;
}
script = null;
state = DESTROYED;
closed ();
}
public override async void flush () {
yield close ();
}
public override async void load () throws Error {
if (state != CREATED)
throw new Error.INVALID_OPERATION ("Script cannot be loaded in its current state");
script.load ();
state = LOADED;
}
public override void post (string json, Bytes? data) throws Error {
if (state != LOADED)
throw new Error.INVALID_OPERATION ("Only active scripts may be posted to");
script.post (json, data);
}
public override async void prepare_for_termination (TerminationReason reason) {
}
public override void unprepare_for_termination () {
}
private void on_message (Gum.PharoScript script, string json, Bytes? data) {
bool handled = rpc_client.try_handle_message (json);
if (!handled)
this.message (json, data);
}
public override async void post_rpc_message (string json, Bytes? data, Cancellable? cancellable)
throws Error, IOError {
if (script == null)
throw new Error.INVALID_OPERATION ("Script is destroyed");
script.post (json, data);
}
}
#endif
public sealed class JSScriptInstance : ScriptInstance {
public Gum.Script? script {
get {
return _script;
@@ -276,7 +411,7 @@ namespace Frida {
private RpcClient rpc_client;
public ScriptInstance (AgentScriptId script_id, Gum.Script script) {
public JSScriptInstance (AgentScriptId script_id, Gum.Script script) {
Object (script_id: script_id, script: script);
}
@@ -284,7 +419,7 @@ namespace Frida {
rpc_client = new RpcClient (this);
}
public async void close () {
public override async void close () {
if (close_request != null) {
try {
yield close_request.future.wait_async (null);
@@ -340,7 +475,7 @@ namespace Frida {
close_request.resolve (true);
}
public async void flush () {
public override async void flush () {
if (close_request == null)
close.begin ();
@@ -351,7 +486,7 @@ namespace Frida {
}
}
public async void load () throws Error {
public override async void load () throws Error {
if (state != CREATED)
throw new Error.INVALID_OPERATION ("Script cannot be loaded in its current state");
@@ -364,11 +499,11 @@ namespace Frida {
load_request.resolve (true);
}
public void interrupt () {
public override void interrupt () throws Error {
script.interrupt ();
}
public async void terminate () {
public override async void terminate () {
script.interrupt ();
yield close ();
@@ -399,7 +534,7 @@ namespace Frida {
return result;
}
public async void prepare_for_termination (TerminationReason reason) {
public override async void prepare_for_termination (TerminationReason reason) {
if (state == LOADED) {
schedule_on_js_thread (() => {
script.get_stalker ().flush ();
@@ -410,7 +545,7 @@ namespace Frida {
yield ensure_dispose_called (reason);
}
public void unprepare_for_termination () {
public override void unprepare_for_termination () {
if (state == DISPOSED) {
state = LOADED;
dispose_request = null;
@@ -450,7 +585,7 @@ namespace Frida {
dispose_request.resolve (true);
}
public void post (string json, Bytes? data) throws Error {
public override void post (string json, Bytes? data) throws Error {
switch (state) {
case LOADING:
case LOADED:
@@ -462,17 +597,17 @@ namespace Frida {
}
}
public void enable_debugger () throws Error {
public override void enable_debugger () throws Error {
if (_script != null)
_script.set_debug_message_handler (on_debug_message);
}
public void disable_debugger () {
public override void disable_debugger () {
if (_script != null)
_script.set_debug_message_handler (null);
}
public void post_debug_message (string message) {
public override void post_debug_message (string message) throws Error {
if (_script != null)
_script.post_debug_message (message);
}
@@ -487,7 +622,8 @@ namespace Frida {
this.debug_message (message);
}
private async void post_rpc_message (string json, Bytes? data, Cancellable? cancellable) throws Error, IOError {
public override async void post_rpc_message (string json, Bytes? data, Cancellable? cancellable)
throws Error, IOError {
if (script == null)
throw new Error.INVALID_OPERATION ("Script is destroyed");
script.post (json, data);
+6 -6
View File
@@ -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 GumMachoSectionDetails * details, FridaFillInfoContext * ctx);
static gboolean frida_fill_info (const GumDarwinSectionDetails * 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;
GumMachoModule * module;
GumDarwinModule * 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_macho_module_new_from_memory ("Frida", mach_task_self (), GPOINTER_TO_SIZE (header),
GUM_MACHO_MODULE_FLAGS_NONE, NULL);
module = gum_darwin_module_new_from_memory ("Frida", mach_task_self (), GPOINTER_TO_SIZE (header),
GUM_DARWIN_MODULE_FLAGS_NONE, NULL);
if (module == NULL)
return cached_sections;
ctx.info = cached_sections;
ctx.missing_info = 2;
gum_macho_module_enumerate_sections (module, (GumFoundMachoSectionFunc) frida_fill_info, &ctx);
gum_darwin_module_enumerate_sections (module, (GumFoundDarwinSectionFunc) frida_fill_info, &ctx);
g_object_unref (module);
@@ -102,7 +102,7 @@ frida_create_cached_sections (FridaCreateArgs * args)
}
static gboolean
frida_fill_info (const GumMachoSectionDetails * details, FridaFillInfoContext * ctx)
frida_fill_info (const GumDarwinSectionDetails * details, FridaFillInfoContext * ctx)
{
if (strcmp ("__TEXT", details->segment_name) != 0)
return TRUE;
+1 -2
View File
@@ -2,7 +2,6 @@
#include "pipe-glue.h"
#include <efni.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
@@ -67,7 +66,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));
efni_unix_socket_tune_buffer_sizes (fd);
frida_unix_socket_tune_buffer_sizes (fd);
}
status = fileport_makeport (sockets[0], &local_wrapper);
+1 -3
View File
@@ -1,5 +1,3 @@
using Efni;
namespace Frida {
public sealed class PipeTransport : Object {
public string local_address {
@@ -159,7 +157,7 @@ namespace Frida {
UnixSocketAddressType type = UnixSocketAddress.abstract_names_supported ()
? UnixSocketAddressType.ABSTRACT
: UnixSocketAddressType.PATH;
var server_address = new UnixSocketAddress.with_type (path.to_utf8 (), type);
var server_address = new UnixSocketAddress.with_type (path, -1, type);
if (role == "server") {
var socket = new Socket (SocketFamily.UNIX, SocketType.STREAM, SocketProtocol.DEFAULT);
+63 -88
View File
@@ -14,7 +14,7 @@ if not meson.get_compiler('vala').version().endswith('-frida')
Frida currently relies on features not yet upstream. Grab the Frida-optimized
Vala sources from:
https://github.com/frida-forks/vala
https://github.com/frida/vala
and compile them with Meson.''')
endif
@@ -273,15 +273,6 @@ if host_os != host_os_family
cdata.set('HAVE_' + host_os.to_upper(), 1)
endif
host_is_prospero = host_os_family == 'freebsd' and cc.get_define('__PROSPERO__') != ''
prospero_crt_link_args = []
if host_is_prospero
cdata.set('HAVE_PROSPERO', 1)
vala_flags += '--define=PROSPERO'
prospero_crt_link_args += \
run_command(cc.cmd_array(), '-print-file-name=crt1.o', check: true).stdout().strip()
endif
cpu_defines = [
['x86', 'HAVE_I386'],
['x86_64', 'HAVE_I386'],
@@ -349,13 +340,9 @@ 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',
@@ -395,6 +382,12 @@ if have_in_tree_payloads
endif
gumjs_inspector_dep = dependency('frida-gumjs-inspector-1.0', default_options: ['default_library=static'])
gumpharo_dep = dependency('frida-gumpharo-' + api_version, required: false)
have_gumpharo = gumpharo_dep.found()
if have_gumpharo
vala_flags += '--define=HAVE_GUMPHARO'
endif
gee_options = [
'default_library=static',
'disable-introspection=true',
@@ -412,27 +405,13 @@ 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',
'gssapi=disabled',
'ntlm=disabled',
'brotli=disabled',
'zstd=disabled',
'tls_check=false',
'introspection=disabled',
'vapi=disabled',
@@ -446,13 +425,7 @@ 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',
'archs=all',
'use_arch_registration=true',
'x86_att_disable=true',
'cli=disabled',
])
capstone_dep = dependency('capstone', default_options: ['default_library=static'])
if capstone_dep.type_name() != 'internal' \
and not cc.has_function('cs_arch_register_x86', dependencies: [capstone_dep])
add_project_arguments(
@@ -466,9 +439,9 @@ endif
quickjs_dep = dependency('quickjs', required: get_option('barebone_backend'), default_options: [
'default_library=static',
'libc=false',
'bignum=true',
'atomics=disabled',
'stack_check=' + ((host_os_family == 'none') ? 'enabled' : 'disabled'),
'stack_mode=' + ((host_os_family == 'none') ? 'optimize' : 'default'),
'stack_check=disabled',
])
lzfse_dep = dependency('lzfse', required: false, default_options: ['default_library=static'])
lzfse_from_system = false
@@ -504,7 +477,6 @@ endif
if host_os == 'android'
minizip_dep = dependency('minizip', required: false, default_options: [
'default_library=static',
'zlib=enabled',
'lzma=disabled',
])
@@ -598,11 +570,7 @@ if compiler_backend_opt.allowed()
have_compiler_backend = true
cdata.set('HAVE_COMPILER_BACKEND', 1)
compiler_backend_mode = lines[1]
go_config = configure_file(
input: 'tools' / 'cgo' / 'go-config.json.in',
output: 'go-config.json',
configuration: {'GO_CONFIG': lines[2]},
)
go_config = lines[2]
if compiler_backend_mode == 'c-archive'
vala_flags += '--define=COMPILER_BACKEND_LINKED'
@@ -633,9 +601,9 @@ if compiler_backend_opt.allowed()
endif
endif
elif compiler_backend_opt.enabled()
error('Need Go >= 1.26 to build the Compiler backend powered by ESBuild and typescript-go')
error('Need Go >= 1.24 to build the Compiler backend powered by ESBuild and typescript-go')
else
warning('Building without Compiler backend: Go >= 1.26 not found')
warning('Building without Compiler backend: Go >= 1.24 not found')
endif
endif
@@ -707,6 +675,9 @@ gumjs_inspector_vala_args = ['--pkg=frida-gumjs-inspector-1.0', gum_vala_args]
if have_in_tree_payloads
gumjs_vala_args = ['--pkg=frida-gumjs-1.0', '--pkg=gio-2.0', gum_vala_args]
if have_gumpharo
gumjs_vala_args += ['--pkg=frida-gumpharo-' + api_version]
endif
have_v8 = gumjs_dep.get_variable('gumjs_v8') == 'enabled'
if have_v8
@@ -726,37 +697,54 @@ if host_os_family == 'windows' and cc.get_argument_syntax() != 'msvc'
openssl_options += 'asm=disabled'
endif
openssl_dep = dependency('openssl',
required: get_option('connectivity').enabled(),
required: have_fruity_backend or 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
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
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'
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()
@@ -778,20 +766,14 @@ if host_os_family == 'linux' and have_local_backend
backend_reqs_private += 'libbpf'
endif
if host_os_family == 'darwin' \
or (host_os_family == 'freebsd' and not host_is_prospero) \
or (host_os_family == 'linux' and cc.has_function('openpty', prefix: '#include <pty.h>'))
if host_os_family in ['darwin', 'freebsd'] or (host_os_family == 'linux' and cc.has_function('openpty', prefix: '#include <pty.h>'))
vala_flags += '--define=HAVE_OPENPTY'
endif
if host_os == 'android'
termux_elf_cleaner = find_program('termux-elf-cleaner', native: true)
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)
libselinux_dep = dependency('libselinux', version: '>=3.0', default_options: ['default_library=static'])
libsepol_dep = dependency('libsepol', version: '>=3.0', default_options: ['default_library=static'])
backend_deps_private += [libselinux_dep, libsepol_dep]
backend_reqs_private += ['libselinux', 'libsepol']
else
@@ -906,19 +888,13 @@ if host_os_family == 'darwin'
endif
if host_os == 'macos'
backend_libs_private += ['-Wl,-framework,AppKit']
if have_compiler_backend
backend_libs_private += ['-Wl,-framework,CoreServices']
endif
endif
if host_os in ['ios', 'tvos']
backend_libs_private += ['-Wl,-framework,CoreGraphics', '-Wl,-framework,UIKit']
endif
if host_os_family == 'freebsd' and not host_is_prospero
if host_os_family == 'freebsd'
backend_libs_private += ['-lutil']
endif
if host_is_prospero
backend_libs_private += ['-lSceSystemService', '-lSceUserService']
endif
if get_option('devkits').length() != 0
if get_option('default_library') != 'static'
@@ -985,7 +961,7 @@ if host_os_family == 'darwin'
executable_no_exports_link_args = ['-Wl,-exported_symbols_list,' + script]
executable_no_exports_link_depends = [script]
elif host_os_family != 'windows'
script_name = (host_os == 'freebsd' and not host_is_prospero) ? 'executable-no-exports-freebsd.version' : 'executable-no-exports.version'
script_name = (host_os == 'freebsd') ? 'executable-no-exports-freebsd.version' : 'executable-no-exports.version'
script = meson.project_source_root() / 'tools' / script_name
executable_no_exports_link_args = ['-Wl,--version-script,' + script]
executable_no_exports_link_depends = [script]
@@ -995,13 +971,12 @@ compile_js_agent = files('tools' / 'compile-js-agent.py')
mapper_opt = get_option('mapper')
if mapper_opt.auto()
have_mapper = host_os_family in ['windows', 'darwin', 'linux']
have_mapper = host_os_family == 'darwin'
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: fe7d31dfbc...0ad9eef364.
-6
View File
@@ -359,11 +359,5 @@
<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 -1
View File
@@ -19,7 +19,7 @@ if host_os_family == 'darwin'
extra_link_args += ['-Wl,-exported_symbols_list,' + meson.current_source_dir() / symlist]
extra_link_depends += [symlist]
elif host_os_family != 'windows'
symscript = (host_os == 'freebsd' and not host_is_prospero) ? 'frida-server-freebsd.version' : 'frida-server.version'
symscript = (host_os == 'freebsd') ? 'frida-server-freebsd.version' : 'frida-server.version'
extra_link_args += ['-Wl,--version-script,' + meson.current_source_dir() / symscript]
extra_link_depends += [symscript]
endif
-9
View File
@@ -29,8 +29,6 @@ void CFLog (CFLogLevel level, CFStringRef format, ...);
#elif defined (HAVE_ANDROID)
# include <android/log.h>
#elif defined (HAVE_PROSPERO)
# include <ps5/klog.h>
#else
# include <stdio.h>
#endif
@@ -139,13 +137,6 @@ frida_server_on_log_message (const gchar * log_domain, GLogLevelFlags log_level,
}
__android_log_write (priority, log_domain, message);
#elif defined (HAVE_PROSPERO)
(void) user_data;
if (log_domain != NULL)
klog_printf ("%s: %s\n", log_domain, message);
else
klog_printf ("%s\n", message);
#else
FILE * file = NULL;
const gchar * severity = NULL;
-2
View File
@@ -1,5 +1,3 @@
using Efni;
namespace Frida.Server {
private static Application application;
+14 -5
View File
@@ -1,5 +1,3 @@
using Efni;
namespace Frida {
public sealed class AgentContainer : Object, AgentSessionProvider {
public DBusConnection connection {
@@ -11,7 +9,9 @@ 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
#if LINUX
private FileDescriptor agent_ctrlfd_for_peer;
#else
private PipeTransport transport;
#endif
private string transport_address;
@@ -38,7 +38,8 @@ 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.transport_address = "socket:%d".printf (agent_ctrlfds[1]);
container.agent_ctrlfd_for_peer = new FileDescriptor (agent_ctrlfds[1]);
container.transport_address = "";
try {
Socket socket = new Socket.from_fd (agent_ctrlfd.handle);
@@ -113,10 +114,18 @@ 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, null);
main_impl (agent_parameters, ref unload_policy, injector_state);
return true;
}
+2 -86
View File
@@ -1,5 +1,3 @@
using Efni;
namespace Frida {
public sealed class AndroidHelperClient : Object {
public AndroidHelperTransport transport {
@@ -7,92 +5,10 @@ namespace Frida {
construct;
}
private AdbShellSession? shell;
public AndroidHelperClient (AndroidHelperTransport transport) {
Object (transport: transport);
}
public static async AndroidHelperClient open (string serial, Cancellable? cancellable) throws Error, IOError {
string instance_id = Uuid.string_random ().replace ("-", "");
string helper_path = "/data/local/tmp/frida-helper-" + instance_id + ".dex";
var helper_dex = new MemoryInputStream.from_bytes (
new Bytes.static (Frida.Data.Android.get_helper_dex_blob ().data));
var helper_meta = new AdbFileMetadata ();
helper_meta.mode = 0100644;
helper_meta.time_modified = new DateTime.now_utc ();
yield AdbFileSync.send (helper_dex, helper_meta, helper_path, serial, cancellable);
var shell = new AdbShellSession ();
try {
var output = new StringBuilder ();
bool waiting = false;
var output_handler = shell.output.connect ((pipe, bytes) => {
if (pipe == STDOUT) {
output.append ((string) bytes.get_data ());
if (waiting)
open.callback ();
}
});
try {
yield shell.open (serial, cancellable);
shell.send_command (("CLASSPATH=%s app_process /data/local/tmp " +
"--nice-name=re.frida.helper re.frida.Helper %s; rm -f %s; echo BYE.")
.printf (helper_path, instance_id, helper_path));
while (!output.str.has_prefix ("READY.\n")) {
waiting = true;
yield;
waiting = false;
if (output.str.has_prefix ("BYE.\n"))
throw new Error.NOT_SUPPORTED ("Unable to start helper");
}
} finally {
shell.disconnect (output_handler);
}
var client = yield AdbClient.open (cancellable);
try {
yield client.request ("host:transport:" + serial, cancellable);
yield client.request_protocol_change ("localabstract:/frida-helper-" + instance_id,
cancellable);
} catch (GLib.Error e) {
client.close.begin ();
throw e;
}
var helper = new AndroidHelperClient (new AndroidHelperStreamTransport (client.stream));
helper.shell = shell;
helper.transport.closed.connect (helper.release_shell);
return helper;
} catch (GLib.Error e) {
shell.close.begin ();
if (e is IOError.CANCELLED)
throw (IOError) e;
throw new Error.NOT_SUPPORTED ("%s", e.message);
}
}
public async void close (Cancellable? cancellable) throws IOError {
yield transport.close (cancellable);
if (shell != null) {
yield shell.close (cancellable);
shell = null;
}
}
private void release_shell () {
if (shell != null) {
shell.close.begin ();
shell = null;
}
}
public async HostApplicationInfo get_frontmost_application (FrontmostQueryOptions options,
Cancellable? cancellable) throws Error, IOError {
var stanza = new Json.Builder ();
@@ -492,13 +408,13 @@ namespace Frida {
}
public interface AndroidHelperTransport : Object {
public signal void closed ();
public abstract async void close (Cancellable? cancellable) throws IOError;
public abstract async Json.Node request (Json.Node stanza, Cancellable? cancellable) throws Error, IOError;
}
public sealed class AndroidHelperStreamTransport : Object, AndroidHelperTransport {
public signal void closed ();
public IOStream stream {
get;
construct;
Binary file not shown.
+17 -39
View File
@@ -18,6 +18,7 @@ import android.graphics.drawable.Drawable;
import android.os.Looper;
import android.os.Process;
import android.util.Base64;
import android.util.Base64OutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -644,7 +645,7 @@ public class HelperBackend {
"Unable to find application with identifier '" + pkgName + "'" +
((uid != 0) ? " belonging to uid " + uid : ""));
} catch (Throwable e) {
return error("NOT_SUPPORTED", describe(e));
return error("NOT_SUPPORTED", e.toString());
}
}
@@ -696,13 +697,10 @@ public class HelperBackend {
intent.setClassName(pkgName, activity);
}
String callingPackage = ourCallingPackage();
int code = (mStartActivityAsUser != null)
? startActivityViaAtm(intent, uid, callingPackage)
: startActivityViaAm(intent, uid, callingPackage);
if (code < 0 || code > 3)
return error("NOT_SUPPORTED", "Activity launch was refused (start code " + code + ")");
if (mStartActivityAsUser != null)
startActivityViaAtm(intent, uid, pkgName);
else
startActivityViaAm(intent, uid, pkgName);
return okVoid();
} catch (NameNotFoundException e) {
@@ -710,18 +708,11 @@ public class HelperBackend {
"Unable to find application with identifier '" + pkgName + "'" +
((uid != 0) ? " belonging to uid " + uid : ""));
} catch (Throwable e) {
return error("NOT_SUPPORTED", describe(e));
return error("NOT_SUPPORTED", e.toString());
}
}
private String ourCallingPackage() {
String[] packages = mPackageManager.getPackagesForUid(Process.myUid());
if (packages != null && packages.length > 0)
return packages[0];
return "com.android.shell";
}
private int startActivityViaAtm(Intent intent, int uid, String callingPackage) throws Exception {
private void startActivityViaAtm(Intent intent, int uid, String callingPackage) throws Exception {
if (mStartActivityAsUser == null)
throw new UnsupportedOperationException("startActivityAsUser unavailable");
@@ -738,11 +729,10 @@ public class HelperBackend {
args[mStartActivityCallingPackageIndex] = callingPackage;
args[mStartActivityUserIdIndex] = (uid != 0) ? uid : 0;
Object result = mStartActivityAsUser.invoke(mActivityTaskManager, args);
return (result instanceof Integer) ? (Integer) result : 0;
mStartActivityAsUser.invoke(mActivityTaskManager, args);
}
private int startActivityViaAm(Intent intent, int uid, String callingPackage) throws Exception {
private void startActivityViaAm(Intent intent, int uid, String callingPackage) throws Exception {
if (mStartActivityLegacy == null)
throw new UnsupportedOperationException("legacy startActivity unavailable");
@@ -763,8 +753,7 @@ public class HelperBackend {
if (mLegacyStartUserIdIndex != -1)
args[mLegacyStartUserIdIndex] = (uid != 0) ? uid : 0;
Object result = mStartActivityLegacy.invoke(mActivityManagerService, args);
return (result instanceof Integer) ? (Integer) result : 0;
mStartActivityLegacy.invoke(mActivityManagerService, args);
}
private JSONArray sendBroadcast(JSONArray request) throws JSONException {
@@ -793,7 +782,7 @@ public class HelperBackend {
"Unable to find application with identifier '" + pkgName + "'" +
((uid != 0) ? " belonging to uid " + uid : ""));
} catch (Throwable e) {
return error("NOT_SUPPORTED", describe(e));
return error("NOT_SUPPORTED", e.toString());
}
}
@@ -821,7 +810,7 @@ public class HelperBackend {
"Unable to find application with identifier '" + pkgName + "'" +
((uid != 0) ? " belonging to uid " + uid : ""));
} catch (Throwable e) {
return error("NOT_SUPPORTED", describe(e));
return error("NOT_SUPPORTED", e.toString());
}
}
@@ -848,7 +837,7 @@ public class HelperBackend {
return okBoolean(false);
} catch (Throwable e) {
return error("NOT_SUPPORTED", describe(e));
return error("NOT_SUPPORTED", e.toString());
}
}
@@ -880,17 +869,6 @@ public class HelperBackend {
return r;
}
private static String describe(Throwable t) {
Throwable cause = t;
while (cause instanceof InvocationTargetException && cause.getCause() != null)
cause = cause.getCause();
String message = cause.getMessage();
return (message != null)
? cause.getClass().getName() + ": " + message
: cause.getClass().getName();
}
private String getFrontmostPackageName() {
List<ActivityManager.RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
if (tasks.isEmpty()) {
@@ -981,10 +959,10 @@ public class HelperBackend {
icon.setBounds(0, 0, width, height);
icon.draw(canvas);
ByteArrayOutputStream png = new ByteArrayOutputStream();
bitmap.compress(CompressFormat.PNG, 100, png);
ByteArrayOutputStream output = new ByteArrayOutputStream();
bitmap.compress(CompressFormat.PNG, 100, new Base64OutputStream(output, Base64.NO_WRAP));
return Base64.encodeToString(png.toByteArray(), Base64.NO_WRAP);
return output.toString();
}
private Map<String, List<RunningAppProcessInfo>> getAppProcesses() {
+11 -87
View File
@@ -21,19 +21,10 @@ CORE_TAG_FIELD = f"{{{CORE_NAMESPACE}}}field"
CORE_TAG_CONSTRUCTOR = f"{{{CORE_NAMESPACE}}}constructor"
CORE_TAG_METHOD = f"{{{CORE_NAMESPACE}}}method"
INTERNAL_INTERFACES = {
"Frida.HostSessionHub",
"Frida.AgentMessageSink",
"FridaBase.AgentMessageSink",
"Json.Serializable",
}
OBJECT_TYPE_PATTERN = re.compile(r"\bpublic\s+(sealed |abstract )?(class|interface)\s+(\w+)\b")
OBJECT_TYPE_PATTERN = re.compile(r"\bpublic\s+(sealed )?(class|interface)\s+(\w+)\b")
TOPLEVEL_NAMES = [
"frida.vala",
"linux-kernel-image.vala",
"xnu-kernelcache.vala",
"package-manager.vala",
"control-service.vala",
"portal-service.vala",
@@ -140,7 +131,6 @@ def emit_header(api, output_dir):
output_header_file.write("\n\n/* Library lifetime */")
output_header_file.write("\nvoid frida_init (void);")
output_header_file.write("\nvoid frida_init_with_runtime (FridaRuntime runtime);")
output_header_file.write("\nvoid frida_shutdown (void);")
output_header_file.write("\nvoid frida_deinit (void);")
output_header_file.write("\nGMainContext * frida_get_main_context (void);")
@@ -240,10 +230,10 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path, docs:
for elem in core_elements + base_elements:
if tag_name == "class":
for child in list(elem):
if (child.tag == CORE_TAG_IMPLEMENTS and child.get("name") in INTERNAL_INTERFACES) \
if (child.tag == CORE_TAG_IMPLEMENTS and child.get("name") in {"Frida.HostSessionHub", "Frida.AgentMessageSink",
"FridaBase.AgentMessageSink"}) \
or child.tag == CORE_TAG_FIELD \
or child.get("name").startswith("_") \
or (child.tag == CORE_TAG_CONSTRUCTOR and is_async_constructor(child)):
or child.get("name").startswith("_"):
elem.remove(child)
merged_namespace.append(elem)
@@ -251,13 +241,6 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path, docs:
merge_and_transform_elements("interface", object_type_names)
merge_and_transform_elements("enumeration", enum_type_names | error_type_names)
function_names = {func.name for func in api.functions}
for source_root in [core_root, base_root]:
source_namespace = source_root.find("namespace", GIR_NAMESPACES)
for function in source_namespace.findall("function", GIR_NAMESPACES):
if function.get("name") in function_names:
merged_namespace.append(function)
for source_root in [core_root, base_root]:
for record in source_root.findall(".//record", GIR_NAMESPACES):
owner = record.get(f"{{{GLIB_NAMESPACE}}}is-gtype-struct-for")
@@ -294,12 +277,6 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path, docs:
def filter_elements(elements: List[ET.Element], spec_set: Set[str]):
return [elem for elem in elements if elem.get("name") in spec_set]
def is_async_constructor(constructor: ET.Element) -> bool:
for type_elem in constructor.iter(f"{{{CORE_NAMESPACE}}}type"):
if type_elem.get("name") == "Gio.Cancellable":
return True
return False
@dataclass
class DocComment:
body: str
@@ -627,14 +604,10 @@ def emit_vapi(api, output_dir):
output_vapi_file.write("[CCode (cheader_filename = \"frida-core.h\", cprefix = \"Frida\", lower_case_cprefix = \"frida_\")]")
output_vapi_file.write("\nnamespace Frida {")
output_vapi_file.write("\n\tpublic static void init ();")
output_vapi_file.write("\n\tpublic static void init_with_runtime (Frida.Runtime runtime);")
output_vapi_file.write("\n\tpublic static void shutdown ();")
output_vapi_file.write("\n\tpublic static void deinit ();")
output_vapi_file.write("\n\tpublic static unowned GLib.MainContext get_main_context ();")
for delegate in referenced_delegates(api):
output_vapi_file.write("\n\t" + delegate)
for object_type in api.object_types:
output_vapi_file.write("\n\n\t%s" % object_type.vapi_declaration)
sections = []
@@ -669,35 +642,6 @@ def emit_vapi(api, output_dir):
output_deps_file.write("glib-2.0\n")
output_deps_file.write("gobject-2.0\n")
output_deps_file.write("gio-2.0\n")
output_deps_file.write("json-glib-1.0\n")
def referenced_delegates(api) -> List[str]:
members = []
for object_type in api.object_types:
members.extend(object_type.vapi_properties)
members.extend(object_type.vapi_methods)
members.extend(object_type.vapi_signals)
if object_type.vapi_constructor is not None:
members.append(object_type.vapi_constructor)
for func in api.functions:
members.append(func.vapi_declaration)
text = "\n".join(members)
return [declaration for name, declaration in api.delegates
if re.search(r"\bFrida\." + name + r"\b", text) is not None]
def strip_internal_interfaces(declaration: str) -> str:
head, sep, rest = declaration.partition(" : ")
if not sep:
return declaration
bases_part, brace_sep, tail = rest.partition("{")
kept = [b.strip() for b in bases_part.split(",") if b.strip() not in INTERNAL_INTERFACES]
if not kept:
return f"{head} {brace_sep}{tail}"
return f"{head} : {', '.join(kept)} {brace_sep}{tail}"
def emit_symbol_maps_from_source(output_dir: Path):
with OutputFile(output_dir / 'frida-core.version') as f:
@@ -856,7 +800,8 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
if stripped_line == "}":
ignoring = False
else:
if stripped_line.startswith("public class Promise") \
if stripped_line.startswith("public abstract") \
or stripped_line.startswith("public class Promise") \
or stripped_line.startswith("public interface Future") \
or stripped_line.startswith("public class CF"):
ignoring = True
@@ -876,7 +821,7 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
ignoring = True
else:
current_object_type = object_type_by_name[name]
current_object_type.vapi_declaration = strip_internal_interfaces(stripped_line)
current_object_type.vapi_declaration = stripped_line
elif stripped_line == "}":
current_enum = None
current_object_type = None
@@ -905,26 +850,17 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
enum.vapi_declaration = m.group(1)
enum.vapi_members.extend([line.lstrip() for line in m.group(2).strip().split("\n")])
functions = [f for f in parse_vapi_functions(core_vapi + base_vapi) if function_is_public(f.name)]
functions = [f for f in parse_vapi_functions(base_vapi) if function_is_public(f.name)]
for f in functions:
m = re.search(r"^(?:VALA_EXTERN )?[\w\*]+ frida_{}.+?;".format(f.name), all_headers, re.MULTILINE | re.DOTALL)
prototype = beautify_cprototype(m.group(0))
if prototype.startswith("VALA_EXTERN "):
prototype = prototype[12:]
f.c_prototype = prototype
m = re.search(r"^[\w\*]+ frida_{}.+?;".format(f.name), all_headers, re.MULTILINE | re.DOTALL)
f.c_prototype = beautify_cprototype(m.group(0))
delegates = [(m.group(1), m.group(0).strip())
for m in re.finditer(r"^\tpublic delegate .+?\b(\w+) \(.*\);$", core_vapi + base_vapi, re.MULTILINE)]
return ApiSpec(frida_version, frida_version_components, api_version, object_types, functions, enum_types, error_types,
delegates)
return ApiSpec(frida_version, frida_version_components, api_version, object_types, functions, enum_types, error_types)
def function_is_public(name):
return not name.startswith("_") and \
not name.startswith("throw_") and \
name not in MANUALLY_DECLARED_FUNCTIONS and \
name not in {
"on_pending_garbage",
"generate_certificate",
"get_dbus_context",
"invalidate_dbus_context",
@@ -945,17 +881,6 @@ def function_is_public(name):
"make_stdio_pipe",
}
MANUALLY_DECLARED_FUNCTIONS = {
"init",
"init_with_runtime",
"shutdown",
"deinit",
"get_main_context",
"unref",
"version",
"version_string",
}
def parse_vala_object_types(source) -> List[ApiObjectType]:
return [ApiObjectType(m.group(3), m.group(2)) for m in OBJECT_TYPE_PATTERN.finditer(source, re.MULTILINE)]
@@ -971,7 +896,6 @@ class ApiSpec:
functions: List[ApiFunction]
enum_types: List[ApiEnum]
error_types: List[ApiEnum]
delegates: List[tuple]
class ApiEnum:
def __init__(self, name):
+2 -9
View File
@@ -132,20 +132,13 @@ if not is_shared
endif
core_public_dep = declare_dependency(kwargs: core_public_dep_kwargs)
core_public_reqs = ['glib-2.0', 'gobject-2.0', 'gio-2.0', 'json-glib-1.0']
if host_os_family == 'windows'
core_public_reqs += 'gio-windows-2.0'
else
core_public_reqs += 'gio-unix-2.0'
endif
pkg = import('pkgconfig')
pkg_kwargs = {
'filebase': f'frida-core-@api_version@',
'name': 'Frida Core Library',
'version': frida_version,
'description': 'Instrumentation toolkit for developers, reverse-engineers, and security researchers',
'requires': core_public_reqs,
'requires': ['glib-2.0', 'gobject-2.0', 'gio-2.0', 'json-glib-1.0'],
'subdirs': f'frida-@api_version@',
'extra_cflags': ['-I${frida_includedir}'],
'variables': installed_variables,
@@ -158,7 +151,7 @@ if is_shared
else
pkg_kwargs += {
'libraries': core_public_lib,
'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,
'requires_private': ['gmodule-2.0', 'gee-0.8', 'libsoup-3.0', 'frida-gum-1.0', 'frida-gumjs-inspector-1.0', 'libbrotlidec'] + backend_reqs_private,
'libraries_private': backend_libs_private,
}
endif
File diff suppressed because it is too large. Load diff
+5 -7
View File
@@ -9,21 +9,19 @@ 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.
xnu-core = []
xnu = ["xnu-core", "blob"]
xnu-kext = ["xnu-core"]
xnu = ["blob"]
win9x = ["blob"]
winnt = ["blob"]
linux-injected = ["blob"]
linux = []
[profile.release]
panic = "abort"
+21 -47
View File
@@ -1,72 +1,46 @@
## Getting the SDK and the devkit
## Getting the C toolchain
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 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:
npm install -g xpm
xpm install
export PATH=$PWD/xpacks/.bin:$PATH
machine=none-arm64-softfloat_pic
## How to build Gum
sdk=~/sdk-$machine
releng/deps.py sync sdk $machine $sdk
gum=~/src/frida-gum
mkdir -p $gum/build/$machine
(cd $gum/build/$machine \
&& CC=clang CXX=clang++ ../../configure --host=$machine --enable-gumjs \
--with-devkits=gumjs --with-devkit-symbol-scope=original \
&& make)
./configure \
--host=aarch64-none-elf \
--enable-gumjs \
--with-devkits=gum,gumjs \
--with-devkit-symbol-scope=original
make
export GUMJS_DEVKIT_DIR=$PWD/build/bindings/gumjs/devkit
## Building
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"
export PATH=$PWD/xpacks/.bin:$PATH
export CC_aarch64_unknown_none=aarch64-none-elf-gcc
export AR_aarch64_unknown_none=aarch64-none-elf-ar
export RANLIB_aarch64_unknown_none=aarch64-none-elf-ranlib
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
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.
cargo build --release --features xnu
## Targets
The agent supports four kernels. One of them has to be picked; none is a default,
since none follows from the host you build on:
The agent supports two kernels. One of them has to be picked; neither is a
default, since neither follows from the host you build on:
- `xnu` — a freestanding ELF executable that the Barebone backend
injects into a running kernel from the outside, over a remote stub.
- `win9x` — the same, for a 32-bit Windows 9x guest.
- `winnt` — the same, for a Windows NT guest, either word size, on x86 or arm64.
- `linux` — a static library that becomes a kernel module, loaded from the
inside. See `linux/README.md`.
Each flavor has an SDK and a devkit of its own, since the addressing differs: an
image the host places goes in position-independent, a Linux module is absolute,
and NT's 64-bit half is called in the Microsoft ABI.
| Flavor | Machine | Rust target |
|---------|-----------------------------------|----------------------------------|
| `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` |
| `winnt` | `none-arm64-softfloat_pic` | `aarch64-unknown-none-pic.json` |
| `linux` | `none-<arch>-softfloat_nopic` | see `linux/README.md` |
Everything above the `kernel` module is shared; each backend supplies the same
set of primitives (logging, allocation, threads, waiting, time, symbols) plus its
half of Gum's platform backend (`gum_xnu.rs` / `gum_windows.rs` / `gum_linux.rs`).
half of Gum's platform backend (`gum_xnu.rs` / `gum_linux.rs`).
## Development loop
export FRIDA_BAREBONE_CONFIG=$PWD/etc/xnu.json
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 \
cargo build --release --features xnu \
&& make -C ~/src/frida-python \
&& killall -9 qemu-system-aarch64 && sleep 2 \
&& frida -D barebone -p 0
@@ -1,21 +0,0 @@
{
"abi": "softfloat",
"arch": "aarch64",
"crt-objects-fallback": "false",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32",
"disable-redzone": true,
"features": "+v8a,+strict-align,-neon,+reserve-x18",
"linker": "rust-lld",
"linker-flavor": "gnu-lld",
"llvm-target": "aarch64-unknown-none",
"max-atomic-width": 128,
"panic-strategy": "abort",
"plt-by-default": false,
"position-independent-executables": true,
"relocation-model": "pic",
"rustc-abi": "softfloat",
"stack-probes": {
"kind": "inline"
},
"target-pointer-width": 64
}
-164
View File
@@ -1,164 +0,0 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rel.dyn : {
_agent_relocs_start = .;
*(.rel.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(4) {
. = ALIGN(4);
__printk = .; . += 4;
_printk = .; . += 4;
_panic = .; . += 4;
_kthread_create_on_node = .; . += 4;
_wake_up_process = .; . += 4;
_fget = .; . += 4;
_kernel_read = .; . += 4;
_filp_open = .; . += 4;
_kernel_write = .; . += 4;
___kmalloc_noprof = .; . += 4;
_kmalloc = .; . += 4;
___kmalloc = .; . += 4;
_kfree = .; . += 4;
_vmalloc = .; . += 4;
_execmem_alloc = .; . += 4;
_set_memory_rw = .; . += 4;
_set_memory_ro = .; . += 4;
_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;
___init_waitqueue_head = .; . += 4;
_prepare_to_wait_event = .; . += 4;
_finish_wait = .; . += 4;
___wake_up = .; . += 4;
_autoremove_wake_function = .; . += 4;
_schedule_hrtimeout_range = .; . += 4;
_pci_get_domain_bus_and_slot = .; . += 4;
_pci_irq_vector = .; . += 4;
_of_irq_parse_and_map_pci = .; . += 4;
_pci_dev_put = .; . += 4;
_schedule = .; . += 4;
_ktime_get_mono_fast_ns = .; . += 4;
_ktime_get_real_ts64 = .; . += 4;
_getnstimeofday64 = .; . += 4;
_send_sig = .; . += 4;
_free_irq = .; . += 4;
_request_threaded_irq = .; . += 4;
_vmap = .; . += 4;
___pv_offset = .; . += 4;
_pgprot_kernel = .; . += 4;
_ioremap = .; . += 4;
_of_find_node_opts_by_path = .; . += 4;
_of_irq_get = .; . += 4;
_of_node_put = .; . += 4;
_init_task = .; . += 4;
_tasklist_lock = .; . += 4;
__raw_read_lock = .; . += 4;
__raw_read_unlock = .; . += 4;
__raw_read_lock_irqsave = .; . += 4;
__raw_read_unlock_irqrestore = .; . += 4;
__raw_write_lock = .; . += 4;
__raw_write_unlock = .; . += 4;
__raw_write_lock_irqsave = .; . += 4;
__raw_write_unlock_irqrestore = .; . += 4;
_detach_pid = .; . += 4;
_free_pids = .; . += 4;
_get_task_exe_file = .; . += 4;
_file_path = .; . += 4;
_fput = .; . += 4;
_copy_from_kernel_nofault = .; . += 4;
_get_task_mm = .; . += 4;
_kthread_use_mm = .; . += 4;
_kthread_unuse_mm = .; . += 4;
_mmput = .; . += 4;
_vm_mmap = .; . += 4;
_vm_munmap = .; . += 4;
_tracepoint_probe_register = .; . += 4;
_tracepoint_probe_unregister = .; . += 4;
___tracepoint_sched_process_fork = .; . += 4;
___tracepoint_sched_process_exit = .; . += 4;
___tracepoint_sched_process_exec = .; . += 4;
___tracepoint_task_rename = .; . += 4;
___set_task_comm = .; . += 4;
_vunmap = .; . += 4;
_arm_copy_to_user = .; . += 4;
_down_read = .; . += 4;
_up_read = .; . += 4;
_get_user_pages_unlocked = .; . += 4;
_get_user_pages_remote = .; . += 4;
_user_mode_thread = .; . += 4;
_fget_task = .; . += 4;
_find_task_by_vpid = .; . += 4;
___start_BTF = .; . += 4;
___stop_BTF = .; . += 4;
_call_usermodehelper_setup = .; . += 4;
_call_usermodehelper_exec = .; . += 4;
_arm_copy_from_user = .; . += 4;
_fixup_exception = .; . += 4;
_init_net = .; . += 4;
_sock_create_kern = .; . += 4;
_kernel_connect = .; . += 4;
_kernel_sendmsg = .; . += 4;
_kernel_recvmsg = .; . += 4;
_sock_release = .; . += 4;
_patch_text = .; . += 4;
}
/* Everything above is only ever read, so a process being instrumented can be shown the very
same pages; everything below it each copy needs its own of. */
. = ALIGN(4096);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
}
. = ALIGN(4);
_heap_start = .;
}
VERSION {
{
global:
_start;
local:
*;
};
}
-196
View File
@@ -1,196 +0,0 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rela.dyn : {
_agent_relocs_start = .;
*(.rela.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(8) {
. = ALIGN(8);
__printk = .; . += 8;
_printk = .; . += 8;
_panic = .; . += 8;
_kthread_create_on_node = .; . += 8;
_wake_up_process = .; . += 8;
_fget = .; . += 8;
_kernel_read = .; . += 8;
_filp_open = .; . += 8;
_kernel_write = .; . += 8;
___kmalloc_noprof = .; . += 8;
_kmalloc = .; . += 8;
___kmalloc = .; . += 8;
_kfree = .; . += 8;
_vmalloc = .; . += 8;
_execmem_alloc = .; . += 8;
_set_memory_rw = .; . += 8;
_set_memory_ro = .; . += 8;
_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;
___init_waitqueue_head = .; . += 8;
_prepare_to_wait_event = .; . += 8;
_finish_wait = .; . += 8;
___wake_up = .; . += 8;
_autoremove_wake_function = .; . += 8;
_schedule_hrtimeout_range = .; . += 8;
_pci_get_domain_bus_and_slot = .; . += 8;
_pci_irq_vector = .; . += 8;
_of_irq_parse_and_map_pci = .; . += 8;
_pci_dev_put = .; . += 8;
_schedule = .; . += 8;
_ktime_get_mono_fast_ns = .; . += 8;
_ktime_get_real_ts64 = .; . += 8;
_getnstimeofday64 = .; . += 8;
_send_sig = .; . += 8;
_free_irq = .; . += 8;
_request_threaded_irq = .; . += 8;
_generic_ioremap_prot = .; . += 8;
___ioremap = .; . += 8;
_vmap = .; . += 8;
_memstart_addr = .; . += 8;
_init_task = .; . += 8;
_tasklist_lock = .; . += 8;
__raw_read_lock = .; . += 8;
__raw_read_unlock = .; . += 8;
__raw_read_lock_irqsave = .; . += 8;
__raw_read_unlock_irqrestore = .; . += 8;
__raw_write_lock = .; . += 8;
__raw_write_unlock = .; . += 8;
__raw_write_lock_irqsave = .; . += 8;
__raw_write_unlock_irqrestore = .; . += 8;
_detach_pid = .; . += 8;
_free_pids = .; . += 8;
_get_task_exe_file = .; . += 8;
_file_path = .; . += 8;
_fput = .; . += 8;
_copy_from_kernel_nofault = .; . += 8;
_get_task_mm = .; . += 8;
_kthread_use_mm = .; . += 8;
_kthread_unuse_mm = .; . += 8;
_mmput = .; . += 8;
_vm_mmap = .; . += 8;
_vm_munmap = .; . += 8;
_tracepoint_probe_register = .; . += 8;
_tracepoint_probe_unregister = .; . += 8;
___tracepoint_sched_process_fork = .; . += 8;
___tracepoint_sched_process_exit = .; . += 8;
___tracepoint_sched_process_exec = .; . += 8;
___tracepoint_task_rename = .; . += 8;
___set_task_comm = .; . += 8;
_vunmap = .; . += 8;
___arch_copy_to_user = .; . += 8;
_down_read = .; . += 8;
_up_read = .; . += 8;
_get_user_pages_unlocked = .; . += 8;
_get_user_pages_remote = .; . += 8;
___arch_copy_from_user = .; . += 8;
_do_futex = .; . += 8;
_user_mode_thread = .; . += 8;
_apply_to_existing_page_range = .; . += 8;
_caches_clean_inval_pou = .; . += 8;
_do_madvise = .; . += 8;
_fget_task = .; . += 8;
_find_task_by_vpid = .; . += 8;
___start_BTF = .; . += 8;
___stop_BTF = .; . += 8;
_call_usermodehelper_setup = .; . += 8;
_call_usermodehelper_exec = .; . += 8;
_access_process_vm = .; . += 8;
_fixup_exception = .; . += 8;
_aarch64_insn_patch_text_nosync = .; . += 8;
_aarch64_insn_patch_text = .; . += 8;
_kthread_worker_fn = .; . += 8;
_vring_interrupt = .; . += 8;
_init_net = .; . += 8;
_sock_create_kern = .; . += 8;
_kernel_connect = .; . += 8;
_kernel_sendmsg = .; . += 8;
_kernel_recvmsg = .; . += 8;
_sock_release = .; . += 8;
_sock_def_readable = .; . += 8;
}
/* Everything above is only ever read, so a process being instrumented can be shown the very
same pages; everything below it each copy needs its own of. */
. = ALIGN(4096);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
}
. = ALIGN(8);
_heap_start = .;
}
VERSION {
{
global:
_start;
local:
*;
};
}
-161
View File
@@ -1,161 +0,0 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rel.dyn : {
_agent_relocs_start = .;
*(.rel.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(4) {
. = ALIGN(4);
__printk = .; . += 4;
_printk = .; . += 4;
_panic = .; . += 4;
_kthread_create_on_node = .; . += 4;
_wake_up_process = .; . += 4;
_fget = .; . += 4;
_kernel_read = .; . += 4;
_filp_open = .; . += 4;
_kernel_write = .; . += 4;
___kmalloc_noprof = .; . += 4;
_kmalloc = .; . += 4;
_kfree = .; . += 4;
_vmalloc = .; . += 4;
_execmem_alloc = .; . += 4;
_set_memory_rw = .; . += 4;
_set_memory_ro = .; . += 4;
_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;
_module_memfree = .; . += 4;
___init_waitqueue_head = .; . += 4;
_prepare_to_wait_event = .; . += 4;
_finish_wait = .; . += 4;
___wake_up = .; . += 4;
_autoremove_wake_function = .; . += 4;
_schedule_hrtimeout_range = .; . += 4;
_pci_get_domain_bus_and_slot = .; . += 4;
_pci_irq_vector = .; . += 4;
_pci_dev_put = .; . += 4;
_schedule = .; . += 4;
_ktime_get_mono_fast_ns = .; . += 4;
_ktime_get_real_ts64 = .; . += 4;
_send_sig = .; . += 4;
_free_irq = .; . += 4;
_request_threaded_irq = .; . += 4;
_vmap = .; . += 4;
_current_task = .; . += 4;
_pcpu_hot = .; . += 4;
_ioremap = .; . += 4;
_ioremap_nocache = .; . += 4;
___default_kernel_pte_mask = .; . += 4;
_init_task = .; . += 4;
_tasklist_lock = .; . += 4;
__raw_read_lock = .; . += 4;
__raw_read_unlock = .; . += 4;
__raw_read_lock_irqsave = .; . += 4;
__raw_read_unlock_irqrestore = .; . += 4;
__raw_write_lock = .; . += 4;
__raw_write_unlock = .; . += 4;
__raw_write_lock_irqsave = .; . += 4;
__raw_write_unlock_irqrestore = .; . += 4;
_detach_pid = .; . += 4;
_free_pids = .; . += 4;
_get_task_exe_file = .; . += 4;
_file_path = .; . += 4;
_fput = .; . += 4;
_copy_from_kernel_nofault = .; . += 4;
_get_task_mm = .; . += 4;
_kthread_use_mm = .; . += 4;
_kthread_unuse_mm = .; . += 4;
_mmput = .; . += 4;
_vm_mmap = .; . += 4;
_vm_munmap = .; . += 4;
_tracepoint_probe_register = .; . += 4;
_tracepoint_probe_unregister = .; . += 4;
___tracepoint_sched_process_fork = .; . += 4;
___tracepoint_sched_process_exit = .; . += 4;
___tracepoint_sched_process_exec = .; . += 4;
_vunmap = .; . += 4;
__copy_to_user = .; . += 4;
_down_read = .; . += 4;
_up_read = .; . += 4;
_get_user_pages_unlocked = .; . += 4;
_get_user_pages_remote = .; . += 4;
_do_futex = .; . += 4;
_user_mode_thread = .; . += 4;
___start_BTF = .; . += 4;
___stop_BTF = .; . += 4;
_call_usermodehelper_setup = .; . += 4;
_call_usermodehelper_exec = .; . += 4;
__copy_from_user = .; . += 4;
_access_process_vm = .; . += 4;
_fget_task = .; . += 4;
_find_task_by_vpid = .; . += 4;
_init_net = .; . += 4;
_sock_create_kern = .; . += 4;
_kernel_connect = .; . += 4;
_kernel_sendmsg = .; . += 4;
_kernel_recvmsg = .; . += 4;
_sock_release = .; . += 4;
_register_die_notifier = .; . += 4;
_unregister_die_notifier = .; . += 4;
}
/* Everything above is only ever read, so a process being instrumented can be shown the very
same pages; everything below it each copy needs its own of. */
. = ALIGN(4096);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
}
. = ALIGN(4);
_heap_start = .;
}
VERSION {
{
global:
_start;
local:
*;
};
}
-193
View File
@@ -1,193 +0,0 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rela.dyn : {
_agent_relocs_start = .;
*(.rela.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(8) {
. = ALIGN(8);
__printk = .; . += 8;
_printk = .; . += 8;
_panic = .; . += 8;
_kthread_create_on_node = .; . += 8;
_wake_up_process = .; . += 8;
_fget = .; . += 8;
_kernel_read = .; . += 8;
_filp_open = .; . += 8;
_kernel_write = .; . += 8;
___kmalloc_noprof = .; . += 8;
_kmalloc = .; . += 8;
___kmalloc = .; . += 8;
_kfree = .; . += 8;
_vmalloc = .; . += 8;
_execmem_alloc = .; . += 8;
_set_memory_rw = .; . += 8;
_set_memory_ro = .; . += 8;
_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;
_module_memfree = .; . += 8;
___init_waitqueue_head = .; . += 8;
_prepare_to_wait_event = .; . += 8;
_finish_wait = .; . += 8;
___wake_up = .; . += 8;
_autoremove_wake_function = .; . += 8;
_schedule_hrtimeout_range = .; . += 8;
_pci_get_domain_bus_and_slot = .; . += 8;
_pci_irq_vector = .; . += 8;
_pci_dev_put = .; . += 8;
_schedule = .; . += 8;
_ktime_get_mono_fast_ns = .; . += 8;
_ktime_get_real_ts64 = .; . += 8;
_getnstimeofday64 = .; . += 8;
_send_sig = .; . += 8;
_free_irq = .; . += 8;
_request_threaded_irq = .; . += 8;
_vmap = .; . += 8;
_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;
_ioremap_nocache = .; . += 8;
___default_kernel_pte_mask = .; . += 8;
_init_task = .; . += 8;
_tasklist_lock = .; . += 8;
__raw_read_lock = .; . += 8;
__raw_read_unlock = .; . += 8;
__raw_read_lock_irqsave = .; . += 8;
__raw_read_unlock_irqrestore = .; . += 8;
__raw_write_lock = .; . += 8;
__raw_write_unlock = .; . += 8;
__raw_write_lock_irqsave = .; . += 8;
__raw_write_unlock_irqrestore = .; . += 8;
_detach_pid = .; . += 8;
_free_pids = .; . += 8;
_get_task_exe_file = .; . += 8;
_file_path = .; . += 8;
_fput = .; . += 8;
_copy_from_kernel_nofault = .; . += 8;
_get_task_mm = .; . += 8;
_kthread_use_mm = .; . += 8;
_kthread_unuse_mm = .; . += 8;
_mmput = .; . += 8;
_vm_mmap = .; . += 8;
_vm_munmap = .; . += 8;
_tracepoint_probe_register = .; . += 8;
_tracepoint_probe_unregister = .; . += 8;
___tracepoint_sched_process_fork = .; . += 8;
___tracepoint_sched_process_exit = .; . += 8;
___tracepoint_sched_process_exec = .; . += 8;
___tracepoint_task_rename = .; . += 8;
___set_task_comm = .; . += 8;
_vunmap = .; . += 8;
__copy_to_user = .; . += 8;
_down_read = .; . += 8;
_up_read = .; . += 8;
_get_user_pages_unlocked = .; . += 8;
_get_user_pages_remote = .; . += 8;
_do_futex = .; . += 8;
_user_mode_thread = .; . += 8;
___start_BTF = .; . += 8;
___stop_BTF = .; . += 8;
_call_usermodehelper_setup = .; . += 8;
_call_usermodehelper_exec = .; . += 8;
__copy_from_user = .; . += 8;
_access_process_vm = .; . += 8;
_fget_task = .; . += 8;
_find_task_by_vpid = .; . += 8;
_init_net = .; . += 8;
_sock_create_kern = .; . += 8;
_kernel_connect = .; . += 8;
_kernel_sendmsg = .; . += 8;
_kernel_recvmsg = .; . += 8;
_sock_release = .; . += 8;
_sock_def_readable = .; . += 8;
_of_irq_parse_and_map_pci = .; . += 8;
}
/* Everything above is only ever read, so a process being instrumented can be shown the very
same pages; everything below it each copy needs its own of. */
. = ALIGN(4096);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
}
. = ALIGN(8);
_heap_start = .;
}
VERSION {
{
global:
_start;
local:
*;
};
}
@@ -3,11 +3,6 @@ SECTIONS {
*(.text.entrypoint)
*(.text*)
}
.rel.dyn : {
_agent_relocs_start = .;
*(.rel.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
@@ -30,19 +25,10 @@ SECTIONS {
__PageFree = .; . += 4;
__CopyPageTable = .; . += 4;
__MapPhysToLinear = .; . += 4;
_VMM_Add_DDB = .; . += 4;
_VMM_Remove_DDB = .; . += 4;
__RegOpenKey = .; . += 4;
__RegCloseKey = .; . += 4;
__RegEnumKey = .; . += 4;
__RegQueryValueEx = .; . += 4;
__PageReserve = .; . += 4;
__PageCommit = .; . += 4;
_Hook_VMM_Fault = .; . += 4;
_Unhook_VMM_Fault = .; . += 4;
_Fatal_Error_Handler = .; . += 4;
_VMMCreateThread = .; . += 4;
_Get_Cur_Thread_Handle = .; . += 4;
_Get_Initial_Thread_Handle = .; . += 4;
_Get_Next_Thread_Handle = .; . += 4;
__Debug_Printf_Service = .; . += 4;
@@ -50,34 +36,15 @@ SECTIONS {
__PageModifyPermissions = .; . += 4;
__GetCurrentContext = .; . += 4;
_VPICD_Virtualize_IRQ = .; . += 4;
_VPICD_Force_Default_Behavior = .; . += 4;
_VPICD_Phys_EOI = .; . += 4;
_VPICD_Physically_Unmask = .; . += 4;
__VWIN32_QueueUserApc = .; . += 4;
__VWIN32_Get_Thread_Context = .; . += 4;
__VWIN32_Set_Thread_Context = .; . += 4;
__VWIN32_CreateRing0Thread = .; . += 4;
_IFSMgr_Ring0_FileIO = .; . += 4;
_KERNEL32_Base = .; . += 4;
_KERNEL32_ProcessIdObfuscator = .; . += 4;
_KERNEL32_ModuleTable = .; . += 4;
__PageCommitPhys = .; . += 4;
}
/* Everything above is only ever read, so a process being instrumented can be shown the very
same pages; everything below it each copy needs its own of. */
. = ALIGN(4096);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.bss : {
*(.bss*)
}
@@ -89,7 +56,6 @@ VERSION {
{
global:
_start;
frida_win9x_user_main;
local:
*;
-112
View File
@@ -1,112 +0,0 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rela.dyn : {
_agent_relocs_start = .;
*(.rela.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(8) {
. = ALIGN(8);
_ExAllocatePoolWithTag = .; . += 8;
_ExFreePoolWithTag = .; . += 8;
_PsCreateSystemThread = .; . += 8;
_PsGetCurrentThreadId = .; . += 8;
_KeInitializeEvent = .; . += 8;
_KeWaitForSingleObject = .; . += 8;
_KeSetEvent = .; . += 8;
_KeInitializeDpc = .; . += 8;
_PsSetCreateThreadNotifyRoutine = .; . += 8;
_PsRemoveCreateThreadNotifyRoutine = .; . += 8;
_KeInsertQueueDpc = .; . += 8;
_ZwYieldExecution = .; . += 8;
_KeExpandKernelStackAndCalloutEx = .; . += 8;
_KeBugCheckEx = .; . += 8;
_DbgPrint = .; . += 8;
_MmGetVirtualForPhysical = .; . += 8;
_MmMapIoSpace = .; . += 8;
_MmGetPhysicalAddress = .; . += 8;
_HalGetInterruptVector = .; . += 8;
_IoConnectInterrupt = .; . += 8;
_IoDisconnectInterrupt = .; . += 8;
_PsActiveProcessHead = .; . += 8;
_PsInitialSystemProcess = .; . += 8;
_PsGetProcessId = .; . += 8;
_PsGetProcessImageFileName = .; . += 8;
_PsGetProcessPeb = .; . += 8;
_PsGetCurrentThread = .; . += 8;
_PsGetThreadProcess = .; . += 8;
_PsGetThreadId = .; . += 8;
_PsGetThreadTeb = .; . += 8;
_PsGetContextThread = .; . += 8;
_PsSetContextThread = .; . += 8;
_KeStackAttachProcess = .; . += 8;
_KeUnstackDetachProcess = .; . += 8;
_ZwCreateFile = .; . += 8;
_ZwOpenKey = .; . += 8;
_ZwEnumerateKey = .; . += 8;
_ZwQueryValueKey = .; . += 8;
_ZwReadFile = .; . += 8;
_ZwWriteFile = .; . += 8;
_ZwEnumerateValueKey = .; . += 8;
_ZwDeviceIoControlFile = .; . += 8;
_ZwClose = .; . += 8;
_ZwCreateEvent = .; . += 8;
_ExAllocatePool2 = .; . += 8;
_MmCopyVirtualMemory = .; . += 8;
_KeIpiGenericCall = .; . += 8;
_MmIsAddressValid = .; . += 8;
_ZwClearEvent = .; . += 8;
_ZwWaitForSingleObject = .; . += 8;
_ZwAllocateVirtualMemory = .; . += 8;
_ZwFreeVirtualMemory = .; . += 8;
_ZwCreateThread = .; . += 8;
_PsProcessType = .; . += 8;
_ObReferenceObjectByHandle = .; . += 8;
_ObOpenObjectByPointer = .; . += 8;
_ExEventObjectType = .; . += 8;
_IoAllocateMdl = .; . += 8;
_IoFreeMdl = .; . += 8;
_MmBuildMdlForNonPagedPool = .; . += 8;
_MmMapLockedPagesSpecifyCache = .; . += 8;
_MmUnmapLockedPages = .; . += 8;
}
. = ALIGN(4096);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
}
. = ALIGN(8);
_heap_start = .;
}
VERSION {
{
global:
_start;
frida_winnt_user_main;
frida_winnt_user_bootstrap;
local:
*;
};
}
-111
View File
@@ -1,111 +0,0 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rel.dyn : {
_agent_relocs_start = .;
*(.rel.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(4) {
. = ALIGN(4);
_ExAllocatePoolWithTag = .; . += 4;
_ExFreePoolWithTag = .; . += 4;
_PsCreateSystemThread = .; . += 4;
_PsGetCurrentThreadId = .; . += 4;
_KeInitializeEvent = .; . += 4;
_KeWaitForSingleObject = .; . += 4;
_KeSetEvent = .; . += 4;
_KeInitializeDpc = .; . += 4;
_PsSetCreateThreadNotifyRoutine = .; . += 4;
_PsRemoveCreateThreadNotifyRoutine = .; . += 4;
_KeInsertQueueDpc = .; . += 4;
_ZwYieldExecution = .; . += 4;
_KeExpandKernelStackAndCalloutEx = .; . += 4;
_KeBugCheckEx = .; . += 4;
_DbgPrint = .; . += 4;
_MmGetVirtualForPhysical = .; . += 4;
_MmMapIoSpace = .; . += 4;
_MmGetPhysicalAddress = .; . += 4;
_HalGetInterruptVector = .; . += 4;
_IoConnectInterrupt = .; . += 4;
_IoDisconnectInterrupt = .; . += 4;
_PsActiveProcessHead = .; . += 4;
_PsInitialSystemProcess = .; . += 4;
_PsGetProcessId = .; . += 4;
_PsGetProcessImageFileName = .; . += 4;
_PsGetProcessPeb = .; . += 4;
_PsGetCurrentThread = .; . += 4;
_PsGetThreadProcess = .; . += 4;
_PsGetThreadId = .; . += 4;
_PsGetThreadTeb = .; . += 4;
_PsGetContextThread = .; . += 4;
_PsSetContextThread = .; . += 4;
_KeStackAttachProcess = .; . += 4;
_KeUnstackDetachProcess = .; . += 4;
_ZwCreateFile = .; . += 4;
_ZwOpenKey = .; . += 4;
_ZwEnumerateKey = .; . += 4;
_ZwQueryValueKey = .; . += 4;
_ZwReadFile = .; . += 4;
_ZwWriteFile = .; . += 4;
_ZwEnumerateValueKey = .; . += 4;
_ZwDeviceIoControlFile = .; . += 4;
_ZwClose = .; . += 4;
_ZwCreateEvent = .; . += 4;
_ExAllocatePool2 = .; . += 4;
_MmCopyVirtualMemory = .; . += 4;
_KeIpiGenericCall = .; . += 4;
_MmIsAddressValid = .; . += 4;
_ZwClearEvent = .; . += 4;
_ZwWaitForSingleObject = .; . += 4;
_ZwAllocateVirtualMemory = .; . += 4;
_ZwFreeVirtualMemory = .; . += 4;
_ZwCreateThread = .; . += 4;
_PsProcessType = .; . += 4;
_ObReferenceObjectByHandle = .; . += 4;
_ObOpenObjectByPointer = .; . += 4;
_ExEventObjectType = .; . += 4;
_IoAllocateMdl = .; . += 4;
_IoFreeMdl = .; . += 4;
_MmBuildMdlForNonPagedPool = .; . += 4;
_MmMapLockedPagesSpecifyCache = .; . += 4;
_MmUnmapLockedPages = .; . += 4;
}
/* Everything above is only ever read, so a process being instrumented can be shown the very
same pages; everything below it each copy needs its own of. */
. = ALIGN(4096);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.bss : {
*(.bss*)
}
. = ALIGN(4);
_heap_start = .;
}
VERSION {
{
global:
_start;
frida_winnt_user_main;
frida_winnt_user_bootstrap;
local:
*;
};
}
-121
View File
@@ -1,121 +0,0 @@
SECTIONS {
/* The large code model gives every section an l-prefixed name of its own, so that
the compiler can reach it with a 64-bit displacement rather than assume 2GB. */
.text : {
*(.text.entrypoint)
*(.ltext.entrypoint)
*(.text*)
*(.ltext*)
}
.rela.dyn : {
_agent_relocs_start = .;
*(.rela.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
*(.lrodata*)
}
.kernel_addrs : ALIGN(8) {
. = ALIGN(8);
_ExAllocatePoolWithTag = .; . += 8;
_ExFreePoolWithTag = .; . += 8;
_PsCreateSystemThread = .; . += 8;
_PsGetCurrentThreadId = .; . += 8;
_KeInitializeEvent = .; . += 8;
_KeWaitForSingleObject = .; . += 8;
_KeSetEvent = .; . += 8;
_KeInitializeDpc = .; . += 8;
_PsSetCreateThreadNotifyRoutine = .; . += 8;
_PsRemoveCreateThreadNotifyRoutine = .; . += 8;
_KeInsertQueueDpc = .; . += 8;
_ZwYieldExecution = .; . += 8;
_KeExpandKernelStackAndCalloutEx = .; . += 8;
_KeBugCheckEx = .; . += 8;
_DbgPrint = .; . += 8;
_MmGetVirtualForPhysical = .; . += 8;
_MmMapIoSpace = .; . += 8;
_MmGetPhysicalAddress = .; . += 8;
_HalGetInterruptVector = .; . += 8;
_IoConnectInterrupt = .; . += 8;
_IoDisconnectInterrupt = .; . += 8;
_PsActiveProcessHead = .; . += 8;
_PsInitialSystemProcess = .; . += 8;
_PsGetProcessId = .; . += 8;
_PsGetProcessImageFileName = .; . += 8;
_PsGetProcessPeb = .; . += 8;
_PsGetCurrentThread = .; . += 8;
_PsGetThreadProcess = .; . += 8;
_PsGetThreadId = .; . += 8;
_PsGetThreadTeb = .; . += 8;
_PsGetContextThread = .; . += 8;
_PsSetContextThread = .; . += 8;
_KeStackAttachProcess = .; . += 8;
_KeUnstackDetachProcess = .; . += 8;
_ZwCreateFile = .; . += 8;
_ZwOpenKey = .; . += 8;
_ZwEnumerateKey = .; . += 8;
_ZwQueryValueKey = .; . += 8;
_ZwReadFile = .; . += 8;
_ZwWriteFile = .; . += 8;
_ZwEnumerateValueKey = .; . += 8;
_ZwDeviceIoControlFile = .; . += 8;
_ZwClose = .; . += 8;
_ZwCreateEvent = .; . += 8;
_ExAllocatePool2 = .; . += 8;
_MmCopyVirtualMemory = .; . += 8;
_KeIpiGenericCall = .; . += 8;
_MmIsAddressValid = .; . += 8;
_ZwClearEvent = .; . += 8;
_ZwWaitForSingleObject = .; . += 8;
_ZwAllocateVirtualMemory = .; . += 8;
_ZwFreeVirtualMemory = .; . += 8;
_ZwCreateThread = .; . += 8;
_PsProcessType = .; . += 8;
_ObReferenceObjectByHandle = .; . += 8;
_ObOpenObjectByPointer = .; . += 8;
_ExEventObjectType = .; . += 8;
_IoAllocateMdl = .; . += 8;
_IoFreeMdl = .; . += 8;
_MmBuildMdlForNonPagedPool = .; . += 8;
_MmMapLockedPagesSpecifyCache = .; . += 8;
_MmUnmapLockedPages = .; . += 8;
}
/* Everything above is only ever read, so a process being instrumented can be shown the very
same pages; everything below it each copy needs its own of. */
. = ALIGN(4096);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.data : {
*(.data*)
*(.ldata*)
}
.bss : {
*(.bss*)
*(.lbss*)
}
. = ALIGN(8);
_heap_start = .;
}
VERSION {
{
global:
_start;
frida_winnt_user_main;
frida_winnt_user_bootstrap;
local:
*;
};
}
-112
View File
@@ -1,112 +0,0 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rela.dyn : {
_agent_relocs_start = .;
*(.rela.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(8) {
. = ALIGN(8);
_panic = .; . += 8;
_IOLog = .; . += 8;
_kalloc = .; . += 8;
_proc_iterate = .; . += 8;
_proc_find = .; . += 8;
_proc_rele = .; . += 8;
_proc_pid = .; . += 8;
_proc_best_name = .; . += 8;
_proc_task = .; . += 8;
_current_task = .; . += 8;
_current_thread = .; . += 8;
_get_task_map = .; . += 8;
_mach_vm_allocate = .; . += 8;
_mach_vm_deallocate = .; . += 8;
_mach_vm_protect = .; . += 8;
_mach_vm_remap = .; . += 8;
_vm_map_copyin = .; . += 8;
_vm_map_copyout = .; . += 8;
_thread_create = .; . += 8;
_thread_set_state = .; . += 8;
_thread_resume = .; . += 8;
_thread_terminate = .; . += 8;
_task_suspend = .; . += 8;
_task_resume = .; . += 8;
_task_clear_return_wait = .; . += 8;
_get_bsdtask_info = .; . += 8;
_thread_suspend = .; . += 8;
_kfree = .; . += 8;
_kernel_thread_start = .; . += 8;
_assert_wait = .; . += 8;
_assert_wait_timeout = .; . += 8;
_thread_block = .; . += 8;
_thread_wakeup = .; . += 8;
_thread_wakeup_prim = .; . += 8;
_mach_absolute_time = .; . += 8;
_absolutetime_to_nanoseconds = .; . += 8;
_clock_get_calendar_microtime = .; . += 8;
_ml_io_map = .; . += 8;
_ml_vtophys = .; . += 8;
_ml_static_vtop = .; . += 8;
__ZN9IOService11getPlatformEv = .; . += 8;
__ZN9IOServiceC2Ev = .; . += 8;
__ZN8OSSymbol17withCStringNoCopyEPKc = .; . += 8;
__ZN6OSData9withBytesEPKvj = .; . += 8;
_kalloc_data = .; . += 8;
_kfree_data = .; . += 8;
_wakeup = .; . += 8;
_sock_socket = .; . += 8;
_sock_connect = .; . += 8;
_sock_send = .; . += 8;
_sock_receive = .; . += 8;
_sock_close = .; . += 8;
_sysent = .; . += 8;
_task_restartable_ranges_register = .; . += 8;
_task_threads = .; . += 8;
_mach_vm_region = .; . += 8;
_mach_vm_region_recurse = .; . += 8;
_copyin = .; . += 8;
_copyout = .; . += 8;
_ulock_wake = .; . += 8;
_task_info = .; . += 8;
_convert_port_to_thread = .; . += 8;
_convert_port_to_thread_read = .; . += 8;
_convert_port_to_thread_inspect = .; . += 8;
}
. = ALIGN(16384);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
}
. = ALIGN(8);
_heap_start = .;
}
VERSION {
{
global:
_start;
local:
*;
};
}
+59
View File
@@ -0,0 +1,59 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(8) {
. = ALIGN(8);
_panic = .; . += 8;
_IOLog = .; . += 8;
_kalloc = .; . += 8;
_kfree = .; . += 8;
_kernel_thread_start = .; . += 8;
_assert_wait = .; . += 8;
_assert_wait_timeout = .; . += 8;
_thread_block = .; . += 8;
_thread_wakeup = .; . += 8;
_mach_absolute_time = .; . += 8;
_absolutetime_to_nanoseconds = .; . += 8;
_clock_get_calendar_microtime = .; . += 8;
_ml_io_map = .; . += 8;
_ml_vtophys = .; . += 8;
__ZN9IOService11getPlatformEv = .; . += 8;
__ZN9IOServiceC2Ev = .; . += 8;
__ZN8OSSymbol17withCStringNoCopyEPKc = .; . += 8;
__ZN6OSData9withBytesEPKvj = .; . += 8;
_kalloc_data = .; . += 8;
_kfree_data = .; . += 8;
_wakeup = .; . += 8;
_sock_socket = .; . += 8;
_sock_connect = .; . += 8;
_sock_send = .; . += 8;
_sock_receive = .; . += 8;
_sock_close = .; . += 8;
}
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.bss : {
*(.bss*)
}
. = ALIGN(8);
_heap_start = .;
}
VERSION {
{
global:
_start;
local:
*;
};
}
@@ -1,17 +0,0 @@
{
"abi": "eabi",
"arch": "arm",
"crt-objects-fallback": "false",
"data-layout": "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64",
"features": "+v7,+thumb2,+thumb-mode,+soft-float,+strict-align,-neon",
"linker": "rust-lld",
"linker-flavor": "gnu-lld",
"llvm-floatabi": "soft",
"llvm-target": "armv7a-none-eabi",
"max-atomic-width": 64,
"panic-strategy": "abort",
"plt-by-default": false,
"position-independent-executables": true,
"relocation-model": "pic",
"target-pointer-width": 32
}
+6 -107
View File
@@ -33,12 +33,7 @@ fn main() {
.clang_arg(format!("{}-none-elf", target.split('-').next().unwrap()))
.clang_args(cc_clang_args)
.clang_arg(format!("-I{}", devkit_dir.to_str().unwrap()))
.clang_arg("-include")
.clang_arg("stddef.h")
.clang_arg("-include")
.clang_arg("stdint.h")
.blocklist_type("max_align_t")
.blocklist_function("memcpy|memmove|memset|memcmp|bcmp|strlen")
.merge_extern_blocks(true)
.generate()
.expect("Unable to generate bindings");
@@ -47,6 +42,8 @@ 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={}",
@@ -65,110 +62,20 @@ fn main() {
println!("cargo:rustc-link-lib=static=m");
println!("cargo:rustc-link-arg=--export-dynamic");
println!("cargo:rustc-link-arg=--emit-relocs");
let flavor = if env::var("CARGO_FEATURE_WIN9X").is_ok() {
"win9x"
} else if env::var("CARGO_FEATURE_WINNT").is_ok() {
"winnt"
} else if env::var("CARGO_FEATURE_LINUX_INJECTED").is_ok() {
"linux"
let script = if env::var("CARGO_FEATURE_WIN9X").is_ok() {
"agent-win9x.lds"
} else {
"xnu"
"agent.lds"
};
let arch = match target.split('-').next().unwrap() {
"i686" => "x86",
"armv7a" => "arm",
"aarch64" => "arm64",
other => other,
};
let script = format!("agent-{flavor}-{arch}.lds");
println!("cargo:rustc-link-arg=--script={script}");
println!("cargo:rustc-link-arg=--gc-sections");
// All of the agent lives in the library crate, so nothing in the binary
// references the entrypoint the host calls; keep the linker from
// dropping it along with the rest of the unreferenced archive members.
println!("cargo:rustc-link-arg=--undefined=_start");
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());
}
if target.starts_with("i686") && env::var("CARGO_FEATURE_LINUX_INJECTED").is_ok() {
let shim = out_dir.join("regparm.o");
let status = Command::new(cc)
.args(&cc_args)
.args(["-m32", "-c", "-O2", "-fPIC", "-ffreestanding", "-o"])
.arg(&shim)
.arg("src/linux/regparm.c")
.status()
.expect("Couldn't run the compiler");
assert!(status.success(), "Couldn't compile the kernel-call shim");
println!("cargo:rustc-link-arg={}", shim.to_string_lossy());
println!("cargo:rerun-if-changed=src/linux/regparm.c");
}
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=GUMJS_DEVKIT_DIR");
println!("cargo:rerun-if-env-changed=FRIDA_VERSION");
println!("cargo:rerun-if-changed={}", devkit_dir.join("frida-gumjs.h").display());
}
fn compile_version_note(out_dir: &Path, cc: &Path, cc_args: &[&str], arch: &str) -> PathBuf {
let version = env::var("FRIDA_VERSION")
.ok()
.filter(|v| !v.is_empty())
.unwrap_or_else(|| "0.0.0".to_string());
let payload = format!(
"{{\"type\":\"frida\",\"name\":\"frida-barebone-agent\",\
\"version\":\"{version}\",\"architecture\":\"{arch}\"}}"
);
let source = out_dir.join("version-note.S");
fs::write(
&source,
format!(
".section .note.package,\"\",%note\n\
.balign 4\n\
.long 2f - 1f\n\
.long 4f - 3f\n\
.long 0xcafe1a7e\n\
1: .asciz \"FDO\"\n\
2: .balign 4\n\
3: .asciz {payload:?}\n\
4: .balign 4\n"
),
)
.expect("Couldn't write the version note");
let object = out_dir.join("version-note.o");
let status = Command::new(cc)
.args(cc_args)
.args(["-target", &llvm_target(), "-c", "-o"])
.arg(&object)
.arg(&source)
.status()
.expect("Couldn't run the compiler");
assert!(status.success(), "Couldn't assemble the version note");
object
}
fn llvm_target() -> String {
let target = env::var("TARGET").unwrap();
let spec = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join(format!("{target}.json"));
let text = fs::read_to_string(&spec).expect("Couldn't read the target specification");
let key = "\"llvm-target\"";
let at = text.find(key).expect("Target specification names no LLVM target");
let rest = &text[at + key.len()..];
let open = rest.find('"').unwrap() + 1;
let close = open + rest[open..].find('"').unwrap();
rest[open..close].to_string()
}
pub fn detect_gcc_include_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
@@ -203,7 +110,7 @@ pub fn detect_gcc_include_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
let p = Path::new(raw);
if p.exists() {
let canonical = without_extended_length_prefix(fs::canonicalize(p).expect("Failed to canonicalize include path"));
let canonical = fs::canonicalize(p).expect("Failed to canonicalize include path");
if !paths.contains(&canonical) {
paths.push(canonical);
}
@@ -213,14 +120,6 @@ pub fn detect_gcc_include_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
paths
}
fn without_extended_length_prefix(path: PathBuf) -> PathBuf {
let text = path.to_string_lossy();
match text.strip_prefix(r"\\?\") {
Some(rest) => PathBuf::from(rest),
None => path,
}
}
pub fn detect_gcc_library_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
let out = Command::new(gcc).args(args).arg("-print-search-dirs").output()
.expect("Failed to execute GCC to detect library paths");
+5 -5
View File
@@ -1,8 +1,8 @@
export FRIDA_BAREBONE_CONFIG=$PWD/etc/xnu.json
export PYTHONPATH=$HOME/src/frida-python
sdk=$HOME/src/frida-core/deps/sdk-none-arm64-softfloat_pic
export CC="clang --target=aarch64-none-elf -ffixed-x18 -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"
export GUMJS_DEVKIT_DIR=$HOME/src/frida-gum/build/none-arm64-softfloat_pic/bindings/gumjs/devkit
export PATH=$PWD/xpacks/.bin:$PATH
export CC_aarch64_unknown_none=aarch64-none-elf-gcc
export AR_aarch64_unknown_none=aarch64-none-elf-ar
export RANLIB_aarch64_unknown_none=aarch64-none-elf-ranlib
export GUMJS_DEVKIT_DIR=/home/oleavr/src/frida-gum/build/bindings/gumjs/devkit
@@ -1,6 +1,5 @@
{
"agent": {
"type": "resident",
"transport": {
"type": "socket",
"path": "/tmp/frida-guest.sock"
-1
View File
@@ -1,6 +1,5 @@
{
"agent": {
"type": "resident",
"transport": {
"type": "device",
"path": "/dev/frida"
-1
View File
@@ -20,7 +20,6 @@
}
},
"image": {
"type": "xnu-kernelcache",
"file": "/Users/oleavr/src/vphone-cli/vm/iPhone17,3_26.1_23B85_Restore/kernelcache.research.vresearch101",
"symbols": {
"panic": "0x12a2814",
+1 -1
View File
@@ -5,7 +5,7 @@
},
"kernel": "win9x",
"agent": {
"path": "/home/oleavr/src/frida-core/src/barebone/agent/target-win9x/i686-unknown-none-pic/release/frida-barebone-agent",
"path": "/home/oleavr/src/frida-core/src/barebone/agent/target-win9x/i686-unknown-none/release/frida-barebone-agent",
"transport": {
"type": "hostlink",
"qmp": "unix:/tmp/claude-1000/-home-oleavr-src-frida-core/0f3e358c-8f66-4246-b793-deb2cd3727f3/scratchpad/qmp.sock",
-15
View File
@@ -1,15 +0,0 @@
{
"connection": {
"host": "127.0.0.1",
"port": 1234
},
"kernel": "winnt",
"agent": {
"path": "/home/oleavr/src/frida-core/src/barebone/agent/target-winnt/i686-unknown-none-pic/release/frida-barebone-agent",
"transport": {
"type": "hostlink",
"qmp": "unix:/tmp/qmp.sock",
"bus": "frida-vserial.0"
}
}
}
-1
View File
@@ -16,7 +16,6 @@
}
},
"image": {
"type": "xnu-kernelcache",
"file": "/Users/oleavr/src/frida-barebone-ios/iPhone11_8_iPhone12_1_14.0_18A5351d_Restore/kernelcache.research.iphone12b",
"base": "0xfffffff007004000",
"symbols": {
+1 -1
View File
@@ -5,6 +5,6 @@ const cm = new CModule(File.readAllBytes("./target/aarch64-unknown-none/release/
const start = new NativeFunction(cm._start, "pointer", []);
const bufferPhysicalAddress = start();
console.log("Ready! Buffer is at physical address:", bufferPhysicalAddress);
$debugger.continue();
$gdb.continue();
Object.assign(globalThis, { cm, start });
@@ -1,20 +0,0 @@
{
"arch": "x86",
"cpu": "pentium4",
"crt-objects-fallback": "false",
"data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
"features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float",
"linker": "rust-lld",
"linker-flavor": "gnu-lld",
"llvm-target": "i686-unknown-none-elf",
"max-atomic-width": 64,
"panic-strategy": "abort",
"plt-by-default": false,
"position-independent-executables": true,
"relocation-model": "pic",
"rustc-abi": "softfloat",
"stack-probes": {
"kind": "inline"
},
"target-pointer-width": 32
}
+21 -9
View File
@@ -2,15 +2,27 @@ MODULE_NAME ?= frida-agent
obj-m += $(MODULE_NAME).o
$(MODULE_NAME)-y := frida-kmod.o frida-agent-image.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
AFLAGS_frida-agent-image.o := -DFRIDA_AGENT_IMAGE='"$(FRIDA_AGENT_IMAGE)"'
targets += frida-agent-prelinked.o
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)
quiet_cmd_frida_agent_prelinked = COPY $@
cmd_frida_agent_prelinked = cp $< $@
$(obj)/frida-agent-image.o: $(FRIDA_AGENT_IMAGE)
$(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)
+159 -28
View File
@@ -1,3 +1,14 @@
# 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
@@ -6,44 +17,142 @@
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-pic
RUST_TARGET ?= i686-unknown-none
AGENT_TARGET_FLAGS ?= --target $(AGENT_DIR)/$(RUST_TARGET).json -Z json-target-spec
SDK_ARCH ?= x86
AGENT_RUSTFLAGS ?=
AGENT_CFLAGS ?= --target=i386-none-elf
TOOLPREFIX ?=
AGENT_LD_EMULATION ?= elf_i386
else ifeq ($(ARCH)-$(BITS),x86-64)
RUST_TARGET ?= x86_64-unknown-none-pic
RUST_TARGET ?= x86_64-unknown-none
SDK_ARCH ?= 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
TOOLPREFIX ?=
AGENT_LD_EMULATION ?= elf_x86_64
else ifeq ($(ARCH)-$(BITS),arm64-64)
RUST_TARGET ?= aarch64-unknown-none-pic
RUST_TARGET ?= aarch64-unknown-none
SDK_ARCH ?= arm64
AGENT_RUSTFLAGS ?=
AGENT_CFLAGS ?= --target=aarch64-none-elf -ffixed-x18
TOOLPREFIX ?= aarch64-none-elf-
else
$(error Unsupported ARCH "$(ARCH)" and BITS "$(BITS)"; this flavor covers \
$(error Unsupported ARCH "$(ARCH)" and BITS "$(BITS)"; this flavour covers \
x86/32, x86/64 and arm64/64)
endif
AGENT_TARGET_FLAGS ?= --target $(RUST_TARGET)
AGENT_DIR := $(CURDIR)/..
AGENT_TARGET_DIR := $(AGENT_DIR)/target-linux
AGENT_LIB := $(AGENT_TARGET_DIR)/$(RUST_TARGET)/$(PROFILE)/libfrida_barebone_agent.a
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)
AGENT_CC ?= clang
AGENT_CFLAGS += -I$(FRIDA_SDK)/include \
# Any binutils that speak the target's ELF will do; nothing here is compiled with
# them. Building for the host's own architecture, that is the host's binutils.
AGENT_LD ?= $(TOOLPREFIX)ld
AGENT_AR ?= $(TOOLPREFIX)ar
AGENT_NM ?= $(TOOLPREFIX)nm
AGENT_OBJCOPY ?= $(TOOLPREFIX)objcopy
# The ABI the kernel imposes on anything linked into it. The flags only reach crates
# Cargo compiles, and the toolchain ships core, alloc and compiler_builtins prebuilt
# without them, so those three have to be rebuilt from source as well. That needs the
# rust-src component and an unstable flag, hence RUSTC_BOOTSTRAP.
#
# On x86_64 that is endbr64 on every address-taken function, since a kernel with
# CONFIG_X86_KERNEL_IBT faults an indirect call landing anywhere else, and switches
# compiled as comparisons rather than jump tables, which objtool cannot follow into.
# Soft-float, the kernel code model and the absent red zone come with the target
# itself. On arm64
# it is x18, which the kernel reserves for the shadow call stack, so nothing linked in
# may use it as a general-purpose register.
ifeq ($(ARCH)-$(BITS),x86-32)
AGENT_RUSTFLAGS ?=
AGENT_CFLAGS ?= --target=i386-none-elf \
-Xclang -target-feature -Xclang +soft-float -mno-mmx -mno-sse \
-mlong-double-64 -fno-pic --sysroot=$(FRIDA_SDK)
else ifeq ($(ARCH)-$(BITS),x86-64)
AGENT_RUSTFLAGS ?= -Zcf-protection=branch \
-Cllvm-args=--min-jump-table-entries=65536 \
-Crelocation-model=static -Zplt=yes
AGENT_CFLAGS ?= --target=x86_64-none-elf \
-Xclang -target-feature -Xclang +soft-float -mno-mmx -mno-sse -mno-red-zone \
-mlong-double-64 -mcmodel=kernel -fcf-protection=branch -fno-jump-tables \
-fno-pic --sysroot=$(FRIDA_SDK)
else
AGENT_RUSTFLAGS ?= -Zfixed-x18
AGENT_CFLAGS ?= --target=aarch64-none-elf -mabi=aapcs-soft -mgeneral-regs-only \
-ffixed-x18 -fno-pic --sysroot=$(FRIDA_SDK)
endif
AGENT_BUILD_STD ?= -Z build-std=core,alloc,compiler_builtins
# The devkit header reaches for GLib's and capstone's, which a devkit built against
# an SDK leaves in that SDK rather than beside itself. The sysroot's own include
# directory is spelled out because only clang's bare-metal driver, which it selects
# for arm64 but not for x86_64, searches there on its own.
AGENT_CFLAGS += -isystem $(FRIDA_SDK)/include \
-I$(FRIDA_SDK)/include/glib-2.0 \
-I$(FRIDA_SDK)/lib/glib-2.0/include \
-I$(FRIDA_SDK)/include/capstone \
-I$(FRIDA_SDK)/include/json-glib-1.0
AGENT_DIR := $(CURDIR)/..
AGENT_TARGET_DIR := $(AGENT_DIR)/target-linux-$(SDK_ARCH)
AGENT_IMAGE := frida-agent.elf
# The soft-float SDK, whose libc is picolibc and whose compiler runtime is
# compiler-rt. GCC's own newlib and libgcc are hard-float and cannot be used here.
FRIDA_SDK ?= $(error Set FRIDA_SDK to a none-$(SDK_ARCH)-softfloat SDK prefix)
LIBM = $(FRIDA_SDK)/lib/libm.a
LIBBUILTINS = $(FRIDA_SDK)/lib/libclang_rt.builtins.a
# bindgen reads the devkit header through whatever this compiler reports as its
# include path, so it has to be one that knows where the SDK keeps its libc. It has
# to be clang either way: -mabi=aapcs-soft landed there in 19 and GCC has it at no
# version, and on x86_64 the soft-float ABI is reachable only as an -Xclang feature.
AGENT_CC ?= clang
PRELINKED := frida-agent-prelinked.o.bin
AGENT_LIB_SOFTFLOAT := $(AGENT_TARGET_DIR)/libfrida-barebone-agent-softfloat.a
# On arm64 the toolchain's own compiler_builtins is compiled for the ordinary AAPCS,
# where a double travels in d0: its __muldf3 opens with "fmov x14, d0". Everything
# else there is -mabi=aapcs-soft and passes that same double in x0. Whole-archiving
# the staticlib puts those definitions in front of the SDK's compiler-rt, which has
# the soft-float ones, and every double the C library computes then comes back
# garbage — quietly, since the arithmetic still runs. Drop them and let compiler-rt
# answer instead. x86_64 needs none of this: soft-float is part of the Rust target
# there, so the copy build-std compiles already passes doubles the way the C half
# expects.
ifeq ($(ARCH),arm64)
DROP_HARDFLOAT_BUILTINS = \
$(AGENT_AR) d $@ $$($(AGENT_NM) --print-armap $@ | sed -n 's/^__muldf3 in //p') && \
$(AGENT_AR) s $@
else
DROP_HARDFLOAT_BUILTINS = :
endif
# The agent puts malloc, the C library's locks and its process stubs on the kernel's
# own facilities, so picolibc's versions are duplicate definitions rather than
# fallbacks. Work out which members they live in instead of naming them: the set
# follows from what the agent defines, and picolibc moves things between releases.
LIBC := $(AGENT_TARGET_DIR)/libc-trimmed.a
ENTRY_POINTS := frida_agent_start frida_agent_stop \
gum_interceptor_obtain gum_interceptor_begin_transaction \
gum_interceptor_end_transaction gum_interceptor_replace \
gum_interceptor_revert
KEEP_GLOBAL := $(foreach s,$(ENTRY_POINTS),--keep-global-symbol=$(s))
# Two structures the reparenting path reaches into lost a member upstream, and
# neither change announces itself as anything frida-kmod.c could test for. The
# headers it is compiled against are the authority on both, so ask them.
FRIDA_KMOD_DEFS := \
$(if $(shell grep -l 'list_head[[:space:]]*thread_group;' \
$(KDIR)/include/linux/sched.h),-DFRIDA_HAVE_TASK_THREAD_GROUP) \
$(if $(shell grep -l 'spinlock_t[[:space:]]*lock;' \
$(KDIR)/include/linux/fs_struct.h),-DFRIDA_HAVE_FS_STRUCT_LOCK)
all: modules
@@ -51,24 +160,46 @@ all: modules
# next boot, so bring-up wants a fresh one per attempt.
MODULE_NAME ?= frida-agent
modules: $(AGENT_IMAGE)
modules: $(PRELINKED)
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) \
MODULE_NAME=$(MODULE_NAME) FRIDA_AGENT_IMAGE=$(CURDIR)/$(AGENT_IMAGE) modules
FRIDA_KMOD_DEFS="$(FRIDA_KMOD_DEFS)" MODULE_NAME=$(MODULE_NAME) modules
$(AGENT_IMAGE): agent
cp $(AGENT_TARGET_DIR)/$(RUST_TARGET)/$(PROFILE)/frida-barebone-agent $@
$(PRELINKED): $(AGENT_LIB_SOFTFLOAT) $(LIBC)
$(AGENT_LD) -r $(if $(AGENT_LD_EMULATION),-m $(AGENT_LD_EMULATION)) -o $@.tmp \
--whole-archive $(AGENT_LIB_SOFTFLOAT) --no-whole-archive \
--start-group \
$(GUMJS_DEVKIT_DIR)/libfrida-gumjs.a \
$(LIBC) \
$(LIBM) \
$(LIBBUILTINS) \
--end-group
$(AGENT_OBJCOPY) $(KEEP_GLOBAL) --strip-debug \
--remove-section=.llvmbc --remove-section=.llvmcmd $@.tmp $@
rm -f $@.tmp
$(AGENT_LIB_SOFTFLOAT): agent
cp $(AGENT_LIB) $@
$(DROP_HARDFLOAT_BUILTINS)
$(LIBC): export LC_ALL = C
$(LIBC): $(AGENT_LIB_SOFTFLOAT)
cp $(FRIDA_SDK)/lib/libc.a $@
$(AGENT_NM) --defined-only $(AGENT_LIB_SOFTFLOAT) \
| awk 'NF == 3 && $$2 ~ /^[TDBR]$$/ { print $$3 }' | sort -u > $@.agent-syms
$(AGENT_NM) --print-armap $@ | sed -n 's/^\([^ ]*\) in \(.*\)$$/\1 \2/p' | sort -u > $@.members
$(AGENT_AR) d $@ $$(join $@.agent-syms $@.members | awk '{ print $$2 }' | sort -u)
$(AGENT_AR) s $@
rm -f $@.agent-syms $@.members
agent:
cd $(AGENT_DIR) && RUSTC_BOOTSTRAP=1 \
RUSTFLAGS="-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
cd $(AGENT_DIR) && RUSTC_BOOTSTRAP=1 RUSTFLAGS="$(AGENT_RUSTFLAGS)" \
CC_$(subst -,_,$(RUST_TARGET))="$(AGENT_CC) $(AGENT_CFLAGS)" \
cargo build --$(PROFILE) \
$(AGENT_TARGET_FLAGS) --features linux \
--target-dir $(AGENT_TARGET_DIR) $(AGENT_BUILD_STD)
clean:
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) clean
rm -f $(AGENT_IMAGE)
rm -f $(PRELINKED) $(PRELINKED).tmp $(AGENT_LIB_SOFTFLOAT) $(LIBC)
.PHONY: all modules agent clean
+217 -127
View File
@@ -1,201 +1,291 @@
# Linux 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.
The same agent that the Barebone backend injects into XNU from the outside, but
loaded from the inside as an ordinary kernel module.
## What the loader does
## How it differs from the XNU flavour
| | 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` |
| | 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 |
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.
Because the Linux agent performs its own memory work, it never issues the
`RemapWritablePages`, `MemoryProtect` or `PatchCode` host RPCs. The wire protocol
is otherwise identical, so the host side sees the same `(yqv)` frames.
## Building
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.
Four inputs: this repository, a GumJS devkit built for `none-<arch>-softfloat`, the
soft-float SDK it was built against, and the kernel the module is for. `Makefile`
covers x86_64 and arm64, and defaults to the architecture of the machine building.
Building for the running kernel is a good deal shorter than cross-building for a
device, so that walkthrough comes first; the arm64 one below it is the same steps
with the kernel obtained from elsewhere.
### Toolchain
rustup toolchain install nightly
rustup component add rust-src --toolchain nightly
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)
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.
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 devkit and the SDK
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.
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
### For the running x86_64 kernel
releng/deps.py sync sdk none-$arch-softfloat_pic ~/sdk-none-$arch-softfloat_pic
No prebuilt bundles are published for `none-x86_64-softfloat`, so both inputs are
built locally, against the same SDK:
`releng/deps.toml` pins which SDK a devkit was built against, and mixing the two
leaves symbols undefined at link time.
releng/deps.py build --bundle=sdk --host=none-x86_64-softfloat
mkdir -p ~/sdk-none-x86_64-softfloat
tar -C ~/sdk-none-x86_64-softfloat -xf deps/sdk-none-x86_64-softfloat.tar.xz
### For the running kernel
gum=subprojects/frida-gum
mkdir -p $gum/build/none-x86_64-softfloat
(cd $gum/build/none-x86_64-softfloat \
&& FRIDA_DEPS=$PWD/../../../../deps ../../configure \
--host=none-x86_64-softfloat \
--enable-gumjs \
--with-devkits=gumjs \
--with-devkit-symbol-scope=original \
&& make)
make -C src/barebone/agent/linux \
FRIDA_SDK=$HOME/sdk-none-$arch-softfloat_pic \
GUMJS_DEVKIT_DIR=$HOME/gumjs-devkit
FRIDA_SDK=$HOME/sdk-none-x86_64-softfloat \
GUMJS_DEVKIT_DIR=$PWD/$gum/build/none-x86_64-softfloat/bindings/gumjs/devkit
`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.
Everything else — `KDIR`, the Rust target, the binutils, the kernel's own name for the
architecture — follows from the running kernel.
### For an Android device
### For an arm64 device
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:
Here there are published bundles to start from, and the kernel has to be assembled.
#### The devkit and the SDK
version=17.17.0
base=https://github.com/frida/frida/releases/download/$version
curl -LO $base/frida-gumjs-devkit-$version-none-arm64-softfloat.tar.xz
mkdir -p ~/gumjs-devkit
tar -C ~/gumjs-devkit -xf frida-gumjs-devkit-$version-none-arm64-softfloat.tar.xz
releng/deps.py sync sdk none-arm64-softfloat ~/sdk-none-arm64-softfloat
The SDK carries picolibc and the compiler-rt builtins the devkit expects to be linked
against, and `releng/deps.toml` pins which one. Mixing a devkit with an SDK built for
a different libc leaves undefined symbols at prelink time.
#### The kernel
Nothing here needs the device's own kernel to be built, but the config alone is not
enough either. `uname -r` ends in the GKI commit and the build number:
adb shell uname -r
6.6.102-android15-8-g6eb5b2a8c46b-ab14739656-4k
6.1.145-android14-11-gc1de4747ac59-ab14219743
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:
which is commit `c1de4747ac59` and build `14219743`. Three pieces are keyed to them:
commit=6eb5b2a8c46b
build=14739656
commit=c1de4747ac59
build=14219743
ci=https://ci.android.com/builds/submitted/$build/kernel_aarch64/latest/raw
mkdir -p ~/kernel
mkdir -p ~/kernel-prepared ~/kernel-source
curl -sSL $ci/modules_prepare_outdir.tar.gz | tar -xz -C ~/kernel-prepared
curl -sSL $ci/kernel_aarch64_Module.symvers -o ~/kernel-prepared/Module.symvers
curl -sSL https://android.googlesource.com/kernel/common/+archive/$commit.tar.gz \
| tar -xz -C ~/kernel
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
| tar -xz -C ~/kernel-source
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.
`modules_prepare_outdir.tar.gz` is the configured build tree with `scripts/` already
compiled as x86-64 binaries, so the kbuild half has to run on an x86-64 Linux host —
an arm64 one gets as far as `CC [M]` and then fails to run `fixdep`. `Module.symvers`
carries the CRCs `CONFIG_MODVERSIONS` checks. The top-level `Makefile` in that prepared tree is a stub
that includes the source's, hence both `KDIR` and `KOUT` below.
A different GKI build of the same branch does not substitute for these: vermagic is
compared verbatim, and it names the exact build.
#### Build
make -C src/barebone/agent/linux \
ARCH=arm64 BITS=64 \
FRIDA_SDK=$HOME/sdk-none-arm64-softfloat_pic \
FRIDA_SDK=$HOME/sdk-none-arm64-softfloat \
GUMJS_DEVKIT_DIR=$HOME/gumjs-devkit \
KDIR=$HOME/kernel \
AGENT_LD=aarch64-linux-gnu-ld \
AGENT_AR=aarch64-linux-gnu-ar \
AGENT_NM=aarch64-linux-gnu-nm \
AGENT_OBJCOPY=aarch64-linux-gnu-objcopy \
KDIR=$HOME/kernel-source \
KOUT=$HOME/kernel-prepared \
LLVM=1
`make` builds the Rust staticlib itself, prelinks it against the devkit and the SDK,
then hands the result to kbuild. The `AGENT_*` overrides are only needed because the
defaults name the `aarch64-none-elf-` toolchain the XNU flavour uses; drop them if
that is what is installed.
The prelink half runs anywhere; the kbuild half needs the x86-64 Linux host above.
Cross-building from macOS means running that step in a container holding the kernel
tree.
adb push src/barebone/agent/linux/frida-agent.ko /data/local/tmp/
## Loading
insmod frida-agent.ko
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.
The module's init path returns as soon as the worker thread is running — watch
`dmesg` for `frida: listening on /dev/frida`. `rmmod frida-agent` unwinds in the
reverse order and does not return until the worker is off the module's text.
## Talking to it
The agent 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.
The agent exposes `/dev/frida` and waits: one client at a time, `read`/`write` of the
same length-prefixed frames the other transports carry, with `poll` for readiness.
It never dials out, so there is no network namespace to get right and no ordering
requirement between loading the module and attaching a client.
On Android devtmpfs creates the node without a policy-specific label, so an
SELinux-enforcing system needs the client's domain granted access to it.
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.
Serve the Barebone device from a frida-server running on the target, so the
`open()` happens next to the device node:
### Reaching it from a host
Serve the Barebone device from a frida-server running on the target, so the `open()`
happens next to the device node and the host talks to an ordinary frida-server:
FRIDA_BAREBONE_CONFIG=/data/local/tmp/linux-kmod.json \
frida-server --device barebone -l 127.0.0.1:27042
`etc/linux-kmod.json` is that config. 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:
`etc/linux-kmod.json` is that config. From the host, with `adb forward tcp:27042
tcp:27042` in place:
frida -H 127.0.0.1:27042 -p 0
## Injecting into userspace
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,
Loaded on a live system the module doubles as a ptrace-free injector for ordinary
processes — how the Linux local backend reaches its targets when the module is
present, falling back to `ptrace` when it is not. There is no second device node;
the channel is `prctl()` with a magic option and a token in the fifth argument,
prctl(0x46524944, op, arg1, arg2, 0x1d5f9e6b2c7a4038)
and any call whose option or token does not match falls through to the real
prctl, so probing for the agent looks like a stock kernel. `op` 32 pings and
prctl, so probing for the module looks like a stock kernel. `op` 32 pings and
returns the magic.
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.
Allocate, free, read, write and spawn — the primitives the injector drives
against a target pid — require `CAP_SYS_ADMIN`. A spawned thread is a real
`CLONE_THREAD` sibling that adopts the target's mm, files, credentials,
namespaces, fs and SysV semaphore undo list, so it is a native member of the
group rather than a kernel thread wearing its address space.
Cloaking is Gum's own registry mirrored into the kernel, so it holds against
`/proc` too: the agent 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 module intercepts the `maps`, `smaps`, task and status readers
and drops cloaked threads, ranges and fds while keeping the counts consistent. A
process cloaks its own resources with the token alone, 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`.
process cloaks its own resources with the token alone — no capability — so an
injected agent self-cloaks by forwarding Gum's updates over the same channel,
and the loader adds the footprint the agent itself never sees. A cloaked thread
still sees through the cloak when it reads `/proc`, so the agent is not blind to
its own process.
## What the target kernel has to provide
- **x86_64 or arm64** 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.
- **x86_64 or arm64.** The register-level pieces — the page size, cache maintenance,
where the syscall ABI leaves prctl()'s arguments, and how a spawned thread's
registers are set up for its return to userspace — have a half for each. The XNU
flavour remains arm64-only.
- **`CONFIG_KPROBES` and `CONFIG_KALLSYMS_ALL`.** A good deal of what the shim
needs is compiled in but not exported — `kallsyms_lookup_name`,
`kallsyms_on_each_symbol` and `set_memory_*` all are, and GKI trims its export
table to the KMI symbol list on top of that. The shim resolves them by name
through a kprobe, which is the standard way back in. Without kallsyms the module
still loads but cannot look up symbols or change page permissions.
- **Unsigned modules.** `CONFIG_MODULE_SIG_FORCE` must be off. Android's
`CONFIG_MODULE_SIG_PROTECT` is fine: it only demands signatures for modules on
the GKI list.
- **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 running inside it, which the image
honors whichever way it got in:
A hardened kernel imposes an ABI on anything linked into it, and the FP constraint
below is why this flavour is built against a soft-float SDK
(`none-arm64-softfloat`, `none-x86_64-softfloat`) rather than an ordinary bare-metal
one. The remaining constraints are per-architecture.
- **FP/SIMD.** JavaScript numbers are doubles, so a hardfloat build would use FP
for as long as the runtime lives, and neither architecture lets a kernel
thread hold it for free — x86 wants every stretch bracketed by
`kernel_fpu_begin()`, and on arm64 kernels before 6.9 there is no correct way
to hold it at all. 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`.
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.
@@ -1,7 +0,0 @@
.section .rodata, "a"
.balign 16
.globl frida_agent_image_start
frida_agent_image_start:
.incbin FRIDA_AGENT_IMAGE
.globl frida_agent_image_end
frida_agent_image_end:
File diff suppressed because it is too large. Load diff
-4
View File
@@ -1,4 +0,0 @@
/frida-agent
/frida-agent.kext/
/frida-kext.o
/frida-signing.o
-37
View File
@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>frida-agent</string>
<key>CFBundleIdentifier</key>
<string>re.frida.agent</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>frida-agent</string>
<key>CFBundlePackageType</key>
<string>KEXT</string>
<key>CFBundleShortVersionString</key>
<string>@FRIDA_VERSION@</string>
<key>CFBundleVersion</key>
<string>@BUNDLE_VERSION@</string>
<key>OSBundleRequired</key>
<string>Root</string>
<key>OSBundleLibraries</key>
<dict>
<key>com.apple.kpi.bsd</key>
<string>12.0</string>
<key>com.apple.kpi.libkern</key>
<string>12.0</string>
<key>com.apple.kpi.iokit</key>
<string>12.0</string>
<key>com.apple.kpi.mach</key>
<string>12.0</string>
<key>com.apple.kpi.unsupported</key>
<string>12.0</string>
</dict>
</dict>
</plist>
-88
View File
@@ -1,88 +0,0 @@
PROFILE ?= release
ARCH ?= arm64e
ifeq ($(ARCH),arm64e)
RUST_TARGET ?= arm64e-apple-darwin
CLANG_TARGET ?= arm64e-apple-macos11
else ifeq ($(ARCH),arm64)
RUST_TARGET ?= aarch64-apple-darwin
CLANG_TARGET ?= arm64-apple-macos11
else
RUST_TARGET ?= x86_64-apple-darwin
CLANG_TARGET ?= x86_64-apple-macos11
endif
AGENT_DIR := $(CURDIR)/..
AGENT_TARGET_DIR := $(AGENT_DIR)/target-xnu-kext
AGENT_LIB := $(AGENT_TARGET_DIR)/$(RUST_TARGET)/$(PROFILE)/libfrida_barebone_agent.a
SDK ?= $(shell xcrun --sdk macosx --show-sdk-path)
KERNEL_HEADERS := $(SDK)/System/Library/Frameworks/Kernel.framework/Headers
FRIDA_VERSION := $(or $(FRIDA_VERSION),0.0.0)
BUNDLE_VERSION := $(firstword $(subst -, ,$(FRIDA_VERSION)))
FRIDA_SDK ?= $(error Set FRIDA_SDK to a macos-$(ARCH)-kernel SDK prefix)
GUMJS_DEVKIT_DIR ?= $(error Set GUMJS_DEVKIT_DIR to the directory holding libfrida-gumjs.a)
AGENT_RUSTFLAGS ?= -Cembed-bitcode=no
AGENT_BUILD_STD ?= -Z build-std=core,alloc,compiler_builtins
GLUE_CFLAGS ?= -target $(CLANG_TARGET) -isysroot $(SDK) -I$(KERNEL_HEADERS) \
-mkernel -fno-builtin -fno-common -nostdinc -std=gnu11 \
-DKERNEL -DKERNEL_PRIVATE -DAPPLE -D__MACHO__=1
AGENT_CC ?= clang -target $(CLANG_TARGET) -mkernel -nostdlibinc \
-isystem $(FRIDA_SDK)/include \
-I$(FRIDA_SDK)/include/glib-2.0 \
-I$(FRIDA_SDK)/lib/glib-2.0/include \
-I$(FRIDA_SDK)/include/capstone \
-I$(FRIDA_SDK)/include/json-glib-1.0
stamp_version = sed -e 's/@FRIDA_VERSION@/$(FRIDA_VERSION)/' \
-e 's/@BUNDLE_VERSION@/$(BUNDLE_VERSION)/' Info.plist
BUNDLE := frida-agent.kext
BINARY := $(BUNDLE)/Contents/MacOS/frida-agent
all: $(BUNDLE)
skeleton: frida-kext.o Info.plist
mkdir -p $(BUNDLE)/Contents/MacOS
$(stamp_version) > $(BUNDLE)/Contents/Info.plist
$(shell xcrun --find ld) -kext -arch $(ARCH) -o $(BINARY) frida-kext.o
$(BUNDLE): frida-agent Info.plist
mkdir -p $(BUNDLE)/Contents/MacOS
$(stamp_version) > $(BUNDLE)/Contents/Info.plist
cp frida-agent $(BINARY)
frida-agent: frida-kext.o frida-signing.o agent
$(shell xcrun --find ld) -kext -arch $(ARCH) -o $@ \
frida-kext.o \
frida-signing.o \
$(AGENT_LIB) \
$(GUMJS_DEVKIT_DIR)/libfrida-gumjs.a \
$(FRIDA_SDK)/lib/libc.a \
$(FRIDA_SDK)/lib/libm.a \
$(FRIDA_SDK)/lib/libclang_rt.builtins.a
frida-kext.o: frida-kext.c
$(shell xcrun --find clang) $(GLUE_CFLAGS) -c $< -o $@
frida-signing.o: frida-signing.c
$(AGENT_CC) -I$(GUMJS_DEVKIT_DIR) -c $< -o $@
agent:
cd $(AGENT_DIR) && RUSTC_BOOTSTRAP=1 RUSTFLAGS="$(AGENT_RUSTFLAGS)" \
CC_$(subst -,_,$(RUST_TARGET))="$(AGENT_CC)" \
GUMJS_DEVKIT_DIR="$(GUMJS_DEVKIT_DIR)" \
cargo build --$(PROFILE) \
--target $(RUST_TARGET) --features xnu-kext \
--target-dir $(AGENT_TARGET_DIR) $(AGENT_BUILD_STD)
clean:
rm -rf $(BUNDLE) frida-agent frida-kext.o frida-signing.o
.PHONY: all agent clean skeleton
-123
View File
@@ -1,123 +0,0 @@
# The agent as a kext
The XNU flavour of the barebone agent is ordinarily a blob: a host with a debugger writes it into
a running kernel, tells it where that kernel keeps the things it calls, and starts it. The Linux
flavour can also be built as a module the kernel loads (`../linux`), and this is the same thing for
XNU -- a kext macOS loads.
What the two packagings share is everything the agent does. What differs is how it is put there,
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-arm64e-kernel` and published prebuilt:
cd frida-core
releng/deps.py sync sdk macos-arm64e-kernel ~/sdk-macos-arm64e-kernel
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-arm64e-kernel --enable-gumjs --with-devkits=gumjs \
--with-devkit-symbol-scope=original
make
Then the kext itself:
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
framework's headers, and links the two with the devkit into `frida-agent.kext`.
`make skeleton` builds the glue alone, which is enough to check the pipeline without the rest.
## How it is told about the kernel
A kext may call what the kernel exports to kexts, and the agent calls a great deal more than that:
`proc_find` and `copyin` are exported, `task_threads` and `mach_vm_allocate` are not. So the
addresses arrive the way they do for the blob -- worked out by whoever knows this kernel -- and are
written in through `/dev/frida` before the agent is started:
ioctl(fd, FRIDA_IOC_SET_ADDR, &(FridaAddrRequest){ .name = "task_threads", .address = ... });
...
ioctl(fd, FRIDA_IOC_START);
A name nothing writes stays zero, and the agent already answers for that: every one of them is an
`Option` on the Rust side, and what needs it says so rather than calling through a null.
Once started, the same device carries the frames: read what the agent says, write what the host
says, framed with a four-byte little-endian length exactly as on every other transport.
## What the sources needed
An SDK for a Darwin kernel is a target nothing here had been built for, and six of the sources say
so. Each is a commit of its own on the fork, a few lines apiece:
- **picolibc** -- Mach-O has no `__attribute__((alias))`, so the aliasing macros fall through to
the assembler ones it already carries for Mach-O, and the names are spent before being spelled.
Its aarch64 assembly is written for ELF and its C stubs answer for it instead. The interrupt
vectors and the `exec` family are not built: one owns a machine this does not, the other crashes
the compiler.
- **compiler-rt** -- `apple_versioning.c` is stubs for macOS releases long past, written against a
header only userspace has.
- **libffi** -- the trampoline table is built out of what userspace has of Mach; whether that is
there is now asked of the header rather than of the system's name.
- **QuickJS**, **tinycc** and **libiconv** -- all three ask what to do on Apple by asking whether
they are on Apple. Where that means userspace -- a userspace malloc's header, a cache-control
header, a locale one -- they now ask whether they are freestanding as well.
## Which architecture, and where pointers are signed
Apple silicon runs an arm64e kernel and loads nothing else, so that is what `ARCH` defaults to,
and the SDK and the devkit are built for `macos-arm64e-kernel` to match.
Pointer authentication is the thing to get right there, and what makes it awkward is that Rust's
`arm64e-apple-darwin` is a tag rather than an ABI: it neither signs a function pointer nor
authenticates one.
_pick: adrp x0, 0 ; add x0, x0, #0x0 ; ret <- no signing
_call: mov x2, x0 ; mov x0, x1 ; br x2 <- no authenticating
So the line is drawn where the kernel is, and not inside:
- **Ourselves.** GLib, GumJS and the agent are built `-fno-ptrauth-calls`, so the C half calls a
Rust callback the way Rust hands it over -- plainly. Nothing there signs, so nothing there is
disappointed. `otool` says as much: not one `blraa` outside `__auth_stubs`.
- **The kernel calling us.** The glue keeps the kernel's own ABI, so the device table it registers
is written with `AUTH` relocations and the loader signs it as the kernel expects.
- **Us calling the kernel.** Through `__auth_stubs`, which the loader fills the same way.
- **Us handing the kernel a function to call.** A thread's entry is the one that matters, and the
agent signs it itself, as it already does for the kernel it is injected into. What it signs with
is no longer written down twice: the glue derives the discriminator from the kernel's own
headers with `ptrauth_type_discriminator`, and asserts that it is the number the blob flavour
carries -- 0xd507 for a `thread_continue_t`, which is what it was.
What this does not buy is pointer authentication *within* the kext, which is protection given up
rather than correctness lost. Getting it back wants rustc to grow the ABI, or every callback
wrapped in a C thunk.
None of this touches x86_64, which has no pointer authentication at all: an Intel kext wants only
its own SDK built the same way.
## Floating point
Every other kernel flavour is built soft-float, since a kernel does not save the floating-point
registers across a trap. This one cannot be: Darwin has no soft-float C ABI on either
architecture. arm64 refuses the type outright without one -- `ABI 'darwinpcs' does not support
it` -- and `-mabi=aapcs-soft` alongside it crashes both Apple's clang and upstream LLVM on the
first varargs function taking a double; x86_64 wants SSE to return one. A script engine is nothing
but doubles, so the floating-point registers stay in play, and what that costs at run time is a
thing to answer on the machine.
## Putting copies into processes
Not there yet. The blob's linker script marks out its own writable half and what a copy of it has
to relocate; a kext is laid out by the kernel's loader instead, so the glue says there is nothing
to copy from. Everything the agent does in the kernel itself is unaffected.
Carrying the blob's ELF and placing it the way the host does is not a way round that, either: on a
machine with SPTM, memory a kext allocates cannot be made executable, while a kext's own text is
mapped executable by the kernel's loader because it was signed.
-446
View File
@@ -1,446 +0,0 @@
#include <libkern/libkern.h>
#include <mach/kmod.h>
#include <mach/mach_types.h>
#include <miscfs/devfs/devfs.h>
#include <sys/conf.h>
#include <sys/errno.h>
#include <sys/ioccom.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/random.h>
#include <sys/uio.h>
#include <kern/locks.h>
#include <kern/thread.h>
#include <ptrauth.h>
extern int frida_agent_start (uint64_t own_base, uint64_t own_size);
extern void frida_agent_stop (void);
extern void frida_agent_wake (void);
#define FRIDA_KERNEL_ADDRS(X) \
X (panic) \
X (kalloc) X (kfree) X (kalloc_data) X (kfree_data) \
X (proc_iterate) X (proc_find) X (proc_rele) X (proc_pid) X (proc_best_name) X (proc_task) \
X (current_task) X (current_thread) X (get_task_map) X (get_bsdtask_info) \
X (mach_vm_allocate) X (mach_vm_deallocate) X (mach_vm_protect) X (mach_vm_remap) \
X (kernel_map) X (kernel_pmap) X (pmap_find_phys) X (ml_static_ptovirt) \
X (mach_vm_region) X (mach_vm_region_recurse) \
X (vm_map_copyin) X (vm_map_copyout) X (vm_map_wire_external) \
X (thread_create) X (thread_set_state) X (thread_resume) X (thread_suspend) \
X (thread_terminate) X (kernel_thread_start) \
X (task_suspend) X (task_resume) X (task_clear_return_wait) X (task_threads) X (task_info) \
X (task_restartable_ranges_register) \
X (assert_wait) X (assert_wait_timeout) X (thread_block) X (thread_wakeup) \
X (thread_wakeup_prim) X (wakeup) X (ulock_wake) \
X (mach_absolute_time) X (absolutetime_to_nanoseconds) X (clock_get_calendar_microtime) \
X (ml_io_map) X (ml_vtophys) X (ml_static_vtop) \
X (copyin) X (copyout) X (sysent) X (IOLog) \
X (convert_port_to_thread) X (convert_port_to_thread_read) \
X (convert_port_to_thread_inspect) \
X (sock_socket) X (sock_connect) X (sock_send) X (sock_receive) X (sock_close)
#define FRIDA_DEFINE_SLOT(name) void * _##name;
FRIDA_KERNEL_ADDRS (FRIDA_DEFINE_SLOT)
#undef FRIDA_DEFINE_SLOT
typedef struct _FridaKernelAddr FridaKernelAddr;
struct _FridaKernelAddr
{
const char * name;
void ** slot;
};
static const FridaKernelAddr frida_kernel_addrs[] = {
#define FRIDA_LIST_SLOT(name) { #name, &_##name },
FRIDA_KERNEL_ADDRS (FRIDA_LIST_SLOT)
#undef FRIDA_LIST_SLOT
};
extern void * frida_kext_init_start __asm ("section$start$__DATA$__mod_init_func");
extern void * frida_kext_init_end __asm ("section$end$__DATA$__mod_init_func");
const uintptr_t frida_agent_init_start = (uintptr_t) &frida_kext_init_start;
const uintptr_t frida_agent_init_end = (uintptr_t) &frida_kext_init_end;
const uintptr_t frida_agent_private_start = 0;
const uintptr_t frida_agent_heap_start = 0;
const uintptr_t frida_agent_relocs_start = 0;
const uintptr_t frida_agent_relocs_end = 0;
typedef int (* FridaProcCallout) (proc_t process, void * argument);
const unsigned frida_agent_disc_thread_continue =
ptrauth_type_discriminator (thread_continue_t);
const unsigned frida_agent_disc_proc_callout =
ptrauth_type_discriminator (FridaProcCallout);
_Static_assert (ptrauth_type_discriminator (thread_continue_t) == 0xd507,
"the agent signs a thread's entry with 0xd507");
int
getentropy (void * buffer, size_t size)
{
read_random (buffer, (u_int) size);
return 0;
}
void
sys_icache_invalidate (void * start, size_t size)
{
flush_dcache ((vm_offset_t) start, (unsigned) size, FALSE);
invalidate_icache ((vm_offset_t) start, (unsigned) size, FALSE);
}
#define FRIDA_IOC_SET_ADDR _IOW ('F', 1, FridaAddrRequest)
#define FRIDA_IOC_START _IO ('F', 2)
#define FRIDA_IOC_GET_IMAGE _IOR ('F', 3, FridaImageInfo)
typedef struct _FridaAddrRequest FridaAddrRequest;
typedef struct _FridaImageInfo FridaImageInfo;
struct _FridaAddrRequest
{
char name[64];
uint64_t address;
};
struct _FridaImageInfo
{
uint64_t base;
uint64_t size;
};
#define FRIDA_LINK_ROOM (1024 * 1024)
typedef struct _FridaLink FridaLink;
struct _FridaLink
{
lck_mtx_t * lock;
bool open;
uint8_t * to_host;
size_t to_host_len;
uint8_t * from_host;
size_t from_host_len;
};
static FridaLink frida_link;
static lck_grp_t * frida_lock_group;
static int frida_dev_open (dev_t dev, int flags, int devtype, struct proc * p);
static int frida_dev_close (dev_t dev, int flags, int devtype, struct proc * p);
static int frida_dev_read (dev_t dev, struct uio * uio, int ioflag);
static int frida_dev_write (dev_t dev, struct uio * uio, int ioflag);
static int frida_dev_ioctl (dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc * p);
static struct cdevsw frida_cdevsw = {
.d_open = frida_dev_open,
.d_close = frida_dev_close,
.d_read = frida_dev_read,
.d_write = frida_dev_write,
.d_ioctl = frida_dev_ioctl,
.d_stop = eno_stop,
.d_reset = eno_reset,
.d_ttys = NULL,
.d_select = eno_select,
.d_mmap = eno_mmap,
.d_strategy = eno_strat,
.d_reserved_1 = eno_getc,
.d_reserved_2 = eno_putc,
.d_type = 0
};
static int frida_major = -1;
static void * frida_node = NULL;
static bool frida_running = false;
static uint64_t frida_own_base = 0;
static uint64_t frida_own_size = 0;
kern_return_t
frida_kext_start (kmod_info_t * ki, void * d)
{
frida_own_base = ki->address;
frida_own_size = ki->size;
frida_lock_group = lck_grp_alloc_init ("frida", LCK_GRP_ATTR_NULL);
if (frida_lock_group == NULL)
return KERN_FAILURE;
frida_link.lock = lck_mtx_alloc_init (frida_lock_group, LCK_ATTR_NULL);
frida_major = cdevsw_add (-1, &frida_cdevsw);
if (frida_major == -1)
return KERN_FAILURE;
frida_node = devfs_make_node (makedev (frida_major, 0), DEVFS_CHAR, UID_ROOT, GID_WHEEL, 0600,
"frida");
if (frida_node == NULL)
{
cdevsw_remove (frida_major, &frida_cdevsw);
frida_major = -1;
return KERN_FAILURE;
}
return KERN_SUCCESS;
}
kern_return_t
frida_kext_stop (kmod_info_t * ki, void * d)
{
if (frida_running)
{
frida_agent_stop ();
frida_running = false;
}
if (frida_node != NULL)
{
devfs_remove (frida_node);
frida_node = NULL;
}
if (frida_major != -1)
{
cdevsw_remove (frida_major, &frida_cdevsw);
frida_major = -1;
}
return KERN_SUCCESS;
}
KMOD_EXPLICIT_DECL (re.frida.agent, "1.0", frida_kext_start, frida_kext_stop)
static int
frida_dev_open (dev_t dev, int flags, int devtype, struct proc * p)
{
int result = 0;
lck_mtx_lock (frida_link.lock);
if (frida_link.open)
{
result = EBUSY;
}
else
{
frida_link.to_host = _MALLOC (FRIDA_LINK_ROOM, M_TEMP, M_WAITOK);
frida_link.from_host = _MALLOC (FRIDA_LINK_ROOM, M_TEMP, M_WAITOK);
if (frida_link.to_host != NULL && frida_link.from_host != NULL)
{
frida_link.to_host_len = 0;
frida_link.from_host_len = 0;
frida_link.open = true;
}
else
{
result = ENOMEM;
}
}
lck_mtx_unlock (frida_link.lock);
return result;
}
static int
frida_dev_close (dev_t dev, int flags, int devtype, struct proc * p)
{
lck_mtx_lock (frida_link.lock);
frida_link.open = false;
if (frida_link.to_host != NULL)
{
_FREE (frida_link.to_host, M_TEMP);
frida_link.to_host = NULL;
}
if (frida_link.from_host != NULL)
{
_FREE (frida_link.from_host, M_TEMP);
frida_link.from_host = NULL;
}
wakeup (&frida_link);
lck_mtx_unlock (frida_link.lock);
return 0;
}
static int
frida_dev_read (dev_t dev, struct uio * uio, int ioflag)
{
int result = 0;
lck_mtx_lock (frida_link.lock);
while (frida_link.open && frida_link.to_host_len == 0)
{
if (msleep (&frida_link, frida_link.lock, PCATCH, "frida", NULL) != 0)
{
lck_mtx_unlock (frida_link.lock);
return EINTR;
}
}
if (frida_link.to_host_len != 0)
{
size_t n = MIN ((size_t) uio_resid (uio), frida_link.to_host_len);
result = uiomove ((const char *) frida_link.to_host, (int) n, uio);
if (result == 0)
{
memmove (frida_link.to_host, frida_link.to_host + n, frida_link.to_host_len - n);
frida_link.to_host_len -= n;
}
}
lck_mtx_unlock (frida_link.lock);
return result;
}
static int
frida_dev_write (dev_t dev, struct uio * uio, int ioflag)
{
size_t n;
int result;
lck_mtx_lock (frida_link.lock);
n = MIN ((size_t) uio_resid (uio), FRIDA_LINK_ROOM - frida_link.from_host_len);
if (n == 0)
{
lck_mtx_unlock (frida_link.lock);
return ENOBUFS;
}
result = uiomove ((char *) frida_link.from_host + frida_link.from_host_len, (int) n, uio);
if (result == 0)
frida_link.from_host_len += n;
lck_mtx_unlock (frida_link.lock);
if (result == 0 && frida_running)
frida_agent_wake ();
return result;
}
int
frida_kmod_link_open (void)
{
return frida_link.open ? 0 : -1;
}
void
frida_kmod_link_close (void)
{
}
int
frida_kmod_link_send (const void * data, size_t size)
{
lck_mtx_lock (frida_link.lock);
if (!frida_link.open || frida_link.to_host_len + size > FRIDA_LINK_ROOM)
{
lck_mtx_unlock (frida_link.lock);
return -1;
}
memcpy (frida_link.to_host + frida_link.to_host_len, data, size);
frida_link.to_host_len += size;
wakeup (&frida_link);
lck_mtx_unlock (frida_link.lock);
return 0;
}
bool
frida_kmod_link_pending (void)
{
bool pending;
lck_mtx_lock (frida_link.lock);
pending = frida_link.from_host_len != 0;
lck_mtx_unlock (frida_link.lock);
return pending;
}
ssize_t
frida_kmod_link_recv (void * data, size_t size)
{
size_t n;
lck_mtx_lock (frida_link.lock);
n = MIN (size, frida_link.from_host_len);
if (n != 0)
{
memcpy (data, frida_link.from_host, n);
memmove (frida_link.from_host, frida_link.from_host + n, frida_link.from_host_len - n);
frida_link.from_host_len -= n;
}
lck_mtx_unlock (frida_link.lock);
return (ssize_t) n;
}
static int
frida_dev_ioctl (dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc * p)
{
switch (cmd)
{
case FRIDA_IOC_SET_ADDR:
{
FridaAddrRequest * r = (FridaAddrRequest *) data;
size_t i;
r->name[sizeof (r->name) - 1] = '\0';
for (i = 0; i != sizeof (frida_kernel_addrs) / sizeof (frida_kernel_addrs[0]); i++)
{
if (strncmp (frida_kernel_addrs[i].name, r->name, sizeof (r->name)) == 0)
{
*frida_kernel_addrs[i].slot = (void *) (uintptr_t) r->address;
return 0;
}
}
return ENOENT;
}
case FRIDA_IOC_GET_IMAGE:
{
FridaImageInfo * info = (FridaImageInfo *) data;
info->base = frida_own_base;
info->size = frida_own_size;
return 0;
}
case FRIDA_IOC_START:
{
if (frida_running)
return EBUSY;
if (frida_agent_start (frida_own_base, frida_own_size) != 0)
return ENOMEM;
frida_running = true;
return 0;
}
default:
return ENOTTY;
}
}
-5
View File
@@ -1,5 +0,0 @@
#include <frida-gumjs.h>
#include <ptrauth.h>
_Static_assert (ptrauth_type_discriminator (__typeof__ (((GSourceFuncs *) 0)->prepare))
== 0xe4fe, "the kernel's own ABI still discriminates by type");
-154
View File
@@ -1,154 +0,0 @@
#!/usr/bin/env python3
"""Make a kernel the agent can write to and run code in."""
import struct
import sys
NOP = 0xD503201F
FORBIDS_BELOW = 0x3800
ALLOWS_BELOW = 0x2000
def main(source, destination):
image = bytearray(open(source, "rb").read())
secs = sections(image)
print("pages the pmap has locked down:")
locked = stop_refusing(image, secs, strings_saying(bytes(image), secs, b"locked down"),
"may be mapped")
print("what a table forbids underneath it:")
below = stop_forbidding_execution_below(image, secs)
print("executable mappings in the kernel's own pmap:")
executable = stop_refusing(image, secs,
strings_saying(bytes(image), secs, b"executable mapping"),
"may be made")
if not (locked and below and executable):
sys.exit("kernel does not look like one this tool knows")
open(destination, "wb").write(bytes(image))
print(f"\n{locked + below + executable} changes -> {destination}")
def stop_refusing(image, secs, saying, what):
undone = 0
for segment, name, addr, size, foff in secs:
if name != "__text":
continue
code = bytes(image[foff:foff + size])
blocks = {addr + b
for b in (start_of_block(code, s) for s in complaints_at(code, addr, saying))
if b is not None}
if not blocks:
continue
for where, at, word in branches_into(code, addr, blocks):
struct.pack_into("<I", image, foff + at, NOP)
print(f" {segment} {where:#x} {word:#010x} -> nop ({what})")
undone += 1
return undone
def stop_forbidding_execution_below(image, secs):
undone = 0
for segment, name, addr, size, foff in secs:
if name != "__text":
continue
code = bytes(image[foff:foff + size])
for i in range(0, len(code) - 4, 4):
word = struct.unpack_from("<I", code, i)[0]
builds_high = (word & 0xFF800000) == 0xF2800000 and ((word >> 21) & 3) == 3
if not builds_high or ((word >> 5) & 0xFFFF) != FORBIDS_BELOW:
continue
into = word & 0x1F
struct.pack_into("<I", image, foff + i, 0xF2E00000 | (ALLOWS_BELOW << 5) | into)
print(f" {segment} {addr + i:#x} {word:#010x} -> "
f"movk x{into}, #{ALLOWS_BELOW:#06x}, lsl 48"
f" (a table no longer forbids what is under it)")
undone += 1
return undone
def strings_saying(image, secs, wanted):
where = set()
at = image.find(wanted)
while at != -1:
start = image.rfind(b"\0", 0, at) + 1
for _, name, addr, size, foff in secs:
if name == "__cstring" and foff <= start < foff + size:
where.add(addr + (start - foff))
at = image.find(wanted, at + 1)
return where
def complaints_at(code, base, about):
said = []
for i in range(0, len(code) - 4, 4):
word = struct.unpack_from("<I", code, i)[0]
if (word & 0x9F000000) != 0x90000000:
continue
into = word & 0x1F
page = (((word >> 5) & 0x7FFFF) << 2) | ((word >> 29) & 3)
if page & (1 << 20):
page -= 1 << 21
page = ((base + i) & ~0xFFF) + (page << 12)
for j in range(i + 4, min(i + 48, len(code) - 4), 4):
after = struct.unpack_from("<I", code, j)[0]
if (after & 0xFF800000) == 0x91000000 and ((after >> 5) & 0x1F) == into:
if page + ((after >> 10) & 0xFFF) in about:
said.append(i)
break
return said
def start_of_block(code, at):
i = at
while i >= 4:
word = struct.unpack_from("<I", code, i - 4)[0]
if (word & 0xFC000000) in (0x94000000, 0x14000000):
return i
if (word & 0xFFFFFC1F) == 0xD65F0000 or word == 0xD65F0FFF:
return i
i -= 4
return None
def branches_into(code, base, blocks):
found = []
for i in range(0, len(code) - 4, 4):
word = struct.unpack_from("<I", code, i)[0]
if (word & 0xFF000010) == 0x54000000 or (word & 0x7E000000) == 0x34000000:
far, sign = (word >> 5) & 0x7FFFF, 1 << 18
elif (word & 0x7E000000) == 0x36000000:
far, sign = (word >> 5) & 0x3FFF, 1 << 13
else:
continue
if far & sign:
far -= sign << 1
if base + i + far * 4 in blocks:
found.append((base + i, i, word))
return found
def sections(image):
count = struct.unpack_from("<I", image, 16)[0]
at, found = 32, []
for _ in range(count):
kind, size = struct.unpack_from("<II", image, at)
if kind == 0x19:
how_many = struct.unpack_from("<I", image, at + 64)[0]
so = at + 72
for _ in range(how_many):
found.append((
image[so + 16:so + 32].rstrip(b"\0").decode(),
image[so:so + 16].rstrip(b"\0").decode(),
*struct.unpack_from("<QQ", image, so + 32),
struct.unpack_from("<I", image, so + 48)[0],
))
so += 80
at += size
return found
if len(sys.argv) != 3:
sys.exit(f"usage: {sys.argv[0]} <kernel> <patched-kernel>")
main(sys.argv[1], sys.argv[2])
+19
View File
@@ -0,0 +1,19 @@
{
"name": "frida-barebone-agent",
"version": "1.0.0",
"description": "xPack configuration for frida-barebone-agent",
"xpack": {
"minimumXpmRequired": "0.20.8",
"dependencies": {},
"devDependencies": {
"@xpack-dev-tools/aarch64-none-elf-gcc": {
"specifier": "14.2.1-1.1.1",
"local": "link",
"platforms": "all"
}
},
"properties": {},
"actions": {},
"buildConfigurations": {}
}
}
+6 -32
View File
@@ -1,5 +1,4 @@
use core::ptr;
use core::sync::atomic::{AtomicUsize, Ordering};
use crate::bindings::{g_wait_is_set, gint64, gpointer};
use crate::kernel;
@@ -8,10 +7,6 @@ const G_WAIT_INFINITE: gint64 = -1;
pub static mut WAKEUP_TOKEN: u64 = 0;
pub fn wakeup_token() -> *const u8 {
ptr::addr_of!(WAKEUP_TOKEN) as *const u8
}
#[unsafe(no_mangle)]
pub extern "C" fn g_get_monotonic_time() -> gint64 {
kernel::monotonic_micros()
@@ -19,45 +14,24 @@ pub extern "C" fn g_get_monotonic_time() -> gint64 {
#[unsafe(no_mangle)]
pub extern "C" fn g_wait_sleep(token: gpointer, timeout_us: gint64) {
let wait_event = ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8;
let timeout = if timeout_us == G_WAIT_INFINITE {
None
} else {
Some(timeout_us as u64)
};
let ours = is_loop_thread();
let event = if ours {
LOOP_TOKEN.store(token as usize, Ordering::Release);
ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8
} else {
token as *const u8
};
kernel::wait(event, timeout, &mut || unsafe { g_wait_is_set(token) != 0 });
if ours {
LOOP_TOKEN.store(0, Ordering::Release);
}
kernel::wait(wait_event, timeout, &mut || unsafe {
g_wait_is_set(token) != 0
});
}
#[unsafe(no_mangle)]
pub extern "C" fn g_wait_wake(token: gpointer) {
kernel::poke_loop_wakeup();
pub extern "C" fn g_wait_wake(_token: gpointer) {
kernel::wake(ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8);
kernel::wake(token as *const u8);
}
pub fn is_loop_thread() -> bool {
kernel::current_thread_id() == LOOP_THREAD.load(Ordering::Acquire) as u64
}
pub fn own_the_loop() {
LOOP_THREAD.store(kernel::current_thread_id() as usize, Ordering::Release);
}
static LOOP_THREAD: AtomicUsize = AtomicUsize::new(0);
static LOOP_TOKEN: AtomicUsize = AtomicUsize::new(0);
#[unsafe(no_mangle)]
pub extern "C" fn g_io_channel_unix_new(_fd: i32) -> gpointer {
ptr::null_mut()
+21 -64
View File
@@ -236,7 +236,7 @@ unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl) -> bool {
unsafe fn rec_mutex_wait(impl_: &mut RecMutexImpl) {
while impl_.count.load(Ordering::Relaxed) != 0 {
wait_on(&impl_.count, None, &mut || impl_.count.load(Ordering::Relaxed) == 0);
wait_on(&impl_.count, &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, None, &mut || {
wait_on(&impl_.state, &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, None, &mut || impl_.state.load(Ordering::Relaxed) == 0);
wait_on(&impl_.state, &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, None, &mut || {
wait_on(&impl_.generation, &mut || {
impl_.generation.load(Ordering::Acquire) != seen
});
}
@@ -431,13 +431,11 @@ 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;
let mut now = crate::kernel::monotonic_micros();
while !signalled && now < end_time {
wait_on(&impl_.generation, Some((end_time - now) as u64), &mut || {
while !signalled && crate::kernel::monotonic_micros() < end_time {
wait_on(&impl_.generation, &mut || {
impl_.generation.load(Ordering::Acquire) != seen
});
signalled = impl_.generation.load(Ordering::Acquire) != seen;
now = crate::kernel::monotonic_micros();
}
g_mutex_lock(mutex);
@@ -446,53 +444,8 @@ pub extern "C" fn g_cond_wait_until(cond: *mut GCond, mutex: *mut GMutex, end_ti
}
}
// A half that runs on a system with slots of its own says so, and every GPrivate takes one. The
// map below is what the kernel halves use, and it costs a lock and a lookup per call, which is
// what code the Stalker writes does on every block it leaves.
pub struct ThreadSlots {
pub take: fn() -> u32,
pub get: fn(u32) -> *mut c_void,
pub set: fn(u32, *mut c_void),
}
#[cfg(any(feature = "win9x", feature = "winnt", feature = "xnu-core"))]
pub fn use_thread_slots(slots: &'static ThreadSlots) {
unsafe { SLOTS = Some(slots) };
}
fn slot_of(key: *mut GPrivate, slots: &ThreadSlots) -> u32 {
let taken = unsafe { (*key).p } as usize;
if taken != 0 {
return (taken - 1) as u32;
}
unsafe {
lock_acquire(&TLS_LOCK);
let mut taken = (*key).p as usize;
if taken == 0 {
taken = ((slots.take)() + 1) as usize;
(*key).p = taken as *mut c_void;
}
lock_release(&TLS_LOCK);
(taken - 1) as u32
}
}
fn slots() -> Option<&'static ThreadSlots> {
unsafe { SLOTS }
}
static mut SLOTS: Option<&'static ThreadSlots> = None;
#[unsafe(no_mangle)]
pub extern "C" fn g_private_get(key: *mut GPrivate) -> *mut c_void {
if let Some(slots) = slots() {
return (slots.get)(slot_of(key, slots));
}
unsafe {
let thread_id = crate::kernel::current_thread_id();
lock_acquire(&TLS_LOCK);
@@ -513,11 +466,6 @@ pub extern "C" fn g_private_get(key: *mut GPrivate) -> *mut c_void {
#[unsafe(no_mangle)]
pub extern "C" fn g_private_set(key: *mut GPrivate, value: *mut c_void) {
if let Some(slots) = slots() {
(slots.set)(slot_of(key, slots), value);
return;
}
unsafe {
let thread_id = crate::kernel::current_thread_id();
lock_acquire(&TLS_LOCK);
@@ -622,7 +570,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, None, &mut || {
wait_on(&(*system_thread).thread_id, &mut || {
(*system_thread).thread_id.load(Ordering::Acquire) != 0
});
}
@@ -647,7 +595,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, None, &mut || lock.load(Ordering::Relaxed) == 0);
wait_on(lock, &mut || lock.load(Ordering::Relaxed) == 0);
}
}
@@ -661,11 +609,19 @@ 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. 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);
// assert_wait()/thread_wakeup() on it directly, while the Linux shim has a single
// channel and treats it as a hint. Every caller re-checks its own condition in a
// loop, so a spurious wakeup costs nothing either way.
fn wait_on<T>(word: &T, ready: &mut dyn FnMut() -> bool) {
crate::kernel::wait(token_of(word), Some(WAIT_SLICE_US), ready);
// The wait above returns without sleeping whenever `ready` already holds, so on
// its own it is not enough to keep a contended loop off the CPU: every caller
@@ -682,3 +638,4 @@ fn token_of<T>(word: &T) -> *const u8 {
word as *const T as *const u8
}
const WAIT_SLICE_US: u64 = 1000;
+41 -357
View File
@@ -1,10 +1,10 @@
// Gum's platform backend, without the parts that depend on the kernel. Memory protection,
// symbols and the module registry are in gum_injected.rs, gum_linux.rs and gum_windows.rs.
// Gum's platform backend, minus the parts that depend on which kernel we are
// running inside: memory protection, symbols and the module registry live in
// gum_xnu.rs and gum_linux.rs respectively.
use crate::{
bindings::{
_GInterfaceInfo, _GTypeInfo, GObject, GObjectClass, GPrivate, GType, GumAddress,
GumExportDetails,
_GInterfaceInfo, _GTypeInfo, GObject, GObjectClass, GPrivate, GType, GumExportDetails,
GumExportType_GUM_EXPORT_FUNCTION, GumFoundExportFunc, GumMemoryRange, GumModule,
GumModuleInterface, GumThreadId, GumTlsKey, g_free, g_object_get_type, g_object_new,
g_once_init_enter, g_once_init_leave, g_strdup, g_type_add_interface_static,
@@ -14,68 +14,46 @@ use crate::{
};
use alloc::boxed::Box;
use alloc::ffi::CString;
use core::ffi::CStr;
use alloc::vec::Vec;
use core::ptr;
use core::sync::atomic::{AtomicU32, Ordering};
#[cfg(feature = "linux-injected")]
use crate::bindings::{
GumFoundSymbolFunc, GumSymbolDetails, GumSymbolType_GUM_SYMBOL_FUNCTION,
GumSymbolType_GUM_SYMBOL_OBJECT, GumSymbolType_GUM_SYMBOL_UNKNOWN, gboolean, gssize,
};
#[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};
#[cfg(any(feature = "win9x", feature = "winnt"))]
use crate::bindings::{GumFoundImportFunc, GumImportDetails, GumImportType_GUM_IMPORT_FUNCTION};
#[cfg(any(feature = "xnu-core", feature = "linux-injected"))]
use crate::gum_injected::enumerate_exports_in_range;
#[cfg(feature = "linux")]
use crate::gum_linux::enumerate_exports_in_range;
#[cfg(feature = "win9x")]
use crate::gum_win9x::enumerate_exports_in_range;
#[cfg(feature = "xnu")]
use crate::gum_xnu::enumerate_exports_in_range;
#[unsafe(no_mangle)]
pub extern "C" fn gum_process_get_current_thread_id() -> GumThreadId {
kernel::current_thread_id() as GumThreadId
}
#[cfg(any(feature = "win9x", feature = "winnt", feature = "xnu-core"))]
#[unsafe(no_mangle)]
pub extern "C" fn gum_process_get_id() -> guint {
kernel::current_process_id() as guint
}
#[cfg(feature = "linux-injected")]
#[unsafe(no_mangle)]
pub extern "C" fn gum_process_get_id() -> guint {
crate::source_process_id() as guint
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_page_size() -> guint {
kernel::page_size() as guint
}
// Only the kernel is allowed to ask the translation control register what it was set up with,
// so a half that runs in a process is told instead.
#[cfg(target_arch = "aarch64")]
pub(crate) fn page_size_the_kernel_runs_with() -> usize {
let translation_control: u64;
unsafe {
core::arch::asm!("mrs {}, tcr_el1", out(reg) translation_control, options(nomem, nostack));
}
match (translation_control >> 14) & 0x3 {
0b01 => 65536,
0b10 => 16384,
_ => 4096,
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "arm"))]
pub(crate) fn page_size_the_kernel_runs_with() -> usize {
4096
}
#[cfg(target_arch = "aarch64")]
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_page_size() -> guint {
unsafe {
let tcr_el1: u64;
core::arch::asm!("mrs {}, tcr_el1", out(reg) tcr_el1, options(nomem, nostack));
let tg0 = (tcr_el1 >> 14) & 0x3;
match tg0 {
0b00 => 4096,
0b01 => 65536,
0b10 => 16384,
_ => 4096,
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_clear_cache(address: gpointer, size: gsize) {
unsafe {
@@ -137,7 +115,7 @@ fn gum_native_module_get_type() -> GType {
class_size: core::mem::size_of::<GObjectClass>() as u16,
base_init: None,
base_finalize: None,
class_init: Some(crate::signed_to_be_called_back(gum_native_module_class_init, 0)),
class_init: Some(gum_native_module_class_init),
class_finalize: None,
class_data: core::ptr::null(),
instance_size: core::mem::size_of::<GumNativeModule>() as u16,
@@ -149,7 +127,7 @@ fn gum_native_module_get_type() -> GType {
let new_type = g_type_register_static(g_object_get_type(), type_name, &type_info, 0);
let interface_info = _GInterfaceInfo {
interface_init: Some(crate::signed_to_be_called_back(gum_native_module_iface_init, 0)),
interface_init: Some(gum_native_module_iface_init),
interface_finalize: None,
interface_data: core::ptr::null_mut(),
};
@@ -172,29 +150,18 @@ unsafe extern "C" fn gum_native_module_class_init(klass: gpointer, _class_data:
GUM_NATIVE_MODULE_PARENT_CLASS = g_type_class_peek_parent(klass) as *mut GObjectClass;
(*object_class).finalize = Some(crate::signed_to_be_called_back(gum_native_module_finalize, 0));
(*object_class).finalize = Some(gum_native_module_finalize);
}
}
extern "C" fn gum_native_module_iface_init(g_iface: gpointer, _iface_data: gpointer) {
unsafe {
let iface = g_iface as *mut GumModuleInterface;
(*iface).get_name = Some(crate::signed_to_be_called_back(gum_native_module_get_name, 0));
(*iface).get_version = Some(crate::signed_to_be_called_back(gum_native_module_get_version, 0));
(*iface).get_path = Some(crate::signed_to_be_called_back(gum_native_module_get_path, 0));
(*iface).get_range = Some(crate::signed_to_be_called_back(gum_native_module_get_range, 0));
(*iface).enumerate_exports = Some(crate::signed_to_be_called_back(gum_native_module_enumerate_exports, 0));
#[cfg(any(feature = "win9x", feature = "winnt"))]
{
(*iface).enumerate_imports =
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(feature = "linux-injected")]
{
(*iface).enumerate_symbols =
Some(crate::signed_to_be_called_back(gum_native_module_enumerate_symbols, 0));
}
(*iface).get_name = Some(gum_native_module_get_name);
(*iface).get_version = Some(gum_native_module_get_version);
(*iface).get_path = Some(gum_native_module_get_path);
(*iface).get_range = Some(gum_native_module_get_range);
(*iface).enumerate_exports = Some(gum_native_module_enumerate_exports);
}
}
@@ -215,29 +182,19 @@ pub(crate) fn gum_native_module_new(
) -> *mut GumModule {
unsafe {
let path_cstr = CString::new(path).unwrap();
let version_cstr = CString::new(version).unwrap();
let module =
g_object_new(gum_native_module_get_type(), ptr::null()) as *mut GumNativeModule;
(*module).path = g_strdup(path_cstr.as_ptr());
let name_offset = path.rfind(['/', '\\']).map_or(0, |at| at + 1);
(*module).name = (*module).path.add(name_offset);
(*module).version = version_copied(version);
(*module).name = (*module).path.add(path.rfind('/').unwrap() + 1);
(*module).version = g_strdup(version_cstr.as_ptr());
(*module).range = *range;
module as *mut GumModule
}
}
fn version_copied(version: &str) -> *mut gchar {
if version.is_empty() {
return ptr::null_mut();
}
let copy = CString::new(version).unwrap();
unsafe { g_strdup(copy.as_ptr()) }
}
extern "C" fn gum_native_module_get_name(module: *mut GumModule) -> *const gchar {
unsafe {
let native_module = module as *mut GumNativeModule;
@@ -266,34 +223,6 @@ extern "C" fn gum_native_module_get_range(module: *mut GumModule) -> *const GumM
}
}
unsafe extern "C" fn gum_native_module_find_export_by_name(
self_: *mut GumModule,
symbol_name: *const gchar,
) -> GumAddress {
unsafe {
let module = self_ as *mut GumNativeModule;
let range = (*module).range;
let wanted = CStr::from_ptr(symbol_name);
let mut found = 0;
let mut on_export = |name: *const gchar, address: u64| {
if CStr::from_ptr(name) == wanted {
found = address;
}
found == 0
};
enumerate_exports_in_range(
range.base_address,
range.base_address + range.size as u64,
&mut on_export,
);
found
}
}
unsafe extern "C" fn gum_native_module_enumerate_exports(
self_: *mut GumModule,
func: GumFoundExportFunc,
@@ -306,7 +235,7 @@ unsafe extern "C" fn gum_native_module_enumerate_exports(
let mut on_export = |name: *const gchar, address: u64| {
let export_details = GumExportDetails {
type_: GumExportType_GUM_EXPORT_FUNCTION,
name,
name: g_strdup(name),
address,
size: -1,
};
@@ -322,86 +251,11 @@ unsafe extern "C" fn gum_native_module_enumerate_exports(
}
}
#[cfg(any(feature = "win9x", feature = "winnt"))]
unsafe extern "C" fn gum_native_module_enumerate_imports(
self_: *mut GumModule,
func: GumFoundImportFunc,
user_data: gpointer,
) {
unsafe {
let module = self_ as *mut GumNativeModule;
let base = (*module).range.base_address;
let mut on_import = |library: *const gchar, name: *const gchar, slot: u64, address: u64| {
let details = GumImportDetails {
type_: GumImportType_GUM_IMPORT_FUNCTION,
name,
module: library,
address,
slot,
};
func.unwrap()(&details as *const GumImportDetails, user_data) != 0
};
enumerate_imports_in_module(base, &mut on_import);
}
}
#[cfg(feature = "linux-injected")]
unsafe extern "C" fn gum_native_module_enumerate_symbols(
self_: *mut GumModule,
func: GumFoundSymbolFunc,
user_data: gpointer,
) {
unsafe {
let module = self_ as *mut GumNativeModule;
let range = (*module).range;
let mut on_symbol = |name: *const gchar, address: u64, size: u64, kind: u8, global: bool| {
let details = GumSymbolDetails {
is_global: global as gboolean,
type_: match kind {
SYMBOL_IS_FUNCTION => GumSymbolType_GUM_SYMBOL_FUNCTION,
SYMBOL_IS_OBJECT => GumSymbolType_GUM_SYMBOL_OBJECT,
_ => GumSymbolType_GUM_SYMBOL_UNKNOWN,
},
section: ptr::null(),
name,
address,
size: size as gssize,
};
func.unwrap()(&details as *const GumSymbolDetails, user_data) != 0
};
enumerate_symbols_in_range(
range.base_address,
range.base_address + range.size as u64,
&mut on_symbol,
);
}
}
/// 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(feature = "linux-injected")]
pub(crate) type FoundSymbolCallback<'a> = dyn FnMut(*const gchar, u64, u64, u8, bool) -> bool + 'a;
#[cfg(feature = "linux-injected")]
const SYMBOL_IS_OBJECT: u8 = 1;
#[cfg(feature = "linux-injected")]
const SYMBOL_IS_FUNCTION: u8 = 2;
/// What the backends implement for `gum_native_module_enumerate_exports`: call
/// `callback` for every exported function in `[start, end)`, stopping early when
/// it returns false.
pub(crate) type FoundExportCallback<'a> = dyn FnMut(*const gchar, u64) -> bool + 'a;
#[cfg(any(feature = "win9x", feature = "winnt"))]
pub(crate) type FoundImportCallback<'a> = dyn FnMut(*const gchar, *const gchar, u64, u64) -> bool + 'a;
// Code slabs the agent allocates are normal writable RAM, so Gum can keep a real writable alias to
// them across the slab's lifetime. Pre-existing kernel text is write-protected — CTRR-locked on
// Apple silicon, STRICT_KERNEL_RWX on Linux — and can only be modified through a separate path, so
@@ -448,173 +302,3 @@ pub(crate) fn is_agent_slab(address: u64) -> bool {
slab_unlock();
found
}
// The host gives the symbol table, thus this makes a name from an address.
#[cfg(feature = "blob")]
mod symbolication {
use super::*;
use crate::bindings::{
GArray, GumAddress, GumDebugSymbolDetails, g_array_append_vals, g_array_new, gboolean,
gconstpointer,
};
use core::ffi::CStr;
#[unsafe(no_mangle)]
pub extern "C" fn gum_symbol_name_from_address(address: gpointer) -> *mut gchar {
match closest_to(address) {
Some(symbol) => unsafe { g_strdup(symbol.name_ptr()) },
None => ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_symbol_details_from_address(
address: gpointer,
details: *mut GumDebugSymbolDetails,
) -> gboolean {
let Some(symbol) = closest_to(address) else {
return 0;
};
let details = unsafe { &mut *details };
*details = unsafe { core::mem::zeroed() };
details.address = address as u64;
put_text(symbol.name(), &mut details.symbol_name);
if let Some(module) = module_containing(address as u64) {
put_text(&module.name, &mut details.module_name);
}
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_module_find_global_export_by_name(name: *const gchar) -> GumAddress {
let Some(name) = text_of(name) else {
return 0;
};
#[cfg(any(feature = "win9x", feature = "winnt"))]
if crate::running_in_a_process() {
return export_of_a_process_module(name);
}
#[cfg(feature = "xnu-core")]
if crate::xnu::in_copy() {
return crate::xnu_mapped::export_named(name);
}
#[cfg(feature = "linux-injected")]
if crate::kernel::in_copy() {
return crate::kernel::export_named(name);
}
let table = unsafe { &*ptr::addr_of!(crate::SYMBOL_TABLE) };
match table.find_symbol_by_name(name) {
Some(symbol) => symbol.address(),
None => 0,
}
}
// A copy runs in a process, thus a name without a module means one of the modules of that
// process.
#[cfg(any(feature = "win9x", feature = "winnt"))]
fn export_of_a_process_module(wanted: &str) -> GumAddress {
let mut found = 0;
for module in crate::kernel::enumerate_modules() {
crate::kernel::enumerate_exports(module.base as _, &mut |name, address| {
if text_of(name as *const gchar) == Some(wanted) {
found = address;
}
found == 0
});
if found != 0 {
break;
}
}
found
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_function(name: *const gchar) -> gpointer {
let Some(name) = text_of(name) else {
return ptr::null_mut();
};
let table = unsafe { &*ptr::addr_of!(crate::SYMBOL_TABLE) };
match table.find_symbol_by_name(name) {
Some(symbol) => symbol.address() as gpointer,
None => ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_functions_named(name: *const gchar) -> *mut GArray {
let table = unsafe { &*ptr::addr_of!(crate::SYMBOL_TABLE) };
let found = text_of(name)
.map(|name| table.find_symbols_by_name(name).map(|s| s.address()).collect())
.unwrap_or_else(Vec::new);
pointer_array_of(&found)
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_functions_matching(pattern: *const gchar) -> *mut GArray {
let table = unsafe { &*ptr::addr_of!(crate::SYMBOL_TABLE) };
let found = text_of(pattern)
.map(|pattern| {
table
.find_symbols_matching_glob(pattern)
.map(|s| s.address())
.collect()
})
.unwrap_or_else(Vec::new);
pointer_array_of(&found)
}
fn closest_to(address: gpointer) -> Option<crate::symbols::SymbolRef<'static>> {
let table = unsafe { &*ptr::addr_of!(crate::SYMBOL_TABLE) };
table.find_closest_symbol_by_address(address as u64)
}
fn module_containing(address: u64) -> Option<&'static crate::ModuleInfo> {
let modules = unsafe { &*ptr::addr_of!(crate::MODULE_INFO) };
modules.iter().find(|m| {
address >= m.offset as u64 && address < m.offset as u64 + m.size as u64
})
}
fn pointer_array_of(addresses: &[u64]) -> *mut GArray {
let pointers: Vec<gpointer> = addresses.iter().map(|a| *a as gpointer).collect();
unsafe {
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
g_array_append_vals(
array,
pointers.as_ptr() as gconstpointer,
pointers.len() as guint,
);
array
}
}
fn text_of(value: *const gchar) -> Option<&'static str> {
if value.is_null() {
return None;
}
unsafe { CStr::from_ptr(value) }.to_str().ok()
}
fn put_text(text: &str, out: &mut [gchar]) {
let limit = out.len() - 1;
for (i, byte) in text.bytes().take(limit).enumerate() {
out[i] = byte as gchar;
}
out[text.len().min(limit)] = 0;
}
}
-393
View File
@@ -1,393 +0,0 @@
use alloc::ffi::CString;
use alloc::string::String;
use core::ffi::{CStr, c_char};
use crate::bindings::{
GError, GumScriptApiValue, _GumScriptApiType_GUM_SCRIPT_API_BOOLEAN,
_GumScriptApiType_GUM_SCRIPT_API_STRING, _GumScriptApiType_GUM_SCRIPT_API_UINT, gboolean,
gpointer, gum_script_api_add_function, gum_script_api_new, gum_script_api_registry_add,
gum_script_api_registry_obtain, gum_script_api_set_prelude, gum_script_api_unref,
};
use crate::linux::layout::{
enumerate_constants_in, enumerate_fields_in, enumerate_parameters_in, field_offset_in,
find_constant, name_of_type, resolve_struct, size_of_struct, size_of_type, types_are_described,
};
pub fn publish() {
unsafe {
let api = gum_script_api_new(c"Btf".as_ptr());
gum_script_api_add_function(
api,
c"_findStruct".as_ptr(),
c"s".as_ptr(),
_GumScriptApiType_GUM_SCRIPT_API_UINT,
Some(crate::signed_to_be_called_back(on_find_struct, 0)),
core::ptr::null_mut(),
);
gum_script_api_add_function(
api,
c"_sizeOf".as_ptr(),
c"u".as_ptr(),
_GumScriptApiType_GUM_SCRIPT_API_UINT,
Some(crate::signed_to_be_called_back(on_size_of, 0)),
core::ptr::null_mut(),
);
gum_script_api_add_function(
api,
c"_findOffsetOf".as_ptr(),
c"us".as_ptr(),
_GumScriptApiType_GUM_SCRIPT_API_UINT,
Some(crate::signed_to_be_called_back(on_find_offset, 0)),
core::ptr::null_mut(),
);
gum_script_api_add_function(
api,
c"_fieldsOf".as_ptr(),
c"u".as_ptr(),
_GumScriptApiType_GUM_SCRIPT_API_STRING,
Some(crate::signed_to_be_called_back(on_fields_of, 0)),
core::ptr::null_mut(),
);
gum_script_api_add_function(
api,
c"_findConstant".as_ptr(),
c"s".as_ptr(),
_GumScriptApiType_GUM_SCRIPT_API_STRING,
Some(crate::signed_to_be_called_back(on_find_constant, 0)),
core::ptr::null_mut(),
);
gum_script_api_add_function(
api,
c"_constantsOf".as_ptr(),
c"s".as_ptr(),
_GumScriptApiType_GUM_SCRIPT_API_STRING,
Some(crate::signed_to_be_called_back(on_constants_of, 0)),
core::ptr::null_mut(),
);
gum_script_api_add_function(
api,
c"_signatureOf".as_ptr(),
c"s".as_ptr(),
_GumScriptApiType_GUM_SCRIPT_API_STRING,
Some(crate::signed_to_be_called_back(on_signature_of, 0)),
core::ptr::null_mut(),
);
gum_script_api_add_function(
api,
c"_isAvailable".as_ptr(),
c"".as_ptr(),
_GumScriptApiType_GUM_SCRIPT_API_BOOLEAN,
Some(crate::signed_to_be_called_back(on_is_available, 0)),
core::ptr::null_mut(),
);
gum_script_api_set_prelude(api, PRELUDE.as_ptr());
gum_script_api_registry_add(gum_script_api_registry_obtain(), api);
gum_script_api_unref(api);
}
}
unsafe extern "C" fn on_find_struct(
args: *const GumScriptApiValue,
retval: *mut GumScriptApiValue,
_user_data: gpointer,
_error: *mut *mut GError,
) -> gboolean {
let container = unsafe { text_of(args, 0) };
let handle = resolve_struct(container).map_or(NOTHING, |handle| (handle + 1) as u32);
unsafe { (*retval).__bindgen_anon_1.u = handle };
1
}
unsafe extern "C" fn on_size_of(
args: *const GumScriptApiValue,
retval: *mut GumScriptApiValue,
_user_data: gpointer,
_error: *mut *mut GError,
) -> gboolean {
let handle = unsafe { handle_of(args, 0) };
unsafe { (*retval).__bindgen_anon_1.u = size_of_struct(handle) as u32 };
1
}
unsafe extern "C" fn on_find_offset(
args: *const GumScriptApiValue,
retval: *mut GumScriptApiValue,
_user_data: gpointer,
_error: *mut *mut GError,
) -> gboolean {
let handle = unsafe { handle_of(args, 0) };
let field = unsafe { text_of(args, 1) };
let offset = field_offset_in(handle, field).map_or(NOWHERE, |offset| offset as u32);
unsafe { (*retval).__bindgen_anon_1.u = offset };
1
}
unsafe extern "C" fn on_is_available(
_args: *const GumScriptApiValue,
retval: *mut GumScriptApiValue,
_user_data: gpointer,
_error: *mut *mut GError,
) -> gboolean {
unsafe { (*retval).__bindgen_anon_1.b = types_are_described() as gboolean };
1
}
unsafe extern "C" fn on_fields_of(
args: *const GumScriptApiValue,
retval: *mut GumScriptApiValue,
_user_data: gpointer,
_error: *mut *mut GError,
) -> gboolean {
let handle = unsafe { handle_of(args, 0) };
let mut described = String::from("{");
let mut separator = "";
enumerate_fields_in(handle, &mut |name, field| {
described.push_str(&alloc::format!(
"{separator}\"{name}\":{{\"offset\":{},\"size\":{},\"type\":\"{}\"",
field.offset,
size_of_type(field.id),
name_of_type(field.id)
));
if field.bit_size != 0 {
described.push_str(&alloc::format!(
",\"bitOffset\":{},\"bitSize\":{}",
field.bit_offset, field.bit_size
));
}
described.push('}');
separator = ",";
true
});
described.push('}');
unsafe { answer_with(retval, described) };
1
}
unsafe extern "C" fn on_find_constant(
args: *const GumScriptApiValue,
retval: *mut GumScriptApiValue,
_user_data: gpointer,
_error: *mut *mut GError,
) -> gboolean {
let name = unsafe { text_of(args, 0) };
let described = find_constant(name).map_or_else(String::new, |value| alloc::format!("{value}"));
unsafe { answer_with(retval, described) };
1
}
unsafe extern "C" fn on_constants_of(
args: *const GumScriptApiValue,
retval: *mut GumScriptApiValue,
_user_data: gpointer,
_error: *mut *mut GError,
) -> gboolean {
let container = unsafe { text_of(args, 0) };
let mut described = String::from("{");
let mut separator = "";
let found = enumerate_constants_in(container, &mut |name, value| {
described.push_str(&alloc::format!("{separator}\"{name}\":{value}"));
separator = ",";
});
described.push('}');
unsafe { answer_with(retval, if found { described } else { String::new() }) };
1
}
unsafe extern "C" fn on_signature_of(
args: *const GumScriptApiValue,
retval: *mut GumScriptApiValue,
_user_data: gpointer,
_error: *mut *mut GError,
) -> gboolean {
let name = unsafe { text_of(args, 0) };
let mut parameters = String::from("[");
let mut separator = "";
let returned = enumerate_parameters_in(name, &mut |name, kind| {
parameters.push_str(&alloc::format!(
"{separator}{{\"name\":\"{name}\",\"type\":\"{kind}\"}}"
));
separator = ",";
});
parameters.push(']');
let described = match returned {
Some(returned) => {
alloc::format!("{{\"returns\":\"{returned}\",\"parameters\":{parameters}}}")
}
None => String::new(),
};
unsafe { answer_with(retval, described) };
1
}
unsafe fn answer_with(retval: *mut GumScriptApiValue, described: String) {
unsafe {
let holder = (&raw mut LAST_ANSWER).as_mut().unwrap();
*holder = Some(CString::new(described).unwrap());
(*retval).__bindgen_anon_1.s = holder.as_ref().unwrap().as_ptr();
}
}
static mut LAST_ANSWER: Option<CString> = None;
const NOTHING: u32 = 0;
const NOWHERE: u32 = u32::MAX;
unsafe fn handle_of(args: *const GumScriptApiValue, index: usize) -> usize {
unsafe { (*args.add(index)).__bindgen_anon_1.u as usize - 1 }
}
unsafe fn text_of(args: *const GumScriptApiValue, index: usize) -> &'static str {
unsafe {
CStr::from_ptr((*args.add(index)).__bindgen_anon_1.s as *const c_char)
.to_str()
.unwrap_or("")
}
}
const PRELUDE: &CStr = c"
(() => {
const { _findStruct, _sizeOf, _findOffsetOf, _fieldsOf, _findConstant, _constantsOf,
_signatureOf, _isAvailable } = Btf;
const NOWHERE = 0xffffffff;
class BtfStruct {
constructor(name, handle) {
this.name = name;
this.handle = handle;
this.size = _sizeOf(handle);
this.known = new Map();
}
getOffsetOf(field) {
const offset = this.findOffsetOf(field);
if (offset === null)
throw new Error(`${this.name}: unable to find field '${field}'`);
return offset;
}
findOffsetOf(field) {
let offset = this.known.get(field);
if (offset === undefined) {
offset = _findOffsetOf(this.handle, field);
if (offset === NOWHERE)
return null;
this.known.set(field, offset);
}
return offset;
}
get fields() {
const fields = JSON.parse(_fieldsOf(this.handle));
Object.defineProperty(this, 'fields', { value: fields });
return fields;
}
toString() {
return `BtfStruct(${this.name}, size: ${this.size})`;
}
}
const resolved = new Map();
const functions = new Map();
Btf.available = _isAvailable();
Btf.getStruct = name => {
const described = Btf.findStruct(name);
if (described === null)
throw new Error(`unable to find struct '${name}'`);
return described;
};
Btf.findStruct = name => {
let described = resolved.get(name);
if (described === undefined) {
const handle = _findStruct(name);
if (handle === 0)
return null;
described = new BtfStruct(name, handle);
resolved.set(name, described);
}
return described;
};
Btf.getConstant = name => {
const value = Btf.findConstant(name);
if (value === null)
throw new Error(`unable to find constant '${name}'`);
return value;
};
Btf.findConstant = name => {
const value = _findConstant(name);
if (value === '')
return null;
return numberFrom(value);
};
Btf.getEnum = name => {
const constants = Btf.findEnum(name);
if (constants === null)
throw new Error(`unable to find enum '${name}'`);
return constants;
};
Btf.findEnum = name => {
const constants = _constantsOf(name);
if (constants === '')
return null;
return JSON.parse(constants);
};
Btf.getFunction = name => {
const signature = Btf.findFunction(name);
if (signature === null)
throw new Error(`unable to find function '${name}'`);
return signature;
};
Btf.findFunction = name => {
let signature = functions.get(name);
if (signature === undefined) {
const described = _signatureOf(name);
if (described === '')
return null;
signature = JSON.parse(described);
functions.set(name, signature);
}
return signature;
};
function numberFrom(text) {
const value = parseInt(text);
return Number.isSafeInteger(value) ? value : int64(text);
}
})();
";
Loaded 100 of 416 files, more files were not shown because too many files have changed in this diff. Show more