mirror of
https://github.com/frida/frida-core.git
synced 2026-11-08 08:05:58 -10:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f67d1e81df | ||
|
|
a068c704d1 | ||
|
|
34ac3a0d50 | ||
|
|
1ea81bcd59 |
No files matched your search
@@ -20,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"' }
|
||||
@@ -71,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 }}
|
||||
@@ -658,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
|
||||
@@ -668,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:
|
||||
@@ -729,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
|
||||
|
||||
+1
-9
@@ -2,15 +2,7 @@
|
||||
/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/linux/*.o
|
||||
/src/barebone/agent/linux/*.o.bin
|
||||
/src/barebone/agent/linux/.*.cmd
|
||||
/src/barebone/agent/linux/Module.symvers
|
||||
/src/barebone/agent/linux/modules.order
|
||||
/src/barebone/agent/target*/
|
||||
/src/barebone/agent/target/
|
||||
/src/barebone/agent/xpacks/
|
||||
/src/barebone/helpers/memory-scanner/target/
|
||||
/src/barebone/helpers/target/
|
||||
|
||||
@@ -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
|
||||
|
||||
+11
-15
@@ -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")
|
||||
|
||||
|
||||
@@ -592,10 +592,8 @@ def load_meson_options(top_builddir: Path,
|
||||
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":
|
||||
@@ -605,46 +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
|
||||
|
||||
our_project_id = "frida-core" if role == "subproject" else ""
|
||||
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 == "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 k.subproject in {"", our_project_id}:
|
||||
if not options.BUILTIN_OPTIONS[k.evolve(subproject=None, machine=MachineChoice.HOST)].yielding:
|
||||
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):
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
+51
-161
@@ -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;
|
||||
@@ -125,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);
|
||||
@@ -191,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);
|
||||
@@ -227,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;
|
||||
@@ -322,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 {
|
||||
@@ -335,6 +292,8 @@ namespace Frida.Agent {
|
||||
|
||||
start.begin ((owned) padder);
|
||||
|
||||
start_alive_task_dumper ();
|
||||
|
||||
main_loop.run ();
|
||||
|
||||
main_context.pop_thread_default ();
|
||||
@@ -370,7 +329,24 @@ namespace Frida.Agent {
|
||||
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);
|
||||
@@ -380,24 +356,6 @@ namespace Frida.Agent {
|
||||
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);
|
||||
});
|
||||
@@ -406,58 +364,14 @@ namespace Frida.Agent {
|
||||
padder = null;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
// TEMPORARY DEBUG HACK: dump alive GTasks every 30 seconds.
|
||||
private void start_alive_task_dumper () {
|
||||
var source = new TimeoutSource.seconds (30);
|
||||
source.set_callback (() => {
|
||||
GLib.info ("%s", g_task_describe_alive_tasks ());
|
||||
return Source.CONTINUE;
|
||||
});
|
||||
source.attach (main_context);
|
||||
}
|
||||
|
||||
private void keep_running_eternalized () {
|
||||
@@ -1022,6 +936,8 @@ namespace Frida.Agent {
|
||||
private void on_session_closed (BaseAgentSession base_session) {
|
||||
LiveAgentSession session = (LiveAgentSession) base_session;
|
||||
|
||||
GLib.info ("detach-trace: Agent emitting provider closed id=%s", session.id.handle);
|
||||
|
||||
closed (session.id);
|
||||
|
||||
unregister_session (session);
|
||||
@@ -1121,10 +1037,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 ();
|
||||
}
|
||||
|
||||
@@ -1170,32 +1082,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 ();
|
||||
}
|
||||
|
||||
private void ensure_eternalized () {
|
||||
if (!_is_eternal) {
|
||||
_is_eternal = true;
|
||||
@@ -1404,7 +1290,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;
|
||||
@@ -1852,4 +1738,8 @@ namespace Frida.Agent {
|
||||
return repeat;
|
||||
});
|
||||
}
|
||||
|
||||
// TEMPORARY DEBUG HACK: only present when GLib is built with G_ENABLE_DEBUG.
|
||||
[CCode (cname = "g_task_describe_alive_tasks")]
|
||||
private extern string g_task_describe_alive_tasks ();
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
global:
|
||||
frida_agent_main;
|
||||
frida_prospero_agent_args;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
@@ -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,
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public sealed class ChannelRegistry : Object {
|
||||
public signal void channel_closed (ChannelId id);
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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()
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using Efni;
|
||||
|
||||
#if HAVE_NICE
|
||||
namespace Frida {
|
||||
namespace PeerConnection {
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
+53
-67
@@ -1,5 +1,3 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
[DBus (name = "re.frida.HostSession17")]
|
||||
public interface HostSession : Object {
|
||||
@@ -126,11 +124,6 @@ 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;
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.AgentMessageSink17")]
|
||||
@@ -368,6 +361,8 @@ namespace Frida {
|
||||
|
||||
private async void teardown_peer_connection_and_emit_closed () {
|
||||
schedule_on_frida_thread (() => {
|
||||
GLib.info ("detach-trace: AgentMessageTransmitter %p emitting closed, nice_agent=%p",
|
||||
this, nice_agent);
|
||||
if (nice_agent != null)
|
||||
close_nice_resources_and_emit_closed.begin ();
|
||||
else
|
||||
@@ -605,6 +600,7 @@ namespace Frida {
|
||||
|
||||
private async void teardown_peer_connection_and_emit_closed () {
|
||||
schedule_on_frida_thread (() => {
|
||||
GLib.info ("detach-trace: AgentMessageTransmitter %p emitting closed", this);
|
||||
closed ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
@@ -1140,15 +1136,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;
|
||||
@@ -1159,8 +1146,6 @@ namespace Frida {
|
||||
public struct LinuxInjectorState {
|
||||
public int frida_ctrlfd;
|
||||
public int agent_ctrlfd;
|
||||
public uint64 mapped_base;
|
||||
public size_t mapped_size;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1170,14 +1155,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}.
|
||||
*/
|
||||
@@ -1299,9 +1276,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;
|
||||
|
||||
@@ -1315,9 +1289,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;
|
||||
|
||||
@@ -1330,41 +1301,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;
|
||||
@@ -2827,4 +2763,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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 ();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida.Gadget {
|
||||
private sealed class Config : Object, Json.Serializable {
|
||||
public Object interaction {
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public abstract class BaseAgentSession : Object, AgentSession {
|
||||
public signal void closed ();
|
||||
@@ -59,6 +57,8 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
GLib.info ("detach-trace: BaseAgentSession.close() id=%s close_request=%p", id.handle, close_request);
|
||||
|
||||
while (close_request != null) {
|
||||
try {
|
||||
yield close_request.future.wait_async (cancellable);
|
||||
@@ -87,6 +87,8 @@ namespace Frida {
|
||||
|
||||
yield transmitter.close (cancellable);
|
||||
|
||||
GLib.info ("detach-trace: BaseAgentSession.close() done id=%s", id.handle);
|
||||
|
||||
close_request.resolve (true);
|
||||
}
|
||||
|
||||
@@ -278,6 +280,8 @@ namespace Frida {
|
||||
transmitter.new_candidates.disconnect (on_transmitter_new_candidates);
|
||||
transmitter.candidate_gathering_done.disconnect (on_transmitter_candidate_gathering_done);
|
||||
|
||||
GLib.info ("detach-trace: BaseAgentSession emitting closed id=%s", id.handle);
|
||||
|
||||
closed ();
|
||||
}
|
||||
|
||||
|
||||
@@ -35,21 +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);
|
||||
}
|
||||
#else
|
||||
var libc = Gum.Process.get_libc_module ();
|
||||
foreach (var symbol in apis) {
|
||||
interceptor.attach ((void *) libc.find_export_by_name (symbol), listener);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
+21
-28
@@ -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,5 +1,3 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public sealed class PortalClient : Object, AgentSessionProvider {
|
||||
public signal void resume ();
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public extern void libc_shim_prepare_to_deinit ();
|
||||
#if DARWIN
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
+2
-15
@@ -12,27 +12,14 @@ frida_pipe_get_sddl_string_for_pipe (void)
|
||||
#define DACL_ACE_EVERYONE_RW L"(A;;GRGW;;;WD)"
|
||||
#define SACL_START L"S:"
|
||||
#define SACL_ACE_LOWINTEGRITY_NORW L"(ML;;NWNR;;;LW)"
|
||||
#define DACL_ACE_SYSTEM_RW L"(A;;GRGW;;;SY)"
|
||||
#define DACL_ACE_ADMINS_RW L"(A;;GRGW;;;BA)"
|
||||
|
||||
if (frida_pipe_is_windows_8_or_greater ())
|
||||
{
|
||||
return DACL_START_NOINHERIT
|
||||
DACL_ACE_APPCONTAINER_RW
|
||||
DACL_ACE_EVERYONE_RW
|
||||
DACL_ACE_SYSTEM_RW
|
||||
DACL_ACE_ADMINS_RW
|
||||
SACL_START
|
||||
SACL_ACE_LOWINTEGRITY_NORW;
|
||||
return DACL_START_NOINHERIT DACL_ACE_APPCONTAINER_RW DACL_ACE_EVERYONE_RW SACL_START SACL_ACE_LOWINTEGRITY_NORW;
|
||||
}
|
||||
else if (frida_pipe_is_windows_vista_or_greater ())
|
||||
{
|
||||
return DACL_START_NOINHERIT
|
||||
DACL_ACE_EVERYONE_RW
|
||||
DACL_ACE_SYSTEM_RW
|
||||
DACL_ACE_ADMINS_RW
|
||||
SACL_START
|
||||
SACL_ACE_LOWINTEGRITY_NORW;
|
||||
return DACL_START_NOINHERIT DACL_ACE_EVERYONE_RW SACL_START SACL_ACE_LOWINTEGRITY_NORW;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+1
-3
@@ -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);
|
||||
|
||||
+54
-83
@@ -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',
|
||||
@@ -412,27 +399,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 +419,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 +433,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 +471,6 @@ endif
|
||||
|
||||
if host_os == 'android'
|
||||
minizip_dep = dependency('minizip', required: false, default_options: [
|
||||
'default_library=static',
|
||||
'zlib=enabled',
|
||||
'lzma=disabled',
|
||||
])
|
||||
@@ -561,7 +527,7 @@ have_droidy_backend = get_option('droidy_backend') \
|
||||
have_socket_backend = get_option('socket_backend').allowed()
|
||||
have_barebone_backend = get_option('barebone_backend') \
|
||||
.disable_if(not (quickjs_dep.found() and lzfse_dep.found())) \
|
||||
.disable_auto_if(not host_docks_mobile_devices and host_os != 'android') \
|
||||
.disable_auto_if(not host_docks_mobile_devices) \
|
||||
.allowed()
|
||||
compiler_backend_opt = get_option('compiler_backend') \
|
||||
.disable_auto_if(host_os == 'watchos')
|
||||
@@ -598,11 +564,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 +595,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
|
||||
|
||||
@@ -726,32 +688,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_blob = false
|
||||
if have_fruity_backend
|
||||
idun_dep = dependency('idun-1.0', default_options: ['default_library=static'])
|
||||
backend_reqs_private += 'idun-1.0'
|
||||
if idun_dep.type_name() == 'internal'
|
||||
idun_sp = subproject('idun')
|
||||
idun_libs += idun_sp.get_variable('idun')
|
||||
if assets_mode == 'embedded' and idun_sp.get_variable('build_daemon')
|
||||
idund_raw = idun_sp.get_variable('idund')
|
||||
have_idund_blob = true
|
||||
vala_flags += '--define=HAVE_IDUND_BLOB'
|
||||
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()
|
||||
@@ -773,20 +757,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
|
||||
@@ -901,19 +879,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'
|
||||
@@ -980,7 +952,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]
|
||||
@@ -990,13 +962,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: a3f1a54307...abb8238970.
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+87
-6
@@ -1,5 +1,3 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida.Server {
|
||||
private static Application application;
|
||||
|
||||
@@ -14,6 +12,7 @@ namespace Frida.Server {
|
||||
private static string? directory = null;
|
||||
#if !WINDOWS && !TVOS
|
||||
private static bool daemonize = false;
|
||||
private static string? output_path = null;
|
||||
#endif
|
||||
private static string? softener_flavor_str = null;
|
||||
private static bool enable_preload = true;
|
||||
@@ -44,6 +43,7 @@ namespace Frida.Server {
|
||||
{ "directory", 'd', 0, OptionArg.STRING, ref directory, "Store binaries in DIRECTORY", "DIRECTORY" },
|
||||
#if !WINDOWS && !TVOS
|
||||
{ "daemonize", 'D', 0, OptionArg.NONE, ref daemonize, "Detach and become a daemon", null },
|
||||
{ "output", 'o', 0, OptionArg.FILENAME, ref output_path, "Redirect output to FILE", "FILE" },
|
||||
#endif
|
||||
{ "policy-softener", 0, 0, OptionArg.STRING, ref softener_flavor_str, "Select policy softener", "system|internal" },
|
||||
{ "disable-preload", 'P', OptionFlags.REVERSE, OptionArg.NONE, ref enable_preload, "Disable preload optimization", null },
|
||||
@@ -80,6 +80,13 @@ namespace Frida.Server {
|
||||
|
||||
Environment.set_verbose_logging_enabled (verbose);
|
||||
|
||||
#if !WINDOWS && !TVOS
|
||||
if (output_path != null && !daemonize) {
|
||||
if (!redirect_output_to (output_path))
|
||||
return 6;
|
||||
}
|
||||
#endif
|
||||
|
||||
EndpointParameters endpoint_params;
|
||||
try {
|
||||
endpoint_params = new EndpointParameters (listen_address, 0, parse_certificate (certpath), origin,
|
||||
@@ -158,12 +165,15 @@ namespace Frida.Server {
|
||||
Posix.setsid ();
|
||||
|
||||
var null_in = Posix.open ("/dev/null", Posix.O_RDONLY);
|
||||
var null_out = Posix.open ("/dev/null", Posix.O_WRONLY);
|
||||
Posix.dup2 (null_in, Posix.STDIN_FILENO);
|
||||
Posix.dup2 (null_out, Posix.STDOUT_FILENO);
|
||||
Posix.dup2 (null_out, Posix.STDERR_FILENO);
|
||||
Posix.close (null_in);
|
||||
Posix.close (null_out);
|
||||
|
||||
if (output_path == null || !redirect_output_to (output_path)) {
|
||||
var null_out = Posix.open ("/dev/null", Posix.O_WRONLY);
|
||||
Posix.dup2 (null_out, Posix.STDOUT_FILENO);
|
||||
Posix.dup2 (null_out, Posix.STDERR_FILENO);
|
||||
Posix.close (null_out);
|
||||
}
|
||||
}
|
||||
|
||||
var status = new uint8[1];
|
||||
@@ -220,6 +230,22 @@ namespace Frida.Server {
|
||||
return application.run ();
|
||||
}
|
||||
|
||||
#if !WINDOWS && !TVOS
|
||||
private static bool redirect_output_to (string path) {
|
||||
int fd = Posix.open (path, Posix.O_WRONLY | Posix.O_CREAT | Posix.O_APPEND, 0644);
|
||||
if (fd == -1) {
|
||||
printerr ("Unable to open “%s”: %s\n", path, Posix.strerror (Posix.errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
Posix.dup2 (fd, Posix.STDOUT_FILENO);
|
||||
Posix.dup2 (fd, Posix.STDERR_FILENO);
|
||||
Posix.close (fd);
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace Environment {
|
||||
public extern void init ();
|
||||
public extern void set_verbose_logging_enabled (bool enabled);
|
||||
@@ -276,6 +302,15 @@ namespace Frida.Server {
|
||||
return false;
|
||||
});
|
||||
|
||||
#if !WINDOWS && !TVOS
|
||||
if (!daemonize)
|
||||
process_commands.begin ();
|
||||
#elif !WINDOWS
|
||||
process_commands.begin ();
|
||||
#endif
|
||||
|
||||
start_alive_task_dumper ();
|
||||
|
||||
exit_code = 0;
|
||||
|
||||
loop.run ();
|
||||
@@ -283,6 +318,45 @@ namespace Frida.Server {
|
||||
return exit_code;
|
||||
}
|
||||
|
||||
#if !WINDOWS
|
||||
// TEMPORARY DEBUG HACK: type “t” + Enter to dump alive GTasks.
|
||||
private async void process_commands () {
|
||||
var input = new DataInputStream (new UnixInputStream (Posix.STDIN_FILENO, false));
|
||||
|
||||
while (true) {
|
||||
string? line;
|
||||
try {
|
||||
line = yield input.read_line_async (Priority.DEFAULT, io_cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
return;
|
||||
}
|
||||
if (line == null)
|
||||
return;
|
||||
|
||||
switch (line.strip ()) {
|
||||
case "t":
|
||||
g_task_print_alive_tasks ();
|
||||
break;
|
||||
case "":
|
||||
break;
|
||||
default:
|
||||
printerr ("Unknown command; try “t”\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// TEMPORARY DEBUG HACK: dump alive GTasks every 30 seconds.
|
||||
private void start_alive_task_dumper () {
|
||||
var source = new TimeoutSource.seconds (30);
|
||||
source.set_callback (() => {
|
||||
GLib.info ("%s", g_task_describe_alive_tasks ());
|
||||
return Source.CONTINUE;
|
||||
});
|
||||
source.attach (MainContext.default ());
|
||||
}
|
||||
|
||||
private async void start () {
|
||||
try {
|
||||
if (device_id != null && device_id != "local") {
|
||||
@@ -357,4 +431,11 @@ namespace Frida.Server {
|
||||
|
||||
return new TlsCertificate.from_file (path);
|
||||
}
|
||||
|
||||
// TEMPORARY DEBUG HACK: only present when GLib is built with G_ENABLE_DEBUG.
|
||||
[CCode (cname = "g_task_print_alive_tasks")]
|
||||
private extern void g_task_print_alive_tasks ();
|
||||
|
||||
[CCode (cname = "g_task_describe_alive_tasks")]
|
||||
private extern string g_task_describe_alive_tasks ();
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.
@@ -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
@@ -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
@@ -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
|
||||
|
||||
+168
-1030
File diff suppressed because it is too large.
Load diff
Generated
+4
-4
@@ -13,9 +13,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.72.1"
|
||||
version = "0.72.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||
checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
@@ -63,9 +63,9 @@ checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.9.1"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
|
||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
|
||||
@@ -4,31 +4,6 @@ version = "1.0.0"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
name = "frida_barebone_agent"
|
||||
path = "src/lib.rs"
|
||||
crate-type = ["rlib", "staticlib"]
|
||||
|
||||
# The XNU flavour is a freestanding executable the host injects and calls into.
|
||||
# The Linux flavour is the staticlib above, linked into a .ko by linux/Kbuild.
|
||||
[[bin]]
|
||||
name = "frida-barebone-agent"
|
||||
path = "src/main.rs"
|
||||
required-features = ["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"]
|
||||
win9x = ["blob"]
|
||||
winnt = ["blob"]
|
||||
linux = []
|
||||
linux-injected = ["blob"]
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "z"
|
||||
|
||||
@@ -1,75 +1,30 @@
|
||||
## 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"
|
||||
|
||||
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.
|
||||
|
||||
## 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:
|
||||
|
||||
- `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`).
|
||||
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
|
||||
|
||||
## 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 \
|
||||
&& make -C ~/src/frida-python \
|
||||
&& killall -9 qemu-system-aarch64 && sleep 2 \
|
||||
&& frida -D barebone -p 0
|
||||
cargo build --release && make -C ~/src/frida-python && killall -9 qemu-system-aarch64 && sleep 2 && frida -D barebone -p 0
|
||||
|
||||
## Speeding up loop
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,150 +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;
|
||||
_register_module_notifier = .; . += 4;
|
||||
_unregister_module_notifier = .; . += 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:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -1,156 +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;
|
||||
_register_module_notifier = .; . += 8;
|
||||
_unregister_module_notifier = .; . += 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:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -1,146 +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;
|
||||
_register_module_notifier = .; . += 4;
|
||||
_unregister_module_notifier = .; . += 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;
|
||||
_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:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -1,154 +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;
|
||||
_register_module_notifier = .; . += 8;
|
||||
_unregister_module_notifier = .; . += 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;
|
||||
_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:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -1,97 +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);
|
||||
_Get_Cur_VM_Handle = .; . += 4;
|
||||
_Get_Sys_VM_Handle = .; . += 4;
|
||||
_Schedule_Global_Event = .; . += 4;
|
||||
_Create_Semaphore = .; . += 4;
|
||||
_Wait_Semaphore = .; . += 4;
|
||||
_Signal_Semaphore = .; . += 4;
|
||||
_Get_Next_VM_Handle = .; . += 4;
|
||||
_Release_Time_Slice = .; . += 4;
|
||||
_Set_Global_Time_Out = .; . += 4;
|
||||
_Cancel_Time_Out = .; . += 4;
|
||||
_Get_System_Time = .; . += 4;
|
||||
__HeapAllocate = .; . += 4;
|
||||
__HeapFree = .; . += 4;
|
||||
__PageAllocate = .; . += 4;
|
||||
__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;
|
||||
_Get_Cur_Thread_Handle = .; . += 4;
|
||||
|
||||
_Get_Initial_Thread_Handle = .; . += 4;
|
||||
_Get_Next_Thread_Handle = .; . += 4;
|
||||
__Debug_Printf_Service = .; . += 4;
|
||||
__ContextSwitch = .; . += 4;
|
||||
__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*)
|
||||
}
|
||||
. = ALIGN(4);
|
||||
_heap_start = .;
|
||||
}
|
||||
|
||||
VERSION {
|
||||
{
|
||||
global:
|
||||
_start;
|
||||
frida_win9x_user_main;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -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:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -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:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -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:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -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:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+23
-148
@@ -8,17 +8,12 @@ fn main() {
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
let devkit_dir = PathBuf::from(env::var("GUMJS_DEVKIT_DIR").unwrap());
|
||||
|
||||
let target = env::var("TARGET").unwrap();
|
||||
let cc_str = env::var(format!("CC_{}", target.replace('-', "_")))
|
||||
let cc_str = env::var("CC_aarch64_unknown_none")
|
||||
.or_else(|_| env::var("CC"))
|
||||
.unwrap_or_else(|_| "cc".to_string());
|
||||
// Same convention as cc-rs: the variable may carry flags, and a bare-metal
|
||||
// compiler needs them to find its libc at all.
|
||||
let mut cc_argv = cc_str.split_whitespace();
|
||||
let cc = Path::new(cc_argv.next().unwrap());
|
||||
let cc_args: Vec<&str> = cc_argv.collect();
|
||||
let cc_include_paths = detect_gcc_include_paths(cc, &cc_args);
|
||||
let cc_library_paths = detect_gcc_library_paths(cc, &cc_args);
|
||||
let cc = Path::new(&cc_str);
|
||||
let cc_include_paths = detect_gcc_include_paths(cc);
|
||||
let cc_library_paths = detect_gcc_library_paths(cc);
|
||||
|
||||
let cc_clang_args: Vec<String> = cc_include_paths
|
||||
.iter()
|
||||
@@ -30,15 +25,9 @@ fn main() {
|
||||
.header(devkit_dir.join("frida-gumjs.h").to_str().unwrap())
|
||||
.clang_arg("-nostdinc")
|
||||
.clang_arg("-target")
|
||||
.clang_arg(format!("{}-none-elf", target.split('-').next().unwrap()))
|
||||
.clang_arg("aarch64-none-elf")
|
||||
.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,131 +36,25 @@ 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={}",
|
||||
devkit_dir.to_str().unwrap()
|
||||
);
|
||||
println!("cargo:rustc-link-lib=static=frida-gumjs");
|
||||
for path in cc_library_paths {
|
||||
println!("cargo:rustc-link-search=native={}", path.to_string_lossy());
|
||||
}
|
||||
for arg in &cc_args {
|
||||
if let Some(path) = arg.strip_prefix("-L") {
|
||||
println!("cargo:rustc-link-search=native={path}");
|
||||
}
|
||||
}
|
||||
println!("cargo:rustc-link-lib=static=c");
|
||||
println!("cargo:rustc-link-lib=static=m");
|
||||
println!("cargo:rustc-link-arg=--export-dynamic");
|
||||
println!("cargo:rustc-link-arg=--emit-relocs");
|
||||
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"
|
||||
} else {
|
||||
"xnu"
|
||||
};
|
||||
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");
|
||||
|
||||
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}\"}}"
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
devkit_dir.to_str().unwrap()
|
||||
);
|
||||
|
||||
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
|
||||
println!("cargo:rustc-link-lib=static=frida-gumjs");
|
||||
for path in cc_library_paths {
|
||||
println!("cargo:rustc-link-search=native={}", path.to_string_lossy());
|
||||
}
|
||||
println!("cargo:rustc-link-lib=static=c");
|
||||
println!("cargo:rustc-link-lib=static=m");
|
||||
println!("cargo:rustc-link-arg=--export-dynamic");
|
||||
println!("cargo:rustc-link-arg=--emit-relocs");
|
||||
println!("cargo:rustc-link-arg=--script=agent.lds");
|
||||
println!("cargo:rustc-link-arg=--gc-sections");
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
}
|
||||
|
||||
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> {
|
||||
pub fn detect_gcc_include_paths(gcc: &Path) -> Vec<PathBuf> {
|
||||
let out = Command::new(gcc)
|
||||
.args(args)
|
||||
.args(["-xc", "-E", "-v", "-"])
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
@@ -201,7 +84,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);
|
||||
}
|
||||
@@ -211,16 +94,8 @@ 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()
|
||||
pub fn detect_gcc_library_paths(gcc: &Path) -> Vec<PathBuf> {
|
||||
let out = Command::new(gcc).arg("-print-search-dirs").output()
|
||||
.expect("Failed to execute GCC to detect library paths");
|
||||
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
|
||||
@@ -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,8 +0,0 @@
|
||||
{
|
||||
"agent": {
|
||||
"transport": {
|
||||
"type": "socket",
|
||||
"path": "/tmp/frida-guest.sock"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"agent": {
|
||||
"transport": {
|
||||
"type": "device",
|
||||
"path": "/dev/frida"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,15 +0,0 @@
|
||||
{
|
||||
"connection": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 1234
|
||||
},
|
||||
"kernel": "win9x",
|
||||
"agent": {
|
||||
"path": "/home/oleavr/src/frida-core/src/barebone/agent/target-win9x/i686-unknown-none-pic/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",
|
||||
"bus": "frida-vserial.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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": true,
|
||||
"position-independent-executables": false,
|
||||
"relocation-model": "static",
|
||||
"rustc-abi": "softfloat",
|
||||
"stack-probes": {
|
||||
"kind": "inline"
|
||||
},
|
||||
"target-pointer-width": 32
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
MODULE_NAME ?= frida-agent
|
||||
|
||||
obj-m += $(MODULE_NAME).o
|
||||
|
||||
# frida-agent-prelinked.o is not built from source here: it is the Rust agent, the
|
||||
# GumJS devkit and the C library collapsed into a single relocatable object by the prelink
|
||||
# step in Makefile. Copy it in through if_changed rather than kbuild's _shipped rule,
|
||||
# so the .cmd file modpost expects gets written.
|
||||
$(MODULE_NAME)-y := frida-kmod.o frida-agent-prelinked.o
|
||||
|
||||
targets += frida-agent-prelinked.o
|
||||
|
||||
quiet_cmd_frida_agent_prelinked = COPY $@
|
||||
cmd_frida_agent_prelinked = cp $< $@
|
||||
|
||||
$(obj)/frida-agent-prelinked.o: $(src)/frida-agent-prelinked.o.bin FORCE
|
||||
$(call if_changed,frida_agent_prelinked)
|
||||
|
||||
# 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)
|
||||
@@ -1,206 +0,0 @@
|
||||
# Builds frida-agent.ko in two steps, because kbuild insists on owning the final
|
||||
# link:
|
||||
#
|
||||
# 1. Collapse the Rust staticlib plus everything it needs — the GumJS devkit and
|
||||
# the C library — into one relocatable object, and localize every symbol except
|
||||
# the entry points frida-kmod.c calls. Localizing matters: Gum and the C library
|
||||
# define names like memcpy and malloc that must not leak into the kernel's
|
||||
# namespace.
|
||||
# 2. Hand that object to kbuild as a shipped object, so vermagic, modinfo and
|
||||
# CRCs all come from the target kernel's own build system.
|
||||
#
|
||||
# See README.md for usage.
|
||||
|
||||
# KDIR is the kernel to build against. When it is a source tree that was configured
|
||||
# elsewhere — which is what Android's modules_prepare tarball gives — point KOUT at
|
||||
# the prepared tree.
|
||||
KDIR ?= /lib/modules/$(shell uname -r)/build
|
||||
PROFILE ?= release
|
||||
|
||||
# The kernel's own name for the architecture, and the one Rust knows it by. Both
|
||||
# follow from the machine this is built for, which defaults to the one building.
|
||||
ARCH ?= $(shell uname -m \
|
||||
| sed -e 's/^i[3-6]86$$/x86/' -e 's/^x86_64$$/x86/' -e 's/^aarch64$$/arm64/')
|
||||
BITS ?= $(shell uname -m \
|
||||
| sed -e 's/^i[3-6]86$$/32/' -e 's/^x86_64$$/64/' -e 's/^aarch64$$/64/')
|
||||
|
||||
ifeq ($(ARCH)-$(BITS),x86-32)
|
||||
RUST_TARGET ?= i686-unknown-none
|
||||
AGENT_TARGET_FLAGS ?= --target $(AGENT_DIR)/$(RUST_TARGET).json -Z json-target-spec
|
||||
SDK_ARCH ?= x86
|
||||
TOOLPREFIX ?=
|
||||
AGENT_LD_EMULATION ?= elf_i386
|
||||
else ifeq ($(ARCH)-$(BITS),x86-64)
|
||||
RUST_TARGET ?= x86_64-unknown-none
|
||||
SDK_ARCH ?= x86_64
|
||||
TOOLPREFIX ?=
|
||||
AGENT_LD_EMULATION ?= elf_x86_64
|
||||
else ifeq ($(ARCH)-$(BITS),arm64-64)
|
||||
RUST_TARGET ?= aarch64-unknown-none
|
||||
SDK_ARCH ?= arm64
|
||||
TOOLPREFIX ?= aarch64-none-elf-
|
||||
else
|
||||
$(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
|
||||
|
||||
GUMJS_DEVKIT_DIR ?= $(error Set GUMJS_DEVKIT_DIR to the directory holding libfrida-gumjs.a)
|
||||
|
||||
# Any binutils that speak the target's ELF will do; nothing here is compiled with
|
||||
# them. Building for the host's own architecture, that is the host's binutils.
|
||||
AGENT_LD ?= $(TOOLPREFIX)ld
|
||||
AGENT_AR ?= $(TOOLPREFIX)ar
|
||||
AGENT_NM ?= $(TOOLPREFIX)nm
|
||||
AGENT_OBJCOPY ?= $(TOOLPREFIX)objcopy
|
||||
|
||||
# The ABI the kernel imposes on anything linked into it. The flags only reach crates
|
||||
# Cargo compiles, and the toolchain ships core, alloc and compiler_builtins prebuilt
|
||||
# without them, so those three have to be rebuilt from source as well. That needs the
|
||||
# rust-src component and an unstable flag, hence RUSTC_BOOTSTRAP.
|
||||
#
|
||||
# On x86_64 that is endbr64 on every address-taken function, since a kernel with
|
||||
# CONFIG_X86_KERNEL_IBT faults an indirect call landing anywhere else, and switches
|
||||
# compiled as comparisons rather than jump tables, which objtool cannot follow into.
|
||||
# Soft-float, the kernel code model and the absent red zone come with the target
|
||||
# itself. On arm64
|
||||
# it is x18, which the kernel reserves for the shadow call stack, so nothing linked in
|
||||
# may use it as a general-purpose register.
|
||||
ifeq ($(ARCH)-$(BITS),x86-32)
|
||||
AGENT_RUSTFLAGS ?=
|
||||
AGENT_CFLAGS ?= --target=i386-none-elf \
|
||||
-Xclang -target-feature -Xclang +soft-float -mno-mmx -mno-sse \
|
||||
-mlong-double-64 -fno-pic --sysroot=$(FRIDA_SDK)
|
||||
else ifeq ($(ARCH)-$(BITS),x86-64)
|
||||
AGENT_RUSTFLAGS ?= -Zcf-protection=branch \
|
||||
-Cllvm-args=--min-jump-table-entries=65536 \
|
||||
-Crelocation-model=static -Zplt=yes
|
||||
AGENT_CFLAGS ?= --target=x86_64-none-elf \
|
||||
-Xclang -target-feature -Xclang +soft-float -mno-mmx -mno-sse -mno-red-zone \
|
||||
-mlong-double-64 -mcmodel=kernel -fcf-protection=branch -fno-jump-tables \
|
||||
-fno-pic --sysroot=$(FRIDA_SDK)
|
||||
else
|
||||
AGENT_RUSTFLAGS ?= -Zfixed-x18
|
||||
AGENT_CFLAGS ?= --target=aarch64-none-elf -mabi=aapcs-soft -mgeneral-regs-only \
|
||||
-ffixed-x18 -fno-pic --sysroot=$(FRIDA_SDK)
|
||||
endif
|
||||
AGENT_BUILD_STD ?= -Z build-std=core,alloc,compiler_builtins
|
||||
|
||||
# The devkit header reaches for GLib's and capstone's, which a devkit built against
|
||||
# an SDK leaves in that SDK rather than beside itself. The sysroot's own include
|
||||
# directory is spelled out because only clang's bare-metal driver, which it selects
|
||||
# for arm64 but not for x86_64, searches there on its own.
|
||||
AGENT_CFLAGS += -isystem $(FRIDA_SDK)/include \
|
||||
-I$(FRIDA_SDK)/include/glib-2.0 \
|
||||
-I$(FRIDA_SDK)/lib/glib-2.0/include \
|
||||
-I$(FRIDA_SDK)/include/capstone \
|
||||
-I$(FRIDA_SDK)/include/json-glib-1.0
|
||||
|
||||
# The soft-float SDK, whose libc is picolibc and whose compiler runtime is
|
||||
# compiler-rt. GCC's own newlib and libgcc are hard-float and cannot be used here.
|
||||
FRIDA_SDK ?= $(error Set FRIDA_SDK to a none-$(SDK_ARCH)-softfloat_nopic SDK prefix)
|
||||
LIBM = $(FRIDA_SDK)/lib/libm.a
|
||||
LIBBUILTINS = $(FRIDA_SDK)/lib/libclang_rt.builtins.a
|
||||
|
||||
# bindgen reads the devkit header through whatever this compiler reports as its
|
||||
# include path, so it has to be one that knows where the SDK keeps its libc. It has
|
||||
# to be clang either way: -mabi=aapcs-soft landed there in 19 and GCC has it at no
|
||||
# version, and on x86_64 the soft-float ABI is reachable only as an -Xclang feature.
|
||||
AGENT_CC ?= clang
|
||||
|
||||
PRELINKED := frida-agent-prelinked.o.bin
|
||||
|
||||
AGENT_LIB_SOFTFLOAT := $(AGENT_TARGET_DIR)/libfrida-barebone-agent-softfloat.a
|
||||
|
||||
# On arm64 the toolchain's own compiler_builtins is compiled for the ordinary AAPCS,
|
||||
# where a double travels in d0: its __muldf3 opens with "fmov x14, d0". Everything
|
||||
# else there is -mabi=aapcs-soft and passes that same double in x0. Whole-archiving
|
||||
# the staticlib puts those definitions in front of the SDK's compiler-rt, which has
|
||||
# the soft-float ones, and every double the C library computes then comes back
|
||||
# garbage — quietly, since the arithmetic still runs. Drop them and let compiler-rt
|
||||
# answer instead. x86_64 needs none of this: soft-float is part of the Rust target
|
||||
# there, so the copy build-std compiles already passes doubles the way the C half
|
||||
# expects.
|
||||
ifeq ($(ARCH),arm64)
|
||||
DROP_HARDFLOAT_BUILTINS = \
|
||||
$(AGENT_AR) d $@ $$($(AGENT_NM) --print-armap $@ | sed -n 's/^__muldf3 in //p') && \
|
||||
$(AGENT_AR) s $@
|
||||
else
|
||||
DROP_HARDFLOAT_BUILTINS = :
|
||||
endif
|
||||
|
||||
# The agent puts malloc, the C library's locks and its process stubs on the kernel's
|
||||
# own facilities, so picolibc's versions are duplicate definitions rather than
|
||||
# fallbacks. Work out which members they live in instead of naming them: the set
|
||||
# follows from what the agent defines, and picolibc moves things between releases.
|
||||
LIBC := $(AGENT_TARGET_DIR)/libc-trimmed.a
|
||||
|
||||
ENTRY_POINTS := frida_agent_start frida_agent_stop \
|
||||
gum_interceptor_obtain gum_interceptor_begin_transaction \
|
||||
gum_interceptor_end_transaction gum_interceptor_replace \
|
||||
gum_interceptor_revert
|
||||
KEEP_GLOBAL := $(foreach s,$(ENTRY_POINTS),--keep-global-symbol=$(s))
|
||||
|
||||
# Two structures the reparenting path reaches into lost a member upstream, and
|
||||
# neither change announces itself as anything frida-kmod.c could test for. The
|
||||
# headers it is compiled against are the authority on both, so ask them.
|
||||
FRIDA_KMOD_DEFS := \
|
||||
$(if $(shell grep -l 'list_head[[:space:]]*thread_group;' \
|
||||
$(KDIR)/include/linux/sched.h),-DFRIDA_HAVE_TASK_THREAD_GROUP) \
|
||||
$(if $(shell grep -l 'spinlock_t[[:space:]]*lock;' \
|
||||
$(KDIR)/include/linux/fs_struct.h),-DFRIDA_HAVE_FS_STRUCT_LOCK)
|
||||
|
||||
all: modules
|
||||
|
||||
# A module that dies in its init path is left wedged under its own name until the
|
||||
# next boot, so bring-up wants a fresh one per attempt.
|
||||
MODULE_NAME ?= frida-agent
|
||||
|
||||
modules: $(PRELINKED)
|
||||
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) \
|
||||
FRIDA_KMOD_DEFS="$(FRIDA_KMOD_DEFS)" MODULE_NAME=$(MODULE_NAME) modules
|
||||
|
||||
$(PRELINKED): $(AGENT_LIB_SOFTFLOAT) $(LIBC) prelink.lds
|
||||
$(AGENT_LD) -r $(if $(AGENT_LD_EMULATION),-m $(AGENT_LD_EMULATION)) \
|
||||
-T prelink.lds -o $@.tmp \
|
||||
--whole-archive $(AGENT_LIB_SOFTFLOAT) --no-whole-archive \
|
||||
--start-group \
|
||||
$(GUMJS_DEVKIT_DIR)/libfrida-gumjs.a \
|
||||
$(LIBC) \
|
||||
$(LIBM) \
|
||||
$(LIBBUILTINS) \
|
||||
--end-group
|
||||
$(AGENT_OBJCOPY) $(KEEP_GLOBAL) --strip-debug \
|
||||
--remove-section=.llvmbc --remove-section=.llvmcmd $@.tmp $@
|
||||
rm -f $@.tmp
|
||||
|
||||
$(AGENT_LIB_SOFTFLOAT): agent
|
||||
cp $(AGENT_LIB) $@
|
||||
$(DROP_HARDFLOAT_BUILTINS)
|
||||
|
||||
$(LIBC): export LC_ALL = C
|
||||
$(LIBC): $(AGENT_LIB_SOFTFLOAT)
|
||||
cp $(FRIDA_SDK)/lib/libc.a $@
|
||||
$(AGENT_NM) --defined-only $(AGENT_LIB_SOFTFLOAT) \
|
||||
| awk 'NF == 3 && $$2 ~ /^[TDBR]$$/ { print $$3 }' | sort -u > $@.agent-syms
|
||||
$(AGENT_NM) --print-armap $@ | sed -n 's/^\([^ ]*\) in \(.*\)$$/\1 \2/p' | sort -u > $@.members
|
||||
$(AGENT_AR) d $@ $$(join $@.agent-syms $@.members | awk '{ print $$2 }' | sort -u)
|
||||
$(AGENT_AR) s $@
|
||||
rm -f $@.agent-syms $@.members
|
||||
|
||||
agent:
|
||||
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 $(PRELINKED) $(PRELINKED).tmp $(AGENT_LIB_SOFTFLOAT) $(LIBC)
|
||||
|
||||
.PHONY: all modules agent clean
|
||||
@@ -1,291 +0,0 @@
|
||||
# Linux kernel module
|
||||
|
||||
The same agent that the Barebone backend injects into XNU from the outside, but
|
||||
loaded from the inside as an ordinary kernel module.
|
||||
|
||||
## How it differs from the XNU flavor
|
||||
|
||||
| | XNU | Linux |
|
||||
|-------------------|--------------------------------------------------|-------------------------------------------|
|
||||
| Delivery | remote stub injects a freestanding ELF | `insmod frida-agent.ko` |
|
||||
| Kernel primitives | addresses patched into `.kernel_addrs` by the host | direct calls into `frida-kmod.c` |
|
||||
| Bootstrap config | GVariant blob written into guest memory | module parameters |
|
||||
| Transport | virtio hostlink or vsock | `/dev/frida` character device |
|
||||
| Page permissions | host rewrites descriptors over the hostlink | `set_memory_*()` |
|
||||
| Writable alias | shadow copy, committed via physical-memory bridge | `vmap()` of the same pages |
|
||||
| Symbols | table supplied by the host | kallsyms |
|
||||
|
||||
Because the Linux agent performs its own memory work, it never issues the
|
||||
`RemapWritablePages`, `MemoryProtect` or `PatchCode` host RPCs. The wire protocol
|
||||
is otherwise identical, so the host side sees the same `(yqv)` frames.
|
||||
|
||||
## Building
|
||||
|
||||
Four inputs: this repository, a GumJS devkit built for
|
||||
`none-<arch>-softfloat_nopic`, the SDK it was built against, and the kernel the module is for. `Makefile`
|
||||
covers x86_64 and arm64, and defaults to the architecture of the machine building.
|
||||
|
||||
Building for the running kernel is a good deal shorter than cross-building for a
|
||||
device, so that walkthrough comes first; the arm64 one below it is the same steps
|
||||
with the kernel obtained from elsewhere.
|
||||
|
||||
### Toolchain
|
||||
|
||||
rustup target add x86_64-unknown-none # or aarch64-unknown-none
|
||||
rustup component add rust-src
|
||||
sudo dnf install clang binutils kernel-devel-$(uname -r)
|
||||
|
||||
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 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.
|
||||
|
||||
### For the running x86_64 kernel
|
||||
|
||||
No prebuilt bundles are published for `none-x86_64-softfloat_nopic`, so both inputs are
|
||||
built locally, against the same SDK:
|
||||
|
||||
releng/deps.py build --bundle=sdk --host=none-x86_64-softfloat_nopic
|
||||
mkdir -p ~/sdk-none-x86_64-softfloat_nopic
|
||||
tar -C ~/sdk-none-x86_64-softfloat_nopic -xf deps/sdk-none-x86_64-softfloat_nopic.tar.xz
|
||||
|
||||
gum=subprojects/frida-gum
|
||||
mkdir -p $gum/build/none-x86_64-softfloat_nopic
|
||||
(cd $gum/build/none-x86_64-softfloat_nopic \
|
||||
&& FRIDA_DEPS=$PWD/../../../../deps ../../configure \
|
||||
--host=none-x86_64-softfloat_nopic \
|
||||
--enable-gumjs \
|
||||
--with-devkits=gumjs \
|
||||
--with-devkit-symbol-scope=original \
|
||||
&& make)
|
||||
|
||||
make -C src/barebone/agent/linux \
|
||||
FRIDA_SDK=$HOME/sdk-none-x86_64-softfloat_nopic \
|
||||
GUMJS_DEVKIT_DIR=$PWD/$gum/build/none-x86_64-softfloat_nopic/bindings/gumjs/devkit
|
||||
|
||||
Everything else — `KDIR`, the Rust target, the binutils, the kernel's own name for the
|
||||
architecture — follows from the running kernel.
|
||||
|
||||
### For an arm64 device
|
||||
|
||||
Here there are published bundles to start from, and the kernel has to be assembled.
|
||||
|
||||
#### The devkit and the SDK
|
||||
|
||||
version=17.17.0
|
||||
base=https://github.com/frida/frida/releases/download/$version
|
||||
curl -LO $base/frida-gumjs-devkit-$version-none-arm64-softfloat_nopic.tar.xz
|
||||
mkdir -p ~/gumjs-devkit
|
||||
tar -C ~/gumjs-devkit -xf frida-gumjs-devkit-$version-none-arm64-softfloat_nopic.tar.xz
|
||||
|
||||
releng/deps.py sync sdk none-arm64-softfloat_nopic ~/sdk-none-arm64-softfloat_nopic
|
||||
|
||||
The SDK carries picolibc and the compiler-rt builtins the devkit expects to be linked
|
||||
against, and `releng/deps.toml` pins which one. Mixing a devkit with an SDK built for
|
||||
a different libc leaves undefined symbols at prelink time.
|
||||
|
||||
#### The kernel
|
||||
|
||||
Nothing here needs the device's own kernel to be built, but the config alone is not
|
||||
enough either. `uname -r` ends in the GKI commit and the build number:
|
||||
|
||||
adb shell uname -r
|
||||
6.1.145-android14-11-gc1de4747ac59-ab14219743
|
||||
|
||||
which is commit `c1de4747ac59` and build `14219743`. Three pieces are keyed to them:
|
||||
|
||||
commit=c1de4747ac59
|
||||
build=14219743
|
||||
ci=https://ci.android.com/builds/submitted/$build/kernel_aarch64/latest/raw
|
||||
|
||||
mkdir -p ~/kernel-prepared ~/kernel-source
|
||||
curl -sSL $ci/modules_prepare_outdir.tar.gz | tar -xz -C ~/kernel-prepared
|
||||
curl -sSL $ci/kernel_aarch64_Module.symvers -o ~/kernel-prepared/Module.symvers
|
||||
curl -sSL https://android.googlesource.com/kernel/common/+archive/$commit.tar.gz \
|
||||
| tar -xz -C ~/kernel-source
|
||||
|
||||
`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 \
|
||||
FRIDA_SDK=$HOME/sdk-none-arm64-softfloat_nopic \
|
||||
GUMJS_DEVKIT_DIR=$HOME/gumjs-devkit \
|
||||
AGENT_LD=aarch64-linux-gnu-ld \
|
||||
AGENT_AR=aarch64-linux-gnu-ar \
|
||||
AGENT_NM=aarch64-linux-gnu-nm \
|
||||
AGENT_OBJCOPY=aarch64-linux-gnu-objcopy \
|
||||
KDIR=$HOME/kernel-source \
|
||||
KOUT=$HOME/kernel-prepared \
|
||||
LLVM=1
|
||||
|
||||
`make` builds the Rust staticlib itself, prelinks it against the devkit and the SDK,
|
||||
then hands the result to kbuild. The `AGENT_*` overrides are only needed because the
|
||||
defaults name an `aarch64-none-elf-` GNU toolchain; drop them if that is what is
|
||||
installed.
|
||||
|
||||
The prelink half runs anywhere; the kbuild half needs the x86-64 Linux host above.
|
||||
Cross-building from macOS means running that step in a container holding the kernel
|
||||
tree.
|
||||
|
||||
adb push src/barebone/agent/linux/frida-agent.ko /data/local/tmp/
|
||||
|
||||
## Loading
|
||||
|
||||
insmod frida-agent.ko
|
||||
|
||||
The module's init path returns as soon as the worker thread is running — watch
|
||||
`dmesg` for `frida: listening on /dev/frida`. `rmmod frida-agent` unwinds in the
|
||||
reverse order and does not return until the worker is off the module's text.
|
||||
|
||||
## Talking to it
|
||||
|
||||
The agent exposes `/dev/frida` and waits: one client at a time, `read`/`write` of the
|
||||
same length-prefixed frames the other transports carry, with `poll` for readiness.
|
||||
It never dials out, so there is no network namespace to get right and no ordering
|
||||
requirement between loading the module and attaching a client.
|
||||
|
||||
On Android the node is created by devtmpfs without a policy-specific label, so an
|
||||
SELinux-enforcing system needs the client's domain granted access to it before it can
|
||||
open the device. A socket-based transport was the other option considered and is a
|
||||
worse fit: `AF_VSOCK` only reaches a peer when a hypervisor is on the other side, and
|
||||
having the kernel connect out to loopback needs something already listening before
|
||||
the module loads.
|
||||
|
||||
### Reaching it from a host
|
||||
|
||||
Serve the Barebone device from a frida-server running on the target, so the `open()`
|
||||
happens next to the device node and the host talks to an ordinary frida-server:
|
||||
|
||||
FRIDA_BAREBONE_CONFIG=/data/local/tmp/linux-kmod.json \
|
||||
frida-server --device barebone -l 127.0.0.1:27042
|
||||
|
||||
`etc/linux-kmod.json` is that config. From the host, with `adb forward tcp:27042
|
||||
tcp:27042` in place:
|
||||
|
||||
frida -H 127.0.0.1:27042 -p 0
|
||||
|
||||
## Injecting into userspace
|
||||
|
||||
Loaded on a live system the module doubles as a ptrace-free injector for ordinary
|
||||
processes — how the Linux local backend reaches its targets when the module is
|
||||
present, falling back to `ptrace` when it is not. There is no second device node;
|
||||
the channel is `prctl()` with a magic option and a token in the fifth argument,
|
||||
|
||||
prctl(0x46524944, op, arg1, arg2, 0x1d5f9e6b2c7a4038)
|
||||
|
||||
and any call whose option or token does not match falls through to the real
|
||||
prctl, so probing for the module looks like a stock kernel. `op` 32 pings and
|
||||
returns the magic.
|
||||
|
||||
Allocate, free, read, write and spawn — the primitives the injector drives
|
||||
against a target pid — require `CAP_SYS_ADMIN`. A spawned thread is a real
|
||||
`CLONE_THREAD` sibling that adopts the target's mm, files, credentials,
|
||||
namespaces, fs and SysV semaphore undo list, so it is a native member of the
|
||||
group rather than a kernel thread wearing its address space.
|
||||
|
||||
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 — 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.** The register-level pieces — the page size, cache maintenance,
|
||||
where the syscall ABI leaves prctl()'s arguments, and how a spawned thread's
|
||||
registers are set up for its return to userspace — have a half for each. The XNU
|
||||
flavor remains arm64-only.
|
||||
- **`CONFIG_KPROBES` and `CONFIG_KALLSYMS_ALL`.** A good deal of what the shim
|
||||
needs is compiled in but not exported — `kallsyms_lookup_name`,
|
||||
`kallsyms_on_each_symbol` and `set_memory_*` all are, and GKI trims its export
|
||||
table to the KMI symbol list on top of that. The shim resolves them by name
|
||||
through a kprobe, which is the standard way back in. Without kallsyms the module
|
||||
still loads but cannot look up symbols or change page permissions.
|
||||
- **Unsigned modules.** `CONFIG_MODULE_SIG_FORCE` must be off. Android's
|
||||
`CONFIG_MODULE_SIG_PROTECT` is fine: it only demands signatures for modules on
|
||||
the GKI list.
|
||||
|
||||
## Kernel ABI constraints
|
||||
|
||||
A hardened kernel imposes an ABI on anything linked into it, and the FP constraint
|
||||
below is why this flavor is built against a soft-float SDK
|
||||
(`none-arm64-softfloat_nopic`, `none-x86_64-softfloat_nopic`) rather than an ordinary
|
||||
bare-metal one. The remaining constraints are per-architecture.
|
||||
|
||||
- **FP/SIMD.** JavaScript numbers are doubles, so a hardfloat build would use FP
|
||||
for as long as the runtime lives, and neither architecture lets a kernel thread
|
||||
hold it for free — x86 wants every stretch bracketed by `kernel_fpu_begin()`, and
|
||||
on arm64 kernels before 6.9 there is no correct way to hold it at all:
|
||||
`kernel_neon_begin()` runs with preemption disabled so we cannot sleep under it,
|
||||
and the scheduler does not save a kernel thread's FP registers — live values are
|
||||
lost on preemption and the FP state of whichever user task was interrupted is
|
||||
corrupted. Building everything soft-float removes the question rather than
|
||||
answering it: `-mabi=aapcs-soft -mgeneral-regs-only` on arm64, and on x86_64
|
||||
LLVM's `+soft-float`, which is also what Rust's own `x86_64-unknown-none` turns
|
||||
on, so the two halves agree on where a double travels. `-mno-sse` alone does not
|
||||
reach the ABI; and `long double` has to be narrowed to 64 bits with
|
||||
`-mlong-double-64`, since soft-float has no lowering for x87's 80-bit format and
|
||||
LLVM crashes rather than calling out to a builtin.
|
||||
- **Indirect branch tracking (x86_64).** With `CONFIG_X86_KERNEL_IBT` an indirect
|
||||
call landing on anything but an `endbr64` faults, so everything linked in is built
|
||||
`-fcf-protection=branch` and the Rust half `-Zcf-protection=branch`.
|
||||
- **Kernel code model and red zone (x86_64).** Modules are loaded into the top 2GB
|
||||
of the address space and their relocations are resolved as if by `-mcmodel=kernel`,
|
||||
and there is no red zone below the stack pointer for an interrupt to step on. Both
|
||||
come with the Rust target; the C half is told explicitly.
|
||||
- **objtool (x86_64).** It validates every object entering a module and builds its
|
||||
ORC unwind tables, and was not written to read a JavaScript runtime linked against
|
||||
a freestanding libc. `Kbuild` marks the prelinked blob `OBJECT_FILES_NON_STANDARD`.
|
||||
- **Shadow call stack (arm64).** With `CONFIG_SHADOW_CALL_STACK` the kernel reserves x18, so
|
||||
the SDK and Gum are built `-ffixed-x18` and the Rust half with `-Zfixed-x18`.
|
||||
Anything linked in that uses x18 as a scratch register overwrites the kernel's SCS
|
||||
pointer, and the thread then returns through whatever that address happens to hold.
|
||||
|
||||
The Rust flag only reaches crates Cargo compiles, so `core`, `alloc` and
|
||||
`compiler_builtins` — which the toolchain ships prebuilt, and which between them
|
||||
account for every x18 write that used to reach the module — are rebuilt from source
|
||||
via `-Z build-std`. That needs `rustup component add rust-src`.
|
||||
|
||||
## The soft-float libc
|
||||
|
||||
The libc is picolibc and the compiler runtime is compiler-rt, both from the
|
||||
soft-float SDK that `FRIDA_SDK` points at. `Makefile` trims a copy of its
|
||||
`libc.a` before the prelink: the agent implements `malloc`, the C library's locks and
|
||||
its process stubs over the kernel's own facilities, so picolibc's are duplicate
|
||||
definitions rather than fallbacks. The set is derived from what the agent defines
|
||||
rather than listed, since picolibc moves things between releases.
|
||||
|
||||
On arm64 those compiler-rt builtins are load-bearing in a way that is easy to miss.
|
||||
Rust's toolchain carries its own `compiler_builtins`, compiled for the ordinary AAPCS
|
||||
where a double travels in `d0`, and the prelink whole-archives the Rust staticlib — so
|
||||
without the filtering step in `Makefile` those definitions answer the C library's calls
|
||||
to `__muldf3` and friends, which pass the same double in `x0`. Nothing crashes. The
|
||||
arithmetic simply returns garbage, and the first thing to notice is GLib deciding a
|
||||
hash table need not grow. x86_64 needs none of that filtering: soft-float is part of
|
||||
the Rust target there, so the copy `-Z build-std` compiles already agrees with the C
|
||||
half.
|
||||
|
||||
## What a script may look like
|
||||
|
||||
The runtime compiles scripts on a kernel thread's stack, and QuickJS parses
|
||||
expressions by recursion, so the ceiling is nesting rather than length: roughly
|
||||
fourteen levels of parentheses or fifteen of object literals on arm64 Linux, whose
|
||||
kernel stack is 16 KiB — as is x86_64's. A script that exceeds it fails to compile rather than taking
|
||||
anything down, though the message is not always `stack overflow` — an over-deep
|
||||
`if`/`else` chain reports `expecting ';'`. Long scripts are fine; deep ones are not.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,8 +0,0 @@
|
||||
SECTIONS
|
||||
{
|
||||
.init_array 0 : {
|
||||
_agent_init_start = .;
|
||||
*(.init_array*)
|
||||
_agent_init_end = .;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/frida-agent
|
||||
/frida-agent.kext/
|
||||
/frida-kext.o
|
||||
/frida-signing.o
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
@@ -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])
|
||||
@@ -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": {}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use core::ffi::c_void;
|
||||
|
||||
use crate::bindings::{gpointer, gsize, gum_try_mprotect, GumPageProtection};
|
||||
use crate::{kernel, libc};
|
||||
use crate::bindings::{gpointer, gsize, GumPageProtection};
|
||||
use crate::{gum, libc, xnu};
|
||||
|
||||
const GUM_PAGE_READ: GumPageProtection = 1;
|
||||
const GUM_PAGE_EXECUTE: GumPageProtection = 4;
|
||||
@@ -10,7 +10,7 @@ const GUM_PAGE_EXECUTE: GumPageProtection = 4;
|
||||
// static trampoline `text` made executable, followed by a writable parameter
|
||||
// table `map_size` bytes later (the trampolines reach their parameters via that
|
||||
// fixed offset). The two halves are whole pages, so flipping the first to RX
|
||||
// leaves the parameter table writable.
|
||||
// over the hostlink leaves the parameter table writable.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn ffi_tramp_embedder_map(
|
||||
text: *const c_void,
|
||||
@@ -19,7 +19,7 @@ pub unsafe extern "C" fn ffi_tramp_embedder_map(
|
||||
parm_table: *mut *mut c_void,
|
||||
) -> i32 {
|
||||
unsafe {
|
||||
let base = kernel::alloc_code(map_size * 2);
|
||||
let base = xnu::kalloc(map_size * 2);
|
||||
if base.is_null() {
|
||||
return 0;
|
||||
}
|
||||
@@ -28,9 +28,9 @@ pub unsafe extern "C" fn ffi_tramp_embedder_map(
|
||||
libc::__clear_cache(base, base.add(map_size));
|
||||
|
||||
let made_executable =
|
||||
gum_try_mprotect(base as gpointer, map_size as gsize, GUM_PAGE_READ | GUM_PAGE_EXECUTE);
|
||||
gum::gum_try_mprotect(base as gpointer, map_size as gsize, GUM_PAGE_READ | GUM_PAGE_EXECUTE);
|
||||
if made_executable == 0 {
|
||||
kernel::free_code(base, map_size * 2);
|
||||
xnu::free(base, map_size * 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -46,5 +46,7 @@ pub unsafe extern "C" fn ffi_tramp_embedder_unmap(
|
||||
_parm_table: *mut c_void,
|
||||
map_size: usize,
|
||||
) {
|
||||
kernel::free_code(code_table as *mut u8, map_size * 2);
|
||||
unsafe {
|
||||
xnu::free(code_table as *mut u8, map_size * 2);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +1,46 @@
|
||||
use core::ptr;
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use crate::bindings::{g_wait_is_set, gint64, gpointer};
|
||||
use crate::kernel;
|
||||
use crate::xnu;
|
||||
|
||||
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()
|
||||
let abstime = xnu::mach_absolute_time();
|
||||
let time = xnu::absolutetime_to_nanoseconds(abstime);
|
||||
(time / 1000) as gint64
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_wait_sleep(token: gpointer, timeout_us: gint64) {
|
||||
let timeout = if timeout_us == G_WAIT_INFINITE {
|
||||
None
|
||||
let wait_event = ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8;
|
||||
|
||||
let wait_result = if timeout_us == G_WAIT_INFINITE {
|
||||
xnu::assert_wait(wait_event, xnu::THREAD_INTERRUPTIBLE)
|
||||
} else {
|
||||
Some(timeout_us as u64)
|
||||
xnu::assert_wait_timeout(
|
||||
wait_event,
|
||||
xnu::THREAD_INTERRUPTIBLE,
|
||||
(timeout_us * 1000) as u32,
|
||||
1, // TODO
|
||||
)
|
||||
};
|
||||
|
||||
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);
|
||||
if wait_result != xnu::THREAD_WAITING {
|
||||
panic!("assert_wait_timeout failed: {}", wait_result);
|
||||
}
|
||||
|
||||
if unsafe { g_wait_is_set(token) != 0 } {
|
||||
xnu::thread_wakeup(wait_event);
|
||||
return;
|
||||
}
|
||||
|
||||
xnu::thread_block(None);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_wait_wake(token: gpointer) {
|
||||
kernel::poke_loop_wakeup();
|
||||
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()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_spawn_async_with_pipes() -> i32 {
|
||||
0
|
||||
pub extern "C" fn g_wait_wake(_token: gpointer) {
|
||||
xnu::thread_wakeup(ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8);
|
||||
}
|
||||
@@ -8,39 +8,34 @@ use crate::bindings::GThreadFunc;
|
||||
use crate::bindings::{gpointer, gulong};
|
||||
use alloc::boxed::Box;
|
||||
use alloc::collections::BTreeMap;
|
||||
use core::arch::asm;
|
||||
use core::ffi::c_void;
|
||||
use core::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering};
|
||||
use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
|
||||
|
||||
// Our implementation structures that overlay the opaque GLib structures
|
||||
|
||||
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
|
||||
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
|
||||
#[repr(C, align(8))]
|
||||
struct MutexImpl {
|
||||
lock: AtomicU32,
|
||||
_padding: [u8; core::mem::size_of::<GMutex>() - core::mem::size_of::<AtomicU32>()],
|
||||
}
|
||||
|
||||
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
|
||||
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
|
||||
#[repr(C, align(8))]
|
||||
struct RecMutexImpl {
|
||||
owner: AtomicUsize, // Thread ID of the owner (uses the 'p' field)
|
||||
owner: AtomicU64, // Thread ID of the owner (uses the 'p' field)
|
||||
count: AtomicU32, // Recursion count (uses i[0])
|
||||
_unused: u32, // Unused (uses i[1])
|
||||
}
|
||||
|
||||
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
|
||||
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
|
||||
#[repr(C, align(8))]
|
||||
struct RWLockImpl {
|
||||
state: AtomicU32, // Lock state (readers count + writer bit)
|
||||
_padding: [u8; core::mem::size_of::<GRWLock>() - core::mem::size_of::<AtomicU32>()],
|
||||
}
|
||||
|
||||
// A generation counter rather than a flag: a flag stays set once signalled, so every
|
||||
// later wait returns at once and the caller's loop spins instead of blocking.
|
||||
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
|
||||
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
|
||||
#[repr(C, align(8))]
|
||||
struct CondImpl {
|
||||
generation: AtomicU32,
|
||||
signal: AtomicU32, // Signal state
|
||||
_padding: [u8; core::mem::size_of::<GCond>() - core::mem::size_of::<AtomicU32>()],
|
||||
}
|
||||
|
||||
@@ -149,9 +144,10 @@ pub extern "C" fn g_rec_mutex_clear(_rec_mutex: *mut GRecMutex) {
|
||||
pub extern "C" fn g_rec_mutex_lock(rec_mutex: *mut GRecMutex) {
|
||||
unsafe {
|
||||
let impl_ = rec_mutex_impl(rec_mutex);
|
||||
let current_thread = get_current_thread_id();
|
||||
|
||||
loop {
|
||||
if rec_mutex_try_acquire(impl_) {
|
||||
if rec_mutex_try_acquire(impl_, current_thread) {
|
||||
break;
|
||||
}
|
||||
rec_mutex_wait(impl_);
|
||||
@@ -163,8 +159,9 @@ pub extern "C" fn g_rec_mutex_lock(rec_mutex: *mut GRecMutex) {
|
||||
pub extern "C" fn g_rec_mutex_trylock(rec_mutex: *mut GRecMutex) -> u32 {
|
||||
unsafe {
|
||||
let impl_ = rec_mutex_impl(rec_mutex);
|
||||
let current_thread = get_current_thread_id();
|
||||
|
||||
if rec_mutex_try_acquire(impl_) {
|
||||
if rec_mutex_try_acquire(impl_, current_thread) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
@@ -186,7 +183,7 @@ pub extern "C" fn g_rec_mutex_unlock(rec_mutex: *mut GRecMutex) {
|
||||
.is_ok()
|
||||
{
|
||||
impl_.owner.store(0, Ordering::Relaxed);
|
||||
wake_waiters(&impl_.count);
|
||||
lock_notify_all();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@@ -201,10 +198,7 @@ pub extern "C" fn g_rec_mutex_unlock(rec_mutex: *mut GRecMutex) {
|
||||
}
|
||||
}
|
||||
|
||||
// Asking the kernel who we are costs more than the uncontended acquisition itself, and
|
||||
// every allocation takes this path, so the identity is only fetched once the lock turns
|
||||
// out to be held.
|
||||
unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl) -> bool {
|
||||
unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl, current_thread: u64) -> bool {
|
||||
let current_count = impl_.count.load(Ordering::Relaxed);
|
||||
|
||||
if current_count == 0 {
|
||||
@@ -212,12 +206,12 @@ unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl) -> bool {
|
||||
.compare_exchange_weak(0, 1, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
{
|
||||
impl_.owner.store(crate::kernel::current_thread_id() as usize, Ordering::Relaxed);
|
||||
impl_.owner.store(current_thread, Ordering::Relaxed);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
let current_owner = impl_.owner.load(Ordering::Relaxed);
|
||||
if current_owner == crate::kernel::current_thread_id() as usize {
|
||||
if current_owner == current_thread {
|
||||
if impl_.count
|
||||
.compare_exchange_weak(
|
||||
current_count,
|
||||
@@ -236,7 +230,7 @@ unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl) -> bool {
|
||||
|
||||
unsafe fn rec_mutex_wait(impl_: &mut RecMutexImpl) {
|
||||
while impl_.count.load(Ordering::Relaxed) != 0 {
|
||||
wait_on(&impl_.count, &mut || impl_.count.load(Ordering::Relaxed) == 0);
|
||||
unsafe { lock_wait_primitive() };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +282,7 @@ pub extern "C" fn g_rw_lock_writer_unlock(rw_lock: *mut GRWLock) {
|
||||
unsafe {
|
||||
let impl_ = rw_lock_impl(rw_lock);
|
||||
impl_.state.store(0, Ordering::Release);
|
||||
wake_waiters(&impl_.state);
|
||||
lock_notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,7 +329,7 @@ pub extern "C" fn g_rw_lock_reader_unlock(rw_lock: *mut GRWLock) {
|
||||
.is_ok()
|
||||
{
|
||||
if current - 1 == 0 {
|
||||
wake_waiters(&impl_.state);
|
||||
lock_notify_all();
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -361,15 +355,13 @@ unsafe fn rw_lock_reader_try_acquire(impl_: &mut RWLockImpl) -> bool {
|
||||
|
||||
unsafe fn rw_lock_wait_for_writers(impl_: &mut RWLockImpl) {
|
||||
while impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT != 0 {
|
||||
wait_on(&impl_.state, &mut || {
|
||||
impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT == 0
|
||||
});
|
||||
unsafe { lock_wait_primitive() };
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn rw_lock_wait_for_all(impl_: &mut RWLockImpl) {
|
||||
while impl_.state.load(Ordering::Relaxed) != 0 {
|
||||
wait_on(&impl_.state, &mut || impl_.state.load(Ordering::Relaxed) == 0);
|
||||
unsafe { lock_wait_primitive() };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +369,7 @@ unsafe fn rw_lock_wait_for_all(impl_: &mut RWLockImpl) {
|
||||
pub extern "C" fn g_cond_init(cond: *mut GCond) {
|
||||
unsafe {
|
||||
let impl_ = cond_impl(cond);
|
||||
impl_.generation.store(0, Ordering::Relaxed);
|
||||
impl_.signal.store(0, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,17 +379,11 @@ pub extern "C" fn g_cond_clear(_cond: *mut GCond) {}
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_cond_wait(cond: *mut GCond, mutex: *mut GMutex) {
|
||||
unsafe {
|
||||
// Read the generation under the mutex, so a signal that lands after the
|
||||
// unlock below is still observed as a change.
|
||||
let seen = cond_impl(cond).generation.load(Ordering::Acquire);
|
||||
|
||||
g_mutex_unlock(mutex);
|
||||
|
||||
let impl_ = cond_impl(cond);
|
||||
while impl_.generation.load(Ordering::Acquire) == seen {
|
||||
wait_on(&impl_.generation, &mut || {
|
||||
impl_.generation.load(Ordering::Acquire) != seen
|
||||
});
|
||||
while impl_.signal.load(Ordering::Acquire) == 0 {
|
||||
lock_wait_primitive();
|
||||
}
|
||||
|
||||
g_mutex_lock(mutex);
|
||||
@@ -408,8 +394,8 @@ pub extern "C" fn g_cond_wait(cond: *mut GCond, mutex: *mut GMutex) {
|
||||
pub extern "C" fn g_cond_signal(cond: *mut GCond) {
|
||||
unsafe {
|
||||
let impl_ = cond_impl(cond);
|
||||
impl_.generation.fetch_add(1, Ordering::Release);
|
||||
wake_waiters(&impl_.generation);
|
||||
impl_.signal.store(1, Ordering::Release);
|
||||
lock_notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,82 +403,22 @@ pub extern "C" fn g_cond_signal(cond: *mut GCond) {
|
||||
pub extern "C" fn g_cond_broadcast(cond: *mut GCond) {
|
||||
unsafe {
|
||||
let impl_ = cond_impl(cond);
|
||||
impl_.generation.fetch_add(1, Ordering::Release);
|
||||
wake_waiters(&impl_.generation);
|
||||
impl_.signal.store(1, Ordering::Release);
|
||||
lock_notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_cond_wait_until(cond: *mut GCond, mutex: *mut GMutex, end_time: i64) -> u32 {
|
||||
unsafe {
|
||||
let seen = cond_impl(cond).generation.load(Ordering::Acquire);
|
||||
|
||||
g_mutex_unlock(mutex);
|
||||
|
||||
let impl_ = cond_impl(cond);
|
||||
let mut signalled = impl_.generation.load(Ordering::Acquire) != seen;
|
||||
while !signalled && crate::kernel::monotonic_micros() < end_time {
|
||||
wait_on(&impl_.generation, &mut || {
|
||||
impl_.generation.load(Ordering::Acquire) != seen
|
||||
});
|
||||
signalled = impl_.generation.load(Ordering::Acquire) != seen;
|
||||
}
|
||||
|
||||
g_mutex_lock(mutex);
|
||||
|
||||
if signalled { 1 } else { 0 }
|
||||
}
|
||||
pub extern "C" fn g_cond_wait_until(cond: *mut GCond, mutex: *mut GMutex, _end_time: i64) -> u32 {
|
||||
// TODO: Implement proper timeout handling
|
||||
g_cond_wait(cond, mutex);
|
||||
1
|
||||
}
|
||||
|
||||
// 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();
|
||||
let thread_id = get_current_thread_id();
|
||||
lock_acquire(&TLS_LOCK);
|
||||
|
||||
ensure_tls_storage();
|
||||
@@ -511,13 +437,8 @@ 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();
|
||||
let thread_id = get_current_thread_id();
|
||||
lock_acquire(&TLS_LOCK);
|
||||
|
||||
ensure_tls_storage();
|
||||
@@ -547,9 +468,7 @@ pub extern "C" fn _g_system_thread_create(
|
||||
data: gpointer,
|
||||
) -> *mut GSystemThread {
|
||||
unsafe {
|
||||
crate::kernel::log("frida: glib is creating a thread\n\0");
|
||||
|
||||
let system_thread = crate::kernel::alloc(core::mem::size_of::<SystemThreadImpl>()) as *mut SystemThreadImpl;
|
||||
let system_thread = crate::xnu::kalloc(core::mem::size_of::<SystemThreadImpl>()) as *mut SystemThreadImpl;
|
||||
|
||||
g_mutex_init(&mut (*system_thread).mutex);
|
||||
g_cond_init(&mut (*system_thread).cond);
|
||||
@@ -559,12 +478,12 @@ pub extern "C" fn _g_system_thread_create(
|
||||
(*system_thread).finished.store(0, Ordering::Relaxed);
|
||||
(*system_thread).detached.store(0, Ordering::Relaxed);
|
||||
|
||||
let wrapper_data = crate::kernel::alloc(core::mem::size_of::<ThreadWrapperData>()) as *mut ThreadWrapperData;
|
||||
let wrapper_data = crate::xnu::kalloc(core::mem::size_of::<ThreadWrapperData>()) as *mut ThreadWrapperData;
|
||||
(*wrapper_data).func = func;
|
||||
(*wrapper_data).data = data;
|
||||
(*wrapper_data).system_thread = system_thread;
|
||||
|
||||
let _xnu_result = crate::kernel::spawn_thread(thread_wrapper, wrapper_data as *mut c_void);
|
||||
let _xnu_result = crate::xnu::kernel_thread_start(thread_wrapper, wrapper_data as *mut c_void);
|
||||
|
||||
system_thread as *mut GSystemThread
|
||||
}
|
||||
@@ -577,9 +496,8 @@ extern "C" fn thread_wrapper(parameter: *mut c_void, _wait_result: i32) {
|
||||
let data = (*wrapper_data).data;
|
||||
let system_thread = (*wrapper_data).system_thread;
|
||||
|
||||
let current_thread_id = crate::kernel::current_thread_id();
|
||||
(*system_thread).thread_id.store(current_thread_id, Ordering::Release);
|
||||
wake_waiters(&(*system_thread).thread_id);
|
||||
let current_thread_id = get_current_thread_id();
|
||||
(*system_thread).thread_id.store(current_thread_id, Ordering::Relaxed);
|
||||
|
||||
func.unwrap()(data);
|
||||
|
||||
@@ -619,10 +537,12 @@ pub extern "C" fn _g_system_thread_wait(thread: *mut GSystemThread) {
|
||||
unsafe {
|
||||
let system_thread = thread as *mut SystemThreadImpl;
|
||||
|
||||
while (*system_thread).thread_id.load(Ordering::Acquire) == 0 {
|
||||
wait_on(&(*system_thread).thread_id, &mut || {
|
||||
(*system_thread).thread_id.load(Ordering::Acquire) != 0
|
||||
});
|
||||
loop {
|
||||
let thread_id = (*system_thread).thread_id.load(Ordering::Acquire);
|
||||
if thread_id != 0 {
|
||||
break;
|
||||
}
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
|
||||
g_mutex_lock(&mut (*system_thread).mutex);
|
||||
@@ -636,16 +556,19 @@ pub extern "C" fn _g_system_thread_wait(thread: *mut GSystemThread) {
|
||||
}
|
||||
|
||||
unsafe fn cleanup_system_thread(system_thread: *mut SystemThreadImpl) {
|
||||
crate::kernel::free(system_thread as *mut u8, core::mem::size_of::<SystemThreadImpl>());
|
||||
crate::xnu::free(system_thread as *mut u8, core::mem::size_of::<SystemThreadImpl>());
|
||||
}
|
||||
|
||||
unsafe fn cleanup_wrapper_data(wrapper_data: *mut ThreadWrapperData) {
|
||||
crate::kernel::free(wrapper_data as *mut u8, core::mem::size_of::<ThreadWrapperData>());
|
||||
crate::xnu::free(wrapper_data as *mut u8, core::mem::size_of::<ThreadWrapperData>());
|
||||
}
|
||||
|
||||
unsafe fn lock_acquire(lock: &AtomicU32) {
|
||||
while !unsafe { lock_try_acquire(lock) } {
|
||||
wait_on(lock, &mut || lock.load(Ordering::Relaxed) == 0);
|
||||
loop {
|
||||
if unsafe { lock_try_acquire(lock) } {
|
||||
break;
|
||||
}
|
||||
unsafe { lock_wait(lock) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -656,36 +579,35 @@ unsafe fn lock_try_acquire(lock: &AtomicU32) -> bool {
|
||||
|
||||
unsafe fn lock_release(lock: &AtomicU32) {
|
||||
lock.store(0, Ordering::Release);
|
||||
wake_waiters(lock);
|
||||
unsafe { lock_notify_all() };
|
||||
}
|
||||
|
||||
// Sleeps until `ready` holds or the slice expires, whichever comes first.
|
||||
//
|
||||
// These used to spin on wfe/sev. A kernel thread that spins never reaches the
|
||||
// scheduler, so a lost wakeup — or a genuine deadlock, which is how we found this —
|
||||
// starves the CPU it is on until the hardware watchdog takes the machine down. The
|
||||
// bounded sleep turns both into a stall that can be observed instead.
|
||||
//
|
||||
// The address of the word being waited on names the wait channel: XNU pairs
|
||||
// assert_wait()/thread_wakeup() on it directly, while the Linux shim has a single
|
||||
// channel and treats it as a hint. Every caller re-checks its own condition in a
|
||||
// loop, so a spurious wakeup costs nothing either way.
|
||||
fn wait_on<T>(word: &T, ready: &mut dyn FnMut() -> bool) {
|
||||
crate::kernel::wait(token_of(word), Some(WAIT_SLICE_US), ready);
|
||||
|
||||
// The wait above returns without sleeping whenever `ready` already holds, so on
|
||||
// its own it is not enough to keep a contended loop off the CPU: every caller
|
||||
// here loops, and a loop in a kernel thread that never reaches the scheduler
|
||||
// takes the machine down rather than merely running hot. Yield unconditionally.
|
||||
crate::kernel::yield_now();
|
||||
unsafe fn lock_wait(lock: &AtomicU32) {
|
||||
while lock.load(Ordering::Relaxed) != 0 {
|
||||
unsafe { lock_wait_primitive() };
|
||||
}
|
||||
}
|
||||
|
||||
fn wake_waiters<T>(word: &T) {
|
||||
crate::kernel::wake(token_of(word));
|
||||
unsafe fn lock_wait_primitive() {
|
||||
unsafe {
|
||||
asm!("wfe", options(nomem, nostack));
|
||||
}
|
||||
}
|
||||
|
||||
fn token_of<T>(word: &T) -> *const u8 {
|
||||
word as *const T as *const u8
|
||||
unsafe fn lock_notify_all() {
|
||||
unsafe {
|
||||
asm!("sev", options(nomem, nostack));
|
||||
}
|
||||
}
|
||||
|
||||
const WAIT_SLICE_US: u64 = 1000;
|
||||
// The thread pointer is exposed to JavaScript as a GumThreadId, so it must fit
|
||||
// in a double without losing precision; the low bits keep it unique per thread.
|
||||
const JS_SAFE_THREAD_ID_MASK: u64 = (1 << 48) - 1;
|
||||
|
||||
pub unsafe fn get_current_thread_id() -> u64 {
|
||||
let thread_ptr: u64;
|
||||
unsafe {
|
||||
asm!("mrs {}, tpidr_el1", out(reg) thread_ptr, options(nomem, nostack));
|
||||
}
|
||||
thread_ptr & JS_SAFE_THREAD_ID_MASK
|
||||
}
|
||||
+438
-428
@@ -1,81 +1,296 @@
|
||||
// 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.
|
||||
|
||||
use crate::{
|
||||
bindings::{
|
||||
_GInterfaceInfo, _GTypeInfo, GObject, GObjectClass, GPrivate, GType, GumAddress,
|
||||
GumExportDetails,
|
||||
GumExportType_GUM_EXPORT_FUNCTION, GumFoundExportFunc, GumMemoryRange, GumModule,
|
||||
GumModuleInterface, GumThreadId, GumTlsKey, g_free, g_object_get_type, g_object_new,
|
||||
g_once_init_enter, g_once_init_leave, g_strdup, g_type_add_interface_static,
|
||||
g_type_class_peek_parent, g_type_register_static, gchar, gpointer, gsize, guint,
|
||||
_GInterfaceInfo, _GTypeInfo, _GumPageProtection_GUM_PAGE_EXECUTE,
|
||||
_GumPageProtection_GUM_PAGE_READ, _GumPageProtection_GUM_PAGE_WRITE,
|
||||
_GumRwxSupport_GUM_RWX_NONE, GArray, GObject, GObjectClass, GPrivate, GType,
|
||||
GumDebugSymbolDetails, GumExportDetails, GumExportType_GUM_EXPORT_FUNCTION,
|
||||
GumFoundExportFunc, GumMemoryRange, GumModule, GumModuleInterface, GumModuleRegistry,
|
||||
GumPageProtection, GumRwxSupport, GumThreadId, GumTlsKey, g_array_append_vals, g_array_new,
|
||||
g_free, g_object_get_type, g_object_new, g_object_unref, g_once_init_enter,
|
||||
g_once_init_leave, g_strdup, g_type_add_interface_static, g_type_class_peek_parent,
|
||||
g_type_register_static, g_variant_get_boolean, g_variant_get_uint64, g_variant_new,
|
||||
g_variant_new_fixed_array, g_variant_type_free, g_variant_type_new, gboolean, gchar,
|
||||
gconstpointer, gpointer, gsize, guint,
|
||||
gum_barebone_register_module,
|
||||
gum_barebone_try_remap_writable_pages as _gum_barebone_try_remap_writable_pages,
|
||||
g_variant_unref, gum_module_get_type, gum_mprotect, gum_query_page_size,
|
||||
},
|
||||
gthread, kernel, libc,
|
||||
gthread, host_rpc, libc, FridaCommand,
|
||||
};
|
||||
use alloc::boxed::Box;
|
||||
use alloc::ffi::CString;
|
||||
use core::ffi::CStr;
|
||||
use alloc::format;
|
||||
use alloc::vec::Vec;
|
||||
use core::ffi::CStr;
|
||||
use core::mem::size_of;
|
||||
use core::ptr;
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
use crate::gum_linux::enumerate_exports_in_range;
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
use crate::bindings::{
|
||||
GumFoundSymbolFunc, GumSymbolDetails, GumSymbolType_GUM_SYMBOL_FUNCTION,
|
||||
GumSymbolType_GUM_SYMBOL_OBJECT, GumSymbolType_GUM_SYMBOL_UNKNOWN, gboolean, gssize,
|
||||
};
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
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;
|
||||
// Code slabs the agent allocates are normal writable RAM, so gum can keep a real writable alias to
|
||||
// them across the slab's lifetime. Pre-existing kernel/kext text is CTRR-locked and can only be
|
||||
// modified through the host's physical-memory bridge, so we shadow it and commit on dispose. We
|
||||
// distinguish the two by tracking the executable ranges we hand out.
|
||||
static SLAB_LOCK: AtomicU32 = AtomicU32::new(0);
|
||||
static mut SLABS: Vec<(u64, u64)> = Vec::new();
|
||||
|
||||
const SHADOW_MAGIC: u64 = 0x4644_4f48_5341_4853;
|
||||
const SHADOW_HEADER: usize = 24;
|
||||
const SHADOW_MIN_ADDRESS: u64 = 0xffff_f000_0000_0000;
|
||||
|
||||
fn slab_lock() {
|
||||
while SLAB_LOCK
|
||||
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
}
|
||||
|
||||
fn slab_unlock() {
|
||||
SLAB_LOCK.store(0, Ordering::Release);
|
||||
}
|
||||
|
||||
fn register_slab(start: u64, size: usize) {
|
||||
slab_lock();
|
||||
unsafe {
|
||||
(*core::ptr::addr_of_mut!(SLABS)).push((start, start + size as u64));
|
||||
}
|
||||
slab_unlock();
|
||||
}
|
||||
|
||||
fn unregister_slab(start: u64) {
|
||||
slab_lock();
|
||||
unsafe {
|
||||
(*core::ptr::addr_of_mut!(SLABS)).retain(|&(begin, _)| begin != start);
|
||||
}
|
||||
slab_unlock();
|
||||
}
|
||||
|
||||
fn is_agent_slab(address: u64) -> bool {
|
||||
slab_lock();
|
||||
let found = unsafe {
|
||||
(*core::ptr::addr_of!(SLABS))
|
||||
.iter()
|
||||
.any(|&(begin, end)| address >= begin && address < end)
|
||||
};
|
||||
slab_unlock();
|
||||
found
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_process_get_current_thread_id() -> GumThreadId {
|
||||
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
|
||||
unsafe { gthread::get_current_thread_id() }
|
||||
}
|
||||
|
||||
#[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));
|
||||
}
|
||||
let tcr_el1: u64;
|
||||
core::arch::asm!("mrs {}, tcr_el1", out(reg) tcr_el1, options(nomem, nostack));
|
||||
|
||||
match (translation_control >> 14) & 0x3 {
|
||||
0b01 => 65536,
|
||||
0b10 => 16384,
|
||||
_ => 4096,
|
||||
let tg0 = (tcr_el1 >> 14) & 0x3;
|
||||
|
||||
match tg0 {
|
||||
0b00 => 4096,
|
||||
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
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_query_rwx_support() -> GumRwxSupport {
|
||||
_GumRwxSupport_GUM_RWX_NONE
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_can_remap_writable() -> gboolean {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_try_remap_writable_pages(
|
||||
first_page: gpointer,
|
||||
n_pages: guint,
|
||||
) -> gpointer {
|
||||
if is_agent_slab(first_page as u64) {
|
||||
return remap_agent_pages(first_page, n_pages);
|
||||
}
|
||||
shadow_kernel_pages(first_page, n_pages)
|
||||
}
|
||||
|
||||
fn remap_agent_pages(first_page: gpointer, n_pages: guint) -> gpointer {
|
||||
unsafe {
|
||||
let page_size = gum_query_page_size() as usize;
|
||||
let mut virtual_addrs = Vec::with_capacity(n_pages as usize);
|
||||
|
||||
let mut current_page = first_page as u64;
|
||||
for _ in 0..n_pages {
|
||||
virtual_addrs.push(current_page as gpointer);
|
||||
current_page += page_size as u64;
|
||||
}
|
||||
|
||||
_gum_barebone_try_remap_writable_pages(
|
||||
virtual_addrs.as_ptr() as *mut *const core::ffi::c_void,
|
||||
virtual_addrs.len() as guint,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn shadow_kernel_pages(first_page: gpointer, n_pages: guint) -> gpointer {
|
||||
unsafe {
|
||||
let total = n_pages as usize * gum_query_page_size() as usize;
|
||||
let buffer = crate::xnu::kalloc(SHADOW_HEADER + total);
|
||||
*(buffer as *mut u64) = SHADOW_MAGIC;
|
||||
*(buffer.add(8) as *mut u64) = first_page as u64;
|
||||
*(buffer.add(16) as *mut u32) = n_pages;
|
||||
|
||||
let body = buffer.add(SHADOW_HEADER);
|
||||
core::ptr::copy_nonoverlapping(first_page as *const u8, body, total);
|
||||
body as gpointer
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages: guint) {
|
||||
if (writable as u64) < SHADOW_MIN_ADDRESS {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
let buffer = (writable as *mut u8).sub(SHADOW_HEADER);
|
||||
if *(buffer as *const u64) != SHADOW_MAGIC {
|
||||
return;
|
||||
}
|
||||
let first_page = *(buffer.add(8) as *const u64);
|
||||
let n_pages = *(buffer.add(16) as *const u32);
|
||||
let total = n_pages as usize * gum_query_page_size() as usize;
|
||||
|
||||
commit_kernel_patch(first_page, writable as *const u8, total);
|
||||
libc::__clear_cache(first_page as *const u8, (first_page + total as u64) as *const u8);
|
||||
|
||||
crate::xnu::free(buffer, SHADOW_HEADER + total);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn commit_kernel_patch(address: u64, data: *const u8, len: usize) {
|
||||
unsafe {
|
||||
let element_type = g_variant_type_new(c"y".as_ptr());
|
||||
let bytes = g_variant_new_fixed_array(element_type, data as gconstpointer, len as gsize, 1);
|
||||
g_variant_type_free(element_type);
|
||||
|
||||
let payload = g_variant_new(c"(t@ay)".as_ptr(), address, bytes);
|
||||
let reply = host_rpc(FridaCommand::PatchCode, payload);
|
||||
g_variant_unref(reply);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_try_remap_writable_pages(
|
||||
addrs: *const gpointer,
|
||||
n_addrs: guint,
|
||||
) -> gpointer {
|
||||
if !crate::transport_is_up() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
unsafe {
|
||||
let element_type = g_variant_type_new(c"t".as_ptr());
|
||||
let payload = g_variant_new_fixed_array(
|
||||
element_type,
|
||||
addrs as gconstpointer,
|
||||
n_addrs as gsize,
|
||||
size_of::<u64>() as gsize,
|
||||
);
|
||||
g_variant_type_free(element_type);
|
||||
|
||||
let reply = host_rpc(FridaCommand::RemapWritablePages, payload);
|
||||
let virtual_address = g_variant_get_uint64(reply);
|
||||
g_variant_unref(reply);
|
||||
|
||||
virtual_address as gpointer
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_try_mprotect(
|
||||
address: gpointer,
|
||||
size: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gboolean {
|
||||
if !crate::transport_is_up() {
|
||||
return 1;
|
||||
}
|
||||
unsafe {
|
||||
let payload = g_variant_new(c"(ttu)".as_ptr(), address as u64, size as u64, prot as u32);
|
||||
|
||||
let reply = host_rpc(FridaCommand::MemoryProtect, payload);
|
||||
let success = g_variant_get_boolean(reply);
|
||||
g_variant_unref(reply);
|
||||
|
||||
if success != 0 {
|
||||
flush_tlb_range(address as u64, size as u64);
|
||||
}
|
||||
|
||||
success
|
||||
}
|
||||
}
|
||||
|
||||
// The host rewrites our page-table descriptors through the physical-memory
|
||||
// bridge, which leaves this CPU's TLB holding the stale translation. Stalker
|
||||
// flips a slab page RW then RX in place, so without this the freeze never takes
|
||||
// effect and executing the page faults with a permission abort.
|
||||
unsafe fn flush_tlb_range(address: u64, size: u64) {
|
||||
unsafe {
|
||||
let page_size = gum_query_page_size() as u64;
|
||||
let start = address & !(page_size - 1);
|
||||
let end = (address + size + page_size - 1) & !(page_size - 1);
|
||||
|
||||
core::arch::asm!("dsb ish", options(nostack, preserves_flags));
|
||||
let mut va = start;
|
||||
while va < end {
|
||||
core::arch::asm!("tlbi vaae1is, {operand}", operand = in(reg) va >> 12,
|
||||
options(nostack, preserves_flags));
|
||||
va += page_size;
|
||||
}
|
||||
core::arch::asm!("dsb ish", "isb", options(nostack, preserves_flags));
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_allocate(
|
||||
_address: gpointer,
|
||||
size: gsize,
|
||||
_alignment: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gpointer {
|
||||
let ptr = crate::xnu::kalloc(size as usize);
|
||||
unsafe {
|
||||
core::ptr::write_bytes(ptr, 0, size as usize);
|
||||
if (prot & _GumPageProtection_GUM_PAGE_EXECUTE) != 0 {
|
||||
register_slab(ptr as u64, size as usize);
|
||||
gum_mprotect(ptr as gpointer, size, prot);
|
||||
}
|
||||
}
|
||||
ptr as gpointer
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_free(address: gpointer, size: gsize) -> gboolean {
|
||||
// Executable slabs were flipped to RX in the page tables; restore RW before returning them to
|
||||
// the allocator, otherwise the reclaimed pages stay non-writable and the next consumer faults.
|
||||
if is_agent_slab(address as u64) {
|
||||
unsafe {
|
||||
gum_mprotect(
|
||||
address,
|
||||
size,
|
||||
(_GumPageProtection_GUM_PAGE_READ | _GumPageProtection_GUM_PAGE_WRITE)
|
||||
as GumPageProtection,
|
||||
);
|
||||
}
|
||||
unregister_slab(address as u64);
|
||||
}
|
||||
crate::xnu::free(address as *mut u8, size as usize);
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
@@ -109,6 +324,40 @@ pub extern "C" fn gum_tls_key_set_value(key: GumTlsKey, value: gpointer) {
|
||||
gthread::g_private_set(key as *mut GPrivate, value)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
|
||||
let kernel_base = crate::xnu::get_kernel_base();
|
||||
|
||||
unsafe {
|
||||
let module_infos = core::ptr::addr_of!(crate::MODULE_INFO);
|
||||
let module_infos = &*module_infos;
|
||||
|
||||
let mut i = 0;
|
||||
for module_info in module_infos.iter() {
|
||||
let module_base = kernel_base + module_info.offset as u64;
|
||||
|
||||
let module_path = if i == 0 {
|
||||
"/System/Library/Kernels/kernel"
|
||||
} else {
|
||||
&format!(
|
||||
"/System/Library/Extensions/{}.kext/{}",
|
||||
module_info.name, module_info.name
|
||||
)
|
||||
};
|
||||
let module_range = GumMemoryRange {
|
||||
base_address: module_base,
|
||||
size: module_info.size as u64,
|
||||
};
|
||||
|
||||
let module = gum_native_module_new(&module_path, &module_info.version, &module_range);
|
||||
gum_barebone_register_module(registry, module);
|
||||
g_object_unref(module as gpointer);
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct GumNativeModule {
|
||||
parent: GObject,
|
||||
@@ -139,7 +388,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,
|
||||
@@ -151,12 +400,12 @@ 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(),
|
||||
};
|
||||
|
||||
g_type_add_interface_static(new_type, crate::bindings::gum_module_get_type(), &interface_info);
|
||||
g_type_add_interface_static(new_type, gum_module_get_type(), &interface_info);
|
||||
|
||||
g_once_init_leave(
|
||||
core::ptr::addr_of_mut!(GUM_NATIVE_MODULE_TYPE) as *mut ::core::ffi::c_void,
|
||||
@@ -174,29 +423,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(any(feature = "linux", 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -210,36 +448,22 @@ unsafe extern "C" fn gum_native_module_finalize(object: *mut GObject) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn gum_native_module_new(
|
||||
path: &str,
|
||||
version: &str,
|
||||
range: &GumMemoryRange,
|
||||
) -> *mut GumModule {
|
||||
fn gum_native_module_new(path: &str, version: &str, range: &GumMemoryRange) -> *mut GumModule {
|
||||
unsafe {
|
||||
let path_cstr = CString::new(path).unwrap();
|
||||
let version_cstr = CString::new(version).unwrap();
|
||||
|
||||
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;
|
||||
@@ -268,34 +492,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,
|
||||
@@ -303,320 +499,134 @@ unsafe extern "C" fn gum_native_module_enumerate_exports(
|
||||
) {
|
||||
unsafe {
|
||||
let module = self_ as *mut GumNativeModule;
|
||||
let range = (*module).range;
|
||||
let module_range = &(*module).range;
|
||||
|
||||
let symbol_table = core::ptr::addr_of!(crate::SYMBOL_TABLE);
|
||||
let symbol_table = &*symbol_table;
|
||||
|
||||
const N_EXT: u8 = 0x01; // External symbol flag
|
||||
const N_TYPE: u8 = 0x0e; // Type mask
|
||||
const N_SECT: u8 = 0x0e; // Defined in section
|
||||
|
||||
let start_address = module_range.base_address;
|
||||
let end_address = module_range.base_address + module_range.size;
|
||||
|
||||
for symbol_ref in symbol_table.iter_symbols_in_range(start_address, end_address) {
|
||||
let is_external = (symbol_ref.symbol_type() & N_EXT) != 0;
|
||||
let is_defined = (symbol_ref.symbol_type() & N_TYPE) == N_SECT;
|
||||
if !is_external || !is_defined {
|
||||
continue;
|
||||
}
|
||||
|
||||
let export_type = GumExportType_GUM_EXPORT_FUNCTION;
|
||||
|
||||
let mut on_export = |name: *const gchar, address: u64| {
|
||||
let export_details = GumExportDetails {
|
||||
type_: GumExportType_GUM_EXPORT_FUNCTION,
|
||||
name,
|
||||
address,
|
||||
type_: export_type,
|
||||
name: g_strdup(symbol_ref.name_ptr()),
|
||||
address: symbol_ref.address(),
|
||||
size: -1,
|
||||
};
|
||||
|
||||
func.unwrap()(&export_details as *const GumExportDetails, user_data) != 0
|
||||
};
|
||||
|
||||
enumerate_exports_in_range(
|
||||
range.base_address,
|
||||
range.base_address + range.size as u64,
|
||||
&mut on_export,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[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(any(feature = "linux", 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(any(feature = "linux", feature = "linux-injected"))]
|
||||
pub(crate) type FoundSymbolCallback<'a> = dyn FnMut(*const gchar, u64, u64, u8, bool) -> bool + 'a;
|
||||
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
const SYMBOL_IS_OBJECT: u8 = 1;
|
||||
#[cfg(any(feature = "linux", 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
|
||||
// we treat it differently. We distinguish the two by tracking the executable ranges we hand out.
|
||||
static SLAB_LOCK: AtomicU32 = AtomicU32::new(0);
|
||||
static mut SLABS: Vec<(u64, u64)> = Vec::new();
|
||||
|
||||
fn slab_lock() {
|
||||
while SLAB_LOCK
|
||||
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
kernel::yield_now();
|
||||
}
|
||||
}
|
||||
|
||||
fn slab_unlock() {
|
||||
SLAB_LOCK.store(0, Ordering::Release);
|
||||
}
|
||||
|
||||
pub(crate) fn register_slab(start: u64, size: usize) {
|
||||
slab_lock();
|
||||
unsafe {
|
||||
(*core::ptr::addr_of_mut!(SLABS)).push((start, start + size as u64));
|
||||
}
|
||||
slab_unlock();
|
||||
}
|
||||
|
||||
pub(crate) fn unregister_slab(start: u64) {
|
||||
slab_lock();
|
||||
unsafe {
|
||||
(*core::ptr::addr_of_mut!(SLABS)).retain(|&(begin, _)| begin != start);
|
||||
}
|
||||
slab_unlock();
|
||||
}
|
||||
|
||||
pub(crate) fn is_agent_slab(address: u64) -> bool {
|
||||
slab_lock();
|
||||
let found = unsafe {
|
||||
(*core::ptr::addr_of!(SLABS))
|
||||
.iter()
|
||||
.any(|&(begin, end)| address >= begin && address < end)
|
||||
};
|
||||
slab_unlock();
|
||||
found
|
||||
}
|
||||
|
||||
// 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 {
|
||||
let should_continue =
|
||||
func.unwrap()(&export_details as *const GumExportDetails, user_data);
|
||||
if should_continue == 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;
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_symbol_details_from_address(
|
||||
address: gpointer,
|
||||
details: *mut GumDebugSymbolDetails,
|
||||
) -> gboolean {
|
||||
unsafe {
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
if let Some(symbol) = table.find_symbol_by_address(address as u64) {
|
||||
let name_bytes = symbol.name().as_bytes();
|
||||
let copy_len = core::cmp::min(name_bytes.len(), 2048);
|
||||
core::ptr::copy_nonoverlapping(
|
||||
name_bytes.as_ptr(),
|
||||
(*details).symbol_name.as_mut_ptr() as *mut u8,
|
||||
copy_len,
|
||||
);
|
||||
(*details).symbol_name[copy_len] = 0;
|
||||
|
||||
(*details).address = symbol.address();
|
||||
|
||||
(*details).module_name[0] = 0;
|
||||
(*details).file_name[0] = 0;
|
||||
(*details).line_number = 0;
|
||||
(*details).column = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_symbol_name_from_address(address: gpointer) -> *mut gchar {
|
||||
unsafe {
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
let name_ptr = table.find_symbol_name_ptr_by_address(address as u64);
|
||||
g_strdup(name_ptr as *const gchar)
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_function(name: *const gchar) -> gpointer {
|
||||
unsafe {
|
||||
let target_name = CStr::from_ptr(name).to_string_lossy();
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
|
||||
if let Some(symbol) = table.find_symbol_by_name(&target_name) {
|
||||
return symbol.address() as gpointer;
|
||||
}
|
||||
|
||||
core::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_functions_named(name: *const gchar) -> *mut GArray {
|
||||
unsafe {
|
||||
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
|
||||
|
||||
let target_name = CStr::from_ptr(name).to_string_lossy();
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
|
||||
let symbols = table.find_symbols_by_name(&target_name);
|
||||
for symbol in symbols {
|
||||
let addr = symbol.address() as gpointer;
|
||||
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
|
||||
}
|
||||
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_functions_matching(pattern: *const gchar) -> *mut GArray {
|
||||
unsafe {
|
||||
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
|
||||
|
||||
let glob_pattern = CStr::from_ptr(pattern).to_string_lossy();
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
|
||||
let symbols = table.find_symbols_matching_glob(&glob_pattern);
|
||||
for symbol in symbols {
|
||||
let addr = symbol.address() as gpointer;
|
||||
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
|
||||
}
|
||||
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
|
||||
0
|
||||
}
|
||||
@@ -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 411 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user