mirror of
https://github.com/frida/frida-core.git
synced 2026-11-08 16:49:36 -10:00
Compare commits
No files matched your search
@@ -21,7 +21,7 @@ jobs:
|
||||
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: MINGW64, pkg: 'mingw-w64-x86_64-gcc' }
|
||||
- { id: windows-x86_64, runner: '"windows-latest"', sys: UCRT64, pkg: 'mingw-w64-ucrt-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"' }
|
||||
@@ -451,7 +451,10 @@ jobs:
|
||||
cd build
|
||||
cp -a tests/frida-tests tests/labrats lib/agent/frida-agent.so /tmp/pkg/
|
||||
tar -C /tmp/pkg -cf /tmp/runner.tar .
|
||||
/opt/sabrelite/run.sh /tmp/runner.tar /opt/frida/frida-tests
|
||||
/opt/sabrelite/run.sh /tmp/runner.tar \
|
||||
"cp /opt/frida/frida-tests /opt/frida/frida-agent.so /tmp/ && \
|
||||
ln -sP /opt/frida/labrats /tmp/labrats && \
|
||||
/tmp/frida-tests"
|
||||
|
||||
# This job is used to check that the Linux helpers are built correctly for all
|
||||
# supported architectures. We do this because the binary artifacts are checked
|
||||
@@ -668,9 +671,17 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: ${{ matrix.msvc-arch }}
|
||||
- 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: actions/setup-python@v6
|
||||
with:
|
||||
|
||||
@@ -43,7 +43,7 @@ platform-dependent (e.g. `linux-x86_64`).
|
||||
## Build Requirements
|
||||
|
||||
- **Vala compiler**: Must be the Frida-optimized fork from
|
||||
https://github.com/frida/vala (checks for `-frida` suffix
|
||||
https://github.com/frida-forks/vala (checks for `-frida` suffix
|
||||
in version)
|
||||
- **C/C++ standards**: gnu99/c99 for C, c++17 for C++
|
||||
- **Go >= 1.24**: Required for compiler backend
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
PYTHON ?= $(shell which python3 >/dev/null && echo python3 || echo python)
|
||||
PYTHON ?= $(shell command -v python3 >/dev/null && echo python3 || echo python)
|
||||
|
||||
all $(MAKECMDGOALS):
|
||||
@$(PYTHON) \
|
||||
@@ -8,7 +8,7 @@ all $(MAKECMDGOALS):
|
||||
$(MAKECMDGOALS)
|
||||
|
||||
git-submodules:
|
||||
@if [ ! -f releng/meson/meson.py ]; then \
|
||||
@if [ ! -f releng/meson/meson.py ] || git submodule status 2>/dev/null | grep -q '^-'; then \
|
||||
git submodule update --init --recursive --depth 1; \
|
||||
fi
|
||||
-include git-submodules
|
||||
|
||||
+15
-11
@@ -19,7 +19,7 @@ import traceback
|
||||
from typing import Any, Literal, Mapping, Optional, Sequence, Tuple
|
||||
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parent.parent
|
||||
REPO_ROOT = Path(__file__).absolute().parent.parent
|
||||
NINJA = os.environ.get("NINJA", "ninja")
|
||||
|
||||
|
||||
@@ -592,8 +592,10 @@ def load_meson_options(top_builddir: Path,
|
||||
subprojects: set[str]) -> Sequence[str]:
|
||||
from mesonbuild import coredata
|
||||
|
||||
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)]
|
||||
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)]
|
||||
|
||||
|
||||
def adapt_key(k: "OptionKey", role: Role) -> "OptionKey":
|
||||
@@ -603,44 +605,46 @@ def adapt_key(k: "OptionKey", role: Role) -> "OptionKey":
|
||||
|
||||
|
||||
def option_should_be_forwarded(k: "OptionKey",
|
||||
v: "coredata.UserOption[Any]",
|
||||
v: "options.UserOption[Any]",
|
||||
optstore: "options.OptionStore",
|
||||
role: Role,
|
||||
subprojects: set[str]) -> bool:
|
||||
from mesonbuild import coredata
|
||||
from mesonbuild import options
|
||||
from mesonbuild.mesonlib import MachineChoice
|
||||
|
||||
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 k.is_project():
|
||||
if optstore.is_project_option(k):
|
||||
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 coredata.MachineChoice.HOST:
|
||||
if k.subproject and k.machine is not MachineChoice.HOST:
|
||||
return False
|
||||
return is_for_us or is_for_child
|
||||
|
||||
if coredata.CoreData.is_per_machine_option(k):
|
||||
if optstore.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 k.is_builtin():
|
||||
if optstore.is_builtin_option(k):
|
||||
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 coredata.BUILTIN_OPTIONS[k.as_root()].yielding:
|
||||
if not options.BUILTIN_OPTIONS[k.evolve(subproject=None, machine=MachineChoice.HOST)].yielding:
|
||||
return k.subproject == our_project_id
|
||||
return True
|
||||
|
||||
if k.module == "python":
|
||||
if k.get_module_prefix() == "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=$(which python3 >/dev/null && echo python3 || echo python)
|
||||
[ -z "$PYTHON" ] && PYTHON=$(command -v python3 >/dev/null && echo python3 || echo python)
|
||||
|
||||
cd $(dirname $0)
|
||||
|
||||
srcroot=$(pwd)
|
||||
|
||||
if [ ! -f releng/meson/meson.py ]; then
|
||||
if [ ! -f releng/meson/meson.py ] || git submodule status 2>/dev/null | grep -q '^-'; then
|
||||
git submodule update --init --recursive --depth 1 || exit $?
|
||||
fi
|
||||
|
||||
|
||||
@@ -13,6 +13,44 @@
|
||||
# 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
|
||||
|
||||
+413
-35
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida.Agent {
|
||||
public void main (string agent_parameters, ref Frida.UnloadPolicy unload_policy, void * injector_state) {
|
||||
if (Runner.shared_instance == null)
|
||||
@@ -78,6 +80,7 @@ namespace Frida.Agent {
|
||||
private ExitMonitor? exit_monitor;
|
||||
private Gum.Interceptor interceptor;
|
||||
private Gum.Exceptor? exceptor;
|
||||
private bool code_memory_delegated = false;
|
||||
|
||||
private uint child_gating_subscriber_count = 0;
|
||||
private ForkMonitor? fork_monitor;
|
||||
@@ -100,6 +103,9 @@ namespace Frida.Agent {
|
||||
private SpawnMonitor? spawn_monitor;
|
||||
private ThreadSuspendMonitor? thread_suspend_monitor;
|
||||
private UnwindSitter? unwind_sitter;
|
||||
#if ANDROID && (ARM || ARM64)
|
||||
private NativeCloakMirror? native_cloak_mirror;
|
||||
#endif
|
||||
|
||||
private delegate void CompletionNotify ();
|
||||
|
||||
@@ -122,12 +128,43 @@ 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);
|
||||
@@ -157,6 +194,19 @@ 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);
|
||||
@@ -180,6 +230,15 @@ namespace Frida.Agent {
|
||||
return;
|
||||
} else {
|
||||
release_shared_instance ();
|
||||
#if WINDOWS
|
||||
if (windows_state != null) {
|
||||
Gum.Cloak.remove_range (windows_state.worker_range);
|
||||
if (windows_state.dbghelp_range.size != 0) {
|
||||
Gum.Cloak.remove_range (windows_state.dbghelp_range);
|
||||
Gum.Cloak.remove_range (windows_state.symsrv_range);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
ignore_scope = null;
|
||||
@@ -266,25 +325,12 @@ namespace Frida.Agent {
|
||||
}
|
||||
|
||||
~Runner () {
|
||||
var interceptor = this.interceptor;
|
||||
interceptor.begin_transaction ();
|
||||
remove_instrumentation ();
|
||||
|
||||
disable_child_gating ();
|
||||
|
||||
exceptor = null;
|
||||
|
||||
exit_monitor = null;
|
||||
|
||||
interceptor.end_transaction ();
|
||||
|
||||
interceptor.begin_transaction ();
|
||||
|
||||
thread_suspend_monitor = null;
|
||||
unwind_sitter = null;
|
||||
if (code_memory_delegated)
|
||||
Gum.Memory.set_hooks (null);
|
||||
|
||||
invalidate_dbus_context ();
|
||||
|
||||
interceptor.end_transaction ();
|
||||
}
|
||||
|
||||
private void run (owned FileDescriptorTablePadder padder) throws Error {
|
||||
@@ -307,6 +353,9 @@ namespace Frida.Agent {
|
||||
bool enable_unwind_broker = true;
|
||||
bool enable_exit_monitor = true;
|
||||
bool enable_thread_suspend_monitor = true;
|
||||
#if ANDROID && (ARM || ARM64)
|
||||
bool in_emulated_realm = false;
|
||||
#endif
|
||||
foreach (unowned string option in tokens[1:]) {
|
||||
if (option == "eternal")
|
||||
ensure_eternalized ();
|
||||
@@ -322,38 +371,47 @@ namespace Frida.Agent {
|
||||
enable_exit_monitor = false;
|
||||
else if (option == "thread-suspend-monitor:off")
|
||||
enable_thread_suspend_monitor = false;
|
||||
#if ANDROID && (ARM || ARM64)
|
||||
else if (option == "realm:emulated")
|
||||
in_emulated_realm = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (exceptor_mode != FULL)
|
||||
Gum.Exceptor.set_mode (exceptor_mode);
|
||||
|
||||
{
|
||||
var interceptor = Gum.Interceptor.obtain ();
|
||||
interceptor.begin_transaction ();
|
||||
|
||||
if (enable_exit_monitor)
|
||||
exit_monitor = new ExitMonitor (this, main_context);
|
||||
|
||||
if (enable_thread_suspend_monitor)
|
||||
thread_suspend_monitor = new ThreadSuspendMonitor (this);
|
||||
|
||||
if (enable_unwind_broker)
|
||||
unwind_sitter = new UnwindSitter (this);
|
||||
|
||||
this.interceptor = interceptor;
|
||||
this.exceptor = Gum.Exceptor.obtain ();
|
||||
|
||||
interceptor.end_transaction ();
|
||||
}
|
||||
interceptor = Gum.Interceptor.obtain ();
|
||||
|
||||
try {
|
||||
yield setup_connection_with_transport_uri (transport_uri);
|
||||
#if ANDROID && (ARM || ARM64)
|
||||
if (in_emulated_realm)
|
||||
yield connect_to_native_realm ();
|
||||
#endif
|
||||
} catch (Error e) {
|
||||
start_error = e;
|
||||
main_loop.quit ();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!can_allocate_code ())
|
||||
delegate_code_memory_to_controller ();
|
||||
|
||||
interceptor.begin_transaction ();
|
||||
|
||||
if (enable_exit_monitor)
|
||||
exit_monitor = new ExitMonitor (this, main_context);
|
||||
|
||||
if (enable_thread_suspend_monitor)
|
||||
thread_suspend_monitor = new ThreadSuspendMonitor (this);
|
||||
|
||||
if (enable_unwind_broker)
|
||||
unwind_sitter = new UnwindSitter (this);
|
||||
|
||||
exceptor = Gum.Exceptor.obtain ();
|
||||
|
||||
interceptor.end_transaction ();
|
||||
|
||||
Gum.ScriptBackend.get_scheduler ().push_job_on_js_thread (Priority.DEFAULT, () => {
|
||||
schedule_idle (start.callback);
|
||||
});
|
||||
@@ -362,6 +420,77 @@ namespace Frida.Agent {
|
||||
padder = null;
|
||||
}
|
||||
|
||||
#if ANDROID && (ARM || ARM64)
|
||||
private async void connect_to_native_realm () throws Error {
|
||||
try {
|
||||
NativeCloak cloak = yield connection.get_proxy (null, ObjectPath.NATIVE_CLOAK, DO_NOT_LOAD_PROPERTIES,
|
||||
null);
|
||||
native_cloak_mirror = new NativeCloakMirror (cloak);
|
||||
native_cloak_mirror.activate ();
|
||||
|
||||
NativeThreadRegistry registry = yield connection.get_proxy (null, ObjectPath.NATIVE_THREAD_REGISTRY,
|
||||
DO_NOT_LOAD_PROPERTIES, null);
|
||||
Gum.ThreadRegistry.set_provider (new NativeThreadProvider (registry));
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private static bool can_allocate_code () {
|
||||
size_t page_size = Gum.query_page_size ();
|
||||
|
||||
void * page = Gum.Memory.allocate (null, page_size, page_size, READ | EXECUTE);
|
||||
if (page == null)
|
||||
return false;
|
||||
|
||||
Gum.Memory.free (page, page_size);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void delegate_code_memory_to_controller () {
|
||||
var hooks = Gum.MemoryHooks () {
|
||||
allocate = allocate_via_controller,
|
||||
protect = protect_via_controller,
|
||||
user_data = this,
|
||||
};
|
||||
Gum.Memory.set_hooks (hooks);
|
||||
|
||||
code_memory_delegated = true;
|
||||
}
|
||||
|
||||
private static void * allocate_via_controller (Gum.AddressSpec? spec, size_t size, size_t alignment,
|
||||
Gum.PageProtection prot, void * user_data) {
|
||||
unowned Runner self = (Runner) user_data;
|
||||
|
||||
uint64 near_address = 0;
|
||||
uint64 max_distance = 0;
|
||||
if (spec != null) {
|
||||
near_address = (uint64) spec.near_address;
|
||||
max_distance = spec.max_distance;
|
||||
}
|
||||
|
||||
try {
|
||||
return (void *) self.controller.allocate_memory (get_process_id (), near_address, max_distance, size,
|
||||
alignment, prot, null);
|
||||
} catch (GLib.Error e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool protect_via_controller (void * address, size_t size, Gum.PageProtection prot,
|
||||
void * user_data) {
|
||||
unowned Runner self = (Runner) user_data;
|
||||
|
||||
try {
|
||||
self.controller.protect_memory (get_process_id (), (uint64) address, size, prot, null);
|
||||
return true;
|
||||
} catch (GLib.Error e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void keep_running_eternalized () {
|
||||
agent_gthread = new Thread<bool> ("frida-eternal-agent", () => {
|
||||
var ignore_scope = new ThreadIgnoreScope (FRIDA_THREAD);
|
||||
@@ -1023,6 +1152,10 @@ namespace Frida.Agent {
|
||||
if (unloading)
|
||||
throw new Error.INVALID_OPERATION ("Agent is already unloading");
|
||||
unloading = true;
|
||||
|
||||
if (code_memory_delegated && !is_eternal)
|
||||
yield remove_instrumentation_while_delegated ();
|
||||
|
||||
perform_unload.begin ();
|
||||
}
|
||||
|
||||
@@ -1068,6 +1201,34 @@ namespace Frida.Agent {
|
||||
}
|
||||
}
|
||||
|
||||
private async void remove_instrumentation_while_delegated () {
|
||||
yield close_all_sessions ();
|
||||
|
||||
remove_instrumentation ();
|
||||
}
|
||||
|
||||
private void remove_instrumentation () {
|
||||
var interceptor = this.interceptor;
|
||||
interceptor.begin_transaction ();
|
||||
|
||||
disable_child_gating ();
|
||||
|
||||
exceptor = null;
|
||||
|
||||
exit_monitor = null;
|
||||
|
||||
interceptor.end_transaction ();
|
||||
|
||||
interceptor.begin_transaction ();
|
||||
|
||||
thread_suspend_monitor = null;
|
||||
unwind_sitter = null;
|
||||
|
||||
interceptor.end_transaction ();
|
||||
|
||||
Gum.shutdown ();
|
||||
}
|
||||
|
||||
private void ensure_eternalized () {
|
||||
if (!_is_eternal) {
|
||||
_is_eternal = true;
|
||||
@@ -1276,7 +1437,7 @@ namespace Frida.Agent {
|
||||
|
||||
private void on_connection_closed (DBusConnection connection, bool remote_peer_vanished, GLib.Error? error) {
|
||||
bool closed_by_us = !remote_peer_vanished && error == null;
|
||||
if (!closed_by_us)
|
||||
if (!closed_by_us && !code_memory_delegated)
|
||||
unload.begin (null);
|
||||
|
||||
Promise<bool> operation = null;
|
||||
@@ -1348,6 +1509,8 @@ namespace Frida.Agent {
|
||||
private Socket? emulated_socket;
|
||||
private BridgeState? emulated_bridge_state;
|
||||
private Thread<void>? emulated_worker;
|
||||
private NativeCloakService? native_cloak;
|
||||
private NativeThreadRegistryService? native_thread_registry;
|
||||
|
||||
private async AgentSessionProvider? try_get_emulated_provider (Cancellable? cancellable) throws IOError {
|
||||
if (get_emulated_request == null)
|
||||
@@ -1387,6 +1550,8 @@ namespace Frida.Agent {
|
||||
emulated_agent = nb_api.load_library_ext (emulated_agent_path, RTLD_LAZY, classloader_namespace);
|
||||
} else {
|
||||
emulated_agent = nb_api.load_library (emulated_agent_path, RTLD_LAZY);
|
||||
if (emulated_agent == null && nb_api.load_library_ext != null)
|
||||
emulated_agent = nb_api.load_library_ext (emulated_agent_path, RTLD_LAZY, null);
|
||||
}
|
||||
if (emulated_agent == null)
|
||||
throw new Error.NOT_SUPPORTED ("Process is not using emulation");
|
||||
@@ -1426,6 +1591,7 @@ namespace Frida.Agent {
|
||||
* code being discarded, which seems like an edge-case the translator doesn't handle.
|
||||
*/
|
||||
parameters.append ("|exceptor:off|exit-monitor:off");
|
||||
parameters.append ("|realm:emulated");
|
||||
|
||||
emulated_bridge_state = new BridgeState (parameters.str);
|
||||
|
||||
@@ -1434,6 +1600,13 @@ namespace Frida.Agent {
|
||||
var connection = yield new DBusConnection (stream, ServerGuid.HOST_SESSION_SERVICE,
|
||||
AUTHENTICATION_SERVER | AUTHENTICATION_ALLOW_ANONYMOUS, null, cancellable);
|
||||
|
||||
native_cloak = new NativeCloakService ();
|
||||
connection.register_object (ObjectPath.NATIVE_CLOAK, (NativeCloak) native_cloak);
|
||||
|
||||
native_thread_registry = new NativeThreadRegistryService ();
|
||||
connection.register_object (ObjectPath.NATIVE_THREAD_REGISTRY,
|
||||
(NativeThreadRegistry) native_thread_registry);
|
||||
|
||||
AgentSessionProvider provider = yield connection.get_proxy (null, ObjectPath.AGENT_SESSION_PROVIDER,
|
||||
DO_NOT_LOAD_PROPERTIES, cancellable);
|
||||
|
||||
@@ -1481,6 +1654,16 @@ namespace Frida.Agent {
|
||||
emulated_worker = null;
|
||||
}
|
||||
|
||||
if (native_thread_registry != null) {
|
||||
native_thread_registry.close ();
|
||||
native_thread_registry = null;
|
||||
}
|
||||
|
||||
if (native_cloak != null) {
|
||||
native_cloak.close ();
|
||||
native_cloak = null;
|
||||
}
|
||||
|
||||
emulated_socket = null;
|
||||
|
||||
if (emulated_agent != null) {
|
||||
@@ -1641,6 +1824,201 @@ namespace Frida.Agent {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ANDROID && (X86 || X86_64)
|
||||
private sealed class NativeCloakService : Object, NativeCloak, Gum.Cloak.Observer {
|
||||
private MainContext main_context = MainContext.ref_thread_default ();
|
||||
private bool observing = false;
|
||||
|
||||
public uint[] activate (Cancellable? cancellable) {
|
||||
Gum.Cloak.add_observer (this);
|
||||
observing = true;
|
||||
|
||||
uint[] threads = {};
|
||||
Gum.Cloak.enumerate_threads (id => {
|
||||
threads += (uint) id;
|
||||
return true;
|
||||
});
|
||||
return threads;
|
||||
}
|
||||
|
||||
public void close () {
|
||||
if (!observing)
|
||||
return;
|
||||
|
||||
Gum.Cloak.remove_observer (this);
|
||||
observing = false;
|
||||
}
|
||||
|
||||
public void thread_added (Gum.ThreadId id) {
|
||||
schedule (() => {
|
||||
thread_cloaked ((uint) id);
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
public void thread_removed (Gum.ThreadId id) {
|
||||
schedule (() => {
|
||||
thread_uncloaked ((uint) id);
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
private void schedule (owned SourceFunc function) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback ((owned) function);
|
||||
source.attach (main_context);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class NativeThreadRegistryService : Object, NativeThreadRegistry {
|
||||
private unowned Gum.ThreadRegistry? registry;
|
||||
private ulong added_handler = 0;
|
||||
private ulong removed_handler = 0;
|
||||
private ulong renamed_handler = 0;
|
||||
|
||||
public NativeThreadInfo[] activate (Cancellable? cancellable) {
|
||||
registry = Gum.ThreadRegistry.obtain ();
|
||||
|
||||
NativeThreadInfo[] threads = {};
|
||||
|
||||
registry.lock ();
|
||||
|
||||
added_handler = registry.thread_added.connect (on_thread_added);
|
||||
removed_handler = registry.thread_removed.connect (on_thread_removed);
|
||||
renamed_handler = registry.thread_renamed.connect (on_thread_renamed);
|
||||
|
||||
registry.enumerate_threads (thread => {
|
||||
threads += NativeThreadInfo ((uint) thread.id, thread.name ?? "");
|
||||
return true;
|
||||
});
|
||||
|
||||
registry.unlock ();
|
||||
|
||||
return threads;
|
||||
}
|
||||
|
||||
public void deactivate (Cancellable? cancellable) {
|
||||
close ();
|
||||
}
|
||||
|
||||
public void close () {
|
||||
if (registry == null)
|
||||
return;
|
||||
|
||||
registry.disconnect (added_handler);
|
||||
registry.disconnect (removed_handler);
|
||||
registry.disconnect (renamed_handler);
|
||||
registry = null;
|
||||
}
|
||||
|
||||
private void on_thread_added (Gum.ThreadDetails thread) {
|
||||
thread_added (NativeThreadInfo ((uint) thread.id, thread.name ?? ""));
|
||||
}
|
||||
|
||||
private void on_thread_removed (Gum.ThreadDetails thread) {
|
||||
thread_removed ((uint) thread.id);
|
||||
}
|
||||
|
||||
private void on_thread_renamed (Gum.ThreadDetails thread, string? previous_name) {
|
||||
thread_renamed ((uint) thread.id, thread.name ?? "");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ANDROID && (ARM || ARM64)
|
||||
private sealed class NativeCloakMirror : Object {
|
||||
public NativeCloak native_cloak {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public NativeCloakMirror (NativeCloak native_cloak) {
|
||||
Object (native_cloak: native_cloak);
|
||||
}
|
||||
|
||||
public void activate () throws GLib.Error {
|
||||
native_cloak.thread_cloaked.connect (on_thread_cloaked);
|
||||
native_cloak.thread_uncloaked.connect (on_thread_uncloaked);
|
||||
|
||||
foreach (uint id in native_cloak.activate (null))
|
||||
on_thread_cloaked (id);
|
||||
}
|
||||
|
||||
private void on_thread_cloaked (uint id) {
|
||||
if (!Gum.Cloak.has_thread (id))
|
||||
Gum.Cloak.add_thread (id);
|
||||
}
|
||||
|
||||
private void on_thread_uncloaked (uint id) {
|
||||
Gum.Cloak.remove_thread (id);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class NativeThreadProvider : Object, Gum.ThreadProvider {
|
||||
public NativeThreadRegistry native_registry {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private unowned Gum.ThreadRegistry registry;
|
||||
private ulong added_handler = 0;
|
||||
private ulong removed_handler = 0;
|
||||
private ulong renamed_handler = 0;
|
||||
|
||||
public NativeThreadProvider (NativeThreadRegistry native_registry) {
|
||||
Object (native_registry: native_registry);
|
||||
}
|
||||
|
||||
public void activate (Gum.ThreadRegistry registry) {
|
||||
this.registry = registry;
|
||||
|
||||
added_handler = native_registry.thread_added.connect (add_thread);
|
||||
removed_handler = native_registry.thread_removed.connect (on_thread_removed);
|
||||
renamed_handler = native_registry.thread_renamed.connect (on_thread_renamed);
|
||||
|
||||
NativeThreadInfo[] threads;
|
||||
try {
|
||||
threads = native_registry.activate (null);
|
||||
} catch (GLib.Error e) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var thread in threads)
|
||||
add_thread (thread);
|
||||
}
|
||||
|
||||
public void deactivate () {
|
||||
native_registry.disconnect (added_handler);
|
||||
native_registry.disconnect (removed_handler);
|
||||
native_registry.disconnect (renamed_handler);
|
||||
|
||||
try {
|
||||
native_registry.deactivate (null);
|
||||
} catch (GLib.Error e) {
|
||||
}
|
||||
}
|
||||
|
||||
private void add_thread (NativeThreadInfo thread) {
|
||||
var details = Gum.ThreadDetails ();
|
||||
details.id = thread.id;
|
||||
if (thread.name != "") {
|
||||
details.name = thread.name;
|
||||
details.flags = NAME;
|
||||
}
|
||||
|
||||
registry.register (details);
|
||||
}
|
||||
|
||||
private void on_thread_removed (uint id) {
|
||||
registry.unregister (id);
|
||||
}
|
||||
|
||||
private void on_thread_renamed (uint id, string name) {
|
||||
registry.rename (id, (name != "") ? name : null);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
private sealed class LiveAgentSession : BaseAgentSession {
|
||||
public uint registration_id {
|
||||
get;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
global:
|
||||
frida_agent_main;
|
||||
frida_prospero_agent_args;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
@@ -25,6 +25,9 @@ if build_agent
|
||||
if host_os_family != 'windows'
|
||||
extra_vala_args += '--pkg=posix'
|
||||
endif
|
||||
if host_os_family == 'windows'
|
||||
extra_vala_args += '--pkg=frida-gum-windows-1.0'
|
||||
endif
|
||||
if host_os_family == 'darwin'
|
||||
extra_vala_args += '--pkg=frida-gum-darwin-1.0'
|
||||
endif
|
||||
@@ -42,6 +45,8 @@ 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
|
||||
@@ -54,6 +59,8 @@ 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,
|
||||
@@ -89,8 +96,6 @@ 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
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
#include "frida-base.h"
|
||||
|
||||
GBytes *
|
||||
frida_make_bytes_with_owner (GType t_type,
|
||||
GBoxedCopyFunc t_dup_func,
|
||||
GDestroyNotify t_destroy_func,
|
||||
void * data,
|
||||
gsize size,
|
||||
gpointer owner)
|
||||
{
|
||||
return g_bytes_new_with_free_func (data, size, t_destroy_func, owner);
|
||||
}
|
||||
@@ -1,797 +0,0 @@
|
||||
namespace Frida {
|
||||
public sealed class BufferBuilder : Object {
|
||||
public ByteOrder byte_order {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public uint pointer_size {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public size_t offset {
|
||||
get {
|
||||
return cursor;
|
||||
}
|
||||
}
|
||||
|
||||
private ByteArray buffer = new ByteArray ();
|
||||
private size_t cursor = 0;
|
||||
|
||||
private uint64 base_address = 0;
|
||||
private Gee.List<LabelRef>? label_refs;
|
||||
private Gee.Map<string, uint>? label_defs;
|
||||
|
||||
public BufferBuilder (ByteOrder byte_order = HOST, uint pointer_size = (uint) sizeof (size_t)) {
|
||||
Object (
|
||||
byte_order: byte_order,
|
||||
pointer_size: pointer_size
|
||||
);
|
||||
}
|
||||
|
||||
public unowned BufferBuilder seek (size_t offset) {
|
||||
if (buffer.len < offset) {
|
||||
size_t n = offset - buffer.len;
|
||||
Memory.set (get_pointer (offset - n, n), 0, n);
|
||||
}
|
||||
cursor = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder skip (size_t n) {
|
||||
seek (cursor + n);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder align (size_t n) {
|
||||
size_t remainder = cursor % n;
|
||||
if (remainder != 0)
|
||||
skip (n - remainder);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_pointer (uint64 val) {
|
||||
write_pointer (cursor, val);
|
||||
cursor += pointer_size;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_pointer_to_label (string name) {
|
||||
if (label_refs == null)
|
||||
label_refs = new Gee.ArrayList<LabelRef> ();
|
||||
label_refs.add (new LabelRef (name, cursor));
|
||||
return skip (pointer_size);
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_pointer_to_label_if (bool present, string name) {
|
||||
if (present)
|
||||
append_pointer_to_label (name);
|
||||
else
|
||||
append_pointer (0);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_label (string name) throws Error {
|
||||
if (label_defs == null)
|
||||
label_defs = new Gee.HashMap<string, uint> ();
|
||||
if (label_defs.has_key (name))
|
||||
throw new Error.INVALID_ARGUMENT ("Label '%s' already exists", name);
|
||||
label_defs[name] = (uint) cursor;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_size (uint64 val) {
|
||||
return append_pointer (val);
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_int8 (int8 val) {
|
||||
write_int8 (cursor, val);
|
||||
cursor += (uint) sizeof (int8);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_uint8 (uint8 val) {
|
||||
write_uint8 (cursor, val);
|
||||
cursor += (uint) sizeof (uint8);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_int16 (int16 val) {
|
||||
write_int16 (cursor, val);
|
||||
cursor += (uint) sizeof (int16);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_uint16 (uint16 val) {
|
||||
write_uint16 (cursor, val);
|
||||
cursor += (uint) sizeof (uint16);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_int32 (int32 val) {
|
||||
write_int32 (cursor, val);
|
||||
cursor += (uint) sizeof (int32);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_uint32 (uint32 val) {
|
||||
write_uint32 (cursor, val);
|
||||
cursor += (uint) sizeof (uint32);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_int64 (int64 val) {
|
||||
write_int64 (cursor, val);
|
||||
cursor += (uint) sizeof (int64);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_uint64 (uint64 val) {
|
||||
write_uint64 (cursor, val);
|
||||
cursor += (uint) sizeof (uint64);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_float (float val) {
|
||||
write_float (cursor, val);
|
||||
cursor += (uint) sizeof (float);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_double (double val) {
|
||||
write_double (cursor, val);
|
||||
cursor += (uint) sizeof (double);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_string (string val, StringTerminator terminator = NUL) {
|
||||
uint size = val.length;
|
||||
if (terminator == NUL)
|
||||
size++;
|
||||
Memory.copy (get_pointer (cursor, size), val, size);
|
||||
cursor += size;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_bytes (Bytes bytes) {
|
||||
return append_data (bytes.get_data ());
|
||||
}
|
||||
|
||||
public unowned BufferBuilder append_data (uint8[] data) {
|
||||
write_data (cursor, data);
|
||||
cursor += data.length;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_pointer (size_t offset, uint64 val) {
|
||||
if (pointer_size == 4)
|
||||
write_uint32 (offset, (uint32) val);
|
||||
else
|
||||
write_uint64 (offset, val);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_size (size_t offset, uint64 val) {
|
||||
return write_pointer (offset, val);
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_int8 (size_t offset, int8 val) {
|
||||
*((int8 *) get_pointer (offset, sizeof (int8))) = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_uint8 (size_t offset, uint8 val) {
|
||||
*get_pointer (offset, sizeof (uint8)) = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_int16 (size_t offset, int16 val) {
|
||||
int16 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((int16 *) get_pointer (offset, sizeof (int16))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_uint16 (size_t offset, uint16 val) {
|
||||
uint16 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((uint16 *) get_pointer (offset, sizeof (uint16))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_int32 (size_t offset, int32 val) {
|
||||
int32 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((int32 *) get_pointer (offset, sizeof (int32))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_uint32 (size_t offset, uint32 val) {
|
||||
uint32 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((uint32 *) get_pointer (offset, sizeof (uint32))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_int64 (size_t offset, int64 val) {
|
||||
int64 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((int64 *) get_pointer (offset, sizeof (int64))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_uint64 (size_t offset, uint64 val) {
|
||||
uint64 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((uint64 *) get_pointer (offset, sizeof (uint64))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_float (size_t offset, float val) {
|
||||
return write_uint32 (offset, *((uint32 *) &val));
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_double (size_t offset, double val) {
|
||||
return write_uint64 (offset, *((uint64 *) &val));
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_string (size_t offset, string val) {
|
||||
uint size = val.length + 1;
|
||||
Memory.copy (get_pointer (offset, size), val, size);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_bytes (size_t offset, Bytes bytes) {
|
||||
return write_data (offset, bytes.get_data ());
|
||||
}
|
||||
|
||||
public unowned BufferBuilder write_data (size_t offset, uint8[] data) {
|
||||
Memory.copy (get_pointer (offset, data.length), data, data.length);
|
||||
return this;
|
||||
}
|
||||
|
||||
private uint8 * get_pointer (size_t offset, size_t n) {
|
||||
size_t minimum_size = offset + n;
|
||||
if (buffer.len < minimum_size)
|
||||
buffer.set_size ((uint) minimum_size);
|
||||
|
||||
return (uint8 *) buffer.data + offset;
|
||||
}
|
||||
|
||||
public Bytes try_build (uint64 base_address = 0) throws Error {
|
||||
this.base_address = base_address;
|
||||
|
||||
if (label_refs != null) {
|
||||
foreach (LabelRef r in label_refs)
|
||||
write_pointer (r.offset, address_of (r.name));
|
||||
}
|
||||
|
||||
return ByteArray.free_to_bytes ((owned) buffer);
|
||||
}
|
||||
|
||||
public Bytes build (uint64 base_address = 0) {
|
||||
try {
|
||||
return try_build (base_address);
|
||||
} catch (Error e) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
public uint64 address_of (string label) throws Error {
|
||||
if (label_defs == null || !label_defs.has_key (label))
|
||||
throw new Error.INVALID_OPERATION ("Label '%s' not defined", label);
|
||||
size_t offset = label_defs[label];
|
||||
return base_address + offset;
|
||||
}
|
||||
|
||||
private class LabelRef {
|
||||
public string name;
|
||||
public size_t offset;
|
||||
|
||||
public LabelRef (string name, size_t offset) {
|
||||
this.name = name;
|
||||
this.offset = offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum StringTerminator {
|
||||
NONE,
|
||||
NUL
|
||||
}
|
||||
|
||||
public sealed class Buffer : Object {
|
||||
public Bytes bytes {
|
||||
get {
|
||||
if (_bytes == null)
|
||||
_bytes = new Bytes.static (_data);
|
||||
return _bytes;
|
||||
}
|
||||
}
|
||||
|
||||
public size_t size {
|
||||
get {
|
||||
return _data.length;
|
||||
}
|
||||
}
|
||||
|
||||
public ByteOrder byte_order {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public uint pointer_size {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private Bytes? _bytes;
|
||||
private unowned uint8[] _data;
|
||||
|
||||
public Buffer (Bytes bytes, ByteOrder byte_order = HOST, uint pointer_size = (uint) sizeof (size_t)) {
|
||||
Object (
|
||||
byte_order: byte_order,
|
||||
pointer_size: pointer_size
|
||||
);
|
||||
|
||||
reset_bytes (bytes);
|
||||
}
|
||||
|
||||
public Buffer.from_data (uint8[] data, ByteOrder byte_order = HOST, uint pointer_size = (uint) sizeof (size_t)) {
|
||||
Object (
|
||||
byte_order: byte_order,
|
||||
pointer_size: pointer_size
|
||||
);
|
||||
|
||||
reset_data (data);
|
||||
}
|
||||
|
||||
public void reset_bytes (Bytes bytes) {
|
||||
_bytes = bytes;
|
||||
_data = bytes.get_data ();
|
||||
}
|
||||
|
||||
public void reset_data (uint8[] data) {
|
||||
_bytes = null;
|
||||
_data = data;
|
||||
}
|
||||
|
||||
public unowned uint8[] peek_data () {
|
||||
return _data;
|
||||
}
|
||||
|
||||
public uint64 read_pointer (size_t offset) {
|
||||
return (pointer_size == 4)
|
||||
? read_uint32 (offset)
|
||||
: read_uint64 (offset);
|
||||
}
|
||||
|
||||
public void write_pointer (size_t offset, uint64 val) {
|
||||
if (pointer_size == 4)
|
||||
write_uint32 (offset, (uint32) val);
|
||||
else
|
||||
write_uint64 (offset, val);
|
||||
}
|
||||
|
||||
public int8 read_int8 (size_t offset) {
|
||||
return *((int8 *) get_pointer (offset, sizeof (int8)));
|
||||
}
|
||||
|
||||
public unowned Buffer write_int8 (size_t offset, int8 val) {
|
||||
*((int8 *) get_pointer (offset, sizeof (int8))) = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public uint8 read_uint8 (size_t offset) {
|
||||
return *get_pointer (offset, sizeof (uint8));
|
||||
}
|
||||
|
||||
public unowned Buffer write_uint8 (size_t offset, uint8 val) {
|
||||
*((uint8 *) get_pointer (offset, sizeof (uint8))) = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int16 read_int16 (size_t offset) {
|
||||
int16 val = *((int16 *) get_pointer (offset, sizeof (int16)));
|
||||
return (byte_order == BIG_ENDIAN)
|
||||
? int16.from_big_endian (val)
|
||||
: int16.from_little_endian (val);
|
||||
}
|
||||
|
||||
public unowned Buffer write_int16 (size_t offset, int16 val) {
|
||||
int16 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((int16 *) get_pointer (offset, sizeof (int16))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public uint16 read_uint16 (size_t offset) {
|
||||
uint16 val = *((uint16 *) get_pointer (offset, sizeof (uint16)));
|
||||
return (byte_order == BIG_ENDIAN)
|
||||
? uint16.from_big_endian (val)
|
||||
: uint16.from_little_endian (val);
|
||||
}
|
||||
|
||||
public unowned Buffer write_uint16 (size_t offset, uint16 val) {
|
||||
uint16 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((uint16 *) get_pointer (offset, sizeof (uint16))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int32 read_int32 (size_t offset) {
|
||||
int32 val = *((int32 *) get_pointer (offset, sizeof (int32)));
|
||||
return (byte_order == BIG_ENDIAN)
|
||||
? int32.from_big_endian (val)
|
||||
: int32.from_little_endian (val);
|
||||
}
|
||||
|
||||
public unowned Buffer write_int32 (size_t offset, int32 val) {
|
||||
int32 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((int32 *) get_pointer (offset, sizeof (int32))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public uint32 read_uint32 (size_t offset) {
|
||||
uint32 val = *((uint32 *) get_pointer (offset, sizeof (uint32)));
|
||||
return (byte_order == BIG_ENDIAN)
|
||||
? uint32.from_big_endian (val)
|
||||
: uint32.from_little_endian (val);
|
||||
}
|
||||
|
||||
public unowned Buffer write_uint32 (size_t offset, uint32 val) {
|
||||
uint32 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((uint32 *) get_pointer (offset, sizeof (uint32))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public int64 read_int64 (size_t offset) {
|
||||
int64 val = *((int64 *) get_pointer (offset, sizeof (int64)));
|
||||
return (byte_order == BIG_ENDIAN)
|
||||
? int64.from_big_endian (val)
|
||||
: int64.from_little_endian (val);
|
||||
}
|
||||
|
||||
public unowned Buffer write_int64 (size_t offset, int64 val) {
|
||||
int64 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((int64 *) get_pointer (offset, sizeof (int64))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public uint64 read_uint64 (size_t offset) {
|
||||
uint64 val = *((uint64 *) get_pointer (offset, sizeof (uint64)));
|
||||
return (byte_order == BIG_ENDIAN)
|
||||
? uint64.from_big_endian (val)
|
||||
: uint64.from_little_endian (val);
|
||||
}
|
||||
|
||||
public unowned Buffer write_uint64 (size_t offset, uint64 val) {
|
||||
uint64 target_val = (byte_order == BIG_ENDIAN)
|
||||
? val.to_big_endian ()
|
||||
: val.to_little_endian ();
|
||||
*((uint64 *) get_pointer (offset, sizeof (uint64))) = target_val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public float read_float (size_t offset) {
|
||||
uint32 bits = read_uint32 (offset);
|
||||
return *((float *) &bits);
|
||||
}
|
||||
|
||||
public double read_double (size_t offset) {
|
||||
uint64 bits = read_uint64 (offset);
|
||||
return *((double *) &bits);
|
||||
}
|
||||
|
||||
public unowned string read_string (size_t offset) throws Error {
|
||||
string * start = (string *) get_pointer (offset, sizeof (char));
|
||||
size_t max_length = _data.length - offset;
|
||||
string * end = memchr (start, 0, max_length);
|
||||
if (end == null)
|
||||
throw new Error.PROTOCOL ("Missing null character");
|
||||
return start;
|
||||
}
|
||||
|
||||
[CCode (cname = "memchr", cheader_filename = "string.h")]
|
||||
private extern static string * memchr (string * s, int c, size_t n);
|
||||
|
||||
public string read_fixed_string (size_t offset, size_t size) throws Error {
|
||||
string * start = (string *) get_pointer (offset, size);
|
||||
size_t max_length = size_t.min (size, _data.length - offset);
|
||||
string * end = memchr (start, 0, max_length);
|
||||
size_t n;
|
||||
if (end != null)
|
||||
n = end - start;
|
||||
else
|
||||
n = size;
|
||||
return start->substring (0, (long) n);
|
||||
}
|
||||
|
||||
public unowned Buffer write_string (size_t offset, string val) {
|
||||
uint size = val.length + 1;
|
||||
Memory.copy (get_pointer (offset, size), val, size);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Bytes read_bytes (size_t offset, size_t size) {
|
||||
if (_bytes == null)
|
||||
return make_bytes_with_owner ((uint8 *) _data + offset, _data.length - size, this);
|
||||
return _bytes[offset:offset + size];
|
||||
}
|
||||
|
||||
public unowned uint8[] read_data (size_t offset, size_t size) {
|
||||
unowned uint8 * ptr = get_pointer (offset, size);
|
||||
return ((uint8[]) ptr)[:size];
|
||||
}
|
||||
|
||||
public unowned Buffer write_bytes (size_t offset, Bytes bytes) {
|
||||
size_t size = bytes.get_size ();
|
||||
Memory.copy (get_pointer (offset, size), bytes.get_data (), size);
|
||||
return this;
|
||||
}
|
||||
|
||||
private uint8 * get_pointer (size_t offset, size_t n) {
|
||||
size_t minimum_size = offset + n;
|
||||
assert (_data.length >= minimum_size);
|
||||
return (uint8 *) _data + offset;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class BufferReader {
|
||||
public Buffer buffer {
|
||||
get;
|
||||
}
|
||||
|
||||
public size_t offset {
|
||||
get;
|
||||
}
|
||||
|
||||
public size_t available {
|
||||
get {
|
||||
return _size - _offset;
|
||||
}
|
||||
}
|
||||
|
||||
private size_t _size;
|
||||
|
||||
public BufferReader (Buffer buffer) {
|
||||
reset (buffer);
|
||||
}
|
||||
|
||||
public void reset (Buffer buffer) {
|
||||
_buffer = buffer;
|
||||
_size = buffer.size;
|
||||
_offset = 0;
|
||||
}
|
||||
|
||||
public void reset_at (Buffer buffer, size_t offset) throws Error {
|
||||
_buffer = buffer;
|
||||
_size = buffer.size;
|
||||
seek (offset);
|
||||
}
|
||||
|
||||
public unowned BufferReader seek (size_t offset) throws Error {
|
||||
if (offset > _size)
|
||||
throw new Error.PROTOCOL ("Malformed buffer: truncated");
|
||||
_offset = offset;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferReader skip (size_t n) throws Error {
|
||||
check_available (n);
|
||||
_offset += n;
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned BufferReader align (size_t alignment) throws Error {
|
||||
size_t mask = alignment - 1;
|
||||
size_t rem = _offset & mask;
|
||||
if (rem != 0)
|
||||
skip (alignment - rem);
|
||||
return this;
|
||||
}
|
||||
|
||||
public uint64 peek_pointer () throws Error {
|
||||
var pointer_size = _buffer.pointer_size;
|
||||
check_available (pointer_size);
|
||||
return _buffer.read_pointer (_offset);
|
||||
}
|
||||
|
||||
public uint64 read_pointer () throws Error {
|
||||
var pointer_size = _buffer.pointer_size;
|
||||
check_available (pointer_size);
|
||||
var ptr = _buffer.read_pointer (_offset);
|
||||
_offset += pointer_size;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
public int8 peek_int8 () throws Error {
|
||||
check_available (sizeof (int8));
|
||||
return _buffer.read_int8 (_offset);
|
||||
}
|
||||
|
||||
public int8 read_int8 () throws Error {
|
||||
check_available (sizeof (int8));
|
||||
var val = _buffer.read_int8 (_offset);
|
||||
_offset += sizeof (int8);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint8 peek_uint8 () throws Error {
|
||||
check_available (sizeof (uint8));
|
||||
return _buffer.read_uint8 (_offset);
|
||||
}
|
||||
|
||||
public uint8 read_uint8 () throws Error {
|
||||
check_available (sizeof (uint8));
|
||||
var val = _buffer.read_uint8 (_offset);
|
||||
_offset += sizeof (uint8);
|
||||
return val;
|
||||
}
|
||||
|
||||
public int16 peek_int16 () throws Error {
|
||||
check_available (sizeof (int16));
|
||||
return _buffer.read_int16 (_offset);
|
||||
}
|
||||
|
||||
public int16 read_int16 () throws Error {
|
||||
check_available (sizeof (int16));
|
||||
var val = _buffer.read_int16 (_offset);
|
||||
_offset += sizeof (int16);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint16 peek_uint16 () throws Error {
|
||||
check_available (sizeof (uint16));
|
||||
return _buffer.read_uint16 (_offset);
|
||||
}
|
||||
|
||||
public uint16 read_uint16 () throws Error {
|
||||
check_available (sizeof (uint16));
|
||||
var val = _buffer.read_uint16 (_offset);
|
||||
_offset += sizeof (uint16);
|
||||
return val;
|
||||
}
|
||||
|
||||
public int32 peek_int32 () throws Error {
|
||||
check_available (sizeof (int32));
|
||||
return _buffer.read_int32 (_offset);
|
||||
}
|
||||
|
||||
public int32 read_int32 () throws Error {
|
||||
check_available (sizeof (int32));
|
||||
var val = _buffer.read_int32 (_offset);
|
||||
_offset += sizeof (int32);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint32 peek_uint32 () throws Error {
|
||||
check_available (sizeof (uint32));
|
||||
return _buffer.read_uint32 (_offset);
|
||||
}
|
||||
|
||||
public uint32 read_uint32 () throws Error {
|
||||
check_available (sizeof (uint32));
|
||||
var val = _buffer.read_uint32 (_offset);
|
||||
_offset += sizeof (uint32);
|
||||
return val;
|
||||
}
|
||||
|
||||
public int64 peek_int64 () throws Error {
|
||||
check_available (sizeof (int64));
|
||||
return _buffer.read_int64 (_offset);
|
||||
}
|
||||
|
||||
public int64 read_int64 () throws Error {
|
||||
check_available (sizeof (int64));
|
||||
var val = _buffer.read_int64 (_offset);
|
||||
_offset += sizeof (int64);
|
||||
return val;
|
||||
}
|
||||
|
||||
public uint64 peek_uint64 () throws Error {
|
||||
check_available (sizeof (uint64));
|
||||
return _buffer.read_uint64 (_offset);
|
||||
}
|
||||
|
||||
public uint64 read_uint64 () throws Error {
|
||||
check_available (sizeof (uint64));
|
||||
var val = _buffer.read_uint64 (_offset);
|
||||
_offset += sizeof (uint64);
|
||||
return val;
|
||||
}
|
||||
|
||||
public float peek_float () throws Error {
|
||||
check_available (sizeof (float));
|
||||
return _buffer.read_float (_offset);
|
||||
}
|
||||
|
||||
public float read_float () throws Error {
|
||||
check_available (sizeof (float));
|
||||
var val = _buffer.read_float (_offset);
|
||||
_offset += sizeof (float);
|
||||
return val;
|
||||
}
|
||||
|
||||
public double peek_double () throws Error {
|
||||
check_available (sizeof (double));
|
||||
return _buffer.read_double (_offset);
|
||||
}
|
||||
|
||||
public double read_double () throws Error {
|
||||
check_available (sizeof (double));
|
||||
var val = _buffer.read_double (_offset);
|
||||
_offset += sizeof (double);
|
||||
return val;
|
||||
}
|
||||
|
||||
public unowned string peek_string () throws Error {
|
||||
check_available (1);
|
||||
return _buffer.read_string (_offset);
|
||||
}
|
||||
|
||||
public unowned string read_string () throws Error {
|
||||
check_available (1);
|
||||
unowned string val = _buffer.read_string (_offset);
|
||||
_offset += val.length + 1;
|
||||
return val;
|
||||
}
|
||||
|
||||
public string peek_fixed_string (size_t size) throws Error {
|
||||
check_available (size);
|
||||
return _buffer.read_fixed_string (_offset, size);
|
||||
}
|
||||
|
||||
public string read_fixed_string (size_t size) throws Error {
|
||||
check_available (size);
|
||||
var val = _buffer.read_fixed_string (_offset, size);
|
||||
_offset += size;
|
||||
return val;
|
||||
}
|
||||
|
||||
public unowned uint8[] peek_data (size_t size) throws Error {
|
||||
check_available (size);
|
||||
return _buffer.read_data (_offset, size);
|
||||
}
|
||||
|
||||
public unowned uint8[] read_data (size_t size) throws Error {
|
||||
check_available (size);
|
||||
unowned uint8[] slice = _buffer.read_data (_offset, size);
|
||||
_offset += size;
|
||||
return slice;
|
||||
}
|
||||
|
||||
public Bytes read_bytes (size_t size) throws Error {
|
||||
check_available (size);
|
||||
var val = _buffer.read_bytes (_offset, size);
|
||||
_offset += size;
|
||||
return val;
|
||||
}
|
||||
|
||||
public unowned uint8[] read_remaining_data () throws Error {
|
||||
return read_data (available);
|
||||
}
|
||||
|
||||
private void check_available (size_t n) throws Error {
|
||||
if (available < n)
|
||||
throw new Error.PROTOCOL ("Malformed buffer: truncated");
|
||||
}
|
||||
}
|
||||
|
||||
public extern Bytes make_bytes_with_owner<T> (void * data, size_t size, owned T? owner = null);
|
||||
}
|
||||
+14
-2
@@ -1,9 +1,13 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public sealed class ChannelRegistry : Object {
|
||||
public signal void channel_closed (ChannelId id);
|
||||
|
||||
private Gee.Map<ChannelId?, weak IOStream> channels =
|
||||
new Gee.HashMap<ChannelId?, weak IOStream> (ChannelId.hash, ChannelId.equal);
|
||||
private Gee.Map<ChannelId?, IOStream> unlinked_channels =
|
||||
new Gee.HashMap<ChannelId?, IOStream> (ChannelId.hash, ChannelId.equal);
|
||||
|
||||
~ChannelRegistry () {
|
||||
clear ();
|
||||
@@ -16,25 +20,33 @@ namespace Frida {
|
||||
channel_stream.abandon ();
|
||||
}
|
||||
channels.clear ();
|
||||
unlinked_channels.clear ();
|
||||
}
|
||||
|
||||
public void register (ChannelId id, IOStream stream) {
|
||||
stream.set_data ("channel-registry-entry", new Entry (this, id));
|
||||
lock (channels)
|
||||
lock (channels) {
|
||||
channels[id] = stream;
|
||||
unlinked_channels[id] = stream;
|
||||
}
|
||||
}
|
||||
|
||||
public IOStream link (ChannelId id) throws Error {
|
||||
IOStream? stream;
|
||||
lock (channels)
|
||||
IOStream? unlinked_stream = null;
|
||||
lock (channels) {
|
||||
stream = channels[id];
|
||||
unlinked_channels.unset (id, out unlinked_stream);
|
||||
}
|
||||
if (stream == null)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid channel ID");
|
||||
return stream;
|
||||
}
|
||||
|
||||
public void unlink (ChannelId id) {
|
||||
IOStream? unlinked_stream = null;
|
||||
lock (channels) {
|
||||
unlinked_channels.unset (id, out unlinked_stream);
|
||||
if (!channels.unset (id))
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
// We need to tease out GDBus' private MainContext as libnice needs to know the MainContext up front :(
|
||||
public async MainContext get_dbus_context () {
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
#include "frida-darwin.h"
|
||||
|
||||
gpointer
|
||||
_frida_dispatch_retain (gpointer object)
|
||||
{
|
||||
dispatch_retain (object);
|
||||
return object;
|
||||
}
|
||||
|
||||
void
|
||||
_frida_xpc_connection_set_event_handler (xpc_connection_t connection, FridaXpcHandler handler, gpointer user_data)
|
||||
{
|
||||
xpc_connection_set_event_handler (connection, ^(xpc_object_t object)
|
||||
{
|
||||
handler (object, user_data);
|
||||
});
|
||||
}
|
||||
|
||||
void
|
||||
_frida_xpc_connection_send_message_with_reply (xpc_connection_t connection, xpc_object_t message, dispatch_queue_t replyq,
|
||||
FridaXpcHandler handler, gpointer user_data, GDestroyNotify notify)
|
||||
{
|
||||
xpc_connection_send_message_with_reply (connection, message, replyq, ^(xpc_object_t object)
|
||||
{
|
||||
handler (object, user_data);
|
||||
if (notify != NULL)
|
||||
notify (user_data);
|
||||
});
|
||||
}
|
||||
|
||||
gchar *
|
||||
_frida_xpc_object_to_string (xpc_object_t object)
|
||||
{
|
||||
gchar * result;
|
||||
char * str;
|
||||
|
||||
str = xpc_copy_description (object);
|
||||
result = g_strdup (str);
|
||||
free (str);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
gboolean
|
||||
_frida_xpc_dictionary_apply (xpc_object_t dict, FridaXpcDictionaryApplier applier, gpointer user_data)
|
||||
{
|
||||
return xpc_dictionary_apply (dict, ^bool (const char * key, xpc_object_t val)
|
||||
{
|
||||
return applier (key, val, user_data);
|
||||
});
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
#ifndef __FRIDA_DARWIN_H__
|
||||
#define __FRIDA_DARWIN_H__
|
||||
|
||||
#ifdef HAVE_MACOS
|
||||
|
||||
#include <glib.h>
|
||||
#include <xpc/xpc.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef void (* FridaXpcHandler) (xpc_object_t object, gpointer user_data);
|
||||
typedef gboolean (* FridaXpcDictionaryApplier) (const gchar * key, xpc_object_t val, gpointer user_data);
|
||||
|
||||
gpointer _frida_dispatch_retain (gpointer object);
|
||||
|
||||
void _frida_xpc_connection_set_event_handler (xpc_connection_t connection, FridaXpcHandler handler, gpointer user_data);
|
||||
void _frida_xpc_connection_send_message_with_reply (xpc_connection_t connection, xpc_object_t message, dispatch_queue_t replyq,
|
||||
FridaXpcHandler handler, gpointer user_data, GDestroyNotify notify);
|
||||
gchar * _frida_xpc_object_to_string (xpc_object_t object);
|
||||
gboolean _frida_xpc_dictionary_apply (xpc_object_t dict, FridaXpcDictionaryApplier applier, gpointer user_data);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,42 @@
|
||||
#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
|
||||
@@ -1,114 +0,0 @@
|
||||
namespace Frida.FS {
|
||||
public void mkdirp (File dir, Cancellable? cancellable = null) throws Error {
|
||||
try {
|
||||
dir.make_directory_with_parents (cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (!(e is IOError.EXISTS))
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public void rmtree (File dir, Cancellable? cancellable = null) throws Error {
|
||||
rmtree_worker (dir, 0, cancellable);
|
||||
}
|
||||
|
||||
private void rmtree_worker (File dir, uint depth, Cancellable? cancellable = null) throws Error {
|
||||
FileEnumerator enumerator;
|
||||
try {
|
||||
enumerator = dir.enumerate_children (FileAttribute.STANDARD_NAME, NOFOLLOW_SYMLINKS, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (depth == 0 && e is IOError.NOT_FOUND)
|
||||
return;
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
|
||||
try {
|
||||
FileInfo? info;
|
||||
File? child;
|
||||
while (enumerator.iterate (out info, out child, cancellable) && info != null) {
|
||||
if (info == null)
|
||||
continue;
|
||||
if (info.get_file_type () == DIRECTORY)
|
||||
rmtree (child, cancellable);
|
||||
else
|
||||
child.delete (cancellable);
|
||||
}
|
||||
|
||||
dir.delete (cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (!(e is IOError.EXISTS))
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async void rmtree_async (File dir, Cancellable? cancellable = null) throws Error, IOError {
|
||||
yield rmtree_async_worker (dir, 0, cancellable);
|
||||
}
|
||||
|
||||
private async void rmtree_async_worker (File dir, uint depth, Cancellable? cancellable = null) throws Error, IOError {
|
||||
int io_priority = Priority.DEFAULT;
|
||||
|
||||
FileEnumerator enumerator;
|
||||
try {
|
||||
enumerator = yield dir.enumerate_children_async (FileAttribute.STANDARD_NAME, NOFOLLOW_SYMLINKS, io_priority,
|
||||
cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
if (depth == 0 && e is IOError.NOT_FOUND)
|
||||
return;
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
var batch = yield enumerator.next_files_async (1000, io_priority, cancellable);
|
||||
if (batch.is_empty ())
|
||||
break;
|
||||
foreach (FileInfo info in batch) {
|
||||
File child = dir.get_child (info.get_name ());
|
||||
if (info.get_file_type () == DIRECTORY)
|
||||
yield rmtree_async_worker (child, depth + 1, cancellable);
|
||||
else
|
||||
yield child.delete_async (io_priority, cancellable);
|
||||
}
|
||||
}
|
||||
|
||||
yield dir.delete_async (io_priority, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async string read_all_text (File f, Cancellable? cancellable) throws Error, IOError {
|
||||
Bytes b = yield read_all_bytes (f, cancellable);
|
||||
string text = (string) b.get_data ();
|
||||
if (!text.validate ())
|
||||
throw new Error.PROTOCOL ("Unable to read '%s': invalid UTF-8", f.get_parse_name ());
|
||||
return text;
|
||||
}
|
||||
|
||||
public async void write_all_text (File f, string t, Cancellable? cancellable) throws Error, IOError {
|
||||
yield write_all_bytes (f, new Bytes (t.data), cancellable);
|
||||
}
|
||||
|
||||
public async Bytes read_all_bytes (File f, Cancellable? cancellable) throws Error, IOError {
|
||||
try {
|
||||
return yield f.load_bytes_async (cancellable, null);
|
||||
} catch (GLib.Error e) {
|
||||
if (e is IOError.CANCELLED)
|
||||
throw (IOError) e;
|
||||
if (e is IOError.NOT_FOUND)
|
||||
throw new Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async void write_all_bytes (File f, Bytes b, Cancellable? cancellable) throws Error, IOError {
|
||||
try {
|
||||
yield f.replace_contents_bytes_async (b, null, false, FileCreateFlags.NONE, cancellable, null);
|
||||
} catch (GLib.Error e) {
|
||||
if (e is IOError.CANCELLED)
|
||||
throw (IOError) e;
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,6 +123,15 @@ namespace Frida {
|
||||
return fd;
|
||||
}
|
||||
|
||||
public static FileDescriptor with_size (string name, size_t size) {
|
||||
assert (is_supported ());
|
||||
|
||||
var fd = new FileDescriptor (memfd_create (name, 0));
|
||||
int res = Posix.ftruncate (fd.handle, (Posix.off_t) size);
|
||||
assert (res == 0);
|
||||
return fd;
|
||||
}
|
||||
|
||||
private int memfd_create (string name, uint flags) {
|
||||
return Linux.syscall (LinuxSyscall.MEMFD_CREATE, name, flags);
|
||||
}
|
||||
|
||||
+4
-18
@@ -1,21 +1,16 @@
|
||||
base_sources = [
|
||||
'asset-location.vala',
|
||||
'promise.vala',
|
||||
'session.vala',
|
||||
'session-glue.c',
|
||||
'channel.vala',
|
||||
'service.vala',
|
||||
'stream.vala',
|
||||
'rpc.vala',
|
||||
'p2p.vala',
|
||||
'p2p-glue.c',
|
||||
'dbus.vala',
|
||||
'promise.vala',
|
||||
'fs.vala',
|
||||
'socket.vala',
|
||||
'socket-glue.c',
|
||||
'buffer.vala',
|
||||
'buffer-glue.c',
|
||||
'value.vala',
|
||||
'xnu-syscalls' / 'xnu-syscalls.vala',
|
||||
'xnu-syscalls' / 'xnu-syscalls-glue.c',
|
||||
]
|
||||
@@ -23,19 +18,12 @@ base_sources = [
|
||||
extra_vala_args = []
|
||||
|
||||
if host_os == 'windows'
|
||||
base_sources += 'windows.vala'
|
||||
base_sources += ['windows.vala', 'windows-glue.c']
|
||||
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'
|
||||
@@ -94,8 +82,6 @@ endif
|
||||
if host_os_family == 'darwin'
|
||||
base_vala_args += [
|
||||
'--pkg=corefoundation',
|
||||
'--pkg=darwin-gcd',
|
||||
'--pkg=darwin-xpc',
|
||||
]
|
||||
endif
|
||||
if host_os_family == 'linux'
|
||||
@@ -114,7 +100,7 @@ base = static_library('frida-base-' + api_version, base_sources,
|
||||
vala_header: 'frida-base.h',
|
||||
vala_gir: base_gir_name,
|
||||
vala_vapi: f'frida-base-@[email protected]',
|
||||
dependencies: [glib_dep, gobject_dep, gio_dep, libsoup_dep, nice_dep, usrsctp_dep, gee_dep, json_glib_dep, gum_dep, openssl_dep] + extra_deps,
|
||||
dependencies: [glib_dep, gobject_dep, gio_dep, libsoup_dep, nice_dep, usrsctp_dep, gee_dep, json_glib_dep, gum_dep, openssl_dep, efni_dep] + extra_deps,
|
||||
install: have_in_tree_payloads,
|
||||
install_dir: [true, header_install_dir, true, false],
|
||||
)
|
||||
@@ -122,7 +108,7 @@ base = static_library('frida-base-' + api_version, base_sources,
|
||||
base_dep = declare_dependency(
|
||||
include_directories: include_directories('.'),
|
||||
link_with: base,
|
||||
dependencies: [nice_dep, json_glib_dep, gum_dep] + extra_deps,
|
||||
dependencies: [nice_dep, json_glib_dep, gum_dep, efni_dep] + extra_deps,
|
||||
)
|
||||
|
||||
base_build_dir = meson.current_build_dir()
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
#if HAVE_NICE
|
||||
namespace Frida {
|
||||
namespace PeerConnection {
|
||||
|
||||
@@ -220,4 +220,128 @@ 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);
|
||||
}
|
||||
}
|
||||
+58
-124
@@ -1,19 +1,18 @@
|
||||
namespace Frida {
|
||||
public sealed class Promise<T> {
|
||||
private Impl<T> impl;
|
||||
private Efni.Promise<T> impl;
|
||||
private Future<T>? _future;
|
||||
|
||||
public Future<T> future {
|
||||
get {
|
||||
return impl;
|
||||
if (_future == null)
|
||||
_future = new FutureImpl<T> (impl.future);
|
||||
return _future;
|
||||
}
|
||||
}
|
||||
|
||||
public Promise () {
|
||||
impl = new Impl<T> ();
|
||||
}
|
||||
|
||||
~Promise () {
|
||||
impl.abandon ();
|
||||
impl = new Efni.Promise<T> ();
|
||||
}
|
||||
|
||||
public void resolve (T result) {
|
||||
@@ -24,149 +23,84 @@ namespace Frida {
|
||||
impl.reject (error);
|
||||
}
|
||||
|
||||
private class Impl<T> : Object, Future<T> {
|
||||
private class FutureImpl<T> : Object, Future<T> {
|
||||
private Efni.Future<T> inner;
|
||||
|
||||
public bool ready {
|
||||
get {
|
||||
return _ready;
|
||||
return inner.ready;
|
||||
}
|
||||
}
|
||||
private bool _ready = false;
|
||||
|
||||
public T? value {
|
||||
get {
|
||||
return _value;
|
||||
return inner.value;
|
||||
}
|
||||
}
|
||||
private T? _value;
|
||||
|
||||
public GLib.Error? error {
|
||||
get {
|
||||
return _error;
|
||||
return inner.error;
|
||||
}
|
||||
}
|
||||
private GLib.Error? _error;
|
||||
|
||||
private Gee.ArrayQueue<CompletionFuncEntry>? on_complete;
|
||||
private Gee.ArrayQueue<ThenHandlerEntry<T>>? on_then;
|
||||
public FutureImpl (Efni.Future<T> inner) {
|
||||
this.inner = inner;
|
||||
}
|
||||
|
||||
public async T wait_async (Cancellable? cancellable) throws Frida.Error, IOError {
|
||||
if (_ready)
|
||||
return get_result ();
|
||||
public async T wait_async (Cancellable? cancellable) throws Error, IOError {
|
||||
if (!inner.ready) {
|
||||
bool resumed = false;
|
||||
|
||||
var entry = new CompletionFuncEntry (wait_async.callback);
|
||||
if (on_complete == null)
|
||||
on_complete = new Gee.ArrayQueue<CompletionFuncEntry> ();
|
||||
on_complete.offer (entry);
|
||||
var cancel_source = new CancellableSource (cancellable);
|
||||
cancel_source.set_callback (() => {
|
||||
if (!resumed) {
|
||||
resumed = true;
|
||||
wait_async.callback ();
|
||||
}
|
||||
return Source.REMOVE;
|
||||
});
|
||||
cancel_source.attach (MainContext.get_thread_default ());
|
||||
|
||||
var cancel_source = new CancellableSource (cancellable);
|
||||
cancel_source.set_callback (() => {
|
||||
on_complete.remove (entry);
|
||||
wait_async.callback ();
|
||||
return false;
|
||||
});
|
||||
cancel_source.attach (MainContext.get_thread_default ());
|
||||
inner.then (f => {
|
||||
if (!resumed) {
|
||||
resumed = true;
|
||||
wait_async.callback ();
|
||||
}
|
||||
});
|
||||
|
||||
yield;
|
||||
yield;
|
||||
|
||||
cancel_source.destroy ();
|
||||
cancel_source.destroy ();
|
||||
|
||||
cancellable.set_error_if_cancelled ();
|
||||
cancellable.set_error_if_cancelled ();
|
||||
}
|
||||
|
||||
return get_result ();
|
||||
}
|
||||
|
||||
public void then (owned FutureCompletionHandler<T> handler) {
|
||||
if (_ready) {
|
||||
schedule (() => {
|
||||
handler (this);
|
||||
return Source.REMOVE;
|
||||
});
|
||||
return;
|
||||
inner.then (f => {
|
||||
handler (this);
|
||||
});
|
||||
}
|
||||
|
||||
public T get_result () throws Error, IOError {
|
||||
GLib.Error? e = inner.error;
|
||||
if (e != null) {
|
||||
if (e is Error)
|
||||
throw (Error) e;
|
||||
|
||||
if (e is Efni.Error)
|
||||
throw error_from_efni ((Efni.Error) e);
|
||||
|
||||
if (e is IOError.CANCELLED)
|
||||
throw (IOError) e;
|
||||
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
|
||||
if (on_then == null)
|
||||
on_then = new Gee.ArrayQueue<ThenHandlerEntry<T>> ();
|
||||
on_then.offer (new ThenHandlerEntry<T> ((owned) handler));
|
||||
}
|
||||
|
||||
public T get_result () throws Frida.Error, IOError {
|
||||
if (error != null) {
|
||||
if (error is Frida.Error)
|
||||
throw (Frida.Error) error;
|
||||
|
||||
if (error is IOError.CANCELLED)
|
||||
throw (IOError) error;
|
||||
|
||||
throw new Frida.Error.TRANSPORT ("%s", error.message);
|
||||
}
|
||||
|
||||
return _value;
|
||||
}
|
||||
|
||||
internal void resolve (T value) {
|
||||
assert (!_ready);
|
||||
|
||||
_value = value;
|
||||
transition_to_ready ();
|
||||
}
|
||||
|
||||
internal void reject (GLib.Error error) {
|
||||
assert (!_ready);
|
||||
|
||||
_error = error;
|
||||
transition_to_ready ();
|
||||
}
|
||||
|
||||
internal void abandon () {
|
||||
if (!_ready) {
|
||||
reject (new Frida.Error.INVALID_OPERATION ("Promise abandoned"));
|
||||
}
|
||||
}
|
||||
|
||||
internal void transition_to_ready () {
|
||||
_ready = true;
|
||||
|
||||
if (on_complete != null || on_then != null) {
|
||||
schedule (() => {
|
||||
if (on_complete != null) {
|
||||
CompletionFuncEntry? entry;
|
||||
while ((entry = on_complete.poll ()) != null)
|
||||
entry.func ();
|
||||
on_complete = null;
|
||||
}
|
||||
|
||||
if (on_then != null) {
|
||||
ThenHandlerEntry? entry;
|
||||
while ((entry = on_then.poll ()) != null)
|
||||
entry.handler (this);
|
||||
on_then = null;
|
||||
}
|
||||
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void schedule (owned SourceFunc function) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback ((owned) function);
|
||||
source.attach (MainContext.get_thread_default ());
|
||||
}
|
||||
}
|
||||
|
||||
private class CompletionFuncEntry {
|
||||
public SourceFunc func;
|
||||
|
||||
public CompletionFuncEntry (owned SourceFunc func) {
|
||||
this.func = (owned) func;
|
||||
}
|
||||
}
|
||||
|
||||
private class ThenHandlerEntry<T> {
|
||||
public FutureCompletionHandler<T> handler;
|
||||
|
||||
public ThenHandlerEntry (owned FutureCompletionHandler<T> handler) {
|
||||
this.handler = (owned) handler;
|
||||
return inner.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,9 +109,9 @@ namespace Frida {
|
||||
public abstract bool ready { get; }
|
||||
public abstract T? value { get; }
|
||||
public abstract GLib.Error? error { get; }
|
||||
public abstract async T wait_async (Cancellable? cancellable) throws Frida.Error, IOError;
|
||||
public abstract async T wait_async (Cancellable? cancellable) throws Error, IOError;
|
||||
public abstract void then (owned FutureCompletionHandler<T> handler);
|
||||
public abstract T get_result () throws Frida.Error, IOError;
|
||||
public abstract T get_result () throws Error, IOError;
|
||||
}
|
||||
|
||||
public delegate void FutureCompletionHandler<T> (Future<T> future);
|
||||
|
||||
+99
-50
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
[DBus (name = "re.frida.HostSession17")]
|
||||
public interface HostSession : Object {
|
||||
@@ -124,8 +126,43 @@ namespace Frida {
|
||||
|
||||
public abstract async void acknowledge_spawn (HostChildInfo info, SpawnStartState start_state,
|
||||
Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
public abstract uint64 allocate_memory (uint pid, uint64 near_address, uint64 max_distance, uint64 size,
|
||||
uint64 alignment, Gum.PageProtection prot, Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract void protect_memory (uint pid, uint64 address, uint64 size, Gum.PageProtection prot,
|
||||
Cancellable? cancellable) throws GLib.Error;
|
||||
}
|
||||
|
||||
#if ANDROID
|
||||
[DBus (name = "re.frida.NativeThreadRegistry17")]
|
||||
public interface NativeThreadRegistry : Object {
|
||||
public abstract NativeThreadInfo[] activate (Cancellable? cancellable) throws GLib.Error;
|
||||
public abstract void deactivate (Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
public signal void thread_added (NativeThreadInfo thread);
|
||||
public signal void thread_removed (uint id);
|
||||
public signal void thread_renamed (uint id, string name);
|
||||
}
|
||||
|
||||
[DBus (name = "re.frida.NativeCloak17")]
|
||||
public interface NativeCloak : Object {
|
||||
public abstract uint[] activate (Cancellable? cancellable) throws GLib.Error;
|
||||
|
||||
public signal void thread_cloaked (uint id);
|
||||
public signal void thread_uncloaked (uint id);
|
||||
}
|
||||
|
||||
public struct NativeThreadInfo {
|
||||
public uint id;
|
||||
public string name;
|
||||
|
||||
public NativeThreadInfo (uint id, string name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
[DBus (name = "re.frida.AgentMessageSink17")]
|
||||
public interface AgentMessageSink : Object {
|
||||
public abstract async void post_messages (AgentMessage[] messages, uint batch_id,
|
||||
@@ -1133,6 +1170,15 @@ namespace Frida {
|
||||
}
|
||||
}
|
||||
|
||||
#if WINDOWS
|
||||
public struct WindowsInjectorState {
|
||||
public Gum.MemoryRange mapped_range;
|
||||
public Gum.MemoryRange worker_range;
|
||||
public Gum.MemoryRange dbghelp_range;
|
||||
public Gum.MemoryRange symsrv_range;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if DARWIN
|
||||
public struct DarwinInjectorState {
|
||||
public Gum.MemoryRange? mapped_range;
|
||||
@@ -1143,6 +1189,8 @@ namespace Frida {
|
||||
public struct LinuxInjectorState {
|
||||
public int frida_ctrlfd;
|
||||
public int agent_ctrlfd;
|
||||
public uint64 mapped_base;
|
||||
public size_t mapped_size;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1152,6 +1200,14 @@ 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}.
|
||||
*/
|
||||
@@ -1273,6 +1329,9 @@ namespace Frida {
|
||||
if (e is Frida.Error)
|
||||
throw (Frida.Error) e;
|
||||
|
||||
if (e is Efni.Error)
|
||||
throw error_from_efni ((Efni.Error) e);
|
||||
|
||||
if (e is IOError.CANCELLED)
|
||||
throw (IOError) e;
|
||||
|
||||
@@ -1286,6 +1345,9 @@ namespace Frida {
|
||||
if (e is Frida.Error)
|
||||
throw (Frida.Error) e;
|
||||
|
||||
if (e is Efni.Error)
|
||||
throw error_from_efni ((Efni.Error) e);
|
||||
|
||||
if (e is IOError.CANCELLED)
|
||||
throw (IOError) e;
|
||||
|
||||
@@ -1298,6 +1360,41 @@ namespace Frida {
|
||||
throw new Frida.Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
|
||||
namespace Marshal {
|
||||
public T enum_from_nick<T> (string nick) throws Error {
|
||||
try {
|
||||
return Efni.Marshal.enum_from_nick<T> (nick);
|
||||
} catch (Efni.Error e) {
|
||||
throw error_from_efni (e);
|
||||
}
|
||||
}
|
||||
|
||||
public string enum_to_nick<T> (int val) {
|
||||
return Efni.Marshal.enum_to_nick<T> (val);
|
||||
}
|
||||
}
|
||||
|
||||
public static Frida.Error error_from_efni (Efni.Error e) {
|
||||
unowned string m = e.message;
|
||||
if (e is Efni.Error.SERVER_NOT_RUNNING)
|
||||
return new Frida.Error.SERVER_NOT_RUNNING ("%s", m);
|
||||
if (e is Efni.Error.INVALID_ARGUMENT)
|
||||
return new Frida.Error.INVALID_ARGUMENT ("%s", m);
|
||||
if (e is Efni.Error.INVALID_OPERATION)
|
||||
return new Frida.Error.INVALID_OPERATION ("%s", m);
|
||||
if (e is Efni.Error.PERMISSION_DENIED)
|
||||
return new Frida.Error.PERMISSION_DENIED ("%s", m);
|
||||
if (e is Efni.Error.ADDRESS_IN_USE)
|
||||
return new Frida.Error.ADDRESS_IN_USE ("%s", m);
|
||||
if (e is Efni.Error.TIMED_OUT)
|
||||
return new Frida.Error.TIMED_OUT ("%s", m);
|
||||
if (e is Efni.Error.NOT_SUPPORTED)
|
||||
return new Frida.Error.NOT_SUPPORTED ("%s", m);
|
||||
if (e is Efni.Error.PROTOCOL)
|
||||
return new Frida.Error.PROTOCOL ("%s", m);
|
||||
return new Frida.Error.TRANSPORT ("%s", m);
|
||||
}
|
||||
|
||||
public struct HostApplicationInfo {
|
||||
public string identifier;
|
||||
public string name;
|
||||
@@ -2735,6 +2832,8 @@ namespace Frida {
|
||||
public const string AGENT_SESSION_PROVIDER = "/re/frida/AgentSessionProvider";
|
||||
public const string AGENT_SESSION = "/re/frida/AgentSession";
|
||||
public const string AGENT_CONTROLLER = "/re/frida/AgentController";
|
||||
public const string NATIVE_THREAD_REGISTRY = "/re/frida/NativeThreadRegistry";
|
||||
public const string NATIVE_CLOAK = "/re/frida/NativeCloak";
|
||||
public const string AGENT_MESSAGE_SINK = "/re/frida/AgentMessageSink";
|
||||
public const string GADGET_SESSION = "/re/frida/GadgetSession";
|
||||
public const string CHANNEL = "/re/frida/Channel";
|
||||
@@ -2760,54 +2859,4 @@ namespace Frida {
|
||||
return SERVICE + "/" + id.handle;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Marshal {
|
||||
public static T enum_from_nick<T> (string nick) throws Error {
|
||||
var klass = (EnumClass) typeof (T).class_ref ();
|
||||
var v = klass.get_value_by_nick (nick);
|
||||
if (v == null)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid %s", klass.get_type ().name ());
|
||||
return (T) v.value;
|
||||
}
|
||||
|
||||
public static string enum_to_nick<T> (int val) {
|
||||
var klass = (EnumClass) typeof (T).class_ref ();
|
||||
unowned EnumValue? v = klass.get_value (val);
|
||||
if (v == null)
|
||||
return "unknown-0x%x".printf (val);
|
||||
return v.value_nick;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Numeric {
|
||||
public uint int64_hash (int64? val) {
|
||||
uint64 v = (uint64) val.abs ();
|
||||
return (uint) ((v >> 32) ^ (v & 0xffffffffU));
|
||||
}
|
||||
|
||||
public bool int64_equal (int64? val_a, int64? val_b) {
|
||||
int64 a = val_a;
|
||||
int64 b = val_b;
|
||||
return a == b;
|
||||
}
|
||||
|
||||
public uint uint64_hash (uint64? val) {
|
||||
uint64 v = val;
|
||||
return (uint) ((v >> 32) ^ (v & 0xffffffffU));
|
||||
}
|
||||
|
||||
public bool uint64_equal (uint64? val_a, uint64? val_b) {
|
||||
uint64 a = val_a;
|
||||
uint64 b = val_b;
|
||||
return a == b;
|
||||
}
|
||||
|
||||
public uint bytes_hash (Bytes b) {
|
||||
return b.hash ();
|
||||
}
|
||||
|
||||
public static bool bytes_equal (Bytes a, Bytes b) {
|
||||
return a.compare (b) == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,5 @@
|
||||
#include "frida-base.h"
|
||||
|
||||
#ifdef HAVE_WINDOWS
|
||||
# include <winsock2.h>
|
||||
#else
|
||||
# include <netinet/in.h>
|
||||
# include <netinet/tcp.h>
|
||||
#endif
|
||||
|
||||
void
|
||||
frida_unix_socket_tune_buffer_sizes (gint fd)
|
||||
{
|
||||
#ifndef HAVE_WINDOWS
|
||||
/* The defaults are typically as low as 4K. */
|
||||
const int buffer_size = 256 * 1024;
|
||||
|
||||
setsockopt (fd, SOL_SOCKET, SO_RCVBUF, &buffer_size, sizeof (buffer_size));
|
||||
setsockopt (fd, SOL_SOCKET, SO_SNDBUF, &buffer_size, sizeof (buffer_size));
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
frida_tcp_enable_nodelay (GSocket * socket)
|
||||
{
|
||||
g_socket_set_option (socket, IPPROTO_TCP, TCP_NODELAY, TRUE, NULL);
|
||||
}
|
||||
|
||||
const gchar *
|
||||
_frida_version_string (void)
|
||||
{
|
||||
|
||||
+29
-87
@@ -1,99 +1,22 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public const uint16 DEFAULT_CONTROL_PORT = 27042;
|
||||
public const uint16 DEFAULT_CLUSTER_PORT = 27052;
|
||||
|
||||
public SocketConnectable parse_control_address (string? address, uint16 port = 0) throws Error {
|
||||
return parse_socket_address (address, port, "127.0.0.1", DEFAULT_CONTROL_PORT);
|
||||
return parse_frida_address (address, port, DEFAULT_CONTROL_PORT);
|
||||
}
|
||||
|
||||
public SocketConnectable parse_cluster_address (string? address, uint16 port = 0) throws Error {
|
||||
return parse_socket_address (address, port, "127.0.0.1", DEFAULT_CLUSTER_PORT);
|
||||
return parse_frida_address (address, port, DEFAULT_CLUSTER_PORT);
|
||||
}
|
||||
|
||||
public SocketConnectable parse_socket_address (string? address, uint16 port, string default_address,
|
||||
uint16 default_port) throws Error {
|
||||
if (address == null)
|
||||
address = default_address;
|
||||
if (port == 0)
|
||||
port = default_port;
|
||||
|
||||
#if !WINDOWS
|
||||
if (address.has_prefix ("unix:")) {
|
||||
string path = address.substring (5);
|
||||
|
||||
UnixSocketAddressType type = UnixSocketAddress.abstract_names_supported ()
|
||||
? UnixSocketAddressType.ABSTRACT
|
||||
: UnixSocketAddressType.PATH;
|
||||
|
||||
return new UnixSocketAddress.with_type (path, -1, type);
|
||||
}
|
||||
#endif
|
||||
|
||||
private SocketConnectable parse_frida_address (string? address, uint16 port, uint16 default_port) throws Error {
|
||||
try {
|
||||
return NetworkAddress.parse (address, port);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
namespace UnixSocket {
|
||||
public extern void tune_buffer_sizes (int fd);
|
||||
}
|
||||
|
||||
namespace Tcp {
|
||||
public extern void enable_nodelay (Socket socket);
|
||||
}
|
||||
|
||||
namespace Udp {
|
||||
public size_t recv (uint8[] data, DatagramBased source, Cancellable? cancellable, out InetSocketAddress remote_address)
|
||||
throws Error, IOError {
|
||||
var v = InputVector ();
|
||||
v.buffer = data;
|
||||
v.size = data.length;
|
||||
|
||||
InputVector[] vectors = { v };
|
||||
|
||||
var m = InputMessage ();
|
||||
remote_address = null;
|
||||
m.address = &remote_address;
|
||||
m.vectors = vectors;
|
||||
m.num_vectors = vectors.length;
|
||||
|
||||
InputMessage[] messages = { m };
|
||||
|
||||
try {
|
||||
source.receive_messages (messages, 0, 0, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
|
||||
return messages[0].bytes_received;
|
||||
}
|
||||
|
||||
public void send (uint8[] data, DatagramBased sink, Cancellable? cancellable) throws Error, IOError {
|
||||
send_to (data, null, sink, cancellable);
|
||||
}
|
||||
|
||||
public void send_to (uint8[] data, InetSocketAddress? dest_addr, DatagramBased sink, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var v = OutputVector ();
|
||||
v.buffer = data;
|
||||
v.size = data.length;
|
||||
|
||||
OutputVector[] vectors = { v };
|
||||
|
||||
var m = OutputMessage ();
|
||||
m.address = dest_addr;
|
||||
m.vectors = vectors;
|
||||
m.num_vectors = vectors.length;
|
||||
|
||||
OutputMessage[] messages = { m };
|
||||
|
||||
try {
|
||||
sink.send_messages (messages, 0, 0, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
return Efni.parse_socket_address (address, port, "127.0.0.1", default_port);
|
||||
} catch (Efni.Error e) {
|
||||
throw error_from_efni (e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -726,7 +649,8 @@ namespace Frida {
|
||||
try {
|
||||
remote_address = sc.get_remote_address ();
|
||||
} catch (GLib.Error e) {
|
||||
assert_not_reached ();
|
||||
remove_connection (peer);
|
||||
return;
|
||||
}
|
||||
|
||||
incoming (peer, remote_address);
|
||||
@@ -1116,6 +1040,9 @@ namespace Frida {
|
||||
construct {
|
||||
websocket.max_incoming_payload_size = (256 * 1024) + 1; // XXX: There's an off-by-one error in libsoup
|
||||
|
||||
if (websocket.get_class ().find_property ("max-total-message-size") != null)
|
||||
websocket.set ("max-total-message-size", websocket.max_incoming_payload_size);
|
||||
|
||||
websocket.closed.connect (on_closed);
|
||||
websocket.message.connect (on_message);
|
||||
}
|
||||
@@ -1165,7 +1092,7 @@ namespace Frida {
|
||||
|
||||
if (recv_queue.len == 0) {
|
||||
#if HAVE_SOUP_WEBSOCKET_BACKPRESSURE
|
||||
websocket.resume_input ();
|
||||
resume_websocket_input ();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1182,6 +1109,21 @@ namespace Frida {
|
||||
return n;
|
||||
}
|
||||
|
||||
#if HAVE_SOUP_WEBSOCKET_BACKPRESSURE
|
||||
private void resume_websocket_input () {
|
||||
if (main_context.is_owner ()) {
|
||||
websocket.resume_input ();
|
||||
} else {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
websocket.resume_input ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
source.attach (main_context);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public override ssize_t write (uint8[] buffer) {
|
||||
with_state_lock (() => {
|
||||
send_queue.append (buffer);
|
||||
|
||||
@@ -1,336 +0,0 @@
|
||||
namespace Frida {
|
||||
public abstract class VirtualStream : IOStream {
|
||||
public override InputStream input_stream {
|
||||
get {
|
||||
return _input_stream;
|
||||
}
|
||||
}
|
||||
|
||||
public override OutputStream output_stream {
|
||||
get {
|
||||
return _output_stream;
|
||||
}
|
||||
}
|
||||
|
||||
public IOCondition pending_io {
|
||||
get {
|
||||
lock (state)
|
||||
return _pending_io;
|
||||
}
|
||||
}
|
||||
|
||||
protected State state = OPEN;
|
||||
|
||||
private VirtualInputStream _input_stream;
|
||||
private VirtualOutputStream _output_stream;
|
||||
|
||||
private IOCondition _pending_io;
|
||||
|
||||
private Gee.Map<unowned Source, IOCondition> sources = new Gee.HashMap<unowned Source, IOCondition> ();
|
||||
|
||||
protected Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
protected MainContext main_context;
|
||||
|
||||
protected enum State {
|
||||
CREATED,
|
||||
OPENING,
|
||||
OPEN,
|
||||
CLOSED
|
||||
}
|
||||
|
||||
protected delegate void WorkFunc ();
|
||||
|
||||
construct {
|
||||
_input_stream = new VirtualInputStream (this);
|
||||
_output_stream = new VirtualOutputStream (this);
|
||||
|
||||
main_context = MainContext.ref_thread_default ();
|
||||
|
||||
state = query_initial_state ();
|
||||
_pending_io = query_events ();
|
||||
}
|
||||
|
||||
public override void dispose () {
|
||||
_output_stream.detach ();
|
||||
_input_stream.detach ();
|
||||
|
||||
io_cancellable.cancel ();
|
||||
|
||||
base.dispose ();
|
||||
}
|
||||
|
||||
protected virtual State query_initial_state () {
|
||||
return OPEN;
|
||||
}
|
||||
|
||||
protected abstract IOCondition query_events ();
|
||||
|
||||
public override bool close (GLib.Cancellable? cancellable) throws IOError {
|
||||
_close ();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool close_async (int io_priority, GLib.Cancellable? cancellable) throws IOError {
|
||||
_close ();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void _close () {
|
||||
if (main_context.is_owner ()) {
|
||||
do_close ();
|
||||
} else {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback (() => {
|
||||
do_close ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
source.attach (main_context);
|
||||
}
|
||||
}
|
||||
|
||||
private void do_close () {
|
||||
if (state == CLOSED)
|
||||
return;
|
||||
|
||||
handle_close ();
|
||||
}
|
||||
|
||||
protected abstract void handle_close ();
|
||||
|
||||
public virtual void shutdown_read () throws IOError {
|
||||
}
|
||||
|
||||
public virtual void shutdown_write () throws IOError {
|
||||
}
|
||||
|
||||
public abstract ssize_t read (uint8[] buffer) throws IOError;
|
||||
|
||||
public abstract ssize_t write (uint8[] buffer) throws IOError;
|
||||
|
||||
protected void with_state_lock (WorkFunc func) {
|
||||
lock (state)
|
||||
func ();
|
||||
}
|
||||
|
||||
public void register_source (Source source, IOCondition condition) {
|
||||
lock (state)
|
||||
sources[source] = condition | IOCondition.ERR | IOCondition.HUP;
|
||||
}
|
||||
|
||||
public void unregister_source (Source source) {
|
||||
lock (state)
|
||||
sources.unset (source);
|
||||
}
|
||||
|
||||
protected virtual void update_pending_io () {
|
||||
lock (state) {
|
||||
_pending_io = query_events ();
|
||||
|
||||
foreach (var entry in sources.entries) {
|
||||
unowned Source source = entry.key;
|
||||
IOCondition c = entry.value;
|
||||
if ((_pending_io & c) != 0)
|
||||
source.set_ready_time (0);
|
||||
}
|
||||
}
|
||||
|
||||
notify_property ("pending-io");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class VirtualInputStream : InputStream, PollableInputStream {
|
||||
private weak VirtualStream stream;
|
||||
|
||||
public VirtualInputStream (VirtualStream stream) {
|
||||
Object ();
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
internal void detach () {
|
||||
stream = null;
|
||||
}
|
||||
|
||||
public override bool close (Cancellable? cancellable) throws IOError {
|
||||
if (stream != null)
|
||||
stream.shutdown_read ();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool close_async (int io_priority, Cancellable? cancellable) throws GLib.IOError {
|
||||
return close (cancellable);
|
||||
}
|
||||
|
||||
public override ssize_t read (uint8[] buffer, Cancellable? cancellable) throws IOError {
|
||||
if (stream == null)
|
||||
return 0;
|
||||
|
||||
if (!is_readable ()) {
|
||||
bool done = false;
|
||||
var mutex = Mutex ();
|
||||
var cond = Cond ();
|
||||
|
||||
ulong io_handler = stream.notify["pending-io"].connect ((obj, pspec) => {
|
||||
if (is_readable ()) {
|
||||
mutex.lock ();
|
||||
done = true;
|
||||
cond.signal ();
|
||||
mutex.unlock ();
|
||||
}
|
||||
});
|
||||
ulong cancellation_handler = 0;
|
||||
if (cancellable != null) {
|
||||
cancellation_handler = cancellable.connect (() => {
|
||||
mutex.lock ();
|
||||
done = true;
|
||||
cond.signal ();
|
||||
mutex.unlock ();
|
||||
});
|
||||
}
|
||||
|
||||
if (!is_readable ()) {
|
||||
mutex.lock ();
|
||||
while (!done)
|
||||
cond.wait (mutex);
|
||||
mutex.unlock ();
|
||||
}
|
||||
|
||||
if (cancellation_handler != 0)
|
||||
cancellable.disconnect (cancellation_handler);
|
||||
stream.disconnect (io_handler);
|
||||
|
||||
cancellable.set_error_if_cancelled ();
|
||||
}
|
||||
|
||||
return stream.read (buffer);
|
||||
}
|
||||
|
||||
public bool can_poll () {
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool is_readable () {
|
||||
if (stream == null)
|
||||
return true;
|
||||
return (stream.pending_io & IOCondition.IN) != 0;
|
||||
}
|
||||
|
||||
public PollableSource create_source (Cancellable? cancellable) {
|
||||
return new PollableSource.full (this, new VirtualIOSource (stream, IOCondition.IN), cancellable);
|
||||
}
|
||||
|
||||
public ssize_t read_nonblocking_fn (uint8[] buffer) throws GLib.Error {
|
||||
if (stream == null)
|
||||
return 0;
|
||||
return stream.read (buffer);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class VirtualOutputStream : OutputStream, PollableOutputStream {
|
||||
private weak VirtualStream? stream;
|
||||
|
||||
public VirtualOutputStream (VirtualStream stream) {
|
||||
Object ();
|
||||
this.stream = stream;
|
||||
}
|
||||
|
||||
internal void detach () {
|
||||
stream = null;
|
||||
}
|
||||
|
||||
public override bool close (Cancellable? cancellable) throws IOError {
|
||||
if (stream != null)
|
||||
stream.shutdown_write ();
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool close_async (int io_priority, Cancellable? cancellable) throws GLib.IOError {
|
||||
return close (cancellable);
|
||||
}
|
||||
|
||||
public override bool flush (GLib.Cancellable? cancellable) throws GLib.Error {
|
||||
return true;
|
||||
}
|
||||
|
||||
public override async bool flush_async (int io_priority, GLib.Cancellable? cancellable) throws GLib.Error {
|
||||
return true;
|
||||
}
|
||||
|
||||
public override ssize_t write (uint8[] buffer, Cancellable? cancellable) throws IOError {
|
||||
return stream.write (buffer);
|
||||
}
|
||||
|
||||
public bool can_poll () {
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool is_writable () {
|
||||
if (stream == null)
|
||||
return false;
|
||||
return (stream.pending_io & IOCondition.OUT) != 0;
|
||||
}
|
||||
|
||||
public PollableSource create_source (Cancellable? cancellable) {
|
||||
return new PollableSource.full (this, new VirtualIOSource (stream, IOCondition.OUT), cancellable);
|
||||
}
|
||||
|
||||
public ssize_t write_nonblocking_fn (uint8[]? buffer) throws GLib.Error {
|
||||
if (stream == null)
|
||||
throw new IOError.CLOSED ("Virtual is closed");
|
||||
return stream.write (buffer);
|
||||
}
|
||||
|
||||
public PollableReturn writev_nonblocking_fn (OutputVector[] vectors, out size_t bytes_written) throws GLib.Error {
|
||||
assert_not_reached ();
|
||||
}
|
||||
}
|
||||
|
||||
private class VirtualIOSource : Source {
|
||||
public VirtualStream? stream;
|
||||
public IOCondition condition;
|
||||
|
||||
public VirtualIOSource (VirtualStream stream, IOCondition condition) {
|
||||
this.stream = stream;
|
||||
this.condition = condition;
|
||||
|
||||
if (stream != null)
|
||||
stream.register_source (this, condition);
|
||||
}
|
||||
|
||||
~VirtualIOSource () {
|
||||
if (stream != null)
|
||||
stream.unregister_source (this);
|
||||
}
|
||||
|
||||
protected override bool prepare (out int timeout) {
|
||||
timeout = -1;
|
||||
return is_ready ();
|
||||
}
|
||||
|
||||
protected override bool check () {
|
||||
return is_ready ();
|
||||
}
|
||||
|
||||
private bool is_ready () {
|
||||
IOCondition pending_io = (stream != null) ? stream.pending_io : IOCondition.IN;
|
||||
return (pending_io & condition) != 0;
|
||||
}
|
||||
|
||||
protected override bool dispatch (SourceFunc? callback) {
|
||||
set_ready_time (-1);
|
||||
|
||||
if (callback == null)
|
||||
return Source.REMOVE;
|
||||
|
||||
return callback ();
|
||||
}
|
||||
|
||||
protected static bool closure_callback (Closure closure) {
|
||||
var return_value = Value (typeof (bool));
|
||||
|
||||
closure.invoke (ref return_value, {});
|
||||
|
||||
return return_value.get_boolean ();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,432 +0,0 @@
|
||||
namespace Frida {
|
||||
public interface ObjectBuilder : Object {
|
||||
public abstract unowned ObjectBuilder begin_dictionary ();
|
||||
public abstract unowned ObjectBuilder set_member_name (string name);
|
||||
public abstract unowned ObjectBuilder end_dictionary ();
|
||||
|
||||
public abstract unowned ObjectBuilder begin_array ();
|
||||
public abstract unowned ObjectBuilder end_array ();
|
||||
|
||||
public abstract unowned ObjectBuilder add_null_value ();
|
||||
public abstract unowned ObjectBuilder add_bool_value (bool val);
|
||||
public abstract unowned ObjectBuilder add_int64_value (int64 val);
|
||||
public abstract unowned ObjectBuilder add_uint64_value (uint64 val);
|
||||
public abstract unowned ObjectBuilder add_data_value (Bytes val);
|
||||
public abstract unowned ObjectBuilder add_string_value (string val);
|
||||
public abstract unowned ObjectBuilder add_uuid_value (uint8[] val);
|
||||
public abstract unowned ObjectBuilder add_raw_value (Bytes val);
|
||||
|
||||
public abstract Bytes build ();
|
||||
}
|
||||
|
||||
public interface ObjectReader : Object {
|
||||
public abstract bool has_member (string name) throws Error;
|
||||
public abstract unowned ObjectReader read_member (string name) throws Error;
|
||||
public abstract unowned ObjectReader end_member ();
|
||||
|
||||
public abstract uint count_elements () throws Error;
|
||||
public abstract unowned ObjectReader read_element (uint index) throws Error;
|
||||
public abstract unowned ObjectReader end_element () throws Error;
|
||||
|
||||
public abstract bool get_bool_value () throws Error;
|
||||
public abstract uint8 get_uint8_value () throws Error;
|
||||
public abstract uint16 get_uint16_value () throws Error;
|
||||
public abstract int64 get_int64_value () throws Error;
|
||||
public abstract uint64 get_uint64_value () throws Error;
|
||||
public abstract Bytes get_data_value () throws Error;
|
||||
public abstract unowned string get_string_value () throws Error;
|
||||
public abstract unowned string get_uuid_value () throws Error;
|
||||
}
|
||||
|
||||
public sealed class VariantReader : Object, ObjectReader {
|
||||
public Variant root_object {
|
||||
get {
|
||||
return scopes.peek_head ().val;
|
||||
}
|
||||
}
|
||||
|
||||
public Variant current_object {
|
||||
get {
|
||||
return scopes.peek_tail ().val;
|
||||
}
|
||||
}
|
||||
|
||||
private Gee.Deque<Scope> scopes = new Gee.ArrayQueue<Scope> ();
|
||||
|
||||
public VariantReader (Variant v) {
|
||||
push_scope (v);
|
||||
}
|
||||
|
||||
public string[] list_members () throws Error {
|
||||
var scope = peek_scope ();
|
||||
if (scope.dict == null)
|
||||
throw new Error.PROTOCOL ("Dictionary expected, but at %s", scope.val.print (true));
|
||||
|
||||
var members = new Gee.ArrayList<string> ();
|
||||
foreach (var item in scope.val) {
|
||||
string k;
|
||||
Variant v;
|
||||
item.get ("{sv}", out k, out v);
|
||||
|
||||
members.add (k);
|
||||
}
|
||||
return members.to_array ();
|
||||
}
|
||||
|
||||
public bool has_member (string name) throws Error {
|
||||
var scope = peek_scope ();
|
||||
if (scope.dict == null)
|
||||
throw new Error.PROTOCOL ("Dictionary expected, but at %s", scope.val.print (true));
|
||||
return scope.dict.contains (name);
|
||||
}
|
||||
|
||||
public unowned ObjectReader read_member (string name) throws Error {
|
||||
var scope = peek_scope ();
|
||||
if (scope.dict == null)
|
||||
throw new Error.PROTOCOL ("Dictionary expected, but at %s", scope.val.print (true));
|
||||
|
||||
Variant? v = scope.dict.lookup_value (name, null);
|
||||
if (v == null)
|
||||
throw new Error.PROTOCOL ("Key '%s' not found in dictionary: %s", name, scope.val.print (true));
|
||||
|
||||
push_scope (v);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectReader end_member () {
|
||||
pop_scope ();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public uint count_elements () throws Error {
|
||||
var scope = peek_scope ();
|
||||
scope.check_array ();
|
||||
return (uint) scope.val.n_children ();
|
||||
}
|
||||
|
||||
public unowned ObjectReader read_element (uint index) throws Error {
|
||||
var scope = peek_scope ();
|
||||
scope.check_array ();
|
||||
push_scope (scope.val.get_child_value (index).get_variant ());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectReader end_element () throws Error {
|
||||
pop_scope ();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool get_bool_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.BOOLEAN).get_boolean ();
|
||||
}
|
||||
|
||||
public uint8 get_uint8_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.BYTE).get_byte ();
|
||||
}
|
||||
|
||||
public uint16 get_uint16_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.UINT16).get_uint16 ();
|
||||
}
|
||||
|
||||
public int64 get_int64_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.INT64).get_int64 ();
|
||||
}
|
||||
|
||||
public uint64 get_uint64_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.UINT64).get_uint64 ();
|
||||
}
|
||||
|
||||
public Bytes get_data_value () throws Error {
|
||||
return peek_scope ().get_value (new VariantType.array (VariantType.BYTE)).get_data_as_bytes ();
|
||||
}
|
||||
|
||||
public unowned string get_string_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.STRING).get_string ();
|
||||
}
|
||||
|
||||
public unowned string get_uuid_value () throws Error {
|
||||
return peek_scope ().get_value (VariantType.STRING).get_string (); // TODO: Use a tuple to avoid ambiguity.
|
||||
}
|
||||
|
||||
private void push_scope (Variant v) {
|
||||
scopes.offer_tail (new Scope (v));
|
||||
}
|
||||
|
||||
private Scope peek_scope () {
|
||||
return scopes.peek_tail ();
|
||||
}
|
||||
|
||||
private Scope pop_scope () {
|
||||
return scopes.poll_tail ();
|
||||
}
|
||||
|
||||
private class Scope {
|
||||
public Variant val;
|
||||
public VariantDict? dict;
|
||||
public bool is_array = false;
|
||||
|
||||
public Scope (Variant v) {
|
||||
val = v;
|
||||
|
||||
VariantType t = v.get_type ();
|
||||
if (t.equal (VariantType.VARDICT))
|
||||
dict = new VariantDict (v);
|
||||
else if (t.is_subtype_of (VariantType.ARRAY))
|
||||
is_array = true;
|
||||
}
|
||||
|
||||
public Variant get_value (VariantType expected_type) throws Error {
|
||||
if (!val.get_type ().equal (expected_type)) {
|
||||
throw new Error.PROTOCOL ("Expected type '%s', got '%s'",
|
||||
(string) expected_type.peek_string (),
|
||||
(string) val.get_type ().peek_string ());
|
||||
}
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
public void check_array () throws Error {
|
||||
if (!is_array)
|
||||
throw new Error.PROTOCOL ("Array expected, but at %s", val.print (true));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class JsonObjectBuilder : Object, ObjectBuilder {
|
||||
private Json.Builder builder = new Json.Builder ();
|
||||
private Gee.Map<string, Bytes> raw_values = new Gee.HashMap<string, Bytes> ();
|
||||
|
||||
public unowned ObjectBuilder begin_dictionary () {
|
||||
builder.begin_object ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder set_member_name (string name) {
|
||||
builder.set_member_name (name);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder end_dictionary () {
|
||||
builder.end_object ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder begin_array () {
|
||||
builder.begin_array ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder end_array () {
|
||||
builder.end_array ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_null_value () {
|
||||
builder.add_null_value ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_bool_value (bool val) {
|
||||
builder.add_boolean_value (val);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_int64_value (int64 val) {
|
||||
builder.add_int_value (val);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_uint64_value (uint64 val) {
|
||||
builder.add_int_value ((int64) val);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_data_value (Bytes val) {
|
||||
builder.add_string_value (Base64.encode (val.get_data ()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_string_value (string val) {
|
||||
builder.add_string_value (val);
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_uuid_value (uint8[] val) {
|
||||
assert_not_reached ();
|
||||
}
|
||||
|
||||
public unowned ObjectBuilder add_raw_value (Bytes val) {
|
||||
string uuid = Uuid.string_random ();
|
||||
builder.add_string_value (uuid);
|
||||
raw_values[uuid] = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Bytes build () {
|
||||
string json = Json.to_string (builder.get_root (), false);
|
||||
|
||||
foreach (var e in raw_values.entries) {
|
||||
unowned string uuid = e.key;
|
||||
Bytes val = e.value;
|
||||
|
||||
unowned string raw_str = (string) val.get_data ();
|
||||
string str = raw_str[:(long) val.get_size ()];
|
||||
|
||||
json = json.replace ("\"" + uuid + "\"", str);
|
||||
}
|
||||
|
||||
return new Bytes (json.data);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class JsonObjectReader : Object, ObjectReader {
|
||||
private Json.Reader reader;
|
||||
|
||||
public JsonObjectReader (string json) throws Error {
|
||||
try {
|
||||
reader = make_json_reader (json);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public bool has_member (string name) throws Error {
|
||||
bool result = reader.read_member (name);
|
||||
reader.end_member ();
|
||||
return result;
|
||||
}
|
||||
|
||||
public unowned ObjectReader read_member (string name) throws Error {
|
||||
if (!reader.read_member (name))
|
||||
throw_dict_access_error ();
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectReader end_member () {
|
||||
reader.end_member ();
|
||||
return this;
|
||||
}
|
||||
|
||||
[NoReturn]
|
||||
private void throw_dict_access_error () throws Error {
|
||||
GLib.Error e = reader.get_error ();
|
||||
reader.end_member ();
|
||||
throw new Error.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
|
||||
public uint count_elements () throws Error {
|
||||
int n = reader.count_elements ();
|
||||
if (n == -1)
|
||||
throw_array_access_error ();
|
||||
return n;
|
||||
}
|
||||
|
||||
public unowned ObjectReader read_element (uint index) throws Error {
|
||||
if (!reader.read_element (index)) {
|
||||
GLib.Error e = reader.get_error ();
|
||||
reader.end_element ();
|
||||
throw new Error.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned ObjectReader end_element () throws Error {
|
||||
reader.end_element ();
|
||||
return this;
|
||||
}
|
||||
|
||||
[NoReturn]
|
||||
private void throw_array_access_error () throws Error {
|
||||
GLib.Error e = reader.get_error ();
|
||||
reader.end_element ();
|
||||
throw new Error.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
|
||||
public bool get_bool_value () throws Error {
|
||||
bool v = reader.get_boolean_value ();
|
||||
if (!v)
|
||||
maybe_throw_value_access_error ();
|
||||
return v;
|
||||
}
|
||||
|
||||
public uint8 get_uint8_value () throws Error {
|
||||
int64 v = get_int64_value ();
|
||||
if (v < 0 || v > uint8.MAX)
|
||||
throw new Error.PROTOCOL ("Invalid uint8");
|
||||
return (uint8) v;
|
||||
}
|
||||
|
||||
public uint16 get_uint16_value () throws Error {
|
||||
int64 v = get_int64_value ();
|
||||
if (v < 0 || v > uint16.MAX)
|
||||
throw new Error.PROTOCOL ("Invalid uint16");
|
||||
return (uint16) v;
|
||||
}
|
||||
|
||||
public int64 get_int64_value () throws Error {
|
||||
int64 v = reader.get_int_value ();
|
||||
if (v == 0)
|
||||
maybe_throw_value_access_error ();
|
||||
return v;
|
||||
}
|
||||
|
||||
public uint64 get_uint64_value () throws Error {
|
||||
int64 v = get_int64_value ();
|
||||
if (v < 0)
|
||||
throw new Error.PROTOCOL ("Invalid uint64");
|
||||
return v;
|
||||
}
|
||||
|
||||
public Bytes get_data_value () throws Error {
|
||||
return new Bytes (Base64.decode (get_string_value ()));
|
||||
}
|
||||
|
||||
public unowned string get_string_value () throws Error {
|
||||
unowned string? v = reader.get_string_value ();
|
||||
if (v == null)
|
||||
maybe_throw_value_access_error ();
|
||||
return v;
|
||||
}
|
||||
|
||||
public unowned string get_uuid_value () throws Error {
|
||||
return get_string_value ();
|
||||
}
|
||||
|
||||
private void maybe_throw_value_access_error () throws Error {
|
||||
GLib.Error? e = reader.get_error ();
|
||||
if (e == null)
|
||||
return;
|
||||
reader.end_member ();
|
||||
throw new Error.PROTOCOL ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Constructs a Json.Reader for the given JSON string, working around the
|
||||
* json_node_copy() bug in unpatched json-glib (GNOME bugzilla #774684).
|
||||
*
|
||||
* Json.Reader's set_root() copies the supplied node via json_node_copy().
|
||||
* For object/array nodes that copy is a thin shell that shares the original
|
||||
* JsonObject/JsonArray via refcount, but the members inside the shared
|
||||
* container have their parent pointers set to the *original* root, not the
|
||||
* shell. If the original is then dropped, json_reader_end_member()'s walk
|
||||
* via json_node_get_parent() touches a freed node — which on builds with
|
||||
* glib checks enabled (e.g. Fedora) trips JSON_NODE_IS_VALID and aborts.
|
||||
*
|
||||
* We keep the original root alive by attaching it to the reader as object
|
||||
* data; the destroy notify drops our reference when the reader is finalized.
|
||||
*/
|
||||
public Json.Reader make_json_reader (string json) throws GLib.Error {
|
||||
return make_json_reader_taking_node (Json.from_string (json));
|
||||
}
|
||||
|
||||
public Json.Reader make_json_reader_taking_node (owned Json.Node root) {
|
||||
var reader = new Json.Reader (root);
|
||||
reader.set_data<Json.Node> ("frida-json-root", (owned) root);
|
||||
return reader;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,376 @@
|
||||
#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,4 +1,8 @@
|
||||
namespace Frida {
|
||||
namespace WindowsNamedPipe {
|
||||
public extern IOStream open_client (string path) throws GLib.Error;
|
||||
}
|
||||
|
||||
public sealed class StdioPipes : Object {
|
||||
public OutputStream input {
|
||||
get;
|
||||
|
||||
@@ -1,291 +0,0 @@
|
||||
namespace Frida {
|
||||
public sealed class XpcClient : Object {
|
||||
public signal void message (Darwin.Xpc.Object obj);
|
||||
|
||||
public State state {
|
||||
get {
|
||||
return _state;
|
||||
}
|
||||
}
|
||||
|
||||
public Darwin.Xpc.Connection connection {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
public Darwin.GCD.DispatchQueue queue {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private State _state;
|
||||
private string? close_reason;
|
||||
private MainContext main_context;
|
||||
|
||||
public enum State {
|
||||
OPEN,
|
||||
CLOSING,
|
||||
CLOSED,
|
||||
}
|
||||
|
||||
public static XpcClient make_for_mach_service (string name, Darwin.GCD.DispatchQueue queue) {
|
||||
return new XpcClient (Darwin.Xpc.Connection.create_mach_service (name, queue, 0), queue);
|
||||
}
|
||||
|
||||
public XpcClient (Darwin.Xpc.Connection connection, Darwin.GCD.DispatchQueue queue) {
|
||||
Object (connection: connection, queue: queue);
|
||||
}
|
||||
|
||||
construct {
|
||||
main_context = MainContext.ref_thread_default ();
|
||||
|
||||
connection.set_event_handler (on_event);
|
||||
connection.activate ();
|
||||
}
|
||||
|
||||
public override void dispose () {
|
||||
if (close_reason != null) {
|
||||
change_state (CLOSED);
|
||||
} else {
|
||||
change_state (CLOSING);
|
||||
this.ref ();
|
||||
connection.cancel ();
|
||||
}
|
||||
|
||||
base.dispose ();
|
||||
}
|
||||
|
||||
public async Darwin.Xpc.Object request (Darwin.Xpc.Object message, Cancellable? cancellable) throws Error, IOError {
|
||||
Darwin.Xpc.Object? reply = null;
|
||||
connection.send_message_with_reply (message, queue, r => {
|
||||
schedule_on_frida_thread (() => {
|
||||
reply = r;
|
||||
request.callback ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
});
|
||||
|
||||
var cancel_source = new CancellableSource (cancellable);
|
||||
cancel_source.set_callback (() => {
|
||||
connection.cancel ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
cancel_source.attach (main_context);
|
||||
|
||||
yield;
|
||||
|
||||
cancel_source.destroy ();
|
||||
|
||||
if (reply.type == Darwin.Xpc.Error.TYPE) {
|
||||
var e = (Darwin.Xpc.Error) reply;
|
||||
throw new Error.NOT_SUPPORTED ("%s", e.get_string (Darwin.Xpc.Error.KEY_DESCRIPTION));
|
||||
}
|
||||
|
||||
return reply;
|
||||
}
|
||||
|
||||
public void post (Darwin.Xpc.Object message) throws Error, IOError {
|
||||
connection.send_message (message);
|
||||
}
|
||||
|
||||
private void change_state (State new_state) {
|
||||
_state = new_state;
|
||||
notify_property ("state");
|
||||
}
|
||||
|
||||
private void on_event (Darwin.Xpc.Object obj) {
|
||||
schedule_on_frida_thread (() => {
|
||||
if (obj.type == Darwin.Xpc.Error.TYPE) {
|
||||
var e = (Darwin.Xpc.Error) obj;
|
||||
close_reason = e.get_string (Darwin.Xpc.Error.KEY_DESCRIPTION);
|
||||
switch (state) {
|
||||
case OPEN:
|
||||
change_state (CLOSED);
|
||||
break;
|
||||
case CLOSING:
|
||||
change_state (CLOSED);
|
||||
unref ();
|
||||
break;
|
||||
case CLOSED:
|
||||
assert_not_reached ();
|
||||
}
|
||||
} else {
|
||||
message (obj);
|
||||
}
|
||||
return Source.REMOVE;
|
||||
});
|
||||
}
|
||||
|
||||
private void schedule_on_frida_thread (owned SourceFunc function) {
|
||||
var source = new IdleSource ();
|
||||
source.set_callback ((owned) function);
|
||||
source.attach (main_context);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class XpcObjectReader {
|
||||
public Darwin.Xpc.Object root_object {
|
||||
get {
|
||||
return scopes.peek_head ().object;
|
||||
}
|
||||
}
|
||||
|
||||
public Darwin.Xpc.Object current_object {
|
||||
get {
|
||||
return scopes.peek_tail ().object;
|
||||
}
|
||||
}
|
||||
|
||||
public delegate int TranslateErrorFunc (string domain, int code, string description);
|
||||
|
||||
private Gee.Deque<Scope> scopes = new Gee.ArrayQueue<Scope> ();
|
||||
|
||||
public XpcObjectReader (Darwin.Xpc.Object obj) {
|
||||
push_scope (obj);
|
||||
}
|
||||
|
||||
public bool has_member (string name) throws Error {
|
||||
return peek_scope ().get_dictionary ().get_value (name) != null;
|
||||
}
|
||||
|
||||
public bool try_read_member (string name) throws Error {
|
||||
var scope = peek_scope ();
|
||||
var dict = scope.get_dictionary ();
|
||||
var val = dict.get_value (name);
|
||||
if (val == null)
|
||||
return false;
|
||||
|
||||
push_scope (val);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public unowned XpcObjectReader read_member (string name) throws Error {
|
||||
var scope = peek_scope ();
|
||||
var dict = scope.get_dictionary ();
|
||||
var val = dict.get_value (name);
|
||||
if (val == null)
|
||||
throw new Error.PROTOCOL ("Key '%s' not found in dictionary: %s", name, scope.object.to_string ());
|
||||
|
||||
push_scope (val);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned XpcObjectReader end_member () {
|
||||
pop_scope ();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public size_t count_elements () throws Error {
|
||||
return peek_scope ().get_array ().count;
|
||||
}
|
||||
|
||||
public unowned XpcObjectReader read_element (size_t index) throws Error {
|
||||
push_scope (peek_scope ().get_array ().get_value (index));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public unowned XpcObjectReader end_element () throws Error {
|
||||
pop_scope ();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool get_bool_value () throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.Bool> (Darwin.Xpc.Bool.TYPE).get_value ();
|
||||
}
|
||||
|
||||
public int64 get_int64_value () throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.Int64> (Darwin.Xpc.Int64.TYPE).get_value ();
|
||||
}
|
||||
|
||||
public uint64 get_uint64_value () throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.UInt64> (Darwin.Xpc.UInt64.TYPE).get_value ();
|
||||
}
|
||||
|
||||
public unowned uint8[] get_data_value () throws Error {
|
||||
var data = peek_scope ().get_object<Darwin.Xpc.Data> (Darwin.Xpc.Data.TYPE);
|
||||
unowned uint8[] buf = (uint8[]) data.get_bytes_ptr ();
|
||||
buf.length = (int) data.get_length ();
|
||||
return buf;
|
||||
}
|
||||
|
||||
public unowned string get_string_value () throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.String> (Darwin.Xpc.String.TYPE).get_string_ptr ();
|
||||
}
|
||||
|
||||
public unowned uint8[] get_uuid_value () throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.Uuid> (Darwin.Xpc.Uuid.TYPE).get_bytes ()[:16];
|
||||
}
|
||||
|
||||
public unowned string get_error_description () throws Error {
|
||||
var error = peek_scope ().get_object<Darwin.Xpc.Error> (Darwin.Xpc.Error.TYPE);
|
||||
return error.get_string (Darwin.Xpc.Error.KEY_DESCRIPTION);
|
||||
}
|
||||
|
||||
public void check_nserror (TranslateErrorFunc translate_error) throws Error {
|
||||
if (!has_member ("error"))
|
||||
return;
|
||||
|
||||
read_member ("error");
|
||||
|
||||
unowned string domain = read_member ("domain").get_string_value ();
|
||||
end_member ();
|
||||
|
||||
var code = (int) read_member ("code").get_int64_value ();
|
||||
end_member ();
|
||||
|
||||
string description = read_member ("userInfo").read_member ("NSLocalizedDescription").get_string_value ();
|
||||
if (description.has_suffix ("."))
|
||||
description = description[:description.length - 1];
|
||||
|
||||
throw (Error) new GLib.Error.literal (
|
||||
Quark.from_string ("frida-error-quark"),
|
||||
translate_error (domain, code, description),
|
||||
description);
|
||||
}
|
||||
|
||||
public unowned Darwin.Xpc.Object get_object_value (Darwin.Xpc.Type expected_type) throws Error {
|
||||
return peek_scope ().get_object<Darwin.Xpc.Object> (expected_type);
|
||||
}
|
||||
|
||||
private void push_scope (Darwin.Xpc.Object obj) {
|
||||
scopes.offer_tail (new Scope (obj));
|
||||
}
|
||||
|
||||
private Scope peek_scope () {
|
||||
return scopes.peek_tail ();
|
||||
}
|
||||
|
||||
private Scope pop_scope () {
|
||||
return scopes.poll_tail ();
|
||||
}
|
||||
|
||||
private class Scope {
|
||||
public Darwin.Xpc.Object object;
|
||||
private unowned Darwin.Xpc.Type type;
|
||||
|
||||
public Scope (Darwin.Xpc.Object obj) {
|
||||
object = obj;
|
||||
type = obj.type;
|
||||
}
|
||||
|
||||
public unowned T get_object<T> (Darwin.Xpc.Type expected_type) throws Error {
|
||||
if (type != expected_type)
|
||||
throw new Error.PROTOCOL ("Expected type '%s', got '%s'", expected_type.name, type.name);
|
||||
return object;
|
||||
}
|
||||
|
||||
public unowned Darwin.Xpc.Array get_array () throws Error {
|
||||
return get_object<Darwin.Xpc.Array> (Darwin.Xpc.Array.TYPE);
|
||||
}
|
||||
|
||||
public unowned Darwin.Xpc.Dictionary get_dictionary () throws Error {
|
||||
return get_object<Darwin.Xpc.Dictionary> (Darwin.Xpc.Dictionary.TYPE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida.Gadget {
|
||||
private sealed class Config : Object, Json.Serializable {
|
||||
public Object interaction {
|
||||
|
||||
@@ -55,6 +55,10 @@ 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,7 +8,6 @@ extra_link_args = []
|
||||
if host_os in ['ios', 'tvos']
|
||||
extra_vala_args += [
|
||||
'--pkg=corefoundation',
|
||||
'--pkg=darwin-gcd',
|
||||
'--pkg=darwin-systemconfiguration',
|
||||
]
|
||||
extra_link_args += '-Wl,-framework,SystemConfiguration'
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public abstract class BaseAgentSession : Object, AgentSession {
|
||||
public signal void closed ();
|
||||
|
||||
@@ -35,15 +35,21 @@ 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
|
||||
}
|
||||
|
||||
|
||||
+28
-21
@@ -28,6 +28,7 @@
|
||||
#undef feof
|
||||
#undef ferror
|
||||
#undef fgetwc
|
||||
#undef fileno
|
||||
#undef fopen
|
||||
#undef fopen64
|
||||
#undef fputwc
|
||||
@@ -214,6 +215,7 @@ 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);
|
||||
@@ -251,7 +253,13 @@ 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);
|
||||
|
||||
#ifndef FRIDA_STDIO_OPAQUE_FILE
|
||||
#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)
|
||||
G_GNUC_INTERNAL FILE __sF[3];
|
||||
|
||||
G_GNUC_INTERNAL FILE * stdin = &__sF[0];
|
||||
@@ -301,33 +309,18 @@ frida_libc_shim_init (void)
|
||||
|
||||
G_LOCK (frida_stdio);
|
||||
|
||||
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
|
||||
frida_stdio_ensure_registries ();
|
||||
|
||||
G_UNLOCK (frida_stdio);
|
||||
|
||||
f0 = frida_file_new (0, FALSE, _IOFBF);
|
||||
#ifndef FRIDA_STDIO_OPAQUE_FILE
|
||||
frida_file_bind_slot (&__sF[0], f0);
|
||||
#else
|
||||
frida_file_bind_slot ((FILE *) &frida_stdio[0], f0);
|
||||
#endif
|
||||
frida_file_bind_slot (stdin, f0);
|
||||
|
||||
f1 = frida_file_new (1, FALSE, _IOLBF);
|
||||
#ifndef FRIDA_STDIO_OPAQUE_FILE
|
||||
frida_file_bind_slot (&__sF[1], f1);
|
||||
#else
|
||||
frida_file_bind_slot ((FILE *) &frida_stdio[1], f1);
|
||||
#endif
|
||||
frida_file_bind_slot (stdout, f1);
|
||||
|
||||
f2 = frida_file_new (2, FALSE, _IONBF);
|
||||
#ifndef FRIDA_STDIO_OPAQUE_FILE
|
||||
frida_file_bind_slot (&__sF[2], f2);
|
||||
#else
|
||||
frida_file_bind_slot ((FILE *) &frida_stdio[2], f2);
|
||||
#endif
|
||||
frida_file_bind_slot (stderr, f2);
|
||||
|
||||
frida_libc_shim_initialized = TRUE;
|
||||
}
|
||||
@@ -638,7 +631,7 @@ __vsnprintf_chk (char * string, size_t size, int flags, size_t len, const char *
|
||||
return gum_vsnprintf (string, size, format, args);
|
||||
}
|
||||
|
||||
#ifdef HAVE_XLOCALE_H
|
||||
#if defined (HAVE_XLOCALE_H) && !defined (HAVE_PROSPERO)
|
||||
|
||||
G_GNUC_INTERNAL int
|
||||
sprintf_l (char * string, locale_t loc, const char * format, ...)
|
||||
@@ -1904,6 +1897,7 @@ 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);
|
||||
@@ -1926,6 +1920,7 @@ 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);
|
||||
@@ -1943,6 +1938,18 @@ 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,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public sealed class PortalClient : Object, AgentSessionProvider {
|
||||
public signal void resume ();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public extern void libc_shim_prepare_to_deinit ();
|
||||
#if DARWIN
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public sealed class ScriptEngine : Object {
|
||||
public signal void message_from_script (AgentScriptId script_id, string json, Bytes? data);
|
||||
|
||||
@@ -34,7 +34,7 @@ struct _FridaCreateArgs
|
||||
|
||||
static FridaDyldUnwindSections * frida_get_cached_sections (GumAddress range_start, GumAddress range_end);
|
||||
static FridaDyldUnwindSections * frida_create_cached_sections (FridaCreateArgs * args);
|
||||
static gboolean frida_fill_info (const GumDarwinSectionDetails * details, FridaFillInfoContext * ctx);
|
||||
static gboolean frida_fill_info (const GumMachoSectionDetails * details, FridaFillInfoContext * ctx);
|
||||
|
||||
void
|
||||
_frida_unwind_sitter_fill_unwind_sections (GumAddress invader_start, GumAddress invader_end, void * info)
|
||||
@@ -70,7 +70,7 @@ frida_create_cached_sections (FridaCreateArgs * args)
|
||||
gsize page_size;
|
||||
gpointer header;
|
||||
GumPageProtection prot;
|
||||
GumDarwinModule * module;
|
||||
GumMachoModule * module;
|
||||
FridaFillInfoContext ctx;
|
||||
|
||||
page_size = gum_query_page_size ();
|
||||
@@ -87,14 +87,14 @@ frida_create_cached_sections (FridaCreateArgs * args)
|
||||
cached_sections = g_slice_new0 (FridaDyldUnwindSections);
|
||||
cached_sections->mh = header;
|
||||
|
||||
module = gum_darwin_module_new_from_memory ("Frida", mach_task_self (), GPOINTER_TO_SIZE (header),
|
||||
GUM_DARWIN_MODULE_FLAGS_NONE, NULL);
|
||||
module = gum_macho_module_new_from_memory ("Frida", mach_task_self (), GPOINTER_TO_SIZE (header),
|
||||
GUM_MACHO_MODULE_FLAGS_NONE, NULL);
|
||||
if (module == NULL)
|
||||
return cached_sections;
|
||||
|
||||
ctx.info = cached_sections;
|
||||
ctx.missing_info = 2;
|
||||
gum_darwin_module_enumerate_sections (module, (GumFoundDarwinSectionFunc) frida_fill_info, &ctx);
|
||||
gum_macho_module_enumerate_sections (module, (GumFoundMachoSectionFunc) frida_fill_info, &ctx);
|
||||
|
||||
g_object_unref (module);
|
||||
|
||||
@@ -102,7 +102,7 @@ frida_create_cached_sections (FridaCreateArgs * args)
|
||||
}
|
||||
|
||||
static gboolean
|
||||
frida_fill_info (const GumDarwinSectionDetails * details, FridaFillInfoContext * ctx)
|
||||
frida_fill_info (const GumMachoSectionDetails * details, FridaFillInfoContext * ctx)
|
||||
{
|
||||
if (strcmp ("__TEXT", details->segment_name) != 0)
|
||||
return TRUE;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "pipe-glue.h"
|
||||
|
||||
#include <efni.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
@@ -66,7 +67,7 @@ _frida_pipe_transport_create_backend (gchar ** local_address, gchar ** remote_ad
|
||||
|
||||
fcntl (fd, F_SETFD, FD_CLOEXEC);
|
||||
setsockopt (fd, SOL_SOCKET, SO_NOSIGPIPE, &no_sigpipe, sizeof (no_sigpipe));
|
||||
frida_unix_socket_tune_buffer_sizes (fd);
|
||||
efni_unix_socket_tune_buffer_sizes (fd);
|
||||
}
|
||||
|
||||
status = fileport_makeport (sockets[0], &local_wrapper);
|
||||
|
||||
+3
-1
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public sealed class PipeTransport : Object {
|
||||
public string local_address {
|
||||
@@ -157,7 +159,7 @@ namespace Frida {
|
||||
UnixSocketAddressType type = UnixSocketAddress.abstract_names_supported ()
|
||||
? UnixSocketAddressType.ABSTRACT
|
||||
: UnixSocketAddressType.PATH;
|
||||
var server_address = new UnixSocketAddress.with_type (path, -1, type);
|
||||
var server_address = new UnixSocketAddress.with_type (path.to_utf8 (), type);
|
||||
|
||||
if (role == "server") {
|
||||
var socket = new Socket (SocketFamily.UNIX, SocketType.STREAM, SocketProtocol.DEFAULT);
|
||||
|
||||
+78
-52
@@ -14,7 +14,7 @@ if not meson.get_compiler('vala').version().endswith('-frida')
|
||||
Frida currently relies on features not yet upstream. Grab the Frida-optimized
|
||||
Vala sources from:
|
||||
|
||||
https://github.com/frida/vala
|
||||
https://github.com/frida-forks/vala
|
||||
|
||||
and compile them with Meson.''')
|
||||
endif
|
||||
@@ -273,6 +273,15 @@ 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'],
|
||||
@@ -340,9 +349,13 @@ glib_options = [
|
||||
'selinux=disabled',
|
||||
'xattr=false',
|
||||
'libmount=disabled',
|
||||
'sysprof=disabled',
|
||||
'tests=false',
|
||||
'nls=disabled',
|
||||
]
|
||||
if host_os_family in ['darwin', 'qnx', 'none'] or host_os == 'android' or libc == 'uclibc'
|
||||
glib_options += 'iconv=external'
|
||||
endif
|
||||
if optimize_for_prod
|
||||
glib_options += [
|
||||
'glib_debug=disabled',
|
||||
@@ -399,13 +412,27 @@ 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',
|
||||
@@ -419,7 +446,13 @@ if libsoup_dep.type_name() == 'internal' \
|
||||
vala_flags += '--define=HAVE_SOUP_WEBSOCKET_BACKPRESSURE'
|
||||
endif
|
||||
brotlidec_dep = dependency('libbrotlidec', default_options: ['default_library=static'])
|
||||
capstone_dep = dependency('capstone', default_options: ['default_library=static'])
|
||||
capstone_dep = dependency('capstone', default_options: [
|
||||
'default_library=static',
|
||||
'archs=all',
|
||||
'use_arch_registration=true',
|
||||
'x86_att_disable=true',
|
||||
'cli=disabled',
|
||||
])
|
||||
if capstone_dep.type_name() != 'internal' \
|
||||
and not cc.has_function('cs_arch_register_x86', dependencies: [capstone_dep])
|
||||
add_project_arguments(
|
||||
@@ -433,9 +466,9 @@ endif
|
||||
quickjs_dep = dependency('quickjs', required: get_option('barebone_backend'), default_options: [
|
||||
'default_library=static',
|
||||
'libc=false',
|
||||
'bignum=true',
|
||||
'atomics=disabled',
|
||||
'stack_check=disabled',
|
||||
'stack_check=' + ((host_os_family == 'none') ? 'enabled' : 'disabled'),
|
||||
'stack_mode=' + ((host_os_family == 'none') ? 'optimize' : 'default'),
|
||||
])
|
||||
lzfse_dep = dependency('lzfse', required: false, default_options: ['default_library=static'])
|
||||
lzfse_from_system = false
|
||||
@@ -471,6 +504,7 @@ endif
|
||||
|
||||
if host_os == 'android'
|
||||
minizip_dep = dependency('minizip', required: false, default_options: [
|
||||
'default_library=static',
|
||||
'zlib=enabled',
|
||||
'lzma=disabled',
|
||||
])
|
||||
@@ -564,7 +598,11 @@ if compiler_backend_opt.allowed()
|
||||
have_compiler_backend = true
|
||||
cdata.set('HAVE_COMPILER_BACKEND', 1)
|
||||
compiler_backend_mode = lines[1]
|
||||
go_config = lines[2]
|
||||
go_config = configure_file(
|
||||
input: 'tools' / 'cgo' / 'go-config.json.in',
|
||||
output: 'go-config.json',
|
||||
configuration: {'GO_CONFIG': lines[2]},
|
||||
)
|
||||
|
||||
if compiler_backend_mode == 'c-archive'
|
||||
vala_flags += '--define=COMPILER_BACKEND_LINKED'
|
||||
@@ -688,54 +726,32 @@ if host_os_family == 'windows' and cc.get_argument_syntax() != 'msvc'
|
||||
openssl_options += 'asm=disabled'
|
||||
endif
|
||||
openssl_dep = dependency('openssl',
|
||||
required: have_fruity_backend or get_option('connectivity').enabled(),
|
||||
required: get_option('connectivity').enabled(),
|
||||
modules: cc.get_argument_syntax() == 'msvc' ? ['OpenSSL::SSL'] : [],
|
||||
default_options: openssl_options,
|
||||
)
|
||||
|
||||
idun_libs = []
|
||||
have_idund_blob = false
|
||||
if have_fruity_backend
|
||||
backend_deps_private += dependency('libnghttp2', default_options: ['default_library=static'])
|
||||
backend_reqs_private += 'libnghttp2'
|
||||
|
||||
if host_os != 'macos'
|
||||
backend_deps_private += dependency('libngtcp2', default_options: ['default_library=static'])
|
||||
backend_reqs_private += 'libngtcp2'
|
||||
|
||||
ngtcp2_crypto_quictls_dep = dependency('libngtcp2_crypto_quictls', required: false, default_options: ['default_library=static'])
|
||||
if ngtcp2_crypto_quictls_dep.found()
|
||||
backend_deps_private += ngtcp2_crypto_quictls_dep
|
||||
backend_reqs_private += 'libngtcp2_crypto_quictls'
|
||||
else
|
||||
backend_deps_private += dependency('libngtcp2_crypto_ossl', default_options: ['default_library=static'])
|
||||
backend_reqs_private += 'libngtcp2_crypto_ossl'
|
||||
vala_flags += '--define=HAVE_NGTCP2_CRYPTO_OSSL'
|
||||
idun_dep = dependency('idun-1.0', default_options: ['default_library=static'])
|
||||
backend_reqs_private += 'idun-1.0'
|
||||
if idun_dep.type_name() == 'internal'
|
||||
idun_sp = subproject('idun')
|
||||
idun_libs += idun_sp.get_variable('idun')
|
||||
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'
|
||||
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()
|
||||
@@ -757,14 +773,20 @@ if host_os_family == 'linux' and have_local_backend
|
||||
backend_reqs_private += 'libbpf'
|
||||
endif
|
||||
|
||||
if host_os_family in ['darwin', 'freebsd'] or (host_os_family == 'linux' and cc.has_function('openpty', prefix: '#include <pty.h>'))
|
||||
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>'))
|
||||
vala_flags += '--define=HAVE_OPENPTY'
|
||||
endif
|
||||
|
||||
if host_os == 'android'
|
||||
termux_elf_cleaner = find_program('termux-elf-cleaner', native: true)
|
||||
libselinux_dep = dependency('libselinux', version: '>=3.0', default_options: ['default_library=static'])
|
||||
libsepol_dep = dependency('libsepol', version: '>=3.0', default_options: ['default_library=static'])
|
||||
selinux_options = [
|
||||
'default_library=static',
|
||||
'regex=disabled',
|
||||
]
|
||||
libselinux_dep = dependency('libselinux', version: '>=3.0', default_options: selinux_options)
|
||||
libsepol_dep = dependency('libsepol', version: '>=3.0', default_options: selinux_options)
|
||||
backend_deps_private += [libselinux_dep, libsepol_dep]
|
||||
backend_reqs_private += ['libselinux', 'libsepol']
|
||||
else
|
||||
@@ -886,9 +908,12 @@ endif
|
||||
if host_os in ['ios', 'tvos']
|
||||
backend_libs_private += ['-Wl,-framework,CoreGraphics', '-Wl,-framework,UIKit']
|
||||
endif
|
||||
if host_os_family == 'freebsd'
|
||||
if host_os_family == 'freebsd' and not host_is_prospero
|
||||
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'
|
||||
@@ -955,7 +980,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') ? 'executable-no-exports-freebsd.version' : 'executable-no-exports.version'
|
||||
script_name = (host_os == 'freebsd' and not host_is_prospero) ? '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]
|
||||
@@ -965,12 +990,13 @@ compile_js_agent = files('tools' / 'compile-js-agent.py')
|
||||
|
||||
mapper_opt = get_option('mapper')
|
||||
if mapper_opt.auto()
|
||||
have_mapper = host_os_family == 'darwin'
|
||||
have_mapper = host_os_family in ['windows', 'darwin', 'linux']
|
||||
else
|
||||
have_mapper = mapper_opt.enabled()
|
||||
endif
|
||||
if have_mapper
|
||||
cdata.set('HAVE_MAPPER', 1)
|
||||
vala_flags += '--define=HAVE_MAPPER'
|
||||
endif
|
||||
|
||||
configure_file(
|
||||
|
||||
+1
-1
Submodule releng updated: 963fe3826f...164a6a4117.
@@ -359,5 +359,11 @@
|
||||
<true/>
|
||||
<key>com.apple.private.amfi.can-execute-cdhash</key>
|
||||
<true/>
|
||||
<key>com.apple.private.FairPlayIOKitUserClient.access</key>
|
||||
<true/>
|
||||
<key>com.apple.security.iokit-user-client-class</key>
|
||||
<array>
|
||||
<string>com_apple_driver_FairPlayIOKitUserClient</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
+1
-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') ? 'frida-server-freebsd.version' : 'frida-server.version'
|
||||
symscript = (host_os == 'freebsd' and not host_is_prospero) ? 'frida-server-freebsd.version' : 'frida-server.version'
|
||||
extra_link_args += ['-Wl,--version-script,' + meson.current_source_dir() / symscript]
|
||||
extra_link_depends += [symscript]
|
||||
endif
|
||||
|
||||
@@ -29,6 +29,8 @@ 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
|
||||
@@ -137,6 +139,13 @@ 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;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida.Server {
|
||||
private static Application application;
|
||||
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
[CCode (gir_namespace = "FridaDroidy", gir_version = "1.0")]
|
||||
namespace Frida.Droidy {
|
||||
public sealed class DeviceTracker : Object {
|
||||
public signal void device_attached (DeviceDetails details);
|
||||
using Efni;
|
||||
|
||||
[CCode (gir_namespace = "FridaAdb", gir_version = "1.0")]
|
||||
namespace Frida {
|
||||
public sealed class AdbDeviceTracker : Object {
|
||||
public signal void device_attached (AdbDeviceDetails details);
|
||||
public signal void device_detached (string serial);
|
||||
|
||||
private Client? client;
|
||||
private AdbClient? client;
|
||||
private Gee.HashMap<string, DeviceEntry> devices = new Gee.HashMap<string, DeviceEntry> ();
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
public async void open (Cancellable? cancellable = null) throws Error, IOError {
|
||||
client = yield Client.open (cancellable);
|
||||
client = yield AdbClient.open (cancellable);
|
||||
client.message.connect (on_message);
|
||||
|
||||
try {
|
||||
@@ -20,7 +22,7 @@ namespace Frida.Droidy {
|
||||
client.message.disconnect (on_message);
|
||||
client = null;
|
||||
|
||||
client = yield Client.open (cancellable);
|
||||
client = yield AdbClient.open (cancellable);
|
||||
var devices_encoded = yield client.request_data ("host:track-devices", cancellable);
|
||||
yield update_devices (devices_encoded, cancellable);
|
||||
}
|
||||
@@ -117,12 +119,12 @@ namespace Frida.Droidy {
|
||||
var still_attached = devices.has_key (entry.serial);
|
||||
if (still_attached) {
|
||||
entry.announced = true;
|
||||
device_attached (new DeviceDetails (entry.serial, entry.name));
|
||||
device_attached (new AdbDeviceDetails (entry.serial, entry.name));
|
||||
}
|
||||
}
|
||||
|
||||
private async string detect_name (string device_serial, Cancellable? cancellable) throws Error, IOError {
|
||||
var output = yield ShellCommand.run ("getprop ro.product.model", device_serial, cancellable);
|
||||
var output = yield AdbShellCommand.run ("getprop ro.product.model", device_serial, cancellable);
|
||||
return output.chomp ();
|
||||
}
|
||||
|
||||
@@ -149,7 +151,7 @@ namespace Frida.Droidy {
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class DeviceDetails : Object {
|
||||
public sealed class AdbDeviceDetails : Object {
|
||||
public string serial {
|
||||
get;
|
||||
construct;
|
||||
@@ -160,16 +162,16 @@ namespace Frida.Droidy {
|
||||
construct;
|
||||
}
|
||||
|
||||
public DeviceDetails (string serial, string name) {
|
||||
public AdbDeviceDetails (string serial, string name) {
|
||||
Object (serial: serial, name: name);
|
||||
}
|
||||
}
|
||||
|
||||
namespace ShellCommand {
|
||||
namespace AdbShellCommand {
|
||||
private const int CHUNK_SIZE = 4096;
|
||||
|
||||
public static async string run (string command, string device_serial, Cancellable? cancellable = null) throws Error, IOError {
|
||||
var client = yield Client.open (cancellable);
|
||||
var client = yield AdbClient.open (cancellable);
|
||||
|
||||
try {
|
||||
yield client.request ("host:transport:" + device_serial, cancellable);
|
||||
@@ -205,8 +207,8 @@ namespace Frida.Droidy {
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ShellSession : Object {
|
||||
public signal void output (StdioPipe pipe, Bytes bytes);
|
||||
public sealed class AdbShellSession : Object {
|
||||
public signal void output (AdbStdioPipe pipe, Bytes bytes);
|
||||
public signal void closed ();
|
||||
|
||||
private IOStream stream;
|
||||
@@ -232,7 +234,7 @@ namespace Frida.Droidy {
|
||||
public async void open (string device_serial, Cancellable? cancellable = null) throws Error, IOError {
|
||||
assert (state == CLOSED);
|
||||
|
||||
var client = yield Client.open (cancellable);
|
||||
var client = yield AdbClient.open (cancellable);
|
||||
|
||||
try {
|
||||
yield client.request ("host:transport:" + device_serial, cancellable);
|
||||
@@ -285,7 +287,7 @@ namespace Frida.Droidy {
|
||||
return result.stdout_text;
|
||||
}
|
||||
|
||||
public async ShellCommandResult run (string command, Cancellable? cancellable) throws Error, IOError {
|
||||
public async AdbShellCommandResult run (string command, Cancellable? cancellable) throws Error, IOError {
|
||||
if (state == CLOSED)
|
||||
throw new Error.INVALID_OPERATION ("Shell session is closed");
|
||||
|
||||
@@ -524,7 +526,7 @@ namespace Frida.Droidy {
|
||||
public string command;
|
||||
private SourceFunc? handler;
|
||||
|
||||
public ShellCommandResult? result;
|
||||
public AdbShellCommandResult? result;
|
||||
public GLib.Error? error;
|
||||
|
||||
public ByteArray stdout_buffer = new ByteArray ();
|
||||
@@ -550,7 +552,7 @@ namespace Frida.Droidy {
|
||||
Bytes stdout_bytes = new Bytes.take ((owned) stdout_terminated);
|
||||
Bytes stderr_bytes = new Bytes.take ((owned) stderr_terminated);
|
||||
|
||||
result = new ShellCommandResult (status, stdout_bytes, stderr_bytes);
|
||||
result = new AdbShellCommandResult (status, stdout_bytes, stderr_bytes);
|
||||
|
||||
handler ();
|
||||
handler = null;
|
||||
@@ -589,12 +591,12 @@ namespace Frida.Droidy {
|
||||
}
|
||||
}
|
||||
|
||||
public enum StdioPipe {
|
||||
public enum AdbStdioPipe {
|
||||
STDOUT,
|
||||
STDERR
|
||||
}
|
||||
|
||||
public sealed class ShellCommandResult : Object {
|
||||
public sealed class AdbShellCommandResult : Object {
|
||||
public uint8 status {
|
||||
get;
|
||||
construct;
|
||||
@@ -622,7 +624,7 @@ namespace Frida.Droidy {
|
||||
construct;
|
||||
}
|
||||
|
||||
public ShellCommandResult (uint8 status, Bytes stdout_bytes, Bytes stderr_bytes) {
|
||||
public AdbShellCommandResult (uint8 status, Bytes stdout_bytes, Bytes stderr_bytes) {
|
||||
Object (
|
||||
status: status,
|
||||
stdout_bytes: stdout_bytes,
|
||||
@@ -631,12 +633,12 @@ namespace Frida.Droidy {
|
||||
}
|
||||
}
|
||||
|
||||
namespace FileSync {
|
||||
namespace AdbFileSync {
|
||||
private const size_t MAX_DATA_SIZE = 65536;
|
||||
|
||||
public static async void send (InputStream content, FileMetadata metadata, string remote_path, string device_serial,
|
||||
public static async void send (InputStream content, AdbFileMetadata metadata, string remote_path, string device_serial,
|
||||
Cancellable? cancellable = null) throws Error, IOError {
|
||||
var client = yield Client.open (cancellable);
|
||||
var client = yield AdbClient.open (cancellable);
|
||||
|
||||
try {
|
||||
yield client.request ("host:transport:" + device_serial, cancellable);
|
||||
@@ -686,7 +688,7 @@ namespace Frida.Droidy {
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class FileMetadata : Object {
|
||||
public sealed class AdbFileMetadata : Object {
|
||||
public uint32 mode {
|
||||
get;
|
||||
set;
|
||||
@@ -700,16 +702,16 @@ namespace Frida.Droidy {
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class JDWPTracker : Object {
|
||||
public sealed class AdbJDWPTracker : Object {
|
||||
public signal void debugger_attached (uint pid);
|
||||
public signal void debugger_detached (uint pid);
|
||||
|
||||
private Client? client;
|
||||
private AdbClient? client;
|
||||
private Gee.HashSet<uint> debugger_pids = new Gee.HashSet<uint> ();
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
|
||||
public async void open (string device_serial, Cancellable? cancellable = null) throws Error, IOError {
|
||||
client = yield Client.open (cancellable);
|
||||
client = yield AdbClient.open (cancellable);
|
||||
client.message.connect (on_message);
|
||||
|
||||
try {
|
||||
@@ -768,7 +770,7 @@ namespace Frida.Droidy {
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class Client : Object {
|
||||
public sealed class AdbClient : Object {
|
||||
public signal void closed ();
|
||||
public signal void message (string payload);
|
||||
|
||||
@@ -793,7 +795,7 @@ namespace Frida.Droidy {
|
||||
private const uint16 ADB_SERVER_DEFAULT_PORT = 5037;
|
||||
private const size_t MAX_MESSAGE_LENGTH = 65536;
|
||||
|
||||
public static async Client open (Cancellable? cancellable = null) throws Error, IOError {
|
||||
public static async AdbClient open (Cancellable? cancellable = null) throws Error, IOError {
|
||||
SocketConnectable? connectable = null;
|
||||
string? env_socket_address = Environment.get_variable ("ADB_SERVER_SOCKET");
|
||||
string? env_server_address = Environment.get_variable ("ANDROID_ADB_SERVER_ADDRESS");
|
||||
@@ -813,7 +815,7 @@ namespace Frida.Droidy {
|
||||
else if (socket_type == "local" || socket_type == "localfilesystem") {
|
||||
connectable = new UnixSocketAddress (socket_address);
|
||||
} else if (socket_type == "localabstract" && UnixSocketAddress.abstract_names_supported ()) {
|
||||
connectable = new UnixSocketAddress.with_type (socket_address, socket_address.length,
|
||||
connectable = new UnixSocketAddress.with_type (socket_address.to_utf8 (),
|
||||
ABSTRACT_PADDED);
|
||||
}
|
||||
#endif
|
||||
@@ -847,10 +849,10 @@ namespace Frida.Droidy {
|
||||
throw new Error.NOT_SUPPORTED ("%s", e.message);
|
||||
}
|
||||
|
||||
return new Client (stream);
|
||||
return new AdbClient (stream);
|
||||
}
|
||||
|
||||
public Client (IOStream stream) {
|
||||
public AdbClient (IOStream stream) {
|
||||
Object (stream: stream);
|
||||
}
|
||||
|
||||
@@ -896,10 +898,10 @@ namespace Frida.Droidy {
|
||||
yield request_with_type (message, RequestType.PROTOCOL_CHANGE, cancellable);
|
||||
}
|
||||
|
||||
public async SyncSession request_sync_session (Cancellable? cancellable = null) throws Error, IOError {
|
||||
public async AdbSyncSession request_sync_session (Cancellable? cancellable = null) throws Error, IOError {
|
||||
yield request ("sync:", cancellable);
|
||||
|
||||
var session = new SyncSession (this);
|
||||
var session = new AdbSyncSession (this);
|
||||
|
||||
PendingResponse pending = null;
|
||||
pending = new PendingResponse (RequestType.SYNC, () => {
|
||||
@@ -1154,15 +1156,15 @@ namespace Frida.Droidy {
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SyncSession : Object {
|
||||
public Client client {
|
||||
public sealed class AdbSyncSession : Object {
|
||||
public AdbClient client {
|
||||
get;
|
||||
construct;
|
||||
}
|
||||
|
||||
private Promise<bool> io_request = new Promise<bool> ();
|
||||
|
||||
public SyncSession (Client client) {
|
||||
public AdbSyncSession (AdbClient client) {
|
||||
Object (client: client);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public sealed class AgentContainer : Object, AgentSessionProvider {
|
||||
public DBusConnection connection {
|
||||
@@ -9,9 +11,7 @@ namespace Frida {
|
||||
[CCode (has_target = false)]
|
||||
private delegate void AgentMainFunc (string data, ref UnloadPolicy unload_policy, void * injector_state);
|
||||
private AgentMainFunc main_impl;
|
||||
#if LINUX
|
||||
private FileDescriptor agent_ctrlfd_for_peer;
|
||||
#else
|
||||
#if !LINUX
|
||||
private PipeTransport transport;
|
||||
#endif
|
||||
private string transport_address;
|
||||
@@ -38,8 +38,7 @@ namespace Frida {
|
||||
if (Posix.socketpair (Posix.AF_UNIX, Posix.SOCK_STREAM, 0, agent_ctrlfds) != 0)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to allocate socketpair");
|
||||
var agent_ctrlfd = new FileDescriptor (agent_ctrlfds[0]);
|
||||
container.agent_ctrlfd_for_peer = new FileDescriptor (agent_ctrlfds[1]);
|
||||
container.transport_address = "";
|
||||
container.transport_address = "socket:%d".printf (agent_ctrlfds[1]);
|
||||
|
||||
try {
|
||||
Socket socket = new Socket.from_fd (agent_ctrlfd.handle);
|
||||
@@ -114,18 +113,10 @@ namespace Frida {
|
||||
|
||||
private bool run () {
|
||||
UnloadPolicy unload_policy = IMMEDIATE;
|
||||
void * injector_state = null;
|
||||
|
||||
#if LINUX
|
||||
var s = LinuxInjectorState ();
|
||||
s.frida_ctrlfd = -1;
|
||||
s.agent_ctrlfd = agent_ctrlfd_for_peer.steal ();
|
||||
injector_state = &s;
|
||||
#endif
|
||||
|
||||
string agent_parameters = transport_address + "|exit-monitor:off|thread-suspend-monitor:off";
|
||||
|
||||
main_impl (agent_parameters, ref unload_policy, injector_state);
|
||||
main_impl (agent_parameters, ref unload_policy, null);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using Efni;
|
||||
|
||||
namespace Frida {
|
||||
public sealed class AndroidHelperClient : Object {
|
||||
public AndroidHelperTransport transport {
|
||||
@@ -5,10 +7,92 @@ 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 ();
|
||||
@@ -408,13 +492,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,7 +18,6 @@ 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;
|
||||
@@ -645,7 +644,7 @@ public class HelperBackend {
|
||||
"Unable to find application with identifier '" + pkgName + "'" +
|
||||
((uid != 0) ? " belonging to uid " + uid : ""));
|
||||
} catch (Throwable e) {
|
||||
return error("NOT_SUPPORTED", e.toString());
|
||||
return error("NOT_SUPPORTED", describe(e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,10 +696,13 @@ public class HelperBackend {
|
||||
intent.setClassName(pkgName, activity);
|
||||
}
|
||||
|
||||
if (mStartActivityAsUser != null)
|
||||
startActivityViaAtm(intent, uid, pkgName);
|
||||
else
|
||||
startActivityViaAm(intent, uid, pkgName);
|
||||
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 + ")");
|
||||
|
||||
return okVoid();
|
||||
} catch (NameNotFoundException e) {
|
||||
@@ -708,11 +710,18 @@ public class HelperBackend {
|
||||
"Unable to find application with identifier '" + pkgName + "'" +
|
||||
((uid != 0) ? " belonging to uid " + uid : ""));
|
||||
} catch (Throwable e) {
|
||||
return error("NOT_SUPPORTED", e.toString());
|
||||
return error("NOT_SUPPORTED", describe(e));
|
||||
}
|
||||
}
|
||||
|
||||
private void startActivityViaAtm(Intent intent, int uid, String callingPackage) throws Exception {
|
||||
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 {
|
||||
if (mStartActivityAsUser == null)
|
||||
throw new UnsupportedOperationException("startActivityAsUser unavailable");
|
||||
|
||||
@@ -729,10 +738,11 @@ public class HelperBackend {
|
||||
args[mStartActivityCallingPackageIndex] = callingPackage;
|
||||
args[mStartActivityUserIdIndex] = (uid != 0) ? uid : 0;
|
||||
|
||||
mStartActivityAsUser.invoke(mActivityTaskManager, args);
|
||||
Object result = mStartActivityAsUser.invoke(mActivityTaskManager, args);
|
||||
return (result instanceof Integer) ? (Integer) result : 0;
|
||||
}
|
||||
|
||||
private void startActivityViaAm(Intent intent, int uid, String callingPackage) throws Exception {
|
||||
private int startActivityViaAm(Intent intent, int uid, String callingPackage) throws Exception {
|
||||
if (mStartActivityLegacy == null)
|
||||
throw new UnsupportedOperationException("legacy startActivity unavailable");
|
||||
|
||||
@@ -753,7 +763,8 @@ public class HelperBackend {
|
||||
if (mLegacyStartUserIdIndex != -1)
|
||||
args[mLegacyStartUserIdIndex] = (uid != 0) ? uid : 0;
|
||||
|
||||
mStartActivityLegacy.invoke(mActivityManagerService, args);
|
||||
Object result = mStartActivityLegacy.invoke(mActivityManagerService, args);
|
||||
return (result instanceof Integer) ? (Integer) result : 0;
|
||||
}
|
||||
|
||||
private JSONArray sendBroadcast(JSONArray request) throws JSONException {
|
||||
@@ -782,7 +793,7 @@ public class HelperBackend {
|
||||
"Unable to find application with identifier '" + pkgName + "'" +
|
||||
((uid != 0) ? " belonging to uid " + uid : ""));
|
||||
} catch (Throwable e) {
|
||||
return error("NOT_SUPPORTED", e.toString());
|
||||
return error("NOT_SUPPORTED", describe(e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -810,7 +821,7 @@ public class HelperBackend {
|
||||
"Unable to find application with identifier '" + pkgName + "'" +
|
||||
((uid != 0) ? " belonging to uid " + uid : ""));
|
||||
} catch (Throwable e) {
|
||||
return error("NOT_SUPPORTED", e.toString());
|
||||
return error("NOT_SUPPORTED", describe(e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -837,7 +848,7 @@ public class HelperBackend {
|
||||
|
||||
return okBoolean(false);
|
||||
} catch (Throwable e) {
|
||||
return error("NOT_SUPPORTED", e.toString());
|
||||
return error("NOT_SUPPORTED", describe(e));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -869,6 +880,17 @@ 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()) {
|
||||
@@ -959,10 +981,10 @@ public class HelperBackend {
|
||||
icon.setBounds(0, 0, width, height);
|
||||
icon.draw(canvas);
|
||||
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
bitmap.compress(CompressFormat.PNG, 100, new Base64OutputStream(output, Base64.NO_WRAP));
|
||||
ByteArrayOutputStream png = new ByteArrayOutputStream();
|
||||
bitmap.compress(CompressFormat.PNG, 100, png);
|
||||
|
||||
return output.toString();
|
||||
return Base64.encodeToString(png.toByteArray(), Base64.NO_WRAP);
|
||||
}
|
||||
|
||||
private Map<String, List<RunningAppProcessInfo>> getAppProcesses() {
|
||||
|
||||
@@ -32,6 +32,8 @@ OBJECT_TYPE_PATTERN = re.compile(r"\bpublic\s+(sealed |abstract )?(class|interfa
|
||||
|
||||
TOPLEVEL_NAMES = [
|
||||
"frida.vala",
|
||||
"linux-kernel-image.vala",
|
||||
"xnu-kernelcache.vala",
|
||||
"package-manager.vala",
|
||||
"control-service.vala",
|
||||
"portal-service.vala",
|
||||
|
||||
+1
-1
@@ -158,7 +158,7 @@ if is_shared
|
||||
else
|
||||
pkg_kwargs += {
|
||||
'libraries': core_public_lib,
|
||||
'requires_private': ['gmodule-2.0', 'gee-0.8', 'libsoup-3.0', 'frida-gum-1.0', 'frida-gumjs-inspector-1.0', 'libbrotlidec'] + backend_reqs_private,
|
||||
'requires_private': ['gmodule-2.0', 'gee-0.8', 'libsoup-3.0', 'efni-1.0', 'frida-gum-1.0', 'frida-gumjs-inspector-1.0', 'libbrotlidec'] + backend_reqs_private,
|
||||
'libraries_private': backend_libs_private,
|
||||
}
|
||||
endif
|
||||
|
||||
+364
-139
@@ -1,6 +1,9 @@
|
||||
using Efni;
|
||||
|
||||
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
|
||||
namespace Frida.Barebone {
|
||||
public sealed class AgentConnection : Object, AsyncInitable {
|
||||
public signal void progress (string status, double fraction);
|
||||
public signal void script_message (uint pid, AgentScriptId id, string json, Bytes? data);
|
||||
|
||||
private Cancellable io_cancellable = new Cancellable ();
|
||||
@@ -14,54 +17,68 @@ namespace Frida.Barebone {
|
||||
private uint pointer_size;
|
||||
|
||||
private BareboneInjectedAgentConfig agent_config;
|
||||
private BareboneVsockTransportConfig? vsock_transport;
|
||||
private string? hostlink_socket_path;
|
||||
private bool pipe_listener_wanted;
|
||||
private SocketService? pipe_service;
|
||||
private IOStream? pipe_stream;
|
||||
private SourceFunc? pipe_accept_handler;
|
||||
private BareboneImageConfig? image_config;
|
||||
private BareboneKernelKind kernel_kind;
|
||||
private KernelRelocation? relocation;
|
||||
private uint64 kernel_base;
|
||||
private Machine machine;
|
||||
private Allocator allocator;
|
||||
private Allocator data_allocator;
|
||||
private Gee.List<ModuleInfo> kernel_modules;
|
||||
private Gee.List<SymbolInfo> kernel_symbols;
|
||||
private Gee.Map<string, uint64?> kernel_notes;
|
||||
|
||||
private KernelFlavor flavor;
|
||||
private Allocation elf_allocation;
|
||||
private uint64 left_flag;
|
||||
private uint64 fault_record;
|
||||
private Gee.List<AgentSymbol> agent_symbols = new Gee.ArrayList<AgentSymbol> ();
|
||||
private Gee.Map<string, SymbolInfo> resolved_symbols;
|
||||
private Allocation config_allocation;
|
||||
private Allocation? agent_stack;
|
||||
|
||||
private Gee.Map<uint16, Promise<Variant>> pending_requests = new Gee.HashMap<uint16, Promise<Variant>> ();
|
||||
private Efni.Promise<bool>? listening;
|
||||
|
||||
private Gee.Map<uint16, Efni.Promise<Variant>> pending_requests = new Gee.HashMap<uint16, Efni.Promise<Variant>> ();
|
||||
private uint16 next_request_id = 1;
|
||||
|
||||
private const size_t AGENT_STACK_SIZE = 512 * 1024;
|
||||
private const int COMMAND_TIMEOUT_MS = 25000;
|
||||
private const uint INJECT_MAX_ATTEMPTS = 100;
|
||||
private const uint INJECT_POLL_INTERVAL_MS = 100;
|
||||
private const uint LEAVE_MAX_ATTEMPTS = 40;
|
||||
private const uint LEAVE_INTERVAL_MS = 50;
|
||||
private const size_t FAULT_RECORD_SIZE = 32;
|
||||
private const uint GREETING_TIMEOUT_MS = 20000;
|
||||
|
||||
public static async AgentConnection open (BareboneInjectedAgentConfig agent_config, BareboneImageConfig? image_config,
|
||||
public AgentConnection (BareboneInjectedAgentConfig agent_config, BareboneImageConfig? image_config,
|
||||
BareboneKernelKind kernel_kind, KernelRelocation? relocation, uint64 kernel_base, Machine machine,
|
||||
Allocator allocator, Gee.List<ModuleInfo> kernel_modules, Gee.List<SymbolInfo> kernel_symbols,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
var connection = new AgentConnection () {
|
||||
agent_config = agent_config,
|
||||
image_config = image_config,
|
||||
kernel_kind = kernel_kind,
|
||||
relocation = relocation,
|
||||
kernel_base = kernel_base,
|
||||
machine = machine,
|
||||
allocator = allocator,
|
||||
kernel_modules = kernel_modules,
|
||||
kernel_symbols = kernel_symbols,
|
||||
};
|
||||
Allocator allocator, Allocator? data_allocator, Gee.List<ModuleInfo> kernel_modules,
|
||||
Gee.List<SymbolInfo> kernel_symbols, Gee.Map<string, uint64?> kernel_notes) {
|
||||
this.agent_config = agent_config;
|
||||
this.image_config = image_config;
|
||||
this.kernel_kind = kernel_kind;
|
||||
this.relocation = relocation;
|
||||
this.kernel_base = kernel_base;
|
||||
this.machine = machine;
|
||||
this.allocator = allocator;
|
||||
this.data_allocator = data_allocator ?? allocator;
|
||||
this.kernel_modules = kernel_modules;
|
||||
this.kernel_symbols = kernel_symbols;
|
||||
this.kernel_notes = kernel_notes;
|
||||
}
|
||||
|
||||
public async void open (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
try {
|
||||
yield connection.init_async (Priority.DEFAULT, cancellable);
|
||||
yield init_async (Priority.DEFAULT, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw_api_error (e);
|
||||
Efni.throw_api_error (e);
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,11 +86,8 @@ namespace Frida.Barebone {
|
||||
* transport with it, such as the Linux kernel module exposing /dev/frida.
|
||||
*/
|
||||
public static async AgentConnection open_resident (IOStream stream, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
var connection = new AgentConnection () {
|
||||
byte_order = ByteOrder.HOST,
|
||||
pointer_size = (uint) sizeof (void *),
|
||||
};
|
||||
throws Efni.Error, IOError {
|
||||
var connection = new AgentConnection.resident ();
|
||||
|
||||
connection.adopt_hostlink_streams (stream);
|
||||
connection.process_incoming_messages.begin ();
|
||||
@@ -81,16 +95,26 @@ namespace Frida.Barebone {
|
||||
return connection;
|
||||
}
|
||||
|
||||
private AgentConnection.resident () {
|
||||
byte_order = ByteOrder.HOST;
|
||||
pointer_size = (uint) sizeof (void *);
|
||||
}
|
||||
|
||||
private const uint8 TRANSPORT_KIND_VIRTIO = 0;
|
||||
private const uint8 TRANSPORT_KIND_VSOCK = 1;
|
||||
private const uint8 TRANSPORT_KIND_VIRTIO_PCI = 2;
|
||||
private const uint8 TRANSPORT_KIND_SERIAL = 3;
|
||||
private const uint8 TRANSPORT_KIND_PIPE_VSOCK = 4;
|
||||
private const uint8 TRANSPORT_KIND_PIPE_GOLDFISH = 5;
|
||||
|
||||
private async bool init_async (int io_priority, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
progress ("Preparing the agent", 0.0);
|
||||
|
||||
private async bool init_async (int io_priority, Cancellable? cancellable) throws Error, IOError {
|
||||
var transport_tag = yield resolve_transport (cancellable);
|
||||
|
||||
var gdb = machine.gdb;
|
||||
byte_order = gdb.byte_order;
|
||||
pointer_size = gdb.pointer_size;
|
||||
var debugger = machine.debugger;
|
||||
byte_order = debugger.byte_order;
|
||||
pointer_size = debugger.pointer_size;
|
||||
|
||||
// Only a kernel shipped as an image carries its symbols in it; the rest name theirs
|
||||
// elsewhere, and the layout is collected before this.
|
||||
@@ -99,7 +123,7 @@ namespace Frida.Barebone {
|
||||
Layout layout;
|
||||
uint64 preferred_base = 0;
|
||||
if (image_carries_symbols) {
|
||||
var payload = yield Img4.parse_file (File.new_for_path (image_config.file), cancellable);
|
||||
var payload = Img4.parse (((BareboneXnuKernelcacheConfig) image_config).kernelcache.blob);
|
||||
var kernelcache = Layout.parse_kernelcache (payload.data);
|
||||
preferred_base = kernelcache.preferred_address;
|
||||
if (relocation != null) {
|
||||
@@ -151,11 +175,11 @@ namespace Frida.Barebone {
|
||||
KernelFlavor flavor;
|
||||
if (kind == XNU) {
|
||||
if (kernel_base == 0)
|
||||
throw new Error.NOT_SUPPORTED ("Missing kernel_base");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Missing kernel_base");
|
||||
flavor = new XnuKernelFlavor (machine, kernel_base, symbols);
|
||||
} else if (kind == LINUX) {
|
||||
if (kernel_base == 0)
|
||||
throw new Error.NOT_SUPPORTED ("Missing kernel_base");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Missing kernel_base");
|
||||
flavor = new LinuxKernelFlavor (machine, kernel_base, symbols);
|
||||
} else if (kind == WIN9X) {
|
||||
flavor = new Win9xKernelFlavor (machine, symbols);
|
||||
@@ -171,10 +195,10 @@ namespace Frida.Barebone {
|
||||
try {
|
||||
elf = new Gum.ElfModule.from_blob (agent_config.image);
|
||||
} catch (Gum.Error e) {
|
||||
throw new Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
throw new Efni.Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
}
|
||||
|
||||
var raw_elf = gdb.make_buffer (new Bytes (elf.get_file_data ()));
|
||||
var raw_elf = debugger.make_buffer (new Bytes (elf.get_file_data ()));
|
||||
// Slots whose symbol is absent on this kernel are left zero; the agent's
|
||||
// xnu.rs uses Option<fn> and falls back across per-kernel name variants.
|
||||
elf.enumerate_symbols (s => {
|
||||
@@ -184,7 +208,8 @@ namespace Frida.Barebone {
|
||||
SymbolInfo? info = symbols[name];
|
||||
if (info != null) {
|
||||
size_t file_offset = (size_t) (sect.offset + (s.address - sect.address));
|
||||
raw_elf.write_pointer (file_offset, kernel_base + info.offset);
|
||||
uint64 value = info.per_cpu ? info.offset : kernel_base + info.offset;
|
||||
raw_elf.write_pointer (file_offset, value);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -194,11 +219,14 @@ namespace Frida.Barebone {
|
||||
|
||||
this.flavor = flavor;
|
||||
|
||||
progress ("Waiting for the target to be ready", 0.1);
|
||||
yield flavor.prepare (cancellable);
|
||||
|
||||
size_t page_size = yield machine.query_page_size (cancellable);
|
||||
|
||||
elf_allocation = yield inject_elf (elf, raw_elf.bytes, page_size, machine, allocator, cancellable);
|
||||
progress ("Injecting the agent", 0.2);
|
||||
elf_allocation = yield inject_elf (elf, raw_elf.bytes, page_size, machine, allocator,
|
||||
uploaded => progress ("Injecting the agent", 0.2 + 0.5 * uploaded), cancellable);
|
||||
|
||||
uint64 start_address = 0;
|
||||
uint64 base_va = elf_allocation.virtual_address;
|
||||
@@ -207,10 +235,14 @@ namespace Frida.Barebone {
|
||||
start_address = base_va + e.address;
|
||||
else if (e.name == "frida_agent_left")
|
||||
left_flag = base_va + e.address;
|
||||
else if (e.name == "frida_agent_fault")
|
||||
fault_record = base_va + e.address;
|
||||
if (e.type == FUNC && e.size != 0)
|
||||
agent_symbols.add (new AgentSymbol (e.name, base_va + e.address, e.size));
|
||||
return true;
|
||||
});
|
||||
if (start_address == 0)
|
||||
throw new Error.INVALID_ARGUMENT ("Invalid agent: no _start symbol found");
|
||||
throw new Efni.Error.INVALID_ARGUMENT ("Invalid agent: no _start symbol found");
|
||||
|
||||
XnuLayout? xnu_layout = null;
|
||||
bool xnu_reads_its_own_processes = symbols.has_key ("proc_pid")
|
||||
@@ -252,6 +284,8 @@ namespace Frida.Barebone {
|
||||
// What a kernel does not say about itself and the agent cannot work out from where it
|
||||
// runs: the host looks once and passes on what it found.
|
||||
config_builder.open (new VariantType ("a(st)"));
|
||||
foreach (var e in kernel_notes.entries)
|
||||
config_builder.add ("(st)", e.key, e.value);
|
||||
if (xnu_layout != null) {
|
||||
config_builder.add ("(st)", "process.number", xnu_layout.number_offset);
|
||||
config_builder.add ("(st)", "process.name", xnu_layout.name_offset);
|
||||
@@ -266,28 +300,47 @@ namespace Frida.Barebone {
|
||||
config_builder.close ();
|
||||
|
||||
var config_blob = config_builder.end ().get_data_as_bytes ();
|
||||
config_allocation = yield allocator.allocate (config_blob.get_size (), 8, cancellable);
|
||||
config_allocation = yield data_allocator.allocate (config_blob.get_size (), 8, cancellable);
|
||||
|
||||
yield machine.write_virtual (config_allocation.virtual_address, config_blob.get_data (),
|
||||
cancellable);
|
||||
|
||||
yield machine.protect_pages (config_allocation.virtual_address, config_allocation.size, READ | WRITE,
|
||||
cancellable);
|
||||
yield protect_unless_already (machine, data_allocator, config_allocation.virtual_address,
|
||||
config_allocation.size, READ | WRITE, cancellable);
|
||||
|
||||
var ia32 = machine as IA32Machine;
|
||||
uint kernel_arguments = (ia32 != null) ? ia32.arguments_in_registers : 0;
|
||||
if (ia32 != null)
|
||||
ia32.arguments_in_registers = 0;
|
||||
|
||||
yield machine.invoke (start_address, {
|
||||
config_allocation.virtual_address,
|
||||
config_allocation.size
|
||||
},
|
||||
cancellable);
|
||||
var x64 = machine as X64Machine;
|
||||
if (x64 != null) {
|
||||
agent_stack = yield allocator.allocate (AGENT_STACK_SIZE, 16, cancellable);
|
||||
x64.call_stack = agent_stack.virtual_address + AGENT_STACK_SIZE;
|
||||
}
|
||||
|
||||
progress ("Starting the agent", 0.75);
|
||||
var start_gdb = machine.debugger as GDB.Client;
|
||||
if (start_gdb != null)
|
||||
start_gdb.suppress_solo = true;
|
||||
try {
|
||||
yield machine.invoke (start_address, {
|
||||
config_allocation.virtual_address,
|
||||
config_allocation.size
|
||||
},
|
||||
cancellable);
|
||||
} finally {
|
||||
if (start_gdb != null)
|
||||
start_gdb.suppress_solo = false;
|
||||
}
|
||||
|
||||
if (x64 != null)
|
||||
x64.call_stack = 0;
|
||||
|
||||
if (ia32 != null)
|
||||
ia32.arguments_in_registers = kernel_arguments;
|
||||
|
||||
progress ("Connecting to the agent", 0.9);
|
||||
yield flavor.settle (cancellable);
|
||||
if (qmp != null)
|
||||
yield qmp.wait_until_hostlink_is_open (cancellable);
|
||||
@@ -295,31 +348,125 @@ namespace Frida.Barebone {
|
||||
|
||||
process_incoming_messages.begin ();
|
||||
|
||||
yield wait_to_be_greeted (cancellable);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Variant resolve_transport (Cancellable? cancellable) throws Error, IOError {
|
||||
private async void wait_to_be_greeted (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (listening == null)
|
||||
return;
|
||||
|
||||
var timeout = new TimeoutSource (GREETING_TIMEOUT_MS);
|
||||
timeout.set_callback (() => {
|
||||
if (!listening.future.ready)
|
||||
listening.reject (new Efni.Error.TIMED_OUT ("The agent never announced itself"));
|
||||
return false;
|
||||
});
|
||||
timeout.attach (MainContext.get_thread_default ());
|
||||
|
||||
try {
|
||||
yield listening.future.wait_async (cancellable);
|
||||
} finally {
|
||||
timeout.destroy ();
|
||||
}
|
||||
}
|
||||
|
||||
private async Variant resolve_transport (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (agent_config.transport is BareboneHostlinkTransportConfig)
|
||||
return yield connect_virtio_transport ((BareboneHostlinkTransportConfig) agent_config.transport, cancellable);
|
||||
if (agent_config.transport is BareboneVsockTransportConfig) {
|
||||
var config = (BareboneVsockTransportConfig) agent_config.transport;
|
||||
vsock_transport = config;
|
||||
hostlink_socket_path = config.socket_path;
|
||||
return new Variant.tuple ({
|
||||
new Variant.byte (TRANSPORT_KIND_VSOCK),
|
||||
new Variant.variant (new Variant.uint32 (config.port))
|
||||
});
|
||||
}
|
||||
throw new Error.NOT_SUPPORTED ("Unsupported transport config");
|
||||
if (agent_config.transport is BareboneSerialTransportConfig) {
|
||||
yield connect_serial_transport ((BareboneSerialTransportConfig) agent_config.transport,
|
||||
cancellable);
|
||||
return new Variant.tuple ({
|
||||
new Variant.byte (TRANSPORT_KIND_SERIAL),
|
||||
new Variant.variant (new Variant.uint32 (0))
|
||||
});
|
||||
}
|
||||
if (agent_config.transport is BareboneVsockPipeTransportConfig) {
|
||||
var config = (BareboneVsockPipeTransportConfig) agent_config.transport;
|
||||
pipe_listener_wanted = true;
|
||||
yield open_pipe_listener (config.socket_path, cancellable);
|
||||
return new Variant.tuple ({
|
||||
new Variant.byte (TRANSPORT_KIND_PIPE_VSOCK),
|
||||
new Variant.variant (new Variant.string (config.socket_path))
|
||||
});
|
||||
}
|
||||
if (agent_config.transport is BareboneGoldfishPipeTransportConfig) {
|
||||
var config = (BareboneGoldfishPipeTransportConfig) agent_config.transport;
|
||||
pipe_listener_wanted = true;
|
||||
yield open_pipe_listener (config.socket_path, cancellable);
|
||||
return new Variant.tuple ({
|
||||
new Variant.byte (TRANSPORT_KIND_PIPE_GOLDFISH),
|
||||
new Variant.variant (new Variant.string (config.socket_path))
|
||||
});
|
||||
}
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unsupported transport config");
|
||||
}
|
||||
|
||||
private async void establish_hostlink (Cancellable? cancellable) throws Error, IOError {
|
||||
if (vsock_transport == null)
|
||||
private async void connect_serial_transport (BareboneSerialTransportConfig config,
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
try {
|
||||
#if WINDOWS
|
||||
adopt_hostlink_streams (WindowsNamedPipe.open_client (config.path));
|
||||
#else
|
||||
var client = new SocketClient ();
|
||||
adopt_hostlink_streams (yield client.connect_async (new UnixSocketAddress (config.path),
|
||||
cancellable));
|
||||
#endif
|
||||
} catch (GLib.Error e) {
|
||||
throw new Efni.Error.TRANSPORT ("Unable to reach the serial port of the guest: %s", e.message);
|
||||
}
|
||||
|
||||
listening = new Efni.Promise<bool> ();
|
||||
}
|
||||
|
||||
private async void open_pipe_listener (string path, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
FileUtils.unlink (path);
|
||||
var service = new SocketService ();
|
||||
try {
|
||||
SocketAddress effective;
|
||||
service.add_address (new UnixSocketAddress (path), STREAM, DEFAULT, null, out effective);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Efni.Error.TRANSPORT ("Unable to listen on %s: %s", path, e.message);
|
||||
}
|
||||
service.incoming.connect ((connection) => {
|
||||
if (pipe_stream == null) {
|
||||
pipe_stream = connection;
|
||||
if (pipe_accept_handler != null) {
|
||||
var handler = (owned) pipe_accept_handler;
|
||||
pipe_accept_handler = null;
|
||||
handler ();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
service.start ();
|
||||
pipe_service = service;
|
||||
}
|
||||
|
||||
private async void establish_hostlink (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (pipe_listener_wanted) {
|
||||
while (pipe_stream == null) {
|
||||
pipe_accept_handler = establish_hostlink.callback;
|
||||
yield;
|
||||
}
|
||||
adopt_hostlink_streams (pipe_stream);
|
||||
return;
|
||||
}
|
||||
|
||||
if (hostlink_socket_path == null)
|
||||
return;
|
||||
|
||||
#if WINDOWS
|
||||
throw new Error.NOT_SUPPORTED ("Hostlink transport is not available on this OS");
|
||||
#else
|
||||
var address = new UnixSocketAddress (vsock_transport.socket_path);
|
||||
var address = new UnixSocketAddress (hostlink_socket_path);
|
||||
var client = new SocketClient ();
|
||||
while (true) {
|
||||
try {
|
||||
@@ -334,11 +481,10 @@ namespace Frida.Barebone {
|
||||
yield;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private async Variant connect_virtio_transport (BareboneHostlinkTransportConfig config, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var qmp = yield QmpClient.open (config.qmp, 0, cancellable);
|
||||
var link = yield qmp.open_hostlink (config.bus, cancellable);
|
||||
this.qmp = qmp;
|
||||
@@ -376,7 +522,7 @@ namespace Frida.Barebone {
|
||||
output = hostlink.get_output_stream ();
|
||||
}
|
||||
|
||||
private void rebase_layout (Layout layout, uint64 preferred_base, KernelRelocation reloc) throws Error {
|
||||
private void rebase_layout (Layout layout, uint64 preferred_base, KernelRelocation reloc) throws Efni.Error {
|
||||
foreach (var s in layout.symbols)
|
||||
s.offset = reloc.runtime_offset (preferred_base + s.offset);
|
||||
foreach (var m in layout.modules) {
|
||||
@@ -415,14 +561,14 @@ namespace Frida.Barebone {
|
||||
}
|
||||
}
|
||||
|
||||
private async void give_the_memory_back (Cancellable? cancellable) throws Error, IOError {
|
||||
private async void give_the_memory_back (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
var timeout = new TimeoutSource (LEAVE_INTERVAL_MS);
|
||||
timeout.set_callback (give_the_memory_back.callback);
|
||||
timeout.attach (MainContext.get_thread_default ());
|
||||
yield;
|
||||
timeout.destroy ();
|
||||
|
||||
yield machine.gdb.stop (cancellable);
|
||||
yield machine.debugger.stop (cancellable);
|
||||
|
||||
yield flavor.prepare (cancellable);
|
||||
|
||||
@@ -439,16 +585,16 @@ namespace Frida.Barebone {
|
||||
yield flavor.settle (cancellable);
|
||||
}
|
||||
|
||||
private async void wait_for_agent_to_leave (Cancellable? cancellable) throws Error, IOError {
|
||||
private async void wait_for_agent_to_leave (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (left_flag == 0)
|
||||
return;
|
||||
|
||||
var gdb = machine.gdb;
|
||||
var debugger = machine.debugger;
|
||||
for (uint attempt = 0; attempt != LEAVE_MAX_ATTEMPTS; attempt++) {
|
||||
yield gdb.stop (cancellable);
|
||||
var flag = gdb.make_buffer (yield gdb.read_byte_array (left_flag, 4, cancellable));
|
||||
yield debugger.stop (cancellable);
|
||||
var flag = debugger.make_buffer (yield debugger.read_byte_array (left_flag, 4, cancellable));
|
||||
bool left = flag.read_uint32 (0) != 0;
|
||||
yield gdb.continue (cancellable);
|
||||
yield debugger.resume (cancellable);
|
||||
if (left) {
|
||||
yield give_the_memory_back (cancellable);
|
||||
return;
|
||||
@@ -471,13 +617,13 @@ namespace Frida.Barebone {
|
||||
// same data. The process receives the code as it is, thus only the writable half needs a new
|
||||
// location, in memory that both sides can address.
|
||||
public async void place_winnt_user_agent (uint pid, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var response = yield execute_command (Command.PLACE_AGENT_IN_PROCESS,
|
||||
new Variant.uint32 (pid), cancellable);
|
||||
if (!response.is_of_type (VariantType.UINT32))
|
||||
throw new Error.PROTOCOL ("Invalid place_agent_in_process response format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid place_agent_in_process response format");
|
||||
if (response.get_uint32 () == 0)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to place the agent in the process");
|
||||
throw new Efni.Error.NOT_SUPPORTED ("Unable to place the agent in the process");
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -485,7 +631,7 @@ namespace Frida.Barebone {
|
||||
* copy reports having woken up in.
|
||||
*/
|
||||
public async uint inject_agent_into_process (uint pid, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
if (kernel_kind == WINNT) {
|
||||
yield place_winnt_user_agent (pid, cancellable);
|
||||
return yield start_winnt_agent_in_process (pid, cancellable);
|
||||
@@ -497,12 +643,12 @@ namespace Frida.Barebone {
|
||||
// The copy cannot answer from the thread that starts it. Thus ask again until it reports its
|
||||
// process id.
|
||||
private async uint start_winnt_agent_in_process (uint pid, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
for (uint attempt = 0; attempt != INJECT_MAX_ATTEMPTS; attempt++) {
|
||||
var response = yield execute_command (Command.START_AGENT_IN_PROCESS,
|
||||
new Variant.uint32 (pid), cancellable);
|
||||
if (!response.is_of_type (VariantType.UINT32))
|
||||
throw new Error.PROTOCOL ("Invalid start_agent_in_process response format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid start_agent_in_process response format");
|
||||
|
||||
uint reached = response.get_uint32 ();
|
||||
if (reached != 0)
|
||||
@@ -515,19 +661,19 @@ namespace Frida.Barebone {
|
||||
timeout.destroy ();
|
||||
}
|
||||
|
||||
throw new Error.TIMED_OUT ("Timed out while starting the agent in the process");
|
||||
throw new Efni.Error.TIMED_OUT ("Timed out while starting the agent in the process");
|
||||
}
|
||||
|
||||
public signal void spawn_added (uint pid, string command_line, uint holder_pid);
|
||||
|
||||
public async Application[] enumerate_applications (string[] identifiers, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var selected = new VariantBuilder (new VariantType ("as"));
|
||||
foreach (unowned string identifier in identifiers)
|
||||
selected.add ("s", identifier);
|
||||
var response = yield execute_command (Command.ENUMERATE_APPLICATIONS, selected.end (), cancellable);
|
||||
if (!response.check_format_string ("a(sss)", false))
|
||||
throw new Error.PROTOCOL ("Invalid enumerate_applications response format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid enumerate_applications response format");
|
||||
|
||||
var applications = new Application[response.n_children ()];
|
||||
for (size_t i = 0; i != applications.length; i++) {
|
||||
@@ -542,11 +688,11 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
public async Shortcut[] enumerate_shortcuts (uint helper_pid, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var response = yield execute_command (Command.ENUMERATE_SHORTCUTS,
|
||||
new Variant.boolean (false), cancellable, helper_pid);
|
||||
if (!response.check_format_string ("a(ssss)", false))
|
||||
throw new Error.PROTOCOL ("Invalid enumerate_shortcuts response format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid enumerate_shortcuts response format");
|
||||
|
||||
var shortcuts = new Shortcut[response.n_children ()];
|
||||
for (size_t i = 0; i != shortcuts.length; i++) {
|
||||
@@ -593,51 +739,59 @@ namespace Frida.Barebone {
|
||||
}
|
||||
}
|
||||
|
||||
public async void gate_spawns (bool on, Cancellable? cancellable) throws Error, IOError {
|
||||
public async void gate_spawns (bool on, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
yield execute_command (Command.GATE_SPAWNS, new Variant.boolean (on), cancellable);
|
||||
}
|
||||
|
||||
public async void arm_named_spawn (string name, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
yield execute_command (Command.ARM_NAMED_SPAWN, new Variant.string (name), cancellable);
|
||||
}
|
||||
|
||||
public async void disarm_named_spawn (string name, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
yield execute_command (Command.DISARM_NAMED_SPAWN, new Variant.string (name), cancellable);
|
||||
}
|
||||
|
||||
public async uint spawn_process (uint helper_pid, string command_line, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
return yield ask_for_a_spawn (helper_pid, new Variant.string (command_line), cancellable);
|
||||
}
|
||||
|
||||
// A kernel that starts programs itself takes the words as they are, since a line to be
|
||||
// split again is what Windows asks for and nothing else does.
|
||||
public async uint spawn_program (string[] words, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
return yield ask_for_a_spawn (0, new Variant.strv (words), cancellable);
|
||||
}
|
||||
|
||||
private async uint ask_for_a_spawn (uint helper_pid, Variant payload, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var response = yield execute_command (Command.SPAWN_PROCESS, payload, cancellable,
|
||||
helper_pid);
|
||||
if (!response.is_of_type (VariantType.UINT32))
|
||||
throw new Error.PROTOCOL ("Invalid spawn_process response format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid spawn_process response format");
|
||||
|
||||
return response.get_uint32 ();
|
||||
}
|
||||
|
||||
public async void resume_process (uint helper_pid, uint pid, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
yield execute_command (Command.RESUME_PROCESS, new Variant.uint32 (pid), cancellable,
|
||||
helper_pid);
|
||||
}
|
||||
|
||||
public async void detach_from_process (uint pid, Cancellable? cancellable) throws Error, IOError {
|
||||
public async void detach_from_process (uint pid, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
yield execute_command (Command.DETACH_FROM_PROCESS, new Variant.uint32 (pid), cancellable);
|
||||
}
|
||||
|
||||
public async uint inject_into_process (uint pid, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
// The agent cannot answer from the context that does the work. Thus it reports the result at
|
||||
// the next request.
|
||||
for (uint attempt = 0; attempt != INJECT_MAX_ATTEMPTS; attempt++) {
|
||||
var response = yield execute_command (Command.INJECT_INTO_PROCESS,
|
||||
new Variant.uint32 (pid), cancellable);
|
||||
if (!response.is_of_type (VariantType.UINT32))
|
||||
throw new Error.PROTOCOL ("Invalid inject_into_process response format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid inject_into_process response format");
|
||||
|
||||
uint reached = response.get_uint32 ();
|
||||
if (reached != 0)
|
||||
@@ -650,19 +804,19 @@ namespace Frida.Barebone {
|
||||
timeout.destroy ();
|
||||
}
|
||||
|
||||
throw new Error.TIMED_OUT ("Timed out while injecting into process");
|
||||
throw new Efni.Error.TIMED_OUT ("Timed out while injecting into process");
|
||||
}
|
||||
|
||||
public async HostProcessInfo[] enumerate_processes (Scope scope, uint[] pids, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
bool include_icons = scope == FULL;
|
||||
var selected = new VariantBuilder (new VariantType ("au"));
|
||||
foreach (uint pid in pids)
|
||||
selected.add ("u", pid);
|
||||
var request = new Variant.tuple ({ new Variant.boolean (include_icons), selected.end () });
|
||||
var response = yield execute_command (Command.ENUMERATE_PROCESSES, request, cancellable);
|
||||
if (!response.check_format_string ("a(usssaay)", false))
|
||||
throw new Error.PROTOCOL ("Invalid enumerate_processes response format");
|
||||
if (!response.check_format_string ("a(usssusuxaay)", false))
|
||||
throw new Efni.Error.PROTOCOL ("Invalid enumerate_processes response format");
|
||||
|
||||
var processes = new HostProcessInfo[response.n_children ()];
|
||||
for (size_t i = 0; i != processes.length; i++) {
|
||||
@@ -676,9 +830,21 @@ namespace Frida.Barebone {
|
||||
unowned string command_line = entry.get_child_value (2).get_string ();
|
||||
if (command_line != "")
|
||||
parameters["argv"] = argv_from_command_line (command_line);
|
||||
|
||||
parameters["uid"] = new Variant.uint32 (entry.get_child_value (4).get_uint32 ());
|
||||
|
||||
unowned string user = entry.get_child_value (5).get_string ();
|
||||
if (user != "")
|
||||
parameters["user"] = user;
|
||||
|
||||
parameters["ppid"] = new Variant.int64 (entry.get_child_value (6).get_uint32 ());
|
||||
|
||||
int64 started = entry.get_child_value (7).get_int64 ();
|
||||
if (started != 0)
|
||||
parameters["started"] = new DateTime.from_unix_utc (started).format_iso8601 ();
|
||||
}
|
||||
if (include_icons)
|
||||
parameters["icons"] = icons_from_resources (entry.get_child_value (4));
|
||||
parameters["icons"] = icons_from_resources (entry.get_child_value (8));
|
||||
|
||||
unowned string description = entry.get_child_value (3).get_string ();
|
||||
string name = (description != "") ? description : basename_of (path);
|
||||
@@ -852,30 +1018,30 @@ namespace Frida.Barebone {
|
||||
private const uint32 MAX_ICON_DIMENSION = 256;
|
||||
|
||||
public async AgentScriptId create_script (string source, uint destination, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var payload = new Variant ("s", source);
|
||||
var response = yield execute_command (Command.CREATE_SCRIPT, payload, cancellable, destination);
|
||||
if (!response.check_format_string ("u", false))
|
||||
throw new Error.PROTOCOL ("Invalid create_script response format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid create_script response format");
|
||||
uint32 script_handle;
|
||||
response.get ("u", out script_handle);
|
||||
return AgentScriptId (script_handle);
|
||||
}
|
||||
|
||||
public async void load_script (AgentScriptId script_id, uint destination, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var payload = new Variant ("u", script_id.handle);
|
||||
yield execute_command (Command.LOAD_SCRIPT, payload, cancellable, destination);
|
||||
}
|
||||
|
||||
public async void destroy_script (AgentScriptId script_id, uint destination, Cancellable? cancellable)
|
||||
throws Error, IOError {
|
||||
throws Efni.Error, IOError {
|
||||
var payload = new Variant ("u", script_id.handle);
|
||||
yield execute_command (Command.DESTROY_SCRIPT, payload, cancellable, destination);
|
||||
}
|
||||
|
||||
public async void post_script_message (AgentScriptId script_id, string message, Bytes? data, uint destination,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
var payload = new Variant ("(us@ay)", script_id.handle, message,
|
||||
new Variant.from_bytes (new VariantType ("ay"), data ?? new Bytes (new uint8[0]), true));
|
||||
yield execute_command (Command.POST_SCRIPT_MESSAGE, payload, cancellable, destination);
|
||||
@@ -883,26 +1049,26 @@ namespace Frida.Barebone {
|
||||
|
||||
// Zero selects the agent in the kernel. A different value selects a process that has a copy.
|
||||
private async Variant execute_command (Command command, Variant payload, Cancellable? cancellable,
|
||||
uint destination = 0) throws Error, IOError {
|
||||
uint destination = 0) throws Efni.Error, IOError {
|
||||
uint16 request_id = next_request_id++;
|
||||
|
||||
Bytes frame = frame_message (command, request_id, destination, payload);
|
||||
|
||||
var promise = new Promise<Variant> ();
|
||||
var promise = new Efni.Promise<Variant> ();
|
||||
pending_requests[request_id] = promise;
|
||||
|
||||
try {
|
||||
yield write_frame (frame, cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
pending_requests.unset (request_id);
|
||||
throw new Error.TRANSPORT ("%s", e.message);
|
||||
throw new Efni.Error.TRANSPORT ("%s", e.message);
|
||||
}
|
||||
|
||||
var timeout_source = new TimeoutSource (COMMAND_TIMEOUT_MS);
|
||||
timeout_source.set_callback (() => {
|
||||
Promise<Variant>? p;
|
||||
Efni.Promise<Variant>? p;
|
||||
if (pending_requests.unset (request_id, out p))
|
||||
p.reject (new Error.TIMED_OUT ("The %s command timed out", command.to_nick ()));
|
||||
p.reject (new Efni.Error.TIMED_OUT ("The %s command timed out", command.to_nick ()));
|
||||
return Source.REMOVE;
|
||||
});
|
||||
timeout_source.attach (MainContext.get_thread_default ());
|
||||
@@ -910,23 +1076,76 @@ namespace Frida.Barebone {
|
||||
Variant response = null;
|
||||
try {
|
||||
response = yield promise.future.wait_async (cancellable);
|
||||
} catch (Efni.Error e) {
|
||||
if (!(e is Efni.Error.TIMED_OUT))
|
||||
throw e;
|
||||
string? fault = yield describe_agent_fault (cancellable);
|
||||
if (fault == null)
|
||||
throw e;
|
||||
throw new Efni.Error.TIMED_OUT ("%s; %s", e.message, fault);
|
||||
} finally {
|
||||
timeout_source.destroy ();
|
||||
}
|
||||
|
||||
if (!response.check_format_string ("(bv)", false))
|
||||
throw new Error.PROTOCOL ("Malformed reply from agent");
|
||||
throw new Efni.Error.PROTOCOL ("Malformed reply from agent");
|
||||
bool succeeded;
|
||||
Variant detail;
|
||||
response.get ("(bv)", out succeeded, out detail);
|
||||
if (!succeeded)
|
||||
throw new Error.NOT_SUPPORTED ("%s", detail.get_string ());
|
||||
throw new Efni.Error.NOT_SUPPORTED ("%s", detail.get_string ());
|
||||
|
||||
return detail;
|
||||
}
|
||||
|
||||
private async string? describe_agent_fault (Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
if (fault_record == 0)
|
||||
return null;
|
||||
|
||||
var debugger = machine.debugger;
|
||||
Buffer record;
|
||||
try {
|
||||
yield debugger.stop (cancellable);
|
||||
record = debugger.make_buffer (yield debugger.read_byte_array (fault_record, FAULT_RECORD_SIZE,
|
||||
cancellable));
|
||||
yield debugger.resume (cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (record.read_uint64 (0) == 0)
|
||||
return null;
|
||||
|
||||
return ("the agent took an unhandled fault: vector 0x%" + uint64.FORMAT_MODIFIER
|
||||
+ "x at %s, accessing 0x%" + uint64.FORMAT_MODIFIER + "x").printf (
|
||||
record.read_uint64 (8), describe_program_counter (record.read_uint64 (16)),
|
||||
record.read_uint64 (24));
|
||||
}
|
||||
|
||||
private string describe_program_counter (uint64 pc) {
|
||||
foreach (var symbol in agent_symbols) {
|
||||
if (pc >= symbol.address && pc - symbol.address < symbol.size) {
|
||||
return ("%s+0x%" + uint64.FORMAT_MODIFIER + "x").printf (symbol.name,
|
||||
pc - symbol.address);
|
||||
}
|
||||
}
|
||||
return ("pc 0x%" + uint64.FORMAT_MODIFIER + "x").printf (pc);
|
||||
}
|
||||
|
||||
private class AgentSymbol {
|
||||
public string name;
|
||||
public uint64 address;
|
||||
public uint64 size;
|
||||
|
||||
public AgentSymbol (string name, uint64 address, uint64 size) {
|
||||
this.name = name;
|
||||
this.address = address;
|
||||
this.size = size;
|
||||
}
|
||||
}
|
||||
|
||||
private async void process_incoming_messages () {
|
||||
reading = new Promise<bool> ();
|
||||
reading = new Efni.Promise<bool> ();
|
||||
try {
|
||||
while (true) {
|
||||
size_t header_size = 4;
|
||||
@@ -954,7 +1173,7 @@ namespace Frida.Barebone {
|
||||
if (byte_order != ByteOrder.HOST)
|
||||
message = message.byteswap ();
|
||||
if (!message.check_format_string ("(yquv)", false))
|
||||
throw new Error.PROTOCOL ("Invalid message format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid message format");
|
||||
|
||||
uint8 command_code;
|
||||
uint16 request_id;
|
||||
@@ -962,9 +1181,12 @@ namespace Frida.Barebone {
|
||||
Variant payload;
|
||||
message.get ("(yquv)", out command_code, out request_id, out destination, out payload);
|
||||
|
||||
if (command_code == Command.SPAWN_ADDED) {
|
||||
if (command_code == Command.READY) {
|
||||
if (listening != null && !listening.future.ready)
|
||||
listening.resolve (true);
|
||||
} else if (command_code == Command.SPAWN_ADDED) {
|
||||
if (!payload.check_format_string ("(us)", false))
|
||||
throw new Error.PROTOCOL ("Invalid spawn added payload format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid spawn added payload format");
|
||||
|
||||
uint32 pid;
|
||||
unowned string command_line;
|
||||
@@ -973,7 +1195,7 @@ namespace Frida.Barebone {
|
||||
spawn_added (pid, command_line, destination);
|
||||
} else if (command_code == Command.SCRIPT_MESSAGE) {
|
||||
if (!payload.check_format_string ("(usay)", false))
|
||||
throw new Error.PROTOCOL ("Invalid script message payload format");
|
||||
throw new Efni.Error.PROTOCOL ("Invalid script message payload format");
|
||||
|
||||
uint32 script_handle;
|
||||
unowned string json;
|
||||
@@ -988,7 +1210,7 @@ namespace Frida.Barebone {
|
||||
} else if (command_code == Command.REMAP_WRITABLE_PAGES ||
|
||||
command_code == Command.MEMORY_PROTECT ||
|
||||
command_code == Command.PATCH_CODE) {
|
||||
bool was_running = yield halt_guest (machine.gdb, io_cancellable);
|
||||
bool was_running = yield halt_guest (machine.debugger, io_cancellable);
|
||||
|
||||
Variant result;
|
||||
try {
|
||||
@@ -998,17 +1220,17 @@ namespace Frida.Barebone {
|
||||
result = yield protect_memory (payload, io_cancellable);
|
||||
else
|
||||
result = yield patch_code (payload, io_cancellable);
|
||||
} catch (Error e) {
|
||||
} catch (Efni.Error e) {
|
||||
result = (command_code == Command.REMAP_WRITABLE_PAGES)
|
||||
? new Variant.uint64 (0)
|
||||
: new Variant.boolean (false);
|
||||
}
|
||||
|
||||
yield resume_guest (machine.gdb, was_running, io_cancellable);
|
||||
yield resume_guest (machine.debugger, was_running, io_cancellable);
|
||||
|
||||
yield send_reply (request_id, result);
|
||||
} else if (command_code == Command.REPLY) {
|
||||
Promise<Variant>? promise;
|
||||
Efni.Promise<Variant>? promise;
|
||||
if (pending_requests.unset (request_id, out promise))
|
||||
promise.resolve (payload);
|
||||
}
|
||||
@@ -1019,9 +1241,9 @@ namespace Frida.Barebone {
|
||||
reading.resolve (true);
|
||||
}
|
||||
|
||||
private Promise<bool>? reading;
|
||||
private Efni.Promise<bool>? reading;
|
||||
|
||||
private async void fill_until_n_bytes_available (size_t minimum) throws Error, IOError {
|
||||
private async void fill_until_n_bytes_available (size_t minimum) throws Efni.Error, IOError {
|
||||
size_t available = input.get_available ();
|
||||
while (available < minimum) {
|
||||
if (input.get_buffer_size () < minimum)
|
||||
@@ -1032,17 +1254,17 @@ namespace Frida.Barebone {
|
||||
n = yield input.fill_async ((ssize_t) (input.get_buffer_size () - available), Priority.DEFAULT,
|
||||
io_cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
throw new Error.TRANSPORT ("Connection closed");
|
||||
throw new Efni.Error.TRANSPORT ("Connection closed");
|
||||
}
|
||||
|
||||
if (n == 0)
|
||||
throw new Error.TRANSPORT ("Connection closed");
|
||||
throw new Efni.Error.TRANSPORT ("Connection closed");
|
||||
|
||||
available += n;
|
||||
}
|
||||
}
|
||||
|
||||
private async Variant remap_writable_pages (Variant payload, Cancellable? cancellable) throws Error, IOError {
|
||||
private async Variant remap_writable_pages (Variant payload, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
var physical_addresses = new Gee.ArrayList<uint64?> ();
|
||||
for (size_t i = 0; i != payload.n_children (); i++) {
|
||||
uint64 va = payload.get_child_value (i).get_uint64 ();
|
||||
@@ -1054,7 +1276,7 @@ namespace Frida.Barebone {
|
||||
return new Variant.uint64 (allocation.virtual_address);
|
||||
}
|
||||
|
||||
private async Variant protect_memory (Variant payload, Cancellable? cancellable) throws Error, IOError {
|
||||
private async Variant protect_memory (Variant payload, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
uint64 address;
|
||||
uint64 size;
|
||||
uint32 prot;
|
||||
@@ -1069,7 +1291,7 @@ namespace Frida.Barebone {
|
||||
// CTRR/KTRR locks kernel text read-only against the guest CPU, so the agent cannot patch it
|
||||
// even through a writable alias. The physical-memory bridge writes the backing store directly,
|
||||
// which the lock does not cover, letting us land hooks in kernel and kext text.
|
||||
private async Variant patch_code (Variant payload, Cancellable? cancellable) throws Error, IOError {
|
||||
private async Variant patch_code (Variant payload, Cancellable? cancellable) throws Efni.Error, IOError {
|
||||
uint64 va;
|
||||
Variant bytes_value;
|
||||
payload.get ("(t@ay)", out va, out bytes_value);
|
||||
@@ -1099,7 +1321,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private async void write_frame (Bytes frame, Cancellable? cancellable) throws GLib.Error {
|
||||
var ours = new Promise<bool> ();
|
||||
var ours = new Efni.Promise<bool> ();
|
||||
var theirs = write_turn;
|
||||
write_turn = ours;
|
||||
|
||||
@@ -1120,7 +1342,7 @@ namespace Frida.Barebone {
|
||||
}
|
||||
}
|
||||
|
||||
private Promise<bool>? write_turn;
|
||||
private Efni.Promise<bool>? write_turn;
|
||||
|
||||
private Bytes frame_message (Command command, uint16 request_id, uint destination, Variant payload) {
|
||||
var message = new Variant ("(yquv)", (uint8) command, request_id, destination, payload);
|
||||
@@ -1134,30 +1356,33 @@ namespace Frida.Barebone {
|
||||
}
|
||||
|
||||
private enum Command {
|
||||
CREATE_SCRIPT = 1,
|
||||
LOAD_SCRIPT = 2,
|
||||
DESTROY_SCRIPT = 3,
|
||||
POST_SCRIPT_MESSAGE = 4,
|
||||
REMAP_WRITABLE_PAGES = 5,
|
||||
MEMORY_PROTECT = 6,
|
||||
PATCH_CODE = 7,
|
||||
ENUMERATE_PROCESSES = 8,
|
||||
INJECT_INTO_PROCESS = 9,
|
||||
DETACH_FROM_PROCESS = 11,
|
||||
PLACE_AGENT_IN_PROCESS = 12,
|
||||
START_AGENT_IN_PROCESS = 13,
|
||||
SPAWN_PROCESS = 14,
|
||||
RESUME_PROCESS = 15,
|
||||
STOP = 16,
|
||||
GATE_SPAWNS = 17,
|
||||
ENUMERATE_APPLICATIONS = 18,
|
||||
ENUMERATE_SHORTCUTS = 19,
|
||||
ENUMERATE_APPLICATIONS = 1,
|
||||
ENUMERATE_SHORTCUTS = 2,
|
||||
ENUMERATE_PROCESSES = 3,
|
||||
GATE_SPAWNS = 4,
|
||||
ARM_NAMED_SPAWN = 5,
|
||||
DISARM_NAMED_SPAWN = 6,
|
||||
SPAWN_PROCESS = 7,
|
||||
RESUME_PROCESS = 8,
|
||||
STOP = 9,
|
||||
INJECT_INTO_PROCESS = 10,
|
||||
PLACE_AGENT_IN_PROCESS = 11,
|
||||
START_AGENT_IN_PROCESS = 12,
|
||||
REMAP_WRITABLE_PAGES = 14,
|
||||
MEMORY_PROTECT = 15,
|
||||
PATCH_CODE = 16,
|
||||
DETACH_FROM_PROCESS = 17,
|
||||
CREATE_SCRIPT = 18,
|
||||
DESTROY_SCRIPT = 19,
|
||||
LOAD_SCRIPT = 20,
|
||||
POST_SCRIPT_MESSAGE = 21,
|
||||
REPLY = 128,
|
||||
SCRIPT_MESSAGE = 129,
|
||||
SPAWN_ADDED = 130;
|
||||
SPAWN_ADDED = 130,
|
||||
READY = 131;
|
||||
|
||||
public string to_nick () {
|
||||
return Marshal.enum_to_nick<Command> (this);
|
||||
return Efni.Marshal.enum_to_nick<Command> (this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,44 +1,36 @@
|
||||
## Getting the SDK and the devkit
|
||||
|
||||
The agent is built against the bare soft-float SDK, which carries picolibc and the
|
||||
compiler runtime, and against a GumJS devkit built for the same flavor. Which flavor
|
||||
is the target's, listed under Targets below; this walks through the XNU one. No
|
||||
prebuilt bundles are published for these, so build both:
|
||||
compiler runtime, and against a GumJS devkit built for the same machine. Which machine
|
||||
is the target's, listed under Targets below; this walks through the XNU one. The SDK
|
||||
is published prebuilt, and the devkit is built from frida-gum:
|
||||
|
||||
machine=none-arm64-softfloat_nopic
|
||||
machine=none-arm64-softfloat_pic
|
||||
|
||||
releng/deps.py build --bundle=sdk --host=$machine
|
||||
mkdir -p ~/sdk-$machine
|
||||
tar -C ~/sdk-$machine -xf deps/sdk-$machine.tar.xz
|
||||
sdk=~/sdk-$machine
|
||||
releng/deps.py sync sdk $machine $sdk
|
||||
|
||||
gum=~/src/frida-gum
|
||||
mkdir -p $gum/build/$machine
|
||||
(cd $gum/build/$machine \
|
||||
&& FRIDA_DEPS=$HOME/src/frida-core/deps CC=clang CXX=clang++ AR=llvm-ar \
|
||||
RANLIB=llvm-ranlib NM=llvm-nm STRIP=llvm-strip \
|
||||
../../configure --host=$machine --enable-gumjs \
|
||||
--with-devkits=gumjs \
|
||||
&& python3 ../../releng/meson/meson.py configure -Ddevkit_symbol_scope=original \
|
||||
&& ninja)
|
||||
&& CC=clang CXX=clang++ ../../configure --host=$machine --enable-gumjs \
|
||||
--with-devkits=gumjs --with-devkit-symbol-scope=original \
|
||||
&& make)
|
||||
|
||||
## Building
|
||||
|
||||
sdk=~/sdk-$machine
|
||||
export GUMJS_DEVKIT_DIR=$gum/build/$machine/bindings/gumjs/devkit
|
||||
export CC="clang -I$sdk/include -I$sdk/include/glib-2.0 -I$sdk/lib/glib-2.0/include \
|
||||
-I$sdk/include/capstone -I$sdk/include/json-glib-1.0"
|
||||
export RUSTFLAGS="-L native=$sdk/lib"
|
||||
|
||||
RUSTC_BOOTSTRAP=1 cargo build --release --features xnu \
|
||||
--target aarch64-unknown-none-softfloat --target-dir target-xnu \
|
||||
-Z build-std=core,alloc,compiler_builtins
|
||||
--target ./aarch64-unknown-none-pic.json -Z json-target-spec \
|
||||
--target-dir target-xnu -Z build-std=core,alloc,compiler_builtins
|
||||
|
||||
Rust names no bare-metal target for the Windows guests, so the agent spells them
|
||||
out and they are asked for by path:
|
||||
|
||||
RUSTC_BOOTSTRAP=1 cargo build --release --features win9x \
|
||||
--target ./i686-unknown-none-pic.json -Z json-target-spec \
|
||||
--target-dir target-win9x -Z build-std=core,alloc,compiler_builtins
|
||||
Every flavor names its target by path, as Rust has no built-in one for a
|
||||
position-independent bare-metal image. The others differ only in feature, machine
|
||||
and target spec, listed under Targets below.
|
||||
|
||||
## Targets
|
||||
|
||||
@@ -48,7 +40,7 @@ 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.
|
||||
- `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`.
|
||||
|
||||
@@ -58,10 +50,11 @@ and NT's 64-bit half is called in the Microsoft ABI.
|
||||
|
||||
| Flavor | Machine | Rust target |
|
||||
|---------|-----------------------------------|----------------------------------|
|
||||
| `xnu` | `none-arm64-softfloat_nopic` | `aarch64-unknown-none-softfloat` |
|
||||
| `xnu` | `none-arm64-softfloat_pic` | `aarch64-unknown-none-pic.json` |
|
||||
| `win9x` | `none-x86-softfloat_pic` | `i686-unknown-none-pic.json` |
|
||||
| `winnt` | `none-x86-softfloat_pic` | `i686-unknown-none-pic.json` |
|
||||
| `winnt` | `none-x86_64-softfloat_pic_msabi` | `x86_64-unknown-none-pic.json` |
|
||||
| `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
|
||||
@@ -71,7 +64,9 @@ half of Gum's platform backend (`gum_xnu.rs` / `gum_windows.rs` / `gum_linux.rs`
|
||||
## Development loop
|
||||
|
||||
export FRIDA_BAREBONE_CONFIG=$PWD/etc/xnu.json
|
||||
cargo build --release --features xnu --target aarch64-unknown-none-softfloat \
|
||||
RUSTC_BOOTSTRAP=1 cargo build --release --features xnu \
|
||||
--target ./aarch64-unknown-none-pic.json -Z json-target-spec \
|
||||
--target-dir target-xnu -Z build-std=core,alloc,compiler_builtins \
|
||||
&& make -C ~/src/frida-python \
|
||||
&& killall -9 qemu-system-aarch64 && sleep 2 \
|
||||
&& frida -D barebone -p 0
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"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",
|
||||
"features": "+v8a,+strict-align,-neon,+reserve-x18",
|
||||
"linker": "rust-lld",
|
||||
"linker-flavor": "gnu-lld",
|
||||
"llvm-target": "aarch64-unknown-none",
|
||||
@@ -18,4 +18,4 @@
|
||||
"kind": "inline"
|
||||
},
|
||||
"target-pointer-width": 64
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,13 @@ SECTIONS {
|
||||
_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;
|
||||
@@ -43,10 +46,12 @@ SECTIONS {
|
||||
_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;
|
||||
@@ -84,6 +89,8 @@ SECTIONS {
|
||||
___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;
|
||||
@@ -91,12 +98,20 @@ SECTIONS {
|
||||
_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
|
||||
|
||||
@@ -20,10 +20,13 @@ SECTIONS {
|
||||
_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;
|
||||
@@ -43,14 +46,17 @@ SECTIONS {
|
||||
_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;
|
||||
@@ -80,6 +86,8 @@ SECTIONS {
|
||||
___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;
|
||||
@@ -89,6 +97,11 @@ SECTIONS {
|
||||
___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;
|
||||
@@ -97,6 +110,15 @@ SECTIONS {
|
||||
_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. */
|
||||
|
||||
@@ -20,10 +20,12 @@ SECTIONS {
|
||||
_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;
|
||||
@@ -54,6 +56,7 @@ SECTIONS {
|
||||
_vmap = .; . += 4;
|
||||
_current_task = .; . += 4;
|
||||
_ioremap = .; . += 4;
|
||||
_ioremap_nocache = .; . += 4;
|
||||
___default_kernel_pte_mask = .; . += 4;
|
||||
_init_task = .; . += 4;
|
||||
_tasklist_lock = .; . += 4;
|
||||
@@ -96,6 +99,14 @@ SECTIONS {
|
||||
_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;
|
||||
}
|
||||
|
||||
@@ -20,10 +20,13 @@ SECTIONS {
|
||||
_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;
|
||||
@@ -48,6 +51,7 @@ SECTIONS {
|
||||
_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;
|
||||
@@ -58,6 +62,7 @@ SECTIONS {
|
||||
_register_die_notifier = .; . += 8;
|
||||
_unregister_die_notifier = .; . += 8;
|
||||
_ioremap = .; . += 8;
|
||||
_ioremap_nocache = .; . += 8;
|
||||
___default_kernel_pte_mask = .; . += 8;
|
||||
_init_task = .; . += 8;
|
||||
_tasklist_lock = .; . += 8;
|
||||
@@ -86,6 +91,8 @@ SECTIONS {
|
||||
___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;
|
||||
@@ -100,6 +107,16 @@ SECTIONS {
|
||||
_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. */
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
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:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -25,7 +25,10 @@ SECTIONS {
|
||||
_PsRemoveCreateThreadNotifyRoutine = .; . += 4;
|
||||
_KeInsertQueueDpc = .; . += 4;
|
||||
_ZwYieldExecution = .; . += 4;
|
||||
_KeExpandKernelStackAndCalloutEx = .; . += 4;
|
||||
_KeBugCheckEx = .; . += 4;
|
||||
_DbgPrint = .; . += 4;
|
||||
_MmGetVirtualForPhysical = .; . += 4;
|
||||
_MmMapIoSpace = .; . += 4;
|
||||
_MmGetPhysicalAddress = .; . += 4;
|
||||
_HalGetInterruptVector = .; . += 4;
|
||||
@@ -39,6 +42,7 @@ SECTIONS {
|
||||
_PsGetCurrentThread = .; . += 4;
|
||||
_PsGetThreadProcess = .; . += 4;
|
||||
_PsGetThreadId = .; . += 4;
|
||||
_PsGetThreadTeb = .; . += 4;
|
||||
_PsGetContextThread = .; . += 4;
|
||||
_PsSetContextThread = .; . += 4;
|
||||
_KeStackAttachProcess = .; . += 4;
|
||||
@@ -48,8 +52,17 @@ SECTIONS {
|
||||
_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;
|
||||
|
||||
@@ -30,7 +30,10 @@ SECTIONS {
|
||||
_PsRemoveCreateThreadNotifyRoutine = .; . += 8;
|
||||
_KeInsertQueueDpc = .; . += 8;
|
||||
_ZwYieldExecution = .; . += 8;
|
||||
_KeExpandKernelStackAndCalloutEx = .; . += 8;
|
||||
_KeBugCheckEx = .; . += 8;
|
||||
_DbgPrint = .; . += 8;
|
||||
_MmGetVirtualForPhysical = .; . += 8;
|
||||
_MmMapIoSpace = .; . += 8;
|
||||
_MmGetPhysicalAddress = .; . += 8;
|
||||
_HalGetInterruptVector = .; . += 8;
|
||||
@@ -44,6 +47,7 @@ SECTIONS {
|
||||
_PsGetCurrentThread = .; . += 8;
|
||||
_PsGetThreadProcess = .; . += 8;
|
||||
_PsGetThreadId = .; . += 8;
|
||||
_PsGetThreadTeb = .; . += 8;
|
||||
_PsGetContextThread = .; . += 8;
|
||||
_PsSetContextThread = .; . += 8;
|
||||
_KeStackAttachProcess = .; . += 8;
|
||||
@@ -53,8 +57,17 @@ SECTIONS {
|
||||
_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;
|
||||
|
||||
@@ -33,7 +33,12 @@ fn main() {
|
||||
.clang_arg(format!("{}-none-elf", target.split('-').next().unwrap()))
|
||||
.clang_args(cc_clang_args)
|
||||
.clang_arg(format!("-I{}", devkit_dir.to_str().unwrap()))
|
||||
.clang_arg("-include")
|
||||
.clang_arg("stddef.h")
|
||||
.clang_arg("-include")
|
||||
.clang_arg("stdint.h")
|
||||
.blocklist_type("max_align_t")
|
||||
.blocklist_function("memcpy|memmove|memset|memcmp|bcmp|strlen")
|
||||
.merge_extern_blocks(true)
|
||||
.generate()
|
||||
.expect("Unable to generate bindings");
|
||||
@@ -196,7 +201,7 @@ pub fn detect_gcc_include_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
|
||||
|
||||
let p = Path::new(raw);
|
||||
if p.exists() {
|
||||
let canonical = fs::canonicalize(p).expect("Failed to canonicalize include path");
|
||||
let canonical = without_extended_length_prefix(fs::canonicalize(p).expect("Failed to canonicalize include path"));
|
||||
if !paths.contains(&canonical) {
|
||||
paths.push(canonical);
|
||||
}
|
||||
@@ -206,6 +211,14 @@ pub fn detect_gcc_include_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
|
||||
paths
|
||||
}
|
||||
|
||||
fn without_extended_length_prefix(path: PathBuf) -> PathBuf {
|
||||
let text = path.to_string_lossy();
|
||||
match text.strip_prefix(r"\\?\") {
|
||||
Some(rest) => PathBuf::from(rest),
|
||||
None => path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detect_gcc_library_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
|
||||
let out = Command::new(gcc).args(args).arg("-print-search-dirs").output()
|
||||
.expect("Failed to execute GCC to detect library paths");
|
||||
|
||||
@@ -2,7 +2,7 @@ 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 -I$sdk/include -I$sdk/include/glib-2.0 -I$sdk/lib/glib-2.0/include \
|
||||
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
|
||||
@@ -20,6 +20,7 @@
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"type": "xnu-kernelcache",
|
||||
"file": "/Users/oleavr/src/vphone-cli/vm/iPhone17,3_26.1_23B85_Restore/kernelcache.research.vresearch101",
|
||||
"symbols": {
|
||||
"panic": "0x12a2814",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
}
|
||||
},
|
||||
"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);
|
||||
$gdb.continue();
|
||||
$debugger.continue();
|
||||
|
||||
Object.assign(globalThis, { cm, start });
|
||||
@@ -11,21 +11,22 @@ how it is told about the kernel around it, and how the host reaches it.
|
||||
## Building
|
||||
|
||||
Three things, in order. First an SDK for this kernel -- picolibc, GLib, QuickJS, capstone and the
|
||||
rest, built for `macos-arm64-kernel`:
|
||||
rest, built for `macos-arm64e-kernel` and published prebuilt:
|
||||
|
||||
cd frida-core
|
||||
python3 -m releng.deps build --host macos-arm64-kernel
|
||||
releng/deps.py sync sdk macos-arm64e-kernel ~/sdk-macos-arm64e-kernel
|
||||
|
||||
That fetches the same sources every other flavour is built from, six of them pinned to a commit
|
||||
that says what a Darwin kernel is not; see below. Then a GumJS devkit against it:
|
||||
It is built from the same sources every other flavour is, six of them pinned to a commit that
|
||||
says what a Darwin kernel is not; see below. Then a GumJS devkit against it:
|
||||
|
||||
cd frida-gum
|
||||
./configure --host=macos-arm64-kernel --enable-gumjs --disable-v8 --with-devkits=gumjs
|
||||
./configure --host=macos-arm64e-kernel --enable-gumjs --with-devkits=gumjs \
|
||||
--with-devkit-symbol-scope=original
|
||||
make
|
||||
|
||||
Then the kext itself:
|
||||
|
||||
make FRIDA_SDK=.../deps/sdk-macos-arm64-kernel \
|
||||
make FRIDA_SDK=~/sdk-macos-arm64e-kernel \
|
||||
GUMJS_DEVKIT_DIR=.../frida-gum/build/bindings/gumjs/devkit
|
||||
|
||||
which builds the agent as a static library for the kernel's ABI, `frida-kext.c` against the Kernel
|
||||
|
||||
@@ -79,7 +79,6 @@ const unsigned frida_agent_disc_proc_callout =
|
||||
_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)
|
||||
{
|
||||
|
||||
@@ -42,6 +42,7 @@ pub extern "C" fn g_wait_sleep(token: gpointer, timeout_us: gint64) {
|
||||
|
||||
#[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);
|
||||
}
|
||||
|
||||
@@ -453,6 +453,7 @@ pub struct ThreadSlots {
|
||||
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) };
|
||||
}
|
||||
|
||||
@@ -6,11 +6,9 @@ use crate::{
|
||||
_GInterfaceInfo, _GTypeInfo, GObject, GObjectClass, GPrivate, GType, GumAddress,
|
||||
GumExportDetails,
|
||||
GumExportType_GUM_EXPORT_FUNCTION, GumFoundExportFunc, GumMemoryRange, GumModule,
|
||||
GumFoundSymbolFunc, GumModuleInterface, GumSymbolDetails, GumSymbolType_GUM_SYMBOL_FUNCTION,
|
||||
GumSymbolType_GUM_SYMBOL_OBJECT, GumSymbolType_GUM_SYMBOL_UNKNOWN, GumThreadId, GumTlsKey,
|
||||
gssize, g_free, g_object_get_type, g_object_new,
|
||||
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, gboolean, gpointer, gsize, guint,
|
||||
g_type_class_peek_parent, g_type_register_static, gchar, gpointer, gsize, guint,
|
||||
},
|
||||
gthread, kernel, libc,
|
||||
};
|
||||
@@ -24,9 +22,16 @@ 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;
|
||||
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;
|
||||
|
||||
@@ -181,6 +186,11 @@ extern "C" fn gum_native_module_iface_init(g_iface: gpointer, _iface_data: gpoin
|
||||
(*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"))]
|
||||
{
|
||||
@@ -314,6 +324,32 @@ unsafe extern "C" fn gum_native_module_enumerate_exports(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
unsafe extern "C" fn gum_native_module_enumerate_imports(
|
||||
self_: *mut GumModule,
|
||||
func: GumFoundImportFunc,
|
||||
user_data: gpointer,
|
||||
) {
|
||||
unsafe {
|
||||
let module = self_ as *mut GumNativeModule;
|
||||
let base = (*module).range.base_address;
|
||||
|
||||
let mut on_import = |library: *const gchar, name: *const gchar, slot: u64, address: u64| {
|
||||
let details = GumImportDetails {
|
||||
type_: GumImportType_GUM_IMPORT_FUNCTION,
|
||||
name,
|
||||
module: library,
|
||||
address,
|
||||
slot,
|
||||
};
|
||||
|
||||
func.unwrap()(&details as *const GumImportDetails, user_data) != 0
|
||||
};
|
||||
|
||||
enumerate_imports_in_module(base, &mut on_import);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
unsafe extern "C" fn gum_native_module_enumerate_symbols(
|
||||
self_: *mut GumModule,
|
||||
@@ -365,6 +401,9 @@ const SYMBOL_IS_FUNCTION: u8 = 2;
|
||||
/// 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
|
||||
@@ -401,24 +440,6 @@ pub(crate) fn unregister_slab(start: u64) {
|
||||
slab_unlock();
|
||||
}
|
||||
|
||||
// For callers that must not block, primarily a fault handler, which can interrupt the code
|
||||
// that holds the lock. Report a busy registry and let the caller decide.
|
||||
pub(crate) fn is_agent_slab_if_idle(address: u64) -> Option<bool> {
|
||||
if SLAB_LOCK
|
||||
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let found = unsafe {
|
||||
(*core::ptr::addr_of!(SLABS))
|
||||
.iter()
|
||||
.any(|&(begin, end)| address >= begin && address < end)
|
||||
};
|
||||
slab_unlock();
|
||||
Some(found)
|
||||
}
|
||||
|
||||
pub(crate) fn is_agent_slab(address: u64) -> bool {
|
||||
slab_lock();
|
||||
let found = unsafe {
|
||||
@@ -435,7 +456,8 @@ pub(crate) fn is_agent_slab(address: u64) -> bool {
|
||||
mod symbolication {
|
||||
use super::*;
|
||||
use crate::bindings::{
|
||||
GArray, GumAddress, GumDebugSymbolDetails, g_array_append_vals, g_array_new, gconstpointer,
|
||||
GArray, GumAddress, GumDebugSymbolDetails, g_array_append_vals, g_array_new, gboolean,
|
||||
gconstpointer,
|
||||
};
|
||||
use core::ffi::CStr;
|
||||
|
||||
@@ -483,6 +505,11 @@ mod symbolication {
|
||||
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) {
|
||||
|
||||
@@ -8,26 +8,34 @@ use crate::{
|
||||
FridaCommand,
|
||||
bindings::{
|
||||
_GumPageProtection_GUM_PAGE_EXECUTE, _GumPageProtection_GUM_PAGE_READ,
|
||||
_GumPageProtection_GUM_PAGE_WRITE, _GumRwxSupport_GUM_RWX_FULL,
|
||||
_GumRwxSupport_GUM_RWX_NONE, GArray,
|
||||
GumDebugSymbolDetails, GumFoundRangeFunc, GumFoundThreadFunc, GumMemoryRange,
|
||||
GumModuleRegistry, GumPageProtection, GumRangeDetails, GumRwxSupport, GumThreadDetails,
|
||||
GumThreadId, GumThreadRegistry, gum_barebone_register_thread,
|
||||
gum_barebone_unregister_thread,
|
||||
g_array_append_vals, g_array_new, g_object_unref, g_strdup, g_variant_get_boolean,
|
||||
g_variant_get_uint64, g_variant_new, g_variant_new_fixed_array, g_variant_type_free,
|
||||
g_variant_type_new, g_variant_unref, gboolean, gchar, gconstpointer, gpointer, gsize, guint,
|
||||
gum_barebone_register_module,
|
||||
gum_barebone_try_remap_writable_pages as _gum_barebone_try_remap_writable_pages,
|
||||
gum_mprotect, gum_query_page_size,
|
||||
_GumPageProtection_GUM_PAGE_WRITE, _GumRwxSupport_GUM_RWX_NONE,
|
||||
GumFoundRangeFunc, GumFoundThreadFunc, GumMemoryRange,
|
||||
GumCpuContext, GumModuleRegistry, GumPageProtection, GumRangeDetails, GumRwxSupport,
|
||||
GumThreadDetails, GumThreadFlags, GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT,
|
||||
GumThreadFlags_GUM_THREAD_FLAGS_STATE, GumThreadId,
|
||||
GumThreadRegistry, gum_barebone_register_thread, g_variant_get_uint64,
|
||||
g_variant_new_fixed_array, g_variant_type_free, g_variant_type_new, g_variant_unref,
|
||||
gboolean, gchar, gconstpointer, gpointer, gsize, guint, gum_mprotect, gum_query_page_size,
|
||||
},
|
||||
gum::{self, FoundExportCallback},
|
||||
host_rpc, kernel, libc,
|
||||
};
|
||||
#[cfg(feature = "xnu-core")]
|
||||
use crate::bindings::{g_object_unref, gum_barebone_register_module};
|
||||
#[cfg(feature = "linux-injected")]
|
||||
use crate::bindings::{
|
||||
GError, GumBareboneStreamOps, g_io_error_from_errno, g_io_error_quark, g_set_error_literal,
|
||||
g_strerror, gint, gssize, gum_barebone_unregister_thread,
|
||||
};
|
||||
#[cfg(not(feature = "xnu-kext"))]
|
||||
use crate::bindings::{
|
||||
g_variant_get_boolean, g_variant_new,
|
||||
gum_barebone_try_remap_writable_pages as _gum_barebone_try_remap_writable_pages,
|
||||
};
|
||||
use alloc::collections::{BTreeMap, BTreeSet};
|
||||
#[cfg(feature = "xnu-core")]
|
||||
use alloc::format;
|
||||
use alloc::vec::Vec;
|
||||
use core::ffi::CStr;
|
||||
use core::mem::size_of;
|
||||
use core::ptr;
|
||||
|
||||
@@ -40,13 +48,6 @@ const MODULE_DIRECTORY: &str = "/System/Library/Extensions/";
|
||||
#[cfg(feature = "xnu-core")]
|
||||
const MODULE_SUFFIX: &str = ".kext";
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
const KERNEL_PATH: &str = "/boot/vmlinux";
|
||||
#[cfg(feature = "linux-injected")]
|
||||
const MODULE_DIRECTORY: &str = "/lib/modules/";
|
||||
#[cfg(feature = "linux-injected")]
|
||||
const MODULE_SUFFIX: &str = ".ko";
|
||||
|
||||
const SHADOW_HEADER: usize = 24;
|
||||
|
||||
#[cfg(feature = "xnu-core")]
|
||||
@@ -84,7 +85,7 @@ pub extern "C" fn gum_barebone_query_stack_size() -> crate::bindings::gsize {
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_query_rwx_support() -> GumRwxSupport {
|
||||
#[cfg(feature = "linux-injected")]
|
||||
return _GumRwxSupport_GUM_RWX_FULL;
|
||||
return _GumRwxSupport_GUM_RWX_NONE;
|
||||
|
||||
#[cfg(not(feature = "linux-injected"))]
|
||||
_GumRwxSupport_GUM_RWX_NONE
|
||||
@@ -137,7 +138,7 @@ fn remap_agent_pages(first_page: gpointer, _n_pages: guint) -> gpointer {
|
||||
fn writable_in_place(first_page: gpointer, n_pages: guint) -> bool {
|
||||
let size = n_pages as usize * unsafe { gum_query_page_size() } as usize;
|
||||
|
||||
kernel::set_protection(first_page as u64, size, _GumPageProtection_GUM_PAGE_RWX)
|
||||
kernel::set_protection(first_page as u64, size, GUM_PAGE_RWX)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "xnu-kext"))]
|
||||
@@ -175,8 +176,53 @@ fn remap_agent_pages_through_host(first_page: gpointer, n_pages: guint) -> gpoin
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "linux-injected", not(target_arch = "arm")))]
|
||||
const GUM_PAGE_RWX: u32 = 7;
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
const _GumPageProtection_GUM_PAGE_RWX: u32 = 7;
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_query_stream_ops() -> *const GumBareboneStreamOps {
|
||||
if kernel::in_copy() { &STREAM_OPS } else { ptr::null() }
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
static STREAM_OPS: GumBareboneStreamOps = GumBareboneStreamOps {
|
||||
poll: Some(crate::linux::user::frida_poll),
|
||||
read: Some(read_descriptor),
|
||||
write: Some(write_descriptor),
|
||||
close: Some(close_descriptor),
|
||||
};
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
unsafe extern "C" fn read_descriptor(handle: gpointer, buffer: gpointer, count: gsize,
|
||||
error: *mut *mut GError) -> gssize {
|
||||
transferred(crate::linux::user::read(handle as i32, buffer as *mut u8, count as usize), error)
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
unsafe extern "C" fn write_descriptor(handle: gpointer, buffer: gconstpointer, count: gsize,
|
||||
error: *mut *mut GError) -> gssize {
|
||||
transferred(crate::linux::user::write(handle as i32, buffer as *const u8, count as usize),
|
||||
error)
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
unsafe extern "C" fn close_descriptor(handle: gpointer, error: *mut *mut GError) -> gboolean {
|
||||
(transferred(crate::linux::user::close(handle as i32), error) >= 0) as gboolean
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
fn transferred(result: isize, error: *mut *mut GError) -> gssize {
|
||||
if result < 0 {
|
||||
let errno = -result as gint;
|
||||
unsafe {
|
||||
g_set_error_literal(error, g_io_error_quark(), g_io_error_from_errno(errno) as gint,
|
||||
g_strerror(errno))
|
||||
};
|
||||
return -1;
|
||||
}
|
||||
result as gssize
|
||||
}
|
||||
|
||||
unsafe fn what_changed(first_page: u64, shadow: *const u8, total: usize) -> Option<(usize, usize)> {
|
||||
let live = unsafe { core::slice::from_raw_parts(first_page as *const u8, total) };
|
||||
@@ -221,17 +267,13 @@ pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
if let Some(base) = crate::xnu::the_page_behind(writable as u64) {
|
||||
let size = _n_pages as usize * unsafe { gum_query_page_size() } as usize;
|
||||
unsafe {
|
||||
libc::__clear_cache(base as *const u8, (base + size as u64) as *const u8);
|
||||
}
|
||||
libc::__clear_cache(base as *const u8, (base + size as u64) as *const u8);
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(executable) = aliases().remove(&(writable as u64)) {
|
||||
let size = _n_pages as usize * unsafe { gum_query_page_size() } as usize;
|
||||
unsafe {
|
||||
libc::__clear_cache(executable as *const u8, (executable + size as u64) as *const u8);
|
||||
}
|
||||
libc::__clear_cache(executable as *const u8, (executable + size as u64) as *const u8);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -239,10 +281,8 @@ pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages
|
||||
#[cfg(all(feature = "linux-injected", not(target_arch = "arm")))]
|
||||
{
|
||||
let size = _n_pages as usize * unsafe { gum_query_page_size() } as usize;
|
||||
unsafe {
|
||||
libc::__clear_cache(writable as *const u8,
|
||||
(writable as u64 + size as u64) as *const u8);
|
||||
}
|
||||
libc::__clear_cache(writable as *const u8,
|
||||
(writable as u64 + size as u64) as *const u8);
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -366,6 +406,7 @@ fn protect_here(address: u64, size: usize, prot: u32) -> bool {
|
||||
ask_the_host_to_protect(address, size, prot)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "xnu-kext"))]
|
||||
pub fn ask_the_host_to_protect(address: u64, size: usize, prot: u32) -> bool {
|
||||
if !crate::transport_is_up() {
|
||||
return true;
|
||||
@@ -381,6 +422,7 @@ pub fn ask_the_host_to_protect(address: u64, size: usize, prot: u32) -> bool {
|
||||
granted
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "xnu-kext"))]
|
||||
fn the_host_grants_it(address: u64, size: usize, prot: u32) -> bool {
|
||||
unsafe {
|
||||
let payload = g_variant_new(c"(ttu)".as_ptr(), address, size as u64, prot);
|
||||
@@ -401,6 +443,7 @@ fn the_host_grants_it(address: u64, size: usize, prot: u32) -> bool {
|
||||
// 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.
|
||||
#[cfg(not(feature = "xnu-kext"))]
|
||||
unsafe fn flush_tlb_range(address: u64, size: u64) {
|
||||
let page_size = unsafe { gum_query_page_size() } as u64;
|
||||
let start = address & !(page_size - 1);
|
||||
@@ -409,7 +452,7 @@ unsafe fn flush_tlb_range(address: u64, size: u64) {
|
||||
unsafe { flush_pages(start, end, page_size) };
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
#[cfg(all(not(feature = "xnu-kext"), target_arch = "arm"))]
|
||||
unsafe fn flush_pages(start: u64, end: u64, page_size: u64) {
|
||||
unsafe {
|
||||
core::arch::asm!("dsb ish", options(nostack, preserves_flags));
|
||||
@@ -423,7 +466,7 @@ unsafe fn flush_pages(start: u64, end: u64, page_size: u64) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[cfg(all(not(feature = "xnu-kext"), target_arch = "aarch64"))]
|
||||
unsafe fn flush_pages(start: u64, end: u64, page_size: u64) {
|
||||
unsafe {
|
||||
core::arch::asm!("dsb ish", options(nostack, preserves_flags));
|
||||
@@ -503,7 +546,7 @@ fn release_pages_now(at: gpointer, size: gsize) {
|
||||
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
fn remember_slab_of(at: gpointer, size: gsize) {
|
||||
unsafe { gum::register_slab(at as u64, size as usize) };
|
||||
gum::register_slab(at as u64, size as usize);
|
||||
crate::xnu::remember_what_we_took(at as u64, size as usize, true);
|
||||
}
|
||||
|
||||
@@ -545,17 +588,17 @@ pub extern "C" fn gum_memory_allocate(
|
||||
} else {
|
||||
kernel::alloc_code(size as usize)
|
||||
};
|
||||
#[cfg(not(feature = "xnu-core"))]
|
||||
#[cfg(all(not(feature = "xnu-core"), not(feature = "linux-injected")))]
|
||||
let ptr = kernel::alloc_code(size as usize);
|
||||
#[cfg(feature = "linux-injected")]
|
||||
let ptr = kernel::alloc_heap(size as usize);
|
||||
#[cfg(not(feature = "xnu-core"))]
|
||||
let _ = address;
|
||||
unsafe {
|
||||
#[cfg(not(feature = "xnu-kext"))]
|
||||
core::ptr::write_bytes(ptr, 0, size as usize);
|
||||
#[cfg(not(feature = "xnu-kext"))]
|
||||
unsafe { core::ptr::write_bytes(ptr, 0, size as usize) };
|
||||
|
||||
if may_run {
|
||||
gum::register_slab(ptr as u64, size as usize);
|
||||
}
|
||||
if may_run {
|
||||
gum::register_slab(ptr as u64, size as usize);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "xnu-kext"))]
|
||||
@@ -661,7 +704,7 @@ pub extern "C" fn gum_barebone_on_registry_deactivating(_registry: *mut GumModul
|
||||
pub extern "C" fn gum_barebone_on_thread_registry_activating(registry: *mut GumThreadRegistry) {
|
||||
unsafe { THREAD_REGISTRY = registry };
|
||||
|
||||
kernel::enumerate_threads(&mut |thread| announce_thread(thread.id));
|
||||
kernel::enumerate_threads(&mut |thread| announce_thread(thread.id), false);
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
|
||||
@@ -670,12 +713,12 @@ pub extern "C" fn gum_barebone_on_thread_registry_deactivating(_registry: *mut G
|
||||
unsafe { THREAD_REGISTRY = ptr::null_mut() };
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub(crate) fn thread_appeared(id: u32) {
|
||||
announce_thread(id);
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub(crate) fn thread_vanished(id: u32) {
|
||||
let registry = unsafe { THREAD_REGISTRY };
|
||||
if registry.is_null() {
|
||||
@@ -703,17 +746,98 @@ static mut THREAD_REGISTRY: *mut GumThreadRegistry = ptr::null_mut();
|
||||
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_enumerate_threads(func: GumFoundThreadFunc, user_data: gpointer) {
|
||||
pub extern "C" fn gum_barebone_enumerate_threads(func: GumFoundThreadFunc, user_data: gpointer,
|
||||
flags: GumThreadFlags) {
|
||||
let Some(emit) = func else {
|
||||
return;
|
||||
};
|
||||
|
||||
let with_registers = (flags & GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT) != 0;
|
||||
kernel::enumerate_threads(&mut |thread| {
|
||||
let mut details: GumThreadDetails = unsafe { core::mem::zeroed() };
|
||||
details.id = thread.id as GumThreadId;
|
||||
|
||||
if let Some(state) = thread.state {
|
||||
details.flags |= GumThreadFlags_GUM_THREAD_FLAGS_STATE;
|
||||
details.state = state;
|
||||
}
|
||||
|
||||
if let Some(cpu) = thread.cpu_state {
|
||||
details.flags |= GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT;
|
||||
details.cpu_context = cpu_context_from(&cpu);
|
||||
}
|
||||
|
||||
unsafe { emit(&details, user_data) };
|
||||
});
|
||||
}, with_registers);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn cpu_context_from(state: &kernel::CpuState) -> GumCpuContext {
|
||||
GumCpuContext {
|
||||
pc: state.pc,
|
||||
sp: state.sp,
|
||||
nzcv: state.nzcv,
|
||||
x: state.x,
|
||||
fp: state.fp,
|
||||
lr: state.lr,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn cpu_context_from(state: &kernel::CpuState) -> GumCpuContext {
|
||||
GumCpuContext {
|
||||
rip: state.rip,
|
||||
r15: state.r15,
|
||||
r14: state.r14,
|
||||
r13: state.r13,
|
||||
r12: state.r12,
|
||||
r11: state.r11,
|
||||
r10: state.r10,
|
||||
r9: state.r9,
|
||||
r8: state.r8,
|
||||
rdi: state.rdi,
|
||||
rsi: state.rsi,
|
||||
rbp: state.rbp,
|
||||
rsp: state.rsp,
|
||||
rbx: state.rbx,
|
||||
rdx: state.rdx,
|
||||
rcx: state.rcx,
|
||||
rax: state.rax,
|
||||
xmm: ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
fn cpu_context_from(state: &kernel::CpuState) -> GumCpuContext {
|
||||
GumCpuContext {
|
||||
eip: state.eip,
|
||||
edi: state.edi,
|
||||
esi: state.esi,
|
||||
ebp: state.ebp,
|
||||
esp: state.esp,
|
||||
ebx: state.ebx,
|
||||
edx: state.edx,
|
||||
ecx: state.ecx,
|
||||
eax: state.eax,
|
||||
xmm: ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
fn cpu_context_from(state: &kernel::CpuState) -> GumCpuContext {
|
||||
GumCpuContext {
|
||||
cpsr: state.cpsr,
|
||||
pc: state.pc,
|
||||
sp: state.sp,
|
||||
r8: state.r8,
|
||||
r9: state.r9,
|
||||
r10: state.r10,
|
||||
r11: state.r11,
|
||||
r12: state.r12,
|
||||
lr: state.lr,
|
||||
r: state.r,
|
||||
..unsafe { core::mem::zeroed() }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
|
||||
@@ -727,7 +851,7 @@ pub extern "C" fn _gum_process_enumerate_ranges(
|
||||
return;
|
||||
};
|
||||
|
||||
let mut report = |base: u64, size: u64, protection: u32| {
|
||||
let report = |base: u64, size: u64, protection: u32| {
|
||||
if (protection & prot as u32) != prot as u32 {
|
||||
return;
|
||||
}
|
||||
@@ -768,32 +892,49 @@ fn each_kernel_module_range(mut visit: impl FnMut(u64, u64)) {
|
||||
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_query_protection(
|
||||
address: gpointer,
|
||||
pub extern "C" fn gum_memory_query_region(
|
||||
address: gconstpointer,
|
||||
range: *mut GumMemoryRange,
|
||||
prot: *mut GumPageProtection,
|
||||
) -> gboolean {
|
||||
let Some(region) = region_at(address as u64).filter(|region| region.protection != 0) else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
unsafe {
|
||||
*range = GumMemoryRange {
|
||||
base_address: region.base,
|
||||
size: region.size as gsize,
|
||||
};
|
||||
*prot = region.protection as GumPageProtection;
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
|
||||
fn region_at(address: u64) -> Option<kernel::MemoryRegion> {
|
||||
#[cfg(feature = "linux-injected")]
|
||||
if !kernel::in_copy() {
|
||||
let mut protection = 0u32;
|
||||
each_kernel_module_range(|base, size| {
|
||||
if address as u64 >= base && (address as u64) < base + size {
|
||||
protection = KERNEL_RANGE_PROTECTION;
|
||||
}
|
||||
});
|
||||
if protection == 0 {
|
||||
return 0;
|
||||
return kernel_module_region_at(address);
|
||||
}
|
||||
|
||||
kernel::region_at(address)
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
fn kernel_module_region_at(address: u64) -> Option<kernel::MemoryRegion> {
|
||||
let mut found = None;
|
||||
each_kernel_module_range(|base, size| {
|
||||
if address >= base && address < base + size {
|
||||
found = Some(kernel::MemoryRegion {
|
||||
base,
|
||||
size,
|
||||
protection: KERNEL_RANGE_PROTECTION,
|
||||
});
|
||||
}
|
||||
unsafe { *prot = protection as GumPageProtection };
|
||||
return 1;
|
||||
}
|
||||
});
|
||||
|
||||
let protection = kernel::protection_at(address as u64);
|
||||
if protection == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
unsafe { *prot = protection as GumPageProtection };
|
||||
1
|
||||
found
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn enumerate_exports_in_range(
|
||||
@@ -844,3 +985,47 @@ pub(crate) unsafe fn enumerate_exports_in_range(
|
||||
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_thread_get_system_error() -> gint {
|
||||
match errno_slot() {
|
||||
Some(slot) => unsafe { *slot },
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_thread_set_system_error(value: gint) {
|
||||
if let Some(slot) = errno_slot() {
|
||||
unsafe { *slot = value };
|
||||
}
|
||||
}
|
||||
|
||||
// errno is thread-local behind a function that hands back its address:
|
||||
// __errno_location on glibc and musl, __errno on Bionic.
|
||||
#[cfg(feature = "linux-injected")]
|
||||
fn errno_slot() -> Option<*mut gint> {
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
if !crate::kernel::in_copy() {
|
||||
return None;
|
||||
}
|
||||
|
||||
static LOCATE: AtomicUsize = AtomicUsize::new(0);
|
||||
let mut locate = LOCATE.load(Ordering::Relaxed);
|
||||
if locate == 0 {
|
||||
locate = crate::kernel::export_named("__errno_location") as usize;
|
||||
if locate == 0 {
|
||||
locate = crate::kernel::export_named("__errno") as usize;
|
||||
}
|
||||
if locate == 0 {
|
||||
return None;
|
||||
}
|
||||
LOCATE.store(locate, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
let locate: unsafe extern "C" fn() -> *mut gint = unsafe { core::mem::transmute(locate) };
|
||||
Some(unsafe { locate() })
|
||||
}
|
||||
@@ -6,15 +6,14 @@ use crate::{
|
||||
bindings::{
|
||||
_GumPageProtection_GUM_PAGE_EXECUTE, _GumPageProtection_GUM_PAGE_READ,
|
||||
_GumPageProtection_GUM_PAGE_WRITE, _GumRwxSupport_GUM_RWX_NONE, GArray,
|
||||
GumDebugSymbolDetails, GumMemoryRange, GumModuleRegistry, GumPageProtection, GumRwxSupport,
|
||||
g_array_append_vals, g_array_new, g_object_unref, g_pattern_spec_free, g_pattern_spec_match_string,
|
||||
GumDebugSymbolDetails, GumModuleRegistry, GumPageProtection, GumRwxSupport,
|
||||
g_array_append_vals, g_array_new, g_pattern_spec_free, g_pattern_spec_match_string,
|
||||
g_pattern_spec_new, g_strdup, gboolean, gchar, gconstpointer, gpointer, gsize, guint,
|
||||
gum_barebone_register_module, gum_mprotect,
|
||||
gum_mprotect,
|
||||
},
|
||||
gum::{self, FoundExportCallback},
|
||||
kernel,
|
||||
};
|
||||
use alloc::format;
|
||||
use core::ffi::CStr;
|
||||
use core::ptr;
|
||||
|
||||
|
||||
@@ -3,25 +3,240 @@
|
||||
|
||||
use crate::{
|
||||
bindings::{
|
||||
_GumPageProtection_GUM_PAGE_EXECUTE, _GumRwxSupport_GUM_RWX_FULL, GumMemoryRange,
|
||||
GumModuleRegistry, GumPageProtection, GumRwxSupport, g_object_unref, gboolean, gpointer,
|
||||
gsize, guint, gum_barebone_register_module, gum_mprotect, GumFoundRangeFunc,
|
||||
GumFoundThreadFunc, GumModifyThreadFlags, GumModifyThreadFunc, GumRangeDetails,
|
||||
GumThreadDetails, GumThreadFlags, GumThreadId, GumThreadRegistry,
|
||||
gum_thread_details_copy,
|
||||
_GumPageProtection_GUM_PAGE_EXECUTE, _GumRwxSupport_GUM_RWX_FULL, GError,
|
||||
GIOErrorEnum_G_IO_ERROR_BROKEN_PIPE, GIOErrorEnum_G_IO_ERROR_FAILED,
|
||||
GIOErrorEnum_G_IO_ERROR_INVALID_ARGUMENT, GIOErrorEnum_G_IO_ERROR_NOT_FOUND,
|
||||
GIOErrorEnum_G_IO_ERROR_PERMISSION_DENIED, GumBareboneStreamOps, GumMemoryRange,
|
||||
GumModuleRegistry, GumPageProtection, GumRwxSupport, g_free, g_io_error_quark, g_malloc,
|
||||
g_object_unref, g_set_error_literal, g_utf8_to_utf16, g_utf16_to_utf8, gboolean, gchar,
|
||||
gint, gpointer, gsize, gssize, guint, gum_barebone_register_module, gum_mprotect,
|
||||
GumFoundRangeFunc, GumFoundThreadFunc, GumModifyThreadFlags, GumModifyThreadFunc,
|
||||
GumRangeDetails, GumThreadDetails, GumThreadFlags, GumThreadId, GumThreadRegistry,
|
||||
gconstpointer, gum_thread_details_copy,
|
||||
},
|
||||
gum::{self, FoundExportCallback},
|
||||
gum::{self, FoundExportCallback, FoundImportCallback},
|
||||
kernel,
|
||||
};
|
||||
use crate::bindings::{GumCpuContext, GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT};
|
||||
use alloc::ffi::CString;
|
||||
#[cfg(not(feature = "winnt"))]
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use core::ptr;
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
macro_rules! windows_fn {
|
||||
($($argument:ty),* $(,)?) => { unsafe extern "stdcall" fn($($argument),*) };
|
||||
($($argument:ty),* $(,)? => $result:ty) => {
|
||||
unsafe extern "stdcall" fn($($argument),*) -> $result
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
macro_rules! windows_fn {
|
||||
($($argument:ty),* $(,)?) => { unsafe extern "win64" fn($($argument),*) };
|
||||
($($argument:ty),* $(,)? => $result:ty) => {
|
||||
unsafe extern "win64" fn($($argument),*) -> $result
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
macro_rules! windows_fn {
|
||||
($($argument:ty),* $(,)?) => { unsafe extern "C" fn($($argument),*) };
|
||||
($($argument:ty),* $(,)? => $result:ty) => {
|
||||
unsafe extern "C" fn($($argument),*) -> $result
|
||||
};
|
||||
}
|
||||
|
||||
#[allow(unused_imports)]
|
||||
pub(crate) use windows_fn;
|
||||
|
||||
pub struct FileApi {
|
||||
pub read_file: windows_fn!(usize, *mut u8, u32, *mut u32, usize => i32),
|
||||
pub write_file: windows_fn!(usize, *const u8, u32, *mut u32, usize => i32),
|
||||
pub close_handle: windows_fn!(usize => i32),
|
||||
pub format_message: windows_fn!(u32, usize, u32, u32, *mut u8, u32, usize => u32),
|
||||
}
|
||||
|
||||
pub struct TextApi {
|
||||
pub multi_byte_to_wide_char: windows_fn!(u32, u32, *const u8, i32, *mut u16, i32 => i32),
|
||||
pub wide_char_to_multi_byte:
|
||||
windows_fn!(u32, u32, *const u16, i32, *mut u8, i32, usize, usize => i32),
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_query_platform() -> *const crate::bindings::gchar {
|
||||
c"windows".as_ptr() as *const crate::bindings::gchar
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_thread_get_system_error() -> crate::bindings::gint {
|
||||
kernel::thread_error() as crate::bindings::gint
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_thread_set_system_error(value: crate::bindings::gint) {
|
||||
kernel::set_thread_error(value as u32)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_ansi_string_to_utf8(str_ansi: *const gchar, length: gint) -> *mut gchar {
|
||||
let ansi = unsafe { bytes_of(str_ansi as *const u8, length) };
|
||||
match kernel::text_api() {
|
||||
Some(api) => ansi_to_utf8(api, ansi),
|
||||
None => handed_to_glib(ansi.iter().map(|&byte| byte as char).collect::<String>().as_bytes()),
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_ansi_string_from_utf8(str_utf8: *const gchar) -> *mut gchar {
|
||||
match kernel::text_api() {
|
||||
Some(api) => ansi_from_utf8(api, str_utf8),
|
||||
None => {
|
||||
let utf8 = unsafe { core::ffi::CStr::from_ptr(str_utf8) }.to_str().unwrap();
|
||||
let latin1: Vec<u8> =
|
||||
utf8.chars().map(|c| if (c as u32) <= 0xff { c as u8 } else { b'?' }).collect();
|
||||
handed_to_glib(&latin1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn bytes_of<'a>(str: *const u8, length: gint) -> &'a [u8] {
|
||||
let length = if length < 0 {
|
||||
unsafe { core::ffi::CStr::from_ptr(str as *const core::ffi::c_char) }.to_bytes().len()
|
||||
} else {
|
||||
length as usize
|
||||
};
|
||||
unsafe { core::slice::from_raw_parts(str, length) }
|
||||
}
|
||||
|
||||
fn ansi_to_utf8(api: &TextApi, ansi: &[u8]) -> *mut gchar {
|
||||
let count = unsafe {
|
||||
(api.multi_byte_to_wide_char)(CP_ACP, 0, ansi.as_ptr(), ansi.len() as i32,
|
||||
ptr::null_mut(), 0)
|
||||
};
|
||||
let mut wide = alloc::vec![0u16; count as usize];
|
||||
unsafe {
|
||||
(api.multi_byte_to_wide_char)(CP_ACP, 0, ansi.as_ptr(), ansi.len() as i32,
|
||||
wide.as_mut_ptr(), count);
|
||||
g_utf16_to_utf8(wide.as_ptr(), count as _, ptr::null_mut(), ptr::null_mut(),
|
||||
ptr::null_mut())
|
||||
}
|
||||
}
|
||||
|
||||
fn ansi_from_utf8(api: &TextApi, utf8: *const gchar) -> *mut gchar {
|
||||
unsafe {
|
||||
let wide = g_utf8_to_utf16(utf8, -1, ptr::null_mut(), ptr::null_mut(), ptr::null_mut());
|
||||
let size = (api.wide_char_to_multi_byte)(CP_ACP, 0, wide, -1, ptr::null_mut(), 0, 0, 0);
|
||||
let ansi = g_malloc(size as gsize) as *mut u8;
|
||||
(api.wide_char_to_multi_byte)(CP_ACP, 0, wide, -1, ansi, size, 0, 0);
|
||||
g_free(wide as gpointer);
|
||||
ansi as *mut gchar
|
||||
}
|
||||
}
|
||||
|
||||
fn handed_to_glib(bytes: &[u8]) -> *mut gchar {
|
||||
unsafe {
|
||||
let copy = g_malloc((bytes.len() + 1) as gsize) as *mut u8;
|
||||
ptr::copy_nonoverlapping(bytes.as_ptr(), copy, bytes.len());
|
||||
*copy.add(bytes.len()) = 0;
|
||||
copy as *mut gchar
|
||||
}
|
||||
}
|
||||
|
||||
const CP_ACP: u32 = 0;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_query_stream_ops() -> *const GumBareboneStreamOps {
|
||||
if kernel::in_copy() { &STREAM_OPS } else { ptr::null() }
|
||||
}
|
||||
|
||||
static STREAM_OPS: GumBareboneStreamOps = GumBareboneStreamOps {
|
||||
poll: Some(kernel::poll_handles),
|
||||
read: Some(read_handle),
|
||||
write: Some(write_handle),
|
||||
close: Some(close_handle),
|
||||
};
|
||||
|
||||
unsafe extern "C" fn read_handle(handle: gpointer, buffer: gpointer, count: gsize,
|
||||
error: *mut *mut GError) -> gssize {
|
||||
let mut transferred = 0u32;
|
||||
let succeeded = unsafe {
|
||||
(kernel::file_api().read_file)(handle as usize, buffer as *mut u8, count as u32,
|
||||
&mut transferred, 0)
|
||||
};
|
||||
if succeeded == 0 {
|
||||
return failed(error);
|
||||
}
|
||||
transferred as gssize
|
||||
}
|
||||
|
||||
unsafe extern "C" fn write_handle(handle: gpointer, buffer: gconstpointer, count: gsize,
|
||||
error: *mut *mut GError) -> gssize {
|
||||
let mut transferred = 0u32;
|
||||
let succeeded = unsafe {
|
||||
(kernel::file_api().write_file)(handle as usize, buffer as *const u8, count as u32,
|
||||
&mut transferred, 0)
|
||||
};
|
||||
if succeeded == 0 {
|
||||
return failed(error);
|
||||
}
|
||||
transferred as gssize
|
||||
}
|
||||
|
||||
unsafe extern "C" fn close_handle(handle: gpointer, error: *mut *mut GError) -> gboolean {
|
||||
let succeeded = unsafe { (kernel::file_api().close_handle)(handle as usize) };
|
||||
if succeeded == 0 {
|
||||
failed(error);
|
||||
return 0;
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
fn failed(error: *mut *mut GError) -> gssize {
|
||||
let code = kernel::thread_error();
|
||||
let message = CString::new(describe_error(code)).unwrap();
|
||||
unsafe {
|
||||
g_set_error_literal(error, g_io_error_quark(), io_error_from_win32(code) as gint,
|
||||
message.as_ptr())
|
||||
};
|
||||
-1
|
||||
}
|
||||
|
||||
fn describe_error(code: u32) -> String {
|
||||
let mut ansi = [0u8; 512];
|
||||
let length = unsafe {
|
||||
(kernel::file_api().format_message)(
|
||||
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, code, 0,
|
||||
ansi.as_mut_ptr(), ansi.len() as u32, 0)
|
||||
};
|
||||
let utf8 = ansi_to_utf8(kernel::text_api().unwrap(), &ansi[..length as usize]);
|
||||
let message = unsafe { core::ffi::CStr::from_ptr(utf8) }.to_str().unwrap().trim_end();
|
||||
let described = String::from(message);
|
||||
unsafe { g_free(utf8 as gpointer) };
|
||||
described
|
||||
}
|
||||
|
||||
fn io_error_from_win32(code: u32) -> u32 {
|
||||
match code {
|
||||
ERROR_FILE_NOT_FOUND | ERROR_PATH_NOT_FOUND => GIOErrorEnum_G_IO_ERROR_NOT_FOUND,
|
||||
ERROR_ACCESS_DENIED => GIOErrorEnum_G_IO_ERROR_PERMISSION_DENIED,
|
||||
ERROR_INVALID_HANDLE | ERROR_INVALID_PARAMETER => GIOErrorEnum_G_IO_ERROR_INVALID_ARGUMENT,
|
||||
ERROR_BROKEN_PIPE => GIOErrorEnum_G_IO_ERROR_BROKEN_PIPE,
|
||||
_ => GIOErrorEnum_G_IO_ERROR_FAILED,
|
||||
}
|
||||
}
|
||||
|
||||
const FORMAT_MESSAGE_IGNORE_INSERTS: u32 = 0x200;
|
||||
const FORMAT_MESSAGE_FROM_SYSTEM: u32 = 0x1000;
|
||||
const ERROR_FILE_NOT_FOUND: u32 = 2;
|
||||
const ERROR_PATH_NOT_FOUND: u32 = 3;
|
||||
const ERROR_ACCESS_DENIED: u32 = 5;
|
||||
const ERROR_INVALID_HANDLE: u32 = 6;
|
||||
const ERROR_INVALID_PARAMETER: u32 = 87;
|
||||
const ERROR_BROKEN_PIPE: u32 = 109;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_query_stack_size() -> crate::bindings::gsize {
|
||||
if crate::on_js_thread() {
|
||||
@@ -70,16 +285,23 @@ pub extern "C" fn gum_memory_dispose_writable_pages(_writable: gpointer, _n_page
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_query_protection(
|
||||
address: gpointer,
|
||||
pub extern "C" fn gum_memory_query_region(
|
||||
address: gconstpointer,
|
||||
range: *mut GumMemoryRange,
|
||||
prot: *mut GumPageProtection,
|
||||
) -> gboolean {
|
||||
let protection = kernel::protection_at(address as usize);
|
||||
if protection == 0 {
|
||||
let Some(region) = kernel::region_at(address as usize).filter(|region| region.protection != 0)
|
||||
else {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
unsafe { *prot = protection as GumPageProtection };
|
||||
unsafe {
|
||||
*range = GumMemoryRange {
|
||||
base_address: region.base,
|
||||
size: region.size as gsize,
|
||||
};
|
||||
*prot = region.protection as GumPageProtection;
|
||||
}
|
||||
1
|
||||
}
|
||||
|
||||
@@ -130,26 +352,10 @@ pub extern "C" fn gum_memory_free(address: gpointer, size: gsize) -> gboolean {
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
if crate::running_in_a_process() {
|
||||
register_the_process_modules(registry);
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let modules = &*core::ptr::addr_of!(crate::MODULE_INFO);
|
||||
|
||||
for module_info in modules.iter() {
|
||||
let path = format!("{}{}", kernel::MODULE_DIRECTORY, module_info.name);
|
||||
let range = GumMemoryRange {
|
||||
base_address: module_info.offset as u64,
|
||||
size: module_info.size as gsize,
|
||||
};
|
||||
|
||||
let module = gum::gum_native_module_new(&path, &module_info.version, &range);
|
||||
gum_barebone_register_module(registry, module);
|
||||
g_object_unref(module as gpointer);
|
||||
}
|
||||
} else {
|
||||
register_the_kernel_modules(registry);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +384,7 @@ pub(crate) fn watch_the_loader() {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
#[cfg(feature = "winnt")]
|
||||
pub(crate) fn forget_the_loader() {
|
||||
let Some(entries) = kernel::loader_entry_points() else {
|
||||
return;
|
||||
@@ -202,7 +408,7 @@ pub(crate) fn loader_load() -> gpointer {
|
||||
unsafe { LOADER_LOAD }
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
#[cfg(feature = "win9x")]
|
||||
pub(crate) fn loader_load_with_flags() -> gpointer {
|
||||
unsafe { LOADER_LOAD_WITH_FLAGS }
|
||||
}
|
||||
@@ -298,19 +504,41 @@ static mut LOADER_UNLOAD: gpointer = ptr::null_mut();
|
||||
|
||||
fn register_the_process_modules(registry: *mut GumModuleRegistry) {
|
||||
for module in kernel::enumerate_modules() {
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
known_mut().insert(module.base);
|
||||
|
||||
let range = GumMemoryRange {
|
||||
base_address: module.base,
|
||||
size: module.size as gsize,
|
||||
};
|
||||
register_module(registry, &module.path, "", module.base, module.size);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let native = gum::gum_native_module_new(&module.path, "", &range);
|
||||
gum_barebone_register_module(registry, native);
|
||||
g_object_unref(native as gpointer);
|
||||
}
|
||||
fn register_the_kernel_modules(registry: *mut GumModuleRegistry) {
|
||||
#[cfg(feature = "winnt")]
|
||||
for module in crate::winnt::enumerate_kernel_modules() {
|
||||
register_module(registry, &module.path, "", module.base, module.size);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "winnt"))]
|
||||
for module_info in unsafe { &*core::ptr::addr_of!(crate::MODULE_INFO) }.iter() {
|
||||
let path = format!("{}{}", kernel::MODULE_DIRECTORY, module_info.name);
|
||||
register_module(
|
||||
registry,
|
||||
&path,
|
||||
&module_info.version,
|
||||
module_info.offset as u64,
|
||||
module_info.size as u64,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn register_module(registry: *mut GumModuleRegistry, path: &str, version: &str, base: u64, size: u64) {
|
||||
let range = GumMemoryRange {
|
||||
base_address: base,
|
||||
size: size as gsize,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let module = gum::gum_native_module_new(path, version, &range);
|
||||
gum_barebone_register_module(registry, module);
|
||||
g_object_unref(module as gpointer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,7 +575,7 @@ pub extern "C" fn _gum_process_enumerate_ranges(
|
||||
pub extern "C" fn gum_barebone_on_thread_registry_activating(registry: *mut GumThreadRegistry) {
|
||||
unsafe { THREAD_REGISTRY = registry };
|
||||
|
||||
kernel::enumerate_threads(&mut |thread| announce_thread(thread.id));
|
||||
kernel::enumerate_threads(&mut |thread| announce_thread(thread.id), false);
|
||||
|
||||
if kernel::watches_threads() {
|
||||
kernel::watch_threads(announce_thread, announce_thread_is_gone);
|
||||
@@ -398,10 +626,6 @@ fn forget_the_threads() {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn thread_appeared(id: u32) {
|
||||
announce_thread(id);
|
||||
}
|
||||
|
||||
pub(crate) fn thread_appeared_at(id: u32, routine: usize, parameter: usize) {
|
||||
let registry = unsafe { THREAD_REGISTRY };
|
||||
if registry.is_null() {
|
||||
@@ -446,11 +670,13 @@ fn announce_thread_is_gone(id: u32) {
|
||||
static mut THREAD_REGISTRY: *mut GumThreadRegistry = ptr::null_mut();
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_enumerate_threads(func: GumFoundThreadFunc, user_data: gpointer) {
|
||||
pub extern "C" fn gum_barebone_enumerate_threads(func: GumFoundThreadFunc, user_data: gpointer,
|
||||
flags: GumThreadFlags) {
|
||||
let Some(emit) = func else {
|
||||
return;
|
||||
};
|
||||
|
||||
let wanted = (flags & GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT) != 0;
|
||||
kernel::enumerate_threads(&mut |thread| {
|
||||
let mut details: GumThreadDetails = unsafe { core::mem::zeroed() };
|
||||
details.flags = 0;
|
||||
@@ -462,13 +688,14 @@ pub extern "C" fn gum_barebone_enumerate_threads(func: GumFoundThreadFunc, user_
|
||||
}
|
||||
|
||||
unsafe { emit(&details, user_data) };
|
||||
});
|
||||
}, wanted);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_find_thread_by_id(thread_id: GumThreadId, _flags: GumThreadFlags)
|
||||
pub extern "C" fn gum_barebone_find_thread_by_id(thread_id: GumThreadId, flags: GumThreadFlags)
|
||||
-> *mut GumThreadDetails {
|
||||
let Some(thread) = kernel::find_thread(thread_id as u32) else {
|
||||
let wanted = (flags & GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT) != 0;
|
||||
let Some(thread) = kernel::find_thread(thread_id as u32, wanted) else {
|
||||
return ptr::null_mut();
|
||||
};
|
||||
|
||||
@@ -577,6 +804,30 @@ pub(crate) fn cpu_state_from(context: &GumCpuContext) -> kernel::CpuState {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub(crate) fn cpu_context_from(state: &kernel::CpuState) -> GumCpuContext {
|
||||
GumCpuContext {
|
||||
pc: state.pc,
|
||||
sp: state.sp,
|
||||
nzcv: state.nzcv,
|
||||
x: state.x,
|
||||
fp: state.fp,
|
||||
lr: state.lr,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub(crate) fn cpu_state_from(context: &GumCpuContext) -> kernel::CpuState {
|
||||
kernel::CpuState {
|
||||
pc: context.pc,
|
||||
sp: context.sp,
|
||||
nzcv: context.nzcv,
|
||||
x: context.x,
|
||||
fp: context.fp,
|
||||
lr: context.lr,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn fault_type_of(code: u32) -> crate::bindings::GumExceptionType {
|
||||
use crate::bindings::*;
|
||||
|
||||
@@ -616,6 +867,14 @@ const INTEGER_OVERFLOW: u32 = 0xc000_0095;
|
||||
const BREAKPOINT: u32 = 0x8000_0003;
|
||||
const SINGLE_STEP: u32 = 0x8000_0004;
|
||||
|
||||
pub(crate) unsafe fn enumerate_imports_in_module(base: u64, callback: &mut FoundImportCallback) {
|
||||
if crate::running_in_a_process() {
|
||||
kernel::enumerate_imports(base as _, &mut |library, name, slot, address| {
|
||||
callback(library as *const crate::bindings::gchar, name as *const crate::bindings::gchar, slot, address)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn enumerate_exports_in_range(
|
||||
start_address: u64,
|
||||
end_address: u64,
|
||||
|
||||
@@ -101,6 +101,7 @@ impl Drop for Hostlink {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
pub fn a_turn_is_wanted() -> bool {
|
||||
unsafe { frida_kmod_link_pending() }
|
||||
}
|
||||
@@ -115,5 +116,6 @@ unsafe extern "C" {
|
||||
fn frida_kmod_link_close();
|
||||
fn frida_kmod_link_send(data: *const c_void, size: usize) -> c_int;
|
||||
fn frida_kmod_link_recv(data: *mut c_void, size: usize) -> isize;
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
fn frida_kmod_link_pending() -> bool;
|
||||
}
|
||||
@@ -0,0 +1,441 @@
|
||||
|
||||
use core::cell::UnsafeCell;
|
||||
use core::ffi::c_void;
|
||||
use core::ptr;
|
||||
|
||||
use alloc::collections::VecDeque;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::kernel;
|
||||
use crate::winnt::windows_fn;
|
||||
|
||||
pub struct Hostlink {
|
||||
state: UnsafeCell<Inner>,
|
||||
on_rx: Option<fn(&[u8])>,
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
port: *mut c_void,
|
||||
event: *mut c_void,
|
||||
}
|
||||
|
||||
fn issue<F>(event: *mut c_void, request: F) -> Option<usize>
|
||||
where
|
||||
F: FnOnce(*mut c_void, *mut c_void) -> i32,
|
||||
{
|
||||
let mut status_block = [0usize; STATUS_BLOCK_WORDS];
|
||||
unsafe {
|
||||
(_ZwClearEvent)(event);
|
||||
}
|
||||
|
||||
let status = request(event, status_block.as_mut_ptr() as *mut c_void);
|
||||
if status == STATUS_PENDING {
|
||||
unsafe {
|
||||
(_ZwWaitForSingleObject)(event, 0, ptr::null());
|
||||
}
|
||||
}
|
||||
|
||||
if status < 0 || (status_block[STATUS_BLOCK_STATUS] as i32) < 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(status_block[STATUS_BLOCK_COUNT])
|
||||
}
|
||||
|
||||
fn create_event() -> Option<*mut c_void> {
|
||||
let mut event: *mut c_void = ptr::null_mut();
|
||||
let status = unsafe {
|
||||
(_ZwCreateEvent)(&mut event, EVENT_ALL_ACCESS, ptr::null_mut(), SYNCHRONIZATION_EVENT, 0)
|
||||
};
|
||||
if status < 0 {
|
||||
return None;
|
||||
}
|
||||
Some(event)
|
||||
}
|
||||
|
||||
impl Hostlink {
|
||||
pub fn init(on_rx: Option<fn(&[u8])>, wake_token: *const u8) -> Result<Self, ()> {
|
||||
let port = open_first_serial_port()?;
|
||||
let event = create_event().ok_or(())?;
|
||||
let reader_event = create_event().ok_or(())?;
|
||||
|
||||
unsafe {
|
||||
WAKE_TOKEN = wake_token;
|
||||
READER_PORT = port;
|
||||
READER_EVENT = reader_event;
|
||||
}
|
||||
kernel::spawn_thread(read_from_host, ptr::null_mut());
|
||||
|
||||
Ok(Self {
|
||||
state: UnsafeCell::new(Inner { port, event }),
|
||||
on_rx,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn send(&self, payload: &[u8]) {
|
||||
let s = unsafe { &*self.state.get() };
|
||||
|
||||
write_all(s.port, s.event, &(payload.len() as u32).to_le_bytes());
|
||||
write_all(s.port, s.event, payload);
|
||||
}
|
||||
|
||||
pub fn process(&self) {
|
||||
loop {
|
||||
let Some(frame) = take_frame() else {
|
||||
return;
|
||||
};
|
||||
if let Some(on_rx) = self.on_rx {
|
||||
on_rx(&frame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shutdown(&self) {
|
||||
let s = unsafe { &*self.state.get() };
|
||||
unsafe {
|
||||
(_ZwClose)(s.port);
|
||||
(_ZwClose)(s.event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn read_from_host(_parameter: *mut c_void, _wait_result: i32) {
|
||||
let port = unsafe { READER_PORT };
|
||||
let event = unsafe { READER_EVENT };
|
||||
|
||||
let mut length = [0u8; FRAME_LENGTH_SIZE];
|
||||
loop {
|
||||
if !read_exactly(port, event, &mut length) {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut frame = alloc::vec![0u8; u32::from_le_bytes(length) as usize];
|
||||
if !read_exactly(port, event, &mut frame) {
|
||||
return;
|
||||
}
|
||||
|
||||
put_frame(frame);
|
||||
kernel::wake(unsafe { WAKE_TOKEN });
|
||||
}
|
||||
}
|
||||
|
||||
fn read_exactly(port: *mut c_void, event: *mut c_void, buffer: &mut [u8]) -> bool {
|
||||
let mut read = 0;
|
||||
while read != buffer.len() {
|
||||
let mut offset = 0i64;
|
||||
let moved = issue(event, |event, status_block| unsafe {
|
||||
(_ZwReadFile)(
|
||||
port,
|
||||
event,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
status_block,
|
||||
buffer.as_mut_ptr().add(read),
|
||||
(buffer.len() - read) as u32,
|
||||
&mut offset,
|
||||
ptr::null_mut(),
|
||||
)
|
||||
});
|
||||
|
||||
let Some(moved) = moved else {
|
||||
return false;
|
||||
};
|
||||
if moved == 0 {
|
||||
return false;
|
||||
}
|
||||
read += moved;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn write_all(port: *mut c_void, event: *mut c_void, buffer: &[u8]) {
|
||||
let mut written = 0;
|
||||
while written != buffer.len() {
|
||||
let mut offset = 0i64;
|
||||
let moved = issue(event, |event, status_block| unsafe {
|
||||
(_ZwWriteFile)(
|
||||
port,
|
||||
event,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
status_block,
|
||||
buffer.as_ptr().add(written),
|
||||
(buffer.len() - written) as u32,
|
||||
&mut offset,
|
||||
ptr::null_mut(),
|
||||
)
|
||||
});
|
||||
|
||||
let Some(moved) = moved else {
|
||||
return;
|
||||
};
|
||||
if moved == 0 {
|
||||
return;
|
||||
}
|
||||
written += moved;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn a_turn_is_wanted() -> bool {
|
||||
A_TURN_IS_WANTED.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
static A_TURN_IS_WANTED: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
fn put_frame(frame: Vec<u8>) {
|
||||
lock_frames();
|
||||
unsafe {
|
||||
(*ptr::addr_of_mut!(FRAMES)).push_back(frame);
|
||||
}
|
||||
unlock_frames();
|
||||
A_TURN_IS_WANTED.store(true, Ordering::Release);
|
||||
}
|
||||
|
||||
fn take_frame() -> Option<Vec<u8>> {
|
||||
lock_frames();
|
||||
let frame = unsafe { (*ptr::addr_of_mut!(FRAMES)).pop_front() };
|
||||
let drained = unsafe { (*ptr::addr_of!(FRAMES)).is_empty() };
|
||||
unlock_frames();
|
||||
if drained {
|
||||
A_TURN_IS_WANTED.store(false, Ordering::Release);
|
||||
}
|
||||
frame
|
||||
}
|
||||
|
||||
fn lock_frames() {
|
||||
while FRAMES_LOCK
|
||||
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
kernel::yield_now();
|
||||
}
|
||||
}
|
||||
|
||||
fn unlock_frames() {
|
||||
FRAMES_LOCK.store(0, Ordering::Release);
|
||||
}
|
||||
|
||||
static mut FRAMES: VecDeque<Vec<u8>> = VecDeque::new();
|
||||
static FRAMES_LOCK: AtomicU32 = AtomicU32::new(0);
|
||||
static mut WAKE_TOKEN: *const u8 = ptr::null();
|
||||
static mut READER_PORT: *mut c_void = ptr::null_mut();
|
||||
static mut READER_EVENT: *mut c_void = ptr::null_mut();
|
||||
|
||||
fn open_first_serial_port() -> Result<*mut c_void, ()> {
|
||||
let ports = open_key(SERIAL_PORT_KEY)?;
|
||||
|
||||
let mut name = [0u8; VALUE_NAME_SIZE];
|
||||
let mut size = 0u32;
|
||||
let status = unsafe {
|
||||
(_ZwEnumerateValueKey)(ports, 0, VALUE_NAME_INFORMATION, name.as_mut_ptr(),
|
||||
name.len() as u32, &mut size)
|
||||
};
|
||||
unsafe {
|
||||
(_ZwClose)(ports);
|
||||
}
|
||||
if status < 0 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let length = u32::from_le_bytes(name[VALUE_NAME_LENGTH..VALUE_NAME_LENGTH + 4]
|
||||
.try_into()
|
||||
.unwrap()) as usize;
|
||||
open_device(&name[VALUE_NAME_OFFSET..VALUE_NAME_OFFSET + length])
|
||||
}
|
||||
|
||||
fn open_key(path: &[u16]) -> Result<*mut c_void, ()> {
|
||||
let mut handle: *mut c_void = ptr::null_mut();
|
||||
let mut name = unicode_string(path);
|
||||
let mut attributes = object_attributes(&mut name);
|
||||
|
||||
let status =
|
||||
unsafe { (_ZwOpenKey)(&mut handle, KEY_READ, attributes.as_mut_ptr() as *mut c_void) };
|
||||
if status < 0 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
fn open_device(path: &[u8]) -> Result<*mut c_void, ()> {
|
||||
let mut handle: *mut c_void = ptr::null_mut();
|
||||
let mut name = UnicodeString {
|
||||
length: path.len() as u16,
|
||||
maximum_length: path.len() as u16,
|
||||
buffer: path.as_ptr() as *const u16,
|
||||
};
|
||||
let mut attributes = object_attributes(&mut name);
|
||||
let mut status_block = [0usize; STATUS_BLOCK_WORDS];
|
||||
|
||||
let status = unsafe {
|
||||
(_ZwCreateFile)(
|
||||
&mut handle,
|
||||
GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE,
|
||||
attributes.as_mut_ptr() as *mut c_void,
|
||||
status_block.as_mut_ptr() as *mut c_void,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
0,
|
||||
FILE_OPEN,
|
||||
0,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
)
|
||||
};
|
||||
if status < 0 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
configure_port(handle);
|
||||
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
fn configure_port(port: *mut c_void) {
|
||||
let mut baud_rate = [0u32; 1];
|
||||
baud_rate[0] = BAUD_RATE;
|
||||
control_port(port, SET_BAUD_RATE, baud_rate.as_ptr() as *const u8,
|
||||
core::mem::size_of_val(&baud_rate) as u32);
|
||||
|
||||
let line_control = [ONE_STOP_BIT, NO_PARITY, DATA_BITS];
|
||||
control_port(port, SET_LINE_CONTROL, line_control.as_ptr(),
|
||||
core::mem::size_of_val(&line_control) as u32);
|
||||
|
||||
let hand_flow = [0u32; HAND_FLOW_WORDS];
|
||||
control_port(port, SET_HAND_FLOW, hand_flow.as_ptr() as *const u8,
|
||||
core::mem::size_of_val(&hand_flow) as u32);
|
||||
|
||||
control_port(port, SET_DTR, core::ptr::null(), 0);
|
||||
control_port(port, SET_RTS, core::ptr::null(), 0);
|
||||
|
||||
let mut timeouts = [0u32; TIMEOUT_WORDS];
|
||||
timeouts[READ_INTERVAL_TIMEOUT] = FOREVER;
|
||||
timeouts[READ_TOTAL_TIMEOUT_MULTIPLIER] = FOREVER;
|
||||
timeouts[READ_TOTAL_TIMEOUT_CONSTANT] = ALMOST_FOREVER;
|
||||
control_port(port, SET_TIMEOUTS, timeouts.as_ptr() as *const u8,
|
||||
core::mem::size_of_val(&timeouts) as u32);
|
||||
}
|
||||
|
||||
fn control_port(port: *mut c_void, code: u32, input: *const u8, length: u32) {
|
||||
let Some(event) = create_event() else {
|
||||
return;
|
||||
};
|
||||
|
||||
issue(event, |event, status_block| unsafe {
|
||||
(_ZwDeviceIoControlFile)(
|
||||
port,
|
||||
event,
|
||||
ptr::null_mut(),
|
||||
ptr::null_mut(),
|
||||
status_block,
|
||||
code,
|
||||
input,
|
||||
length,
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
)
|
||||
});
|
||||
|
||||
unsafe {
|
||||
(_ZwClose)(event);
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct UnicodeString {
|
||||
length: u16,
|
||||
maximum_length: u16,
|
||||
buffer: *const u16,
|
||||
}
|
||||
|
||||
fn unicode_string(text: &[u16]) -> UnicodeString {
|
||||
let bytes = (text.len() * 2) as u16;
|
||||
UnicodeString {
|
||||
length: bytes,
|
||||
maximum_length: bytes,
|
||||
buffer: text.as_ptr(),
|
||||
}
|
||||
}
|
||||
|
||||
fn object_attributes(name: &mut UnicodeString) -> [usize; OBJECT_ATTRIBUTES_WORDS] {
|
||||
let mut attributes = [0usize; OBJECT_ATTRIBUTES_WORDS];
|
||||
attributes[0] = core::mem::size_of::<[usize; OBJECT_ATTRIBUTES_WORDS]>();
|
||||
attributes[2] = name as *mut UnicodeString as usize;
|
||||
attributes[3] = OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE;
|
||||
attributes
|
||||
}
|
||||
|
||||
const FRAME_LENGTH_SIZE: usize = 4;
|
||||
const STATUS_BLOCK_WORDS: usize = 2;
|
||||
const STATUS_BLOCK_COUNT: usize = 1;
|
||||
const STATUS_BLOCK_STATUS: usize = 0;
|
||||
const OBJECT_ATTRIBUTES_WORDS: usize = 6;
|
||||
|
||||
const SERIAL_PORT_KEY: &[u16] = &[
|
||||
0x5c, 0x52, 0x45, 0x47, 0x49, 0x53, 0x54, 0x52, 0x59, 0x5c, 0x4d, 0x41, 0x43, 0x48, 0x49, 0x4e,
|
||||
0x45, 0x5c, 0x48, 0x41, 0x52, 0x44, 0x57, 0x41, 0x52, 0x45, 0x5c, 0x44, 0x45, 0x56, 0x49, 0x43,
|
||||
0x45, 0x4d, 0x41, 0x50, 0x5c, 0x53, 0x45, 0x52, 0x49, 0x41, 0x4c, 0x43, 0x4f, 0x4d, 0x4d,
|
||||
];
|
||||
|
||||
const VALUE_NAME_INFORMATION: u32 = 0;
|
||||
const VALUE_NAME_SIZE: usize = 512;
|
||||
const VALUE_NAME_LENGTH: usize = 0x08;
|
||||
const VALUE_NAME_OFFSET: usize = 0x0c;
|
||||
|
||||
const SET_BAUD_RATE: u32 = 0x001b_0004;
|
||||
const SET_LINE_CONTROL: u32 = 0x001b_000c;
|
||||
const SET_TIMEOUTS: u32 = 0x001b_001c;
|
||||
const SET_DTR: u32 = 0x001b_0024;
|
||||
const SET_RTS: u32 = 0x001b_0030;
|
||||
const SET_HAND_FLOW: u32 = 0x001b_0064;
|
||||
|
||||
const BAUD_RATE: u32 = 921_600;
|
||||
const DATA_BITS: u8 = 8;
|
||||
const NO_PARITY: u8 = 0;
|
||||
const ONE_STOP_BIT: u8 = 0;
|
||||
const HAND_FLOW_WORDS: usize = 4;
|
||||
const TIMEOUT_WORDS: usize = 5;
|
||||
const READ_INTERVAL_TIMEOUT: usize = 0;
|
||||
const READ_TOTAL_TIMEOUT_MULTIPLIER: usize = 1;
|
||||
const READ_TOTAL_TIMEOUT_CONSTANT: usize = 2;
|
||||
const FOREVER: u32 = u32::MAX;
|
||||
const ALMOST_FOREVER: u32 = u32::MAX - 1;
|
||||
const STATUS_PENDING: i32 = 0x103;
|
||||
const SYNCHRONIZATION_EVENT: u32 = 1;
|
||||
const EVENT_ALL_ACCESS: u32 = 0x1f_0003;
|
||||
|
||||
const KEY_READ: u32 = 0x2_0019;
|
||||
const GENERIC_READ: u32 = 0x8000_0000;
|
||||
const GENERIC_WRITE: u32 = 0x4000_0000;
|
||||
const SYNCHRONIZE: u32 = 0x0010_0000;
|
||||
const FILE_OPEN: u32 = 1;
|
||||
const OBJ_CASE_INSENSITIVE: usize = 0x40;
|
||||
const OBJ_KERNEL_HANDLE: usize = 0x200;
|
||||
|
||||
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
|
||||
|
||||
unsafe extern "C" {
|
||||
static _ZwOpenKey: windows_fn!(*mut *mut c_void, u32, *mut c_void => i32);
|
||||
static _ZwEnumerateValueKey: windows_fn!(
|
||||
*mut c_void, u32, u32, *mut u8, u32, *mut u32 => i32);
|
||||
static _ZwCreateFile: windows_fn!(
|
||||
*mut *mut c_void, u32, *mut c_void, *mut c_void, *mut i64, u32, u32, u32, u32,
|
||||
*mut c_void, u32 => i32);
|
||||
static _ZwReadFile: windows_fn!(
|
||||
*mut c_void, *mut c_void, *mut c_void, *mut c_void, *mut c_void, *mut u8, u32, *mut i64,
|
||||
*mut u32 => i32);
|
||||
static _ZwWriteFile: windows_fn!(
|
||||
*mut c_void, *mut c_void, *mut c_void, *mut c_void, *mut c_void, *const u8, u32, *mut i64,
|
||||
*mut u32 => i32);
|
||||
static _ZwDeviceIoControlFile: windows_fn!(
|
||||
*mut c_void, *mut c_void, *mut c_void, *mut c_void, *mut c_void, u32, *const u8, u32,
|
||||
*mut u8, u32 => i32);
|
||||
static _ZwClose: windows_fn!(*mut c_void => i32);
|
||||
static _ZwCreateEvent: windows_fn!(*mut *mut c_void, u32, *mut c_void, u32, u8 => i32);
|
||||
static _ZwClearEvent: windows_fn!(*mut c_void => i32);
|
||||
static _ZwWaitForSingleObject: windows_fn!(*mut c_void, u8, *const i64 => i32);
|
||||
static _ObReferenceObjectByHandle: windows_fn!(
|
||||
*mut c_void, u32, *mut c_void, u8, *mut *mut c_void, *mut c_void => i32);
|
||||
static _KeWaitForSingleObject: windows_fn!(*mut c_void, u32, u32, u8, *const i64 => i32);
|
||||
}
|
||||
@@ -51,7 +51,9 @@ const F_MULTIPORT: u64 = 1u64 << 1;
|
||||
|
||||
const INT_VRING: u32 = 1;
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
const PCI_CONFIG_ADDRESS: u16 = 0xcf8;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
const PCI_CONFIG_DATA: u16 = 0xcfc;
|
||||
|
||||
const PCI_VENDOR_VIRTIO: u16 = 0x1af4;
|
||||
@@ -62,14 +64,18 @@ const PCI_COMMAND: u8 = 0x04;
|
||||
const PCI_CAP_LIST_POINTER: u8 = 0x34;
|
||||
const PCI_INTERRUPT_LINE: u8 = 0x3c;
|
||||
const PCI_BASE_ADDRESS_0: u8 = 0x10;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
const PCI_INTERRUPT_PIN: u8 = 0x3d;
|
||||
const PCI_COMMAND_INTX_DISABLE: u32 = 0x400;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
const ISA_BRIDGE_DEVFN: u8 = 0x08;
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
const ECAM_DEVFN_SHIFT: usize = 12;
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
const ECAM_BUS_SIZE: u64 = 256 * 4096;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
const PIRQ_ROUTE: u8 = 0x60;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
const PIRQ_DISABLED: u32 = 1 << 7;
|
||||
|
||||
const PCI_COMMAND_MEMORY: u32 = 1 << 1;
|
||||
@@ -185,6 +191,8 @@ struct Vq {
|
||||
}
|
||||
impl Vq {
|
||||
fn new(regs: &Regs, sel: u16, size: u16) -> Self {
|
||||
let size = size.min(regs.queue_max(sel));
|
||||
|
||||
let d = dma_page_alloc();
|
||||
let a = dma_page_alloc();
|
||||
let u = dma_page_alloc();
|
||||
@@ -475,7 +483,7 @@ impl Hostlink {
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
|
||||
A_TURN_IS_WANTED.store(false, core::sync::atomic::Ordering::Release);
|
||||
s.regs.isr_ack();
|
||||
s.regs.interrupt_is_ours();
|
||||
|
||||
self.ctrl_complete();
|
||||
self.ctrl_prime_rx(QSZ as usize);
|
||||
@@ -848,15 +856,24 @@ impl Hostlink {
|
||||
}
|
||||
|
||||
extern "C" fn isr_wake(token: *mut c_void, _refcon: *mut c_void, _nub: *mut c_void, _src: i32) {
|
||||
unsafe {
|
||||
if let Some(regs) = ISR_REGS {
|
||||
regs.isr_ack();
|
||||
let up = unsafe {
|
||||
match ISR_REGS {
|
||||
Some(regs) => {
|
||||
regs.interrupt_is_ours();
|
||||
true
|
||||
}
|
||||
None => false,
|
||||
}
|
||||
};
|
||||
if !up {
|
||||
return;
|
||||
}
|
||||
|
||||
A_TURN_IS_WANTED.store(true, core::sync::atomic::Ordering::Release);
|
||||
crate::nudge_the_loop(token as *const u8);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "xnu-core"))]
|
||||
pub fn a_turn_is_wanted() -> bool {
|
||||
A_TURN_IS_WANTED.load(core::sync::atomic::Ordering::Acquire)
|
||||
}
|
||||
@@ -893,9 +910,7 @@ impl Regs {
|
||||
while r8(p.common, COMMON_STATUS) != 0 {
|
||||
left -= 1;
|
||||
if left == 0 {
|
||||
unsafe {
|
||||
kernel::log("virtio: reset not taken\n");
|
||||
}
|
||||
kernel::log("virtio: reset not taken\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -944,12 +959,25 @@ impl Regs {
|
||||
}
|
||||
}
|
||||
|
||||
fn queue_max(&self, sel: u16) -> u16 {
|
||||
match self {
|
||||
Regs::Mmio(base) => {
|
||||
let base = *base;
|
||||
w32(base, QSEL, sel as u32);
|
||||
r32(base, QNUM_MAX) as u16
|
||||
}
|
||||
Regs::Pci(p) => {
|
||||
w16(p.common, COMMON_QSEL, sel);
|
||||
r16(p.common, COMMON_QNUM)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn queue_prepare(&self, sel: u16, size: u16, desc: u64, avail: u64, used: u64) -> u16 {
|
||||
match self {
|
||||
Regs::Mmio(base) => {
|
||||
let base = *base;
|
||||
w32(base, QSEL, sel as u32);
|
||||
debug_assert!(r32(base, QNUM_MAX) as u16 >= size);
|
||||
w32(base, QNUM, size as u32);
|
||||
w64(base, QDESC_LO, QDESC_HI, desc);
|
||||
w64(base, QAVAIL_LO, QAVAIL_HI, avail);
|
||||
@@ -959,7 +987,6 @@ impl Regs {
|
||||
}
|
||||
Regs::Pci(p) => {
|
||||
w16(p.common, COMMON_QSEL, sel);
|
||||
debug_assert!(r16(p.common, COMMON_QNUM) >= size);
|
||||
w16(p.common, COMMON_QNUM, size);
|
||||
w64(p.common, COMMON_QDESC, COMMON_QDESC + 4, desc);
|
||||
w64(p.common, COMMON_QAVAIL, COMMON_QAVAIL + 4, avail);
|
||||
@@ -984,21 +1011,22 @@ impl Regs {
|
||||
}
|
||||
}
|
||||
|
||||
fn isr_ack(&self) -> u8 {
|
||||
fn interrupt_is_ours(&self) -> bool {
|
||||
match self {
|
||||
Regs::Mmio(base) => {
|
||||
if (r32(*base, ISR) & INT_VRING) != 0 {
|
||||
w32(*base, ISR_ACK, INT_VRING);
|
||||
}
|
||||
0
|
||||
true
|
||||
}
|
||||
Regs::Pci(p) => r8(p.isr, 0),
|
||||
Regs::Pci(p) => r8(p.isr, 0) != 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct Bus {
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64", feature = "linux-injected"))]
|
||||
number: u8,
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
config: *mut u8,
|
||||
@@ -1013,7 +1041,11 @@ impl Bus {
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
fn first(ecam: u64) -> Self {
|
||||
let config = kernel::map_io(ecam, ECAM_BUS_SIZE) as *mut u8;
|
||||
Bus { number: 0, config }
|
||||
Bus {
|
||||
#[cfg(feature = "linux-injected")]
|
||||
number: 0,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
@@ -1024,7 +1056,9 @@ impl Bus {
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
fn function(&self, devfn: u8) -> PciDevice {
|
||||
PciDevice {
|
||||
#[cfg(feature = "linux-injected")]
|
||||
bus: self.number,
|
||||
#[cfg(feature = "linux-injected")]
|
||||
devfn,
|
||||
config: unsafe { self.config.add((devfn as usize) << ECAM_DEVFN_SHIFT) },
|
||||
}
|
||||
@@ -1033,7 +1067,9 @@ impl Bus {
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
struct PciDevice {
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64", feature = "linux-injected"))]
|
||||
bus: u8,
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64", feature = "linux-injected"))]
|
||||
devfn: u8,
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
config: *mut u8,
|
||||
@@ -1113,7 +1149,7 @@ impl PciDevice {
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
fn irq_line(&self) -> Option<u32> {
|
||||
kernel::pci_interrupt(self.bus, self.devfn)
|
||||
self.line_in_config().or_else(|| kernel::pci_interrupt(self.bus, self.devfn))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "linux-injected"))]
|
||||
@@ -1149,6 +1185,10 @@ impl PciDevice {
|
||||
// Point the link at the interrupt in the config space of the device.
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn route_interrupt_line(&self) {
|
||||
let Some(line) = self.interrupt_line_to_use() else {
|
||||
return;
|
||||
};
|
||||
|
||||
let link = ((self.read_config_byte(PCI_INTERRUPT_PIN) - 1) + (self.devfn >> 3) - 1) & 3;
|
||||
let router = PciDevice {
|
||||
bus: 0,
|
||||
@@ -1157,9 +1197,36 @@ impl PciDevice {
|
||||
|
||||
let offset = PIRQ_ROUTE + link;
|
||||
let shift = (offset & 3) * 8;
|
||||
let route = (self.irq_line().unwrap() & !PIRQ_DISABLED) << shift;
|
||||
let route = (line & !PIRQ_DISABLED) << shift;
|
||||
let others = router.read_config(offset) & !(0xff << shift);
|
||||
router.write_config(offset, others | route);
|
||||
|
||||
self.write_config_byte(PCI_INTERRUPT_LINE, line as u8);
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn interrupt_line_to_use(&self) -> Option<u32> {
|
||||
if let Some(line) = self.line_in_config() {
|
||||
return Some(line);
|
||||
}
|
||||
|
||||
let router = PciDevice {
|
||||
bus: 0,
|
||||
devfn: ISA_BRIDGE_DEVFN,
|
||||
};
|
||||
let routes = router.read_config(PIRQ_ROUTE);
|
||||
|
||||
(0..4)
|
||||
.map(|link| (routes >> (link * 8)) & 0xff)
|
||||
.find(|candidate| (candidate & PIRQ_DISABLED) == 0)
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64", feature = "linux-injected"))]
|
||||
fn line_in_config(&self) -> Option<u32> {
|
||||
match self.read_config_byte(PCI_INTERRUPT_LINE) {
|
||||
0 => None,
|
||||
line => Some(line as u32),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_bar_region(&self, bar: u8, offset: u32, length: u32) -> *mut u8 {
|
||||
@@ -1180,6 +1247,13 @@ impl PciDevice {
|
||||
(self.read_config(offset) >> ((offset & 3) * 8)) as u8
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn write_config_byte(&self, offset: u8, value: u8) {
|
||||
let shift = (offset & 3) * 8;
|
||||
let others = self.read_config(offset) & !(0xff << shift);
|
||||
self.write_config(offset, others | ((value as u32) << shift));
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
fn read_config(&self, offset: u8) -> u32 {
|
||||
outl(PCI_CONFIG_ADDRESS, self.config_address(offset));
|
||||
|
||||
@@ -8,8 +8,6 @@ use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::kernel;
|
||||
|
||||
const AF_VSOCK: c_int = 40;
|
||||
const SOCK_STREAM: c_int = 1;
|
||||
const VMADDR_CID_HOST: u32 = 2;
|
||||
|
||||
@@ -87,13 +87,13 @@ fn write_identity(company: &[u8], program: &[u8], into: &mut [u8]) -> usize {
|
||||
};
|
||||
|
||||
let mut written = 0;
|
||||
let mut put = |byte: u8, into: &mut [u8], written: &mut usize| {
|
||||
let put = |byte: u8, into: &mut [u8], written: &mut usize| {
|
||||
if *written != into.len() {
|
||||
into[*written] = byte;
|
||||
*written += 1;
|
||||
}
|
||||
};
|
||||
let mut put_word = |word: &[u8], into: &mut [u8], written: &mut usize| {
|
||||
let put_word = |word: &[u8], into: &mut [u8], written: &mut usize| {
|
||||
for byte in word {
|
||||
if byte.is_ascii_alphanumeric() {
|
||||
put(byte.to_ascii_lowercase(), into, written);
|
||||
|
||||
@@ -37,6 +37,7 @@ pub type ThreadEntry = unsafe extern "C" fn(parameter: *mut c_void, wait_result:
|
||||
|
||||
pub struct ThreadInfo {
|
||||
pub id: u32,
|
||||
pub state: Option<crate::bindings::GumThreadState>,
|
||||
pub cpu_state: Option<CpuState>,
|
||||
}
|
||||
|
||||
@@ -98,6 +99,12 @@ pub struct CpuState {
|
||||
pub eax: u32,
|
||||
}
|
||||
|
||||
pub struct MemoryRegion {
|
||||
pub base: u64,
|
||||
pub size: u64,
|
||||
pub protection: u32,
|
||||
}
|
||||
|
||||
// What the host looked up before the agent started, for the kernels that say nothing about
|
||||
// their own layout and leave nowhere for the agent to read it.
|
||||
pub fn take_note_of(what: &str, number: u64) {
|
||||
|
||||
+353
-64
@@ -25,19 +25,12 @@ use core::ptr;
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use bindings::{
|
||||
GAsyncResult, GBytes, GError, GMainContext, GObject, GSource, GSourceFunc, GSourceFuncs,
|
||||
GVariant, GumMemoryRange, gboolean, g_main_context_acquire, g_main_context_check,
|
||||
g_main_context_dispatch, g_main_context_prepare, g_main_context_query, g_main_context_release,
|
||||
g_main_context_wakeup,
|
||||
g_source_attach, g_source_new, g_source_unref,
|
||||
GAsyncResult, GBytes, GError, GMainContext, GObject, GVariant, GumMemoryRange,
|
||||
GumScript, GumScriptBackend, g_error_free, g_free, g_main_context_iteration,
|
||||
g_main_context_push_thread_default, g_memdup2, g_object_unref, g_variant_check_format_string,
|
||||
g_variant_get, g_variant_get_boolean, g_variant_get_child_value, g_variant_get_data,
|
||||
g_variant_get_size, g_variant_get_string, g_variant_get_strv,
|
||||
g_variant_get, g_variant_get_data, g_variant_get_size, g_variant_get_string,
|
||||
g_variant_get_uint32, g_variant_new, g_variant_new_from_data, g_variant_new_string,
|
||||
g_variant_new_tuple, g_variant_new_uint32, g_variant_type_free, g_variant_type_new,
|
||||
g_variant_builder_add, g_variant_builder_add_value, g_variant_builder_close,
|
||||
g_variant_builder_end, g_variant_builder_new, g_variant_builder_open,
|
||||
g_variant_new_fixed_array, g_variant_get_fixed_array, g_bytes_new, g_bytes_get_data,
|
||||
g_bytes_unref, GVariantType,
|
||||
g_variant_unref, gchar, gpointer, gsize, gum_script_backend_create,
|
||||
@@ -47,12 +40,20 @@ use bindings::{
|
||||
gum_script_scheduler_get_js_context, gum_script_set_message_handler, gum_script_unload,
|
||||
gum_script_unload_finish, gum_stalker_exclude,
|
||||
};
|
||||
#[cfg(any(feature = "blob", feature = "xnu-core"))]
|
||||
use bindings::{
|
||||
GSource, GSourceFunc, GSourceFuncs, gboolean, g_main_context_wakeup, g_source_attach,
|
||||
g_source_new, g_source_unref, g_variant_builder_add, g_variant_builder_add_value,
|
||||
g_variant_builder_close, g_variant_builder_end, g_variant_builder_new, g_variant_builder_open,
|
||||
g_variant_get_boolean, g_variant_get_child_value,
|
||||
};
|
||||
|
||||
mod ffi;
|
||||
mod glib;
|
||||
mod gthread;
|
||||
mod gum;
|
||||
mod libc;
|
||||
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected", feature = "xnu-core"))]
|
||||
mod ring;
|
||||
|
||||
pub mod kernel;
|
||||
@@ -91,6 +92,8 @@ mod winnt_user;
|
||||
mod gum_injected;
|
||||
#[cfg(feature = "blob")]
|
||||
mod hostlink_virtio;
|
||||
#[cfg(feature = "winnt")]
|
||||
mod hostlink_serial;
|
||||
#[cfg(feature = "xnu-core")]
|
||||
mod hostlink_vsock;
|
||||
#[cfg(feature = "xnu-core")]
|
||||
@@ -141,29 +144,32 @@ mod bindings {
|
||||
#[repr(u8)]
|
||||
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
|
||||
pub enum FridaCommand {
|
||||
CreateScript = 1,
|
||||
LoadScript = 2,
|
||||
DestroyScript = 3,
|
||||
PostScriptMessage = 4,
|
||||
RemapWritablePages = 5,
|
||||
MemoryProtect = 6,
|
||||
PatchCode = 7,
|
||||
EnumerateProcesses = 8,
|
||||
InjectIntoProcess = 9,
|
||||
AllocateShared = 10,
|
||||
DetachFromProcess = 11,
|
||||
PlaceAgentInProcess = 12,
|
||||
StartAgentInProcess = 13,
|
||||
SpawnProcess = 14,
|
||||
ResumeProcess = 15,
|
||||
Stop = 16,
|
||||
GateSpawns = 17,
|
||||
EnumerateApplications = 18,
|
||||
EnumerateShortcuts = 19,
|
||||
EnumerateApplications = 1,
|
||||
EnumerateShortcuts = 2,
|
||||
EnumerateProcesses = 3,
|
||||
GateSpawns = 4,
|
||||
ArmNamedSpawn = 5,
|
||||
DisarmNamedSpawn = 6,
|
||||
SpawnProcess = 7,
|
||||
ResumeProcess = 8,
|
||||
Stop = 9,
|
||||
InjectIntoProcess = 10,
|
||||
PlaceAgentInProcess = 11,
|
||||
StartAgentInProcess = 12,
|
||||
AllocateShared = 13,
|
||||
RemapWritablePages = 14,
|
||||
MemoryProtect = 15,
|
||||
PatchCode = 16,
|
||||
DetachFromProcess = 17,
|
||||
CreateScript = 18,
|
||||
DestroyScript = 19,
|
||||
LoadScript = 20,
|
||||
PostScriptMessage = 21,
|
||||
|
||||
Reply = 128,
|
||||
ScriptMessage = 129,
|
||||
SpawnAdded = 130,
|
||||
Ready = 131,
|
||||
}
|
||||
|
||||
impl core::fmt::Display for FridaCommand {
|
||||
@@ -173,6 +179,7 @@ impl core::fmt::Display for FridaCommand {
|
||||
FridaCommand::EnumerateApplications => write!(f, "EnumerateApplications"),
|
||||
FridaCommand::EnumerateShortcuts => write!(f, "EnumerateShortcuts"),
|
||||
FridaCommand::SpawnAdded => write!(f, "SpawnAdded"),
|
||||
FridaCommand::Ready => write!(f, "Ready"),
|
||||
FridaCommand::CreateScript => write!(f, "CreateScript"),
|
||||
FridaCommand::LoadScript => write!(f, "LoadScript"),
|
||||
FridaCommand::DestroyScript => write!(f, "DestroyScript"),
|
||||
@@ -180,6 +187,8 @@ impl core::fmt::Display for FridaCommand {
|
||||
FridaCommand::RemapWritablePages => write!(f, "RemapWritablePages"),
|
||||
FridaCommand::MemoryProtect => write!(f, "MemoryProtect"),
|
||||
FridaCommand::PatchCode => write!(f, "PatchCode"),
|
||||
FridaCommand::ArmNamedSpawn => write!(f, "ArmNamedSpawn"),
|
||||
FridaCommand::DisarmNamedSpawn => write!(f, "DisarmNamedSpawn"),
|
||||
FridaCommand::EnumerateProcesses => write!(f, "EnumerateProcesses"),
|
||||
FridaCommand::InjectIntoProcess => write!(f, "InjectIntoProcess"),
|
||||
FridaCommand::AllocateShared => write!(f, "AllocateShared"),
|
||||
@@ -285,9 +294,6 @@ mod entrypoint_blob {
|
||||
|
||||
unsafe extern "C" fn worker(_parameter: *mut c_void, _wait_result: i32) {
|
||||
unsafe {
|
||||
#[cfg(not(feature = "linux-injected"))]
|
||||
kernel::install_fault_reporter();
|
||||
|
||||
crate::run_constructors();
|
||||
init_gum();
|
||||
|
||||
@@ -298,11 +304,13 @@ mod entrypoint_blob {
|
||||
SYMBOL_TABLE = symbol_table;
|
||||
OWN_RANGE = own_range;
|
||||
|
||||
kernel::install_fault_reporter();
|
||||
#[cfg(feature = "winnt")]
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
|
||||
kernel::disarm_patchguard();
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
{
|
||||
kernel::install_fault_reporter();
|
||||
crate::gum_btf::publish();
|
||||
}
|
||||
crate::gum_btf::publish();
|
||||
|
||||
let wake_token = ptr::addr_of_mut!(glib::WAKEUP_TOKEN) as *const u8;
|
||||
// Install this before the loop waits the first time, thus a copy placed in a process later
|
||||
@@ -324,14 +332,30 @@ mod entrypoint_blob {
|
||||
hostlink_virtio::Hostlink::init_pci(ecam, Some(on_frame_from_host), wake_token)
|
||||
.unwrap(),
|
||||
),
|
||||
#[cfg(feature = "linux-injected")]
|
||||
TransportConfig::PipeVsock { path } => Transport::PipeVsock(
|
||||
linux::hostlink_pipe_vsock::Hostlink::init(&path, Some(on_frame_from_host), wake_token)
|
||||
.unwrap(),
|
||||
),
|
||||
#[cfg(feature = "linux-injected")]
|
||||
TransportConfig::PipeGoldfish { path } => Transport::PipeGoldfish(
|
||||
linux::hostlink_pipe_goldfish::Hostlink::init(&path, Some(on_frame_from_host), wake_token)
|
||||
.unwrap(),
|
||||
),
|
||||
#[cfg(feature = "xnu-core")]
|
||||
TransportConfig::Vsock { host_port } => Transport::Vsock(
|
||||
hostlink_vsock::Hostlink::init(host_port, Some(on_frame_from_host), wake_token)
|
||||
.unwrap(),
|
||||
),
|
||||
#[cfg(feature = "winnt")]
|
||||
TransportConfig::Serial => Transport::Serial(
|
||||
hostlink_serial::Hostlink::init(Some(on_frame_from_host), wake_token).unwrap(),
|
||||
),
|
||||
};
|
||||
transport_set(transport);
|
||||
|
||||
tell_the_host_we_are_listening();
|
||||
|
||||
let context = adopt_js_context();
|
||||
run_main_loop(context);
|
||||
|
||||
@@ -419,6 +443,38 @@ mod entrypoint_blob {
|
||||
#[cfg(not(any(feature = "win9x", feature = "winnt", feature = "linux-injected")))]
|
||||
{ let _ = ecam; panic!("virtio-pci is not this kernel's") }
|
||||
}
|
||||
3 => {
|
||||
#[cfg(feature = "winnt")]
|
||||
{ TransportConfig::Serial }
|
||||
#[cfg(not(feature = "winnt"))]
|
||||
{ panic!("serial is NT's") }
|
||||
}
|
||||
4 => {
|
||||
#[cfg(feature = "linux-injected")]
|
||||
{
|
||||
let raw = g_variant_get_string(transport_cfg_inner, ptr::null_mut());
|
||||
let path = core::ffi::CStr::from_ptr(raw as *const core::ffi::c_char)
|
||||
.to_str()
|
||||
.unwrap_or("")
|
||||
.into();
|
||||
TransportConfig::PipeVsock { path }
|
||||
}
|
||||
#[cfg(not(feature = "linux-injected"))]
|
||||
{ panic!("pipe-vsock is linux-injected's") }
|
||||
}
|
||||
5 => {
|
||||
#[cfg(feature = "linux-injected")]
|
||||
{
|
||||
let raw = g_variant_get_string(transport_cfg_inner, ptr::null_mut());
|
||||
let path = core::ffi::CStr::from_ptr(raw as *const core::ffi::c_char)
|
||||
.to_str()
|
||||
.unwrap_or("")
|
||||
.into();
|
||||
TransportConfig::PipeGoldfish { path }
|
||||
}
|
||||
#[cfg(not(feature = "linux-injected"))]
|
||||
{ panic!("pipe-goldfish is linux-injected's") }
|
||||
}
|
||||
_ => panic!("Unsupported transport kind: {}", transport_kind),
|
||||
};
|
||||
g_variant_unref(transport_cfg_inner);
|
||||
@@ -677,10 +733,6 @@ mod entrypoint_linux {
|
||||
STOP_REQUESTED.load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
fn start_worker() {
|
||||
kernel::spawn_thread(worker, 12345usize as *mut c_void);
|
||||
}
|
||||
|
||||
unsafe extern "C" fn worker(_parameter: *mut c_void, _wait_result: i32) {
|
||||
unsafe {
|
||||
kernel::log("frida: worker entry\n\0");
|
||||
@@ -722,8 +774,14 @@ pub use entrypoint_blob::_start;
|
||||
pub enum Transport {
|
||||
#[cfg(feature = "blob")]
|
||||
Virtio(hostlink_virtio::Hostlink),
|
||||
#[cfg(feature = "winnt")]
|
||||
Serial(hostlink_serial::Hostlink),
|
||||
#[cfg(feature = "xnu-core")]
|
||||
Vsock(hostlink_vsock::Hostlink),
|
||||
#[cfg(feature = "linux-injected")]
|
||||
PipeVsock(linux::hostlink_pipe_vsock::Hostlink),
|
||||
#[cfg(feature = "linux-injected")]
|
||||
PipeGoldfish(linux::hostlink_pipe_goldfish::Hostlink),
|
||||
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
|
||||
CharDevice(hostlink_chardev::Hostlink),
|
||||
}
|
||||
@@ -733,8 +791,14 @@ impl Transport {
|
||||
match self {
|
||||
#[cfg(feature = "blob")]
|
||||
Transport::Virtio(h) => h.send(payload),
|
||||
#[cfg(feature = "winnt")]
|
||||
Transport::Serial(h) => h.send(payload),
|
||||
#[cfg(feature = "xnu-core")]
|
||||
Transport::Vsock(h) => h.send(payload),
|
||||
#[cfg(feature = "linux-injected")]
|
||||
Transport::PipeVsock(h) => h.send(payload),
|
||||
#[cfg(feature = "linux-injected")]
|
||||
Transport::PipeGoldfish(h) => h.send(payload),
|
||||
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
|
||||
Transport::CharDevice(h) => h.send(payload),
|
||||
}
|
||||
@@ -744,8 +808,14 @@ impl Transport {
|
||||
match self {
|
||||
#[cfg(feature = "blob")]
|
||||
Transport::Virtio(h) => h.shutdown(),
|
||||
#[cfg(feature = "winnt")]
|
||||
Transport::Serial(h) => h.shutdown(),
|
||||
#[cfg(feature = "xnu-core")]
|
||||
Transport::Vsock(_) => {}
|
||||
#[cfg(feature = "linux-injected")]
|
||||
Transport::PipeVsock(h) => h.shutdown(),
|
||||
#[cfg(feature = "linux-injected")]
|
||||
Transport::PipeGoldfish(h) => h.shutdown(),
|
||||
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
|
||||
Transport::CharDevice(_) => {}
|
||||
}
|
||||
@@ -755,8 +825,14 @@ impl Transport {
|
||||
match self {
|
||||
#[cfg(feature = "blob")]
|
||||
Transport::Virtio(h) => h.process(),
|
||||
#[cfg(feature = "winnt")]
|
||||
Transport::Serial(h) => h.process(),
|
||||
#[cfg(feature = "xnu-core")]
|
||||
Transport::Vsock(h) => h.process(),
|
||||
#[cfg(feature = "linux-injected")]
|
||||
Transport::PipeVsock(h) => h.process(),
|
||||
#[cfg(feature = "linux-injected")]
|
||||
Transport::PipeGoldfish(h) => h.process(),
|
||||
#[cfg(any(feature = "linux", feature = "xnu-kext"))]
|
||||
Transport::CharDevice(h) => h.process(),
|
||||
}
|
||||
@@ -765,7 +841,7 @@ impl Transport {
|
||||
|
||||
// Configuration space is reached through I/O ports where there are any, and only a machine
|
||||
// without them is told where it is mapped instead.
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "arm"))]
|
||||
#[cfg(all(feature = "blob", any(target_arch = "aarch64", target_arch = "arm")))]
|
||||
unsafe fn where_configuration_space_is_mapped(transport: *mut GVariant) -> u64 {
|
||||
use crate::bindings::{g_variant_get_child_value, g_variant_get_uint64, g_variant_unref};
|
||||
|
||||
@@ -778,7 +854,7 @@ unsafe fn where_configuration_space_is_mapped(transport: *mut GVariant) -> u64 {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
#[cfg(all(feature = "blob", any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
unsafe fn where_configuration_space_is_mapped(_transport: *mut GVariant) -> u64 {
|
||||
0
|
||||
}
|
||||
@@ -786,10 +862,16 @@ unsafe fn where_configuration_space_is_mapped(_transport: *mut GVariant) -> u64
|
||||
#[cfg(feature = "blob")]
|
||||
pub enum TransportConfig {
|
||||
Virtio { mmio: u64, irq: u32 },
|
||||
#[cfg(feature = "winnt")]
|
||||
Serial,
|
||||
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected"))]
|
||||
VirtioPci { ecam: u64 },
|
||||
#[cfg(feature = "xnu-core")]
|
||||
Vsock { host_port: u32 },
|
||||
#[cfg(feature = "linux-injected")]
|
||||
PipeVsock { path: alloc::string::String },
|
||||
#[cfg(feature = "linux-injected")]
|
||||
PipeGoldfish { path: alloc::string::String },
|
||||
}
|
||||
|
||||
static mut TRANSPORT_DRIVER: *mut Transport = core::ptr::null_mut();
|
||||
@@ -802,6 +884,25 @@ fn transport_set(driver: Transport) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "blob")]
|
||||
fn tell_the_host_we_are_listening() {
|
||||
unsafe {
|
||||
let message = g_variant_new(
|
||||
c"(yquv)".as_ptr(),
|
||||
FridaCommand::Ready as u8 as u32,
|
||||
0u32,
|
||||
0u32,
|
||||
g_variant_new(c"()".as_ptr()),
|
||||
);
|
||||
|
||||
if let Some(serialized) = serialize_message(message) {
|
||||
send_frame(&serialized);
|
||||
}
|
||||
|
||||
g_variant_unref(message);
|
||||
}
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn send_frame(frame: &[u8]) {
|
||||
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected", feature = "xnu-core"))]
|
||||
@@ -840,10 +941,16 @@ static mut SCRIPTS: BTreeMap<u32, *mut GumScript> = BTreeMap::new();
|
||||
static NEXT_SCRIPT_ID: AtomicU32 = AtomicU32::new(1);
|
||||
// A copy of the agent runs the same code, but it cannot share what gets written to. Thus keep
|
||||
// the writable half as it is before anything writes to it.
|
||||
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected", feature = "xnu-core"))]
|
||||
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected", feature = "xnu"))]
|
||||
pub(crate) unsafe fn preserve_writable_half() {
|
||||
let size = writable_half_size();
|
||||
let pristine = kernel::alloc(size);
|
||||
// A few-hundred-KiB kmalloc needs an order-9 contiguous block, which a fragmented guest can
|
||||
// refuse; the pristine copy only feeds process-copies, so proceed without it rather than
|
||||
// faulting into a NULL destination.
|
||||
if pristine.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(writable_half_start() as *const u8, pristine, size);
|
||||
PRISTINE_WRITABLE_HALF = pristine;
|
||||
@@ -908,7 +1015,6 @@ pub(crate) fn writable_half_start() -> usize {
|
||||
unsafe { frida_agent_private_start }
|
||||
}
|
||||
|
||||
|
||||
#[cfg(all(any(feature = "win9x", feature = "winnt", feature = "linux-injected",
|
||||
feature = "xnu-core"), not(feature = "xnu-kext")))]
|
||||
pub(crate) fn writable_half_start() -> usize {
|
||||
@@ -928,9 +1034,11 @@ fn writable_half_size() -> usize {
|
||||
|
||||
// A relocation names the slot first, and the rest of it says the same thing as the value that
|
||||
// the slot already holds.
|
||||
#[cfg(any(target_arch = "x86", target_arch = "arm"))]
|
||||
#[cfg(all(any(feature = "win9x", feature = "winnt", feature = "linux-injected",
|
||||
feature = "xnu-core"), any(target_arch = "x86", target_arch = "arm")))]
|
||||
const RELOCATION_SIZE: usize = 8;
|
||||
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
|
||||
#[cfg(all(any(feature = "win9x", feature = "winnt", feature = "linux-injected",
|
||||
feature = "xnu-core"), any(target_arch = "x86_64", target_arch = "aarch64")))]
|
||||
const RELOCATION_SIZE: usize = 24;
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected", feature = "xnu-core"))]
|
||||
@@ -963,6 +1071,7 @@ unsafe extern "C" {
|
||||
|
||||
// A copy runs at a base of its own, thus the half that placed it there says where. The
|
||||
// Stalker keeps out of this range: it must not instrument the runtime it runs on.
|
||||
#[cfg(any(feature = "win9x", feature = "winnt", feature = "xnu-core"))]
|
||||
pub(crate) unsafe fn set_own_range(base: u64, size: u64) {
|
||||
unsafe {
|
||||
OWN_RANGE = GumMemoryRange {
|
||||
@@ -972,12 +1081,21 @@ pub(crate) unsafe fn set_own_range(base: u64, size: u64) {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "winnt")]
|
||||
pub(crate) fn own_code() -> (usize, usize) {
|
||||
let start = entrypoint_blob::_start as *const () as usize;
|
||||
|
||||
(start, (&raw const _agent_private_start as usize) - start)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
|
||||
pub(crate) fn own_range() -> (usize, usize) {
|
||||
let range = unsafe { ptr::addr_of!(OWN_RANGE).read() };
|
||||
|
||||
(range.base_address as usize, range.size as usize)
|
||||
}
|
||||
|
||||
#[cfg(feature = "win9x")]
|
||||
pub(crate) fn own_range_contains(address: u32) -> bool {
|
||||
let range = unsafe { ptr::addr_of!(OWN_RANGE).read() };
|
||||
let base = range.base_address as u32;
|
||||
@@ -1000,14 +1118,27 @@ unsafe fn init_gum() {
|
||||
|
||||
// The Exceptor installs fault handling for the full machine, which only the kernel half may
|
||||
// do. A copy in a process would take it from the half that uses it.
|
||||
#[cfg(any(feature = "blob", feature = "xnu-core"))]
|
||||
pub(crate) unsafe fn init_gum_without_exceptor() {
|
||||
unsafe { init_gum_with_exceptor(false) };
|
||||
unsafe {
|
||||
init_gum_with_exceptor(false);
|
||||
warm_async_io();
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn warm_async_io() {
|
||||
unsafe {
|
||||
let task = bindings::g_task_new(core::ptr::null_mut(), core::ptr::null_mut(), None,
|
||||
core::ptr::null_mut());
|
||||
bindings::g_object_unref(task as bindings::gpointer);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn init_gum_with_exceptor(exceptor: bool) {
|
||||
unsafe {
|
||||
bindings::g_set_panic_handler(Some(signed_to_be_called_back(frida_panic_handler, 0)), ptr::null_mut());
|
||||
bindings::gum_init_embedded();
|
||||
bindings::glib_enable_io_features();
|
||||
if exceptor {
|
||||
bindings::gum_exceptor_obtain();
|
||||
}
|
||||
@@ -1062,7 +1193,6 @@ fn destination_of(variant: *mut GVariant) -> u32 {
|
||||
pub(crate) unsafe fn adopt_js_context() -> *mut GMainContext {
|
||||
unsafe {
|
||||
JS_THREAD_ID = kernel::current_thread_id();
|
||||
|
||||
let context = gum_script_scheduler_get_js_context(gum_script_backend_get_scheduler());
|
||||
|
||||
// Acquires the context as well, which is what lets this thread run the jobs the
|
||||
@@ -1076,6 +1206,7 @@ pub(crate) unsafe fn adopt_js_context() -> *mut GMainContext {
|
||||
|
||||
static mut JS_THREAD_ID: u64 = 0;
|
||||
|
||||
#[cfg(any(feature = "blob", feature = "xnu-core"))]
|
||||
pub(crate) fn on_js_thread() -> bool {
|
||||
let id = unsafe { JS_THREAD_ID };
|
||||
id != 0 && kernel::current_thread_id() == id
|
||||
@@ -1087,6 +1218,33 @@ pub(crate) static STOP_REQUESTED: core::sync::atomic::AtomicBool =
|
||||
#[unsafe(no_mangle)]
|
||||
pub static mut frida_agent_left: u32 = 0;
|
||||
|
||||
#[cfg(any(feature = "win9x", all(feature = "linux-injected", target_arch = "aarch64")))]
|
||||
pub(crate) fn note_unhandled_fault(vector: u64, pc: u64, address: u64) {
|
||||
unsafe {
|
||||
let fault = &raw mut frida_agent_fault;
|
||||
(*fault).count += 1;
|
||||
(*fault).vector = vector;
|
||||
(*fault).pc = pc;
|
||||
(*fault).address = address;
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static mut frida_agent_fault: AgentFault = AgentFault {
|
||||
count: 0,
|
||||
vector: 0,
|
||||
pc: 0,
|
||||
address: 0,
|
||||
};
|
||||
|
||||
#[repr(C)]
|
||||
pub struct AgentFault {
|
||||
pub count: u64,
|
||||
pub vector: u64,
|
||||
pub pc: u64,
|
||||
pub address: u64,
|
||||
}
|
||||
|
||||
pub(crate) fn stop_requested() -> bool {
|
||||
STOP_REQUESTED.load(Ordering::Acquire)
|
||||
}
|
||||
@@ -1125,6 +1283,21 @@ fn kernel_half_has_work() -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
#[cfg(feature = "winnt")]
|
||||
if hostlink_serial::a_turn_is_wanted() {
|
||||
return true;
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
if linux::hostlink_pipe_vsock::a_turn_is_wanted() {
|
||||
return true;
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
if linux::hostlink_pipe_goldfish::a_turn_is_wanted() {
|
||||
return true;
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
if hostlink_chardev::a_turn_is_wanted() {
|
||||
return true;
|
||||
@@ -1148,7 +1321,19 @@ fn kernel_half_has_work() -> bool {
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
if kernel::a_copy_has_something_to_say() || kernel::a_spawn_is_held() {
|
||||
if kernel::a_copy_is_asking() {
|
||||
return true;
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
if kernel::a_copy_has_something_to_say() || kernel::a_spawn_is_held()
|
||||
|| kernel::a_pending_spawn_waits()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "linux-injected", target_arch = "aarch64"))]
|
||||
if kernel::a_copy_wants_executable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1162,24 +1347,33 @@ fn serve_the_kernel_half() {
|
||||
#[cfg(feature = "xnu-core")]
|
||||
relay_frames_from_targets();
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
kernel::drain_pending_spawn();
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
kernel::tell_of_held_spawns(&mut |id, program| {
|
||||
let said = alloc::ffi::CString::new(program).unwrap();
|
||||
tell_the_host_of_a_spawn(id, said.as_ptr() as *const u8);
|
||||
});
|
||||
|
||||
unsafe { transport_get_unchecked().process() };
|
||||
|
||||
transport_get_unchecked().process();
|
||||
|
||||
#[cfg(feature = "win9x")]
|
||||
serve_deferred_work();
|
||||
|
||||
#[cfg(all(feature = "linux-injected", target_arch = "aarch64"))]
|
||||
kernel::serve_executable_requests();
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
kernel::serve_register_requests();
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
kernel::report_what_the_copies_hit();
|
||||
|
||||
relay_frames_from_targets();
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "blob", feature = "xnu-core"))]
|
||||
pub(crate) fn watch_for_work(main_context: *mut GMainContext, ready: fn() -> bool, serve: fn()) {
|
||||
unsafe {
|
||||
WORK_READY = Some(ready);
|
||||
@@ -1189,11 +1383,31 @@ pub(crate) fn watch_for_work(main_context: *mut GMainContext, ready: fn() -> boo
|
||||
sign_what_the_loop_calls_back();
|
||||
|
||||
let source = g_source_new(&raw mut WORK_FUNCS, core::mem::size_of::<GSource>() as u32);
|
||||
WORK_SOURCE = source;
|
||||
g_source_attach(source, main_context);
|
||||
g_source_unref(source);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "blob", feature = "xnu-core"))]
|
||||
static mut WORK_SOURCE: *mut GSource = ptr::null_mut();
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected"))]
|
||||
static mut WAKEUP_POLLFD: bindings::GPollFD =
|
||||
bindings::GPollFD { fd: -1, events: 0, revents: 0, user_data: ptr::null_mut() };
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt", feature = "linux-injected"))]
|
||||
pub(crate) fn watch_a_descriptor(context: *mut GMainContext, fd: i32,
|
||||
poll: unsafe extern "C" fn(*mut bindings::GPollFD, u32, i32) -> i32) {
|
||||
const G_IO_IN: u16 = 1;
|
||||
unsafe {
|
||||
WAKEUP_POLLFD =
|
||||
bindings::GPollFD { fd, events: G_IO_IN, revents: 0, user_data: ptr::null_mut() };
|
||||
bindings::g_source_add_poll(WORK_SOURCE, ptr::addr_of_mut!(WAKEUP_POLLFD));
|
||||
bindings::g_main_context_set_poll_func(context, Some(poll));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
pub(crate) unsafe fn signed_to_be_called_back<T>(callback: T, discriminator: core::ffi::c_uint) -> T {
|
||||
unsafe {
|
||||
@@ -1217,9 +1431,12 @@ unsafe fn sign_what_the_loop_calls_back() {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "blob", feature = "xnu-core"))]
|
||||
static mut WORK_READY: Option<fn() -> bool> = None;
|
||||
#[cfg(any(feature = "blob", feature = "xnu-core"))]
|
||||
static mut WORK_SERVE: Option<fn()> = None;
|
||||
|
||||
#[cfg(any(feature = "blob", feature = "xnu-core"))]
|
||||
unsafe extern "C" fn work_prepare(source: *mut GSource, timeout: *mut i32) -> gboolean {
|
||||
unsafe {
|
||||
*timeout = -1;
|
||||
@@ -1228,6 +1445,7 @@ unsafe extern "C" fn work_prepare(source: *mut GSource, timeout: *mut i32) -> gb
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "blob", feature = "xnu-core"))]
|
||||
unsafe extern "C" fn work_check(_source: *mut GSource) -> gboolean {
|
||||
let Some(ready) = (unsafe { ptr::addr_of!(WORK_READY).read() }) else {
|
||||
return 0;
|
||||
@@ -1236,6 +1454,7 @@ unsafe extern "C" fn work_check(_source: *mut GSource) -> gboolean {
|
||||
ready() as gboolean
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "blob", feature = "xnu-core"))]
|
||||
unsafe extern "C" fn work_dispatch(_source: *mut GSource, _callback: GSourceFunc,
|
||||
_data: gpointer) -> gboolean {
|
||||
if let Some(serve) = unsafe { ptr::addr_of!(WORK_SERVE).read() } {
|
||||
@@ -1245,6 +1464,7 @@ unsafe extern "C" fn work_dispatch(_source: *mut GSource, _callback: GSourceFunc
|
||||
1
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "blob", feature = "xnu-core"))]
|
||||
static mut WORK_FUNCS: GSourceFuncs = GSourceFuncs {
|
||||
prepare: Some(work_prepare),
|
||||
check: Some(work_check),
|
||||
@@ -1265,6 +1485,7 @@ pub(crate) unsafe fn dispatch_pending_work(main_context: *mut GMainContext) {
|
||||
static SERVICE_CONTEXT: core::sync::atomic::AtomicUsize =
|
||||
core::sync::atomic::AtomicUsize::new(0);
|
||||
|
||||
#[cfg(any(feature = "blob", feature = "xnu-core"))]
|
||||
pub(crate) fn nudge_the_loop(token: *const u8) {
|
||||
let context = SERVICE_CONTEXT.load(Ordering::Acquire) as *mut GMainContext;
|
||||
if !context.is_null() {
|
||||
@@ -1398,6 +1619,10 @@ fn process_incoming_message(variant: *mut GVariant) {
|
||||
FridaCommand::PostScriptMessage => Some(handle_post_script_message(payload_variant)),
|
||||
#[cfg(any(feature = "win9x", feature = "linux-injected", feature = "xnu-core"))]
|
||||
FridaCommand::GateSpawns => Some(handle_gate_spawns(payload_variant)),
|
||||
#[cfg(feature = "linux-injected")]
|
||||
FridaCommand::ArmNamedSpawn => Some(handle_arm_named_spawn(payload_variant)),
|
||||
#[cfg(feature = "linux-injected")]
|
||||
FridaCommand::DisarmNamedSpawn => Some(handle_disarm_named_spawn(payload_variant)),
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
FridaCommand::EnumerateApplications => Some(handle_enumerate_applications(payload_variant)),
|
||||
#[cfg(feature = "xnu-core")]
|
||||
@@ -1405,6 +1630,8 @@ fn process_incoming_message(variant: *mut GVariant) {
|
||||
kernel::list_applications_when_the_loop_can(request_id);
|
||||
None
|
||||
}
|
||||
#[cfg(feature = "linux-injected")]
|
||||
FridaCommand::EnumerateApplications => Some(handle_enumerate_no_applications()),
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
FridaCommand::EnumerateShortcuts => Some(handle_enumerate_shortcuts()),
|
||||
#[cfg(any(
|
||||
@@ -1581,7 +1808,6 @@ fn serve_pending_detach() {
|
||||
send_command_reply(request_id, response);
|
||||
}
|
||||
|
||||
|
||||
#[cfg(feature = "xnu-core")]
|
||||
pub static mut ASKED_THE_HOST: u32 = 0;
|
||||
|
||||
@@ -1676,6 +1902,16 @@ fn what_is_installed() -> HandlerResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
fn handle_enumerate_no_applications() -> HandlerResponse {
|
||||
unsafe {
|
||||
let list_type = g_variant_type_new(c"a(sss)".as_ptr() as *const gchar);
|
||||
let list = g_variant_builder_end(g_variant_builder_new(list_type));
|
||||
g_variant_type_free(list_type);
|
||||
HandlerResponse::success(list)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
fn handle_enumerate_applications(payload: *mut GVariant) -> HandlerResponse {
|
||||
unsafe {
|
||||
@@ -1692,7 +1928,7 @@ fn handle_enumerate_applications(payload: *mut GVariant) -> HandlerResponse {
|
||||
let path = as_text(path, &mut path_text);
|
||||
|
||||
let identity = kernel::identify_image(path as *const u8);
|
||||
let named = if unsafe { identity.read() } != 0 { identity as *const gchar } else {
|
||||
let named = if identity.read() != 0 { identity as *const gchar } else {
|
||||
identifier
|
||||
};
|
||||
|
||||
@@ -1716,15 +1952,17 @@ fn handle_enumerate_applications(payload: *mut GVariant) -> HandlerResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
struct SelectedIdentifiers {
|
||||
names: *mut *const gchar,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
impl SelectedIdentifiers {
|
||||
unsafe fn from(variant: *mut GVariant) -> Self {
|
||||
let mut count: gsize = 0;
|
||||
let names = unsafe { g_variant_get_strv(variant, &mut count) };
|
||||
let names = unsafe { bindings::g_variant_get_strv(variant, &mut count) };
|
||||
SelectedIdentifiers { names, count: count as usize }
|
||||
}
|
||||
|
||||
@@ -1737,12 +1975,14 @@ impl SelectedIdentifiers {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
impl Drop for SelectedIdentifiers {
|
||||
fn drop(&mut self) {
|
||||
unsafe { g_free(self.names as gpointer) };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
unsafe fn c_string_equal(a: *const gchar, b: *const gchar) -> bool {
|
||||
unsafe {
|
||||
let mut offset = 0;
|
||||
@@ -1807,7 +2047,21 @@ fn as_text<'a>(bytes: &[u8], into: &'a mut [u8]) -> *const gchar {
|
||||
fn handle_gate_spawns(payload: *mut GVariant) -> HandlerResponse {
|
||||
kernel::gate_spawns(unsafe { g_variant_get_boolean(payload) } != 0);
|
||||
|
||||
HandlerResponse::success(unsafe { g_variant_new_uint32(0) })
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
fn handle_arm_named_spawn(payload: *mut GVariant) -> HandlerResponse {
|
||||
let name = unsafe { core::ffi::CStr::from_ptr(g_variant_get_string(payload, ptr::null_mut())) };
|
||||
kernel::arm_named_spawn(name.to_bytes());
|
||||
|
||||
HandlerResponse::success(unsafe { g_variant_new_uint32(0) })
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
fn handle_disarm_named_spawn(payload: *mut GVariant) -> HandlerResponse {
|
||||
let name = unsafe { core::ffi::CStr::from_ptr(g_variant_get_string(payload, ptr::null_mut())) };
|
||||
kernel::disarm_named_spawn(name.to_bytes());
|
||||
|
||||
HandlerResponse::success(unsafe { g_variant_new_uint32(0) })
|
||||
}
|
||||
@@ -1846,8 +2100,8 @@ fn handle_enumerate_processes(payload: *mut GVariant) -> HandlerResponse {
|
||||
let selection = g_variant_get_child_value(payload, 1);
|
||||
let wanted = SelectedPids::from(selection);
|
||||
|
||||
let list_type = g_variant_type_new(c"a(usssaay)".as_ptr() as *const gchar);
|
||||
let process_type = g_variant_type_new(c"(usssaay)".as_ptr() as *const gchar);
|
||||
let list_type = g_variant_type_new(c"a(usssusuxaay)".as_ptr() as *const gchar);
|
||||
let process_type = g_variant_type_new(c"(usssusuxaay)".as_ptr() as *const gchar);
|
||||
let icons_type = g_variant_type_new(c"aay".as_ptr() as *const gchar);
|
||||
let byte_type = g_variant_type_new(c"y".as_ptr() as *const gchar);
|
||||
let builder = g_variant_builder_new(list_type);
|
||||
@@ -1858,6 +2112,10 @@ fn handle_enumerate_processes(payload: *mut GVariant) -> HandlerResponse {
|
||||
g_variant_builder_add(builder, c"s".as_ptr(), c"".as_ptr());
|
||||
g_variant_builder_add(builder, c"s".as_ptr(), c"".as_ptr());
|
||||
g_variant_builder_add(builder, c"s".as_ptr(), kernel_name());
|
||||
g_variant_builder_add(builder, c"u".as_ptr(), 0u32);
|
||||
g_variant_builder_add(builder, c"s".as_ptr(), c"".as_ptr());
|
||||
g_variant_builder_add(builder, c"u".as_ptr(), 0u32);
|
||||
g_variant_builder_add(builder, c"x".as_ptr(), 0i64);
|
||||
g_variant_builder_open(builder, icons_type);
|
||||
g_variant_builder_close(builder);
|
||||
g_variant_builder_close(builder);
|
||||
@@ -1879,6 +2137,10 @@ fn handle_enumerate_processes(payload: *mut GVariant) -> HandlerResponse {
|
||||
c"s".as_ptr(),
|
||||
text_or_empty(kernel::describe_process(&process)),
|
||||
);
|
||||
g_variant_builder_add(builder, c"u".as_ptr(), process.uid);
|
||||
g_variant_builder_add(builder, c"s".as_ptr(), text_or_empty(process.user));
|
||||
g_variant_builder_add(builder, c"u".as_ptr(), process.ppid);
|
||||
g_variant_builder_add(builder, c"x".as_ptr(), process.started);
|
||||
|
||||
g_variant_builder_open(builder, icons_type);
|
||||
if include_icons && !process.path.is_null() {
|
||||
@@ -1909,11 +2171,23 @@ fn handle_enumerate_processes(payload: *mut GVariant) -> HandlerResponse {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
feature = "win9x",
|
||||
feature = "winnt",
|
||||
feature = "linux-injected",
|
||||
feature = "xnu-core"
|
||||
))]
|
||||
struct SelectedPids {
|
||||
pids: *const u32,
|
||||
count: usize,
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
feature = "win9x",
|
||||
feature = "winnt",
|
||||
feature = "linux-injected",
|
||||
feature = "xnu-core"
|
||||
))]
|
||||
impl SelectedPids {
|
||||
unsafe fn from(variant: *mut GVariant) -> Self {
|
||||
let mut count: gsize = 0;
|
||||
@@ -1940,7 +2214,7 @@ fn kernel_name() -> *const gchar {
|
||||
c"ntoskrnl.exe".as_ptr() as *const gchar
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
#[cfg(feature = "linux-injected")]
|
||||
fn kernel_name() -> *const gchar {
|
||||
c"kernel".as_ptr() as *const gchar
|
||||
}
|
||||
@@ -1950,6 +2224,12 @@ fn kernel_name() -> *const gchar {
|
||||
c"kernel".as_ptr() as *const gchar
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
feature = "win9x",
|
||||
feature = "winnt",
|
||||
feature = "linux-injected",
|
||||
feature = "xnu-core"
|
||||
))]
|
||||
const KERNEL_PID: u32 = 0;
|
||||
|
||||
#[cfg(any(
|
||||
@@ -2185,7 +2465,6 @@ fn handle_create_script(payload_variant: *mut GVariant, request_id: u16) -> Opti
|
||||
return Some(HandlerResponse::error("Invalid payload format: expected string"));
|
||||
}
|
||||
let source = g_variant_get_string(payload_variant, core::ptr::null_mut());
|
||||
|
||||
gum_script_backend_create(
|
||||
gum_script_backend_obtain_qjs(),
|
||||
c"agent.js".as_ptr(),
|
||||
@@ -2195,7 +2474,6 @@ fn handle_create_script(payload_variant: *mut GVariant, request_id: u16) -> Opti
|
||||
Some(signed_to_be_called_back(on_script_created, 0)),
|
||||
Box::into_raw(Box::new(request_id)) as *mut c_void,
|
||||
);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -2296,7 +2574,7 @@ unsafe extern "C" fn frida_message_handler(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
#[cfg(feature = "win9x")]
|
||||
pub(crate) fn routed_arena() -> u64 {
|
||||
unsafe { ROUTED_ARENA }
|
||||
}
|
||||
@@ -2346,7 +2624,6 @@ fn handle_load_script(payload_variant: *mut GVariant, request_id: u16) -> Option
|
||||
Some(signed_to_be_called_back(on_script_loaded, 0)),
|
||||
Box::into_raw(Box::new(request_id)) as *mut c_void,
|
||||
);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -2383,7 +2660,6 @@ fn handle_destroy_script(payload_variant: *mut GVariant, request_id: u16) -> Opt
|
||||
Some(signed_to_be_called_back(on_script_destroyed, 0)),
|
||||
Box::into_raw(Box::new(request_id)) as *mut c_void,
|
||||
);
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -2483,14 +2759,27 @@ pub struct KernelAllocator;
|
||||
|
||||
unsafe impl GlobalAlloc for KernelAllocator {
|
||||
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
|
||||
kernel::alloc(layout.size())
|
||||
let mut memory: *mut c_void = ptr::null_mut();
|
||||
let alignment = layout.align().max(SMALLEST_ALIGNMENT);
|
||||
if unsafe { posix_memalign(&mut memory, alignment, layout.size()) } != 0 {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
memory as *mut u8
|
||||
}
|
||||
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
|
||||
kernel::free(ptr, layout.size());
|
||||
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
|
||||
unsafe { free(ptr as *mut c_void) };
|
||||
}
|
||||
}
|
||||
|
||||
const SMALLEST_ALIGNMENT: usize = 2 * core::mem::size_of::<*const u8>();
|
||||
|
||||
unsafe extern "C" {
|
||||
fn posix_memalign(memory: *mut *mut c_void, alignment: usize, size: usize) -> i32;
|
||||
fn free(memory: *mut c_void);
|
||||
}
|
||||
|
||||
#[global_allocator]
|
||||
static GLOBAL: KernelAllocator = KernelAllocator;
|
||||
extern crate alloc;
|
||||
|
||||
@@ -134,24 +134,16 @@ pub extern "C" fn __aeabi_uwrite8(value: u64, address: *mut u8) -> u64 {
|
||||
value
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "aarch64", not(any(feature = "linux-injected", feature = "xnu-core"))))]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __clear_cache(_start: *const u8, _end: *const u8) {
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"ic iallu", // Invalidate all instruction caches
|
||||
"dsb sy", // Data synchronization barrier
|
||||
"isb", // Instruction synchronization barrier
|
||||
options(nomem, nostack),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidating the whole instruction cache is the kernel's to do, and the copy runs where that
|
||||
// instruction is not allowed. Line by line is what both halves may ask for.
|
||||
#[cfg(all(target_arch = "aarch64", any(feature = "linux-injected", feature = "xnu-core")))]
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __clear_cache(start: *const u8, end: *const u8) {
|
||||
#[cfg(feature = "winnt")]
|
||||
if crate::winnt_user::flush_code(start, end) {
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-core")]
|
||||
let told = crate::kernel::cache_shape();
|
||||
#[cfg(not(feature = "xnu-core"))]
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use core::mem::size_of;
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
// What the kernel half and the copy leave for each other. Both reach it by its own address, so
|
||||
@@ -12,10 +13,6 @@ impl Arena {
|
||||
Arena { begins }
|
||||
}
|
||||
|
||||
pub fn at_offset(&self, offset: usize) -> usize {
|
||||
self.begins + offset
|
||||
}
|
||||
|
||||
// Until the frames flow, what the copy has to say is left here for the kernel half to read.
|
||||
pub fn say(&self, msg: &str) {
|
||||
let said = msg.as_bytes();
|
||||
@@ -28,14 +25,6 @@ impl Arena {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn said(&self) -> &str {
|
||||
let bytes =
|
||||
unsafe { core::slice::from_raw_parts((self.begins + SAID) as *const u8, SAID_SIZE) };
|
||||
let length = bytes.iter().position(|byte| *byte == 0).unwrap_or(SAID_SIZE);
|
||||
|
||||
core::str::from_utf8(&bytes[..length]).unwrap_or("")
|
||||
}
|
||||
|
||||
pub fn page_size(&self) -> u32 {
|
||||
self.word(PAGE_SIZE).load(Ordering::Acquire)
|
||||
}
|
||||
@@ -44,10 +33,6 @@ impl Arena {
|
||||
unsafe { ((self.begins + offset) as *mut u64).write_volatile(value) };
|
||||
}
|
||||
|
||||
pub fn progress(&self) -> u32 {
|
||||
self.word(PROGRESS).load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub fn note(&self, step: u32) {
|
||||
self.word(PROGRESS).store(step, Ordering::Release);
|
||||
}
|
||||
@@ -125,6 +110,84 @@ impl Arena {
|
||||
super::user::say_something();
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub fn request_executable(&self, address: u64, size: u64) -> u32 {
|
||||
self.leave(EXEC_ADDR, address);
|
||||
self.leave(EXEC_SIZE, size);
|
||||
let seq = self.word(EXEC_SEQ).load(Ordering::Relaxed).wrapping_add(1).max(1);
|
||||
self.word(EXEC_SEQ).store(seq, Ordering::Release);
|
||||
seq
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub fn executable_settled(&self, seq: u32) -> bool {
|
||||
self.word(EXEC_ACK).load(Ordering::Acquire) == seq
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub fn executable_was_granted(&self) -> bool {
|
||||
self.word(EXEC_OK).load(Ordering::Acquire) != 0
|
||||
}
|
||||
|
||||
pub fn wants_executable(&self) -> Option<(u64, u64)> {
|
||||
let seq = self.word(EXEC_SEQ).load(Ordering::Acquire);
|
||||
if seq == 0 || seq == self.word(EXEC_ACK).load(Ordering::Acquire) {
|
||||
return None;
|
||||
}
|
||||
Some((self.read_wide(EXEC_ADDR), self.read_wide(EXEC_SIZE)))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub fn grant_executable(&self, ok: bool) {
|
||||
self.word(EXEC_OK).store(ok as u32, Ordering::Release);
|
||||
let seq = self.word(EXEC_SEQ).load(Ordering::Acquire);
|
||||
self.word(EXEC_ACK).store(seq, Ordering::Release);
|
||||
}
|
||||
|
||||
pub fn request_registers(&self, thread: u32) -> u32 {
|
||||
self.word(REG_TID).store(thread, Ordering::Release);
|
||||
let seq = self.word(REG_SEQ).load(Ordering::Relaxed).wrapping_add(1).max(1);
|
||||
self.word(REG_SEQ).store(seq, Ordering::Release);
|
||||
seq
|
||||
}
|
||||
|
||||
pub fn registers_settled(&self, seq: u32) -> bool {
|
||||
self.word(REG_ACK).load(Ordering::Acquire) == seq
|
||||
}
|
||||
|
||||
pub fn registers_were_captured(&self) -> bool {
|
||||
self.word(REG_OK).load(Ordering::Acquire) != 0
|
||||
}
|
||||
|
||||
pub fn captured_registers(&self, into: &mut [u64]) {
|
||||
for (slot, word) in into.iter_mut().enumerate() {
|
||||
*word = self.read_wide(REG_STATE + slot * size_of::<u64>());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wants_registers(&self) -> Option<u32> {
|
||||
let seq = self.word(REG_SEQ).load(Ordering::Acquire);
|
||||
if seq == 0 || seq == self.word(REG_ACK).load(Ordering::Acquire) {
|
||||
return None;
|
||||
}
|
||||
Some(self.word(REG_TID).load(Ordering::Acquire))
|
||||
}
|
||||
|
||||
pub fn deliver_registers(&self, state: Option<&[u64]>) {
|
||||
if let Some(words) = state {
|
||||
for (slot, word) in words.iter().enumerate() {
|
||||
self.leave(REG_STATE + slot * size_of::<u64>(), *word);
|
||||
}
|
||||
}
|
||||
self.word(REG_OK).store(state.is_some() as u32, Ordering::Release);
|
||||
let seq = self.word(REG_SEQ).load(Ordering::Acquire);
|
||||
self.word(REG_ACK).store(seq, Ordering::Release);
|
||||
}
|
||||
|
||||
fn read_wide(&self, offset: usize) -> u64 {
|
||||
unsafe { ((self.begins + offset) as *const u64).read_volatile() }
|
||||
}
|
||||
|
||||
fn word(&self, offset: usize) -> &AtomicU32 {
|
||||
unsafe { &*((self.begins + offset) as *const AtomicU32) }
|
||||
}
|
||||
@@ -145,10 +208,22 @@ const THREADS: usize = 304;
|
||||
const THREAD_SLOTS: u32 = 32;
|
||||
pub const SAID_SIZE: usize = 192;
|
||||
|
||||
|
||||
pub const REPORTED: usize = 0;
|
||||
pub const HOME: usize = 4;
|
||||
pub const SAYS: usize = 8;
|
||||
pub const HEARS: usize = 52;
|
||||
pub const TO_KERNEL: usize = 12;
|
||||
pub const WOKEN: usize = 16;
|
||||
|
||||
const EXEC_SEQ: usize = 432;
|
||||
const EXEC_ACK: usize = 436;
|
||||
const EXEC_ADDR: usize = 440;
|
||||
const EXEC_SIZE: usize = 448;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const EXEC_OK: usize = 456;
|
||||
|
||||
const REG_SEQ: usize = 460;
|
||||
const REG_ACK: usize = 464;
|
||||
const REG_TID: usize = 468;
|
||||
const REG_OK: usize = 472;
|
||||
const REG_STATE: usize = 480;
|
||||
@@ -78,6 +78,10 @@ pub fn install_fault_reporter() {
|
||||
(primitives().install_fault_reporter)()
|
||||
}
|
||||
|
||||
pub fn poke_loop_wakeup() {
|
||||
(primitives().poke_loop_wakeup)()
|
||||
}
|
||||
|
||||
pub fn select_user() {
|
||||
unsafe { ACTIVE = &super::user::USER };
|
||||
}
|
||||
@@ -97,7 +101,7 @@ static KERNEL: Primitives = Primitives {
|
||||
panic: super::native::panic,
|
||||
alloc: super::native::alloc,
|
||||
free: super::native::free,
|
||||
alloc_heap: super::native::alloc,
|
||||
alloc_heap: super::native::alloc_heap,
|
||||
alloc_code: super::native::alloc_code,
|
||||
free_code: super::native::free_code,
|
||||
page_size: super::native::page_size,
|
||||
@@ -112,6 +116,7 @@ static KERNEL: Primitives = Primitives {
|
||||
current_process_id: super::native::current_process_id,
|
||||
current_thread_id: super::native::current_thread_id,
|
||||
install_fault_reporter: super::native::install_fault_reporter,
|
||||
poke_loop_wakeup: super::native::poke_loop_wakeup,
|
||||
};
|
||||
|
||||
pub struct Primitives {
|
||||
@@ -134,4 +139,5 @@ pub struct Primitives {
|
||||
pub current_process_id: fn() -> u32,
|
||||
pub current_thread_id: fn() -> u64,
|
||||
pub install_fault_reporter: fn(),
|
||||
pub poke_loop_wakeup: fn(),
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
// Linux hostlink over the Android emulator's QEMU pipe, which is how the emulator
|
||||
// carries adb as well: open /dev/qemu_pipe, name the "pipe:unix:<path>" service,
|
||||
// and the emulator joins that stream to the host UNIX socket <path>, where
|
||||
// frida-core listens. Framed like the other hostlinks, with a 4-byte
|
||||
// little-endian length in front of each payload.
|
||||
//
|
||||
// Every open of the pipe is a channel of its own, handed out by the guest's own
|
||||
// driver, so the agent shares no device with the drivers already running.
|
||||
|
||||
use core::cell::UnsafeCell;
|
||||
use core::ffi::{c_int, c_void};
|
||||
use core::ptr;
|
||||
use core::sync::atomic::{AtomicPtr, AtomicU32, Ordering};
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::native;
|
||||
|
||||
const O_RDWR: c_int = 2;
|
||||
const PIPE_PATH: &str = "/dev/qemu_pipe\0";
|
||||
const LAST_ERRNO: usize = 4095;
|
||||
|
||||
static PENDING: AtomicU32 = AtomicU32::new(0);
|
||||
static WAKE_TOKEN: AtomicPtr<u8> = AtomicPtr::new(ptr::null_mut());
|
||||
struct Shared(UnsafeCell<Vec<u8>>);
|
||||
|
||||
unsafe impl Sync for Shared {}
|
||||
|
||||
static INCOMING: Shared = Shared(UnsafeCell::new(Vec::new()));
|
||||
static INCOMING_LOCK: AtomicU32 = AtomicU32::new(0);
|
||||
static PIPE: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());
|
||||
|
||||
pub fn a_turn_is_wanted() -> bool {
|
||||
PENDING.load(Ordering::Acquire) != 0
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
file: *mut c_void,
|
||||
rx_lenbuf: [u8; 4],
|
||||
rx_lenhave: usize,
|
||||
rx_buf: Vec<u8>,
|
||||
rx_have: usize,
|
||||
rx_need: usize,
|
||||
}
|
||||
|
||||
pub struct Hostlink {
|
||||
state: UnsafeCell<Inner>,
|
||||
on_rx: Option<fn(&[u8])>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Hostlink {}
|
||||
unsafe impl Sync for Hostlink {}
|
||||
|
||||
impl Hostlink {
|
||||
pub fn init(path: &str, on_rx: Option<fn(&[u8])>, wake_token: *const u8) -> Result<Self, ()> {
|
||||
WAKE_TOKEN.store(wake_token as *mut u8, Ordering::Release);
|
||||
|
||||
let file = open_the_pipe()?;
|
||||
PIPE.store(file, Ordering::Release);
|
||||
|
||||
let mut service = Vec::with_capacity(path.len() + 12);
|
||||
service.extend_from_slice(b"pipe:unix:");
|
||||
service.extend_from_slice(path.as_bytes());
|
||||
service.push(0);
|
||||
if write_all(file, &service).is_err() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
native::spawn_thread(read_forever, ptr::null_mut());
|
||||
|
||||
Ok(Hostlink {
|
||||
state: UnsafeCell::new(Inner {
|
||||
file,
|
||||
rx_lenbuf: [0; 4],
|
||||
rx_lenhave: 0,
|
||||
rx_buf: Vec::new(),
|
||||
rx_have: 0,
|
||||
rx_need: 0,
|
||||
}),
|
||||
on_rx,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn send(&self, payload: &[u8]) {
|
||||
let s = unsafe { &*self.state.get() };
|
||||
let length = (payload.len() as u32).to_le_bytes();
|
||||
let _ = write_all(s.file, &length);
|
||||
let _ = write_all(s.file, payload);
|
||||
}
|
||||
|
||||
pub fn process(&self) {
|
||||
PENDING.store(0, Ordering::Release);
|
||||
|
||||
let arrived = take_what_arrived();
|
||||
if arrived.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
let mut at = 0;
|
||||
while at < arrived.len() {
|
||||
if s.rx_lenhave < 4 {
|
||||
let want = core::cmp::min(4 - s.rx_lenhave, arrived.len() - at);
|
||||
s.rx_lenbuf[s.rx_lenhave..s.rx_lenhave + want]
|
||||
.copy_from_slice(&arrived[at..at + want]);
|
||||
s.rx_lenhave += want;
|
||||
at += want;
|
||||
if s.rx_lenhave < 4 {
|
||||
break;
|
||||
}
|
||||
s.rx_need = u32::from_le_bytes(s.rx_lenbuf) as usize;
|
||||
s.rx_have = 0;
|
||||
s.rx_buf.resize(s.rx_need, 0);
|
||||
}
|
||||
|
||||
let want = core::cmp::min(s.rx_need - s.rx_have, arrived.len() - at);
|
||||
let lo = s.rx_have;
|
||||
s.rx_buf[lo..lo + want].copy_from_slice(&arrived[at..at + want]);
|
||||
s.rx_have += want;
|
||||
at += want;
|
||||
if s.rx_have < s.rx_need {
|
||||
break;
|
||||
}
|
||||
|
||||
let frame = core::mem::take(&mut s.rx_buf);
|
||||
let need = s.rx_need;
|
||||
s.rx_lenhave = 0;
|
||||
s.rx_have = 0;
|
||||
s.rx_need = 0;
|
||||
|
||||
if let Some(cb) = self.on_rx {
|
||||
cb(&frame[..need]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shutdown(&self) {
|
||||
PIPE.store(ptr::null_mut(), Ordering::Release);
|
||||
}
|
||||
}
|
||||
|
||||
fn open_the_pipe() -> Result<*mut c_void, ()> {
|
||||
let file = unsafe { _filp_open(PIPE_PATH.as_ptr(), O_RDWR, 0) };
|
||||
if (file as usize) > usize::MAX - LAST_ERRNO {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
Ok(file)
|
||||
}
|
||||
|
||||
// The pipe blocks until the host has something, so a thread of its own is what
|
||||
// reads it; the loop is told there is a turn to take rather than asking.
|
||||
unsafe extern "C" fn read_forever(_parameter: *mut c_void, _wait_result: i32) {
|
||||
let mut chunk = [0u8; 4096];
|
||||
|
||||
loop {
|
||||
let file = PIPE.load(Ordering::Acquire);
|
||||
if file.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let n = read_some(file, &mut chunk);
|
||||
if n <= 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
held(|| unsafe { (*INCOMING.0.get()).extend_from_slice(&chunk[..n as usize]) });
|
||||
|
||||
PENDING.store(1, Ordering::Release);
|
||||
crate::nudge_the_loop(WAKE_TOKEN.load(Ordering::Acquire) as *const u8);
|
||||
}
|
||||
}
|
||||
|
||||
fn take_what_arrived() -> Vec<u8> {
|
||||
held(|| core::mem::take(unsafe { &mut *INCOMING.0.get() }))
|
||||
}
|
||||
|
||||
fn held<T>(action: impl FnOnce() -> T) -> T {
|
||||
while INCOMING_LOCK
|
||||
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
|
||||
let outcome = action();
|
||||
|
||||
INCOMING_LOCK.store(0, Ordering::Release);
|
||||
|
||||
outcome
|
||||
}
|
||||
|
||||
fn read_some(file: *mut c_void, into: &mut [u8]) -> isize {
|
||||
let mut position: i64 = 0;
|
||||
unsafe { native::pipe_read(file, into.as_mut_ptr(), into.len(), &mut position) }
|
||||
}
|
||||
|
||||
fn write_all(file: *mut c_void, bytes: &[u8]) -> Result<(), ()> {
|
||||
let mut sent = 0;
|
||||
while sent < bytes.len() {
|
||||
let mut position: i64 = 0;
|
||||
let n = unsafe {
|
||||
native::pipe_write(file, bytes[sent..].as_ptr(), bytes.len() - sent, &mut position)
|
||||
};
|
||||
if n <= 0 {
|
||||
return Err(());
|
||||
}
|
||||
sent += n as usize;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
static _filp_open: unsafe extern "C" fn(*const u8, c_int, c_int) -> *mut c_void;
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
// Linux hostlink over AF_VSOCK, bridged to the host by the Android emulator's
|
||||
// pipe-over-vsock connector: connect to CID 2 (host) on port 5002, send a
|
||||
// NUL-terminated "pipe:unix:<path>" handshake, and the emulator connects that
|
||||
// stream to the host UNIX socket <path>, where frida-core listens. Framed the
|
||||
// same way as the other hostlinks (4-byte little-endian length prefix).
|
||||
//
|
||||
// The agent uses the guest kernel's own socket API, so it never drives a virtio
|
||||
// device and never fights the guest's own drivers.
|
||||
|
||||
use core::cell::UnsafeCell;
|
||||
use core::ffi::{c_int, c_void};
|
||||
use core::ptr;
|
||||
use core::sync::atomic::{AtomicPtr, AtomicU32, AtomicUsize, Ordering};
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
const AF_VSOCK: c_int = 40;
|
||||
const SOCK_STREAM: c_int = 1;
|
||||
const VMADDR_CID_HOST: u32 = 2;
|
||||
const MSG_DONTWAIT: c_int = 0x40;
|
||||
const PIPE_CONNECTOR_PORT: u32 = 5002;
|
||||
|
||||
#[repr(C)]
|
||||
struct SockaddrVm {
|
||||
svm_family: u16,
|
||||
svm_reserved1: u16,
|
||||
svm_port: u32,
|
||||
svm_cid: u32,
|
||||
svm_zero: [u8; 4],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct Kvec {
|
||||
iov_base: *mut c_void,
|
||||
iov_len: usize,
|
||||
}
|
||||
|
||||
type Socket = *mut c_void;
|
||||
|
||||
static PENDING: AtomicU32 = AtomicU32::new(0);
|
||||
static WAKE_TOKEN: AtomicPtr<u8> = AtomicPtr::new(ptr::null_mut());
|
||||
static ORIG_DATA_READY: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
pub fn a_turn_is_wanted() -> bool {
|
||||
PENDING.load(Ordering::Acquire) != 0
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
so: Socket,
|
||||
rx_lenbuf: [u8; 4],
|
||||
rx_lenhave: usize,
|
||||
rx_buf: Vec<u8>,
|
||||
rx_have: usize,
|
||||
rx_need: usize,
|
||||
}
|
||||
|
||||
pub struct Hostlink {
|
||||
state: UnsafeCell<Inner>,
|
||||
on_rx: Option<fn(&[u8])>,
|
||||
}
|
||||
|
||||
unsafe impl Send for Hostlink {}
|
||||
|
||||
impl Hostlink {
|
||||
pub fn init(path: &str, on_rx: Option<fn(&[u8])>, wake_token: *const u8) -> Result<Self, ()> {
|
||||
WAKE_TOKEN.store(wake_token as *mut u8, Ordering::Release);
|
||||
|
||||
unsafe {
|
||||
let mut so: Socket = ptr::null_mut();
|
||||
if _sock_create_kern(_init_net_addr(), AF_VSOCK, SOCK_STREAM, 0, &mut so) != 0
|
||||
|| so.is_null()
|
||||
{
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let addr = SockaddrVm {
|
||||
svm_family: AF_VSOCK as u16,
|
||||
svm_reserved1: 0,
|
||||
svm_port: PIPE_CONNECTOR_PORT,
|
||||
svm_cid: VMADDR_CID_HOST,
|
||||
svm_zero: [0; 4],
|
||||
};
|
||||
if _kernel_connect(
|
||||
so,
|
||||
&addr as *const _ as *const c_void,
|
||||
core::mem::size_of::<SockaddrVm>() as c_int,
|
||||
0,
|
||||
) != 0
|
||||
{
|
||||
_sock_release(so);
|
||||
return Err(());
|
||||
}
|
||||
|
||||
hook_data_ready(so);
|
||||
|
||||
let hostlink = Hostlink {
|
||||
state: UnsafeCell::new(Inner {
|
||||
so,
|
||||
rx_lenbuf: [0; 4],
|
||||
rx_lenhave: 0,
|
||||
rx_buf: Vec::new(),
|
||||
rx_have: 0,
|
||||
rx_need: 0,
|
||||
}),
|
||||
on_rx,
|
||||
};
|
||||
|
||||
let mut handshake = Vec::with_capacity(path.len() + 12);
|
||||
handshake.extend_from_slice(b"pipe:unix:");
|
||||
handshake.extend_from_slice(path.as_bytes());
|
||||
handshake.push(0);
|
||||
send_all(so, &handshake);
|
||||
|
||||
Ok(hostlink)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send(&self, payload: &[u8]) {
|
||||
let s = unsafe { &*self.state.get() };
|
||||
let length = (payload.len() as u32).to_le_bytes();
|
||||
unsafe {
|
||||
send_all(s.so, &length);
|
||||
send_all(s.so, payload);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process(&self) {
|
||||
PENDING.store(0, Ordering::Release);
|
||||
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
loop {
|
||||
if s.rx_lenhave < 4 {
|
||||
let lo = s.rx_lenhave;
|
||||
let n = unsafe { recv_nonblocking(s.so, &mut s.rx_lenbuf[lo..4]) };
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
s.rx_lenhave += n;
|
||||
if s.rx_lenhave < 4 {
|
||||
continue;
|
||||
}
|
||||
s.rx_need = u32::from_le_bytes(s.rx_lenbuf) as usize;
|
||||
s.rx_have = 0;
|
||||
s.rx_buf.resize(s.rx_need, 0);
|
||||
}
|
||||
|
||||
while s.rx_have < s.rx_need {
|
||||
let lo = s.rx_have;
|
||||
let hi = s.rx_need;
|
||||
let n = unsafe { recv_nonblocking(s.so, &mut s.rx_buf[lo..hi]) };
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
s.rx_have += n;
|
||||
}
|
||||
|
||||
let frame = core::mem::take(&mut s.rx_buf);
|
||||
let need = s.rx_need;
|
||||
s.rx_lenhave = 0;
|
||||
s.rx_have = 0;
|
||||
s.rx_need = 0;
|
||||
|
||||
if let Some(cb) = self.on_rx {
|
||||
cb(&frame[..need]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shutdown(&self) {
|
||||
let s = unsafe { &*self.state.get() };
|
||||
unsafe { _sock_release(s.so) };
|
||||
}
|
||||
}
|
||||
|
||||
// The vsock stack calls sk->sk_data_ready(sk) via a CONFIG_CFI_CLANG-checked indirect
|
||||
// call, so the replacement must carry the type id of a void(struct sock*) callback;
|
||||
// sock_def_readable is the kernel's own one. Store the original and chain to it so the
|
||||
// socket's normal wakeups still fire.
|
||||
unsafe fn hook_data_ready(so: Socket) {
|
||||
let (Some(sk_off), Some(dr_off)) = (
|
||||
super::layout::field_offset("socket", "sk"),
|
||||
super::layout::field_offset("sock", "sk_data_ready"),
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let sk = unsafe { *((so as usize + sk_off) as *const usize) };
|
||||
if sk == 0 {
|
||||
return;
|
||||
}
|
||||
let slot = (sk + dr_off) as *mut usize;
|
||||
ORIG_DATA_READY.store(unsafe { *slot }, Ordering::Release);
|
||||
|
||||
let thunk =
|
||||
super::native::kcfi_thunk(on_data_ready as *const () as usize, sock_def_readable_ref());
|
||||
unsafe { *slot = thunk };
|
||||
}
|
||||
|
||||
fn sock_def_readable_ref() -> Option<usize> {
|
||||
let addr = unsafe { _sock_def_readable_addr() };
|
||||
if addr == 0 { None } else { Some(addr) }
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_data_ready(sk: *mut c_void) {
|
||||
PENDING.fetch_add(1, Ordering::Release);
|
||||
let token = WAKE_TOKEN.load(Ordering::Acquire);
|
||||
if !token.is_null() {
|
||||
crate::nudge_the_loop(token as *const u8);
|
||||
}
|
||||
let orig = ORIG_DATA_READY.load(Ordering::Acquire);
|
||||
if orig != 0 {
|
||||
let f: unsafe extern "C" fn(*mut c_void) = unsafe { core::mem::transmute(orig) };
|
||||
unsafe { f(sk) };
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn recv_nonblocking(so: Socket, dst: &mut [u8]) -> usize {
|
||||
let mut vec = Kvec {
|
||||
iov_base: dst.as_mut_ptr() as *mut c_void,
|
||||
iov_len: dst.len(),
|
||||
};
|
||||
let mut msg = [0u64; 16];
|
||||
let n = unsafe {
|
||||
_kernel_recvmsg(
|
||||
so,
|
||||
msg.as_mut_ptr() as *mut c_void,
|
||||
&mut vec,
|
||||
1,
|
||||
dst.len(),
|
||||
MSG_DONTWAIT,
|
||||
)
|
||||
};
|
||||
if n <= 0 { 0 } else { n as usize }
|
||||
}
|
||||
|
||||
unsafe fn send_all(so: Socket, bytes: &[u8]) {
|
||||
let mut gone = 0;
|
||||
while gone < bytes.len() {
|
||||
let mut vec = Kvec {
|
||||
iov_base: unsafe { bytes.as_ptr().add(gone) } as *mut c_void,
|
||||
iov_len: bytes.len() - gone,
|
||||
};
|
||||
let mut msg = [0u64; 16];
|
||||
let n = unsafe {
|
||||
_kernel_sendmsg(
|
||||
so,
|
||||
msg.as_mut_ptr() as *mut c_void,
|
||||
&mut vec,
|
||||
1,
|
||||
bytes.len() - gone,
|
||||
)
|
||||
};
|
||||
if n <= 0 {
|
||||
return;
|
||||
}
|
||||
gone += n as usize;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn _init_net_addr() -> *mut c_void {
|
||||
ptr::addr_of!(_init_net) as *mut c_void
|
||||
}
|
||||
|
||||
unsafe fn _sock_def_readable_addr() -> usize {
|
||||
unsafe { _sock_def_readable }.map_or(0, |f| f as usize)
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
static _init_net: c_void;
|
||||
static _sock_create_kern:
|
||||
unsafe extern "C" fn(*mut c_void, c_int, c_int, c_int, *mut Socket) -> c_int;
|
||||
static _kernel_connect:
|
||||
unsafe extern "C" fn(Socket, *const c_void, c_int, c_int) -> c_int;
|
||||
static _kernel_sendmsg:
|
||||
unsafe extern "C" fn(Socket, *mut c_void, *mut Kvec, usize, usize) -> c_int;
|
||||
static _kernel_recvmsg:
|
||||
unsafe extern "C" fn(Socket, *mut c_void, *mut Kvec, usize, usize, c_int) -> c_int;
|
||||
static _sock_release: unsafe extern "C" fn(Socket);
|
||||
static _sock_def_readable: Option<unsafe extern "C" fn()>;
|
||||
}
|
||||
@@ -2,9 +2,16 @@ use core::ffi::{c_int, c_void};
|
||||
use core::ptr;
|
||||
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::string::String;
|
||||
|
||||
use super::layout::{field_offset, struct_size};
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
use crate::bindings::{
|
||||
gum_interceptor_begin_transaction, gum_interceptor_end_transaction, gum_interceptor_obtain,
|
||||
gum_interceptor_replace_fast,
|
||||
};
|
||||
|
||||
use super::layout::field_offset;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use super::layout::struct_size;
|
||||
use super::STACK_SPAN;
|
||||
use super::native;
|
||||
use super::processes::task_with_id;
|
||||
@@ -39,7 +46,6 @@ pub fn inject_into_process(id: u32) -> u32 {
|
||||
task,
|
||||
base: home.base,
|
||||
arena: home.arena,
|
||||
seen_by_the_copy: home.seen_by_the_copy,
|
||||
stack: home.stack,
|
||||
says: ptr::null_mut(),
|
||||
hears: ptr::null_mut(),
|
||||
@@ -52,8 +58,11 @@ pub fn inject_into_process(id: u32) -> u32 {
|
||||
|
||||
fn take_what_the_copy_opened(placed: &mut Placement) {
|
||||
let arena = Arena::at(placed.arena);
|
||||
placed.says = native::take_the_file(arena.says_through());
|
||||
placed.hears = native::take_the_file(arena.hears_through());
|
||||
let says_fd = arena.says_through();
|
||||
let hears_fd = arena.hears_through();
|
||||
let copy = COPY_TASK.load(core::sync::atomic::Ordering::Acquire) as *mut c_void;
|
||||
placed.says = native::take_the_file_of(copy, says_fd);
|
||||
placed.hears = native::take_the_file_of(copy, hears_fd);
|
||||
listen_to_the_copy(placed.says);
|
||||
}
|
||||
|
||||
@@ -63,7 +72,7 @@ fn listen_to_the_copy(says: *mut c_void) {
|
||||
|
||||
unsafe extern "C" fn carry_what_the_copy_says(argument: *mut c_void, _reason: i32) {
|
||||
while native::wait_for_a_word(argument) {
|
||||
native::wake(argument as *const u8);
|
||||
crate::nudge_the_loop(crate::glib::wakeup_token());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +80,6 @@ struct Placement {
|
||||
task: usize,
|
||||
base: usize,
|
||||
arena: usize,
|
||||
seen_by_the_copy: usize,
|
||||
stack: usize,
|
||||
says: *mut c_void,
|
||||
hears: *mut c_void,
|
||||
@@ -86,7 +94,6 @@ fn pid_reported_by(placed: &Placement) -> u32 {
|
||||
struct Home {
|
||||
base: usize,
|
||||
arena: usize,
|
||||
seen_by_the_copy: usize,
|
||||
stack: usize,
|
||||
}
|
||||
|
||||
@@ -126,16 +133,12 @@ fn map_and_start(id: u32) -> Option<Home> {
|
||||
|
||||
start(base, where_the_copy_sees_it, stack);
|
||||
|
||||
Some(Home {
|
||||
base,
|
||||
arena,
|
||||
seen_by_the_copy: where_the_copy_sees_it,
|
||||
stack,
|
||||
})
|
||||
Some(Home { base, arena, stack })
|
||||
}
|
||||
|
||||
// The kernel half reaches the same pages by an address of its own, so what the two leave for
|
||||
// each other needs neither the address space borrowed nor a copy made.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn borrowed_memory() -> Option<usize> {
|
||||
let at = field_offset("task_struct", "mm")?;
|
||||
|
||||
@@ -185,12 +188,13 @@ fn map_a_copy() -> Option<usize> {
|
||||
let shared = crate::writable_half_start() - own;
|
||||
let private = crate::writable_half_size();
|
||||
|
||||
let executable_up_front = if cfg!(target_arch = "aarch64") { 0 } else { PROT_EXEC };
|
||||
let base = unsafe {
|
||||
_vm_mmap(
|
||||
ptr::null_mut(),
|
||||
0,
|
||||
size + ARENA_SIZE,
|
||||
PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
PROT_READ | PROT_WRITE | executable_up_front,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS,
|
||||
0,
|
||||
)
|
||||
@@ -199,6 +203,9 @@ fn map_a_copy() -> Option<usize> {
|
||||
return None;
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
keep_the_copy_in_small_pages(base, size + ARENA_SIZE)?;
|
||||
|
||||
give(base, own, shared)?;
|
||||
|
||||
let rebased = native::alloc(private);
|
||||
@@ -207,9 +214,79 @@ fn map_a_copy() -> Option<usize> {
|
||||
native::free(rebased, private);
|
||||
handed_over?;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
make_the_code_executable(base, shared)?;
|
||||
|
||||
Some(base)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn keep_the_copy_in_small_pages(base: usize, size: usize) -> Option<()> {
|
||||
let memory = borrowed_memory()? as *mut c_void;
|
||||
unsafe { _do_madvise(memory, base, size, MADV_NOHUGEPAGE) };
|
||||
Some(())
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn make_the_code_executable(base: usize, size: usize) -> Option<()> {
|
||||
let memory = borrowed_memory()? as *mut c_void;
|
||||
unsafe {
|
||||
let apply = _apply_to_existing_page_range?;
|
||||
let clean_to_unification = _caches_clean_inval_pou?;
|
||||
if apply(memory, base, size, frida_pte_exec_trampoline, ptr::null_mut()) != 0 {
|
||||
return None;
|
||||
}
|
||||
clean_to_unification(base, base + size);
|
||||
flush_user_translations();
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
core::arch::global_asm!(
|
||||
".pushsection .text,\"ax\",%progbits",
|
||||
".balign 4",
|
||||
".word 0xa8320d42",
|
||||
".globl frida_pte_exec_trampoline",
|
||||
".type frida_pte_exec_trampoline,%function",
|
||||
"frida_pte_exec_trampoline:",
|
||||
"bti c",
|
||||
"b {inner}",
|
||||
".popsection",
|
||||
inner = sym grant_user_execute,
|
||||
);
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
unsafe extern "C" {
|
||||
fn frida_pte_exec_trampoline(pte: *mut c_void, address: usize, data: *mut c_void) -> c_int;
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
unsafe extern "C" fn grant_user_execute(entry: *mut c_void, _address: usize, _data: *mut c_void) -> c_int {
|
||||
let entry = entry as *mut u64;
|
||||
let revised = (unsafe { entry.read_volatile() } | PTE_READ_ONLY) & !PTE_USER_EXECUTE_NEVER;
|
||||
unsafe { entry.write_volatile(revised) };
|
||||
0
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
unsafe fn flush_user_translations() {
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"dsb ishst",
|
||||
"tlbi vmalle1is",
|
||||
"dsb ish",
|
||||
"isb",
|
||||
options(nostack, preserves_flags),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PTE_READ_ONLY: u64 = 1 << 7;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PTE_USER_EXECUTE_NEVER: u64 = 1 << 54;
|
||||
|
||||
fn give(destination: usize, source: usize, size: usize) -> Option<()> {
|
||||
let opened = unsafe { open_user_access() };
|
||||
let left =
|
||||
@@ -287,6 +364,8 @@ pub unsafe extern "C" fn frida_cb_user(argument: *mut c_void) -> c_int {
|
||||
let (arena, bootstrap, stack) = unsafe { ((*entry).arena, (*entry).bootstrap, (*entry).stack) };
|
||||
native::free(entry as *mut u8, size_of::<Entry>());
|
||||
|
||||
COPY_TASK.store(native::current_task() as usize, core::sync::atomic::Ordering::Release);
|
||||
|
||||
let Some(places) = describe_registers() else {
|
||||
return 0;
|
||||
};
|
||||
@@ -309,11 +388,13 @@ pub unsafe extern "C" fn frida_cb_user(argument: *mut c_void) -> c_int {
|
||||
// What a task starts with in userland is at the top of the kernel stack it was given.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn registers_of_this_task(places: &Places) -> usize {
|
||||
let stack = unsafe {
|
||||
((native::current_task() as usize + places.stack_of_task) as *const usize).read()
|
||||
};
|
||||
registers_of_task(native::current_task() as usize, places)
|
||||
}
|
||||
|
||||
stack + STACK_SPAN - places.size
|
||||
fn registers_of_task(task: usize, places: &Places) -> usize {
|
||||
let stack = unsafe { ((task + places.stack_of_task) as *const usize).read() };
|
||||
|
||||
stack + STACK_SPAN - TOP_OF_STACK_PADDING - places.size
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -335,6 +416,273 @@ const TOP_OF_STACK_PADDING: usize = 8;
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "arm")))]
|
||||
const TOP_OF_STACK_PADDING: usize = 0;
|
||||
|
||||
pub fn thread_state(id: u32) -> Option<crate::bindings::GumThreadState> {
|
||||
use crate::bindings::{
|
||||
GumThreadState_GUM_THREAD_HALTED, GumThreadState_GUM_THREAD_RUNNING,
|
||||
GumThreadState_GUM_THREAD_STOPPED, GumThreadState_GUM_THREAD_UNINTERRUPTIBLE,
|
||||
GumThreadState_GUM_THREAD_WAITING,
|
||||
};
|
||||
|
||||
let task = native::task_for_thread(id) as usize;
|
||||
if task == 0 {
|
||||
return None;
|
||||
}
|
||||
let at = field_offset("task_struct", "__state")?;
|
||||
let running = unsafe { ((task + at) as *const u32).read() };
|
||||
|
||||
Some(if running == 0 {
|
||||
GumThreadState_GUM_THREAD_RUNNING
|
||||
} else if running & TASK_UNINTERRUPTIBLE != 0 {
|
||||
GumThreadState_GUM_THREAD_UNINTERRUPTIBLE
|
||||
} else if running & TASK_INTERRUPTIBLE != 0 {
|
||||
GumThreadState_GUM_THREAD_WAITING
|
||||
} else if running & (TASK_STOPPED | TASK_TRACED) != 0 {
|
||||
GumThreadState_GUM_THREAD_STOPPED
|
||||
} else {
|
||||
GumThreadState_GUM_THREAD_HALTED
|
||||
})
|
||||
}
|
||||
|
||||
const TASK_INTERRUPTIBLE: u32 = 0x0001;
|
||||
const TASK_UNINTERRUPTIBLE: u32 = 0x0002;
|
||||
const TASK_STOPPED: u32 = 0x0004;
|
||||
const TASK_TRACED: u32 = 0x0008;
|
||||
|
||||
fn registers_of_thread(id: u32) -> Option<usize> {
|
||||
let task = native::task_for_thread(id) as usize;
|
||||
if task == 0 {
|
||||
return None;
|
||||
}
|
||||
let mut places = describe_registers()?;
|
||||
if places.stack_of_task == 0 {
|
||||
places.stack_of_task = field_offset("task_struct", "stack")?;
|
||||
}
|
||||
Some(registers_of_task(task, &places))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub fn capture_registers(id: u32) -> Option<crate::kernel::CpuState> {
|
||||
let regs = registers_of_thread(id)?;
|
||||
let general = regs as *const u64;
|
||||
|
||||
let mut x = [0u64; 29];
|
||||
for slot in 0..29 {
|
||||
x[slot] = unsafe { general.add(slot).read() };
|
||||
}
|
||||
|
||||
Some(crate::kernel::CpuState {
|
||||
x,
|
||||
fp: unsafe { general.add(29).read() },
|
||||
lr: unsafe { general.add(30).read() },
|
||||
sp: unsafe { general.add(31).read() },
|
||||
pc: unsafe { general.add(32).read() },
|
||||
nzcv: unsafe { general.add(33).read() },
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn capture_registers(id: u32) -> Option<crate::kernel::CpuState> {
|
||||
let regs = registers_of_thread(id)?;
|
||||
let at = |slot: usize| unsafe { ((regs + slot * 8) as *const u64).read() };
|
||||
|
||||
Some(crate::kernel::CpuState {
|
||||
r15: at(0),
|
||||
r14: at(1),
|
||||
r13: at(2),
|
||||
r12: at(3),
|
||||
rbp: at(4),
|
||||
rbx: at(5),
|
||||
r11: at(6),
|
||||
r10: at(7),
|
||||
r9: at(8),
|
||||
r8: at(9),
|
||||
rax: at(10),
|
||||
rcx: at(11),
|
||||
rdx: at(12),
|
||||
rsi: at(13),
|
||||
rdi: at(14),
|
||||
rip: at(16),
|
||||
rsp: at(19),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub fn capture_registers(id: u32) -> Option<crate::kernel::CpuState> {
|
||||
let regs = registers_of_thread(id)?;
|
||||
let at = |slot: usize| unsafe { ((regs + slot * 4) as *const u32).read() };
|
||||
|
||||
Some(crate::kernel::CpuState {
|
||||
ebx: at(0),
|
||||
ecx: at(1),
|
||||
edx: at(2),
|
||||
esi: at(3),
|
||||
edi: at(4),
|
||||
ebp: at(5),
|
||||
eax: at(6),
|
||||
eip: at(12),
|
||||
esp: at(15),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub fn capture_registers(id: u32) -> Option<crate::kernel::CpuState> {
|
||||
let regs = registers_of_thread(id)?;
|
||||
let at = |slot: usize| unsafe { ((regs + slot * 4) as *const u32).read() };
|
||||
|
||||
let mut r = [0u32; 8];
|
||||
for slot in 0..8 {
|
||||
r[slot] = at(slot);
|
||||
}
|
||||
|
||||
Some(crate::kernel::CpuState {
|
||||
pc: at(15),
|
||||
sp: at(13),
|
||||
cpsr: at(16),
|
||||
r,
|
||||
r8: at(8),
|
||||
r9: at(9),
|
||||
r10: at(10),
|
||||
r11: at(11),
|
||||
r12: at(12),
|
||||
lr: at(14),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub const REGISTER_WORDS: usize = 34;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub const REGISTER_WORDS: usize = 17;
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub const REGISTER_WORDS: usize = 9;
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub const REGISTER_WORDS: usize = 17;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub fn words_from_registers(state: &crate::kernel::CpuState) -> [u64; REGISTER_WORDS] {
|
||||
let mut words = [0u64; REGISTER_WORDS];
|
||||
words[..29].copy_from_slice(&state.x);
|
||||
words[29] = state.fp;
|
||||
words[30] = state.lr;
|
||||
words[31] = state.sp;
|
||||
words[32] = state.pc;
|
||||
words[33] = state.nzcv;
|
||||
words
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub fn registers_from_words(words: &[u64; REGISTER_WORDS]) -> crate::kernel::CpuState {
|
||||
let mut x = [0u64; 29];
|
||||
x.copy_from_slice(&words[..29]);
|
||||
crate::kernel::CpuState {
|
||||
x,
|
||||
fp: words[29],
|
||||
lr: words[30],
|
||||
sp: words[31],
|
||||
pc: words[32],
|
||||
nzcv: words[33],
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn words_from_registers(state: &crate::kernel::CpuState) -> [u64; REGISTER_WORDS] {
|
||||
[
|
||||
state.rip, state.r15, state.r14, state.r13, state.r12, state.r11, state.r10, state.r9,
|
||||
state.r8, state.rdi, state.rsi, state.rbp, state.rsp, state.rbx, state.rdx, state.rcx,
|
||||
state.rax,
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn registers_from_words(words: &[u64; REGISTER_WORDS]) -> crate::kernel::CpuState {
|
||||
crate::kernel::CpuState {
|
||||
rip: words[0],
|
||||
r15: words[1],
|
||||
r14: words[2],
|
||||
r13: words[3],
|
||||
r12: words[4],
|
||||
r11: words[5],
|
||||
r10: words[6],
|
||||
r9: words[7],
|
||||
r8: words[8],
|
||||
rdi: words[9],
|
||||
rsi: words[10],
|
||||
rbp: words[11],
|
||||
rsp: words[12],
|
||||
rbx: words[13],
|
||||
rdx: words[14],
|
||||
rcx: words[15],
|
||||
rax: words[16],
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub fn words_from_registers(state: &crate::kernel::CpuState) -> [u64; REGISTER_WORDS] {
|
||||
[
|
||||
state.eip as u64,
|
||||
state.edi as u64,
|
||||
state.esi as u64,
|
||||
state.ebp as u64,
|
||||
state.esp as u64,
|
||||
state.ebx as u64,
|
||||
state.edx as u64,
|
||||
state.ecx as u64,
|
||||
state.eax as u64,
|
||||
]
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub fn registers_from_words(words: &[u64; REGISTER_WORDS]) -> crate::kernel::CpuState {
|
||||
crate::kernel::CpuState {
|
||||
eip: words[0] as u32,
|
||||
edi: words[1] as u32,
|
||||
esi: words[2] as u32,
|
||||
ebp: words[3] as u32,
|
||||
esp: words[4] as u32,
|
||||
ebx: words[5] as u32,
|
||||
edx: words[6] as u32,
|
||||
ecx: words[7] as u32,
|
||||
eax: words[8] as u32,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub fn words_from_registers(state: &crate::kernel::CpuState) -> [u64; REGISTER_WORDS] {
|
||||
let mut words = [0u64; REGISTER_WORDS];
|
||||
for slot in 0..8 {
|
||||
words[slot] = state.r[slot] as u64;
|
||||
}
|
||||
words[8] = state.r8 as u64;
|
||||
words[9] = state.r9 as u64;
|
||||
words[10] = state.r10 as u64;
|
||||
words[11] = state.r11 as u64;
|
||||
words[12] = state.r12 as u64;
|
||||
words[13] = state.sp as u64;
|
||||
words[14] = state.lr as u64;
|
||||
words[15] = state.pc as u64;
|
||||
words[16] = state.cpsr as u64;
|
||||
words
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub fn registers_from_words(words: &[u64; REGISTER_WORDS]) -> crate::kernel::CpuState {
|
||||
let mut r = [0u32; 8];
|
||||
for slot in 0..8 {
|
||||
r[slot] = words[slot] as u32;
|
||||
}
|
||||
crate::kernel::CpuState {
|
||||
pc: words[15] as u32,
|
||||
sp: words[13] as u32,
|
||||
cpsr: words[16] as u32,
|
||||
r,
|
||||
r8: words[8] as u32,
|
||||
r9: words[9] as u32,
|
||||
r10: words[10] as u32,
|
||||
r11: words[11] as u32,
|
||||
r12: words[12] as u32,
|
||||
lr: words[14] as u32,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
unsafe fn write_selectors(registers: usize, _places: &Places) {
|
||||
unsafe {
|
||||
@@ -456,33 +804,62 @@ pub fn a_copy_has_something_to_say() -> bool {
|
||||
unsafe { placements() }.values().any(|placed| what_it_says(placed).is_some())
|
||||
}
|
||||
|
||||
pub fn report_what_the_copies_hit() {
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub fn a_copy_wants_executable() -> bool {
|
||||
unsafe { placements() }
|
||||
.values()
|
||||
.any(|placed| Arena::at(placed.arena).wants_executable().is_some())
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub fn serve_executable_requests() {
|
||||
for placed in unsafe { placements() }.values() {
|
||||
let Some(hit) = what_it_says(placed) else {
|
||||
let arena = Arena::at(placed.arena);
|
||||
let Some((address, size)) = arena.wants_executable() else {
|
||||
continue;
|
||||
};
|
||||
bump_to(placed.arena + super::arena::PROGRESS, NOTHING_MORE);
|
||||
let granted = make_a_range_executable(placed.task, address as usize, size as usize);
|
||||
arena.grant_executable(granted);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn make_a_range_executable(task: usize, base: usize, size: usize) -> bool {
|
||||
let memory = unsafe { _get_task_mm(task as *mut c_void) };
|
||||
if memory.is_null() {
|
||||
return false;
|
||||
}
|
||||
unsafe { _kthread_use_mm(memory) };
|
||||
let granted = make_the_code_executable(base, size).is_some();
|
||||
unsafe { _kthread_unuse_mm(memory) };
|
||||
unsafe { _mmput(memory) };
|
||||
granted
|
||||
}
|
||||
|
||||
pub fn a_copy_is_asking() -> bool {
|
||||
unsafe { placements() }.values().any(|placed| {
|
||||
let arena = Arena::at(placed.arena);
|
||||
let said = arena.said();
|
||||
if hit == super::user::SPOKE {
|
||||
native::log(&alloc::format!("copy in {}: {}\n", pid_reported_by(placed), said));
|
||||
} else if hit == super::user::PANICKED {
|
||||
native::log(&alloc::format!("copy in {} gave up: {}\n", pid_reported_by(placed), said));
|
||||
} else {
|
||||
let kind = read_address(placed.arena + super::arena::FAULT_KIND);
|
||||
native::log(&alloc::format!(
|
||||
"copy in {} died on signal {} ({}) at {:#x}, pc {}, lr {}, image at {:#x}, {}\n",
|
||||
pid_reported_by(placed),
|
||||
kind as u32,
|
||||
(kind >> 32) as u32,
|
||||
read_address(placed.arena + super::arena::FAULT_ADDRESS),
|
||||
relative_to_image(read_address(placed.arena + super::arena::FAULT_PC), placed),
|
||||
relative_to_image(read_address(placed.arena + super::arena::FAULT_LR), placed),
|
||||
placed.base,
|
||||
said
|
||||
));
|
||||
arena.wants_registers().is_some() || arena.wants_executable().is_some()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn serve_register_requests() {
|
||||
for placed in unsafe { placements() }.values() {
|
||||
let arena = Arena::at(placed.arena);
|
||||
let Some(thread) = arena.wants_registers() else {
|
||||
continue;
|
||||
};
|
||||
let words = capture_registers(thread).map(|state| words_from_registers(&state));
|
||||
arena.deliver_registers(words.as_ref().map(|w| w.as_slice()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn report_what_the_copies_hit() {
|
||||
for placed in unsafe { placements() }.values() {
|
||||
if what_it_says(placed).is_none() {
|
||||
continue;
|
||||
}
|
||||
bump_to(placed.arena + super::arena::PROGRESS, NOTHING_MORE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,19 +877,11 @@ fn bump_to(address: usize, value: u32) {
|
||||
unsafe { (address as *mut u32).write_volatile(value) };
|
||||
}
|
||||
|
||||
fn relative_to_image(address: u64, placed: &Placement) -> String {
|
||||
let base = placed.base as u64;
|
||||
let size = crate::own_range().1 as u64;
|
||||
if address >= base && address - base < size {
|
||||
alloc::format!("image+{:#x}", address - base)
|
||||
} else {
|
||||
alloc::format!("{:#x}", address)
|
||||
}
|
||||
}
|
||||
|
||||
const NOTHING_MORE: u32 = 0;
|
||||
|
||||
fn watch_the_threads() {
|
||||
return;
|
||||
#[allow(unreachable_code)]
|
||||
if WATCHING.swap(true, core::sync::atomic::Ordering::AcqRel) {
|
||||
return;
|
||||
}
|
||||
@@ -526,6 +895,8 @@ fn watch_the_threads() {
|
||||
}
|
||||
|
||||
pub fn hold_what_is_spawned() {
|
||||
return;
|
||||
#[allow(unreachable_code)]
|
||||
if HOLDING.swap(true, core::sync::atomic::Ordering::AcqRel) {
|
||||
return;
|
||||
}
|
||||
@@ -558,6 +929,49 @@ pub unsafe extern "C" fn frida_cb_exec(_data: *mut c_void, task: *mut c_void, _w
|
||||
native::wake(task as *const u8);
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
pub fn watch_for_named_spawns() {
|
||||
if RENAME_WATCHING.swap(true, core::sync::atomic::Ordering::AcqRel) {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let interceptor = gum_interceptor_obtain();
|
||||
gum_interceptor_begin_transaction(interceptor);
|
||||
gum_interceptor_replace_fast(interceptor, ___set_task_comm as *mut c_void,
|
||||
frida_replaced_set_task_comm as *mut c_void, &raw mut ORIGINAL_SET_TASK_COMM,
|
||||
ptr::null());
|
||||
gum_interceptor_end_transaction(interceptor);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub fn watch_for_named_spawns() {
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static RENAME_WATCHING: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static mut ORIGINAL_SET_TASK_COMM: *mut c_void = ptr::null_mut();
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
unsafe extern "C" fn frida_replaced_set_task_comm(task: *mut c_void, buf: *const core::ffi::c_char,
|
||||
exec: c_int) {
|
||||
let name = unsafe { core::ffi::CStr::from_ptr(buf) };
|
||||
if super::spawn::claim_if_wanted(name.to_bytes()) {
|
||||
if let Some(id) = id_of(task as usize) {
|
||||
native::send_signal(STOP, task as usize);
|
||||
super::spawn::note_pending_spawn(id, name.to_bytes());
|
||||
native::wake(ptr::null());
|
||||
}
|
||||
}
|
||||
|
||||
let original: unsafe extern "C" fn(*mut c_void, *const core::ffi::c_char, c_int) =
|
||||
unsafe { core::mem::transmute(ORIGINAL_SET_TASK_COMM) };
|
||||
unsafe { original(task, buf, exec) };
|
||||
}
|
||||
|
||||
static HOLDING: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
const STOP: c_int = 19;
|
||||
@@ -586,9 +1000,7 @@ fn note_a_thread(task: usize, is_gone: bool) {
|
||||
};
|
||||
|
||||
Arena::at(placed.arena).note_a_thread(thread, is_gone);
|
||||
if !placed.hears.is_null() {
|
||||
native::leave_a_word(placed.hears);
|
||||
}
|
||||
wake_the_copy(placed);
|
||||
}
|
||||
|
||||
fn group_of(task: usize) -> Option<u32> {
|
||||
@@ -603,10 +1015,12 @@ pub fn id_of(task: usize) -> Option<u32> {
|
||||
Some(unsafe { ((task + at) as *const u32).read() })
|
||||
}
|
||||
|
||||
static COPY_TASK: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0);
|
||||
|
||||
static WATCHING: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
|
||||
|
||||
pub fn detach_from_process(id: u32) -> bool {
|
||||
let Some(placed) = (unsafe { placements() }.remove(&id)) else {
|
||||
let Some(placed) = unsafe { placements() }.remove(&id) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -640,9 +1054,7 @@ pub fn stop_copies() {
|
||||
fn ask_it_to_leave(placed: &Placement) {
|
||||
let arena = Arena::at(placed.arena);
|
||||
arena.tell_it_to_go();
|
||||
if !placed.hears.is_null() {
|
||||
native::leave_a_word(placed.hears);
|
||||
}
|
||||
wake_the_copy(placed);
|
||||
|
||||
let waited_on = &placed.arena as *const usize as *const u8;
|
||||
while !arena.has_gone() {
|
||||
@@ -676,20 +1088,18 @@ fn take_back_what_it_was_given(placed: &Placement, memory: *mut c_void) {
|
||||
const LEAVING_GRACE_US: u64 = 100_000;
|
||||
|
||||
pub fn tell_the_copy(id: u32) {
|
||||
let Some(placed) = (unsafe { placements() }).get_mut(&id) else {
|
||||
let Some(placed) = (unsafe { placements() }).get(&id) else {
|
||||
return;
|
||||
};
|
||||
|
||||
bump(placed.arena + WOKEN);
|
||||
if placed.hears.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
native::leave_a_word(placed.hears);
|
||||
wake_the_copy(placed);
|
||||
}
|
||||
|
||||
fn read_address(address: usize) -> u64 {
|
||||
unsafe { (address as *const u64).read_volatile() }
|
||||
fn wake_the_copy(placed: &Placement) {
|
||||
if !placed.hears.is_null() {
|
||||
native::leave_a_word(placed.hears);
|
||||
}
|
||||
}
|
||||
|
||||
fn read_word(address: usize) -> u32 {
|
||||
@@ -734,12 +1144,11 @@ const PROT_WRITE: usize = 2;
|
||||
const PROT_EXEC: usize = 4;
|
||||
const MAP_PRIVATE: usize = 2;
|
||||
const MAP_ANONYMOUS: usize = 0x20;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const MADV_NOHUGEPAGE: c_int = 15;
|
||||
const FIRST_ERROR_ADDRESS: usize = usize::MAX - 4095;
|
||||
const PAGE_SIZE: usize = 4096;
|
||||
const FOLL_WRITE: c_int = 1;
|
||||
const FUTEX_WAIT: c_int = 128;
|
||||
const FUTEX_WAKE: c_int = 129;
|
||||
const WAKE_EVERY_WAITER: u32 = i32::MAX as u32;
|
||||
|
||||
const CLONE_VM: usize = 0x100;
|
||||
const CLONE_FS: usize = 0x200;
|
||||
@@ -770,6 +1179,8 @@ unsafe extern "C" {
|
||||
static ___tracepoint_sched_process_exit: *mut c_void;
|
||||
static ___tracepoint_sched_process_exec: *mut c_void;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static ___set_task_comm: unsafe extern "C" fn(*mut c_void, *const core::ffi::c_char, c_int);
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _vunmap: unsafe extern "C" fn(*mut c_void);
|
||||
#[cfg(not(any(target_arch = "arm", target_arch = "x86", target_arch = "x86_64")))]
|
||||
static ___arch_copy_to_user: unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> usize;
|
||||
@@ -800,6 +1211,20 @@ unsafe extern "C" {
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _user_mode_thread:
|
||||
unsafe extern "C" fn(unsafe extern "C" fn(*mut c_void) -> c_int, *mut c_void, usize) -> c_int;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static _apply_to_existing_page_range: Option<
|
||||
unsafe extern "C" fn(
|
||||
*mut c_void,
|
||||
usize,
|
||||
usize,
|
||||
unsafe extern "C" fn(*mut c_void, usize, *mut c_void) -> c_int,
|
||||
*mut c_void,
|
||||
) -> c_int,
|
||||
>;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static _caches_clean_inval_pou: Option<unsafe extern "C" fn(usize, usize)>;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static _do_madvise: unsafe extern "C" fn(*mut c_void, usize, usize, c_int) -> c_int;
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
@@ -859,6 +1284,7 @@ const EXEC_PROBE: unsafe extern "C" fn(*mut c_void, *mut c_void, c_int, *mut c_v
|
||||
const EXEC_PROBE: unsafe extern "C" fn(*mut c_void, *mut c_void, c_int, *mut c_void) =
|
||||
frida_cb_exec;
|
||||
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
const THREAD_APPEARED_PROBE: unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void) =
|
||||
frida_kcb_thread_appeared;
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use core::ffi::{c_int, c_long, c_void};
|
||||
use core::ffi::{c_int, c_void};
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
use core::ffi::c_long;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
use core::ptr;
|
||||
|
||||
use crate::bindings::{
|
||||
@@ -90,7 +93,7 @@ unsafe fn build_resume_stub() {
|
||||
stub.write(0xbf);
|
||||
(stub.add(1) as *mut u32).write((&raw mut RESUME_CONTEXT) as u32);
|
||||
stub.add(5).write(0xe9);
|
||||
let rel = (frida_i386_resume as usize).wrapping_sub(stub as usize + 10) as u32;
|
||||
let rel = (frida_i386_resume as *const () as usize).wrapping_sub(stub as usize + 10) as u32;
|
||||
(stub.add(6) as *mut u32).write(rel);
|
||||
RESUME_STUB = stub as usize;
|
||||
}
|
||||
@@ -316,7 +319,7 @@ pub fn install() {
|
||||
|
||||
unsafe {
|
||||
let addr = target as *mut u32;
|
||||
_patch_text(addr.add(1) as *mut c_void, frida_arm_fixup as usize as u32);
|
||||
_patch_text(addr.add(1) as *mut c_void, frida_arm_fixup as *const () as usize as u32);
|
||||
_patch_text(addr as *mut c_void, 0xe51ff004);
|
||||
}
|
||||
}
|
||||
@@ -449,7 +452,7 @@ pub fn install() {
|
||||
|
||||
unsafe { FRIDA_ARM64_ORIGINAL_CONT = target.add(4) as u64 };
|
||||
|
||||
let jump = detour_to(frida_arm64_fixup as usize);
|
||||
let jump = detour_to(frida_arm64_fixup as *const () as usize);
|
||||
unsafe {
|
||||
let addr = target as *mut u32;
|
||||
_aarch64_insn_patch_text_nosync(addr.add(2) as *mut c_void, jump[2]);
|
||||
@@ -523,13 +526,26 @@ unsafe fn recovered(regs: *mut c_void) -> bool {
|
||||
)
|
||||
};
|
||||
if handled == 0 {
|
||||
return false;
|
||||
let esr: u64;
|
||||
unsafe { core::arch::asm!("mrs {}, esr_el1", out(reg) esr, options(nomem, nostack)) };
|
||||
crate::note_unhandled_fault(esr, faulted_at as u64, accessed as u64);
|
||||
context.pc = frida_arm64_park as *const () as usize as u64;
|
||||
unsafe { restore_context(regs, &context) };
|
||||
return true;
|
||||
}
|
||||
|
||||
unsafe { restore_context(regs, &context) };
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
extern "C" fn frida_arm64_park() -> ! {
|
||||
static mut PARK_TOKEN: u8 = 0;
|
||||
loop {
|
||||
crate::kernel::wait(core::ptr::addr_of!(PARK_TOKEN), Some(1_000_000), &mut || false);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
unsafe fn context_of(regs: *mut c_void) -> GumCpuContext {
|
||||
let r = regs as *const u64;
|
||||
|
||||
@@ -82,6 +82,8 @@ pub fn wait(_token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut()
|
||||
unsafe { frida_kmod_wait_commit(seq, timeout_us.map_or(-1, |us| us as i64)) };
|
||||
}
|
||||
|
||||
pub fn poke_loop_wakeup() {}
|
||||
|
||||
pub fn wake(_token: *const u8) {
|
||||
unsafe { frida_kmod_wake() };
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use core::ffi::c_void;
|
||||
use core::ffi::{CStr, c_void};
|
||||
use core::ptr;
|
||||
|
||||
use crate::bindings::{
|
||||
@@ -14,6 +14,7 @@ use crate::bindings::{
|
||||
};
|
||||
use crate::bindings::{GumElfSymbolBind_GUM_ELF_BIND_LOCAL, GumElfSymbolDetails};
|
||||
use crate::gum::{self, FoundExportCallback, FoundSymbolCallback};
|
||||
use crate::kernel::MemoryRegion;
|
||||
use alloc::ffi::CString;
|
||||
|
||||
use super::user::{EXECUTABLE, READABLE, WRITABLE, contents_of};
|
||||
@@ -82,7 +83,7 @@ fn announce(image: &Image) {
|
||||
|
||||
let module = gum::gum_native_module_new(&image.path, "", &range);
|
||||
unsafe {
|
||||
gum_barebone_register_module(unsafe { REGISTRY }, module);
|
||||
gum_barebone_register_module(REGISTRY, module);
|
||||
g_object_unref(module as gpointer);
|
||||
}
|
||||
}
|
||||
@@ -111,6 +112,22 @@ pub fn enumerate_exports_in_range(from: u64, to: u64, found: &mut FoundExportCal
|
||||
}
|
||||
}
|
||||
|
||||
pub fn export_named(wanted: &str) -> u64 {
|
||||
let mut found: u64 = 0;
|
||||
for image in mapped_images() {
|
||||
enumerate_exports_in_range(image.base, image.base + image.size, &mut |name, address| {
|
||||
if !name.is_null() && unsafe { CStr::from_ptr(name as *const _) }.to_bytes() == wanted.as_bytes() {
|
||||
found = address;
|
||||
}
|
||||
found == 0
|
||||
});
|
||||
if found != 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
found
|
||||
}
|
||||
|
||||
pub fn enumerate_symbols_in_range(from: u64, to: u64, found: &mut FoundSymbolCallback<'_>) {
|
||||
let Some(image) = mapped_images().into_iter().find(|image| image.base == from) else {
|
||||
return;
|
||||
@@ -257,9 +274,9 @@ pub fn enumerate_ranges(found: &mut dyn FnMut(u64, u64, u32)) {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn protection_at(address: u64) -> u32 {
|
||||
pub fn region_at(address: u64) -> Option<MemoryRegion> {
|
||||
if super::user::range_is_ours(address) {
|
||||
return 0;
|
||||
return None;
|
||||
}
|
||||
|
||||
let listed = contents_of(c"/proc/self/maps");
|
||||
@@ -270,11 +287,15 @@ pub fn protection_at(address: u64) -> u32 {
|
||||
};
|
||||
|
||||
if address >= mapping.start && address < mapping.end {
|
||||
return mapping.protection;
|
||||
return Some(MemoryRegion {
|
||||
base: mapping.start,
|
||||
size: mapping.end - mapping.start,
|
||||
protection: mapping.protection,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
0
|
||||
None
|
||||
}
|
||||
|
||||
fn mapped_images() -> Vec<Image> {
|
||||
|
||||
@@ -27,6 +27,9 @@ mod arena;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
mod facade;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub mod hostlink_pipe_goldfish;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub mod hostlink_pipe_vsock;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
mod injection;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
|
||||
@@ -651,10 +651,6 @@ impl Regions {
|
||||
}
|
||||
}
|
||||
|
||||
fn reads_as_pointer(address: usize) -> bool {
|
||||
address >= LOWEST_MAPPING && address & (OFFSET_SIZE - 1) == 0
|
||||
}
|
||||
|
||||
fn try_read_word(address: usize) -> Option<usize> {
|
||||
Some(usize::from_ne_bytes(try_read(address)?))
|
||||
}
|
||||
@@ -767,7 +763,6 @@ const REGION_WINDOW: usize = 512;
|
||||
const MAX_REGIONS: usize = 16;
|
||||
const OFFSET_SIZE: usize = 4;
|
||||
const SYMBOL_SIZE: usize = if WORD == 8 { 24 } else { 16 };
|
||||
const LOWEST_MAPPING: usize = 1 << 20;
|
||||
const MAX_SYMBOLS: usize = 1 << 20;
|
||||
const MAX_EXPORTS: usize = 1 << 16;
|
||||
const EXPORTS_SAMPLED: usize = 64;
|
||||
|
||||
@@ -8,6 +8,8 @@ use core::ffi::{c_char, c_int, c_uint, c_void};
|
||||
use core::ptr;
|
||||
use core::ptr::read_volatile;
|
||||
use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
use core::sync::atomic::AtomicUsize;
|
||||
|
||||
use crate::kernel::ThreadEntry;
|
||||
|
||||
@@ -57,8 +59,12 @@ pub fn release_interrupt() {
|
||||
unsafe { _free_irq(interrupt.irq, interrupt.target) };
|
||||
}
|
||||
|
||||
pub fn take_the_file(descriptor: u32) -> *mut c_void {
|
||||
unsafe { _fget(descriptor) }
|
||||
pub fn take_the_file_of(task: *mut c_void, descriptor: u32) -> *mut c_void {
|
||||
unsafe { _fget_task(task, descriptor) }
|
||||
}
|
||||
|
||||
pub fn task_for_thread(id: u32) -> *mut c_void {
|
||||
unsafe { _find_task_by_vpid(id) }
|
||||
}
|
||||
|
||||
pub fn let_the_file_go(file: *mut c_void) {
|
||||
@@ -67,16 +73,122 @@ pub fn let_the_file_go(file: *mut c_void) {
|
||||
|
||||
pub fn wait_for_a_word(file: *mut c_void) -> bool {
|
||||
let mut byte = 0u8;
|
||||
let mut at = 0i64;
|
||||
let mut position: i64 = 0;
|
||||
|
||||
unsafe { _kernel_read(file, &mut byte, 1, &mut at) == 1 }
|
||||
unsafe { _kernel_read(file, &mut byte, 1, &mut position) == 1 }
|
||||
}
|
||||
|
||||
pub fn leave_a_word(file: *mut c_void) {
|
||||
let byte = 0u8;
|
||||
let mut at = 0i64;
|
||||
// kernel_read and kernel_write take the position by reference; the pair above
|
||||
// hands them a literal one because the file it reads is a counter.
|
||||
pub unsafe fn pipe_read(file: *mut c_void, into: *mut u8, count: usize, position: *mut i64) -> isize {
|
||||
unsafe { _kernel_read(file, into, count, position) }
|
||||
}
|
||||
|
||||
unsafe { _kernel_write(file, &byte, 1, &mut at) };
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
pub unsafe fn pipe_write(file: *mut c_void, from: *const u8, count: usize, position: *mut i64) -> isize {
|
||||
let write: unsafe extern "C" fn(*mut c_void, *const u8, usize, *mut i64) -> isize =
|
||||
unsafe { core::mem::transmute(_kernel_write) };
|
||||
unsafe { write(file, from, count, position) }
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub unsafe fn pipe_write(file: *mut c_void, from: *const u8, count: usize, position: *mut i64) -> isize {
|
||||
unsafe { _kernel_write(file, from, count, position) }
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
pub fn leave_a_word(file: *mut c_void) {
|
||||
let one: u64 = 1;
|
||||
unsafe { _kernel_write(file, &one as *const u64 as *const u8, 8, 0) };
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub fn leave_a_word(file: *mut c_void) {
|
||||
let one: u64 = 1;
|
||||
unsafe { _kernel_write(file, &one as *const u64 as *const u8, 8, core::ptr::null_mut()) };
|
||||
}
|
||||
|
||||
// A CONFIG_CFI_CLANG kernel checks the 4-byte type id in front of any function it
|
||||
// calls indirectly. Our callbacks carry no such id, so hand the kernel a small
|
||||
// executable thunk instead: the matching id (copied from a kernel function of the
|
||||
// same signature), a BTI landing pad, then a direct branch to the real callback.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static THREAD_THUNK: AtomicUsize = AtomicUsize::new(0);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static INTERRUPT_THUNK: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub(crate) fn kcfi_thunk(callback: usize, type_ref: Option<usize>) -> usize {
|
||||
let Some(ref_fn) = type_ref else { return callback; };
|
||||
let type_id = unsafe { ((ref_fn - 4) as *const u32).read() };
|
||||
let thunk = alloc_code(32) as usize;
|
||||
if thunk == 0 {
|
||||
return callback;
|
||||
}
|
||||
// The callback lives in the agent, which may be far beyond a direct branch's +-128MB, so
|
||||
// load its full address and branch indirectly. The agent's execmem pages are not BTI-guarded,
|
||||
// so br needs no landing pad at the callback. Layout, with the type id in the word before the
|
||||
// entry so the caller's CONFIG_CFI_CLANG check passes:
|
||||
// +0 type id
|
||||
// +4 bti c <- entry (thunk + 4)
|
||||
// +8 ldr x16, #8 (loads the address at +16)
|
||||
// +12 br x16
|
||||
// +16 callback address (u64)
|
||||
let insns: [u32; 6] = [
|
||||
type_id,
|
||||
0xd503245f, // bti c
|
||||
0x58000050, // ldr x16, #8
|
||||
0xd61f0200, // br x16
|
||||
(callback & 0xffff_ffff) as u32,
|
||||
(callback >> 32) as u32,
|
||||
];
|
||||
set_protection(thunk as u64, 32, GUM_PAGE_EXECUTE);
|
||||
patch_text(thunk as u64, insns.as_ptr() as *const u8, 24);
|
||||
thunk + 4
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
pub(crate) fn kcfi_thunk(callback: usize, _type_ref: Option<usize>) -> usize {
|
||||
callback
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn kcfi_thunk_cached(cache: &AtomicUsize, callback: usize, type_ref: Option<usize>) -> usize {
|
||||
let existing = cache.load(Ordering::Acquire);
|
||||
if existing != 0 {
|
||||
return existing;
|
||||
}
|
||||
let thunk = kcfi_thunk(callback, type_ref);
|
||||
cache.store(thunk, Ordering::Release);
|
||||
thunk
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn thread_entry() -> unsafe extern "C" fn(*mut c_void) -> c_int {
|
||||
let ref_fn = unsafe { _kthread_worker_fn.map(|f| f as usize) };
|
||||
unsafe {
|
||||
core::mem::transmute(kcfi_thunk_cached(&THREAD_THUNK, frida_cb_thread as *const () as usize,
|
||||
ref_fn))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
fn thread_entry() -> unsafe extern "C" fn(*mut c_void) -> c_int {
|
||||
THREAD_ENTRY
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn interrupt_entry() -> unsafe extern "C" fn(c_int, *mut c_void) -> c_int {
|
||||
let ref_fn = unsafe { _vring_interrupt.map(|f| f as usize) };
|
||||
unsafe {
|
||||
core::mem::transmute(kcfi_thunk_cached(&INTERRUPT_THUNK,
|
||||
frida_cb_interrupt as *const () as usize, ref_fn))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
fn interrupt_entry() -> unsafe extern "C" fn(c_int, *mut c_void) -> c_int {
|
||||
INTERRUPT_ENTRY
|
||||
}
|
||||
|
||||
pub fn spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> isize {
|
||||
@@ -84,8 +196,9 @@ pub fn spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> isize {
|
||||
unsafe {
|
||||
trampoline.write(Trampoline { entry, parameter });
|
||||
|
||||
let te = thread_entry();
|
||||
let task = _kthread_create_on_node(
|
||||
THREAD_ENTRY,
|
||||
te,
|
||||
trampoline as *mut c_void,
|
||||
NUMA_NO_NODE,
|
||||
c"frida".as_ptr(),
|
||||
@@ -107,8 +220,10 @@ pub fn alloc(size: usize) -> *mut u8 {
|
||||
unsafe {
|
||||
if let Some(f) = ___kmalloc_noprof {
|
||||
f(size, GFP_KERNEL)
|
||||
} else if let Some(f) = _kmalloc {
|
||||
f(size, GFP_KERNEL)
|
||||
} else {
|
||||
_kmalloc.unwrap()(size, GFP_KERNEL)
|
||||
___kmalloc.unwrap()(size, GFP_KERNEL)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,6 +232,17 @@ pub fn free(ptr: *mut u8, _size: usize) {
|
||||
unsafe { _kfree(ptr) };
|
||||
}
|
||||
|
||||
pub fn alloc_heap(size: usize) -> *mut u8 {
|
||||
unsafe {
|
||||
if let Some(f) = _vmalloc {
|
||||
return f(size);
|
||||
}
|
||||
}
|
||||
|
||||
alloc(size)
|
||||
}
|
||||
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub fn alloc_code(size: usize) -> *mut u8 {
|
||||
unsafe {
|
||||
@@ -183,14 +309,15 @@ pub fn patch_text(address: u64, data: *const u8, len: usize) -> bool {
|
||||
|
||||
let count = len / 4;
|
||||
|
||||
let mut addrs = alloc::vec::Vec::with_capacity(count);
|
||||
let mut insns = alloc::vec::Vec::with_capacity(count);
|
||||
for i in 0..count {
|
||||
addrs.push((address as usize + (i * 4)) as *mut c_void);
|
||||
insns.push(unsafe { (data as *const u32).add(i).read_unaligned() });
|
||||
let insn = unsafe { (data as *const u32).add(i).read_unaligned() };
|
||||
let at = (address as usize + (i * 4)) as *mut c_void;
|
||||
if unsafe { _aarch64_insn_patch_text_nosync(at, insn) } != 0 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe { _aarch64_insn_patch_text(addrs.as_mut_ptr(), insns.as_ptr(), count as c_int) == 0 }
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
@@ -248,7 +375,9 @@ pub fn set_protection(address: u64, size: usize, protection: u32) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
const GUM_PAGE_WRITE: u32 = 2;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
const GUM_PAGE_EXECUTE: u32 = 4;
|
||||
|
||||
pub fn current_process_id() -> u32 {
|
||||
@@ -288,6 +417,8 @@ pub fn wake(_token: *const u8) {
|
||||
unsafe { ___wake_up(waiters(), TASK_NORMAL, WAKE_EVERY_WAITER, ptr::null_mut()) };
|
||||
}
|
||||
|
||||
pub fn poke_loop_wakeup() {}
|
||||
|
||||
fn queue_this_thread(entry: &mut [usize; WAIT_ENTRY_WORDS]) -> *mut c_void {
|
||||
let entry = entry.as_mut_ptr();
|
||||
|
||||
@@ -306,7 +437,6 @@ fn queue_this_thread(entry: &mut [usize; WAIT_ENTRY_WORDS]) -> *mut c_void {
|
||||
fn waiters() -> *mut c_void {
|
||||
let queue = (&raw mut WAITERS) as *mut c_void;
|
||||
|
||||
|
||||
if !WAITERS_CLAIMED.swap(true, Ordering::AcqRel) {
|
||||
unsafe {
|
||||
___init_waitqueue_head(queue, c"frida".as_ptr(), (&raw mut WAITERS_KEY) as *mut c_void)
|
||||
@@ -331,16 +461,34 @@ pub fn pci_interrupt(bus: u8, devfn: u8) -> Option<u32> {
|
||||
return None;
|
||||
}
|
||||
|
||||
let line = unsafe { _pci_irq_vector(device, 0) };
|
||||
// pci_irq_vector arrived in 4.10; older kernels resolve a legacy INTx line through the
|
||||
// device-tree PCI mapping instead.
|
||||
let line = if let Some(f) = pci_irq_vector() {
|
||||
unsafe { f(device, 0) }
|
||||
} else if let Some(f) = unsafe { _of_irq_parse_and_map_pci } {
|
||||
unsafe { f(device, 0, 0) }
|
||||
} else {
|
||||
-1
|
||||
};
|
||||
unsafe { _pci_dev_put(device) };
|
||||
|
||||
if line < 0 {
|
||||
if line <= 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(line as u32)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
fn pci_irq_vector() -> Option<unsafe extern "C" fn(*mut c_void, c_uint) -> c_int> {
|
||||
Some(_pci_irq_vector)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
fn pci_irq_vector() -> Option<unsafe extern "C" fn(*mut c_void, c_uint) -> c_int> {
|
||||
unsafe { _pci_irq_vector }
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub fn mmio_interrupt(mmio_base: u64) -> Option<u32> {
|
||||
let path = alloc::format!("/virtio_mmio@{:x}\0", mmio_base);
|
||||
@@ -374,10 +522,26 @@ pub fn monotonic_micros() -> i64 {
|
||||
|
||||
pub fn wall_clock_micros() -> (u32, u32) {
|
||||
let mut now = Timespec64 { tv_sec: 0, tv_nsec: 0 };
|
||||
unsafe { _ktime_get_real_ts64(&mut now) };
|
||||
unsafe {
|
||||
if let Some(f) = ktime_get_real_ts64() {
|
||||
f(&mut now);
|
||||
} else if let Some(f) = _getnstimeofday64 {
|
||||
f(&mut now);
|
||||
}
|
||||
}
|
||||
(now.tv_sec as u32, (now.tv_nsec / 1000) as u32)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
fn ktime_get_real_ts64() -> Option<unsafe extern "C" fn(*mut Timespec64)> {
|
||||
Some(_ktime_get_real_ts64)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
fn ktime_get_real_ts64() -> Option<unsafe extern "C" fn(*mut Timespec64)> {
|
||||
unsafe { _ktime_get_real_ts64 }
|
||||
}
|
||||
|
||||
// Linux keeps the running task where the architecture puts it: in a register
|
||||
// of its own on arm64, and behind the per-CPU segment elsewhere, at an offset
|
||||
// the host resolves along with everything else.
|
||||
@@ -461,7 +625,7 @@ pub fn install_interrupt_handler(
|
||||
|
||||
_request_threaded_irq(
|
||||
irq,
|
||||
INTERRUPT_ENTRY,
|
||||
interrupt_entry(),
|
||||
None,
|
||||
IRQF_SHARED,
|
||||
c"frida".as_ptr(),
|
||||
@@ -475,14 +639,40 @@ pub fn map_io(phys_addr: u64, size: u64) -> *mut c_void {
|
||||
unsafe { _ioremap(phys_addr as u32, size as usize) }
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn map_io(phys_addr: u64, size: u64) -> *mut c_void {
|
||||
unsafe {
|
||||
if let Some(f) = _ioremap {
|
||||
return f(phys_addr as usize, size as usize);
|
||||
}
|
||||
|
||||
if let Some(f) = _ioremap_nocache {
|
||||
return f(phys_addr as usize, size as usize);
|
||||
}
|
||||
}
|
||||
|
||||
core::ptr::null_mut()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub fn map_io(phys_addr: u64, size: u64) -> *mut c_void {
|
||||
unsafe { _ioremap(phys_addr as usize, size as usize) }
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_arch = "arm", target_arch = "x86", target_arch = "x86_64")))]
|
||||
|
||||
pub fn map_io(phys_addr: u64, size: u64) -> *mut c_void {
|
||||
unsafe { _generic_ioremap_prot(phys_addr, size as usize, page_of(DEVICE_MEMORY) as usize) }
|
||||
let prot = page_of(DEVICE_MEMORY);
|
||||
if let Some(f) = unsafe { _generic_ioremap_prot } {
|
||||
return unsafe { f(phys_addr, size as usize, prot as usize) };
|
||||
}
|
||||
// Kernels predating generic_ioremap_prot (added in 5.x) map through __ioremap, which takes the
|
||||
// same phys/size and a pgprot carrying the device-memory attributes.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
if let Some(f) = unsafe { ___ioremap } {
|
||||
return unsafe { f(phys_addr, size as usize, prot) };
|
||||
}
|
||||
core::ptr::null_mut()
|
||||
}
|
||||
|
||||
pub fn map_pages(pages: *mut c_void, count: usize) -> *mut c_void {
|
||||
@@ -508,7 +698,7 @@ const PAGE_KERNEL: u64 = 0x163;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn page_of(memory: u64) -> u64 {
|
||||
PTE_TYPE_PAGE | PTE_AF | PTE_SHARED | PTE_WRITE | PTE_PXN | PTE_UXN | (attribute_of(memory) << 2)
|
||||
PTE_TYPE_PAGE | PTE_AF | PTE_SHARED | PTE_WRITE | PTE_DIRTY | PTE_PXN | PTE_UXN | (attribute_of(memory) << 2)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
@@ -532,17 +722,26 @@ fn attribute_of(memory: u64) -> u64 {
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub fn virt_to_phys(vaddr: u64) -> u64 {
|
||||
let memstart = unsafe { read_volatile(_memstart_addr) };
|
||||
vaddr - linear_map_base() + memstart
|
||||
vaddr - linear_map_base(vaddr) + memstart
|
||||
}
|
||||
|
||||
// The linear map sat at the top of the kernel half (PAGE_OFFSET = -1 << (VA_BITS - 1)) until the
|
||||
// 5.4 VA-space flip moved it to the bottom (-1 << VA_BITS). Both candidates are 256 GB apart, so a
|
||||
// linear-map address selects its layout by being at or above the higher (older) base.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn linear_map_base() -> u64 {
|
||||
fn linear_map_base(vaddr: u64) -> u64 {
|
||||
let tcr_el1: u64;
|
||||
unsafe {
|
||||
core::arch::asm!("mrs {}, tcr_el1", out(reg) tcr_el1, options(nomem, nostack));
|
||||
}
|
||||
let kernel_address_bits = 64 - ((tcr_el1 >> TCR_T1SZ_SHIFT) & TCR_T1SZ_MASK);
|
||||
u64::MAX << kernel_address_bits
|
||||
let flipped = u64::MAX << kernel_address_bits;
|
||||
let classic = u64::MAX << (kernel_address_bits - 1);
|
||||
if vaddr >= classic {
|
||||
classic
|
||||
} else {
|
||||
flipped
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
@@ -633,15 +832,25 @@ const WAIT_ENTRY_WAKE: usize = 2;
|
||||
const WAIT_ENTRY_QUEUED: usize = 3;
|
||||
const WAIT_QUEUE_WORDS: usize = 16;
|
||||
const LOCK_KEY_WORDS: usize = 8;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const DEVICE_MEMORY: u64 = 0x04;
|
||||
const ORDINARY_MEMORY: u64 = 0xff;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const ATTRIBUTES_KEPT: u64 = 8;
|
||||
const VM_MAP: u64 = 0x4;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PTE_TYPE_PAGE: u64 = 0x3;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PTE_SHARED: u64 = 3 << 8;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PTE_AF: u64 = 1 << 10;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PTE_WRITE: u64 = 1 << 51;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PTE_DIRTY: u64 = 1 << 55;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PTE_PXN: u64 = 1 << 53;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PTE_UXN: u64 = 1 << 54;
|
||||
const IRQF_SHARED: usize = 0x80;
|
||||
const IRQ_NONE: c_int = 0;
|
||||
@@ -672,15 +881,23 @@ unsafe extern "C" {
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _fget: unsafe extern "C" fn(c_uint) -> *mut c_void;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _fget_task: unsafe extern "C" fn(*mut c_void, c_uint) -> *mut c_void;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _find_task_by_vpid: unsafe extern "C" fn(c_uint) -> *mut c_void;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _kernel_read: unsafe extern "C" fn(*mut c_void, *mut u8, usize, *mut i64) -> isize;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _kernel_write: unsafe extern "C" fn(*mut c_void, *const u8, usize, *mut i64) -> isize;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _kernel_write: unsafe extern "C" fn(*mut c_void, *const u8, usize, i64) -> isize;
|
||||
// Allocation profiling renamed the entry points in 6.10; before that the
|
||||
// size-plus-flags pair went to __kmalloc.
|
||||
static ___kmalloc_noprof: Option<unsafe extern "C" fn(usize, u32) -> *mut u8>;
|
||||
static _kmalloc: Option<unsafe extern "C" fn(usize, u32) -> *mut u8>;
|
||||
// Kernels before ~5.x inline kmalloc, so kallsyms names only __kmalloc.
|
||||
static ___kmalloc: Option<unsafe extern "C" fn(usize, u32) -> *mut u8>;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _kfree: unsafe extern "C" fn(*mut u8);
|
||||
static _vmalloc: Option<unsafe extern "C" fn(usize) -> *mut u8>;
|
||||
// Executable memory moved out of the module loader in 6.12.
|
||||
static _execmem_alloc: Option<unsafe extern "C" fn(u32, usize) -> *mut u8>;
|
||||
static _set_memory_rw: Option<unsafe extern "C" fn(usize, c_int) -> c_int>;
|
||||
@@ -688,8 +905,13 @@ unsafe extern "C" {
|
||||
static _set_memory_x: Option<unsafe extern "C" fn(usize, c_int) -> c_int>;
|
||||
static _set_memory_nx: Option<unsafe extern "C" fn(usize, c_int) -> c_int>;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static _aarch64_insn_patch_text:
|
||||
unsafe extern "C" fn(*mut *mut c_void, *const u32, c_int) -> c_int;
|
||||
static _aarch64_insn_patch_text_nosync: unsafe extern "C" fn(*mut c_void, u32) -> c_int;
|
||||
// Addresses of kernel functions of the same C signature as our kthread and IRQ
|
||||
// callbacks; the kCFI type-id the kernel checks sits in the word before each.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static _kthread_worker_fn: Option<unsafe extern "C" fn()>;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static _vring_interrupt: Option<unsafe extern "C" fn()>;
|
||||
#[cfg(target_arch = "arm")]
|
||||
static _patch_text: unsafe extern "C" fn(*mut c_void, u32);
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
@@ -712,7 +934,8 @@ unsafe extern "C" {
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _pci_get_domain_bus_and_slot: unsafe extern "C" fn(c_int, c_uint, c_uint) -> *mut c_void;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _pci_irq_vector: unsafe extern "C" fn(*mut c_void, c_uint) -> c_int;
|
||||
static _pci_irq_vector: Option<unsafe extern "C" fn(*mut c_void, c_uint) -> c_int>;
|
||||
static _of_irq_parse_and_map_pci: Option<unsafe extern "C" fn(*mut c_void, u8, u8) -> c_int>;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _pci_dev_put: unsafe extern "C" fn(*mut c_void);
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
@@ -720,7 +943,8 @@ unsafe extern "C" {
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _ktime_get_mono_fast_ns: unsafe extern "C" fn() -> u64;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _ktime_get_real_ts64: unsafe extern "C" fn(*mut Timespec64);
|
||||
static _ktime_get_real_ts64: Option<unsafe extern "C" fn(*mut Timespec64)>;
|
||||
static _getnstimeofday64: Option<unsafe extern "C" fn(*mut Timespec64)>;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _send_sig: unsafe extern "C" fn(c_int, *mut c_void, c_int) -> c_int;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
@@ -733,7 +957,10 @@ unsafe extern "C" {
|
||||
usize,
|
||||
*const c_char,
|
||||
*mut c_void,
|
||||
) -> c_int; static _generic_ioremap_prot: unsafe extern "C" fn(u64, usize, usize) -> *mut c_void;
|
||||
) -> c_int;
|
||||
static _generic_ioremap_prot: Option<unsafe extern "C" fn(u64, usize, usize) -> *mut c_void>;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static ___ioremap: Option<unsafe extern "C" fn(u64, usize, u64) -> *mut c_void>;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _vmap: unsafe extern "C" fn(*mut c_void, u32, usize, usize) -> *mut c_void;
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
@@ -757,14 +984,14 @@ unsafe extern "C" {
|
||||
static _of_irq_get: unsafe extern "C" fn(*mut c_void, c_int) -> c_int;
|
||||
#[cfg(target_arch = "arm")]
|
||||
static _of_node_put: unsafe extern "C" fn(*mut c_void);
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _ioremap: unsafe extern "C" fn(usize, usize) -> *mut c_void;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
static _ioremap: Option<unsafe extern "C" fn(usize, usize) -> *mut c_void>;
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
static _ioremap_nocache: Option<unsafe extern "C" fn(usize, usize) -> *mut c_void>;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
static ___default_kernel_pte_mask: *const usize;
|
||||
}
|
||||
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
unsafe extern "C" {
|
||||
fn frida_k_log(message: *const c_char);
|
||||
@@ -782,6 +1009,10 @@ unsafe extern "C" {
|
||||
fn _wake_up_process(a0: *mut c_void) -> c_int;
|
||||
#[link_name = "frida_k_fget"]
|
||||
fn _fget(a0: c_uint) -> *mut c_void;
|
||||
#[link_name = "frida_k_fget_task"]
|
||||
fn _fget_task(a0: *mut c_void, a1: c_uint) -> *mut c_void;
|
||||
#[link_name = "frida_k_find_task_by_vpid"]
|
||||
fn _find_task_by_vpid(a0: c_uint) -> *mut c_void;
|
||||
#[link_name = "frida_k_kernel_read"]
|
||||
fn _kernel_read(a0: *mut c_void, a1: *mut u8, a2: usize, a3: *mut i64) -> isize;
|
||||
#[link_name = "frida_k_kernel_write"]
|
||||
@@ -837,10 +1068,10 @@ unsafe extern "C" {
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
const THREAD_ENTRY: unsafe extern "C" fn(*mut c_void) -> c_int = frida_kcb_thread;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "aarch64")))]
|
||||
const THREAD_ENTRY: unsafe extern "C" fn(*mut c_void) -> c_int = frida_cb_thread;
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
const INTERRUPT_ENTRY: unsafe extern "C" fn(c_int, *mut c_void) -> c_int = frida_kcb_interrupt;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "aarch64")))]
|
||||
const INTERRUPT_ENTRY: unsafe extern "C" fn(c_int, *mut c_void) -> c_int = frida_cb_interrupt;
|
||||
@@ -1,3 +1,5 @@
|
||||
use alloc::format;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use core::ffi::{c_char, c_int, c_long, c_void};
|
||||
use core::ptr;
|
||||
@@ -7,6 +9,10 @@ pub struct ProcessInfo {
|
||||
pub name: *const u8,
|
||||
pub path: *const u8,
|
||||
pub command_line: *const u8,
|
||||
pub uid: u32,
|
||||
pub user: *const u8,
|
||||
pub ppid: u32,
|
||||
pub started: i64,
|
||||
}
|
||||
|
||||
pub fn enumerate_processes(found: &mut dyn FnMut(ProcessInfo)) {
|
||||
@@ -16,14 +22,31 @@ pub fn enumerate_processes(found: &mut dyn FnMut(ProcessInfo)) {
|
||||
|
||||
let mut path = Vec::new();
|
||||
path.resize(PATH_MAX, 0u8);
|
||||
let mut cmdline = Vec::new();
|
||||
cmdline.resize(CMDLINE_MAX, 0u8);
|
||||
let mut label = Vec::new();
|
||||
label.resize(NAME_MAX, 0u8);
|
||||
let mut owner = Vec::new();
|
||||
owner.resize(NAME_MAX, 0u8);
|
||||
|
||||
let (now_boottime, now_realtime) = current_time();
|
||||
|
||||
for task in take_task_snapshot(layout) {
|
||||
found(ProcessInfo {
|
||||
id: task.id,
|
||||
name: task.name.as_ptr(),
|
||||
path: path_of(task.executable, &mut path),
|
||||
command_line: ptr::null(),
|
||||
});
|
||||
if !task.memory.is_null() {
|
||||
let (name, command_line) = describe(task.memory, &mut cmdline, &mut label)
|
||||
.unwrap_or((task.name.as_ptr(), ptr::null()));
|
||||
found(ProcessInfo {
|
||||
id: task.id,
|
||||
name,
|
||||
path: path_of(task.executable, &mut path),
|
||||
command_line,
|
||||
uid: task.uid,
|
||||
user: format_user(task.uid, &mut owner),
|
||||
ppid: task.ppid,
|
||||
started: wall_time(task.started, now_boottime, now_realtime),
|
||||
});
|
||||
unsafe { _mmput(task.memory) };
|
||||
}
|
||||
|
||||
if !task.executable.is_null() {
|
||||
unsafe { _fput(task.executable) };
|
||||
@@ -69,6 +92,10 @@ struct Task {
|
||||
id: u32,
|
||||
name: [u8; NAME_SIZE + 1],
|
||||
executable: *mut c_void,
|
||||
memory: *mut c_void,
|
||||
uid: u32,
|
||||
ppid: u32,
|
||||
started: u64,
|
||||
}
|
||||
|
||||
fn take_task_snapshot(layout: &Layout) -> Vec<Task> {
|
||||
@@ -94,14 +121,162 @@ fn read_task(task: usize, layout: &Layout) -> Task {
|
||||
id: read_id(task, layout),
|
||||
name,
|
||||
executable: unsafe { _get_task_exe_file(task as *mut c_void) },
|
||||
memory: unsafe { _get_task_mm(task as *mut c_void) },
|
||||
uid: read_uid(task, layout),
|
||||
ppid: read_ppid(task, layout),
|
||||
started: layout.started.and_then(|at| read_kernel_word(task + at)).unwrap_or(0) as u64,
|
||||
}
|
||||
}
|
||||
|
||||
fn read_id(task: usize, layout: &Layout) -> u32 {
|
||||
let mut id = [0u8; 4];
|
||||
read_kernel(task + layout.id, &mut id);
|
||||
fn read_uid(task: usize, layout: &Layout) -> u32 {
|
||||
let (Some(credentials), Some(uid)) = (layout.credentials, layout.uid) else {
|
||||
return 0;
|
||||
};
|
||||
let Some(cred) = read_kernel_word(task + credentials) else {
|
||||
return 0;
|
||||
};
|
||||
read_kernel_u32(cred + uid)
|
||||
}
|
||||
|
||||
u32::from_ne_bytes(id)
|
||||
fn read_ppid(task: usize, layout: &Layout) -> u32 {
|
||||
let (Some(parent), Some(group)) = (layout.parent, layout.group) else {
|
||||
return 0;
|
||||
};
|
||||
let Some(task) = read_kernel_word(task + parent) else {
|
||||
return 0;
|
||||
};
|
||||
read_kernel_u32(task + group)
|
||||
}
|
||||
|
||||
fn read_id(task: usize, layout: &Layout) -> u32 {
|
||||
read_kernel_u32(task + layout.id)
|
||||
}
|
||||
|
||||
fn read_kernel_u32(address: usize) -> u32 {
|
||||
let mut bytes = [0u8; 4];
|
||||
if !read_kernel(address, &mut bytes) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
u32::from_ne_bytes(bytes)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn describe(memory: *mut c_void, cmdline: &mut [u8], label: &mut [u8]) -> Option<(*const u8, *const u8)> {
|
||||
let read = read_cmdline(memory, cmdline)?;
|
||||
let name = base_name(&cmdline[..read], label);
|
||||
let command_line = join_arguments(cmdline, read);
|
||||
Some((name, command_line))
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
fn describe(_memory: *mut c_void, _cmdline: &mut [u8], _label: &mut [u8]) -> Option<(*const u8, *const u8)> {
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn read_cmdline(memory: *mut c_void, buffer: &mut [u8]) -> Option<usize> {
|
||||
let start = read_kernel_word(memory as usize + super::layout::field_offset("mm_struct", "arg_start")?)?;
|
||||
if start == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let room = buffer.len() - 1;
|
||||
unsafe { _kthread_use_mm(memory) };
|
||||
let missed = unsafe {
|
||||
___arch_copy_from_user(buffer.as_mut_ptr() as *mut c_void, start as *const c_void, room)
|
||||
};
|
||||
unsafe { _kthread_unuse_mm(memory) };
|
||||
|
||||
let read = room - missed;
|
||||
(read != 0).then_some(read)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn base_name(arguments: &[u8], label: &mut [u8]) -> *const u8 {
|
||||
let first = &arguments[..arguments.iter().position(|byte| *byte == 0).unwrap_or(arguments.len())];
|
||||
let start = first.iter().rposition(|byte| *byte == b'/').map_or(0, |slash| slash + 1);
|
||||
let name = &first[start..];
|
||||
|
||||
let taken = name.len().min(label.len() - 1);
|
||||
label[..taken].copy_from_slice(&name[..taken]);
|
||||
label[taken] = 0;
|
||||
|
||||
label.as_ptr()
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn join_arguments(cmdline: &mut [u8], read: usize) -> *const u8 {
|
||||
for byte in &mut cmdline[..read] {
|
||||
if *byte == 0 {
|
||||
*byte = b' ';
|
||||
}
|
||||
}
|
||||
cmdline[read] = 0;
|
||||
|
||||
cmdline.as_ptr()
|
||||
}
|
||||
|
||||
fn format_user(uid: u32, buffer: &mut [u8]) -> *const u8 {
|
||||
let text = user_label(uid);
|
||||
let bytes = text.as_bytes();
|
||||
|
||||
let taken = bytes.len().min(buffer.len() - 1);
|
||||
buffer[..taken].copy_from_slice(&bytes[..taken]);
|
||||
buffer[taken] = 0;
|
||||
|
||||
buffer.as_ptr()
|
||||
}
|
||||
|
||||
fn user_label(uid: u32) -> String {
|
||||
let user = uid / AID_USER_OFFSET;
|
||||
let app = uid % AID_USER_OFFSET;
|
||||
|
||||
if (AID_APP_START..AID_APP_END).contains(&app) {
|
||||
return format!("u{}_a{}", user, app - AID_APP_START);
|
||||
}
|
||||
|
||||
match app {
|
||||
0 => "root".into(),
|
||||
1000 => "system".into(),
|
||||
1001 => "radio".into(),
|
||||
2000 => "shell".into(),
|
||||
_ => format!("{}", uid),
|
||||
}
|
||||
}
|
||||
|
||||
fn wall_time(started: u64, now_boottime: u64, now_realtime: u64) -> i64 {
|
||||
if started == 0 || started > now_boottime {
|
||||
return 0;
|
||||
}
|
||||
let age = now_boottime - started;
|
||||
if age > now_realtime {
|
||||
return 0;
|
||||
}
|
||||
|
||||
((now_realtime - age) / 1_000_000_000) as i64
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
fn current_time() -> (u64, u64) {
|
||||
let mut wall = Timespec { seconds: 0, nanoseconds: 0 };
|
||||
unsafe { _ktime_get_real_ts64(&mut wall) };
|
||||
let wallclock = (wall.seconds as u64) * 1_000_000_000 + (wall.nanoseconds as u64);
|
||||
let uptime = unsafe { _ktime_get_mono_fast_ns() };
|
||||
|
||||
(uptime, wallclock)
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
fn current_time() -> (u64, u64) {
|
||||
(0, 0)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
#[repr(C)]
|
||||
struct Timespec {
|
||||
seconds: i64,
|
||||
nanoseconds: i64,
|
||||
}
|
||||
|
||||
fn path_of(executable: *mut c_void, buffer: &mut [u8]) -> *const u8 {
|
||||
@@ -175,6 +350,11 @@ struct Layout {
|
||||
list: usize,
|
||||
name: usize,
|
||||
id: usize,
|
||||
credentials: Option<usize>,
|
||||
uid: Option<usize>,
|
||||
parent: Option<usize>,
|
||||
group: Option<usize>,
|
||||
started: Option<usize>,
|
||||
}
|
||||
|
||||
fn discover_layout() -> Option<Layout> {
|
||||
@@ -189,16 +369,32 @@ fn discover_layout() -> Option<Layout> {
|
||||
let name = find(&image, IDLE_TASK_NAME)?;
|
||||
let (list, id) = find_task_list(init, &image, name)?;
|
||||
|
||||
Some(Layout { init, list, name, id })
|
||||
Some(Layout {
|
||||
init,
|
||||
list,
|
||||
name,
|
||||
id,
|
||||
credentials: None,
|
||||
uid: None,
|
||||
parent: None,
|
||||
group: None,
|
||||
started: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn described_layout(init: usize) -> Option<Layout> {
|
||||
use super::layout::field_offset;
|
||||
|
||||
Some(Layout {
|
||||
init,
|
||||
list: super::layout::field_offset("task_struct", "tasks")?,
|
||||
name: super::layout::field_offset("task_struct", "comm")?,
|
||||
id: super::layout::field_offset("task_struct", "pid")?,
|
||||
list: field_offset("task_struct", "tasks")?,
|
||||
name: field_offset("task_struct", "comm")?,
|
||||
id: field_offset("task_struct", "pid")?,
|
||||
credentials: field_offset("task_struct", "real_cred"),
|
||||
uid: field_offset("cred", "uid"),
|
||||
parent: field_offset("task_struct", "real_parent"),
|
||||
group: field_offset("task_struct", "tgid"),
|
||||
started: field_offset("task_struct", "start_time"),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -406,6 +602,14 @@ const PIDTYPE_TGID: c_int = 1;
|
||||
const PIDTYPE_MAX: usize = 4;
|
||||
|
||||
pub fn cloak_task(task: *mut c_void) {
|
||||
// detach_pid's arity differs across kernels (the classic detach_pid(task, type)
|
||||
// versus the batched detach_pid(pids, task, type) paired with free_pids); calling
|
||||
// the wrong one drives a UBSAN/CFI trap on hardened kernels. Leave the thread
|
||||
// visible rather than risk it.
|
||||
if true {
|
||||
let _ = task;
|
||||
return;
|
||||
}
|
||||
let (Some(tasks), Some(sibling), Some(thread_node)) = (
|
||||
super::layout::field_offset("task_struct", "tasks"),
|
||||
super::layout::field_offset("task_struct", "sibling"),
|
||||
@@ -495,6 +699,11 @@ fn write_unlock_tasklist(flags: usize) {
|
||||
const IDLE_TASK_NAME: &[u8] = b"swapper";
|
||||
const NAME_SIZE: usize = 16;
|
||||
const PATH_MAX: usize = 4096;
|
||||
const CMDLINE_MAX: usize = 512;
|
||||
const NAME_MAX: usize = 256;
|
||||
const AID_USER_OFFSET: u32 = 100000;
|
||||
const AID_APP_START: u32 = 10000;
|
||||
const AID_APP_END: u32 = 20000;
|
||||
|
||||
const MAX_TASK_SIZE: usize = 16 * 1024;
|
||||
const READ_CHUNK: usize = 64;
|
||||
@@ -509,7 +718,6 @@ const MAX_IDENTIFIER: u32 = 4 * 1024 * 1024;
|
||||
|
||||
const ERROR_POINTER_START: usize = usize::MAX - 4095;
|
||||
|
||||
|
||||
unsafe extern "C" {
|
||||
static _init_task: *const c_void;
|
||||
static _tasklist_lock: *mut c_void;
|
||||
@@ -538,9 +746,23 @@ unsafe extern "C" {
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _copy_from_kernel_nofault:
|
||||
unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> c_long;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _get_task_mm: unsafe extern "C" fn(*mut c_void) -> *mut c_void;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _mmput: unsafe extern "C" fn(*mut c_void);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static _kthread_use_mm: unsafe extern "C" fn(*mut c_void);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static _kthread_unuse_mm: unsafe extern "C" fn(*mut c_void);
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
static ___arch_copy_from_user:
|
||||
unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> usize;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _ktime_get_real_ts64: unsafe extern "C" fn(*mut Timespec);
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _ktime_get_mono_fast_ns: unsafe extern "C" fn() -> u64;
|
||||
}
|
||||
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
unsafe extern "C" {
|
||||
#[link_name = "frida_k_copy_from_kernel_nofault"]
|
||||
@@ -561,4 +783,8 @@ unsafe extern "C" {
|
||||
fn _file_path(a0: *mut c_void, a1: *mut c_char, a2: c_int) -> *const c_char;
|
||||
#[link_name = "frida_k_fput"]
|
||||
fn _fput(a0: *mut c_void);
|
||||
#[link_name = "frida_k_get_task_mm"]
|
||||
fn _get_task_mm(a0: *mut c_void) -> *mut c_void;
|
||||
#[link_name = "frida_k_mmput"]
|
||||
fn _mmput(a0: *mut c_void);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
extern void *_panic;
|
||||
extern void * _panic;
|
||||
|
||||
void frida_k_panic (void * a0)
|
||||
{
|
||||
@@ -7,7 +7,7 @@ void frida_k_panic (void * a0)
|
||||
((fn_t) _panic) (a0);
|
||||
}
|
||||
|
||||
extern void *_wake_up_process;
|
||||
extern void * _wake_up_process;
|
||||
|
||||
unsigned long frida_k_wake_up_process (void * a0)
|
||||
{
|
||||
@@ -16,7 +16,7 @@ unsigned long frida_k_wake_up_process (void * a0)
|
||||
return ((fn_t) _wake_up_process) (a0);
|
||||
}
|
||||
|
||||
extern void *_fget;
|
||||
extern void * _fget;
|
||||
|
||||
void * frida_k_fget (unsigned long a0)
|
||||
{
|
||||
@@ -25,7 +25,25 @@ void * frida_k_fget (unsigned long a0)
|
||||
return ((fn_t) _fget) (a0);
|
||||
}
|
||||
|
||||
extern void *_kernel_read;
|
||||
extern void * _fget_task;
|
||||
|
||||
void * frida_k_fget_task (void * a0, unsigned int a1)
|
||||
{
|
||||
typedef void * (__attribute__((regparm(3))) * fn_t) (void *, unsigned int);
|
||||
|
||||
return ((fn_t) _fget_task) (a0, a1);
|
||||
}
|
||||
|
||||
extern void * _find_task_by_vpid;
|
||||
|
||||
void * frida_k_find_task_by_vpid (unsigned int a0)
|
||||
{
|
||||
typedef void * (__attribute__((regparm(3))) * fn_t) (unsigned int);
|
||||
|
||||
return ((fn_t) _find_task_by_vpid) (a0);
|
||||
}
|
||||
|
||||
extern void * _kernel_read;
|
||||
|
||||
long frida_k_kernel_read (void * a0, void * a1, unsigned long a2, void * a3)
|
||||
{
|
||||
@@ -34,7 +52,7 @@ long frida_k_kernel_read (void * a0, void * a1, unsigned long a2, void * a3)
|
||||
return ((fn_t) _kernel_read) (a0, a1, a2, a3);
|
||||
}
|
||||
|
||||
extern void *_kernel_write;
|
||||
extern void * _kernel_write;
|
||||
|
||||
long frida_k_kernel_write (void * a0, void * a1, unsigned long a2, void * a3)
|
||||
{
|
||||
@@ -43,7 +61,7 @@ long frida_k_kernel_write (void * a0, void * a1, unsigned long a2, void * a3)
|
||||
return ((fn_t) _kernel_write) (a0, a1, a2, a3);
|
||||
}
|
||||
|
||||
extern void *_kfree;
|
||||
extern void * _kfree;
|
||||
|
||||
void frida_k_kfree (void * a0)
|
||||
{
|
||||
@@ -52,7 +70,7 @@ void frida_k_kfree (void * a0)
|
||||
((fn_t) _kfree) (a0);
|
||||
}
|
||||
|
||||
extern void *___init_waitqueue_head;
|
||||
extern void * ___init_waitqueue_head;
|
||||
|
||||
void frida_k___init_waitqueue_head (void * a0, void * a1, void * a2)
|
||||
{
|
||||
@@ -61,7 +79,7 @@ void frida_k___init_waitqueue_head (void * a0, void * a1, void * a2)
|
||||
((fn_t) ___init_waitqueue_head) (a0, a1, a2);
|
||||
}
|
||||
|
||||
extern void *_prepare_to_wait_event;
|
||||
extern void * _prepare_to_wait_event;
|
||||
|
||||
unsigned long frida_k_prepare_to_wait_event (void * a0, void * a1, unsigned long a2)
|
||||
{
|
||||
@@ -70,7 +88,7 @@ unsigned long frida_k_prepare_to_wait_event (void * a0, void * a1, unsigned long
|
||||
return ((fn_t) _prepare_to_wait_event) (a0, a1, a2);
|
||||
}
|
||||
|
||||
extern void *_finish_wait;
|
||||
extern void * _finish_wait;
|
||||
|
||||
void frida_k_finish_wait (void * a0, void * a1)
|
||||
{
|
||||
@@ -79,7 +97,7 @@ void frida_k_finish_wait (void * a0, void * a1)
|
||||
((fn_t) _finish_wait) (a0, a1);
|
||||
}
|
||||
|
||||
extern void *___wake_up;
|
||||
extern void * ___wake_up;
|
||||
|
||||
void frida_k___wake_up (void * a0, unsigned long a1, unsigned long a2, void * a3)
|
||||
{
|
||||
@@ -88,7 +106,7 @@ void frida_k___wake_up (void * a0, unsigned long a1, unsigned long a2, void * a3
|
||||
((fn_t) ___wake_up) (a0, a1, a2, a3);
|
||||
}
|
||||
|
||||
extern void *_schedule_hrtimeout_range;
|
||||
extern void * _schedule_hrtimeout_range;
|
||||
|
||||
unsigned long frida_k_schedule_hrtimeout_range (void * a0, unsigned long long a1, unsigned long a2)
|
||||
{
|
||||
@@ -97,7 +115,7 @@ unsigned long frida_k_schedule_hrtimeout_range (void * a0, unsigned long long a1
|
||||
return ((fn_t) _schedule_hrtimeout_range) (a0, a1, a2);
|
||||
}
|
||||
|
||||
extern void *_pci_get_domain_bus_and_slot;
|
||||
extern void * _pci_get_domain_bus_and_slot;
|
||||
|
||||
void * frida_k_pci_get_domain_bus_and_slot (unsigned long a0, unsigned long a1, unsigned long a2)
|
||||
{
|
||||
@@ -106,7 +124,7 @@ void * frida_k_pci_get_domain_bus_and_slot (unsigned long a0, unsigned long a1,
|
||||
return ((fn_t) _pci_get_domain_bus_and_slot) (a0, a1, a2);
|
||||
}
|
||||
|
||||
extern void *_pci_irq_vector;
|
||||
extern void * _pci_irq_vector;
|
||||
|
||||
unsigned long frida_k_pci_irq_vector (void * a0, unsigned long a1)
|
||||
{
|
||||
@@ -115,7 +133,7 @@ unsigned long frida_k_pci_irq_vector (void * a0, unsigned long a1)
|
||||
return ((fn_t) _pci_irq_vector) (a0, a1);
|
||||
}
|
||||
|
||||
extern void *_pci_dev_put;
|
||||
extern void * _pci_dev_put;
|
||||
|
||||
void frida_k_pci_dev_put (void * a0)
|
||||
{
|
||||
@@ -124,7 +142,7 @@ void frida_k_pci_dev_put (void * a0)
|
||||
((fn_t) _pci_dev_put) (a0);
|
||||
}
|
||||
|
||||
extern void *_schedule;
|
||||
extern void * _schedule;
|
||||
|
||||
void frida_k_schedule (void)
|
||||
{
|
||||
@@ -133,7 +151,7 @@ void frida_k_schedule (void)
|
||||
((fn_t) _schedule) ();
|
||||
}
|
||||
|
||||
extern void *_ktime_get_mono_fast_ns;
|
||||
extern void * _ktime_get_mono_fast_ns;
|
||||
|
||||
unsigned long long frida_k_ktime_get_mono_fast_ns (void)
|
||||
{
|
||||
@@ -142,7 +160,7 @@ unsigned long long frida_k_ktime_get_mono_fast_ns (void)
|
||||
return ((fn_t) _ktime_get_mono_fast_ns) ();
|
||||
}
|
||||
|
||||
extern void *_ktime_get_real_ts64;
|
||||
extern void * _ktime_get_real_ts64;
|
||||
|
||||
void frida_k_ktime_get_real_ts64 (void * a0)
|
||||
{
|
||||
@@ -151,7 +169,7 @@ void frida_k_ktime_get_real_ts64 (void * a0)
|
||||
((fn_t) _ktime_get_real_ts64) (a0);
|
||||
}
|
||||
|
||||
extern void *_send_sig;
|
||||
extern void * _send_sig;
|
||||
|
||||
unsigned long frida_k_send_sig (unsigned long a0, void * a1, unsigned long a2)
|
||||
{
|
||||
@@ -160,7 +178,7 @@ unsigned long frida_k_send_sig (unsigned long a0, void * a1, unsigned long a2)
|
||||
return ((fn_t) _send_sig) (a0, a1, a2);
|
||||
}
|
||||
|
||||
extern void *_free_irq;
|
||||
extern void * _free_irq;
|
||||
|
||||
void * frida_k_free_irq (unsigned long a0, void * a1)
|
||||
{
|
||||
@@ -169,7 +187,7 @@ void * frida_k_free_irq (unsigned long a0, void * a1)
|
||||
return ((fn_t) _free_irq) (a0, a1);
|
||||
}
|
||||
|
||||
extern void *_vmap;
|
||||
extern void * _vmap;
|
||||
|
||||
void * frida_k_vmap (void * a0, unsigned long a1, unsigned long a2, unsigned long a3)
|
||||
{
|
||||
@@ -178,7 +196,7 @@ void * frida_k_vmap (void * a0, unsigned long a1, unsigned long a2, unsigned lon
|
||||
return ((fn_t) _vmap) (a0, a1, a2, a3);
|
||||
}
|
||||
|
||||
extern void *_ioremap;
|
||||
extern void * _ioremap;
|
||||
|
||||
void * frida_k_ioremap (unsigned long a0, unsigned long a1)
|
||||
{
|
||||
@@ -187,7 +205,7 @@ void * frida_k_ioremap (unsigned long a0, unsigned long a1)
|
||||
return ((fn_t) _ioremap) (a0, a1);
|
||||
}
|
||||
|
||||
extern void *_get_task_exe_file;
|
||||
extern void * _get_task_exe_file;
|
||||
|
||||
void * frida_k_get_task_exe_file (void * a0)
|
||||
{
|
||||
@@ -196,7 +214,7 @@ void * frida_k_get_task_exe_file (void * a0)
|
||||
return ((fn_t) _get_task_exe_file) (a0);
|
||||
}
|
||||
|
||||
extern void *_file_path;
|
||||
extern void * _file_path;
|
||||
|
||||
void * frida_k_file_path (void * a0, void * a1, unsigned long a2)
|
||||
{
|
||||
@@ -205,7 +223,7 @@ void * frida_k_file_path (void * a0, void * a1, unsigned long a2)
|
||||
return ((fn_t) _file_path) (a0, a1, a2);
|
||||
}
|
||||
|
||||
extern void *_fput;
|
||||
extern void * _fput;
|
||||
|
||||
void frida_k_fput (void * a0)
|
||||
{
|
||||
@@ -214,7 +232,7 @@ void frida_k_fput (void * a0)
|
||||
((fn_t) _fput) (a0);
|
||||
}
|
||||
|
||||
extern void *_get_task_mm;
|
||||
extern void * _get_task_mm;
|
||||
|
||||
void * frida_k_get_task_mm (void * a0)
|
||||
{
|
||||
@@ -223,7 +241,7 @@ void * frida_k_get_task_mm (void * a0)
|
||||
return ((fn_t) _get_task_mm) (a0);
|
||||
}
|
||||
|
||||
extern void *_kthread_use_mm;
|
||||
extern void * _kthread_use_mm;
|
||||
|
||||
void frida_k_kthread_use_mm (void * a0)
|
||||
{
|
||||
@@ -232,7 +250,7 @@ void frida_k_kthread_use_mm (void * a0)
|
||||
((fn_t) _kthread_use_mm) (a0);
|
||||
}
|
||||
|
||||
extern void *_kthread_unuse_mm;
|
||||
extern void * _kthread_unuse_mm;
|
||||
|
||||
void frida_k_kthread_unuse_mm (void * a0)
|
||||
{
|
||||
@@ -241,7 +259,7 @@ void frida_k_kthread_unuse_mm (void * a0)
|
||||
((fn_t) _kthread_unuse_mm) (a0);
|
||||
}
|
||||
|
||||
extern void *_mmput;
|
||||
extern void * _mmput;
|
||||
|
||||
void frida_k_mmput (void * a0)
|
||||
{
|
||||
@@ -250,7 +268,7 @@ void frida_k_mmput (void * a0)
|
||||
((fn_t) _mmput) (a0);
|
||||
}
|
||||
|
||||
extern void *_vm_mmap;
|
||||
extern void * _vm_mmap;
|
||||
|
||||
unsigned long frida_k_vm_mmap (void * a0, unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5)
|
||||
{
|
||||
@@ -259,7 +277,7 @@ unsigned long frida_k_vm_mmap (void * a0, unsigned long a1, unsigned long a2, un
|
||||
return ((fn_t) _vm_mmap) (a0, a1, a2, a3, a4, a5);
|
||||
}
|
||||
|
||||
extern void *_vm_munmap;
|
||||
extern void * _vm_munmap;
|
||||
|
||||
unsigned long frida_k_vm_munmap (unsigned long a0, unsigned long a1)
|
||||
{
|
||||
@@ -268,7 +286,7 @@ unsigned long frida_k_vm_munmap (unsigned long a0, unsigned long a1)
|
||||
return ((fn_t) _vm_munmap) (a0, a1);
|
||||
}
|
||||
|
||||
extern void *_vunmap;
|
||||
extern void * _vunmap;
|
||||
|
||||
void frida_k_vunmap (void * a0)
|
||||
{
|
||||
@@ -277,7 +295,7 @@ void frida_k_vunmap (void * a0)
|
||||
((fn_t) _vunmap) (a0);
|
||||
}
|
||||
|
||||
extern void *__copy_to_user;
|
||||
extern void * __copy_to_user;
|
||||
|
||||
unsigned long frida_k__copy_to_user (void * a0, void * a1, unsigned long a2)
|
||||
{
|
||||
@@ -286,7 +304,7 @@ unsigned long frida_k__copy_to_user (void * a0, void * a1, unsigned long a2)
|
||||
return ((fn_t) __copy_to_user) (a0, a1, a2);
|
||||
}
|
||||
|
||||
extern void *_down_read;
|
||||
extern void * _down_read;
|
||||
|
||||
void frida_k_down_read (void * a0)
|
||||
{
|
||||
@@ -295,7 +313,7 @@ void frida_k_down_read (void * a0)
|
||||
((fn_t) _down_read) (a0);
|
||||
}
|
||||
|
||||
extern void *_up_read;
|
||||
extern void * _up_read;
|
||||
|
||||
void frida_k_up_read (void * a0)
|
||||
{
|
||||
@@ -304,7 +322,7 @@ void frida_k_up_read (void * a0)
|
||||
((fn_t) _up_read) (a0);
|
||||
}
|
||||
|
||||
extern void *_get_user_pages_remote;
|
||||
extern void * _get_user_pages_remote;
|
||||
|
||||
long frida_k_get_user_pages_remote (void * a0, unsigned long a1, unsigned long a2, unsigned long a3, void * a4, void * a5)
|
||||
{
|
||||
@@ -313,7 +331,7 @@ long frida_k_get_user_pages_remote (void * a0, unsigned long a1, unsigned long a
|
||||
return ((fn_t) _get_user_pages_remote) (a0, a1, a2, a3, a4, a5);
|
||||
}
|
||||
|
||||
extern void *_do_futex;
|
||||
extern void * _do_futex;
|
||||
|
||||
long frida_k_do_futex (unsigned long a0, unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5, unsigned long a6)
|
||||
{
|
||||
@@ -322,7 +340,7 @@ long frida_k_do_futex (unsigned long a0, unsigned long a1, unsigned long a2, uns
|
||||
return ((fn_t) _do_futex) (a0, a1, a2, a3, a4, a5, a6);
|
||||
}
|
||||
|
||||
extern void *_call_usermodehelper_exec;
|
||||
extern void * _call_usermodehelper_exec;
|
||||
|
||||
unsigned long frida_k_call_usermodehelper_exec (void * a0, unsigned long a1)
|
||||
{
|
||||
@@ -331,26 +349,26 @@ unsigned long frida_k_call_usermodehelper_exec (void * a0, unsigned long a1)
|
||||
return ((fn_t) _call_usermodehelper_exec) (a0, a1);
|
||||
}
|
||||
|
||||
extern void *__printk;
|
||||
extern void *_printk;
|
||||
extern void *___kmalloc_noprof;
|
||||
extern void *_kmalloc;
|
||||
extern void *_execmem_alloc;
|
||||
extern void *_module_alloc;
|
||||
extern void *_set_memory_rw;
|
||||
extern void *_execmem_free;
|
||||
extern void *_module_memfree;
|
||||
extern void *__raw_read_lock;
|
||||
extern void *__raw_read_unlock;
|
||||
extern void *__raw_read_lock_irqsave;
|
||||
extern void *__raw_read_unlock_irqrestore;
|
||||
extern void *__raw_write_lock;
|
||||
extern void *__raw_write_unlock;
|
||||
extern void *__raw_write_lock_irqsave;
|
||||
extern void *__raw_write_unlock_irqrestore;
|
||||
extern void *_detach_pid;
|
||||
extern void *_free_pids;
|
||||
extern void *_tasklist_lock;
|
||||
extern void * __printk;
|
||||
extern void * _printk;
|
||||
extern void * ___kmalloc_noprof;
|
||||
extern void * _kmalloc;
|
||||
extern void * _execmem_alloc;
|
||||
extern void * _module_alloc;
|
||||
extern void * _set_memory_rw;
|
||||
extern void * _execmem_free;
|
||||
extern void * _module_memfree;
|
||||
extern void * __raw_read_lock;
|
||||
extern void * __raw_read_unlock;
|
||||
extern void * __raw_read_lock_irqsave;
|
||||
extern void * __raw_read_unlock_irqrestore;
|
||||
extern void * __raw_write_lock;
|
||||
extern void * __raw_write_unlock;
|
||||
extern void * __raw_write_lock_irqsave;
|
||||
extern void * __raw_write_unlock_irqrestore;
|
||||
extern void * _detach_pid;
|
||||
extern void * _free_pids;
|
||||
extern void * _tasklist_lock;
|
||||
|
||||
typedef void (* frida_say_t) (const char *, ...);
|
||||
typedef void * (__attribute__((regparm(3))) * frida_take_t) (unsigned long, unsigned long);
|
||||
@@ -363,21 +381,21 @@ typedef void (__attribute__((regparm(3))) * frida_release_flags_t) (void *, unsi
|
||||
|
||||
void frida_k_log (const char * message)
|
||||
{
|
||||
void *say = __printk ? __printk : _printk;
|
||||
void * say = __printk ? __printk : _printk;
|
||||
|
||||
((frida_say_t) say) (message);
|
||||
}
|
||||
|
||||
void * frida_k_alloc (unsigned long size, unsigned long flags)
|
||||
{
|
||||
void *take = ___kmalloc_noprof ? ___kmalloc_noprof : _kmalloc;
|
||||
void * take = ___kmalloc_noprof ? ___kmalloc_noprof : _kmalloc;
|
||||
|
||||
return ((frida_take_t) take) (size, flags);
|
||||
}
|
||||
|
||||
void * frida_k_alloc_code (unsigned long kind, unsigned long size, unsigned long pages)
|
||||
{
|
||||
void *code;
|
||||
void * code;
|
||||
|
||||
if (_execmem_alloc)
|
||||
code = ((frida_take_t) _execmem_alloc) (kind, size);
|
||||
@@ -392,7 +410,7 @@ void * frida_k_alloc_code (unsigned long kind, unsigned long size, unsigned long
|
||||
|
||||
void frida_k_free_code (void * code)
|
||||
{
|
||||
void *give = _execmem_free ? _execmem_free : _module_memfree;
|
||||
void * give = _execmem_free ? _execmem_free : _module_memfree;
|
||||
|
||||
((frida_give_t) give) (code);
|
||||
}
|
||||
@@ -490,7 +508,7 @@ frida_kcb_thread_left (void * data, void * task, _Bool group_dead)
|
||||
frida_cb_thread_left (data, task, group_dead);
|
||||
}
|
||||
|
||||
extern void *_request_threaded_irq;
|
||||
extern void * _request_threaded_irq;
|
||||
|
||||
int
|
||||
frida_k_request_threaded_irq (unsigned int a0, void * a1, void * a2, unsigned long a3, const char * a4,
|
||||
@@ -502,7 +520,7 @@ frida_k_request_threaded_irq (unsigned int a0, void * a1, void * a2, unsigned lo
|
||||
return ((fn_t) _request_threaded_irq) (a0, a1, a2, a3, a4, a5);
|
||||
}
|
||||
|
||||
extern void *_copy_from_kernel_nofault;
|
||||
extern void * _copy_from_kernel_nofault;
|
||||
|
||||
long
|
||||
frida_k_copy_from_kernel_nofault (void * a0, const void * a1, unsigned int a2)
|
||||
@@ -512,10 +530,10 @@ frida_k_copy_from_kernel_nofault (void * a0, const void * a1, unsigned int a2)
|
||||
return ((fn_t) _copy_from_kernel_nofault) (a0, a1, a2);
|
||||
}
|
||||
|
||||
extern void *_tracepoint_probe_register;
|
||||
extern void *_tracepoint_probe_unregister;
|
||||
extern void *_user_mode_thread;
|
||||
extern void *_call_usermodehelper_setup;
|
||||
extern void * _tracepoint_probe_register;
|
||||
extern void * _tracepoint_probe_unregister;
|
||||
extern void * _user_mode_thread;
|
||||
extern void * _call_usermodehelper_setup;
|
||||
|
||||
int
|
||||
frida_k_tracepoint_probe_register (void * a0, void * a1, void * a2)
|
||||
@@ -566,7 +584,7 @@ frida_kcb_release_words (void * info)
|
||||
frida_cb_release_words (info);
|
||||
}
|
||||
|
||||
extern void *_get_user_pages_unlocked;
|
||||
extern void * _get_user_pages_unlocked;
|
||||
|
||||
long
|
||||
frida_k_get_user_pages_unlocked (unsigned long a0, unsigned long a1, void * a2, int a3)
|
||||
@@ -577,7 +595,7 @@ frida_k_get_user_pages_unlocked (unsigned long a0, unsigned long a1, void * a2,
|
||||
return ((fn_t) _get_user_pages_unlocked) (a0, a1, a2, a3);
|
||||
}
|
||||
|
||||
extern void *_text_poke_kgdb;
|
||||
extern void * _text_poke_kgdb;
|
||||
|
||||
void * frida_k_text_poke (void * addr, const void * opcode, unsigned long len)
|
||||
{
|
||||
@@ -589,7 +607,7 @@ void * frida_k_text_poke (void * addr, const void * opcode, unsigned long len)
|
||||
return ((fn_t) _text_poke_kgdb) (addr, opcode, len);
|
||||
}
|
||||
|
||||
extern void *_register_module_notifier;
|
||||
extern void * _register_module_notifier;
|
||||
|
||||
int frida_k_register_module_notifier (void * a0)
|
||||
{
|
||||
@@ -598,7 +616,7 @@ int frida_k_register_module_notifier (void * a0)
|
||||
return ((fn_t) _register_module_notifier) (a0);
|
||||
}
|
||||
|
||||
extern void *_unregister_module_notifier;
|
||||
extern void * _unregister_module_notifier;
|
||||
|
||||
int frida_k_unregister_module_notifier (void * a0)
|
||||
{
|
||||
@@ -615,7 +633,7 @@ frida_kcb_module_state (void * nb, unsigned long action, void * data)
|
||||
return frida_cb_module_state (nb, action, data);
|
||||
}
|
||||
|
||||
extern void *_register_die_notifier;
|
||||
extern void * _register_die_notifier;
|
||||
|
||||
int frida_k_register_die_notifier (void * a0)
|
||||
{
|
||||
@@ -624,7 +642,7 @@ int frida_k_register_die_notifier (void * a0)
|
||||
return ((fn_t) _register_die_notifier) (a0);
|
||||
}
|
||||
|
||||
extern void *_unregister_die_notifier;
|
||||
extern void * _unregister_die_notifier;
|
||||
|
||||
int frida_k_unregister_die_notifier (void * a0)
|
||||
{
|
||||
|
||||
@@ -67,6 +67,7 @@ fn send_what_waits(arena: u64) {
|
||||
let Some(written) = piece else {
|
||||
return;
|
||||
};
|
||||
publish_fence();
|
||||
wake_the_copy(arena);
|
||||
|
||||
if written == frame.len() {
|
||||
@@ -85,6 +86,7 @@ fn write_frame(ring: &Ring, arena: u64, frame: &[u8], tell: fn(u64)) -> bool {
|
||||
match ring.write(arena, arena + ring.buffer, frame, written) {
|
||||
Some(now) => {
|
||||
written = now;
|
||||
publish_fence();
|
||||
tell(arena);
|
||||
}
|
||||
None => {
|
||||
@@ -126,6 +128,16 @@ pub fn forget(arena: u64) {
|
||||
unsafe { waiting() }.remove(&arena);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn publish_fence() {
|
||||
unsafe { core::arch::asm!("dsb ish", options(nostack, preserves_flags)) };
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "aarch64"))]
|
||||
fn publish_fence() {
|
||||
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
|
||||
}
|
||||
|
||||
fn wake_the_copy(arena: u64) {
|
||||
super::injection::tell_the_copy_at(arena);
|
||||
}
|
||||
|
||||
Loaded 100 of 363 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user