mirror of
https://github.com/frida/frida-core.git
synced 2026-11-08 08:05:58 -10:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f67d1e81df | ||
|
|
a068c704d1 | ||
|
|
34ac3a0d50 | ||
|
|
1ea81bcd59 |
No files matched your search
@@ -20,8 +20,8 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- { id: windows-arm64, runner: '"windows-11-arm"', sys: CLANGARM64, pkg: 'mingw-w64-clang-aarch64-clang', opts: "'-Dfrida-gum:v8=disabled'" }
|
||||
- { id: windows-x86_64, runner: '"windows-latest"', sys: UCRT64, pkg: 'mingw-w64-ucrt-x86_64-gcc' }
|
||||
- { id: windows-arm64, runner: '"windows-11-arm"', sys: CLANGARM64, pkg: 'mingw-w64-clang-aarch64-clang' }
|
||||
- { id: windows-x86_64, runner: '"windows-latest"', sys: MINGW64, pkg: 'mingw-w64-x86_64-gcc' }
|
||||
- { id: windows-x86, runner: '"windows-latest"', sys: MINGW32, pkg: 'mingw-w64-i686-gcc' }
|
||||
- { id: macos-x86_64, runner: '"macos-latest"' }
|
||||
- { id: macos-arm64, runner: '"macos-latest"' }
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
--host=${{ matrix.id }} `
|
||||
--enable-compiler-backend `
|
||||
${{ env.FRIDA_CORE_OPTIONS }} `
|
||||
-- --force-fallback-for=openssl ${{ matrix.opts }}
|
||||
-- --force-fallback-for=openssl
|
||||
.\make
|
||||
env:
|
||||
MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }}
|
||||
@@ -658,7 +658,7 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- { arch: x86_64, runner: windows-latest, msvc-arch: amd64, vcpkg-triplet: x64-windows-release }
|
||||
- { arch: arm64, runner: windows-11-arm, msvc-arch: arm64, vcpkg-triplet: arm64-windows-release, opts: "'-Dfrida-gum:v8=disabled'" }
|
||||
- { arch: arm64, runner: windows-11-arm, msvc-arch: arm64, vcpkg-triplet: arm64-windows-release }
|
||||
fail-fast: false
|
||||
runs-on: ${{ matrix.runner }}
|
||||
timeout-minutes: 120
|
||||
@@ -668,17 +668,9 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up MSVC environment
|
||||
run: |
|
||||
$vs = & "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -products * -latest -property installationPath
|
||||
$before = @{}
|
||||
Get-ChildItem env: | ForEach-Object { $before[$_.Name] = $_.Value }
|
||||
cmd /c "`"$vs\VC\Auxiliary\Build\vcvarsall.bat`" ${{ matrix.msvc-arch }} >nul && set" | ForEach-Object {
|
||||
$name, $value = $_ -split '=', 2
|
||||
if ($before[$name] -ne $value) {
|
||||
"$name=$value" >> $env:GITHUB_ENV
|
||||
}
|
||||
}
|
||||
- uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: ${{ matrix.msvc-arch }}
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
with:
|
||||
@@ -729,7 +721,7 @@ jobs:
|
||||
--enable-shared `
|
||||
--without-prebuilds=sdk `
|
||||
"--prefix=$env:RUNNER_TEMP\frida-prefix" `
|
||||
-- --force-fallback-for=openssl,libnghttp2 -Dcompiler_backend=disabled -Dtests=disabled ${{ matrix.opts }}
|
||||
-- --force-fallback-for=openssl,libnghttp2 -Dcompiler_backend=disabled -Dtests=disabled
|
||||
|
||||
- name: Compile
|
||||
run: python releng/meson/meson.py compile -C build
|
||||
|
||||
+1
-9
@@ -2,15 +2,7 @@
|
||||
/build/
|
||||
/deps/
|
||||
/src/android-helper/build/
|
||||
/src/barebone/agent/linux/*.ko
|
||||
/src/barebone/agent/linux/*.mod
|
||||
/src/barebone/agent/linux/*.mod.c
|
||||
/src/barebone/agent/linux/*.o
|
||||
/src/barebone/agent/linux/*.o.bin
|
||||
/src/barebone/agent/linux/.*.cmd
|
||||
/src/barebone/agent/linux/Module.symvers
|
||||
/src/barebone/agent/linux/modules.order
|
||||
/src/barebone/agent/target*/
|
||||
/src/barebone/agent/target/
|
||||
/src/barebone/agent/xpacks/
|
||||
/src/barebone/helpers/memory-scanner/target/
|
||||
/src/barebone/helpers/target/
|
||||
|
||||
@@ -13,44 +13,6 @@
|
||||
# include <gioapple.h>
|
||||
#elif defined (HAVE_GIOOPENSSL)
|
||||
# include <gioopenssl.h>
|
||||
#endif
|
||||
#ifdef HAVE_PROSPERO
|
||||
# include "frida-prospero.h"
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_PROSPERO
|
||||
|
||||
FridaProsperoAgentArgs frida_prospero_agent_args = { .fifo_fd = -1 };
|
||||
|
||||
int
|
||||
main (int argc, char * argv[], char * envp[])
|
||||
{
|
||||
FridaProsperoAgentArgs * args = &frida_prospero_agent_args;
|
||||
FridaProsperoInjectorState injector_state;
|
||||
guint8 hello_byte = FRIDA_PROSPERO_HELLO_BYTE;
|
||||
FridaUnloadPolicy unload_policy = FRIDA_UNLOAD_POLICY_IMMEDIATE;
|
||||
guint8 policy_byte;
|
||||
guint32 worker_id;
|
||||
|
||||
injector_state.fifo_fd = args->fifo_fd;
|
||||
injector_state.agent_ctrlfd = args->agent_ctrlfd;
|
||||
injector_state.mapped_range = &args->mapped_range;
|
||||
|
||||
write (injector_state.fifo_fd, &hello_byte, sizeof (hello_byte));
|
||||
|
||||
frida_agent_main (GSIZE_TO_POINTER (args->agent_parameters), &unload_policy, &injector_state);
|
||||
|
||||
policy_byte = unload_policy;
|
||||
worker_id = gum_process_get_current_thread_id ();
|
||||
|
||||
write (injector_state.fifo_fd, &policy_byte, sizeof (policy_byte));
|
||||
write (injector_state.fifo_fd, &worker_id, sizeof (worker_id));
|
||||
close (injector_state.fifo_fd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void
|
||||
|
||||
+18
-19
@@ -128,12 +128,6 @@ namespace Frida.Agent {
|
||||
mapped_range = injector_state.mapped_range;
|
||||
#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);
|
||||
@@ -163,19 +157,6 @@ namespace Frida.Agent {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if PROSPERO
|
||||
string? agent_parameters_with_transport_uri = null;
|
||||
if (prospero_state != null) {
|
||||
int agent_ctrlfd = prospero_state.agent_ctrlfd;
|
||||
prospero_state.agent_ctrlfd = -1;
|
||||
|
||||
fdt_padder.move_descriptor_if_needed (ref agent_ctrlfd);
|
||||
|
||||
agent_parameters_with_transport_uri = "socket:%d%s".printf (agent_ctrlfd, agent_parameters);
|
||||
agent_parameters = agent_parameters_with_transport_uri;
|
||||
}
|
||||
#endif
|
||||
|
||||
var ignore_scope = new ThreadIgnoreScope (FRIDA_THREAD);
|
||||
|
||||
shared_instance = new Runner (agent_parameters, cached_agent_path, cached_agent_range);
|
||||
@@ -311,6 +292,8 @@ namespace Frida.Agent {
|
||||
|
||||
start.begin ((owned) padder);
|
||||
|
||||
start_alive_task_dumper ();
|
||||
|
||||
main_loop.run ();
|
||||
|
||||
main_context.pop_thread_default ();
|
||||
@@ -381,6 +364,16 @@ namespace Frida.Agent {
|
||||
padder = null;
|
||||
}
|
||||
|
||||
// TEMPORARY DEBUG HACK: dump alive GTasks every 30 seconds.
|
||||
private void start_alive_task_dumper () {
|
||||
var source = new TimeoutSource.seconds (30);
|
||||
source.set_callback (() => {
|
||||
GLib.info ("%s", g_task_describe_alive_tasks ());
|
||||
return Source.CONTINUE;
|
||||
});
|
||||
source.attach (main_context);
|
||||
}
|
||||
|
||||
private void keep_running_eternalized () {
|
||||
agent_gthread = new Thread<bool> ("frida-eternal-agent", () => {
|
||||
var ignore_scope = new ThreadIgnoreScope (FRIDA_THREAD);
|
||||
@@ -943,6 +936,8 @@ namespace Frida.Agent {
|
||||
private void on_session_closed (BaseAgentSession base_session) {
|
||||
LiveAgentSession session = (LiveAgentSession) base_session;
|
||||
|
||||
GLib.info ("detach-trace: Agent emitting provider closed id=%s", session.id.handle);
|
||||
|
||||
closed (session.id);
|
||||
|
||||
unregister_session (session);
|
||||
@@ -1743,4 +1738,8 @@ namespace Frida.Agent {
|
||||
return repeat;
|
||||
});
|
||||
}
|
||||
|
||||
// TEMPORARY DEBUG HACK: only present when GLib is built with G_ENABLE_DEBUG.
|
||||
[CCode (cname = "g_task_describe_alive_tasks")]
|
||||
private extern string g_task_describe_alive_tasks ();
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
global:
|
||||
frida_agent_main;
|
||||
frida_prospero_agent_args;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
@@ -42,8 +42,6 @@ if build_agent
|
||||
symscript = 'frida-agent-glibc.version'
|
||||
elif host_os == 'android'
|
||||
symscript = 'frida-agent-android.version'
|
||||
elif host_is_prospero
|
||||
symscript = 'frida-agent-prospero.version'
|
||||
else
|
||||
symscript = 'frida-agent.version'
|
||||
endif
|
||||
@@ -56,8 +54,6 @@ if build_agent
|
||||
extra_link_args += '-Wl,-Bsymbolic'
|
||||
endif
|
||||
|
||||
extra_link_args += prospero_crt_link_args
|
||||
|
||||
raw_agent = shared_library('frida-agent-raw', agent_sources,
|
||||
name_prefix: 'lib',
|
||||
c_args: frida_component_cflags,
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
#ifndef __FRIDA_PROSPERO_H__
|
||||
#define __FRIDA_PROSPERO_H__
|
||||
|
||||
#ifdef HAVE_PROSPERO
|
||||
|
||||
#include <gum/gum.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define FRIDA_PROSPERO_AGENT_ARGS_SYMBOL "frida_prospero_agent_args"
|
||||
#define FRIDA_PROSPERO_HELLO_BYTE 0xff
|
||||
|
||||
typedef struct _FridaProsperoAgentArgs FridaProsperoAgentArgs;
|
||||
|
||||
struct _FridaProsperoAgentArgs
|
||||
{
|
||||
GumAddress agent_parameters;
|
||||
gint fifo_fd;
|
||||
gint agent_ctrlfd;
|
||||
GumMemoryRange mapped_range;
|
||||
};
|
||||
|
||||
typedef struct _FridaProsperoAppLaunchContext FridaProsperoAppLaunchContext;
|
||||
|
||||
struct _FridaProsperoAppLaunchContext
|
||||
{
|
||||
guint32 size;
|
||||
guint32 user_id;
|
||||
guint32 app_opt;
|
||||
guint64 crash_report;
|
||||
guint32 check_flag;
|
||||
};
|
||||
|
||||
extern int sceUserServiceInitialize (void * params);
|
||||
extern int sceUserServiceGetForegroundUser (guint32 * user_id);
|
||||
extern int sceSystemServiceLaunchApp (const gchar * title_id, gchar ** argv, FridaProsperoAppLaunchContext * ctx);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -90,18 +90,6 @@ namespace Frida.FS {
|
||||
yield write_all_bytes (f, new Bytes (t.data), cancellable);
|
||||
}
|
||||
|
||||
public Bytes read_all_bytes_sync (File f) throws Error {
|
||||
try {
|
||||
uint8[] contents;
|
||||
f.load_contents (null, out contents, null);
|
||||
return new Bytes.take ((owned) contents);
|
||||
} catch (GLib.Error e) {
|
||||
if (e is IOError.NOT_FOUND)
|
||||
throw new Error.INVALID_ARGUMENT ("%s", e.message);
|
||||
throw new Error.PERMISSION_DENIED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async Bytes read_all_bytes (File f, Cancellable? cancellable) throws Error, IOError {
|
||||
try {
|
||||
return yield f.load_bytes_async (cancellable, null);
|
||||
|
||||
@@ -23,7 +23,7 @@ base_sources = [
|
||||
extra_vala_args = []
|
||||
|
||||
if host_os == 'windows'
|
||||
base_sources += ['windows.vala', 'windows-glue.c']
|
||||
base_sources += 'windows.vala'
|
||||
else
|
||||
base_sources += 'posix.vala'
|
||||
extra_vala_args += '--pkg=posix'
|
||||
|
||||
@@ -220,128 +220,4 @@ namespace Frida {
|
||||
Posix.tcsetattr (fd, 0, tios);
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace ChildProcess {
|
||||
public async WaitResult wait_for_next_stop (uint pid, Cancellable? cancellable) throws Error, IOError {
|
||||
var main_context = MainContext.get_thread_default ();
|
||||
|
||||
bool timed_out = false;
|
||||
var timeout_source = new TimeoutSource.seconds (5);
|
||||
timeout_source.set_callback (() => {
|
||||
timed_out = true;
|
||||
return Source.REMOVE;
|
||||
});
|
||||
timeout_source.attach (main_context);
|
||||
|
||||
int status = 0;
|
||||
uint[] delays = { 0, 1, 2, 5, 10, 20, 50, 250 };
|
||||
|
||||
try {
|
||||
for (uint i = 0; !timed_out && !cancellable.set_error_if_cancelled (); i++) {
|
||||
int res = Posix.waitpid ((Posix.pid_t) pid, out status, Posix.WNOHANG);
|
||||
if (res == -1)
|
||||
throw new Error.NOT_SUPPORTED ("Unable to wait for next stop: %s", strerror (errno));
|
||||
if (res != 0)
|
||||
break;
|
||||
|
||||
uint delay_ms = (i < delays.length) ? delays[i] : delays[delays.length - 1];
|
||||
|
||||
var delay_source = new TimeoutSource (delay_ms);
|
||||
delay_source.set_callback (wait_for_next_stop.callback);
|
||||
delay_source.attach (main_context);
|
||||
|
||||
var cancel_source = new CancellableSource (cancellable);
|
||||
cancel_source.set_callback (wait_for_next_stop.callback);
|
||||
cancel_source.attach (main_context);
|
||||
|
||||
yield;
|
||||
|
||||
cancel_source.destroy ();
|
||||
delay_source.destroy ();
|
||||
}
|
||||
} finally {
|
||||
timeout_source.destroy ();
|
||||
}
|
||||
|
||||
if (timed_out)
|
||||
throw new Error.TIMED_OUT ("Unexpectedly timed out while waiting for stop from process with PID %u", pid);
|
||||
|
||||
return WaitResult (pid, status);
|
||||
}
|
||||
}
|
||||
|
||||
public struct WaitResult {
|
||||
public uint pid;
|
||||
public int status;
|
||||
|
||||
public WaitKind kind;
|
||||
|
||||
public uint exit_status;
|
||||
|
||||
public int term_signal;
|
||||
|
||||
public int stop_signal;
|
||||
|
||||
public WaitResult (uint pid, int status) {
|
||||
this.pid = pid;
|
||||
this.status = status;
|
||||
|
||||
kind = WaitKind.OTHER;
|
||||
exit_status = 0;
|
||||
term_signal = 0;
|
||||
stop_signal = 0;
|
||||
|
||||
if (PosixStatus.is_exit (status)) {
|
||||
kind = WaitKind.EXITED;
|
||||
exit_status = PosixStatus.parse_exit_status (status);
|
||||
} else if (PosixStatus.is_signaled (status)) {
|
||||
kind = WaitKind.SIGNALED;
|
||||
term_signal = PosixStatus.parse_termination_signal (status);
|
||||
} else if (PosixStatus.is_stopped (status)) {
|
||||
kind = WaitKind.STOPPED;
|
||||
|
||||
stop_signal = PosixStatus.parse_stop_signal (status);
|
||||
}
|
||||
}
|
||||
|
||||
public void check_stopped () throws Error {
|
||||
switch (kind) {
|
||||
case WaitKind.EXITED:
|
||||
throw new Error.NOT_SUPPORTED ("Target exited with status %u", exit_status);
|
||||
case WaitKind.SIGNALED:
|
||||
throw new Error.NOT_SUPPORTED ("Target terminated with signal %u", term_signal);
|
||||
case WaitKind.STOPPED:
|
||||
return;
|
||||
default:
|
||||
throw new Error.NOT_SUPPORTED ("Unexpected status: 0x%08x", status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum WaitKind {
|
||||
EXITED,
|
||||
SIGNALED,
|
||||
STOPPED,
|
||||
OTHER
|
||||
}
|
||||
|
||||
namespace PosixStatus {
|
||||
[CCode (cname = "WIFEXITED", cheader_filename = "sys/wait.h")]
|
||||
private extern bool is_exit (int status);
|
||||
|
||||
[CCode (cname = "WIFSIGNALED", cheader_filename = "sys/wait.h")]
|
||||
private extern bool is_signaled (int status);
|
||||
|
||||
[CCode (cname = "WIFSTOPPED", cheader_filename = "sys/wait.h")]
|
||||
private extern bool is_stopped (int status);
|
||||
|
||||
[CCode (cname = "WEXITSTATUS", cheader_filename = "sys/wait.h")]
|
||||
private extern uint parse_exit_status (int status);
|
||||
|
||||
[CCode (cname = "WTERMSIG", cheader_filename = "sys/wait.h")]
|
||||
private extern Posix.Signal parse_termination_signal (int status);
|
||||
|
||||
[CCode (cname = "WSTOPSIG", cheader_filename = "sys/wait.h")]
|
||||
private extern Posix.Signal parse_stop_signal (int status);
|
||||
}
|
||||
}
|
||||
@@ -361,6 +361,8 @@ namespace Frida {
|
||||
|
||||
private async void teardown_peer_connection_and_emit_closed () {
|
||||
schedule_on_frida_thread (() => {
|
||||
GLib.info ("detach-trace: AgentMessageTransmitter %p emitting closed, nice_agent=%p",
|
||||
this, nice_agent);
|
||||
if (nice_agent != null)
|
||||
close_nice_resources_and_emit_closed.begin ();
|
||||
else
|
||||
@@ -598,6 +600,7 @@ namespace Frida {
|
||||
|
||||
private async void teardown_peer_connection_and_emit_closed () {
|
||||
schedule_on_frida_thread (() => {
|
||||
GLib.info ("detach-trace: AgentMessageTransmitter %p emitting closed", this);
|
||||
closed ();
|
||||
return Source.REMOVE;
|
||||
});
|
||||
@@ -1152,14 +1155,6 @@ namespace Frida {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if PROSPERO
|
||||
public struct ProsperoInjectorState {
|
||||
public int fifo_fd;
|
||||
public int agent_ctrlfd;
|
||||
public Gum.MemoryRange? mapped_range;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Why spawn gating was disabled on a {@link Device}.
|
||||
*/
|
||||
|
||||
@@ -726,8 +726,7 @@ namespace Frida {
|
||||
try {
|
||||
remote_address = sc.get_remote_address ();
|
||||
} catch (GLib.Error e) {
|
||||
remove_connection (peer);
|
||||
return;
|
||||
assert_not_reached ();
|
||||
}
|
||||
|
||||
incoming (peer, remote_address);
|
||||
|
||||
@@ -1,376 +0,0 @@
|
||||
#include "frida-base.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#define FRIDA_TYPE_WINDOWS_HANDLE_STREAM (frida_windows_handle_stream_get_type ())
|
||||
#define FRIDA_TYPE_WINDOWS_HANDLE_INPUT_STREAM (frida_windows_handle_input_stream_get_type ())
|
||||
#define FRIDA_TYPE_WINDOWS_HANDLE_OUTPUT_STREAM (frida_windows_handle_output_stream_get_type ())
|
||||
|
||||
#define FRIDA_WINDOWS_HANDLE_STREAM(o) ((FridaWindowsHandleStream *) (o))
|
||||
#define FRIDA_WINDOWS_HANDLE_INPUT_STREAM(o) ((FridaWindowsHandleInputStream *) (o))
|
||||
#define FRIDA_WINDOWS_HANDLE_OUTPUT_STREAM(o) ((FridaWindowsHandleOutputStream *) (o))
|
||||
|
||||
typedef struct _FridaWindowsHandleStream FridaWindowsHandleStream;
|
||||
typedef struct _FridaWindowsHandleStreamClass FridaWindowsHandleStreamClass;
|
||||
typedef struct _FridaWindowsHandleInputStream FridaWindowsHandleInputStream;
|
||||
typedef struct _FridaWindowsHandleInputStreamClass FridaWindowsHandleInputStreamClass;
|
||||
typedef struct _FridaWindowsHandleOutputStream FridaWindowsHandleOutputStream;
|
||||
typedef struct _FridaWindowsHandleOutputStreamClass FridaWindowsHandleOutputStreamClass;
|
||||
|
||||
struct _FridaWindowsHandleStream
|
||||
{
|
||||
GIOStream parent;
|
||||
|
||||
HANDLE handle;
|
||||
HANDLE read_complete;
|
||||
HANDLE read_cancel;
|
||||
HANDLE write_complete;
|
||||
HANDLE write_cancel;
|
||||
|
||||
GInputStream * input;
|
||||
GOutputStream * output;
|
||||
};
|
||||
|
||||
struct _FridaWindowsHandleStreamClass
|
||||
{
|
||||
GIOStreamClass parent_class;
|
||||
};
|
||||
|
||||
struct _FridaWindowsHandleInputStream
|
||||
{
|
||||
GInputStream parent;
|
||||
|
||||
FridaWindowsHandleStream * stream;
|
||||
};
|
||||
|
||||
struct _FridaWindowsHandleInputStreamClass
|
||||
{
|
||||
GInputStreamClass parent_class;
|
||||
};
|
||||
|
||||
struct _FridaWindowsHandleOutputStream
|
||||
{
|
||||
GOutputStream parent;
|
||||
|
||||
FridaWindowsHandleStream * stream;
|
||||
};
|
||||
|
||||
struct _FridaWindowsHandleOutputStreamClass
|
||||
{
|
||||
GOutputStreamClass parent_class;
|
||||
};
|
||||
|
||||
static GIOStream * frida_windows_handle_stream_new (HANDLE handle);
|
||||
|
||||
static void frida_windows_handle_stream_finalize (GObject * object);
|
||||
static GInputStream * frida_windows_handle_stream_get_input_stream (GIOStream * stream);
|
||||
static GOutputStream * frida_windows_handle_stream_get_output_stream (GIOStream * stream);
|
||||
static gboolean frida_windows_handle_stream_close_fn (GIOStream * stream, GCancellable * cancellable, GError ** error);
|
||||
|
||||
static gssize frida_windows_handle_input_stream_read (GInputStream * base, void * buffer, gsize count,
|
||||
GCancellable * cancellable, GError ** error);
|
||||
static gboolean frida_windows_handle_input_stream_close (GInputStream * base, GCancellable * cancellable, GError ** error);
|
||||
|
||||
static gssize frida_windows_handle_output_stream_write (GOutputStream * base, const void * buffer, gsize count,
|
||||
GCancellable * cancellable, GError ** error);
|
||||
static gboolean frida_windows_handle_output_stream_close (GOutputStream * base, GCancellable * cancellable,
|
||||
GError ** error);
|
||||
|
||||
static gboolean frida_windows_handle_stream_await (FridaWindowsHandleStream * self, HANDLE complete, HANDLE cancel,
|
||||
GCancellable * cancellable, GError ** error);
|
||||
static void frida_windows_handle_stream_on_cancel (GCancellable * cancellable, gpointer user_data);
|
||||
|
||||
G_DEFINE_TYPE (FridaWindowsHandleStream, frida_windows_handle_stream, G_TYPE_IO_STREAM)
|
||||
G_DEFINE_TYPE (FridaWindowsHandleInputStream, frida_windows_handle_input_stream, G_TYPE_INPUT_STREAM)
|
||||
G_DEFINE_TYPE (FridaWindowsHandleOutputStream, frida_windows_handle_output_stream, G_TYPE_OUTPUT_STREAM)
|
||||
|
||||
GIOStream *
|
||||
frida_windows_named_pipe_open_client (const gchar * path, GError ** error)
|
||||
{
|
||||
HANDLE handle;
|
||||
WCHAR * path_utf16;
|
||||
DWORD last_error;
|
||||
|
||||
path_utf16 = (WCHAR *) g_utf8_to_utf16 (path, -1, NULL, NULL, NULL);
|
||||
if (path_utf16 == NULL)
|
||||
goto invalid_path;
|
||||
|
||||
handle = CreateFileW (path_utf16,
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
0,
|
||||
NULL,
|
||||
OPEN_EXISTING,
|
||||
FILE_FLAG_OVERLAPPED,
|
||||
NULL);
|
||||
last_error = GetLastError ();
|
||||
g_free (path_utf16);
|
||||
|
||||
if (handle == INVALID_HANDLE_VALUE)
|
||||
goto cannot_open;
|
||||
|
||||
return frida_windows_handle_stream_new (handle);
|
||||
|
||||
invalid_path:
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_INVALID_ARGUMENT, "Invalid pipe path");
|
||||
return NULL;
|
||||
}
|
||||
cannot_open:
|
||||
{
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
g_io_error_from_win32_error (last_error),
|
||||
"Unable to open %s (CreateFile returned 0x%08lx)",
|
||||
path, last_error);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static GIOStream *
|
||||
frida_windows_handle_stream_new (HANDLE handle)
|
||||
{
|
||||
FridaWindowsHandleStream * stream;
|
||||
FridaWindowsHandleInputStream * input;
|
||||
FridaWindowsHandleOutputStream * output;
|
||||
|
||||
stream = g_object_new (FRIDA_TYPE_WINDOWS_HANDLE_STREAM, NULL);
|
||||
stream->handle = handle;
|
||||
stream->read_complete = CreateEvent (NULL, TRUE, FALSE, NULL);
|
||||
stream->read_cancel = CreateEvent (NULL, TRUE, FALSE, NULL);
|
||||
stream->write_complete = CreateEvent (NULL, TRUE, FALSE, NULL);
|
||||
stream->write_cancel = CreateEvent (NULL, TRUE, FALSE, NULL);
|
||||
|
||||
input = g_object_new (FRIDA_TYPE_WINDOWS_HANDLE_INPUT_STREAM, NULL);
|
||||
input->stream = stream;
|
||||
stream->input = G_INPUT_STREAM (input);
|
||||
|
||||
output = g_object_new (FRIDA_TYPE_WINDOWS_HANDLE_OUTPUT_STREAM, NULL);
|
||||
output->stream = stream;
|
||||
stream->output = G_OUTPUT_STREAM (output);
|
||||
|
||||
return G_IO_STREAM (stream);
|
||||
}
|
||||
|
||||
static void
|
||||
frida_windows_handle_stream_class_init (FridaWindowsHandleStreamClass * klass)
|
||||
{
|
||||
GObjectClass * object_class = G_OBJECT_CLASS (klass);
|
||||
GIOStreamClass * stream_class = G_IO_STREAM_CLASS (klass);
|
||||
|
||||
object_class->finalize = frida_windows_handle_stream_finalize;
|
||||
|
||||
stream_class->get_input_stream = frida_windows_handle_stream_get_input_stream;
|
||||
stream_class->get_output_stream = frida_windows_handle_stream_get_output_stream;
|
||||
stream_class->close_fn = frida_windows_handle_stream_close_fn;
|
||||
}
|
||||
|
||||
static void
|
||||
frida_windows_handle_stream_init (FridaWindowsHandleStream * self)
|
||||
{
|
||||
self->handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
static void
|
||||
frida_windows_handle_stream_finalize (GObject * object)
|
||||
{
|
||||
FridaWindowsHandleStream * self = FRIDA_WINDOWS_HANDLE_STREAM (object);
|
||||
|
||||
g_clear_object (&self->input);
|
||||
g_clear_object (&self->output);
|
||||
|
||||
CloseHandle (self->read_complete);
|
||||
CloseHandle (self->read_cancel);
|
||||
CloseHandle (self->write_complete);
|
||||
CloseHandle (self->write_cancel);
|
||||
|
||||
if (self->handle != INVALID_HANDLE_VALUE)
|
||||
CloseHandle (self->handle);
|
||||
|
||||
G_OBJECT_CLASS (frida_windows_handle_stream_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static GInputStream *
|
||||
frida_windows_handle_stream_get_input_stream (GIOStream * stream)
|
||||
{
|
||||
return FRIDA_WINDOWS_HANDLE_STREAM (stream)->input;
|
||||
}
|
||||
|
||||
static GOutputStream *
|
||||
frida_windows_handle_stream_get_output_stream (GIOStream * stream)
|
||||
{
|
||||
return FRIDA_WINDOWS_HANDLE_STREAM (stream)->output;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
frida_windows_handle_stream_close_fn (GIOStream * stream, GCancellable * cancellable, GError ** error)
|
||||
{
|
||||
FridaWindowsHandleStream * self = FRIDA_WINDOWS_HANDLE_STREAM (stream);
|
||||
|
||||
SetEvent (self->read_cancel);
|
||||
SetEvent (self->write_cancel);
|
||||
|
||||
if (self->handle != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CancelIo (self->handle);
|
||||
CloseHandle (self->handle);
|
||||
self->handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
frida_windows_handle_input_stream_class_init (FridaWindowsHandleInputStreamClass * klass)
|
||||
{
|
||||
GInputStreamClass * stream_class = G_INPUT_STREAM_CLASS (klass);
|
||||
|
||||
stream_class->read_fn = frida_windows_handle_input_stream_read;
|
||||
stream_class->close_fn = frida_windows_handle_input_stream_close;
|
||||
}
|
||||
|
||||
static void
|
||||
frida_windows_handle_input_stream_init (FridaWindowsHandleInputStream * self)
|
||||
{
|
||||
}
|
||||
|
||||
static gssize
|
||||
frida_windows_handle_input_stream_read (GInputStream * base, void * buffer, gsize count, GCancellable * cancellable,
|
||||
GError ** error)
|
||||
{
|
||||
FridaWindowsHandleStream * stream = FRIDA_WINDOWS_HANDLE_INPUT_STREAM (base)->stream;
|
||||
OVERLAPPED overlapped = { 0, };
|
||||
DWORD bytes_transferred, last_error;
|
||||
|
||||
if (stream->handle == INVALID_HANDLE_VALUE)
|
||||
return 0;
|
||||
|
||||
overlapped.hEvent = stream->read_complete;
|
||||
if (!ReadFile (stream->handle, buffer, count, NULL, &overlapped) && GetLastError () != ERROR_IO_PENDING)
|
||||
goto failure;
|
||||
|
||||
if (!frida_windows_handle_stream_await (stream, stream->read_complete, stream->read_cancel, cancellable, error))
|
||||
return -1;
|
||||
|
||||
if (!GetOverlappedResult (stream->handle, &overlapped, &bytes_transferred, FALSE))
|
||||
goto failure;
|
||||
|
||||
return bytes_transferred;
|
||||
|
||||
failure:
|
||||
{
|
||||
last_error = GetLastError ();
|
||||
if (last_error == ERROR_BROKEN_PIPE || last_error == ERROR_PIPE_NOT_CONNECTED)
|
||||
return 0;
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
g_io_error_from_win32_error (last_error),
|
||||
"Error reading from pipe");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
frida_windows_handle_input_stream_close (GInputStream * base, GCancellable * cancellable, GError ** error)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
frida_windows_handle_output_stream_class_init (FridaWindowsHandleOutputStreamClass * klass)
|
||||
{
|
||||
GOutputStreamClass * stream_class = G_OUTPUT_STREAM_CLASS (klass);
|
||||
|
||||
stream_class->write_fn = frida_windows_handle_output_stream_write;
|
||||
stream_class->close_fn = frida_windows_handle_output_stream_close;
|
||||
}
|
||||
|
||||
static void
|
||||
frida_windows_handle_output_stream_init (FridaWindowsHandleOutputStream * self)
|
||||
{
|
||||
}
|
||||
|
||||
static gssize
|
||||
frida_windows_handle_output_stream_write (GOutputStream * base, const void * buffer, gsize count,
|
||||
GCancellable * cancellable, GError ** error)
|
||||
{
|
||||
FridaWindowsHandleStream * stream = FRIDA_WINDOWS_HANDLE_OUTPUT_STREAM (base)->stream;
|
||||
OVERLAPPED overlapped = { 0, };
|
||||
DWORD bytes_transferred, last_error;
|
||||
|
||||
if (stream->handle == INVALID_HANDLE_VALUE)
|
||||
goto closed;
|
||||
|
||||
overlapped.hEvent = stream->write_complete;
|
||||
if (!WriteFile (stream->handle, buffer, count, NULL, &overlapped) && GetLastError () != ERROR_IO_PENDING)
|
||||
goto failure;
|
||||
|
||||
if (!frida_windows_handle_stream_await (stream, stream->write_complete, stream->write_cancel, cancellable, error))
|
||||
return -1;
|
||||
|
||||
if (!GetOverlappedResult (stream->handle, &overlapped, &bytes_transferred, FALSE))
|
||||
goto failure;
|
||||
|
||||
return bytes_transferred;
|
||||
|
||||
closed:
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, "Pipe is closed");
|
||||
return -1;
|
||||
}
|
||||
failure:
|
||||
{
|
||||
last_error = GetLastError ();
|
||||
g_set_error (error,
|
||||
G_IO_ERROR,
|
||||
g_io_error_from_win32_error (last_error),
|
||||
"Error writing to pipe");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
frida_windows_handle_output_stream_close (GOutputStream * base, GCancellable * cancellable, GError ** error)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
frida_windows_handle_stream_await (FridaWindowsHandleStream * self, HANDLE complete, HANDLE cancel,
|
||||
GCancellable * cancellable, GError ** error)
|
||||
{
|
||||
gulong handler_id = 0;
|
||||
HANDLE events[2];
|
||||
|
||||
if (cancellable != NULL)
|
||||
handler_id = g_cancellable_connect (cancellable, G_CALLBACK (frida_windows_handle_stream_on_cancel), cancel, NULL);
|
||||
|
||||
events[0] = complete;
|
||||
events[1] = cancel;
|
||||
WaitForMultipleObjects (G_N_ELEMENTS (events), events, FALSE, INFINITE);
|
||||
|
||||
if (cancellable != NULL)
|
||||
{
|
||||
g_cancellable_disconnect (cancellable, handler_id);
|
||||
if (g_cancellable_set_error_if_cancelled (cancellable, error))
|
||||
{
|
||||
CancelIo (self->handle);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
if (WaitForSingleObject (cancel, 0) == WAIT_OBJECT_0)
|
||||
{
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_CLOSED, "Pipe is closed");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
frida_windows_handle_stream_on_cancel (GCancellable * cancellable, gpointer user_data)
|
||||
{
|
||||
HANDLE cancel = (HANDLE) user_data;
|
||||
|
||||
SetEvent (cancel);
|
||||
}
|
||||
@@ -1,8 +1,4 @@
|
||||
namespace Frida {
|
||||
namespace WindowsNamedPipe {
|
||||
public extern IOStream open_client (string path) throws GLib.Error;
|
||||
}
|
||||
|
||||
public sealed class StdioPipes : Object {
|
||||
public OutputStream input {
|
||||
get;
|
||||
|
||||
@@ -55,10 +55,6 @@ if host_os_family in ['linux', 'qnx']
|
||||
extra_link_args += ['-Wl,-Bsymbolic']
|
||||
endif
|
||||
|
||||
if host_is_prospero
|
||||
extra_link_args += [prospero_crt_link_args, '-Wl,--defsym=main=0']
|
||||
endif
|
||||
|
||||
raw_gadget = shared_library('frida-gadget-raw', gadget_sources,
|
||||
name_prefix: 'lib',
|
||||
vala_args: [base_vala_args, payload_vala_args, extra_vala_args],
|
||||
|
||||
@@ -57,6 +57,8 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
GLib.info ("detach-trace: BaseAgentSession.close() id=%s close_request=%p", id.handle, close_request);
|
||||
|
||||
while (close_request != null) {
|
||||
try {
|
||||
yield close_request.future.wait_async (cancellable);
|
||||
@@ -85,6 +87,8 @@ namespace Frida {
|
||||
|
||||
yield transmitter.close (cancellable);
|
||||
|
||||
GLib.info ("detach-trace: BaseAgentSession.close() done id=%s", id.handle);
|
||||
|
||||
close_request.resolve (true);
|
||||
}
|
||||
|
||||
@@ -276,6 +280,8 @@ namespace Frida {
|
||||
transmitter.new_candidates.disconnect (on_transmitter_new_candidates);
|
||||
transmitter.candidate_gathering_done.disconnect (on_transmitter_candidate_gathering_done);
|
||||
|
||||
GLib.info ("detach-trace: BaseAgentSession emitting closed id=%s", id.handle);
|
||||
|
||||
closed ();
|
||||
}
|
||||
|
||||
|
||||
@@ -35,21 +35,15 @@ namespace Frida {
|
||||
interceptor.attach ((void *) Gum.Process.find_module_by_name ("kernel32.dll").find_export_by_name ("ExitProcess"),
|
||||
listener);
|
||||
#else
|
||||
var libc = Gum.Process.get_libc_module ();
|
||||
const string[] apis = {
|
||||
"exit",
|
||||
"_exit",
|
||||
"abort",
|
||||
};
|
||||
#if PROSPERO
|
||||
foreach (var symbol in apis) {
|
||||
interceptor.attach ((void *) Gum.Module.find_global_export_by_name (symbol), listener);
|
||||
}
|
||||
#else
|
||||
var libc = Gum.Process.get_libc_module ();
|
||||
foreach (var symbol in apis) {
|
||||
interceptor.attach ((void *) libc.find_export_by_name (symbol), listener);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
+5
-18
@@ -28,7 +28,6 @@
|
||||
#undef feof
|
||||
#undef ferror
|
||||
#undef fgetwc
|
||||
#undef fileno
|
||||
#undef fopen
|
||||
#undef fopen64
|
||||
#undef fputwc
|
||||
@@ -215,7 +214,6 @@ static void frida_stdio_unregister_stream (FILE * stream);
|
||||
static void frida_stdio_register_dir (DIR * dirp);
|
||||
static void frida_stdio_unregister_dir (DIR * dirp);
|
||||
#endif
|
||||
static void frida_stdio_ensure_registries (void);
|
||||
static void frida_flush_all_streams (int * result);
|
||||
|
||||
static FridaFile * frida_file_get_impl (FILE * stream);
|
||||
@@ -303,7 +301,10 @@ frida_libc_shim_init (void)
|
||||
|
||||
G_LOCK (frida_stdio);
|
||||
|
||||
frida_stdio_ensure_registries ();
|
||||
frida_streams = g_hash_table_new (g_direct_hash, g_direct_equal);
|
||||
#ifdef HAVE_FRIDA_DIR
|
||||
frida_dirs = g_hash_table_new (g_direct_hash, g_direct_equal);
|
||||
#endif
|
||||
|
||||
G_UNLOCK (frida_stdio);
|
||||
|
||||
@@ -637,7 +638,7 @@ __vsnprintf_chk (char * string, size_t size, int flags, size_t len, const char *
|
||||
return gum_vsnprintf (string, size, format, args);
|
||||
}
|
||||
|
||||
#if defined (HAVE_XLOCALE_H) && !defined (HAVE_PROSPERO)
|
||||
#ifdef HAVE_XLOCALE_H
|
||||
|
||||
G_GNUC_INTERNAL int
|
||||
sprintf_l (char * string, locale_t loc, const char * format, ...)
|
||||
@@ -1903,7 +1904,6 @@ frida_stdio_register_stream (FILE * stream)
|
||||
{
|
||||
G_LOCK (frida_stdio);
|
||||
|
||||
frida_stdio_ensure_registries ();
|
||||
g_hash_table_add (frida_streams, stream);
|
||||
|
||||
G_UNLOCK (frida_stdio);
|
||||
@@ -1926,7 +1926,6 @@ frida_stdio_register_dir (DIR * dirp)
|
||||
{
|
||||
G_LOCK (frida_stdio);
|
||||
|
||||
frida_stdio_ensure_registries ();
|
||||
g_hash_table_add (frida_dirs, dirp);
|
||||
|
||||
G_UNLOCK (frida_stdio);
|
||||
@@ -1944,18 +1943,6 @@ frida_stdio_unregister_dir (DIR * dirp)
|
||||
|
||||
#endif
|
||||
|
||||
static void
|
||||
frida_stdio_ensure_registries (void)
|
||||
{
|
||||
if (frida_streams != NULL)
|
||||
return;
|
||||
|
||||
frida_streams = g_hash_table_new (g_direct_hash, g_direct_equal);
|
||||
#ifdef HAVE_FRIDA_DIR
|
||||
frida_dirs = g_hash_table_new (g_direct_hash, g_direct_equal);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
frida_flush_all_streams (int * result)
|
||||
{
|
||||
|
||||
+2
-15
@@ -12,27 +12,14 @@ frida_pipe_get_sddl_string_for_pipe (void)
|
||||
#define DACL_ACE_EVERYONE_RW L"(A;;GRGW;;;WD)"
|
||||
#define SACL_START L"S:"
|
||||
#define SACL_ACE_LOWINTEGRITY_NORW L"(ML;;NWNR;;;LW)"
|
||||
#define DACL_ACE_SYSTEM_RW L"(A;;GRGW;;;SY)"
|
||||
#define DACL_ACE_ADMINS_RW L"(A;;GRGW;;;BA)"
|
||||
|
||||
if (frida_pipe_is_windows_8_or_greater ())
|
||||
{
|
||||
return DACL_START_NOINHERIT
|
||||
DACL_ACE_APPCONTAINER_RW
|
||||
DACL_ACE_EVERYONE_RW
|
||||
DACL_ACE_SYSTEM_RW
|
||||
DACL_ACE_ADMINS_RW
|
||||
SACL_START
|
||||
SACL_ACE_LOWINTEGRITY_NORW;
|
||||
return DACL_START_NOINHERIT DACL_ACE_APPCONTAINER_RW DACL_ACE_EVERYONE_RW SACL_START SACL_ACE_LOWINTEGRITY_NORW;
|
||||
}
|
||||
else if (frida_pipe_is_windows_vista_or_greater ())
|
||||
{
|
||||
return DACL_START_NOINHERIT
|
||||
DACL_ACE_EVERYONE_RW
|
||||
DACL_ACE_SYSTEM_RW
|
||||
DACL_ACE_ADMINS_RW
|
||||
SACL_START
|
||||
SACL_ACE_LOWINTEGRITY_NORW;
|
||||
return DACL_START_NOINHERIT DACL_ACE_EVERYONE_RW SACL_START SACL_ACE_LOWINTEGRITY_NORW;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+6
-23
@@ -273,15 +273,6 @@ if host_os != host_os_family
|
||||
cdata.set('HAVE_' + host_os.to_upper(), 1)
|
||||
endif
|
||||
|
||||
host_is_prospero = host_os_family == 'freebsd' and cc.get_define('__PROSPERO__') != ''
|
||||
prospero_crt_link_args = []
|
||||
if host_is_prospero
|
||||
cdata.set('HAVE_PROSPERO', 1)
|
||||
vala_flags += '--define=PROSPERO'
|
||||
prospero_crt_link_args += \
|
||||
run_command(cc.cmd_array(), '-print-file-name=crt1.o', check: true).stdout().strip()
|
||||
endif
|
||||
|
||||
cpu_defines = [
|
||||
['x86', 'HAVE_I386'],
|
||||
['x86_64', 'HAVE_I386'],
|
||||
@@ -536,7 +527,7 @@ have_droidy_backend = get_option('droidy_backend') \
|
||||
have_socket_backend = get_option('socket_backend').allowed()
|
||||
have_barebone_backend = get_option('barebone_backend') \
|
||||
.disable_if(not (quickjs_dep.found() and lzfse_dep.found())) \
|
||||
.disable_auto_if(not host_docks_mobile_devices and host_os != 'android') \
|
||||
.disable_auto_if(not host_docks_mobile_devices) \
|
||||
.allowed()
|
||||
compiler_backend_opt = get_option('compiler_backend') \
|
||||
.disable_auto_if(host_os == 'watchos')
|
||||
@@ -604,9 +595,9 @@ if compiler_backend_opt.allowed()
|
||||
endif
|
||||
endif
|
||||
elif compiler_backend_opt.enabled()
|
||||
error('Need Go >= 1.26 to build the Compiler backend powered by ESBuild and typescript-go')
|
||||
error('Need Go >= 1.24 to build the Compiler backend powered by ESBuild and typescript-go')
|
||||
else
|
||||
warning('Building without Compiler backend: Go >= 1.26 not found')
|
||||
warning('Building without Compiler backend: Go >= 1.24 not found')
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -766,9 +757,7 @@ if host_os_family == 'linux' and have_local_backend
|
||||
backend_reqs_private += 'libbpf'
|
||||
endif
|
||||
|
||||
if host_os_family == 'darwin' \
|
||||
or (host_os_family == 'freebsd' and not host_is_prospero) \
|
||||
or (host_os_family == 'linux' and cc.has_function('openpty', prefix: '#include <pty.h>'))
|
||||
if host_os_family in ['darwin', 'freebsd'] or (host_os_family == 'linux' and cc.has_function('openpty', prefix: '#include <pty.h>'))
|
||||
vala_flags += '--define=HAVE_OPENPTY'
|
||||
endif
|
||||
|
||||
@@ -890,19 +879,13 @@ if host_os_family == 'darwin'
|
||||
endif
|
||||
if host_os == 'macos'
|
||||
backend_libs_private += ['-Wl,-framework,AppKit']
|
||||
if have_compiler_backend
|
||||
backend_libs_private += ['-Wl,-framework,CoreServices']
|
||||
endif
|
||||
endif
|
||||
if host_os in ['ios', 'tvos']
|
||||
backend_libs_private += ['-Wl,-framework,CoreGraphics', '-Wl,-framework,UIKit']
|
||||
endif
|
||||
if host_os_family == 'freebsd' and not host_is_prospero
|
||||
if host_os_family == 'freebsd'
|
||||
backend_libs_private += ['-lutil']
|
||||
endif
|
||||
if host_is_prospero
|
||||
backend_libs_private += ['-lSceSystemService', '-lSceUserService']
|
||||
endif
|
||||
|
||||
if get_option('devkits').length() != 0
|
||||
if get_option('default_library') != 'static'
|
||||
@@ -969,7 +952,7 @@ if host_os_family == 'darwin'
|
||||
executable_no_exports_link_args = ['-Wl,-exported_symbols_list,' + script]
|
||||
executable_no_exports_link_depends = [script]
|
||||
elif host_os_family != 'windows'
|
||||
script_name = (host_os == 'freebsd' and not host_is_prospero) ? 'executable-no-exports-freebsd.version' : 'executable-no-exports.version'
|
||||
script_name = (host_os == 'freebsd') ? 'executable-no-exports-freebsd.version' : 'executable-no-exports.version'
|
||||
script = meson.project_source_root() / 'tools' / script_name
|
||||
executable_no_exports_link_args = ['-Wl,--version-script,' + script]
|
||||
executable_no_exports_link_depends = [script]
|
||||
|
||||
+1
-1
Submodule releng updated: 1089fe24c2...abb8238970.
+1
-1
@@ -19,7 +19,7 @@ if host_os_family == 'darwin'
|
||||
extra_link_args += ['-Wl,-exported_symbols_list,' + meson.current_source_dir() / symlist]
|
||||
extra_link_depends += [symlist]
|
||||
elif host_os_family != 'windows'
|
||||
symscript = (host_os == 'freebsd' and not host_is_prospero) ? 'frida-server-freebsd.version' : 'frida-server.version'
|
||||
symscript = (host_os == 'freebsd') ? 'frida-server-freebsd.version' : 'frida-server.version'
|
||||
extra_link_args += ['-Wl,--version-script,' + meson.current_source_dir() / symscript]
|
||||
extra_link_depends += [symscript]
|
||||
endif
|
||||
|
||||
@@ -29,8 +29,6 @@ void CFLog (CFLogLevel level, CFStringRef format, ...);
|
||||
|
||||
#elif defined (HAVE_ANDROID)
|
||||
# include <android/log.h>
|
||||
#elif defined (HAVE_PROSPERO)
|
||||
# include <ps5/klog.h>
|
||||
#else
|
||||
# include <stdio.h>
|
||||
#endif
|
||||
@@ -139,13 +137,6 @@ frida_server_on_log_message (const gchar * log_domain, GLogLevelFlags log_level,
|
||||
}
|
||||
|
||||
__android_log_write (priority, log_domain, message);
|
||||
#elif defined (HAVE_PROSPERO)
|
||||
(void) user_data;
|
||||
|
||||
if (log_domain != NULL)
|
||||
klog_printf ("%s: %s\n", log_domain, message);
|
||||
else
|
||||
klog_printf ("%s\n", message);
|
||||
#else
|
||||
FILE * file = NULL;
|
||||
const gchar * severity = NULL;
|
||||
|
||||
+87
-4
@@ -12,6 +12,7 @@ namespace Frida.Server {
|
||||
private static string? directory = null;
|
||||
#if !WINDOWS && !TVOS
|
||||
private static bool daemonize = false;
|
||||
private static string? output_path = null;
|
||||
#endif
|
||||
private static string? softener_flavor_str = null;
|
||||
private static bool enable_preload = true;
|
||||
@@ -42,6 +43,7 @@ namespace Frida.Server {
|
||||
{ "directory", 'd', 0, OptionArg.STRING, ref directory, "Store binaries in DIRECTORY", "DIRECTORY" },
|
||||
#if !WINDOWS && !TVOS
|
||||
{ "daemonize", 'D', 0, OptionArg.NONE, ref daemonize, "Detach and become a daemon", null },
|
||||
{ "output", 'o', 0, OptionArg.FILENAME, ref output_path, "Redirect output to FILE", "FILE" },
|
||||
#endif
|
||||
{ "policy-softener", 0, 0, OptionArg.STRING, ref softener_flavor_str, "Select policy softener", "system|internal" },
|
||||
{ "disable-preload", 'P', OptionFlags.REVERSE, OptionArg.NONE, ref enable_preload, "Disable preload optimization", null },
|
||||
@@ -78,6 +80,13 @@ namespace Frida.Server {
|
||||
|
||||
Environment.set_verbose_logging_enabled (verbose);
|
||||
|
||||
#if !WINDOWS && !TVOS
|
||||
if (output_path != null && !daemonize) {
|
||||
if (!redirect_output_to (output_path))
|
||||
return 6;
|
||||
}
|
||||
#endif
|
||||
|
||||
EndpointParameters endpoint_params;
|
||||
try {
|
||||
endpoint_params = new EndpointParameters (listen_address, 0, parse_certificate (certpath), origin,
|
||||
@@ -156,12 +165,15 @@ namespace Frida.Server {
|
||||
Posix.setsid ();
|
||||
|
||||
var null_in = Posix.open ("/dev/null", Posix.O_RDONLY);
|
||||
var null_out = Posix.open ("/dev/null", Posix.O_WRONLY);
|
||||
Posix.dup2 (null_in, Posix.STDIN_FILENO);
|
||||
Posix.dup2 (null_out, Posix.STDOUT_FILENO);
|
||||
Posix.dup2 (null_out, Posix.STDERR_FILENO);
|
||||
Posix.close (null_in);
|
||||
Posix.close (null_out);
|
||||
|
||||
if (output_path == null || !redirect_output_to (output_path)) {
|
||||
var null_out = Posix.open ("/dev/null", Posix.O_WRONLY);
|
||||
Posix.dup2 (null_out, Posix.STDOUT_FILENO);
|
||||
Posix.dup2 (null_out, Posix.STDERR_FILENO);
|
||||
Posix.close (null_out);
|
||||
}
|
||||
}
|
||||
|
||||
var status = new uint8[1];
|
||||
@@ -218,6 +230,22 @@ namespace Frida.Server {
|
||||
return application.run ();
|
||||
}
|
||||
|
||||
#if !WINDOWS && !TVOS
|
||||
private static bool redirect_output_to (string path) {
|
||||
int fd = Posix.open (path, Posix.O_WRONLY | Posix.O_CREAT | Posix.O_APPEND, 0644);
|
||||
if (fd == -1) {
|
||||
printerr ("Unable to open “%s”: %s\n", path, Posix.strerror (Posix.errno));
|
||||
return false;
|
||||
}
|
||||
|
||||
Posix.dup2 (fd, Posix.STDOUT_FILENO);
|
||||
Posix.dup2 (fd, Posix.STDERR_FILENO);
|
||||
Posix.close (fd);
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace Environment {
|
||||
public extern void init ();
|
||||
public extern void set_verbose_logging_enabled (bool enabled);
|
||||
@@ -274,6 +302,15 @@ namespace Frida.Server {
|
||||
return false;
|
||||
});
|
||||
|
||||
#if !WINDOWS && !TVOS
|
||||
if (!daemonize)
|
||||
process_commands.begin ();
|
||||
#elif !WINDOWS
|
||||
process_commands.begin ();
|
||||
#endif
|
||||
|
||||
start_alive_task_dumper ();
|
||||
|
||||
exit_code = 0;
|
||||
|
||||
loop.run ();
|
||||
@@ -281,6 +318,45 @@ namespace Frida.Server {
|
||||
return exit_code;
|
||||
}
|
||||
|
||||
#if !WINDOWS
|
||||
// TEMPORARY DEBUG HACK: type “t” + Enter to dump alive GTasks.
|
||||
private async void process_commands () {
|
||||
var input = new DataInputStream (new UnixInputStream (Posix.STDIN_FILENO, false));
|
||||
|
||||
while (true) {
|
||||
string? line;
|
||||
try {
|
||||
line = yield input.read_line_async (Priority.DEFAULT, io_cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
return;
|
||||
}
|
||||
if (line == null)
|
||||
return;
|
||||
|
||||
switch (line.strip ()) {
|
||||
case "t":
|
||||
g_task_print_alive_tasks ();
|
||||
break;
|
||||
case "":
|
||||
break;
|
||||
default:
|
||||
printerr ("Unknown command; try “t”\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// TEMPORARY DEBUG HACK: dump alive GTasks every 30 seconds.
|
||||
private void start_alive_task_dumper () {
|
||||
var source = new TimeoutSource.seconds (30);
|
||||
source.set_callback (() => {
|
||||
GLib.info ("%s", g_task_describe_alive_tasks ());
|
||||
return Source.CONTINUE;
|
||||
});
|
||||
source.attach (MainContext.default ());
|
||||
}
|
||||
|
||||
private async void start () {
|
||||
try {
|
||||
if (device_id != null && device_id != "local") {
|
||||
@@ -355,4 +431,11 @@ namespace Frida.Server {
|
||||
|
||||
return new TlsCertificate.from_file (path);
|
||||
}
|
||||
|
||||
// TEMPORARY DEBUG HACK: only present when GLib is built with G_ENABLE_DEBUG.
|
||||
[CCode (cname = "g_task_print_alive_tasks")]
|
||||
private extern void g_task_print_alive_tasks ();
|
||||
|
||||
[CCode (cname = "g_task_describe_alive_tasks")]
|
||||
private extern string g_task_describe_alive_tasks ();
|
||||
}
|
||||
@@ -5,92 +5,10 @@ namespace Frida {
|
||||
construct;
|
||||
}
|
||||
|
||||
private AdbShellSession? shell;
|
||||
|
||||
public AndroidHelperClient (AndroidHelperTransport transport) {
|
||||
Object (transport: transport);
|
||||
}
|
||||
|
||||
public static async AndroidHelperClient open (string serial, Cancellable? cancellable) throws Error, IOError {
|
||||
string instance_id = Uuid.string_random ().replace ("-", "");
|
||||
string helper_path = "/data/local/tmp/frida-helper-" + instance_id + ".dex";
|
||||
|
||||
var helper_dex = new MemoryInputStream.from_bytes (
|
||||
new Bytes.static (Frida.Data.Android.get_helper_dex_blob ().data));
|
||||
var helper_meta = new AdbFileMetadata ();
|
||||
helper_meta.mode = 0100644;
|
||||
helper_meta.time_modified = new DateTime.now_utc ();
|
||||
yield AdbFileSync.send (helper_dex, helper_meta, helper_path, serial, cancellable);
|
||||
|
||||
var shell = new AdbShellSession ();
|
||||
try {
|
||||
var output = new StringBuilder ();
|
||||
bool waiting = false;
|
||||
var output_handler = shell.output.connect ((pipe, bytes) => {
|
||||
if (pipe == STDOUT) {
|
||||
output.append ((string) bytes.get_data ());
|
||||
if (waiting)
|
||||
open.callback ();
|
||||
}
|
||||
});
|
||||
try {
|
||||
yield shell.open (serial, cancellable);
|
||||
|
||||
shell.send_command (("CLASSPATH=%s app_process /data/local/tmp " +
|
||||
"--nice-name=re.frida.helper re.frida.Helper %s; rm -f %s; echo BYE.")
|
||||
.printf (helper_path, instance_id, helper_path));
|
||||
|
||||
while (!output.str.has_prefix ("READY.\n")) {
|
||||
waiting = true;
|
||||
yield;
|
||||
waiting = false;
|
||||
|
||||
if (output.str.has_prefix ("BYE.\n"))
|
||||
throw new Error.NOT_SUPPORTED ("Unable to start helper");
|
||||
}
|
||||
} finally {
|
||||
shell.disconnect (output_handler);
|
||||
}
|
||||
|
||||
var client = yield AdbClient.open (cancellable);
|
||||
try {
|
||||
yield client.request ("host:transport:" + serial, cancellable);
|
||||
yield client.request_protocol_change ("localabstract:/frida-helper-" + instance_id,
|
||||
cancellable);
|
||||
} catch (GLib.Error e) {
|
||||
client.close.begin ();
|
||||
throw e;
|
||||
}
|
||||
|
||||
var helper = new AndroidHelperClient (new AndroidHelperStreamTransport (client.stream));
|
||||
helper.shell = shell;
|
||||
helper.transport.closed.connect (helper.release_shell);
|
||||
|
||||
return helper;
|
||||
} catch (GLib.Error e) {
|
||||
shell.close.begin ();
|
||||
if (e is IOError.CANCELLED)
|
||||
throw (IOError) e;
|
||||
throw new Error.NOT_SUPPORTED ("%s", e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async void close (Cancellable? cancellable) throws IOError {
|
||||
yield transport.close (cancellable);
|
||||
|
||||
if (shell != null) {
|
||||
yield shell.close (cancellable);
|
||||
shell = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void release_shell () {
|
||||
if (shell != null) {
|
||||
shell.close.begin ();
|
||||
shell = null;
|
||||
}
|
||||
}
|
||||
|
||||
public async HostApplicationInfo get_frontmost_application (FrontmostQueryOptions options,
|
||||
Cancellable? cancellable) throws Error, IOError {
|
||||
var stanza = new Json.Builder ();
|
||||
@@ -490,13 +408,13 @@ namespace Frida {
|
||||
}
|
||||
|
||||
public interface AndroidHelperTransport : Object {
|
||||
public signal void closed ();
|
||||
|
||||
public abstract async void close (Cancellable? cancellable) throws IOError;
|
||||
public abstract async Json.Node request (Json.Node stanza, Cancellable? cancellable) throws Error, IOError;
|
||||
}
|
||||
|
||||
public sealed class AndroidHelperStreamTransport : Object, AndroidHelperTransport {
|
||||
public signal void closed ();
|
||||
|
||||
public IOStream stream {
|
||||
get;
|
||||
construct;
|
||||
|
||||
Binary file not shown.
@@ -645,7 +645,7 @@ public class HelperBackend {
|
||||
"Unable to find application with identifier '" + pkgName + "'" +
|
||||
((uid != 0) ? " belonging to uid " + uid : ""));
|
||||
} catch (Throwable e) {
|
||||
return error("NOT_SUPPORTED", describe(e));
|
||||
return error("NOT_SUPPORTED", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -697,13 +697,10 @@ public class HelperBackend {
|
||||
intent.setClassName(pkgName, activity);
|
||||
}
|
||||
|
||||
String callingPackage = ourCallingPackage();
|
||||
|
||||
int code = (mStartActivityAsUser != null)
|
||||
? startActivityViaAtm(intent, uid, callingPackage)
|
||||
: startActivityViaAm(intent, uid, callingPackage);
|
||||
if (code < 0 || code > 3)
|
||||
return error("NOT_SUPPORTED", "Activity launch was refused (start code " + code + ")");
|
||||
if (mStartActivityAsUser != null)
|
||||
startActivityViaAtm(intent, uid, pkgName);
|
||||
else
|
||||
startActivityViaAm(intent, uid, pkgName);
|
||||
|
||||
return okVoid();
|
||||
} catch (NameNotFoundException e) {
|
||||
@@ -711,18 +708,11 @@ public class HelperBackend {
|
||||
"Unable to find application with identifier '" + pkgName + "'" +
|
||||
((uid != 0) ? " belonging to uid " + uid : ""));
|
||||
} catch (Throwable e) {
|
||||
return error("NOT_SUPPORTED", describe(e));
|
||||
return error("NOT_SUPPORTED", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private String ourCallingPackage() {
|
||||
String[] packages = mPackageManager.getPackagesForUid(Process.myUid());
|
||||
if (packages != null && packages.length > 0)
|
||||
return packages[0];
|
||||
return "com.android.shell";
|
||||
}
|
||||
|
||||
private int startActivityViaAtm(Intent intent, int uid, String callingPackage) throws Exception {
|
||||
private void startActivityViaAtm(Intent intent, int uid, String callingPackage) throws Exception {
|
||||
if (mStartActivityAsUser == null)
|
||||
throw new UnsupportedOperationException("startActivityAsUser unavailable");
|
||||
|
||||
@@ -739,11 +729,10 @@ public class HelperBackend {
|
||||
args[mStartActivityCallingPackageIndex] = callingPackage;
|
||||
args[mStartActivityUserIdIndex] = (uid != 0) ? uid : 0;
|
||||
|
||||
Object result = mStartActivityAsUser.invoke(mActivityTaskManager, args);
|
||||
return (result instanceof Integer) ? (Integer) result : 0;
|
||||
mStartActivityAsUser.invoke(mActivityTaskManager, args);
|
||||
}
|
||||
|
||||
private int startActivityViaAm(Intent intent, int uid, String callingPackage) throws Exception {
|
||||
private void startActivityViaAm(Intent intent, int uid, String callingPackage) throws Exception {
|
||||
if (mStartActivityLegacy == null)
|
||||
throw new UnsupportedOperationException("legacy startActivity unavailable");
|
||||
|
||||
@@ -764,8 +753,7 @@ public class HelperBackend {
|
||||
if (mLegacyStartUserIdIndex != -1)
|
||||
args[mLegacyStartUserIdIndex] = (uid != 0) ? uid : 0;
|
||||
|
||||
Object result = mStartActivityLegacy.invoke(mActivityManagerService, args);
|
||||
return (result instanceof Integer) ? (Integer) result : 0;
|
||||
mStartActivityLegacy.invoke(mActivityManagerService, args);
|
||||
}
|
||||
|
||||
private JSONArray sendBroadcast(JSONArray request) throws JSONException {
|
||||
@@ -794,7 +782,7 @@ public class HelperBackend {
|
||||
"Unable to find application with identifier '" + pkgName + "'" +
|
||||
((uid != 0) ? " belonging to uid " + uid : ""));
|
||||
} catch (Throwable e) {
|
||||
return error("NOT_SUPPORTED", describe(e));
|
||||
return error("NOT_SUPPORTED", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -822,7 +810,7 @@ public class HelperBackend {
|
||||
"Unable to find application with identifier '" + pkgName + "'" +
|
||||
((uid != 0) ? " belonging to uid " + uid : ""));
|
||||
} catch (Throwable e) {
|
||||
return error("NOT_SUPPORTED", describe(e));
|
||||
return error("NOT_SUPPORTED", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -849,7 +837,7 @@ public class HelperBackend {
|
||||
|
||||
return okBoolean(false);
|
||||
} catch (Throwable e) {
|
||||
return error("NOT_SUPPORTED", describe(e));
|
||||
return error("NOT_SUPPORTED", e.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -881,17 +869,6 @@ public class HelperBackend {
|
||||
return r;
|
||||
}
|
||||
|
||||
private static String describe(Throwable t) {
|
||||
Throwable cause = t;
|
||||
while (cause instanceof InvocationTargetException && cause.getCause() != null)
|
||||
cause = cause.getCause();
|
||||
|
||||
String message = cause.getMessage();
|
||||
return (message != null)
|
||||
? cause.getClass().getName() + ": " + message
|
||||
: cause.getClass().getName();
|
||||
}
|
||||
|
||||
private String getFrontmostPackageName() {
|
||||
List<ActivityManager.RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
|
||||
if (tasks.isEmpty()) {
|
||||
|
||||
+11
-87
@@ -21,19 +21,10 @@ CORE_TAG_FIELD = f"{{{CORE_NAMESPACE}}}field"
|
||||
CORE_TAG_CONSTRUCTOR = f"{{{CORE_NAMESPACE}}}constructor"
|
||||
CORE_TAG_METHOD = f"{{{CORE_NAMESPACE}}}method"
|
||||
|
||||
INTERNAL_INTERFACES = {
|
||||
"Frida.HostSessionHub",
|
||||
"Frida.AgentMessageSink",
|
||||
"FridaBase.AgentMessageSink",
|
||||
"Json.Serializable",
|
||||
}
|
||||
|
||||
OBJECT_TYPE_PATTERN = re.compile(r"\bpublic\s+(sealed |abstract )?(class|interface)\s+(\w+)\b")
|
||||
OBJECT_TYPE_PATTERN = re.compile(r"\bpublic\s+(sealed )?(class|interface)\s+(\w+)\b")
|
||||
|
||||
TOPLEVEL_NAMES = [
|
||||
"frida.vala",
|
||||
"linux-kernel-image.vala",
|
||||
"xnu-kernelcache.vala",
|
||||
"package-manager.vala",
|
||||
"control-service.vala",
|
||||
"portal-service.vala",
|
||||
@@ -140,7 +131,6 @@ def emit_header(api, output_dir):
|
||||
|
||||
output_header_file.write("\n\n/* Library lifetime */")
|
||||
output_header_file.write("\nvoid frida_init (void);")
|
||||
output_header_file.write("\nvoid frida_init_with_runtime (FridaRuntime runtime);")
|
||||
output_header_file.write("\nvoid frida_shutdown (void);")
|
||||
output_header_file.write("\nvoid frida_deinit (void);")
|
||||
output_header_file.write("\nGMainContext * frida_get_main_context (void);")
|
||||
@@ -240,10 +230,10 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path, docs:
|
||||
for elem in core_elements + base_elements:
|
||||
if tag_name == "class":
|
||||
for child in list(elem):
|
||||
if (child.tag == CORE_TAG_IMPLEMENTS and child.get("name") in INTERNAL_INTERFACES) \
|
||||
if (child.tag == CORE_TAG_IMPLEMENTS and child.get("name") in {"Frida.HostSessionHub", "Frida.AgentMessageSink",
|
||||
"FridaBase.AgentMessageSink"}) \
|
||||
or child.tag == CORE_TAG_FIELD \
|
||||
or child.get("name").startswith("_") \
|
||||
or (child.tag == CORE_TAG_CONSTRUCTOR and is_async_constructor(child)):
|
||||
or child.get("name").startswith("_"):
|
||||
elem.remove(child)
|
||||
merged_namespace.append(elem)
|
||||
|
||||
@@ -251,13 +241,6 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path, docs:
|
||||
merge_and_transform_elements("interface", object_type_names)
|
||||
merge_and_transform_elements("enumeration", enum_type_names | error_type_names)
|
||||
|
||||
function_names = {func.name for func in api.functions}
|
||||
for source_root in [core_root, base_root]:
|
||||
source_namespace = source_root.find("namespace", GIR_NAMESPACES)
|
||||
for function in source_namespace.findall("function", GIR_NAMESPACES):
|
||||
if function.get("name") in function_names:
|
||||
merged_namespace.append(function)
|
||||
|
||||
for source_root in [core_root, base_root]:
|
||||
for record in source_root.findall(".//record", GIR_NAMESPACES):
|
||||
owner = record.get(f"{{{GLIB_NAMESPACE}}}is-gtype-struct-for")
|
||||
@@ -294,12 +277,6 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path, docs:
|
||||
def filter_elements(elements: List[ET.Element], spec_set: Set[str]):
|
||||
return [elem for elem in elements if elem.get("name") in spec_set]
|
||||
|
||||
def is_async_constructor(constructor: ET.Element) -> bool:
|
||||
for type_elem in constructor.iter(f"{{{CORE_NAMESPACE}}}type"):
|
||||
if type_elem.get("name") == "Gio.Cancellable":
|
||||
return True
|
||||
return False
|
||||
|
||||
@dataclass
|
||||
class DocComment:
|
||||
body: str
|
||||
@@ -627,14 +604,10 @@ def emit_vapi(api, output_dir):
|
||||
output_vapi_file.write("[CCode (cheader_filename = \"frida-core.h\", cprefix = \"Frida\", lower_case_cprefix = \"frida_\")]")
|
||||
output_vapi_file.write("\nnamespace Frida {")
|
||||
output_vapi_file.write("\n\tpublic static void init ();")
|
||||
output_vapi_file.write("\n\tpublic static void init_with_runtime (Frida.Runtime runtime);")
|
||||
output_vapi_file.write("\n\tpublic static void shutdown ();")
|
||||
output_vapi_file.write("\n\tpublic static void deinit ();")
|
||||
output_vapi_file.write("\n\tpublic static unowned GLib.MainContext get_main_context ();")
|
||||
|
||||
for delegate in referenced_delegates(api):
|
||||
output_vapi_file.write("\n\t" + delegate)
|
||||
|
||||
for object_type in api.object_types:
|
||||
output_vapi_file.write("\n\n\t%s" % object_type.vapi_declaration)
|
||||
sections = []
|
||||
@@ -669,35 +642,6 @@ def emit_vapi(api, output_dir):
|
||||
output_deps_file.write("glib-2.0\n")
|
||||
output_deps_file.write("gobject-2.0\n")
|
||||
output_deps_file.write("gio-2.0\n")
|
||||
output_deps_file.write("json-glib-1.0\n")
|
||||
|
||||
def referenced_delegates(api) -> List[str]:
|
||||
members = []
|
||||
for object_type in api.object_types:
|
||||
members.extend(object_type.vapi_properties)
|
||||
members.extend(object_type.vapi_methods)
|
||||
members.extend(object_type.vapi_signals)
|
||||
if object_type.vapi_constructor is not None:
|
||||
members.append(object_type.vapi_constructor)
|
||||
for func in api.functions:
|
||||
members.append(func.vapi_declaration)
|
||||
text = "\n".join(members)
|
||||
|
||||
return [declaration for name, declaration in api.delegates
|
||||
if re.search(r"\bFrida\." + name + r"\b", text) is not None]
|
||||
|
||||
def strip_internal_interfaces(declaration: str) -> str:
|
||||
head, sep, rest = declaration.partition(" : ")
|
||||
if not sep:
|
||||
return declaration
|
||||
|
||||
bases_part, brace_sep, tail = rest.partition("{")
|
||||
|
||||
kept = [b.strip() for b in bases_part.split(",") if b.strip() not in INTERNAL_INTERFACES]
|
||||
if not kept:
|
||||
return f"{head} {brace_sep}{tail}"
|
||||
|
||||
return f"{head} : {', '.join(kept)} {brace_sep}{tail}"
|
||||
|
||||
def emit_symbol_maps_from_source(output_dir: Path):
|
||||
with OutputFile(output_dir / 'frida-core.version') as f:
|
||||
@@ -856,7 +800,8 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
|
||||
if stripped_line == "}":
|
||||
ignoring = False
|
||||
else:
|
||||
if stripped_line.startswith("public class Promise") \
|
||||
if stripped_line.startswith("public abstract") \
|
||||
or stripped_line.startswith("public class Promise") \
|
||||
or stripped_line.startswith("public interface Future") \
|
||||
or stripped_line.startswith("public class CF"):
|
||||
ignoring = True
|
||||
@@ -876,7 +821,7 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
|
||||
ignoring = True
|
||||
else:
|
||||
current_object_type = object_type_by_name[name]
|
||||
current_object_type.vapi_declaration = strip_internal_interfaces(stripped_line)
|
||||
current_object_type.vapi_declaration = stripped_line
|
||||
elif stripped_line == "}":
|
||||
current_enum = None
|
||||
current_object_type = None
|
||||
@@ -905,26 +850,17 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
|
||||
enum.vapi_declaration = m.group(1)
|
||||
enum.vapi_members.extend([line.lstrip() for line in m.group(2).strip().split("\n")])
|
||||
|
||||
functions = [f for f in parse_vapi_functions(core_vapi + base_vapi) if function_is_public(f.name)]
|
||||
functions = [f for f in parse_vapi_functions(base_vapi) if function_is_public(f.name)]
|
||||
for f in functions:
|
||||
m = re.search(r"^(?:VALA_EXTERN )?[\w\*]+ frida_{}.+?;".format(f.name), all_headers, re.MULTILINE | re.DOTALL)
|
||||
prototype = beautify_cprototype(m.group(0))
|
||||
if prototype.startswith("VALA_EXTERN "):
|
||||
prototype = prototype[12:]
|
||||
f.c_prototype = prototype
|
||||
m = re.search(r"^[\w\*]+ frida_{}.+?;".format(f.name), all_headers, re.MULTILINE | re.DOTALL)
|
||||
f.c_prototype = beautify_cprototype(m.group(0))
|
||||
|
||||
delegates = [(m.group(1), m.group(0).strip())
|
||||
for m in re.finditer(r"^\tpublic delegate .+?\b(\w+) \(.*\);$", core_vapi + base_vapi, re.MULTILINE)]
|
||||
|
||||
return ApiSpec(frida_version, frida_version_components, api_version, object_types, functions, enum_types, error_types,
|
||||
delegates)
|
||||
return ApiSpec(frida_version, frida_version_components, api_version, object_types, functions, enum_types, error_types)
|
||||
|
||||
def function_is_public(name):
|
||||
return not name.startswith("_") and \
|
||||
not name.startswith("throw_") and \
|
||||
name not in MANUALLY_DECLARED_FUNCTIONS and \
|
||||
name not in {
|
||||
"on_pending_garbage",
|
||||
"generate_certificate",
|
||||
"get_dbus_context",
|
||||
"invalidate_dbus_context",
|
||||
@@ -945,17 +881,6 @@ def function_is_public(name):
|
||||
"make_stdio_pipe",
|
||||
}
|
||||
|
||||
MANUALLY_DECLARED_FUNCTIONS = {
|
||||
"init",
|
||||
"init_with_runtime",
|
||||
"shutdown",
|
||||
"deinit",
|
||||
"get_main_context",
|
||||
"unref",
|
||||
"version",
|
||||
"version_string",
|
||||
}
|
||||
|
||||
def parse_vala_object_types(source) -> List[ApiObjectType]:
|
||||
return [ApiObjectType(m.group(3), m.group(2)) for m in OBJECT_TYPE_PATTERN.finditer(source, re.MULTILINE)]
|
||||
|
||||
@@ -971,7 +896,6 @@ class ApiSpec:
|
||||
functions: List[ApiFunction]
|
||||
enum_types: List[ApiEnum]
|
||||
error_types: List[ApiEnum]
|
||||
delegates: List[tuple]
|
||||
|
||||
class ApiEnum:
|
||||
def __init__(self, name):
|
||||
|
||||
+1
-8
@@ -132,20 +132,13 @@ if not is_shared
|
||||
endif
|
||||
core_public_dep = declare_dependency(kwargs: core_public_dep_kwargs)
|
||||
|
||||
core_public_reqs = ['glib-2.0', 'gobject-2.0', 'gio-2.0', 'json-glib-1.0']
|
||||
if host_os_family == 'windows'
|
||||
core_public_reqs += 'gio-windows-2.0'
|
||||
else
|
||||
core_public_reqs += 'gio-unix-2.0'
|
||||
endif
|
||||
|
||||
pkg = import('pkgconfig')
|
||||
pkg_kwargs = {
|
||||
'filebase': f'frida-core-@api_version@',
|
||||
'name': 'Frida Core Library',
|
||||
'version': frida_version,
|
||||
'description': 'Instrumentation toolkit for developers, reverse-engineers, and security researchers',
|
||||
'requires': core_public_reqs,
|
||||
'requires': ['glib-2.0', 'gobject-2.0', 'gio-2.0', 'json-glib-1.0'],
|
||||
'subdirs': f'frida-@api_version@',
|
||||
'extra_cflags': ['-I${frida_includedir}'],
|
||||
'variables': installed_variables,
|
||||
|
||||
+144
-1004
File diff suppressed because it is too large.
Load diff
Generated
+4
-4
@@ -13,9 +13,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bindgen"
|
||||
version = "0.72.1"
|
||||
version = "0.72.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||
checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cexpr",
|
||||
@@ -63,9 +63,9 @@ checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.9.1"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
|
||||
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"libc",
|
||||
|
||||
@@ -4,31 +4,6 @@ version = "1.0.0"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
[lib]
|
||||
name = "frida_barebone_agent"
|
||||
path = "src/lib.rs"
|
||||
crate-type = ["rlib", "staticlib"]
|
||||
|
||||
# The XNU flavour is a freestanding executable the host injects and calls into.
|
||||
# The Linux flavour is the staticlib above, linked into a .ko by linux/Kbuild.
|
||||
[[bin]]
|
||||
name = "frida-barebone-agent"
|
||||
path = "src/main.rs"
|
||||
required-features = ["blob"]
|
||||
|
||||
[features]
|
||||
# Flavors the host injects and calls into, as opposed to the module Linux loads.
|
||||
blob = []
|
||||
# The XNU flavour itself, and the two ways it is packaged: a blob the host injects
|
||||
# into a running kernel, and a kext that kernel loads.
|
||||
xnu-core = []
|
||||
xnu = ["xnu-core", "blob"]
|
||||
xnu-kext = ["xnu-core"]
|
||||
win9x = ["blob"]
|
||||
winnt = ["blob"]
|
||||
linux = []
|
||||
linux-injected = ["blob"]
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
opt-level = "z"
|
||||
|
||||
@@ -1,81 +1,30 @@
|
||||
## Getting the SDK and the devkit
|
||||
## Getting the C toolchain
|
||||
|
||||
The agent is built against the bare soft-float SDK, which carries picolibc and the
|
||||
compiler runtime, and against a GumJS devkit built for the same 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:
|
||||
npm install -g xpm
|
||||
xpm install
|
||||
export PATH=$PWD/xpacks/.bin:$PATH
|
||||
|
||||
machine=none-arm64-softfloat_nopic
|
||||
## How to build Gum
|
||||
|
||||
releng/deps.py build --bundle=sdk --host=$machine
|
||||
mkdir -p ~/sdk-$machine
|
||||
tar -C ~/sdk-$machine -xf deps/sdk-$machine.tar.xz
|
||||
|
||||
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)
|
||||
./configure \
|
||||
--host=aarch64-none-elf \
|
||||
--enable-gumjs \
|
||||
--with-devkits=gum,gumjs \
|
||||
--with-devkit-symbol-scope=original
|
||||
make
|
||||
export GUMJS_DEVKIT_DIR=$PWD/build/bindings/gumjs/devkit
|
||||
|
||||
## Building
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
## Targets
|
||||
|
||||
The agent supports four kernels. One of them has to be picked; none is a default,
|
||||
since none follows from the host you build on:
|
||||
|
||||
- `xnu` — a freestanding ELF executable that the Barebone backend
|
||||
injects into a running kernel from the outside, over a remote stub.
|
||||
- `win9x` — the same, for a 32-bit Windows 9x guest.
|
||||
- `winnt` — the same, for a Windows NT guest, either word size, on x86 or arm64.
|
||||
- `linux` — a static library that becomes a kernel module, loaded from the
|
||||
inside. See `linux/README.md`.
|
||||
|
||||
Each flavor has an SDK and a devkit of its own, since the addressing differs: an
|
||||
image the host places goes in position-independent, a Linux module is absolute,
|
||||
and NT's 64-bit half is called in the Microsoft ABI.
|
||||
|
||||
| Flavor | Machine | Rust target |
|
||||
|---------|-----------------------------------|----------------------------------|
|
||||
| `xnu` | `none-arm64-softfloat_nopic` | `aarch64-unknown-none-softfloat` |
|
||||
| `win9x` | `none-x86-softfloat_pic` | `i686-unknown-none-pic.json` |
|
||||
| `winnt` | `none-x86-softfloat_pic` | `i686-unknown-none-pic.json` |
|
||||
| `winnt` | `none-x86_64-softfloat_pic_msabi` | `x86_64-unknown-none-pic.json` |
|
||||
| `winnt` | `none-arm64-softfloat_pic` | `aarch64-unknown-none-pic.json` |
|
||||
| `linux` | `none-<arch>-softfloat_nopic` | see `linux/README.md` |
|
||||
|
||||
Everything above the `kernel` module is shared; each backend supplies the same
|
||||
set of primitives (logging, allocation, threads, waiting, time, symbols) plus its
|
||||
half of Gum's platform backend (`gum_xnu.rs` / `gum_windows.rs` / `gum_linux.rs`).
|
||||
export PATH=$PWD/xpacks/.bin:$PATH
|
||||
export CC_aarch64_unknown_none=aarch64-none-elf-gcc
|
||||
export AR_aarch64_unknown_none=aarch64-none-elf-ar
|
||||
export RANLIB_aarch64_unknown_none=aarch64-none-elf-ranlib
|
||||
|
||||
## Development loop
|
||||
|
||||
export FRIDA_BAREBONE_CONFIG=$PWD/etc/xnu.json
|
||||
cargo build --release --features xnu --target aarch64-unknown-none-softfloat \
|
||||
&& make -C ~/src/frida-python \
|
||||
&& killall -9 qemu-system-aarch64 && sleep 2 \
|
||||
&& frida -D barebone -p 0
|
||||
cargo build --release && make -C ~/src/frida-python && killall -9 qemu-system-aarch64 && sleep 2 && frida -D barebone -p 0
|
||||
|
||||
## Speeding up loop
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"abi": "softfloat",
|
||||
"arch": "aarch64",
|
||||
"crt-objects-fallback": "false",
|
||||
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32",
|
||||
"disable-redzone": true,
|
||||
"features": "+v8a,+strict-align,-neon,+reserve-x18",
|
||||
"linker": "rust-lld",
|
||||
"linker-flavor": "gnu-lld",
|
||||
"llvm-target": "aarch64-unknown-none",
|
||||
"max-atomic-width": 128,
|
||||
"panic-strategy": "abort",
|
||||
"plt-by-default": false,
|
||||
"position-independent-executables": true,
|
||||
"relocation-model": "pic",
|
||||
"rustc-abi": "softfloat",
|
||||
"stack-probes": {
|
||||
"kind": "inline"
|
||||
},
|
||||
"target-pointer-width": 64
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
SECTIONS {
|
||||
.text : {
|
||||
*(.text.entrypoint)
|
||||
*(.text*)
|
||||
}
|
||||
.rel.dyn : {
|
||||
_agent_relocs_start = .;
|
||||
*(.rel.dyn)
|
||||
_agent_relocs_end = .;
|
||||
}
|
||||
.rodata : {
|
||||
*(.rodata*)
|
||||
}
|
||||
.kernel_addrs : ALIGN(4) {
|
||||
. = ALIGN(4);
|
||||
__printk = .; . += 4;
|
||||
_printk = .; . += 4;
|
||||
_panic = .; . += 4;
|
||||
_kthread_create_on_node = .; . += 4;
|
||||
_wake_up_process = .; . += 4;
|
||||
_fget = .; . += 4;
|
||||
_kernel_read = .; . += 4;
|
||||
_filp_open = .; . += 4;
|
||||
_kernel_write = .; . += 4;
|
||||
___kmalloc_noprof = .; . += 4;
|
||||
_kmalloc = .; . += 4;
|
||||
___kmalloc = .; . += 4;
|
||||
_kfree = .; . += 4;
|
||||
_vmalloc = .; . += 4;
|
||||
_execmem_alloc = .; . += 4;
|
||||
_set_memory_rw = .; . += 4;
|
||||
_set_memory_ro = .; . += 4;
|
||||
_set_memory_x = .; . += 4;
|
||||
_set_memory_nx = .; . += 4;
|
||||
_modules = .; . += 4;
|
||||
_register_module_notifier = .; . += 4;
|
||||
_unregister_module_notifier = .; . += 4;
|
||||
_execmem_free = .; . += 4;
|
||||
_module_alloc = .; . += 4;
|
||||
_module_memfree = .; . += 4;
|
||||
___init_waitqueue_head = .; . += 4;
|
||||
_prepare_to_wait_event = .; . += 4;
|
||||
_finish_wait = .; . += 4;
|
||||
___wake_up = .; . += 4;
|
||||
_autoremove_wake_function = .; . += 4;
|
||||
_schedule_hrtimeout_range = .; . += 4;
|
||||
_pci_get_domain_bus_and_slot = .; . += 4;
|
||||
_pci_irq_vector = .; . += 4;
|
||||
_of_irq_parse_and_map_pci = .; . += 4;
|
||||
_pci_dev_put = .; . += 4;
|
||||
_schedule = .; . += 4;
|
||||
_ktime_get_mono_fast_ns = .; . += 4;
|
||||
_ktime_get_real_ts64 = .; . += 4;
|
||||
_getnstimeofday64 = .; . += 4;
|
||||
_send_sig = .; . += 4;
|
||||
_free_irq = .; . += 4;
|
||||
_request_threaded_irq = .; . += 4;
|
||||
_vmap = .; . += 4;
|
||||
___pv_offset = .; . += 4;
|
||||
_pgprot_kernel = .; . += 4;
|
||||
_ioremap = .; . += 4;
|
||||
_of_find_node_opts_by_path = .; . += 4;
|
||||
_of_irq_get = .; . += 4;
|
||||
_of_node_put = .; . += 4;
|
||||
_init_task = .; . += 4;
|
||||
_tasklist_lock = .; . += 4;
|
||||
__raw_read_lock = .; . += 4;
|
||||
__raw_read_unlock = .; . += 4;
|
||||
__raw_read_lock_irqsave = .; . += 4;
|
||||
__raw_read_unlock_irqrestore = .; . += 4;
|
||||
__raw_write_lock = .; . += 4;
|
||||
__raw_write_unlock = .; . += 4;
|
||||
__raw_write_lock_irqsave = .; . += 4;
|
||||
__raw_write_unlock_irqrestore = .; . += 4;
|
||||
_detach_pid = .; . += 4;
|
||||
_free_pids = .; . += 4;
|
||||
_get_task_exe_file = .; . += 4;
|
||||
_file_path = .; . += 4;
|
||||
_fput = .; . += 4;
|
||||
_copy_from_kernel_nofault = .; . += 4;
|
||||
_get_task_mm = .; . += 4;
|
||||
_kthread_use_mm = .; . += 4;
|
||||
_kthread_unuse_mm = .; . += 4;
|
||||
_mmput = .; . += 4;
|
||||
_vm_mmap = .; . += 4;
|
||||
_vm_munmap = .; . += 4;
|
||||
_tracepoint_probe_register = .; . += 4;
|
||||
_tracepoint_probe_unregister = .; . += 4;
|
||||
___tracepoint_sched_process_fork = .; . += 4;
|
||||
___tracepoint_sched_process_exit = .; . += 4;
|
||||
___tracepoint_sched_process_exec = .; . += 4;
|
||||
___tracepoint_task_rename = .; . += 4;
|
||||
___set_task_comm = .; . += 4;
|
||||
_vunmap = .; . += 4;
|
||||
_arm_copy_to_user = .; . += 4;
|
||||
_down_read = .; . += 4;
|
||||
_up_read = .; . += 4;
|
||||
_get_user_pages_unlocked = .; . += 4;
|
||||
_get_user_pages_remote = .; . += 4;
|
||||
_user_mode_thread = .; . += 4;
|
||||
_fget_task = .; . += 4;
|
||||
_find_task_by_vpid = .; . += 4;
|
||||
___start_BTF = .; . += 4;
|
||||
___stop_BTF = .; . += 4;
|
||||
_call_usermodehelper_setup = .; . += 4;
|
||||
_call_usermodehelper_exec = .; . += 4;
|
||||
_arm_copy_from_user = .; . += 4;
|
||||
_fixup_exception = .; . += 4;
|
||||
_init_net = .; . += 4;
|
||||
_sock_create_kern = .; . += 4;
|
||||
_kernel_connect = .; . += 4;
|
||||
_kernel_sendmsg = .; . += 4;
|
||||
_kernel_recvmsg = .; . += 4;
|
||||
_sock_release = .; . += 4;
|
||||
_patch_text = .; . += 4;
|
||||
}
|
||||
/* Everything above is only ever read, so a process being instrumented can be shown the very
|
||||
same pages; everything below it each copy needs its own of. */
|
||||
. = ALIGN(4096);
|
||||
_agent_private_start = .;
|
||||
.data.rel.ro : {
|
||||
*(.data.rel.ro*)
|
||||
}
|
||||
.got : {
|
||||
*(.got*)
|
||||
}
|
||||
.init_array : {
|
||||
_agent_init_start = .;
|
||||
*(.init_array*)
|
||||
_agent_init_end = .;
|
||||
}
|
||||
.data : {
|
||||
*(.data*)
|
||||
}
|
||||
.bss : {
|
||||
*(.bss*)
|
||||
}
|
||||
. = ALIGN(4);
|
||||
_heap_start = .;
|
||||
}
|
||||
|
||||
VERSION {
|
||||
{
|
||||
global:
|
||||
_start;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
SECTIONS {
|
||||
.text : {
|
||||
*(.text.entrypoint)
|
||||
*(.text*)
|
||||
}
|
||||
.rela.dyn : {
|
||||
_agent_relocs_start = .;
|
||||
*(.rela.dyn)
|
||||
_agent_relocs_end = .;
|
||||
}
|
||||
.rodata : {
|
||||
*(.rodata*)
|
||||
}
|
||||
.kernel_addrs : ALIGN(8) {
|
||||
. = ALIGN(8);
|
||||
__printk = .; . += 8;
|
||||
_printk = .; . += 8;
|
||||
_panic = .; . += 8;
|
||||
_kthread_create_on_node = .; . += 8;
|
||||
_wake_up_process = .; . += 8;
|
||||
_fget = .; . += 8;
|
||||
_kernel_read = .; . += 8;
|
||||
_filp_open = .; . += 8;
|
||||
_kernel_write = .; . += 8;
|
||||
___kmalloc_noprof = .; . += 8;
|
||||
_kmalloc = .; . += 8;
|
||||
___kmalloc = .; . += 8;
|
||||
_kfree = .; . += 8;
|
||||
_vmalloc = .; . += 8;
|
||||
_execmem_alloc = .; . += 8;
|
||||
_set_memory_rw = .; . += 8;
|
||||
_set_memory_ro = .; . += 8;
|
||||
_set_memory_x = .; . += 8;
|
||||
_set_memory_nx = .; . += 8;
|
||||
_modules = .; . += 8;
|
||||
_register_module_notifier = .; . += 8;
|
||||
_unregister_module_notifier = .; . += 8;
|
||||
_execmem_free = .; . += 8;
|
||||
_module_alloc = .; . += 8;
|
||||
_module_memfree = .; . += 8;
|
||||
___init_waitqueue_head = .; . += 8;
|
||||
_prepare_to_wait_event = .; . += 8;
|
||||
_finish_wait = .; . += 8;
|
||||
___wake_up = .; . += 8;
|
||||
_autoremove_wake_function = .; . += 8;
|
||||
_schedule_hrtimeout_range = .; . += 8;
|
||||
_pci_get_domain_bus_and_slot = .; . += 8;
|
||||
_pci_irq_vector = .; . += 8;
|
||||
_of_irq_parse_and_map_pci = .; . += 8;
|
||||
_pci_dev_put = .; . += 8;
|
||||
_schedule = .; . += 8;
|
||||
_ktime_get_mono_fast_ns = .; . += 8;
|
||||
_ktime_get_real_ts64 = .; . += 8;
|
||||
_getnstimeofday64 = .; . += 8;
|
||||
_send_sig = .; . += 8;
|
||||
_free_irq = .; . += 8;
|
||||
_request_threaded_irq = .; . += 8;
|
||||
_generic_ioremap_prot = .; . += 8;
|
||||
___ioremap = .; . += 8;
|
||||
_vmap = .; . += 8;
|
||||
_memstart_addr = .; . += 8;
|
||||
_init_task = .; . += 8;
|
||||
_tasklist_lock = .; . += 8;
|
||||
__raw_read_lock = .; . += 8;
|
||||
__raw_read_unlock = .; . += 8;
|
||||
__raw_read_lock_irqsave = .; . += 8;
|
||||
__raw_read_unlock_irqrestore = .; . += 8;
|
||||
__raw_write_lock = .; . += 8;
|
||||
__raw_write_unlock = .; . += 8;
|
||||
__raw_write_lock_irqsave = .; . += 8;
|
||||
__raw_write_unlock_irqrestore = .; . += 8;
|
||||
_detach_pid = .; . += 8;
|
||||
_free_pids = .; . += 8;
|
||||
_get_task_exe_file = .; . += 8;
|
||||
_file_path = .; . += 8;
|
||||
_fput = .; . += 8;
|
||||
_copy_from_kernel_nofault = .; . += 8;
|
||||
_get_task_mm = .; . += 8;
|
||||
_kthread_use_mm = .; . += 8;
|
||||
_kthread_unuse_mm = .; . += 8;
|
||||
_mmput = .; . += 8;
|
||||
_vm_mmap = .; . += 8;
|
||||
_vm_munmap = .; . += 8;
|
||||
_tracepoint_probe_register = .; . += 8;
|
||||
_tracepoint_probe_unregister = .; . += 8;
|
||||
___tracepoint_sched_process_fork = .; . += 8;
|
||||
___tracepoint_sched_process_exit = .; . += 8;
|
||||
___tracepoint_sched_process_exec = .; . += 8;
|
||||
___tracepoint_task_rename = .; . += 8;
|
||||
___set_task_comm = .; . += 8;
|
||||
_vunmap = .; . += 8;
|
||||
___arch_copy_to_user = .; . += 8;
|
||||
_down_read = .; . += 8;
|
||||
_up_read = .; . += 8;
|
||||
_get_user_pages_unlocked = .; . += 8;
|
||||
_get_user_pages_remote = .; . += 8;
|
||||
___arch_copy_from_user = .; . += 8;
|
||||
_do_futex = .; . += 8;
|
||||
_user_mode_thread = .; . += 8;
|
||||
_apply_to_existing_page_range = .; . += 8;
|
||||
_caches_clean_inval_pou = .; . += 8;
|
||||
_do_madvise = .; . += 8;
|
||||
_fget_task = .; . += 8;
|
||||
_find_task_by_vpid = .; . += 8;
|
||||
___start_BTF = .; . += 8;
|
||||
___stop_BTF = .; . += 8;
|
||||
_call_usermodehelper_setup = .; . += 8;
|
||||
_call_usermodehelper_exec = .; . += 8;
|
||||
_access_process_vm = .; . += 8;
|
||||
_fixup_exception = .; . += 8;
|
||||
_aarch64_insn_patch_text_nosync = .; . += 8;
|
||||
_aarch64_insn_patch_text = .; . += 8;
|
||||
_kthread_worker_fn = .; . += 8;
|
||||
_vring_interrupt = .; . += 8;
|
||||
_init_net = .; . += 8;
|
||||
_sock_create_kern = .; . += 8;
|
||||
_kernel_connect = .; . += 8;
|
||||
_kernel_sendmsg = .; . += 8;
|
||||
_kernel_recvmsg = .; . += 8;
|
||||
_sock_release = .; . += 8;
|
||||
_sock_def_readable = .; . += 8;
|
||||
}
|
||||
/* Everything above is only ever read, so a process being instrumented can be shown the very
|
||||
same pages; everything below it each copy needs its own of. */
|
||||
. = ALIGN(4096);
|
||||
_agent_private_start = .;
|
||||
.data.rel.ro : {
|
||||
*(.data.rel.ro*)
|
||||
}
|
||||
.got : {
|
||||
*(.got*)
|
||||
}
|
||||
.init_array : {
|
||||
_agent_init_start = .;
|
||||
*(.init_array*)
|
||||
_agent_init_end = .;
|
||||
}
|
||||
.data : {
|
||||
*(.data*)
|
||||
}
|
||||
.bss : {
|
||||
*(.bss*)
|
||||
}
|
||||
. = ALIGN(8);
|
||||
_heap_start = .;
|
||||
}
|
||||
|
||||
VERSION {
|
||||
{
|
||||
global:
|
||||
_start;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -1,146 +0,0 @@
|
||||
SECTIONS {
|
||||
.text : {
|
||||
*(.text.entrypoint)
|
||||
*(.text*)
|
||||
}
|
||||
.rel.dyn : {
|
||||
_agent_relocs_start = .;
|
||||
*(.rel.dyn)
|
||||
_agent_relocs_end = .;
|
||||
}
|
||||
.rodata : {
|
||||
*(.rodata*)
|
||||
}
|
||||
.kernel_addrs : ALIGN(4) {
|
||||
. = ALIGN(4);
|
||||
__printk = .; . += 4;
|
||||
_printk = .; . += 4;
|
||||
_panic = .; . += 4;
|
||||
_kthread_create_on_node = .; . += 4;
|
||||
_wake_up_process = .; . += 4;
|
||||
_fget = .; . += 4;
|
||||
_kernel_read = .; . += 4;
|
||||
_filp_open = .; . += 4;
|
||||
_kernel_write = .; . += 4;
|
||||
___kmalloc_noprof = .; . += 4;
|
||||
_kmalloc = .; . += 4;
|
||||
_kfree = .; . += 4;
|
||||
_vmalloc = .; . += 4;
|
||||
_execmem_alloc = .; . += 4;
|
||||
_set_memory_rw = .; . += 4;
|
||||
_set_memory_ro = .; . += 4;
|
||||
_set_memory_x = .; . += 4;
|
||||
_set_memory_nx = .; . += 4;
|
||||
_modules = .; . += 4;
|
||||
_register_module_notifier = .; . += 4;
|
||||
_unregister_module_notifier = .; . += 4;
|
||||
_text_poke_kgdb = .; . += 4;
|
||||
_execmem_free = .; . += 4;
|
||||
_module_alloc = .; . += 4;
|
||||
_module_memfree = .; . += 4;
|
||||
___init_waitqueue_head = .; . += 4;
|
||||
_prepare_to_wait_event = .; . += 4;
|
||||
_finish_wait = .; . += 4;
|
||||
___wake_up = .; . += 4;
|
||||
_autoremove_wake_function = .; . += 4;
|
||||
_schedule_hrtimeout_range = .; . += 4;
|
||||
_pci_get_domain_bus_and_slot = .; . += 4;
|
||||
_pci_irq_vector = .; . += 4;
|
||||
_pci_dev_put = .; . += 4;
|
||||
_schedule = .; . += 4;
|
||||
_ktime_get_mono_fast_ns = .; . += 4;
|
||||
_ktime_get_real_ts64 = .; . += 4;
|
||||
_send_sig = .; . += 4;
|
||||
_free_irq = .; . += 4;
|
||||
_request_threaded_irq = .; . += 4;
|
||||
_vmap = .; . += 4;
|
||||
_current_task = .; . += 4;
|
||||
_ioremap = .; . += 4;
|
||||
_ioremap_nocache = .; . += 4;
|
||||
___default_kernel_pte_mask = .; . += 4;
|
||||
_init_task = .; . += 4;
|
||||
_tasklist_lock = .; . += 4;
|
||||
__raw_read_lock = .; . += 4;
|
||||
__raw_read_unlock = .; . += 4;
|
||||
__raw_read_lock_irqsave = .; . += 4;
|
||||
__raw_read_unlock_irqrestore = .; . += 4;
|
||||
__raw_write_lock = .; . += 4;
|
||||
__raw_write_unlock = .; . += 4;
|
||||
__raw_write_lock_irqsave = .; . += 4;
|
||||
__raw_write_unlock_irqrestore = .; . += 4;
|
||||
_detach_pid = .; . += 4;
|
||||
_free_pids = .; . += 4;
|
||||
_get_task_exe_file = .; . += 4;
|
||||
_file_path = .; . += 4;
|
||||
_fput = .; . += 4;
|
||||
_copy_from_kernel_nofault = .; . += 4;
|
||||
_get_task_mm = .; . += 4;
|
||||
_kthread_use_mm = .; . += 4;
|
||||
_kthread_unuse_mm = .; . += 4;
|
||||
_mmput = .; . += 4;
|
||||
_vm_mmap = .; . += 4;
|
||||
_vm_munmap = .; . += 4;
|
||||
_tracepoint_probe_register = .; . += 4;
|
||||
_tracepoint_probe_unregister = .; . += 4;
|
||||
___tracepoint_sched_process_fork = .; . += 4;
|
||||
___tracepoint_sched_process_exit = .; . += 4;
|
||||
___tracepoint_sched_process_exec = .; . += 4;
|
||||
_vunmap = .; . += 4;
|
||||
__copy_to_user = .; . += 4;
|
||||
_down_read = .; . += 4;
|
||||
_up_read = .; . += 4;
|
||||
_get_user_pages_unlocked = .; . += 4;
|
||||
_get_user_pages_remote = .; . += 4;
|
||||
_do_futex = .; . += 4;
|
||||
_user_mode_thread = .; . += 4;
|
||||
___start_BTF = .; . += 4;
|
||||
___stop_BTF = .; . += 4;
|
||||
_call_usermodehelper_setup = .; . += 4;
|
||||
_call_usermodehelper_exec = .; . += 4;
|
||||
__copy_from_user = .; . += 4;
|
||||
_access_process_vm = .; . += 4;
|
||||
_fget_task = .; . += 4;
|
||||
_find_task_by_vpid = .; . += 4;
|
||||
_init_net = .; . += 4;
|
||||
_sock_create_kern = .; . += 4;
|
||||
_kernel_connect = .; . += 4;
|
||||
_kernel_sendmsg = .; . += 4;
|
||||
_kernel_recvmsg = .; . += 4;
|
||||
_sock_release = .; . += 4;
|
||||
_register_die_notifier = .; . += 4;
|
||||
_unregister_die_notifier = .; . += 4;
|
||||
}
|
||||
/* Everything above is only ever read, so a process being instrumented can be shown the very
|
||||
same pages; everything below it each copy needs its own of. */
|
||||
. = ALIGN(4096);
|
||||
_agent_private_start = .;
|
||||
.data.rel.ro : {
|
||||
*(.data.rel.ro*)
|
||||
}
|
||||
.got : {
|
||||
*(.got*)
|
||||
}
|
||||
.init_array : {
|
||||
_agent_init_start = .;
|
||||
*(.init_array*)
|
||||
_agent_init_end = .;
|
||||
}
|
||||
.data : {
|
||||
*(.data*)
|
||||
}
|
||||
.bss : {
|
||||
*(.bss*)
|
||||
}
|
||||
. = ALIGN(4);
|
||||
_heap_start = .;
|
||||
}
|
||||
|
||||
VERSION {
|
||||
{
|
||||
global:
|
||||
_start;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
SECTIONS {
|
||||
.text : {
|
||||
*(.text.entrypoint)
|
||||
*(.text*)
|
||||
}
|
||||
.rela.dyn : {
|
||||
_agent_relocs_start = .;
|
||||
*(.rela.dyn)
|
||||
_agent_relocs_end = .;
|
||||
}
|
||||
.rodata : {
|
||||
*(.rodata*)
|
||||
}
|
||||
.kernel_addrs : ALIGN(8) {
|
||||
. = ALIGN(8);
|
||||
__printk = .; . += 8;
|
||||
_printk = .; . += 8;
|
||||
_panic = .; . += 8;
|
||||
_kthread_create_on_node = .; . += 8;
|
||||
_wake_up_process = .; . += 8;
|
||||
_fget = .; . += 8;
|
||||
_kernel_read = .; . += 8;
|
||||
_filp_open = .; . += 8;
|
||||
_kernel_write = .; . += 8;
|
||||
___kmalloc_noprof = .; . += 8;
|
||||
_kmalloc = .; . += 8;
|
||||
___kmalloc = .; . += 8;
|
||||
_kfree = .; . += 8;
|
||||
_vmalloc = .; . += 8;
|
||||
_execmem_alloc = .; . += 8;
|
||||
_set_memory_rw = .; . += 8;
|
||||
_set_memory_ro = .; . += 8;
|
||||
_set_memory_x = .; . += 8;
|
||||
_set_memory_nx = .; . += 8;
|
||||
_modules = .; . += 8;
|
||||
_register_module_notifier = .; . += 8;
|
||||
_unregister_module_notifier = .; . += 8;
|
||||
_text_poke_kgdb = .; . += 8;
|
||||
_execmem_free = .; . += 8;
|
||||
_module_alloc = .; . += 8;
|
||||
_module_memfree = .; . += 8;
|
||||
___init_waitqueue_head = .; . += 8;
|
||||
_prepare_to_wait_event = .; . += 8;
|
||||
_finish_wait = .; . += 8;
|
||||
___wake_up = .; . += 8;
|
||||
_autoremove_wake_function = .; . += 8;
|
||||
_schedule_hrtimeout_range = .; . += 8;
|
||||
_pci_get_domain_bus_and_slot = .; . += 8;
|
||||
_pci_irq_vector = .; . += 8;
|
||||
_pci_dev_put = .; . += 8;
|
||||
_schedule = .; . += 8;
|
||||
_ktime_get_mono_fast_ns = .; . += 8;
|
||||
_ktime_get_real_ts64 = .; . += 8;
|
||||
_getnstimeofday64 = .; . += 8;
|
||||
_send_sig = .; . += 8;
|
||||
_free_irq = .; . += 8;
|
||||
_request_threaded_irq = .; . += 8;
|
||||
_vmap = .; . += 8;
|
||||
_page_offset_base = .; . += 8;
|
||||
_current_task = .; . += 8;
|
||||
_cpu_current_top_of_stack = .; . += 8;
|
||||
_register_die_notifier = .; . += 8;
|
||||
_unregister_die_notifier = .; . += 8;
|
||||
_ioremap = .; . += 8;
|
||||
_ioremap_nocache = .; . += 8;
|
||||
___default_kernel_pte_mask = .; . += 8;
|
||||
_init_task = .; . += 8;
|
||||
_tasklist_lock = .; . += 8;
|
||||
__raw_read_lock = .; . += 8;
|
||||
__raw_read_unlock = .; . += 8;
|
||||
__raw_read_lock_irqsave = .; . += 8;
|
||||
__raw_read_unlock_irqrestore = .; . += 8;
|
||||
__raw_write_lock = .; . += 8;
|
||||
__raw_write_unlock = .; . += 8;
|
||||
__raw_write_lock_irqsave = .; . += 8;
|
||||
__raw_write_unlock_irqrestore = .; . += 8;
|
||||
_detach_pid = .; . += 8;
|
||||
_free_pids = .; . += 8;
|
||||
_get_task_exe_file = .; . += 8;
|
||||
_file_path = .; . += 8;
|
||||
_fput = .; . += 8;
|
||||
_copy_from_kernel_nofault = .; . += 8;
|
||||
_get_task_mm = .; . += 8;
|
||||
_kthread_use_mm = .; . += 8;
|
||||
_kthread_unuse_mm = .; . += 8;
|
||||
_mmput = .; . += 8;
|
||||
_vm_mmap = .; . += 8;
|
||||
_vm_munmap = .; . += 8;
|
||||
_tracepoint_probe_register = .; . += 8;
|
||||
_tracepoint_probe_unregister = .; . += 8;
|
||||
___tracepoint_sched_process_fork = .; . += 8;
|
||||
___tracepoint_sched_process_exit = .; . += 8;
|
||||
___tracepoint_sched_process_exec = .; . += 8;
|
||||
___tracepoint_task_rename = .; . += 8;
|
||||
___set_task_comm = .; . += 8;
|
||||
_vunmap = .; . += 8;
|
||||
__copy_to_user = .; . += 8;
|
||||
_down_read = .; . += 8;
|
||||
_up_read = .; . += 8;
|
||||
_get_user_pages_unlocked = .; . += 8;
|
||||
_get_user_pages_remote = .; . += 8;
|
||||
_do_futex = .; . += 8;
|
||||
_user_mode_thread = .; . += 8;
|
||||
___start_BTF = .; . += 8;
|
||||
___stop_BTF = .; . += 8;
|
||||
_call_usermodehelper_setup = .; . += 8;
|
||||
_call_usermodehelper_exec = .; . += 8;
|
||||
__copy_from_user = .; . += 8;
|
||||
_access_process_vm = .; . += 8;
|
||||
_fget_task = .; . += 8;
|
||||
_find_task_by_vpid = .; . += 8;
|
||||
_init_net = .; . += 8;
|
||||
_sock_create_kern = .; . += 8;
|
||||
_kernel_connect = .; . += 8;
|
||||
_kernel_sendmsg = .; . += 8;
|
||||
_kernel_recvmsg = .; . += 8;
|
||||
_sock_release = .; . += 8;
|
||||
_sock_def_readable = .; . += 8;
|
||||
_of_irq_parse_and_map_pci = .; . += 8;
|
||||
}
|
||||
/* Everything above is only ever read, so a process being instrumented can be shown the very
|
||||
same pages; everything below it each copy needs its own of. */
|
||||
. = ALIGN(4096);
|
||||
_agent_private_start = .;
|
||||
.data.rel.ro : {
|
||||
*(.data.rel.ro*)
|
||||
}
|
||||
.got : {
|
||||
*(.got*)
|
||||
}
|
||||
.init_array : {
|
||||
_agent_init_start = .;
|
||||
*(.init_array*)
|
||||
_agent_init_end = .;
|
||||
}
|
||||
.data : {
|
||||
*(.data*)
|
||||
}
|
||||
.bss : {
|
||||
*(.bss*)
|
||||
}
|
||||
. = ALIGN(8);
|
||||
_heap_start = .;
|
||||
}
|
||||
|
||||
VERSION {
|
||||
{
|
||||
global:
|
||||
_start;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
SECTIONS {
|
||||
.text : {
|
||||
*(.text.entrypoint)
|
||||
*(.text*)
|
||||
}
|
||||
.rel.dyn : {
|
||||
_agent_relocs_start = .;
|
||||
*(.rel.dyn)
|
||||
_agent_relocs_end = .;
|
||||
}
|
||||
.rodata : {
|
||||
*(.rodata*)
|
||||
}
|
||||
.kernel_addrs : ALIGN(4) {
|
||||
. = ALIGN(4);
|
||||
_Get_Cur_VM_Handle = .; . += 4;
|
||||
_Get_Sys_VM_Handle = .; . += 4;
|
||||
_Schedule_Global_Event = .; . += 4;
|
||||
_Create_Semaphore = .; . += 4;
|
||||
_Wait_Semaphore = .; . += 4;
|
||||
_Signal_Semaphore = .; . += 4;
|
||||
_Get_Next_VM_Handle = .; . += 4;
|
||||
_Release_Time_Slice = .; . += 4;
|
||||
_Set_Global_Time_Out = .; . += 4;
|
||||
_Cancel_Time_Out = .; . += 4;
|
||||
_Get_System_Time = .; . += 4;
|
||||
__HeapAllocate = .; . += 4;
|
||||
__HeapFree = .; . += 4;
|
||||
__PageAllocate = .; . += 4;
|
||||
__PageFree = .; . += 4;
|
||||
__CopyPageTable = .; . += 4;
|
||||
__MapPhysToLinear = .; . += 4;
|
||||
_VMM_Add_DDB = .; . += 4;
|
||||
_VMM_Remove_DDB = .; . += 4;
|
||||
__RegOpenKey = .; . += 4;
|
||||
__RegCloseKey = .; . += 4;
|
||||
__RegEnumKey = .; . += 4;
|
||||
__RegQueryValueEx = .; . += 4;
|
||||
__PageReserve = .; . += 4;
|
||||
__PageCommit = .; . += 4;
|
||||
_Hook_VMM_Fault = .; . += 4;
|
||||
_Unhook_VMM_Fault = .; . += 4;
|
||||
_Fatal_Error_Handler = .; . += 4;
|
||||
_Get_Cur_Thread_Handle = .; . += 4;
|
||||
|
||||
_Get_Initial_Thread_Handle = .; . += 4;
|
||||
_Get_Next_Thread_Handle = .; . += 4;
|
||||
__Debug_Printf_Service = .; . += 4;
|
||||
__ContextSwitch = .; . += 4;
|
||||
__PageModifyPermissions = .; . += 4;
|
||||
__GetCurrentContext = .; . += 4;
|
||||
_VPICD_Virtualize_IRQ = .; . += 4;
|
||||
_VPICD_Force_Default_Behavior = .; . += 4;
|
||||
_VPICD_Phys_EOI = .; . += 4;
|
||||
_VPICD_Physically_Unmask = .; . += 4;
|
||||
__VWIN32_QueueUserApc = .; . += 4;
|
||||
__VWIN32_Get_Thread_Context = .; . += 4;
|
||||
__VWIN32_Set_Thread_Context = .; . += 4;
|
||||
__VWIN32_CreateRing0Thread = .; . += 4;
|
||||
_IFSMgr_Ring0_FileIO = .; . += 4;
|
||||
_KERNEL32_Base = .; . += 4;
|
||||
_KERNEL32_ProcessIdObfuscator = .; . += 4;
|
||||
_KERNEL32_ModuleTable = .; . += 4;
|
||||
__PageCommitPhys = .; . += 4;
|
||||
}
|
||||
/* Everything above is only ever read, so a process being instrumented can be shown the very
|
||||
same pages; everything below it each copy needs its own of. */
|
||||
. = ALIGN(4096);
|
||||
_agent_private_start = .;
|
||||
.data.rel.ro : {
|
||||
*(.data.rel.ro*)
|
||||
}
|
||||
.got : {
|
||||
*(.got*)
|
||||
}
|
||||
.init_array : {
|
||||
_agent_init_start = .;
|
||||
*(.init_array*)
|
||||
_agent_init_end = .;
|
||||
}
|
||||
.bss : {
|
||||
*(.bss*)
|
||||
}
|
||||
. = ALIGN(4);
|
||||
_heap_start = .;
|
||||
}
|
||||
|
||||
VERSION {
|
||||
{
|
||||
global:
|
||||
_start;
|
||||
frida_win9x_user_main;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
SECTIONS {
|
||||
.text : {
|
||||
*(.text.entrypoint)
|
||||
*(.text*)
|
||||
}
|
||||
.rela.dyn : {
|
||||
_agent_relocs_start = .;
|
||||
*(.rela.dyn)
|
||||
_agent_relocs_end = .;
|
||||
}
|
||||
.rodata : {
|
||||
*(.rodata*)
|
||||
}
|
||||
.kernel_addrs : ALIGN(8) {
|
||||
. = ALIGN(8);
|
||||
_ExAllocatePoolWithTag = .; . += 8;
|
||||
_ExFreePoolWithTag = .; . += 8;
|
||||
_PsCreateSystemThread = .; . += 8;
|
||||
_PsGetCurrentThreadId = .; . += 8;
|
||||
_KeInitializeEvent = .; . += 8;
|
||||
_KeWaitForSingleObject = .; . += 8;
|
||||
_KeSetEvent = .; . += 8;
|
||||
_KeInitializeDpc = .; . += 8;
|
||||
_PsSetCreateThreadNotifyRoutine = .; . += 8;
|
||||
_PsRemoveCreateThreadNotifyRoutine = .; . += 8;
|
||||
_KeInsertQueueDpc = .; . += 8;
|
||||
_ZwYieldExecution = .; . += 8;
|
||||
_KeExpandKernelStackAndCalloutEx = .; . += 8;
|
||||
_KeBugCheckEx = .; . += 8;
|
||||
_DbgPrint = .; . += 8;
|
||||
_MmGetVirtualForPhysical = .; . += 8;
|
||||
_MmMapIoSpace = .; . += 8;
|
||||
_MmGetPhysicalAddress = .; . += 8;
|
||||
_HalGetInterruptVector = .; . += 8;
|
||||
_IoConnectInterrupt = .; . += 8;
|
||||
_IoDisconnectInterrupt = .; . += 8;
|
||||
_PsActiveProcessHead = .; . += 8;
|
||||
_PsInitialSystemProcess = .; . += 8;
|
||||
_PsGetProcessId = .; . += 8;
|
||||
_PsGetProcessImageFileName = .; . += 8;
|
||||
_PsGetProcessPeb = .; . += 8;
|
||||
_PsGetCurrentThread = .; . += 8;
|
||||
_PsGetThreadProcess = .; . += 8;
|
||||
_PsGetThreadId = .; . += 8;
|
||||
_PsGetThreadTeb = .; . += 8;
|
||||
_PsGetContextThread = .; . += 8;
|
||||
_PsSetContextThread = .; . += 8;
|
||||
_KeStackAttachProcess = .; . += 8;
|
||||
_KeUnstackDetachProcess = .; . += 8;
|
||||
_ZwCreateFile = .; . += 8;
|
||||
_ZwOpenKey = .; . += 8;
|
||||
_ZwEnumerateKey = .; . += 8;
|
||||
_ZwQueryValueKey = .; . += 8;
|
||||
_ZwReadFile = .; . += 8;
|
||||
_ZwWriteFile = .; . += 8;
|
||||
_ZwEnumerateValueKey = .; . += 8;
|
||||
_ZwDeviceIoControlFile = .; . += 8;
|
||||
_ZwClose = .; . += 8;
|
||||
_ZwCreateEvent = .; . += 8;
|
||||
_ExAllocatePool2 = .; . += 8;
|
||||
_MmCopyVirtualMemory = .; . += 8;
|
||||
_KeIpiGenericCall = .; . += 8;
|
||||
_MmIsAddressValid = .; . += 8;
|
||||
_ZwClearEvent = .; . += 8;
|
||||
_ZwWaitForSingleObject = .; . += 8;
|
||||
_ZwAllocateVirtualMemory = .; . += 8;
|
||||
_ZwFreeVirtualMemory = .; . += 8;
|
||||
_ZwCreateThread = .; . += 8;
|
||||
_PsProcessType = .; . += 8;
|
||||
_ObReferenceObjectByHandle = .; . += 8;
|
||||
_ObOpenObjectByPointer = .; . += 8;
|
||||
_ExEventObjectType = .; . += 8;
|
||||
_IoAllocateMdl = .; . += 8;
|
||||
_IoFreeMdl = .; . += 8;
|
||||
_MmBuildMdlForNonPagedPool = .; . += 8;
|
||||
_MmMapLockedPagesSpecifyCache = .; . += 8;
|
||||
_MmUnmapLockedPages = .; . += 8;
|
||||
}
|
||||
. = ALIGN(4096);
|
||||
_agent_private_start = .;
|
||||
.data.rel.ro : {
|
||||
*(.data.rel.ro*)
|
||||
}
|
||||
.got : {
|
||||
*(.got*)
|
||||
}
|
||||
.init_array : {
|
||||
_agent_init_start = .;
|
||||
*(.init_array*)
|
||||
_agent_init_end = .;
|
||||
}
|
||||
.data : {
|
||||
*(.data*)
|
||||
}
|
||||
.bss : {
|
||||
*(.bss*)
|
||||
}
|
||||
. = ALIGN(8);
|
||||
_heap_start = .;
|
||||
}
|
||||
|
||||
VERSION {
|
||||
{
|
||||
global:
|
||||
_start;
|
||||
frida_winnt_user_main;
|
||||
frida_winnt_user_bootstrap;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
SECTIONS {
|
||||
.text : {
|
||||
*(.text.entrypoint)
|
||||
*(.text*)
|
||||
}
|
||||
.rel.dyn : {
|
||||
_agent_relocs_start = .;
|
||||
*(.rel.dyn)
|
||||
_agent_relocs_end = .;
|
||||
}
|
||||
.rodata : {
|
||||
*(.rodata*)
|
||||
}
|
||||
.kernel_addrs : ALIGN(4) {
|
||||
. = ALIGN(4);
|
||||
_ExAllocatePoolWithTag = .; . += 4;
|
||||
_ExFreePoolWithTag = .; . += 4;
|
||||
_PsCreateSystemThread = .; . += 4;
|
||||
_PsGetCurrentThreadId = .; . += 4;
|
||||
_KeInitializeEvent = .; . += 4;
|
||||
_KeWaitForSingleObject = .; . += 4;
|
||||
_KeSetEvent = .; . += 4;
|
||||
_KeInitializeDpc = .; . += 4;
|
||||
_PsSetCreateThreadNotifyRoutine = .; . += 4;
|
||||
_PsRemoveCreateThreadNotifyRoutine = .; . += 4;
|
||||
_KeInsertQueueDpc = .; . += 4;
|
||||
_ZwYieldExecution = .; . += 4;
|
||||
_KeExpandKernelStackAndCalloutEx = .; . += 4;
|
||||
_KeBugCheckEx = .; . += 4;
|
||||
_DbgPrint = .; . += 4;
|
||||
_MmGetVirtualForPhysical = .; . += 4;
|
||||
_MmMapIoSpace = .; . += 4;
|
||||
_MmGetPhysicalAddress = .; . += 4;
|
||||
_HalGetInterruptVector = .; . += 4;
|
||||
_IoConnectInterrupt = .; . += 4;
|
||||
_IoDisconnectInterrupt = .; . += 4;
|
||||
_PsActiveProcessHead = .; . += 4;
|
||||
_PsInitialSystemProcess = .; . += 4;
|
||||
_PsGetProcessId = .; . += 4;
|
||||
_PsGetProcessImageFileName = .; . += 4;
|
||||
_PsGetProcessPeb = .; . += 4;
|
||||
_PsGetCurrentThread = .; . += 4;
|
||||
_PsGetThreadProcess = .; . += 4;
|
||||
_PsGetThreadId = .; . += 4;
|
||||
_PsGetThreadTeb = .; . += 4;
|
||||
_PsGetContextThread = .; . += 4;
|
||||
_PsSetContextThread = .; . += 4;
|
||||
_KeStackAttachProcess = .; . += 4;
|
||||
_KeUnstackDetachProcess = .; . += 4;
|
||||
_ZwCreateFile = .; . += 4;
|
||||
_ZwOpenKey = .; . += 4;
|
||||
_ZwEnumerateKey = .; . += 4;
|
||||
_ZwQueryValueKey = .; . += 4;
|
||||
_ZwReadFile = .; . += 4;
|
||||
_ZwWriteFile = .; . += 4;
|
||||
_ZwEnumerateValueKey = .; . += 4;
|
||||
_ZwDeviceIoControlFile = .; . += 4;
|
||||
_ZwClose = .; . += 4;
|
||||
_ZwCreateEvent = .; . += 4;
|
||||
_ExAllocatePool2 = .; . += 4;
|
||||
_MmCopyVirtualMemory = .; . += 4;
|
||||
_KeIpiGenericCall = .; . += 4;
|
||||
_MmIsAddressValid = .; . += 4;
|
||||
_ZwClearEvent = .; . += 4;
|
||||
_ZwWaitForSingleObject = .; . += 4;
|
||||
_ZwAllocateVirtualMemory = .; . += 4;
|
||||
_ZwFreeVirtualMemory = .; . += 4;
|
||||
_ZwCreateThread = .; . += 4;
|
||||
_PsProcessType = .; . += 4;
|
||||
_ObReferenceObjectByHandle = .; . += 4;
|
||||
_ObOpenObjectByPointer = .; . += 4;
|
||||
_ExEventObjectType = .; . += 4;
|
||||
_IoAllocateMdl = .; . += 4;
|
||||
_IoFreeMdl = .; . += 4;
|
||||
_MmBuildMdlForNonPagedPool = .; . += 4;
|
||||
_MmMapLockedPagesSpecifyCache = .; . += 4;
|
||||
_MmUnmapLockedPages = .; . += 4;
|
||||
}
|
||||
/* Everything above is only ever read, so a process being instrumented can be shown the very
|
||||
same pages; everything below it each copy needs its own of. */
|
||||
. = ALIGN(4096);
|
||||
_agent_private_start = .;
|
||||
.data.rel.ro : {
|
||||
*(.data.rel.ro*)
|
||||
}
|
||||
.got : {
|
||||
*(.got*)
|
||||
}
|
||||
.init_array : {
|
||||
_agent_init_start = .;
|
||||
*(.init_array*)
|
||||
_agent_init_end = .;
|
||||
}
|
||||
.bss : {
|
||||
*(.bss*)
|
||||
}
|
||||
. = ALIGN(4);
|
||||
_heap_start = .;
|
||||
}
|
||||
|
||||
VERSION {
|
||||
{
|
||||
global:
|
||||
_start;
|
||||
frida_winnt_user_main;
|
||||
frida_winnt_user_bootstrap;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
SECTIONS {
|
||||
/* The large code model gives every section an l-prefixed name of its own, so that
|
||||
the compiler can reach it with a 64-bit displacement rather than assume 2GB. */
|
||||
.text : {
|
||||
*(.text.entrypoint)
|
||||
*(.ltext.entrypoint)
|
||||
*(.text*)
|
||||
*(.ltext*)
|
||||
}
|
||||
.rela.dyn : {
|
||||
_agent_relocs_start = .;
|
||||
*(.rela.dyn)
|
||||
_agent_relocs_end = .;
|
||||
}
|
||||
.rodata : {
|
||||
*(.rodata*)
|
||||
*(.lrodata*)
|
||||
}
|
||||
.kernel_addrs : ALIGN(8) {
|
||||
. = ALIGN(8);
|
||||
_ExAllocatePoolWithTag = .; . += 8;
|
||||
_ExFreePoolWithTag = .; . += 8;
|
||||
_PsCreateSystemThread = .; . += 8;
|
||||
_PsGetCurrentThreadId = .; . += 8;
|
||||
_KeInitializeEvent = .; . += 8;
|
||||
_KeWaitForSingleObject = .; . += 8;
|
||||
_KeSetEvent = .; . += 8;
|
||||
_KeInitializeDpc = .; . += 8;
|
||||
_PsSetCreateThreadNotifyRoutine = .; . += 8;
|
||||
_PsRemoveCreateThreadNotifyRoutine = .; . += 8;
|
||||
_KeInsertQueueDpc = .; . += 8;
|
||||
_ZwYieldExecution = .; . += 8;
|
||||
_KeExpandKernelStackAndCalloutEx = .; . += 8;
|
||||
_KeBugCheckEx = .; . += 8;
|
||||
_DbgPrint = .; . += 8;
|
||||
_MmGetVirtualForPhysical = .; . += 8;
|
||||
_MmMapIoSpace = .; . += 8;
|
||||
_MmGetPhysicalAddress = .; . += 8;
|
||||
_HalGetInterruptVector = .; . += 8;
|
||||
_IoConnectInterrupt = .; . += 8;
|
||||
_IoDisconnectInterrupt = .; . += 8;
|
||||
_PsActiveProcessHead = .; . += 8;
|
||||
_PsInitialSystemProcess = .; . += 8;
|
||||
_PsGetProcessId = .; . += 8;
|
||||
_PsGetProcessImageFileName = .; . += 8;
|
||||
_PsGetProcessPeb = .; . += 8;
|
||||
_PsGetCurrentThread = .; . += 8;
|
||||
_PsGetThreadProcess = .; . += 8;
|
||||
_PsGetThreadId = .; . += 8;
|
||||
_PsGetThreadTeb = .; . += 8;
|
||||
_PsGetContextThread = .; . += 8;
|
||||
_PsSetContextThread = .; . += 8;
|
||||
_KeStackAttachProcess = .; . += 8;
|
||||
_KeUnstackDetachProcess = .; . += 8;
|
||||
_ZwCreateFile = .; . += 8;
|
||||
_ZwOpenKey = .; . += 8;
|
||||
_ZwEnumerateKey = .; . += 8;
|
||||
_ZwQueryValueKey = .; . += 8;
|
||||
_ZwReadFile = .; . += 8;
|
||||
_ZwWriteFile = .; . += 8;
|
||||
_ZwEnumerateValueKey = .; . += 8;
|
||||
_ZwDeviceIoControlFile = .; . += 8;
|
||||
_ZwClose = .; . += 8;
|
||||
_ZwCreateEvent = .; . += 8;
|
||||
_ExAllocatePool2 = .; . += 8;
|
||||
_MmCopyVirtualMemory = .; . += 8;
|
||||
_KeIpiGenericCall = .; . += 8;
|
||||
_MmIsAddressValid = .; . += 8;
|
||||
_ZwClearEvent = .; . += 8;
|
||||
_ZwWaitForSingleObject = .; . += 8;
|
||||
_ZwAllocateVirtualMemory = .; . += 8;
|
||||
_ZwFreeVirtualMemory = .; . += 8;
|
||||
_ZwCreateThread = .; . += 8;
|
||||
_PsProcessType = .; . += 8;
|
||||
_ObReferenceObjectByHandle = .; . += 8;
|
||||
_ObOpenObjectByPointer = .; . += 8;
|
||||
_ExEventObjectType = .; . += 8;
|
||||
_IoAllocateMdl = .; . += 8;
|
||||
_IoFreeMdl = .; . += 8;
|
||||
_MmBuildMdlForNonPagedPool = .; . += 8;
|
||||
_MmMapLockedPagesSpecifyCache = .; . += 8;
|
||||
_MmUnmapLockedPages = .; . += 8;
|
||||
}
|
||||
/* Everything above is only ever read, so a process being instrumented can be shown the very
|
||||
same pages; everything below it each copy needs its own of. */
|
||||
. = ALIGN(4096);
|
||||
_agent_private_start = .;
|
||||
.data.rel.ro : {
|
||||
*(.data.rel.ro*)
|
||||
}
|
||||
.got : {
|
||||
*(.got*)
|
||||
}
|
||||
.init_array : {
|
||||
_agent_init_start = .;
|
||||
*(.init_array*)
|
||||
_agent_init_end = .;
|
||||
}
|
||||
.data : {
|
||||
*(.data*)
|
||||
*(.ldata*)
|
||||
}
|
||||
.bss : {
|
||||
*(.bss*)
|
||||
*(.lbss*)
|
||||
}
|
||||
. = ALIGN(8);
|
||||
_heap_start = .;
|
||||
}
|
||||
|
||||
VERSION {
|
||||
{
|
||||
global:
|
||||
_start;
|
||||
frida_winnt_user_main;
|
||||
frida_winnt_user_bootstrap;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
SECTIONS {
|
||||
.text : {
|
||||
*(.text.entrypoint)
|
||||
*(.text*)
|
||||
}
|
||||
.rela.dyn : {
|
||||
_agent_relocs_start = .;
|
||||
*(.rela.dyn)
|
||||
_agent_relocs_end = .;
|
||||
}
|
||||
.rodata : {
|
||||
*(.rodata*)
|
||||
}
|
||||
.kernel_addrs : ALIGN(8) {
|
||||
. = ALIGN(8);
|
||||
_panic = .; . += 8;
|
||||
_IOLog = .; . += 8;
|
||||
_kalloc = .; . += 8;
|
||||
_proc_iterate = .; . += 8;
|
||||
_proc_find = .; . += 8;
|
||||
_proc_rele = .; . += 8;
|
||||
_proc_pid = .; . += 8;
|
||||
_proc_best_name = .; . += 8;
|
||||
_proc_task = .; . += 8;
|
||||
_current_task = .; . += 8;
|
||||
_current_thread = .; . += 8;
|
||||
_get_task_map = .; . += 8;
|
||||
_mach_vm_allocate = .; . += 8;
|
||||
_mach_vm_deallocate = .; . += 8;
|
||||
_mach_vm_protect = .; . += 8;
|
||||
_mach_vm_remap = .; . += 8;
|
||||
_vm_map_copyin = .; . += 8;
|
||||
_vm_map_copyout = .; . += 8;
|
||||
_thread_create = .; . += 8;
|
||||
_thread_set_state = .; . += 8;
|
||||
_thread_resume = .; . += 8;
|
||||
_thread_terminate = .; . += 8;
|
||||
_task_suspend = .; . += 8;
|
||||
_task_resume = .; . += 8;
|
||||
_task_clear_return_wait = .; . += 8;
|
||||
_get_bsdtask_info = .; . += 8;
|
||||
_thread_suspend = .; . += 8;
|
||||
_kfree = .; . += 8;
|
||||
_kernel_thread_start = .; . += 8;
|
||||
_assert_wait = .; . += 8;
|
||||
_assert_wait_timeout = .; . += 8;
|
||||
_thread_block = .; . += 8;
|
||||
_thread_wakeup = .; . += 8;
|
||||
_thread_wakeup_prim = .; . += 8;
|
||||
_mach_absolute_time = .; . += 8;
|
||||
_absolutetime_to_nanoseconds = .; . += 8;
|
||||
_clock_get_calendar_microtime = .; . += 8;
|
||||
_ml_io_map = .; . += 8;
|
||||
_ml_vtophys = .; . += 8;
|
||||
_ml_static_vtop = .; . += 8;
|
||||
__ZN9IOService11getPlatformEv = .; . += 8;
|
||||
__ZN9IOServiceC2Ev = .; . += 8;
|
||||
__ZN8OSSymbol17withCStringNoCopyEPKc = .; . += 8;
|
||||
__ZN6OSData9withBytesEPKvj = .; . += 8;
|
||||
_kalloc_data = .; . += 8;
|
||||
_kfree_data = .; . += 8;
|
||||
_wakeup = .; . += 8;
|
||||
_sock_socket = .; . += 8;
|
||||
_sock_connect = .; . += 8;
|
||||
_sock_send = .; . += 8;
|
||||
_sock_receive = .; . += 8;
|
||||
_sock_close = .; . += 8;
|
||||
_sysent = .; . += 8;
|
||||
_task_restartable_ranges_register = .; . += 8;
|
||||
_task_threads = .; . += 8;
|
||||
_mach_vm_region = .; . += 8;
|
||||
_mach_vm_region_recurse = .; . += 8;
|
||||
_copyin = .; . += 8;
|
||||
_copyout = .; . += 8;
|
||||
_ulock_wake = .; . += 8;
|
||||
_task_info = .; . += 8;
|
||||
_convert_port_to_thread = .; . += 8;
|
||||
_convert_port_to_thread_read = .; . += 8;
|
||||
_convert_port_to_thread_inspect = .; . += 8;
|
||||
}
|
||||
. = ALIGN(16384);
|
||||
_agent_private_start = .;
|
||||
.data.rel.ro : {
|
||||
*(.data.rel.ro*)
|
||||
}
|
||||
.got : {
|
||||
*(.got*)
|
||||
}
|
||||
.init_array : {
|
||||
_agent_init_start = .;
|
||||
*(.init_array*)
|
||||
_agent_init_end = .;
|
||||
}
|
||||
.data : {
|
||||
*(.data*)
|
||||
}
|
||||
.bss : {
|
||||
*(.bss*)
|
||||
}
|
||||
. = ALIGN(8);
|
||||
_heap_start = .;
|
||||
}
|
||||
|
||||
VERSION {
|
||||
{
|
||||
global:
|
||||
_start;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
SECTIONS {
|
||||
.text : {
|
||||
*(.text.entrypoint)
|
||||
*(.text*)
|
||||
}
|
||||
.rodata : {
|
||||
*(.rodata*)
|
||||
}
|
||||
.kernel_addrs : ALIGN(8) {
|
||||
. = ALIGN(8);
|
||||
_panic = .; . += 8;
|
||||
_IOLog = .; . += 8;
|
||||
_kalloc = .; . += 8;
|
||||
_kfree = .; . += 8;
|
||||
_kernel_thread_start = .; . += 8;
|
||||
_assert_wait = .; . += 8;
|
||||
_assert_wait_timeout = .; . += 8;
|
||||
_thread_block = .; . += 8;
|
||||
_thread_wakeup = .; . += 8;
|
||||
_mach_absolute_time = .; . += 8;
|
||||
_absolutetime_to_nanoseconds = .; . += 8;
|
||||
_clock_get_calendar_microtime = .; . += 8;
|
||||
_ml_io_map = .; . += 8;
|
||||
_ml_vtophys = .; . += 8;
|
||||
__ZN9IOService11getPlatformEv = .; . += 8;
|
||||
__ZN9IOServiceC2Ev = .; . += 8;
|
||||
__ZN8OSSymbol17withCStringNoCopyEPKc = .; . += 8;
|
||||
__ZN6OSData9withBytesEPKvj = .; . += 8;
|
||||
_kalloc_data = .; . += 8;
|
||||
_kfree_data = .; . += 8;
|
||||
_wakeup = .; . += 8;
|
||||
_sock_socket = .; . += 8;
|
||||
_sock_connect = .; . += 8;
|
||||
_sock_send = .; . += 8;
|
||||
_sock_receive = .; . += 8;
|
||||
_sock_close = .; . += 8;
|
||||
}
|
||||
.data.rel.ro : {
|
||||
*(.data.rel.ro*)
|
||||
}
|
||||
.got : {
|
||||
*(.got*)
|
||||
}
|
||||
.bss : {
|
||||
*(.bss*)
|
||||
}
|
||||
. = ALIGN(8);
|
||||
_heap_start = .;
|
||||
}
|
||||
|
||||
VERSION {
|
||||
{
|
||||
global:
|
||||
_start;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"abi": "eabi",
|
||||
"arch": "arm",
|
||||
"crt-objects-fallback": "false",
|
||||
"data-layout": "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64",
|
||||
"features": "+v7,+thumb2,+thumb-mode,+soft-float,+strict-align,-neon",
|
||||
"linker": "rust-lld",
|
||||
"linker-flavor": "gnu-lld",
|
||||
"llvm-floatabi": "soft",
|
||||
"llvm-target": "armv7a-none-eabi",
|
||||
"max-atomic-width": 64,
|
||||
"panic-strategy": "abort",
|
||||
"plt-by-default": false,
|
||||
"position-independent-executables": true,
|
||||
"relocation-model": "pic",
|
||||
"target-pointer-width": 32
|
||||
}
|
||||
+23
-148
@@ -8,17 +8,12 @@ fn main() {
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
let devkit_dir = PathBuf::from(env::var("GUMJS_DEVKIT_DIR").unwrap());
|
||||
|
||||
let target = env::var("TARGET").unwrap();
|
||||
let cc_str = env::var(format!("CC_{}", target.replace('-', "_")))
|
||||
let cc_str = env::var("CC_aarch64_unknown_none")
|
||||
.or_else(|_| env::var("CC"))
|
||||
.unwrap_or_else(|_| "cc".to_string());
|
||||
// Same convention as cc-rs: the variable may carry flags, and a bare-metal
|
||||
// compiler needs them to find its libc at all.
|
||||
let mut cc_argv = cc_str.split_whitespace();
|
||||
let cc = Path::new(cc_argv.next().unwrap());
|
||||
let cc_args: Vec<&str> = cc_argv.collect();
|
||||
let cc_include_paths = detect_gcc_include_paths(cc, &cc_args);
|
||||
let cc_library_paths = detect_gcc_library_paths(cc, &cc_args);
|
||||
let cc = Path::new(&cc_str);
|
||||
let cc_include_paths = detect_gcc_include_paths(cc);
|
||||
let cc_library_paths = detect_gcc_library_paths(cc);
|
||||
|
||||
let cc_clang_args: Vec<String> = cc_include_paths
|
||||
.iter()
|
||||
@@ -30,15 +25,9 @@ fn main() {
|
||||
.header(devkit_dir.join("frida-gumjs.h").to_str().unwrap())
|
||||
.clang_arg("-nostdinc")
|
||||
.clang_arg("-target")
|
||||
.clang_arg(format!("{}-none-elf", target.split('-').next().unwrap()))
|
||||
.clang_arg("aarch64-none-elf")
|
||||
.clang_args(cc_clang_args)
|
||||
.clang_arg(format!("-I{}", devkit_dir.to_str().unwrap()))
|
||||
.clang_arg("-include")
|
||||
.clang_arg("stddef.h")
|
||||
.clang_arg("-include")
|
||||
.clang_arg("stdint.h")
|
||||
.blocklist_type("max_align_t")
|
||||
.blocklist_function("memcpy|memmove|memset|memcmp|bcmp|strlen")
|
||||
.merge_extern_blocks(true)
|
||||
.generate()
|
||||
.expect("Unable to generate bindings");
|
||||
@@ -47,131 +36,25 @@ fn main() {
|
||||
.write_to_file(out_dir.join("bindings.rs"))
|
||||
.expect("Couldn't write bindings");
|
||||
|
||||
// The Linux flavour is a staticlib: kbuild owns the final link, so it — not
|
||||
// us — decides where the devkit and libc archives come from.
|
||||
if env::var("CARGO_FEATURE_BLOB").is_ok() {
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
devkit_dir.to_str().unwrap()
|
||||
);
|
||||
println!("cargo:rustc-link-lib=static=frida-gumjs");
|
||||
for path in cc_library_paths {
|
||||
println!("cargo:rustc-link-search=native={}", path.to_string_lossy());
|
||||
}
|
||||
for arg in &cc_args {
|
||||
if let Some(path) = arg.strip_prefix("-L") {
|
||||
println!("cargo:rustc-link-search=native={path}");
|
||||
}
|
||||
}
|
||||
println!("cargo:rustc-link-lib=static=c");
|
||||
println!("cargo:rustc-link-lib=static=m");
|
||||
println!("cargo:rustc-link-arg=--export-dynamic");
|
||||
println!("cargo:rustc-link-arg=--emit-relocs");
|
||||
let flavor = if env::var("CARGO_FEATURE_WIN9X").is_ok() {
|
||||
"win9x"
|
||||
} else if env::var("CARGO_FEATURE_WINNT").is_ok() {
|
||||
"winnt"
|
||||
} else if env::var("CARGO_FEATURE_LINUX_INJECTED").is_ok() {
|
||||
"linux"
|
||||
} else {
|
||||
"xnu"
|
||||
};
|
||||
let arch = match target.split('-').next().unwrap() {
|
||||
"i686" => "x86",
|
||||
"armv7a" => "arm",
|
||||
"aarch64" => "arm64",
|
||||
other => other,
|
||||
};
|
||||
let script = format!("agent-{flavor}-{arch}.lds");
|
||||
println!("cargo:rustc-link-arg=--script={script}");
|
||||
println!("cargo:rustc-link-arg=--gc-sections");
|
||||
// All of the agent lives in the library crate, so nothing in the binary
|
||||
// references the entrypoint the host calls; keep the linker from
|
||||
// dropping it along with the rest of the unreferenced archive members.
|
||||
println!("cargo:rustc-link-arg=--undefined=_start");
|
||||
|
||||
let note = compile_version_note(&out_dir, cc, &cc_args, arch);
|
||||
println!("cargo:rustc-link-arg={}", note.to_string_lossy());
|
||||
}
|
||||
|
||||
if target.starts_with("i686") && env::var("CARGO_FEATURE_LINUX_INJECTED").is_ok() {
|
||||
let shim = out_dir.join("regparm.o");
|
||||
let status = Command::new(cc)
|
||||
.args(&cc_args)
|
||||
.args(["-m32", "-c", "-O2", "-fPIC", "-ffreestanding", "-o"])
|
||||
.arg(&shim)
|
||||
.arg("src/linux/regparm.c")
|
||||
.status()
|
||||
.expect("Couldn't run the compiler");
|
||||
assert!(status.success(), "Couldn't compile the kernel-call shim");
|
||||
|
||||
println!("cargo:rustc-link-arg={}", shim.to_string_lossy());
|
||||
println!("cargo:rerun-if-changed=src/linux/regparm.c");
|
||||
}
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-env-changed=GUMJS_DEVKIT_DIR");
|
||||
println!("cargo:rerun-if-env-changed=FRIDA_VERSION");
|
||||
println!("cargo:rerun-if-changed={}", devkit_dir.join("frida-gumjs.h").display());
|
||||
}
|
||||
|
||||
fn compile_version_note(out_dir: &Path, cc: &Path, cc_args: &[&str], arch: &str) -> PathBuf {
|
||||
let version = env::var("FRIDA_VERSION")
|
||||
.ok()
|
||||
.filter(|v| !v.is_empty())
|
||||
.unwrap_or_else(|| "0.0.0".to_string());
|
||||
let payload = format!(
|
||||
"{{\"type\":\"frida\",\"name\":\"frida-barebone-agent\",\
|
||||
\"version\":\"{version}\",\"architecture\":\"{arch}\"}}"
|
||||
println!(
|
||||
"cargo:rustc-link-search=native={}",
|
||||
devkit_dir.to_str().unwrap()
|
||||
);
|
||||
|
||||
let source = out_dir.join("version-note.S");
|
||||
fs::write(
|
||||
&source,
|
||||
format!(
|
||||
".section .note.package,\"\",%note\n\
|
||||
.balign 4\n\
|
||||
.long 2f - 1f\n\
|
||||
.long 4f - 3f\n\
|
||||
.long 0xcafe1a7e\n\
|
||||
1: .asciz \"FDO\"\n\
|
||||
2: .balign 4\n\
|
||||
3: .asciz {payload:?}\n\
|
||||
4: .balign 4\n"
|
||||
),
|
||||
)
|
||||
.expect("Couldn't write the version note");
|
||||
|
||||
let object = out_dir.join("version-note.o");
|
||||
let status = Command::new(cc)
|
||||
.args(cc_args)
|
||||
.args(["-target", &llvm_target(), "-c", "-o"])
|
||||
.arg(&object)
|
||||
.arg(&source)
|
||||
.status()
|
||||
.expect("Couldn't run the compiler");
|
||||
assert!(status.success(), "Couldn't assemble the version note");
|
||||
|
||||
object
|
||||
println!("cargo:rustc-link-lib=static=frida-gumjs");
|
||||
for path in cc_library_paths {
|
||||
println!("cargo:rustc-link-search=native={}", path.to_string_lossy());
|
||||
}
|
||||
println!("cargo:rustc-link-lib=static=c");
|
||||
println!("cargo:rustc-link-lib=static=m");
|
||||
println!("cargo:rustc-link-arg=--export-dynamic");
|
||||
println!("cargo:rustc-link-arg=--emit-relocs");
|
||||
println!("cargo:rustc-link-arg=--script=agent.lds");
|
||||
println!("cargo:rustc-link-arg=--gc-sections");
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
}
|
||||
|
||||
fn llvm_target() -> String {
|
||||
let target = env::var("TARGET").unwrap();
|
||||
let spec = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join(format!("{target}.json"));
|
||||
let text = fs::read_to_string(&spec).expect("Couldn't read the target specification");
|
||||
|
||||
let key = "\"llvm-target\"";
|
||||
let at = text.find(key).expect("Target specification names no LLVM target");
|
||||
let rest = &text[at + key.len()..];
|
||||
let open = rest.find('"').unwrap() + 1;
|
||||
let close = open + rest[open..].find('"').unwrap();
|
||||
|
||||
rest[open..close].to_string()
|
||||
}
|
||||
|
||||
pub fn detect_gcc_include_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
|
||||
pub fn detect_gcc_include_paths(gcc: &Path) -> Vec<PathBuf> {
|
||||
let out = Command::new(gcc)
|
||||
.args(args)
|
||||
.args(["-xc", "-E", "-v", "-"])
|
||||
.stdin(Stdio::null())
|
||||
.output()
|
||||
@@ -201,7 +84,7 @@ pub fn detect_gcc_include_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
|
||||
|
||||
let p = Path::new(raw);
|
||||
if p.exists() {
|
||||
let canonical = without_extended_length_prefix(fs::canonicalize(p).expect("Failed to canonicalize include path"));
|
||||
let canonical = fs::canonicalize(p).expect("Failed to canonicalize include path");
|
||||
if !paths.contains(&canonical) {
|
||||
paths.push(canonical);
|
||||
}
|
||||
@@ -211,16 +94,8 @@ pub fn detect_gcc_include_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
|
||||
paths
|
||||
}
|
||||
|
||||
fn without_extended_length_prefix(path: PathBuf) -> PathBuf {
|
||||
let text = path.to_string_lossy();
|
||||
match text.strip_prefix(r"\\?\") {
|
||||
Some(rest) => PathBuf::from(rest),
|
||||
None => path,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detect_gcc_library_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
|
||||
let out = Command::new(gcc).args(args).arg("-print-search-dirs").output()
|
||||
pub fn detect_gcc_library_paths(gcc: &Path) -> Vec<PathBuf> {
|
||||
let out = Command::new(gcc).arg("-print-search-dirs").output()
|
||||
.expect("Failed to execute GCC to detect library paths");
|
||||
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export FRIDA_BAREBONE_CONFIG=$PWD/etc/xnu.json
|
||||
export PYTHONPATH=$HOME/src/frida-python
|
||||
|
||||
sdk=$HOME/src/frida-core/deps/sdk-none-arm64-softfloat_pic
|
||||
export CC="clang --target=aarch64-none-elf -ffixed-x18 -I$sdk/include -I$sdk/include/glib-2.0 -I$sdk/lib/glib-2.0/include \
|
||||
-I$sdk/include/capstone -I$sdk/include/json-glib-1.0"
|
||||
export RUSTFLAGS="-L native=$sdk/lib"
|
||||
export GUMJS_DEVKIT_DIR=$HOME/src/frida-gum/build/none-arm64-softfloat_pic/bindings/gumjs/devkit
|
||||
export PATH=$PWD/xpacks/.bin:$PATH
|
||||
export CC_aarch64_unknown_none=aarch64-none-elf-gcc
|
||||
export AR_aarch64_unknown_none=aarch64-none-elf-ar
|
||||
export RANLIB_aarch64_unknown_none=aarch64-none-elf-ranlib
|
||||
export GUMJS_DEVKIT_DIR=/home/oleavr/src/frida-gum/build/bindings/gumjs/devkit
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"agent": {
|
||||
"transport": {
|
||||
"type": "socket",
|
||||
"path": "/tmp/frida-guest.sock"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"agent": {
|
||||
"transport": {
|
||||
"type": "device",
|
||||
"path": "/dev/frida"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,6 @@
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"type": "xnu-kernelcache",
|
||||
"file": "/Users/oleavr/src/vphone-cli/vm/iPhone17,3_26.1_23B85_Restore/kernelcache.research.vresearch101",
|
||||
"symbols": {
|
||||
"panic": "0x12a2814",
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"connection": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 1234
|
||||
},
|
||||
"kernel": "win9x",
|
||||
"agent": {
|
||||
"path": "/home/oleavr/src/frida-core/src/barebone/agent/target-win9x/i686-unknown-none-pic/release/frida-barebone-agent",
|
||||
"transport": {
|
||||
"type": "hostlink",
|
||||
"qmp": "unix:/tmp/claude-1000/-home-oleavr-src-frida-core/0f3e358c-8f66-4246-b793-deb2cd3727f3/scratchpad/qmp.sock",
|
||||
"bus": "frida-vserial.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
{
|
||||
"connection": {
|
||||
"host": "127.0.0.1",
|
||||
"port": 1234
|
||||
},
|
||||
"kernel": "winnt",
|
||||
"agent": {
|
||||
"path": "/home/oleavr/src/frida-core/src/barebone/agent/target-winnt/i686-unknown-none-pic/release/frida-barebone-agent",
|
||||
"transport": {
|
||||
"type": "hostlink",
|
||||
"qmp": "unix:/tmp/qmp.sock",
|
||||
"bus": "frida-vserial.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,6 @@
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"type": "xnu-kernelcache",
|
||||
"file": "/Users/oleavr/src/frida-barebone-ios/iPhone11_8_iPhone12_1_14.0_18A5351d_Restore/kernelcache.research.iphone12b",
|
||||
"base": "0xfffffff007004000",
|
||||
"symbols": {
|
||||
|
||||
@@ -5,6 +5,6 @@ const cm = new CModule(File.readAllBytes("./target/aarch64-unknown-none/release/
|
||||
const start = new NativeFunction(cm._start, "pointer", []);
|
||||
const bufferPhysicalAddress = start();
|
||||
console.log("Ready! Buffer is at physical address:", bufferPhysicalAddress);
|
||||
$debugger.continue();
|
||||
$gdb.continue();
|
||||
|
||||
Object.assign(globalThis, { cm, start });
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"arch": "x86",
|
||||
"cpu": "pentium4",
|
||||
"crt-objects-fallback": "false",
|
||||
"data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
|
||||
"features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float",
|
||||
"linker": "rust-lld",
|
||||
"linker-flavor": "gnu-lld",
|
||||
"llvm-target": "i686-unknown-none-elf",
|
||||
"max-atomic-width": 64,
|
||||
"panic-strategy": "abort",
|
||||
"plt-by-default": false,
|
||||
"position-independent-executables": true,
|
||||
"relocation-model": "pic",
|
||||
"rustc-abi": "softfloat",
|
||||
"stack-probes": {
|
||||
"kind": "inline"
|
||||
},
|
||||
"target-pointer-width": 32
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"arch": "x86",
|
||||
"cpu": "pentium4",
|
||||
"crt-objects-fallback": "false",
|
||||
"data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
|
||||
"features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float",
|
||||
"linker": "rust-lld",
|
||||
"linker-flavor": "gnu-lld",
|
||||
"llvm-target": "i686-unknown-none-elf",
|
||||
"max-atomic-width": 64,
|
||||
"panic-strategy": "abort",
|
||||
"plt-by-default": true,
|
||||
"position-independent-executables": false,
|
||||
"relocation-model": "static",
|
||||
"rustc-abi": "softfloat",
|
||||
"stack-probes": {
|
||||
"kind": "inline"
|
||||
},
|
||||
"target-pointer-width": 32
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
MODULE_NAME ?= frida-agent
|
||||
|
||||
obj-m += $(MODULE_NAME).o
|
||||
|
||||
# frida-agent-prelinked.o is not built from source here: it is the Rust agent, the
|
||||
# GumJS devkit and the C library collapsed into a single relocatable object by the prelink
|
||||
# step in Makefile. Copy it in through if_changed rather than kbuild's _shipped rule,
|
||||
# so the .cmd file modpost expects gets written.
|
||||
$(MODULE_NAME)-y := frida-kmod.o frida-agent-prelinked.o
|
||||
|
||||
targets += frida-agent-prelinked.o
|
||||
|
||||
quiet_cmd_frida_agent_prelinked = COPY $@
|
||||
cmd_frida_agent_prelinked = cp $< $@
|
||||
|
||||
$(obj)/frida-agent-prelinked.o: $(src)/frida-agent-prelinked.o.bin FORCE
|
||||
$(call if_changed,frida_agent_prelinked)
|
||||
|
||||
# On x86 objtool validates every object going into a module and generates its ORC
|
||||
# unwind tables. It was not built to read a JavaScript runtime linked against a
|
||||
# freestanding libc: it gives up on the first Rust switch jump table it cannot
|
||||
# follow, and the tables would only ever describe frames no kernel stack trace walks
|
||||
# into. Both the blob and the module object it is linked into have to be excused,
|
||||
# since objtool runs over each.
|
||||
OBJECT_FILES_NON_STANDARD_frida-agent-prelinked.o := y
|
||||
OBJECT_FILES_NON_STANDARD_$(MODULE_NAME).o := y
|
||||
|
||||
ccflags-y += $(FRIDA_KMOD_DEFS)
|
||||
@@ -1,206 +0,0 @@
|
||||
# Builds frida-agent.ko in two steps, because kbuild insists on owning the final
|
||||
# link:
|
||||
#
|
||||
# 1. Collapse the Rust staticlib plus everything it needs — the GumJS devkit and
|
||||
# the C library — into one relocatable object, and localize every symbol except
|
||||
# the entry points frida-kmod.c calls. Localizing matters: Gum and the C library
|
||||
# define names like memcpy and malloc that must not leak into the kernel's
|
||||
# namespace.
|
||||
# 2. Hand that object to kbuild as a shipped object, so vermagic, modinfo and
|
||||
# CRCs all come from the target kernel's own build system.
|
||||
#
|
||||
# See README.md for usage.
|
||||
|
||||
# KDIR is the kernel to build against. When it is a source tree that was configured
|
||||
# elsewhere — which is what Android's modules_prepare tarball gives — point KOUT at
|
||||
# the prepared tree.
|
||||
KDIR ?= /lib/modules/$(shell uname -r)/build
|
||||
PROFILE ?= release
|
||||
|
||||
# The kernel's own name for the architecture, and the one Rust knows it by. Both
|
||||
# follow from the machine this is built for, which defaults to the one building.
|
||||
ARCH ?= $(shell uname -m \
|
||||
| sed -e 's/^i[3-6]86$$/x86/' -e 's/^x86_64$$/x86/' -e 's/^aarch64$$/arm64/')
|
||||
BITS ?= $(shell uname -m \
|
||||
| sed -e 's/^i[3-6]86$$/32/' -e 's/^x86_64$$/64/' -e 's/^aarch64$$/64/')
|
||||
|
||||
ifeq ($(ARCH)-$(BITS),x86-32)
|
||||
RUST_TARGET ?= i686-unknown-none
|
||||
AGENT_TARGET_FLAGS ?= --target $(AGENT_DIR)/$(RUST_TARGET).json -Z json-target-spec
|
||||
SDK_ARCH ?= x86
|
||||
TOOLPREFIX ?=
|
||||
AGENT_LD_EMULATION ?= elf_i386
|
||||
else ifeq ($(ARCH)-$(BITS),x86-64)
|
||||
RUST_TARGET ?= x86_64-unknown-none
|
||||
SDK_ARCH ?= x86_64
|
||||
TOOLPREFIX ?=
|
||||
AGENT_LD_EMULATION ?= elf_x86_64
|
||||
else ifeq ($(ARCH)-$(BITS),arm64-64)
|
||||
RUST_TARGET ?= aarch64-unknown-none
|
||||
SDK_ARCH ?= arm64
|
||||
TOOLPREFIX ?= aarch64-none-elf-
|
||||
else
|
||||
$(error Unsupported ARCH "$(ARCH)" and BITS "$(BITS)"; this flavour covers \
|
||||
x86/32, x86/64 and arm64/64)
|
||||
endif
|
||||
AGENT_TARGET_FLAGS ?= --target $(RUST_TARGET)
|
||||
|
||||
AGENT_DIR := $(CURDIR)/..
|
||||
AGENT_TARGET_DIR := $(AGENT_DIR)/target-linux
|
||||
AGENT_LIB := $(AGENT_TARGET_DIR)/$(RUST_TARGET)/$(PROFILE)/libfrida_barebone_agent.a
|
||||
|
||||
GUMJS_DEVKIT_DIR ?= $(error Set GUMJS_DEVKIT_DIR to the directory holding libfrida-gumjs.a)
|
||||
|
||||
# Any binutils that speak the target's ELF will do; nothing here is compiled with
|
||||
# them. Building for the host's own architecture, that is the host's binutils.
|
||||
AGENT_LD ?= $(TOOLPREFIX)ld
|
||||
AGENT_AR ?= $(TOOLPREFIX)ar
|
||||
AGENT_NM ?= $(TOOLPREFIX)nm
|
||||
AGENT_OBJCOPY ?= $(TOOLPREFIX)objcopy
|
||||
|
||||
# The ABI the kernel imposes on anything linked into it. The flags only reach crates
|
||||
# Cargo compiles, and the toolchain ships core, alloc and compiler_builtins prebuilt
|
||||
# without them, so those three have to be rebuilt from source as well. That needs the
|
||||
# rust-src component and an unstable flag, hence RUSTC_BOOTSTRAP.
|
||||
#
|
||||
# On x86_64 that is endbr64 on every address-taken function, since a kernel with
|
||||
# CONFIG_X86_KERNEL_IBT faults an indirect call landing anywhere else, and switches
|
||||
# compiled as comparisons rather than jump tables, which objtool cannot follow into.
|
||||
# Soft-float, the kernel code model and the absent red zone come with the target
|
||||
# itself. On arm64
|
||||
# it is x18, which the kernel reserves for the shadow call stack, so nothing linked in
|
||||
# may use it as a general-purpose register.
|
||||
ifeq ($(ARCH)-$(BITS),x86-32)
|
||||
AGENT_RUSTFLAGS ?=
|
||||
AGENT_CFLAGS ?= --target=i386-none-elf \
|
||||
-Xclang -target-feature -Xclang +soft-float -mno-mmx -mno-sse \
|
||||
-mlong-double-64 -fno-pic --sysroot=$(FRIDA_SDK)
|
||||
else ifeq ($(ARCH)-$(BITS),x86-64)
|
||||
AGENT_RUSTFLAGS ?= -Zcf-protection=branch \
|
||||
-Cllvm-args=--min-jump-table-entries=65536 \
|
||||
-Crelocation-model=static -Zplt=yes
|
||||
AGENT_CFLAGS ?= --target=x86_64-none-elf \
|
||||
-Xclang -target-feature -Xclang +soft-float -mno-mmx -mno-sse -mno-red-zone \
|
||||
-mlong-double-64 -mcmodel=kernel -fcf-protection=branch -fno-jump-tables \
|
||||
-fno-pic --sysroot=$(FRIDA_SDK)
|
||||
else
|
||||
AGENT_RUSTFLAGS ?= -Zfixed-x18
|
||||
AGENT_CFLAGS ?= --target=aarch64-none-elf -mabi=aapcs-soft -mgeneral-regs-only \
|
||||
-ffixed-x18 -fno-pic --sysroot=$(FRIDA_SDK)
|
||||
endif
|
||||
AGENT_BUILD_STD ?= -Z build-std=core,alloc,compiler_builtins
|
||||
|
||||
# The devkit header reaches for GLib's and capstone's, which a devkit built against
|
||||
# an SDK leaves in that SDK rather than beside itself. The sysroot's own include
|
||||
# directory is spelled out because only clang's bare-metal driver, which it selects
|
||||
# for arm64 but not for x86_64, searches there on its own.
|
||||
AGENT_CFLAGS += -isystem $(FRIDA_SDK)/include \
|
||||
-I$(FRIDA_SDK)/include/glib-2.0 \
|
||||
-I$(FRIDA_SDK)/lib/glib-2.0/include \
|
||||
-I$(FRIDA_SDK)/include/capstone \
|
||||
-I$(FRIDA_SDK)/include/json-glib-1.0
|
||||
|
||||
# The soft-float SDK, whose libc is picolibc and whose compiler runtime is
|
||||
# compiler-rt. GCC's own newlib and libgcc are hard-float and cannot be used here.
|
||||
FRIDA_SDK ?= $(error Set FRIDA_SDK to a none-$(SDK_ARCH)-softfloat_nopic SDK prefix)
|
||||
LIBM = $(FRIDA_SDK)/lib/libm.a
|
||||
LIBBUILTINS = $(FRIDA_SDK)/lib/libclang_rt.builtins.a
|
||||
|
||||
# bindgen reads the devkit header through whatever this compiler reports as its
|
||||
# include path, so it has to be one that knows where the SDK keeps its libc. It has
|
||||
# to be clang either way: -mabi=aapcs-soft landed there in 19 and GCC has it at no
|
||||
# version, and on x86_64 the soft-float ABI is reachable only as an -Xclang feature.
|
||||
AGENT_CC ?= clang
|
||||
|
||||
PRELINKED := frida-agent-prelinked.o.bin
|
||||
|
||||
AGENT_LIB_SOFTFLOAT := $(AGENT_TARGET_DIR)/libfrida-barebone-agent-softfloat.a
|
||||
|
||||
# On arm64 the toolchain's own compiler_builtins is compiled for the ordinary AAPCS,
|
||||
# where a double travels in d0: its __muldf3 opens with "fmov x14, d0". Everything
|
||||
# else there is -mabi=aapcs-soft and passes that same double in x0. Whole-archiving
|
||||
# the staticlib puts those definitions in front of the SDK's compiler-rt, which has
|
||||
# the soft-float ones, and every double the C library computes then comes back
|
||||
# garbage — quietly, since the arithmetic still runs. Drop them and let compiler-rt
|
||||
# answer instead. x86_64 needs none of this: soft-float is part of the Rust target
|
||||
# there, so the copy build-std compiles already passes doubles the way the C half
|
||||
# expects.
|
||||
ifeq ($(ARCH),arm64)
|
||||
DROP_HARDFLOAT_BUILTINS = \
|
||||
$(AGENT_AR) d $@ $$($(AGENT_NM) --print-armap $@ | sed -n 's/^__muldf3 in //p') && \
|
||||
$(AGENT_AR) s $@
|
||||
else
|
||||
DROP_HARDFLOAT_BUILTINS = :
|
||||
endif
|
||||
|
||||
# The agent puts malloc, the C library's locks and its process stubs on the kernel's
|
||||
# own facilities, so picolibc's versions are duplicate definitions rather than
|
||||
# fallbacks. Work out which members they live in instead of naming them: the set
|
||||
# follows from what the agent defines, and picolibc moves things between releases.
|
||||
LIBC := $(AGENT_TARGET_DIR)/libc-trimmed.a
|
||||
|
||||
ENTRY_POINTS := frida_agent_start frida_agent_stop \
|
||||
gum_interceptor_obtain gum_interceptor_begin_transaction \
|
||||
gum_interceptor_end_transaction gum_interceptor_replace \
|
||||
gum_interceptor_revert
|
||||
KEEP_GLOBAL := $(foreach s,$(ENTRY_POINTS),--keep-global-symbol=$(s))
|
||||
|
||||
# Two structures the reparenting path reaches into lost a member upstream, and
|
||||
# neither change announces itself as anything frida-kmod.c could test for. The
|
||||
# headers it is compiled against are the authority on both, so ask them.
|
||||
FRIDA_KMOD_DEFS := \
|
||||
$(if $(shell grep -l 'list_head[[:space:]]*thread_group;' \
|
||||
$(KDIR)/include/linux/sched.h),-DFRIDA_HAVE_TASK_THREAD_GROUP) \
|
||||
$(if $(shell grep -l 'spinlock_t[[:space:]]*lock;' \
|
||||
$(KDIR)/include/linux/fs_struct.h),-DFRIDA_HAVE_FS_STRUCT_LOCK)
|
||||
|
||||
all: modules
|
||||
|
||||
# A module that dies in its init path is left wedged under its own name until the
|
||||
# next boot, so bring-up wants a fresh one per attempt.
|
||||
MODULE_NAME ?= frida-agent
|
||||
|
||||
modules: $(PRELINKED)
|
||||
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) \
|
||||
FRIDA_KMOD_DEFS="$(FRIDA_KMOD_DEFS)" MODULE_NAME=$(MODULE_NAME) modules
|
||||
|
||||
$(PRELINKED): $(AGENT_LIB_SOFTFLOAT) $(LIBC) prelink.lds
|
||||
$(AGENT_LD) -r $(if $(AGENT_LD_EMULATION),-m $(AGENT_LD_EMULATION)) \
|
||||
-T prelink.lds -o $@.tmp \
|
||||
--whole-archive $(AGENT_LIB_SOFTFLOAT) --no-whole-archive \
|
||||
--start-group \
|
||||
$(GUMJS_DEVKIT_DIR)/libfrida-gumjs.a \
|
||||
$(LIBC) \
|
||||
$(LIBM) \
|
||||
$(LIBBUILTINS) \
|
||||
--end-group
|
||||
$(AGENT_OBJCOPY) $(KEEP_GLOBAL) --strip-debug \
|
||||
--remove-section=.llvmbc --remove-section=.llvmcmd $@.tmp $@
|
||||
rm -f $@.tmp
|
||||
|
||||
$(AGENT_LIB_SOFTFLOAT): agent
|
||||
cp $(AGENT_LIB) $@
|
||||
$(DROP_HARDFLOAT_BUILTINS)
|
||||
|
||||
$(LIBC): export LC_ALL = C
|
||||
$(LIBC): $(AGENT_LIB_SOFTFLOAT)
|
||||
cp $(FRIDA_SDK)/lib/libc.a $@
|
||||
$(AGENT_NM) --defined-only $(AGENT_LIB_SOFTFLOAT) \
|
||||
| awk 'NF == 3 && $$2 ~ /^[TDBR]$$/ { print $$3 }' | sort -u > $@.agent-syms
|
||||
$(AGENT_NM) --print-armap $@ | sed -n 's/^\([^ ]*\) in \(.*\)$$/\1 \2/p' | sort -u > $@.members
|
||||
$(AGENT_AR) d $@ $$(join $@.agent-syms $@.members | awk '{ print $$2 }' | sort -u)
|
||||
$(AGENT_AR) s $@
|
||||
rm -f $@.agent-syms $@.members
|
||||
|
||||
agent:
|
||||
cd $(AGENT_DIR) && RUSTC_BOOTSTRAP=1 RUSTFLAGS="$(AGENT_RUSTFLAGS)" \
|
||||
CC_$(subst -,_,$(RUST_TARGET))="$(AGENT_CC) $(AGENT_CFLAGS)" \
|
||||
cargo build --$(PROFILE) \
|
||||
$(AGENT_TARGET_FLAGS) --features linux \
|
||||
--target-dir $(AGENT_TARGET_DIR) $(AGENT_BUILD_STD)
|
||||
|
||||
clean:
|
||||
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) clean
|
||||
rm -f $(PRELINKED) $(PRELINKED).tmp $(AGENT_LIB_SOFTFLOAT) $(LIBC)
|
||||
|
||||
.PHONY: all modules agent clean
|
||||
@@ -1,291 +0,0 @@
|
||||
# Linux kernel module
|
||||
|
||||
The same agent that the Barebone backend injects into XNU from the outside, but
|
||||
loaded from the inside as an ordinary kernel module.
|
||||
|
||||
## How it differs from the XNU flavor
|
||||
|
||||
| | XNU | Linux |
|
||||
|-------------------|--------------------------------------------------|-------------------------------------------|
|
||||
| Delivery | remote stub injects a freestanding ELF | `insmod frida-agent.ko` |
|
||||
| Kernel primitives | addresses patched into `.kernel_addrs` by the host | direct calls into `frida-kmod.c` |
|
||||
| Bootstrap config | GVariant blob written into guest memory | module parameters |
|
||||
| Transport | virtio hostlink or vsock | `/dev/frida` character device |
|
||||
| Page permissions | host rewrites descriptors over the hostlink | `set_memory_*()` |
|
||||
| Writable alias | shadow copy, committed via physical-memory bridge | `vmap()` of the same pages |
|
||||
| Symbols | table supplied by the host | kallsyms |
|
||||
|
||||
Because the Linux agent performs its own memory work, it never issues the
|
||||
`RemapWritablePages`, `MemoryProtect` or `PatchCode` host RPCs. The wire protocol
|
||||
is otherwise identical, so the host side sees the same `(yqv)` frames.
|
||||
|
||||
## Building
|
||||
|
||||
Four inputs: this repository, a GumJS devkit built for
|
||||
`none-<arch>-softfloat_nopic`, the SDK it was built against, and the kernel the module is for. `Makefile`
|
||||
covers x86_64 and arm64, and defaults to the architecture of the machine building.
|
||||
|
||||
Building for the running kernel is a good deal shorter than cross-building for a
|
||||
device, so that walkthrough comes first; the arm64 one below it is the same steps
|
||||
with the kernel obtained from elsewhere.
|
||||
|
||||
### Toolchain
|
||||
|
||||
rustup target add x86_64-unknown-none # or aarch64-unknown-none
|
||||
rustup component add rust-src
|
||||
sudo dnf install clang binutils kernel-devel-$(uname -r)
|
||||
|
||||
clang must be 19 or newer, since that is where `-mabi=aapcs-soft` landed; GCC does
|
||||
not implement it at any version, nor does it reach x86_64's soft-float ABI. The
|
||||
binutils are for the prelink, which reads the archive map that only GNU `nm` prints —
|
||||
the LLVM one does not; cross-building wants the target's, natively the host's own
|
||||
will do. The host compiler is for Cargo's own build scripts, which run on the machine
|
||||
doing the building.
|
||||
|
||||
The commands below are run from the top of this repository, with `releng` checked
|
||||
out — `git submodule update --init releng` if the clone did not recurse.
|
||||
|
||||
### For the running x86_64 kernel
|
||||
|
||||
No prebuilt bundles are published for `none-x86_64-softfloat_nopic`, so both inputs are
|
||||
built locally, against the same SDK:
|
||||
|
||||
releng/deps.py build --bundle=sdk --host=none-x86_64-softfloat_nopic
|
||||
mkdir -p ~/sdk-none-x86_64-softfloat_nopic
|
||||
tar -C ~/sdk-none-x86_64-softfloat_nopic -xf deps/sdk-none-x86_64-softfloat_nopic.tar.xz
|
||||
|
||||
gum=subprojects/frida-gum
|
||||
mkdir -p $gum/build/none-x86_64-softfloat_nopic
|
||||
(cd $gum/build/none-x86_64-softfloat_nopic \
|
||||
&& FRIDA_DEPS=$PWD/../../../../deps ../../configure \
|
||||
--host=none-x86_64-softfloat_nopic \
|
||||
--enable-gumjs \
|
||||
--with-devkits=gumjs \
|
||||
--with-devkit-symbol-scope=original \
|
||||
&& make)
|
||||
|
||||
make -C src/barebone/agent/linux \
|
||||
FRIDA_SDK=$HOME/sdk-none-x86_64-softfloat_nopic \
|
||||
GUMJS_DEVKIT_DIR=$PWD/$gum/build/none-x86_64-softfloat_nopic/bindings/gumjs/devkit
|
||||
|
||||
Everything else — `KDIR`, the Rust target, the binutils, the kernel's own name for the
|
||||
architecture — follows from the running kernel.
|
||||
|
||||
### For an arm64 device
|
||||
|
||||
Here there are published bundles to start from, and the kernel has to be assembled.
|
||||
|
||||
#### The devkit and the SDK
|
||||
|
||||
version=17.17.0
|
||||
base=https://github.com/frida/frida/releases/download/$version
|
||||
curl -LO $base/frida-gumjs-devkit-$version-none-arm64-softfloat_nopic.tar.xz
|
||||
mkdir -p ~/gumjs-devkit
|
||||
tar -C ~/gumjs-devkit -xf frida-gumjs-devkit-$version-none-arm64-softfloat_nopic.tar.xz
|
||||
|
||||
releng/deps.py sync sdk none-arm64-softfloat_nopic ~/sdk-none-arm64-softfloat_nopic
|
||||
|
||||
The SDK carries picolibc and the compiler-rt builtins the devkit expects to be linked
|
||||
against, and `releng/deps.toml` pins which one. Mixing a devkit with an SDK built for
|
||||
a different libc leaves undefined symbols at prelink time.
|
||||
|
||||
#### The kernel
|
||||
|
||||
Nothing here needs the device's own kernel to be built, but the config alone is not
|
||||
enough either. `uname -r` ends in the GKI commit and the build number:
|
||||
|
||||
adb shell uname -r
|
||||
6.1.145-android14-11-gc1de4747ac59-ab14219743
|
||||
|
||||
which is commit `c1de4747ac59` and build `14219743`. Three pieces are keyed to them:
|
||||
|
||||
commit=c1de4747ac59
|
||||
build=14219743
|
||||
ci=https://ci.android.com/builds/submitted/$build/kernel_aarch64/latest/raw
|
||||
|
||||
mkdir -p ~/kernel-prepared ~/kernel-source
|
||||
curl -sSL $ci/modules_prepare_outdir.tar.gz | tar -xz -C ~/kernel-prepared
|
||||
curl -sSL $ci/kernel_aarch64_Module.symvers -o ~/kernel-prepared/Module.symvers
|
||||
curl -sSL https://android.googlesource.com/kernel/common/+archive/$commit.tar.gz \
|
||||
| tar -xz -C ~/kernel-source
|
||||
|
||||
`modules_prepare_outdir.tar.gz` is the configured build tree with `scripts/` already
|
||||
compiled as x86-64 binaries, so the kbuild half has to run on an x86-64 Linux host —
|
||||
an arm64 one gets as far as `CC [M]` and then fails to run `fixdep`. `Module.symvers`
|
||||
carries the CRCs `CONFIG_MODVERSIONS` checks. The top-level `Makefile` in that prepared tree is a stub
|
||||
that includes the source's, hence both `KDIR` and `KOUT` below.
|
||||
|
||||
A different GKI build of the same branch does not substitute for these: vermagic is
|
||||
compared verbatim, and it names the exact build.
|
||||
|
||||
#### Build
|
||||
|
||||
make -C src/barebone/agent/linux \
|
||||
FRIDA_SDK=$HOME/sdk-none-arm64-softfloat_nopic \
|
||||
GUMJS_DEVKIT_DIR=$HOME/gumjs-devkit \
|
||||
AGENT_LD=aarch64-linux-gnu-ld \
|
||||
AGENT_AR=aarch64-linux-gnu-ar \
|
||||
AGENT_NM=aarch64-linux-gnu-nm \
|
||||
AGENT_OBJCOPY=aarch64-linux-gnu-objcopy \
|
||||
KDIR=$HOME/kernel-source \
|
||||
KOUT=$HOME/kernel-prepared \
|
||||
LLVM=1
|
||||
|
||||
`make` builds the Rust staticlib itself, prelinks it against the devkit and the SDK,
|
||||
then hands the result to kbuild. The `AGENT_*` overrides are only needed because the
|
||||
defaults name an `aarch64-none-elf-` GNU toolchain; drop them if that is what is
|
||||
installed.
|
||||
|
||||
The prelink half runs anywhere; the kbuild half needs the x86-64 Linux host above.
|
||||
Cross-building from macOS means running that step in a container holding the kernel
|
||||
tree.
|
||||
|
||||
adb push src/barebone/agent/linux/frida-agent.ko /data/local/tmp/
|
||||
|
||||
## Loading
|
||||
|
||||
insmod frida-agent.ko
|
||||
|
||||
The module's init path returns as soon as the worker thread is running — watch
|
||||
`dmesg` for `frida: listening on /dev/frida`. `rmmod frida-agent` unwinds in the
|
||||
reverse order and does not return until the worker is off the module's text.
|
||||
|
||||
## Talking to it
|
||||
|
||||
The agent exposes `/dev/frida` and waits: one client at a time, `read`/`write` of the
|
||||
same length-prefixed frames the other transports carry, with `poll` for readiness.
|
||||
It never dials out, so there is no network namespace to get right and no ordering
|
||||
requirement between loading the module and attaching a client.
|
||||
|
||||
On Android the node is created by devtmpfs without a policy-specific label, so an
|
||||
SELinux-enforcing system needs the client's domain granted access to it before it can
|
||||
open the device. A socket-based transport was the other option considered and is a
|
||||
worse fit: `AF_VSOCK` only reaches a peer when a hypervisor is on the other side, and
|
||||
having the kernel connect out to loopback needs something already listening before
|
||||
the module loads.
|
||||
|
||||
### Reaching it from a host
|
||||
|
||||
Serve the Barebone device from a frida-server running on the target, so the `open()`
|
||||
happens next to the device node and the host talks to an ordinary frida-server:
|
||||
|
||||
FRIDA_BAREBONE_CONFIG=/data/local/tmp/linux-kmod.json \
|
||||
frida-server --device barebone -l 127.0.0.1:27042
|
||||
|
||||
`etc/linux-kmod.json` is that config. From the host, with `adb forward tcp:27042
|
||||
tcp:27042` in place:
|
||||
|
||||
frida -H 127.0.0.1:27042 -p 0
|
||||
|
||||
## Injecting into userspace
|
||||
|
||||
Loaded on a live system the module doubles as a ptrace-free injector for ordinary
|
||||
processes — how the Linux local backend reaches its targets when the module is
|
||||
present, falling back to `ptrace` when it is not. There is no second device node;
|
||||
the channel is `prctl()` with a magic option and a token in the fifth argument,
|
||||
|
||||
prctl(0x46524944, op, arg1, arg2, 0x1d5f9e6b2c7a4038)
|
||||
|
||||
and any call whose option or token does not match falls through to the real
|
||||
prctl, so probing for the module looks like a stock kernel. `op` 32 pings and
|
||||
returns the magic.
|
||||
|
||||
Allocate, free, read, write and spawn — the primitives the injector drives
|
||||
against a target pid — require `CAP_SYS_ADMIN`. A spawned thread is a real
|
||||
`CLONE_THREAD` sibling that adopts the target's mm, files, credentials,
|
||||
namespaces, fs and SysV semaphore undo list, so it is a native member of the
|
||||
group rather than a kernel thread wearing its address space.
|
||||
|
||||
Cloaking is Gum's own registry mirrored into the kernel so it holds against
|
||||
`/proc` too: the module intercepts the `maps`, `smaps`, task and status readers
|
||||
and drops cloaked threads, ranges and fds while keeping the counts consistent. A
|
||||
process cloaks its own resources with the token alone — no capability — so an
|
||||
injected agent self-cloaks by forwarding Gum's updates over the same channel,
|
||||
and the loader adds the footprint the agent itself never sees. A cloaked thread
|
||||
still sees through the cloak when it reads `/proc`, so the agent is not blind to
|
||||
its own process.
|
||||
|
||||
## What the target kernel has to provide
|
||||
|
||||
- **x86_64 or arm64.** The register-level pieces — the page size, cache maintenance,
|
||||
where the syscall ABI leaves prctl()'s arguments, and how a spawned thread's
|
||||
registers are set up for its return to userspace — have a half for each. The XNU
|
||||
flavor remains arm64-only.
|
||||
- **`CONFIG_KPROBES` and `CONFIG_KALLSYMS_ALL`.** A good deal of what the shim
|
||||
needs is compiled in but not exported — `kallsyms_lookup_name`,
|
||||
`kallsyms_on_each_symbol` and `set_memory_*` all are, and GKI trims its export
|
||||
table to the KMI symbol list on top of that. The shim resolves them by name
|
||||
through a kprobe, which is the standard way back in. Without kallsyms the module
|
||||
still loads but cannot look up symbols or change page permissions.
|
||||
- **Unsigned modules.** `CONFIG_MODULE_SIG_FORCE` must be off. Android's
|
||||
`CONFIG_MODULE_SIG_PROTECT` is fine: it only demands signatures for modules on
|
||||
the GKI list.
|
||||
|
||||
## Kernel ABI constraints
|
||||
|
||||
A hardened kernel imposes an ABI on anything linked into it, and the FP constraint
|
||||
below is why this flavor is built against a soft-float SDK
|
||||
(`none-arm64-softfloat_nopic`, `none-x86_64-softfloat_nopic`) rather than an ordinary
|
||||
bare-metal one. The remaining constraints are per-architecture.
|
||||
|
||||
- **FP/SIMD.** JavaScript numbers are doubles, so a hardfloat build would use FP
|
||||
for as long as the runtime lives, and neither architecture lets a kernel thread
|
||||
hold it for free — x86 wants every stretch bracketed by `kernel_fpu_begin()`, and
|
||||
on arm64 kernels before 6.9 there is no correct way to hold it at all:
|
||||
`kernel_neon_begin()` runs with preemption disabled so we cannot sleep under it,
|
||||
and the scheduler does not save a kernel thread's FP registers — live values are
|
||||
lost on preemption and the FP state of whichever user task was interrupted is
|
||||
corrupted. Building everything soft-float removes the question rather than
|
||||
answering it: `-mabi=aapcs-soft -mgeneral-regs-only` on arm64, and on x86_64
|
||||
LLVM's `+soft-float`, which is also what Rust's own `x86_64-unknown-none` turns
|
||||
on, so the two halves agree on where a double travels. `-mno-sse` alone does not
|
||||
reach the ABI; and `long double` has to be narrowed to 64 bits with
|
||||
`-mlong-double-64`, since soft-float has no lowering for x87's 80-bit format and
|
||||
LLVM crashes rather than calling out to a builtin.
|
||||
- **Indirect branch tracking (x86_64).** With `CONFIG_X86_KERNEL_IBT` an indirect
|
||||
call landing on anything but an `endbr64` faults, so everything linked in is built
|
||||
`-fcf-protection=branch` and the Rust half `-Zcf-protection=branch`.
|
||||
- **Kernel code model and red zone (x86_64).** Modules are loaded into the top 2GB
|
||||
of the address space and their relocations are resolved as if by `-mcmodel=kernel`,
|
||||
and there is no red zone below the stack pointer for an interrupt to step on. Both
|
||||
come with the Rust target; the C half is told explicitly.
|
||||
- **objtool (x86_64).** It validates every object entering a module and builds its
|
||||
ORC unwind tables, and was not written to read a JavaScript runtime linked against
|
||||
a freestanding libc. `Kbuild` marks the prelinked blob `OBJECT_FILES_NON_STANDARD`.
|
||||
- **Shadow call stack (arm64).** With `CONFIG_SHADOW_CALL_STACK` the kernel reserves x18, so
|
||||
the SDK and Gum are built `-ffixed-x18` and the Rust half with `-Zfixed-x18`.
|
||||
Anything linked in that uses x18 as a scratch register overwrites the kernel's SCS
|
||||
pointer, and the thread then returns through whatever that address happens to hold.
|
||||
|
||||
The Rust flag only reaches crates Cargo compiles, so `core`, `alloc` and
|
||||
`compiler_builtins` — which the toolchain ships prebuilt, and which between them
|
||||
account for every x18 write that used to reach the module — are rebuilt from source
|
||||
via `-Z build-std`. That needs `rustup component add rust-src`.
|
||||
|
||||
## The soft-float libc
|
||||
|
||||
The libc is picolibc and the compiler runtime is compiler-rt, both from the
|
||||
soft-float SDK that `FRIDA_SDK` points at. `Makefile` trims a copy of its
|
||||
`libc.a` before the prelink: the agent implements `malloc`, the C library's locks and
|
||||
its process stubs over the kernel's own facilities, so picolibc's are duplicate
|
||||
definitions rather than fallbacks. The set is derived from what the agent defines
|
||||
rather than listed, since picolibc moves things between releases.
|
||||
|
||||
On arm64 those compiler-rt builtins are load-bearing in a way that is easy to miss.
|
||||
Rust's toolchain carries its own `compiler_builtins`, compiled for the ordinary AAPCS
|
||||
where a double travels in `d0`, and the prelink whole-archives the Rust staticlib — so
|
||||
without the filtering step in `Makefile` those definitions answer the C library's calls
|
||||
to `__muldf3` and friends, which pass the same double in `x0`. Nothing crashes. The
|
||||
arithmetic simply returns garbage, and the first thing to notice is GLib deciding a
|
||||
hash table need not grow. x86_64 needs none of that filtering: soft-float is part of
|
||||
the Rust target there, so the copy `-Z build-std` compiles already agrees with the C
|
||||
half.
|
||||
|
||||
## What a script may look like
|
||||
|
||||
The runtime compiles scripts on a kernel thread's stack, and QuickJS parses
|
||||
expressions by recursion, so the ceiling is nesting rather than length: roughly
|
||||
fourteen levels of parentheses or fifteen of object literals on arm64 Linux, whose
|
||||
kernel stack is 16 KiB — as is x86_64's. A script that exceeds it fails to compile rather than taking
|
||||
anything down, though the message is not always `stack overflow` — an over-deep
|
||||
`if`/`else` chain reports `expecting ';'`. Long scripts are fine; deep ones are not.
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,8 +0,0 @@
|
||||
SECTIONS
|
||||
{
|
||||
.init_array 0 : {
|
||||
_agent_init_start = .;
|
||||
*(.init_array*)
|
||||
_agent_init_end = .;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
/frida-agent
|
||||
/frida-agent.kext/
|
||||
/frida-kext.o
|
||||
/frida-signing.o
|
||||
@@ -1,37 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>frida-agent</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>re.frida.agent</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>frida-agent</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>KEXT</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>@FRIDA_VERSION@</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>@BUNDLE_VERSION@</string>
|
||||
<key>OSBundleRequired</key>
|
||||
<string>Root</string>
|
||||
<key>OSBundleLibraries</key>
|
||||
<dict>
|
||||
<key>com.apple.kpi.bsd</key>
|
||||
<string>12.0</string>
|
||||
<key>com.apple.kpi.libkern</key>
|
||||
<string>12.0</string>
|
||||
<key>com.apple.kpi.iokit</key>
|
||||
<string>12.0</string>
|
||||
<key>com.apple.kpi.mach</key>
|
||||
<string>12.0</string>
|
||||
<key>com.apple.kpi.unsupported</key>
|
||||
<string>12.0</string>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,88 +0,0 @@
|
||||
|
||||
PROFILE ?= release
|
||||
ARCH ?= arm64e
|
||||
|
||||
ifeq ($(ARCH),arm64e)
|
||||
RUST_TARGET ?= arm64e-apple-darwin
|
||||
CLANG_TARGET ?= arm64e-apple-macos11
|
||||
else ifeq ($(ARCH),arm64)
|
||||
RUST_TARGET ?= aarch64-apple-darwin
|
||||
CLANG_TARGET ?= arm64-apple-macos11
|
||||
else
|
||||
RUST_TARGET ?= x86_64-apple-darwin
|
||||
CLANG_TARGET ?= x86_64-apple-macos11
|
||||
endif
|
||||
|
||||
AGENT_DIR := $(CURDIR)/..
|
||||
AGENT_TARGET_DIR := $(AGENT_DIR)/target-xnu-kext
|
||||
AGENT_LIB := $(AGENT_TARGET_DIR)/$(RUST_TARGET)/$(PROFILE)/libfrida_barebone_agent.a
|
||||
|
||||
SDK ?= $(shell xcrun --sdk macosx --show-sdk-path)
|
||||
KERNEL_HEADERS := $(SDK)/System/Library/Frameworks/Kernel.framework/Headers
|
||||
|
||||
FRIDA_VERSION := $(or $(FRIDA_VERSION),0.0.0)
|
||||
BUNDLE_VERSION := $(firstword $(subst -, ,$(FRIDA_VERSION)))
|
||||
|
||||
FRIDA_SDK ?= $(error Set FRIDA_SDK to a macos-$(ARCH)-kernel SDK prefix)
|
||||
GUMJS_DEVKIT_DIR ?= $(error Set GUMJS_DEVKIT_DIR to the directory holding libfrida-gumjs.a)
|
||||
|
||||
AGENT_RUSTFLAGS ?= -Cembed-bitcode=no
|
||||
AGENT_BUILD_STD ?= -Z build-std=core,alloc,compiler_builtins
|
||||
|
||||
GLUE_CFLAGS ?= -target $(CLANG_TARGET) -isysroot $(SDK) -I$(KERNEL_HEADERS) \
|
||||
-mkernel -fno-builtin -fno-common -nostdinc -std=gnu11 \
|
||||
-DKERNEL -DKERNEL_PRIVATE -DAPPLE -D__MACHO__=1
|
||||
|
||||
AGENT_CC ?= clang -target $(CLANG_TARGET) -mkernel -nostdlibinc \
|
||||
-isystem $(FRIDA_SDK)/include \
|
||||
-I$(FRIDA_SDK)/include/glib-2.0 \
|
||||
-I$(FRIDA_SDK)/lib/glib-2.0/include \
|
||||
-I$(FRIDA_SDK)/include/capstone \
|
||||
-I$(FRIDA_SDK)/include/json-glib-1.0
|
||||
|
||||
stamp_version = sed -e 's/@FRIDA_VERSION@/$(FRIDA_VERSION)/' \
|
||||
-e 's/@BUNDLE_VERSION@/$(BUNDLE_VERSION)/' Info.plist
|
||||
|
||||
BUNDLE := frida-agent.kext
|
||||
BINARY := $(BUNDLE)/Contents/MacOS/frida-agent
|
||||
|
||||
all: $(BUNDLE)
|
||||
|
||||
skeleton: frida-kext.o Info.plist
|
||||
mkdir -p $(BUNDLE)/Contents/MacOS
|
||||
$(stamp_version) > $(BUNDLE)/Contents/Info.plist
|
||||
$(shell xcrun --find ld) -kext -arch $(ARCH) -o $(BINARY) frida-kext.o
|
||||
|
||||
$(BUNDLE): frida-agent Info.plist
|
||||
mkdir -p $(BUNDLE)/Contents/MacOS
|
||||
$(stamp_version) > $(BUNDLE)/Contents/Info.plist
|
||||
cp frida-agent $(BINARY)
|
||||
|
||||
frida-agent: frida-kext.o frida-signing.o agent
|
||||
$(shell xcrun --find ld) -kext -arch $(ARCH) -o $@ \
|
||||
frida-kext.o \
|
||||
frida-signing.o \
|
||||
$(AGENT_LIB) \
|
||||
$(GUMJS_DEVKIT_DIR)/libfrida-gumjs.a \
|
||||
$(FRIDA_SDK)/lib/libc.a \
|
||||
$(FRIDA_SDK)/lib/libm.a \
|
||||
$(FRIDA_SDK)/lib/libclang_rt.builtins.a
|
||||
|
||||
frida-kext.o: frida-kext.c
|
||||
$(shell xcrun --find clang) $(GLUE_CFLAGS) -c $< -o $@
|
||||
|
||||
frida-signing.o: frida-signing.c
|
||||
$(AGENT_CC) -I$(GUMJS_DEVKIT_DIR) -c $< -o $@
|
||||
|
||||
agent:
|
||||
cd $(AGENT_DIR) && RUSTC_BOOTSTRAP=1 RUSTFLAGS="$(AGENT_RUSTFLAGS)" \
|
||||
CC_$(subst -,_,$(RUST_TARGET))="$(AGENT_CC)" \
|
||||
GUMJS_DEVKIT_DIR="$(GUMJS_DEVKIT_DIR)" \
|
||||
cargo build --$(PROFILE) \
|
||||
--target $(RUST_TARGET) --features xnu-kext \
|
||||
--target-dir $(AGENT_TARGET_DIR) $(AGENT_BUILD_STD)
|
||||
|
||||
clean:
|
||||
rm -rf $(BUNDLE) frida-agent frida-kext.o frida-signing.o
|
||||
|
||||
.PHONY: all agent clean skeleton
|
||||
@@ -1,122 +0,0 @@
|
||||
# The agent as a kext
|
||||
|
||||
The XNU flavour of the barebone agent is ordinarily a blob: a host with a debugger writes it into
|
||||
a running kernel, tells it where that kernel keeps the things it calls, and starts it. The Linux
|
||||
flavour can also be built as a module the kernel loads (`../linux`), and this is the same thing for
|
||||
XNU -- a kext macOS loads.
|
||||
|
||||
What the two packagings share is everything the agent does. What differs is how it is put there,
|
||||
how it is told about the kernel around it, and how the host reaches it.
|
||||
|
||||
## Building
|
||||
|
||||
Three things, in order. First an SDK for this kernel -- picolibc, GLib, QuickJS, capstone and the
|
||||
rest, built for `macos-arm64-kernel`:
|
||||
|
||||
cd frida-core
|
||||
python3 -m releng.deps build --host macos-arm64-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:
|
||||
|
||||
cd frida-gum
|
||||
./configure --host=macos-arm64-kernel --enable-gumjs --disable-v8 --with-devkits=gumjs
|
||||
make
|
||||
|
||||
Then the kext itself:
|
||||
|
||||
make FRIDA_SDK=.../deps/sdk-macos-arm64-kernel \
|
||||
GUMJS_DEVKIT_DIR=.../frida-gum/build/bindings/gumjs/devkit
|
||||
|
||||
which builds the agent as a static library for the kernel's ABI, `frida-kext.c` against the Kernel
|
||||
framework's headers, and links the two with the devkit into `frida-agent.kext`.
|
||||
|
||||
`make skeleton` builds the glue alone, which is enough to check the pipeline without the rest.
|
||||
|
||||
## How it is told about the kernel
|
||||
|
||||
A kext may call what the kernel exports to kexts, and the agent calls a great deal more than that:
|
||||
`proc_find` and `copyin` are exported, `task_threads` and `mach_vm_allocate` are not. So the
|
||||
addresses arrive the way they do for the blob -- worked out by whoever knows this kernel -- and are
|
||||
written in through `/dev/frida` before the agent is started:
|
||||
|
||||
ioctl(fd, FRIDA_IOC_SET_ADDR, &(FridaAddrRequest){ .name = "task_threads", .address = ... });
|
||||
...
|
||||
ioctl(fd, FRIDA_IOC_START);
|
||||
|
||||
A name nothing writes stays zero, and the agent already answers for that: every one of them is an
|
||||
`Option` on the Rust side, and what needs it says so rather than calling through a null.
|
||||
|
||||
Once started, the same device carries the frames: read what the agent says, write what the host
|
||||
says, framed with a four-byte little-endian length exactly as on every other transport.
|
||||
|
||||
## What the sources needed
|
||||
|
||||
An SDK for a Darwin kernel is a target nothing here had been built for, and six of the sources say
|
||||
so. Each is a commit of its own on the fork, a few lines apiece:
|
||||
|
||||
- **picolibc** -- Mach-O has no `__attribute__((alias))`, so the aliasing macros fall through to
|
||||
the assembler ones it already carries for Mach-O, and the names are spent before being spelled.
|
||||
Its aarch64 assembly is written for ELF and its C stubs answer for it instead. The interrupt
|
||||
vectors and the `exec` family are not built: one owns a machine this does not, the other crashes
|
||||
the compiler.
|
||||
- **compiler-rt** -- `apple_versioning.c` is stubs for macOS releases long past, written against a
|
||||
header only userspace has.
|
||||
- **libffi** -- the trampoline table is built out of what userspace has of Mach; whether that is
|
||||
there is now asked of the header rather than of the system's name.
|
||||
- **QuickJS**, **tinycc** and **libiconv** -- all three ask what to do on Apple by asking whether
|
||||
they are on Apple. Where that means userspace -- a userspace malloc's header, a cache-control
|
||||
header, a locale one -- they now ask whether they are freestanding as well.
|
||||
|
||||
## Which architecture, and where pointers are signed
|
||||
|
||||
Apple silicon runs an arm64e kernel and loads nothing else, so that is what `ARCH` defaults to,
|
||||
and the SDK and the devkit are built for `macos-arm64e-kernel` to match.
|
||||
|
||||
Pointer authentication is the thing to get right there, and what makes it awkward is that Rust's
|
||||
`arm64e-apple-darwin` is a tag rather than an ABI: it neither signs a function pointer nor
|
||||
authenticates one.
|
||||
|
||||
_pick: adrp x0, 0 ; add x0, x0, #0x0 ; ret <- no signing
|
||||
_call: mov x2, x0 ; mov x0, x1 ; br x2 <- no authenticating
|
||||
|
||||
So the line is drawn where the kernel is, and not inside:
|
||||
|
||||
- **Ourselves.** GLib, GumJS and the agent are built `-fno-ptrauth-calls`, so the C half calls a
|
||||
Rust callback the way Rust hands it over -- plainly. Nothing there signs, so nothing there is
|
||||
disappointed. `otool` says as much: not one `blraa` outside `__auth_stubs`.
|
||||
- **The kernel calling us.** The glue keeps the kernel's own ABI, so the device table it registers
|
||||
is written with `AUTH` relocations and the loader signs it as the kernel expects.
|
||||
- **Us calling the kernel.** Through `__auth_stubs`, which the loader fills the same way.
|
||||
- **Us handing the kernel a function to call.** A thread's entry is the one that matters, and the
|
||||
agent signs it itself, as it already does for the kernel it is injected into. What it signs with
|
||||
is no longer written down twice: the glue derives the discriminator from the kernel's own
|
||||
headers with `ptrauth_type_discriminator`, and asserts that it is the number the blob flavour
|
||||
carries -- 0xd507 for a `thread_continue_t`, which is what it was.
|
||||
|
||||
What this does not buy is pointer authentication *within* the kext, which is protection given up
|
||||
rather than correctness lost. Getting it back wants rustc to grow the ABI, or every callback
|
||||
wrapped in a C thunk.
|
||||
|
||||
None of this touches x86_64, which has no pointer authentication at all: an Intel kext wants only
|
||||
its own SDK built the same way.
|
||||
|
||||
## Floating point
|
||||
|
||||
Every other kernel flavour is built soft-float, since a kernel does not save the floating-point
|
||||
registers across a trap. This one cannot be: Darwin has no soft-float C ABI on either
|
||||
architecture. arm64 refuses the type outright without one -- `ABI 'darwinpcs' does not support
|
||||
it` -- and `-mabi=aapcs-soft` alongside it crashes both Apple's clang and upstream LLVM on the
|
||||
first varargs function taking a double; x86_64 wants SSE to return one. A script engine is nothing
|
||||
but doubles, so the floating-point registers stay in play, and what that costs at run time is a
|
||||
thing to answer on the machine.
|
||||
|
||||
## Putting copies into processes
|
||||
|
||||
Not there yet. The blob's linker script marks out its own writable half and what a copy of it has
|
||||
to relocate; a kext is laid out by the kernel's loader instead, so the glue says there is nothing
|
||||
to copy from. Everything the agent does in the kernel itself is unaffected.
|
||||
|
||||
Carrying the blob's ELF and placing it the way the host does is not a way round that, either: on a
|
||||
machine with SPTM, memory a kext allocates cannot be made executable, while a kext's own text is
|
||||
mapped executable by the kernel's loader because it was signed.
|
||||
@@ -1,446 +0,0 @@
|
||||
|
||||
#include <libkern/libkern.h>
|
||||
#include <mach/kmod.h>
|
||||
#include <mach/mach_types.h>
|
||||
#include <miscfs/devfs/devfs.h>
|
||||
#include <sys/conf.h>
|
||||
#include <sys/errno.h>
|
||||
#include <sys/ioccom.h>
|
||||
#include <sys/malloc.h>
|
||||
#include <sys/proc.h>
|
||||
#include <sys/random.h>
|
||||
#include <sys/uio.h>
|
||||
#include <kern/locks.h>
|
||||
#include <kern/thread.h>
|
||||
#include <ptrauth.h>
|
||||
|
||||
extern int frida_agent_start (uint64_t own_base, uint64_t own_size);
|
||||
extern void frida_agent_stop (void);
|
||||
extern void frida_agent_wake (void);
|
||||
|
||||
#define FRIDA_KERNEL_ADDRS(X) \
|
||||
X (panic) \
|
||||
X (kalloc) X (kfree) X (kalloc_data) X (kfree_data) \
|
||||
X (proc_iterate) X (proc_find) X (proc_rele) X (proc_pid) X (proc_best_name) X (proc_task) \
|
||||
X (current_task) X (current_thread) X (get_task_map) X (get_bsdtask_info) \
|
||||
X (mach_vm_allocate) X (mach_vm_deallocate) X (mach_vm_protect) X (mach_vm_remap) \
|
||||
X (kernel_map) X (kernel_pmap) X (pmap_find_phys) X (ml_static_ptovirt) \
|
||||
X (mach_vm_region) X (mach_vm_region_recurse) \
|
||||
X (vm_map_copyin) X (vm_map_copyout) X (vm_map_wire_external) \
|
||||
X (thread_create) X (thread_set_state) X (thread_resume) X (thread_suspend) \
|
||||
X (thread_terminate) X (kernel_thread_start) \
|
||||
X (task_suspend) X (task_resume) X (task_clear_return_wait) X (task_threads) X (task_info) \
|
||||
X (task_restartable_ranges_register) \
|
||||
X (assert_wait) X (assert_wait_timeout) X (thread_block) X (thread_wakeup) \
|
||||
X (thread_wakeup_prim) X (wakeup) X (ulock_wake) \
|
||||
X (mach_absolute_time) X (absolutetime_to_nanoseconds) X (clock_get_calendar_microtime) \
|
||||
X (ml_io_map) X (ml_vtophys) X (ml_static_vtop) \
|
||||
X (copyin) X (copyout) X (sysent) X (IOLog) \
|
||||
X (convert_port_to_thread) X (convert_port_to_thread_read) \
|
||||
X (convert_port_to_thread_inspect) \
|
||||
X (sock_socket) X (sock_connect) X (sock_send) X (sock_receive) X (sock_close)
|
||||
|
||||
#define FRIDA_DEFINE_SLOT(name) void * _##name;
|
||||
FRIDA_KERNEL_ADDRS (FRIDA_DEFINE_SLOT)
|
||||
#undef FRIDA_DEFINE_SLOT
|
||||
|
||||
typedef struct _FridaKernelAddr FridaKernelAddr;
|
||||
|
||||
struct _FridaKernelAddr
|
||||
{
|
||||
const char * name;
|
||||
void ** slot;
|
||||
};
|
||||
|
||||
static const FridaKernelAddr frida_kernel_addrs[] = {
|
||||
#define FRIDA_LIST_SLOT(name) { #name, &_##name },
|
||||
FRIDA_KERNEL_ADDRS (FRIDA_LIST_SLOT)
|
||||
#undef FRIDA_LIST_SLOT
|
||||
};
|
||||
|
||||
extern void * frida_kext_init_start __asm ("section$start$__DATA$__mod_init_func");
|
||||
extern void * frida_kext_init_end __asm ("section$end$__DATA$__mod_init_func");
|
||||
|
||||
const uintptr_t frida_agent_init_start = (uintptr_t) &frida_kext_init_start;
|
||||
const uintptr_t frida_agent_init_end = (uintptr_t) &frida_kext_init_end;
|
||||
|
||||
const uintptr_t frida_agent_private_start = 0;
|
||||
const uintptr_t frida_agent_heap_start = 0;
|
||||
const uintptr_t frida_agent_relocs_start = 0;
|
||||
const uintptr_t frida_agent_relocs_end = 0;
|
||||
|
||||
typedef int (* FridaProcCallout) (proc_t process, void * argument);
|
||||
|
||||
const unsigned frida_agent_disc_thread_continue =
|
||||
ptrauth_type_discriminator (thread_continue_t);
|
||||
const unsigned frida_agent_disc_proc_callout =
|
||||
ptrauth_type_discriminator (FridaProcCallout);
|
||||
|
||||
_Static_assert (ptrauth_type_discriminator (thread_continue_t) == 0xd507,
|
||||
"the agent signs a thread's entry with 0xd507");
|
||||
|
||||
int
|
||||
getentropy (void * buffer, size_t size)
|
||||
{
|
||||
read_random (buffer, (u_int) size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
sys_icache_invalidate (void * start, size_t size)
|
||||
{
|
||||
flush_dcache ((vm_offset_t) start, (unsigned) size, FALSE);
|
||||
invalidate_icache ((vm_offset_t) start, (unsigned) size, FALSE);
|
||||
}
|
||||
|
||||
#define FRIDA_IOC_SET_ADDR _IOW ('F', 1, FridaAddrRequest)
|
||||
#define FRIDA_IOC_START _IO ('F', 2)
|
||||
#define FRIDA_IOC_GET_IMAGE _IOR ('F', 3, FridaImageInfo)
|
||||
|
||||
typedef struct _FridaAddrRequest FridaAddrRequest;
|
||||
typedef struct _FridaImageInfo FridaImageInfo;
|
||||
|
||||
struct _FridaAddrRequest
|
||||
{
|
||||
char name[64];
|
||||
uint64_t address;
|
||||
};
|
||||
|
||||
struct _FridaImageInfo
|
||||
{
|
||||
uint64_t base;
|
||||
uint64_t size;
|
||||
};
|
||||
|
||||
#define FRIDA_LINK_ROOM (1024 * 1024)
|
||||
|
||||
typedef struct _FridaLink FridaLink;
|
||||
|
||||
struct _FridaLink
|
||||
{
|
||||
lck_mtx_t * lock;
|
||||
bool open;
|
||||
|
||||
uint8_t * to_host;
|
||||
size_t to_host_len;
|
||||
|
||||
uint8_t * from_host;
|
||||
size_t from_host_len;
|
||||
};
|
||||
|
||||
static FridaLink frida_link;
|
||||
static lck_grp_t * frida_lock_group;
|
||||
|
||||
static int frida_dev_open (dev_t dev, int flags, int devtype, struct proc * p);
|
||||
static int frida_dev_close (dev_t dev, int flags, int devtype, struct proc * p);
|
||||
static int frida_dev_read (dev_t dev, struct uio * uio, int ioflag);
|
||||
static int frida_dev_write (dev_t dev, struct uio * uio, int ioflag);
|
||||
static int frida_dev_ioctl (dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc * p);
|
||||
|
||||
static struct cdevsw frida_cdevsw = {
|
||||
.d_open = frida_dev_open,
|
||||
.d_close = frida_dev_close,
|
||||
.d_read = frida_dev_read,
|
||||
.d_write = frida_dev_write,
|
||||
.d_ioctl = frida_dev_ioctl,
|
||||
.d_stop = eno_stop,
|
||||
.d_reset = eno_reset,
|
||||
.d_ttys = NULL,
|
||||
.d_select = eno_select,
|
||||
.d_mmap = eno_mmap,
|
||||
.d_strategy = eno_strat,
|
||||
.d_reserved_1 = eno_getc,
|
||||
.d_reserved_2 = eno_putc,
|
||||
.d_type = 0
|
||||
};
|
||||
|
||||
static int frida_major = -1;
|
||||
static void * frida_node = NULL;
|
||||
static bool frida_running = false;
|
||||
static uint64_t frida_own_base = 0;
|
||||
static uint64_t frida_own_size = 0;
|
||||
|
||||
kern_return_t
|
||||
frida_kext_start (kmod_info_t * ki, void * d)
|
||||
{
|
||||
frida_own_base = ki->address;
|
||||
frida_own_size = ki->size;
|
||||
|
||||
frida_lock_group = lck_grp_alloc_init ("frida", LCK_GRP_ATTR_NULL);
|
||||
if (frida_lock_group == NULL)
|
||||
return KERN_FAILURE;
|
||||
frida_link.lock = lck_mtx_alloc_init (frida_lock_group, LCK_ATTR_NULL);
|
||||
|
||||
frida_major = cdevsw_add (-1, &frida_cdevsw);
|
||||
if (frida_major == -1)
|
||||
return KERN_FAILURE;
|
||||
|
||||
frida_node = devfs_make_node (makedev (frida_major, 0), DEVFS_CHAR, UID_ROOT, GID_WHEEL, 0600,
|
||||
"frida");
|
||||
if (frida_node == NULL)
|
||||
{
|
||||
cdevsw_remove (frida_major, &frida_cdevsw);
|
||||
frida_major = -1;
|
||||
return KERN_FAILURE;
|
||||
}
|
||||
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
kern_return_t
|
||||
frida_kext_stop (kmod_info_t * ki, void * d)
|
||||
{
|
||||
if (frida_running)
|
||||
{
|
||||
frida_agent_stop ();
|
||||
frida_running = false;
|
||||
}
|
||||
|
||||
if (frida_node != NULL)
|
||||
{
|
||||
devfs_remove (frida_node);
|
||||
frida_node = NULL;
|
||||
}
|
||||
|
||||
if (frida_major != -1)
|
||||
{
|
||||
cdevsw_remove (frida_major, &frida_cdevsw);
|
||||
frida_major = -1;
|
||||
}
|
||||
|
||||
return KERN_SUCCESS;
|
||||
}
|
||||
|
||||
KMOD_EXPLICIT_DECL (re.frida.agent, "1.0", frida_kext_start, frida_kext_stop)
|
||||
|
||||
static int
|
||||
frida_dev_open (dev_t dev, int flags, int devtype, struct proc * p)
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
lck_mtx_lock (frida_link.lock);
|
||||
|
||||
if (frida_link.open)
|
||||
{
|
||||
result = EBUSY;
|
||||
}
|
||||
else
|
||||
{
|
||||
frida_link.to_host = _MALLOC (FRIDA_LINK_ROOM, M_TEMP, M_WAITOK);
|
||||
frida_link.from_host = _MALLOC (FRIDA_LINK_ROOM, M_TEMP, M_WAITOK);
|
||||
if (frida_link.to_host != NULL && frida_link.from_host != NULL)
|
||||
{
|
||||
frida_link.to_host_len = 0;
|
||||
frida_link.from_host_len = 0;
|
||||
frida_link.open = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = ENOMEM;
|
||||
}
|
||||
}
|
||||
|
||||
lck_mtx_unlock (frida_link.lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int
|
||||
frida_dev_close (dev_t dev, int flags, int devtype, struct proc * p)
|
||||
{
|
||||
lck_mtx_lock (frida_link.lock);
|
||||
|
||||
frida_link.open = false;
|
||||
if (frida_link.to_host != NULL)
|
||||
{
|
||||
_FREE (frida_link.to_host, M_TEMP);
|
||||
frida_link.to_host = NULL;
|
||||
}
|
||||
if (frida_link.from_host != NULL)
|
||||
{
|
||||
_FREE (frida_link.from_host, M_TEMP);
|
||||
frida_link.from_host = NULL;
|
||||
}
|
||||
|
||||
wakeup (&frida_link);
|
||||
|
||||
lck_mtx_unlock (frida_link.lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
frida_dev_read (dev_t dev, struct uio * uio, int ioflag)
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
lck_mtx_lock (frida_link.lock);
|
||||
|
||||
while (frida_link.open && frida_link.to_host_len == 0)
|
||||
{
|
||||
if (msleep (&frida_link, frida_link.lock, PCATCH, "frida", NULL) != 0)
|
||||
{
|
||||
lck_mtx_unlock (frida_link.lock);
|
||||
return EINTR;
|
||||
}
|
||||
}
|
||||
|
||||
if (frida_link.to_host_len != 0)
|
||||
{
|
||||
size_t n = MIN ((size_t) uio_resid (uio), frida_link.to_host_len);
|
||||
|
||||
result = uiomove ((const char *) frida_link.to_host, (int) n, uio);
|
||||
if (result == 0)
|
||||
{
|
||||
memmove (frida_link.to_host, frida_link.to_host + n, frida_link.to_host_len - n);
|
||||
frida_link.to_host_len -= n;
|
||||
}
|
||||
}
|
||||
|
||||
lck_mtx_unlock (frida_link.lock);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int
|
||||
frida_dev_write (dev_t dev, struct uio * uio, int ioflag)
|
||||
{
|
||||
size_t n;
|
||||
int result;
|
||||
|
||||
lck_mtx_lock (frida_link.lock);
|
||||
|
||||
n = MIN ((size_t) uio_resid (uio), FRIDA_LINK_ROOM - frida_link.from_host_len);
|
||||
if (n == 0)
|
||||
{
|
||||
lck_mtx_unlock (frida_link.lock);
|
||||
return ENOBUFS;
|
||||
}
|
||||
|
||||
result = uiomove ((char *) frida_link.from_host + frida_link.from_host_len, (int) n, uio);
|
||||
if (result == 0)
|
||||
frida_link.from_host_len += n;
|
||||
|
||||
lck_mtx_unlock (frida_link.lock);
|
||||
|
||||
if (result == 0 && frida_running)
|
||||
frida_agent_wake ();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int
|
||||
frida_kmod_link_open (void)
|
||||
{
|
||||
return frida_link.open ? 0 : -1;
|
||||
}
|
||||
|
||||
void
|
||||
frida_kmod_link_close (void)
|
||||
{
|
||||
}
|
||||
|
||||
int
|
||||
frida_kmod_link_send (const void * data, size_t size)
|
||||
{
|
||||
lck_mtx_lock (frida_link.lock);
|
||||
|
||||
if (!frida_link.open || frida_link.to_host_len + size > FRIDA_LINK_ROOM)
|
||||
{
|
||||
lck_mtx_unlock (frida_link.lock);
|
||||
return -1;
|
||||
}
|
||||
|
||||
memcpy (frida_link.to_host + frida_link.to_host_len, data, size);
|
||||
frida_link.to_host_len += size;
|
||||
|
||||
wakeup (&frida_link);
|
||||
|
||||
lck_mtx_unlock (frida_link.lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
frida_kmod_link_pending (void)
|
||||
{
|
||||
bool pending;
|
||||
|
||||
lck_mtx_lock (frida_link.lock);
|
||||
|
||||
pending = frida_link.from_host_len != 0;
|
||||
|
||||
lck_mtx_unlock (frida_link.lock);
|
||||
|
||||
return pending;
|
||||
}
|
||||
|
||||
ssize_t
|
||||
frida_kmod_link_recv (void * data, size_t size)
|
||||
{
|
||||
size_t n;
|
||||
|
||||
lck_mtx_lock (frida_link.lock);
|
||||
|
||||
n = MIN (size, frida_link.from_host_len);
|
||||
if (n != 0)
|
||||
{
|
||||
memcpy (data, frida_link.from_host, n);
|
||||
memmove (frida_link.from_host, frida_link.from_host + n, frida_link.from_host_len - n);
|
||||
frida_link.from_host_len -= n;
|
||||
}
|
||||
|
||||
lck_mtx_unlock (frida_link.lock);
|
||||
|
||||
return (ssize_t) n;
|
||||
}
|
||||
|
||||
static int
|
||||
frida_dev_ioctl (dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc * p)
|
||||
{
|
||||
switch (cmd)
|
||||
{
|
||||
case FRIDA_IOC_SET_ADDR:
|
||||
{
|
||||
FridaAddrRequest * r = (FridaAddrRequest *) data;
|
||||
size_t i;
|
||||
|
||||
r->name[sizeof (r->name) - 1] = '\0';
|
||||
|
||||
for (i = 0; i != sizeof (frida_kernel_addrs) / sizeof (frida_kernel_addrs[0]); i++)
|
||||
{
|
||||
if (strncmp (frida_kernel_addrs[i].name, r->name, sizeof (r->name)) == 0)
|
||||
{
|
||||
*frida_kernel_addrs[i].slot = (void *) (uintptr_t) r->address;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return ENOENT;
|
||||
}
|
||||
case FRIDA_IOC_GET_IMAGE:
|
||||
{
|
||||
FridaImageInfo * info = (FridaImageInfo *) data;
|
||||
|
||||
info->base = frida_own_base;
|
||||
info->size = frida_own_size;
|
||||
|
||||
return 0;
|
||||
}
|
||||
case FRIDA_IOC_START:
|
||||
{
|
||||
if (frida_running)
|
||||
return EBUSY;
|
||||
|
||||
if (frida_agent_start (frida_own_base, frida_own_size) != 0)
|
||||
return ENOMEM;
|
||||
|
||||
frida_running = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
default:
|
||||
return ENOTTY;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
#include <frida-gumjs.h>
|
||||
#include <ptrauth.h>
|
||||
|
||||
_Static_assert (ptrauth_type_discriminator (__typeof__ (((GSourceFuncs *) 0)->prepare))
|
||||
== 0xe4fe, "the kernel's own ABI still discriminates by type");
|
||||
@@ -1,154 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Make a kernel the agent can write to and run code in."""
|
||||
import struct
|
||||
import sys
|
||||
|
||||
NOP = 0xD503201F
|
||||
FORBIDS_BELOW = 0x3800
|
||||
ALLOWS_BELOW = 0x2000
|
||||
|
||||
|
||||
def main(source, destination):
|
||||
image = bytearray(open(source, "rb").read())
|
||||
secs = sections(image)
|
||||
|
||||
print("pages the pmap has locked down:")
|
||||
locked = stop_refusing(image, secs, strings_saying(bytes(image), secs, b"locked down"),
|
||||
"may be mapped")
|
||||
print("what a table forbids underneath it:")
|
||||
below = stop_forbidding_execution_below(image, secs)
|
||||
print("executable mappings in the kernel's own pmap:")
|
||||
executable = stop_refusing(image, secs,
|
||||
strings_saying(bytes(image), secs, b"executable mapping"),
|
||||
"may be made")
|
||||
|
||||
if not (locked and below and executable):
|
||||
sys.exit("kernel does not look like one this tool knows")
|
||||
|
||||
open(destination, "wb").write(bytes(image))
|
||||
print(f"\n{locked + below + executable} changes -> {destination}")
|
||||
|
||||
|
||||
def stop_refusing(image, secs, saying, what):
|
||||
undone = 0
|
||||
for segment, name, addr, size, foff in secs:
|
||||
if name != "__text":
|
||||
continue
|
||||
code = bytes(image[foff:foff + size])
|
||||
blocks = {addr + b
|
||||
for b in (start_of_block(code, s) for s in complaints_at(code, addr, saying))
|
||||
if b is not None}
|
||||
if not blocks:
|
||||
continue
|
||||
for where, at, word in branches_into(code, addr, blocks):
|
||||
struct.pack_into("<I", image, foff + at, NOP)
|
||||
print(f" {segment} {where:#x} {word:#010x} -> nop ({what})")
|
||||
undone += 1
|
||||
return undone
|
||||
|
||||
|
||||
def stop_forbidding_execution_below(image, secs):
|
||||
undone = 0
|
||||
for segment, name, addr, size, foff in secs:
|
||||
if name != "__text":
|
||||
continue
|
||||
code = bytes(image[foff:foff + size])
|
||||
for i in range(0, len(code) - 4, 4):
|
||||
word = struct.unpack_from("<I", code, i)[0]
|
||||
builds_high = (word & 0xFF800000) == 0xF2800000 and ((word >> 21) & 3) == 3
|
||||
if not builds_high or ((word >> 5) & 0xFFFF) != FORBIDS_BELOW:
|
||||
continue
|
||||
into = word & 0x1F
|
||||
struct.pack_into("<I", image, foff + i, 0xF2E00000 | (ALLOWS_BELOW << 5) | into)
|
||||
print(f" {segment} {addr + i:#x} {word:#010x} -> "
|
||||
f"movk x{into}, #{ALLOWS_BELOW:#06x}, lsl 48"
|
||||
f" (a table no longer forbids what is under it)")
|
||||
undone += 1
|
||||
return undone
|
||||
|
||||
|
||||
def strings_saying(image, secs, wanted):
|
||||
where = set()
|
||||
at = image.find(wanted)
|
||||
while at != -1:
|
||||
start = image.rfind(b"\0", 0, at) + 1
|
||||
for _, name, addr, size, foff in secs:
|
||||
if name == "__cstring" and foff <= start < foff + size:
|
||||
where.add(addr + (start - foff))
|
||||
at = image.find(wanted, at + 1)
|
||||
return where
|
||||
|
||||
|
||||
def complaints_at(code, base, about):
|
||||
said = []
|
||||
for i in range(0, len(code) - 4, 4):
|
||||
word = struct.unpack_from("<I", code, i)[0]
|
||||
if (word & 0x9F000000) != 0x90000000:
|
||||
continue
|
||||
into = word & 0x1F
|
||||
page = (((word >> 5) & 0x7FFFF) << 2) | ((word >> 29) & 3)
|
||||
if page & (1 << 20):
|
||||
page -= 1 << 21
|
||||
page = ((base + i) & ~0xFFF) + (page << 12)
|
||||
for j in range(i + 4, min(i + 48, len(code) - 4), 4):
|
||||
after = struct.unpack_from("<I", code, j)[0]
|
||||
if (after & 0xFF800000) == 0x91000000 and ((after >> 5) & 0x1F) == into:
|
||||
if page + ((after >> 10) & 0xFFF) in about:
|
||||
said.append(i)
|
||||
break
|
||||
return said
|
||||
|
||||
|
||||
def start_of_block(code, at):
|
||||
i = at
|
||||
while i >= 4:
|
||||
word = struct.unpack_from("<I", code, i - 4)[0]
|
||||
if (word & 0xFC000000) in (0x94000000, 0x14000000):
|
||||
return i
|
||||
if (word & 0xFFFFFC1F) == 0xD65F0000 or word == 0xD65F0FFF:
|
||||
return i
|
||||
i -= 4
|
||||
return None
|
||||
|
||||
|
||||
def branches_into(code, base, blocks):
|
||||
found = []
|
||||
for i in range(0, len(code) - 4, 4):
|
||||
word = struct.unpack_from("<I", code, i)[0]
|
||||
if (word & 0xFF000010) == 0x54000000 or (word & 0x7E000000) == 0x34000000:
|
||||
far, sign = (word >> 5) & 0x7FFFF, 1 << 18
|
||||
elif (word & 0x7E000000) == 0x36000000:
|
||||
far, sign = (word >> 5) & 0x3FFF, 1 << 13
|
||||
else:
|
||||
continue
|
||||
if far & sign:
|
||||
far -= sign << 1
|
||||
if base + i + far * 4 in blocks:
|
||||
found.append((base + i, i, word))
|
||||
return found
|
||||
|
||||
|
||||
def sections(image):
|
||||
count = struct.unpack_from("<I", image, 16)[0]
|
||||
at, found = 32, []
|
||||
for _ in range(count):
|
||||
kind, size = struct.unpack_from("<II", image, at)
|
||||
if kind == 0x19:
|
||||
how_many = struct.unpack_from("<I", image, at + 64)[0]
|
||||
so = at + 72
|
||||
for _ in range(how_many):
|
||||
found.append((
|
||||
image[so + 16:so + 32].rstrip(b"\0").decode(),
|
||||
image[so:so + 16].rstrip(b"\0").decode(),
|
||||
*struct.unpack_from("<QQ", image, so + 32),
|
||||
struct.unpack_from("<I", image, so + 48)[0],
|
||||
))
|
||||
so += 80
|
||||
at += size
|
||||
return found
|
||||
|
||||
|
||||
if len(sys.argv) != 3:
|
||||
sys.exit(f"usage: {sys.argv[0]} <kernel> <patched-kernel>")
|
||||
|
||||
main(sys.argv[1], sys.argv[2])
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "frida-barebone-agent",
|
||||
"version": "1.0.0",
|
||||
"description": "xPack configuration for frida-barebone-agent",
|
||||
"xpack": {
|
||||
"minimumXpmRequired": "0.20.8",
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"@xpack-dev-tools/aarch64-none-elf-gcc": {
|
||||
"specifier": "14.2.1-1.1.1",
|
||||
"local": "link",
|
||||
"platforms": "all"
|
||||
}
|
||||
},
|
||||
"properties": {},
|
||||
"actions": {},
|
||||
"buildConfigurations": {}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
use core::ffi::c_void;
|
||||
|
||||
use crate::bindings::{gpointer, gsize, gum_try_mprotect, GumPageProtection};
|
||||
use crate::{kernel, libc};
|
||||
use crate::bindings::{gpointer, gsize, GumPageProtection};
|
||||
use crate::{gum, libc, xnu};
|
||||
|
||||
const GUM_PAGE_READ: GumPageProtection = 1;
|
||||
const GUM_PAGE_EXECUTE: GumPageProtection = 4;
|
||||
@@ -10,7 +10,7 @@ const GUM_PAGE_EXECUTE: GumPageProtection = 4;
|
||||
// static trampoline `text` made executable, followed by a writable parameter
|
||||
// table `map_size` bytes later (the trampolines reach their parameters via that
|
||||
// fixed offset). The two halves are whole pages, so flipping the first to RX
|
||||
// leaves the parameter table writable.
|
||||
// over the hostlink leaves the parameter table writable.
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn ffi_tramp_embedder_map(
|
||||
text: *const c_void,
|
||||
@@ -19,7 +19,7 @@ pub unsafe extern "C" fn ffi_tramp_embedder_map(
|
||||
parm_table: *mut *mut c_void,
|
||||
) -> i32 {
|
||||
unsafe {
|
||||
let base = kernel::alloc_code(map_size * 2);
|
||||
let base = xnu::kalloc(map_size * 2);
|
||||
if base.is_null() {
|
||||
return 0;
|
||||
}
|
||||
@@ -28,9 +28,9 @@ pub unsafe extern "C" fn ffi_tramp_embedder_map(
|
||||
libc::__clear_cache(base, base.add(map_size));
|
||||
|
||||
let made_executable =
|
||||
gum_try_mprotect(base as gpointer, map_size as gsize, GUM_PAGE_READ | GUM_PAGE_EXECUTE);
|
||||
gum::gum_try_mprotect(base as gpointer, map_size as gsize, GUM_PAGE_READ | GUM_PAGE_EXECUTE);
|
||||
if made_executable == 0 {
|
||||
kernel::free_code(base, map_size * 2);
|
||||
xnu::free(base, map_size * 2);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -46,5 +46,7 @@ pub unsafe extern "C" fn ffi_tramp_embedder_unmap(
|
||||
_parm_table: *mut c_void,
|
||||
map_size: usize,
|
||||
) {
|
||||
kernel::free_code(code_table as *mut u8, map_size * 2);
|
||||
unsafe {
|
||||
xnu::free(code_table as *mut u8, map_size * 2);
|
||||
}
|
||||
}
|
||||
@@ -1,69 +1,46 @@
|
||||
use core::ptr;
|
||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
use crate::bindings::{g_wait_is_set, gint64, gpointer};
|
||||
use crate::kernel;
|
||||
use crate::xnu;
|
||||
|
||||
const G_WAIT_INFINITE: gint64 = -1;
|
||||
|
||||
pub static mut WAKEUP_TOKEN: u64 = 0;
|
||||
|
||||
pub fn wakeup_token() -> *const u8 {
|
||||
ptr::addr_of!(WAKEUP_TOKEN) as *const u8
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_get_monotonic_time() -> gint64 {
|
||||
kernel::monotonic_micros()
|
||||
let abstime = xnu::mach_absolute_time();
|
||||
let time = xnu::absolutetime_to_nanoseconds(abstime);
|
||||
(time / 1000) as gint64
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_wait_sleep(token: gpointer, timeout_us: gint64) {
|
||||
let timeout = if timeout_us == G_WAIT_INFINITE {
|
||||
None
|
||||
let wait_event = ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8;
|
||||
|
||||
let wait_result = if timeout_us == G_WAIT_INFINITE {
|
||||
xnu::assert_wait(wait_event, xnu::THREAD_INTERRUPTIBLE)
|
||||
} else {
|
||||
Some(timeout_us as u64)
|
||||
xnu::assert_wait_timeout(
|
||||
wait_event,
|
||||
xnu::THREAD_INTERRUPTIBLE,
|
||||
(timeout_us * 1000) as u32,
|
||||
1, // TODO
|
||||
)
|
||||
};
|
||||
|
||||
let ours = is_loop_thread();
|
||||
let event = if ours {
|
||||
LOOP_TOKEN.store(token as usize, Ordering::Release);
|
||||
ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8
|
||||
} else {
|
||||
token as *const u8
|
||||
};
|
||||
|
||||
kernel::wait(event, timeout, &mut || unsafe { g_wait_is_set(token) != 0 });
|
||||
|
||||
if ours {
|
||||
LOOP_TOKEN.store(0, Ordering::Release);
|
||||
if wait_result != xnu::THREAD_WAITING {
|
||||
panic!("assert_wait_timeout failed: {}", wait_result);
|
||||
}
|
||||
|
||||
if unsafe { g_wait_is_set(token) != 0 } {
|
||||
xnu::thread_wakeup(wait_event);
|
||||
return;
|
||||
}
|
||||
|
||||
xnu::thread_block(None);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_wait_wake(token: gpointer) {
|
||||
kernel::poke_loop_wakeup();
|
||||
kernel::wake(ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8);
|
||||
kernel::wake(token as *const u8);
|
||||
}
|
||||
|
||||
pub fn is_loop_thread() -> bool {
|
||||
kernel::current_thread_id() == LOOP_THREAD.load(Ordering::Acquire) as u64
|
||||
}
|
||||
|
||||
pub fn own_the_loop() {
|
||||
LOOP_THREAD.store(kernel::current_thread_id() as usize, Ordering::Release);
|
||||
}
|
||||
|
||||
static LOOP_THREAD: AtomicUsize = AtomicUsize::new(0);
|
||||
static LOOP_TOKEN: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_io_channel_unix_new(_fd: i32) -> gpointer {
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_spawn_async_with_pipes() -> i32 {
|
||||
0
|
||||
pub extern "C" fn g_wait_wake(_token: gpointer) {
|
||||
xnu::thread_wakeup(ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8);
|
||||
}
|
||||
@@ -8,39 +8,34 @@ use crate::bindings::GThreadFunc;
|
||||
use crate::bindings::{gpointer, gulong};
|
||||
use alloc::boxed::Box;
|
||||
use alloc::collections::BTreeMap;
|
||||
use core::arch::asm;
|
||||
use core::ffi::c_void;
|
||||
use core::sync::atomic::{AtomicU32, AtomicU64, AtomicUsize, Ordering};
|
||||
use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
|
||||
|
||||
// Our implementation structures that overlay the opaque GLib structures
|
||||
|
||||
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
|
||||
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
|
||||
#[repr(C, align(8))]
|
||||
struct MutexImpl {
|
||||
lock: AtomicU32,
|
||||
_padding: [u8; core::mem::size_of::<GMutex>() - core::mem::size_of::<AtomicU32>()],
|
||||
}
|
||||
|
||||
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
|
||||
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
|
||||
#[repr(C, align(8))]
|
||||
struct RecMutexImpl {
|
||||
owner: AtomicUsize, // Thread ID of the owner (uses the 'p' field)
|
||||
owner: AtomicU64, // Thread ID of the owner (uses the 'p' field)
|
||||
count: AtomicU32, // Recursion count (uses i[0])
|
||||
_unused: u32, // Unused (uses i[1])
|
||||
}
|
||||
|
||||
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
|
||||
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
|
||||
#[repr(C, align(8))]
|
||||
struct RWLockImpl {
|
||||
state: AtomicU32, // Lock state (readers count + writer bit)
|
||||
_padding: [u8; core::mem::size_of::<GRWLock>() - core::mem::size_of::<AtomicU32>()],
|
||||
}
|
||||
|
||||
// A generation counter rather than a flag: a flag stays set once signalled, so every
|
||||
// later wait returns at once and the caller's loop spins instead of blocking.
|
||||
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
|
||||
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
|
||||
#[repr(C, align(8))]
|
||||
struct CondImpl {
|
||||
generation: AtomicU32,
|
||||
signal: AtomicU32, // Signal state
|
||||
_padding: [u8; core::mem::size_of::<GCond>() - core::mem::size_of::<AtomicU32>()],
|
||||
}
|
||||
|
||||
@@ -149,9 +144,10 @@ pub extern "C" fn g_rec_mutex_clear(_rec_mutex: *mut GRecMutex) {
|
||||
pub extern "C" fn g_rec_mutex_lock(rec_mutex: *mut GRecMutex) {
|
||||
unsafe {
|
||||
let impl_ = rec_mutex_impl(rec_mutex);
|
||||
let current_thread = get_current_thread_id();
|
||||
|
||||
loop {
|
||||
if rec_mutex_try_acquire(impl_) {
|
||||
if rec_mutex_try_acquire(impl_, current_thread) {
|
||||
break;
|
||||
}
|
||||
rec_mutex_wait(impl_);
|
||||
@@ -163,8 +159,9 @@ pub extern "C" fn g_rec_mutex_lock(rec_mutex: *mut GRecMutex) {
|
||||
pub extern "C" fn g_rec_mutex_trylock(rec_mutex: *mut GRecMutex) -> u32 {
|
||||
unsafe {
|
||||
let impl_ = rec_mutex_impl(rec_mutex);
|
||||
let current_thread = get_current_thread_id();
|
||||
|
||||
if rec_mutex_try_acquire(impl_) {
|
||||
if rec_mutex_try_acquire(impl_, current_thread) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
@@ -186,7 +183,7 @@ pub extern "C" fn g_rec_mutex_unlock(rec_mutex: *mut GRecMutex) {
|
||||
.is_ok()
|
||||
{
|
||||
impl_.owner.store(0, Ordering::Relaxed);
|
||||
wake_waiters(&impl_.count);
|
||||
lock_notify_all();
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
@@ -201,10 +198,7 @@ pub extern "C" fn g_rec_mutex_unlock(rec_mutex: *mut GRecMutex) {
|
||||
}
|
||||
}
|
||||
|
||||
// Asking the kernel who we are costs more than the uncontended acquisition itself, and
|
||||
// every allocation takes this path, so the identity is only fetched once the lock turns
|
||||
// out to be held.
|
||||
unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl) -> bool {
|
||||
unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl, current_thread: u64) -> bool {
|
||||
let current_count = impl_.count.load(Ordering::Relaxed);
|
||||
|
||||
if current_count == 0 {
|
||||
@@ -212,12 +206,12 @@ unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl) -> bool {
|
||||
.compare_exchange_weak(0, 1, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_ok()
|
||||
{
|
||||
impl_.owner.store(crate::kernel::current_thread_id() as usize, Ordering::Relaxed);
|
||||
impl_.owner.store(current_thread, Ordering::Relaxed);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
let current_owner = impl_.owner.load(Ordering::Relaxed);
|
||||
if current_owner == crate::kernel::current_thread_id() as usize {
|
||||
if current_owner == current_thread {
|
||||
if impl_.count
|
||||
.compare_exchange_weak(
|
||||
current_count,
|
||||
@@ -236,7 +230,7 @@ unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl) -> bool {
|
||||
|
||||
unsafe fn rec_mutex_wait(impl_: &mut RecMutexImpl) {
|
||||
while impl_.count.load(Ordering::Relaxed) != 0 {
|
||||
wait_on(&impl_.count, &mut || impl_.count.load(Ordering::Relaxed) == 0);
|
||||
unsafe { lock_wait_primitive() };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,7 +282,7 @@ pub extern "C" fn g_rw_lock_writer_unlock(rw_lock: *mut GRWLock) {
|
||||
unsafe {
|
||||
let impl_ = rw_lock_impl(rw_lock);
|
||||
impl_.state.store(0, Ordering::Release);
|
||||
wake_waiters(&impl_.state);
|
||||
lock_notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,7 +329,7 @@ pub extern "C" fn g_rw_lock_reader_unlock(rw_lock: *mut GRWLock) {
|
||||
.is_ok()
|
||||
{
|
||||
if current - 1 == 0 {
|
||||
wake_waiters(&impl_.state);
|
||||
lock_notify_all();
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -361,15 +355,13 @@ unsafe fn rw_lock_reader_try_acquire(impl_: &mut RWLockImpl) -> bool {
|
||||
|
||||
unsafe fn rw_lock_wait_for_writers(impl_: &mut RWLockImpl) {
|
||||
while impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT != 0 {
|
||||
wait_on(&impl_.state, &mut || {
|
||||
impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT == 0
|
||||
});
|
||||
unsafe { lock_wait_primitive() };
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn rw_lock_wait_for_all(impl_: &mut RWLockImpl) {
|
||||
while impl_.state.load(Ordering::Relaxed) != 0 {
|
||||
wait_on(&impl_.state, &mut || impl_.state.load(Ordering::Relaxed) == 0);
|
||||
unsafe { lock_wait_primitive() };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,7 +369,7 @@ unsafe fn rw_lock_wait_for_all(impl_: &mut RWLockImpl) {
|
||||
pub extern "C" fn g_cond_init(cond: *mut GCond) {
|
||||
unsafe {
|
||||
let impl_ = cond_impl(cond);
|
||||
impl_.generation.store(0, Ordering::Relaxed);
|
||||
impl_.signal.store(0, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -387,17 +379,11 @@ pub extern "C" fn g_cond_clear(_cond: *mut GCond) {}
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_cond_wait(cond: *mut GCond, mutex: *mut GMutex) {
|
||||
unsafe {
|
||||
// Read the generation under the mutex, so a signal that lands after the
|
||||
// unlock below is still observed as a change.
|
||||
let seen = cond_impl(cond).generation.load(Ordering::Acquire);
|
||||
|
||||
g_mutex_unlock(mutex);
|
||||
|
||||
let impl_ = cond_impl(cond);
|
||||
while impl_.generation.load(Ordering::Acquire) == seen {
|
||||
wait_on(&impl_.generation, &mut || {
|
||||
impl_.generation.load(Ordering::Acquire) != seen
|
||||
});
|
||||
while impl_.signal.load(Ordering::Acquire) == 0 {
|
||||
lock_wait_primitive();
|
||||
}
|
||||
|
||||
g_mutex_lock(mutex);
|
||||
@@ -408,8 +394,8 @@ pub extern "C" fn g_cond_wait(cond: *mut GCond, mutex: *mut GMutex) {
|
||||
pub extern "C" fn g_cond_signal(cond: *mut GCond) {
|
||||
unsafe {
|
||||
let impl_ = cond_impl(cond);
|
||||
impl_.generation.fetch_add(1, Ordering::Release);
|
||||
wake_waiters(&impl_.generation);
|
||||
impl_.signal.store(1, Ordering::Release);
|
||||
lock_notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,82 +403,22 @@ pub extern "C" fn g_cond_signal(cond: *mut GCond) {
|
||||
pub extern "C" fn g_cond_broadcast(cond: *mut GCond) {
|
||||
unsafe {
|
||||
let impl_ = cond_impl(cond);
|
||||
impl_.generation.fetch_add(1, Ordering::Release);
|
||||
wake_waiters(&impl_.generation);
|
||||
impl_.signal.store(1, Ordering::Release);
|
||||
lock_notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_cond_wait_until(cond: *mut GCond, mutex: *mut GMutex, end_time: i64) -> u32 {
|
||||
unsafe {
|
||||
let seen = cond_impl(cond).generation.load(Ordering::Acquire);
|
||||
|
||||
g_mutex_unlock(mutex);
|
||||
|
||||
let impl_ = cond_impl(cond);
|
||||
let mut signalled = impl_.generation.load(Ordering::Acquire) != seen;
|
||||
while !signalled && crate::kernel::monotonic_micros() < end_time {
|
||||
wait_on(&impl_.generation, &mut || {
|
||||
impl_.generation.load(Ordering::Acquire) != seen
|
||||
});
|
||||
signalled = impl_.generation.load(Ordering::Acquire) != seen;
|
||||
}
|
||||
|
||||
g_mutex_lock(mutex);
|
||||
|
||||
if signalled { 1 } else { 0 }
|
||||
}
|
||||
pub extern "C" fn g_cond_wait_until(cond: *mut GCond, mutex: *mut GMutex, _end_time: i64) -> u32 {
|
||||
// TODO: Implement proper timeout handling
|
||||
g_cond_wait(cond, mutex);
|
||||
1
|
||||
}
|
||||
|
||||
// A half that runs on a system with slots of its own says so, and every GPrivate takes one. The
|
||||
// map below is what the kernel halves use, and it costs a lock and a lookup per call, which is
|
||||
// what code the Stalker writes does on every block it leaves.
|
||||
pub struct ThreadSlots {
|
||||
pub take: fn() -> u32,
|
||||
pub get: fn(u32) -> *mut c_void,
|
||||
pub set: fn(u32, *mut c_void),
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt", feature = "xnu-core"))]
|
||||
pub fn use_thread_slots(slots: &'static ThreadSlots) {
|
||||
unsafe { SLOTS = Some(slots) };
|
||||
}
|
||||
|
||||
fn slot_of(key: *mut GPrivate, slots: &ThreadSlots) -> u32 {
|
||||
let taken = unsafe { (*key).p } as usize;
|
||||
if taken != 0 {
|
||||
return (taken - 1) as u32;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
lock_acquire(&TLS_LOCK);
|
||||
|
||||
let mut taken = (*key).p as usize;
|
||||
if taken == 0 {
|
||||
taken = ((slots.take)() + 1) as usize;
|
||||
(*key).p = taken as *mut c_void;
|
||||
}
|
||||
|
||||
lock_release(&TLS_LOCK);
|
||||
|
||||
(taken - 1) as u32
|
||||
}
|
||||
}
|
||||
|
||||
fn slots() -> Option<&'static ThreadSlots> {
|
||||
unsafe { SLOTS }
|
||||
}
|
||||
|
||||
static mut SLOTS: Option<&'static ThreadSlots> = None;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_private_get(key: *mut GPrivate) -> *mut c_void {
|
||||
if let Some(slots) = slots() {
|
||||
return (slots.get)(slot_of(key, slots));
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let thread_id = crate::kernel::current_thread_id();
|
||||
let thread_id = get_current_thread_id();
|
||||
lock_acquire(&TLS_LOCK);
|
||||
|
||||
ensure_tls_storage();
|
||||
@@ -511,13 +437,8 @@ pub extern "C" fn g_private_get(key: *mut GPrivate) -> *mut c_void {
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn g_private_set(key: *mut GPrivate, value: *mut c_void) {
|
||||
if let Some(slots) = slots() {
|
||||
(slots.set)(slot_of(key, slots), value);
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let thread_id = crate::kernel::current_thread_id();
|
||||
let thread_id = get_current_thread_id();
|
||||
lock_acquire(&TLS_LOCK);
|
||||
|
||||
ensure_tls_storage();
|
||||
@@ -547,9 +468,7 @@ pub extern "C" fn _g_system_thread_create(
|
||||
data: gpointer,
|
||||
) -> *mut GSystemThread {
|
||||
unsafe {
|
||||
crate::kernel::log("frida: glib is creating a thread\n\0");
|
||||
|
||||
let system_thread = crate::kernel::alloc(core::mem::size_of::<SystemThreadImpl>()) as *mut SystemThreadImpl;
|
||||
let system_thread = crate::xnu::kalloc(core::mem::size_of::<SystemThreadImpl>()) as *mut SystemThreadImpl;
|
||||
|
||||
g_mutex_init(&mut (*system_thread).mutex);
|
||||
g_cond_init(&mut (*system_thread).cond);
|
||||
@@ -559,12 +478,12 @@ pub extern "C" fn _g_system_thread_create(
|
||||
(*system_thread).finished.store(0, Ordering::Relaxed);
|
||||
(*system_thread).detached.store(0, Ordering::Relaxed);
|
||||
|
||||
let wrapper_data = crate::kernel::alloc(core::mem::size_of::<ThreadWrapperData>()) as *mut ThreadWrapperData;
|
||||
let wrapper_data = crate::xnu::kalloc(core::mem::size_of::<ThreadWrapperData>()) as *mut ThreadWrapperData;
|
||||
(*wrapper_data).func = func;
|
||||
(*wrapper_data).data = data;
|
||||
(*wrapper_data).system_thread = system_thread;
|
||||
|
||||
let _xnu_result = crate::kernel::spawn_thread(thread_wrapper, wrapper_data as *mut c_void);
|
||||
let _xnu_result = crate::xnu::kernel_thread_start(thread_wrapper, wrapper_data as *mut c_void);
|
||||
|
||||
system_thread as *mut GSystemThread
|
||||
}
|
||||
@@ -577,9 +496,8 @@ extern "C" fn thread_wrapper(parameter: *mut c_void, _wait_result: i32) {
|
||||
let data = (*wrapper_data).data;
|
||||
let system_thread = (*wrapper_data).system_thread;
|
||||
|
||||
let current_thread_id = crate::kernel::current_thread_id();
|
||||
(*system_thread).thread_id.store(current_thread_id, Ordering::Release);
|
||||
wake_waiters(&(*system_thread).thread_id);
|
||||
let current_thread_id = get_current_thread_id();
|
||||
(*system_thread).thread_id.store(current_thread_id, Ordering::Relaxed);
|
||||
|
||||
func.unwrap()(data);
|
||||
|
||||
@@ -619,10 +537,12 @@ pub extern "C" fn _g_system_thread_wait(thread: *mut GSystemThread) {
|
||||
unsafe {
|
||||
let system_thread = thread as *mut SystemThreadImpl;
|
||||
|
||||
while (*system_thread).thread_id.load(Ordering::Acquire) == 0 {
|
||||
wait_on(&(*system_thread).thread_id, &mut || {
|
||||
(*system_thread).thread_id.load(Ordering::Acquire) != 0
|
||||
});
|
||||
loop {
|
||||
let thread_id = (*system_thread).thread_id.load(Ordering::Acquire);
|
||||
if thread_id != 0 {
|
||||
break;
|
||||
}
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
|
||||
g_mutex_lock(&mut (*system_thread).mutex);
|
||||
@@ -636,16 +556,19 @@ pub extern "C" fn _g_system_thread_wait(thread: *mut GSystemThread) {
|
||||
}
|
||||
|
||||
unsafe fn cleanup_system_thread(system_thread: *mut SystemThreadImpl) {
|
||||
crate::kernel::free(system_thread as *mut u8, core::mem::size_of::<SystemThreadImpl>());
|
||||
crate::xnu::free(system_thread as *mut u8, core::mem::size_of::<SystemThreadImpl>());
|
||||
}
|
||||
|
||||
unsafe fn cleanup_wrapper_data(wrapper_data: *mut ThreadWrapperData) {
|
||||
crate::kernel::free(wrapper_data as *mut u8, core::mem::size_of::<ThreadWrapperData>());
|
||||
crate::xnu::free(wrapper_data as *mut u8, core::mem::size_of::<ThreadWrapperData>());
|
||||
}
|
||||
|
||||
unsafe fn lock_acquire(lock: &AtomicU32) {
|
||||
while !unsafe { lock_try_acquire(lock) } {
|
||||
wait_on(lock, &mut || lock.load(Ordering::Relaxed) == 0);
|
||||
loop {
|
||||
if unsafe { lock_try_acquire(lock) } {
|
||||
break;
|
||||
}
|
||||
unsafe { lock_wait(lock) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -656,36 +579,35 @@ unsafe fn lock_try_acquire(lock: &AtomicU32) -> bool {
|
||||
|
||||
unsafe fn lock_release(lock: &AtomicU32) {
|
||||
lock.store(0, Ordering::Release);
|
||||
wake_waiters(lock);
|
||||
unsafe { lock_notify_all() };
|
||||
}
|
||||
|
||||
// Sleeps until `ready` holds or the slice expires, whichever comes first.
|
||||
//
|
||||
// These used to spin on wfe/sev. A kernel thread that spins never reaches the
|
||||
// scheduler, so a lost wakeup — or a genuine deadlock, which is how we found this —
|
||||
// starves the CPU it is on until the hardware watchdog takes the machine down. The
|
||||
// bounded sleep turns both into a stall that can be observed instead.
|
||||
//
|
||||
// The address of the word being waited on names the wait channel: XNU pairs
|
||||
// assert_wait()/thread_wakeup() on it directly, while the Linux shim has a single
|
||||
// channel and treats it as a hint. Every caller re-checks its own condition in a
|
||||
// loop, so a spurious wakeup costs nothing either way.
|
||||
fn wait_on<T>(word: &T, ready: &mut dyn FnMut() -> bool) {
|
||||
crate::kernel::wait(token_of(word), Some(WAIT_SLICE_US), ready);
|
||||
|
||||
// The wait above returns without sleeping whenever `ready` already holds, so on
|
||||
// its own it is not enough to keep a contended loop off the CPU: every caller
|
||||
// here loops, and a loop in a kernel thread that never reaches the scheduler
|
||||
// takes the machine down rather than merely running hot. Yield unconditionally.
|
||||
crate::kernel::yield_now();
|
||||
unsafe fn lock_wait(lock: &AtomicU32) {
|
||||
while lock.load(Ordering::Relaxed) != 0 {
|
||||
unsafe { lock_wait_primitive() };
|
||||
}
|
||||
}
|
||||
|
||||
fn wake_waiters<T>(word: &T) {
|
||||
crate::kernel::wake(token_of(word));
|
||||
unsafe fn lock_wait_primitive() {
|
||||
unsafe {
|
||||
asm!("wfe", options(nomem, nostack));
|
||||
}
|
||||
}
|
||||
|
||||
fn token_of<T>(word: &T) -> *const u8 {
|
||||
word as *const T as *const u8
|
||||
unsafe fn lock_notify_all() {
|
||||
unsafe {
|
||||
asm!("sev", options(nomem, nostack));
|
||||
}
|
||||
}
|
||||
|
||||
const WAIT_SLICE_US: u64 = 1000;
|
||||
// The thread pointer is exposed to JavaScript as a GumThreadId, so it must fit
|
||||
// in a double without losing precision; the low bits keep it unique per thread.
|
||||
const JS_SAFE_THREAD_ID_MASK: u64 = (1 << 48) - 1;
|
||||
|
||||
pub unsafe fn get_current_thread_id() -> u64 {
|
||||
let thread_ptr: u64;
|
||||
unsafe {
|
||||
asm!("mrs {}, tpidr_el1", out(reg) thread_ptr, options(nomem, nostack));
|
||||
}
|
||||
thread_ptr & JS_SAFE_THREAD_ID_MASK
|
||||
}
|
||||
+438
-387
@@ -1,79 +1,296 @@
|
||||
// Gum's platform backend, without the parts that depend on the kernel. Memory protection,
|
||||
// symbols and the module registry are in gum_injected.rs, gum_linux.rs and gum_windows.rs.
|
||||
|
||||
use crate::{
|
||||
bindings::{
|
||||
_GInterfaceInfo, _GTypeInfo, GObject, GObjectClass, GPrivate, GType, GumAddress,
|
||||
GumExportDetails,
|
||||
GumExportType_GUM_EXPORT_FUNCTION, GumFoundExportFunc, GumMemoryRange, GumModule,
|
||||
GumModuleInterface, GumThreadId, GumTlsKey, g_free, g_object_get_type, g_object_new,
|
||||
g_once_init_enter, g_once_init_leave, g_strdup, g_type_add_interface_static,
|
||||
g_type_class_peek_parent, g_type_register_static, gchar, gpointer, gsize, guint,
|
||||
_GInterfaceInfo, _GTypeInfo, _GumPageProtection_GUM_PAGE_EXECUTE,
|
||||
_GumPageProtection_GUM_PAGE_READ, _GumPageProtection_GUM_PAGE_WRITE,
|
||||
_GumRwxSupport_GUM_RWX_NONE, GArray, GObject, GObjectClass, GPrivate, GType,
|
||||
GumDebugSymbolDetails, GumExportDetails, GumExportType_GUM_EXPORT_FUNCTION,
|
||||
GumFoundExportFunc, GumMemoryRange, GumModule, GumModuleInterface, GumModuleRegistry,
|
||||
GumPageProtection, GumRwxSupport, GumThreadId, GumTlsKey, g_array_append_vals, g_array_new,
|
||||
g_free, g_object_get_type, g_object_new, g_object_unref, g_once_init_enter,
|
||||
g_once_init_leave, g_strdup, g_type_add_interface_static, g_type_class_peek_parent,
|
||||
g_type_register_static, g_variant_get_boolean, g_variant_get_uint64, g_variant_new,
|
||||
g_variant_new_fixed_array, g_variant_type_free, g_variant_type_new, gboolean, gchar,
|
||||
gconstpointer, gpointer, gsize, guint,
|
||||
gum_barebone_register_module,
|
||||
gum_barebone_try_remap_writable_pages as _gum_barebone_try_remap_writable_pages,
|
||||
g_variant_unref, gum_module_get_type, gum_mprotect, gum_query_page_size,
|
||||
},
|
||||
gthread, kernel, libc,
|
||||
gthread, host_rpc, libc, FridaCommand,
|
||||
};
|
||||
use alloc::boxed::Box;
|
||||
use alloc::ffi::CString;
|
||||
use core::ffi::CStr;
|
||||
use alloc::format;
|
||||
use alloc::vec::Vec;
|
||||
use core::ffi::CStr;
|
||||
use core::mem::size_of;
|
||||
use core::ptr;
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
use crate::gum_linux::enumerate_exports_in_range;
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
use crate::bindings::{
|
||||
GumFoundSymbolFunc, GumSymbolDetails, GumSymbolType_GUM_SYMBOL_FUNCTION,
|
||||
GumSymbolType_GUM_SYMBOL_OBJECT, GumSymbolType_GUM_SYMBOL_UNKNOWN, gboolean, gssize,
|
||||
};
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
use crate::gum_modules::enumerate_symbols_in_range;
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
use crate::gum_windows::enumerate_exports_in_range;
|
||||
#[cfg(any(feature = "xnu-core", feature = "linux-injected"))]
|
||||
use crate::gum_injected::enumerate_exports_in_range;
|
||||
// Code slabs the agent allocates are normal writable RAM, so gum can keep a real writable alias to
|
||||
// them across the slab's lifetime. Pre-existing kernel/kext text is CTRR-locked and can only be
|
||||
// modified through the host's physical-memory bridge, so we shadow it and commit on dispose. We
|
||||
// distinguish the two by tracking the executable ranges we hand out.
|
||||
static SLAB_LOCK: AtomicU32 = AtomicU32::new(0);
|
||||
static mut SLABS: Vec<(u64, u64)> = Vec::new();
|
||||
|
||||
const SHADOW_MAGIC: u64 = 0x4644_4f48_5341_4853;
|
||||
const SHADOW_HEADER: usize = 24;
|
||||
const SHADOW_MIN_ADDRESS: u64 = 0xffff_f000_0000_0000;
|
||||
|
||||
fn slab_lock() {
|
||||
while SLAB_LOCK
|
||||
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
}
|
||||
|
||||
fn slab_unlock() {
|
||||
SLAB_LOCK.store(0, Ordering::Release);
|
||||
}
|
||||
|
||||
fn register_slab(start: u64, size: usize) {
|
||||
slab_lock();
|
||||
unsafe {
|
||||
(*core::ptr::addr_of_mut!(SLABS)).push((start, start + size as u64));
|
||||
}
|
||||
slab_unlock();
|
||||
}
|
||||
|
||||
fn unregister_slab(start: u64) {
|
||||
slab_lock();
|
||||
unsafe {
|
||||
(*core::ptr::addr_of_mut!(SLABS)).retain(|&(begin, _)| begin != start);
|
||||
}
|
||||
slab_unlock();
|
||||
}
|
||||
|
||||
fn is_agent_slab(address: u64) -> bool {
|
||||
slab_lock();
|
||||
let found = unsafe {
|
||||
(*core::ptr::addr_of!(SLABS))
|
||||
.iter()
|
||||
.any(|&(begin, end)| address >= begin && address < end)
|
||||
};
|
||||
slab_unlock();
|
||||
found
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_process_get_current_thread_id() -> GumThreadId {
|
||||
kernel::current_thread_id() as GumThreadId
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt", feature = "xnu-core"))]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_process_get_id() -> guint {
|
||||
kernel::current_process_id() as guint
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_process_get_id() -> guint {
|
||||
crate::source_process_id() as guint
|
||||
unsafe { gthread::get_current_thread_id() }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_query_page_size() -> guint {
|
||||
kernel::page_size() as guint
|
||||
}
|
||||
|
||||
// Only the kernel is allowed to ask the translation control register what it was set up with,
|
||||
// so a half that runs in a process is told instead.
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub(crate) fn page_size_the_kernel_runs_with() -> usize {
|
||||
let translation_control: u64;
|
||||
unsafe {
|
||||
core::arch::asm!("mrs {}, tcr_el1", out(reg) translation_control, options(nomem, nostack));
|
||||
}
|
||||
let tcr_el1: u64;
|
||||
core::arch::asm!("mrs {}, tcr_el1", out(reg) tcr_el1, options(nomem, nostack));
|
||||
|
||||
match (translation_control >> 14) & 0x3 {
|
||||
0b01 => 65536,
|
||||
0b10 => 16384,
|
||||
_ => 4096,
|
||||
let tg0 = (tcr_el1 >> 14) & 0x3;
|
||||
|
||||
match tg0 {
|
||||
0b00 => 4096,
|
||||
0b01 => 65536,
|
||||
0b10 => 16384,
|
||||
_ => 4096,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "arm"))]
|
||||
pub(crate) fn page_size_the_kernel_runs_with() -> usize {
|
||||
4096
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_query_rwx_support() -> GumRwxSupport {
|
||||
_GumRwxSupport_GUM_RWX_NONE
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_can_remap_writable() -> gboolean {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_try_remap_writable_pages(
|
||||
first_page: gpointer,
|
||||
n_pages: guint,
|
||||
) -> gpointer {
|
||||
if is_agent_slab(first_page as u64) {
|
||||
return remap_agent_pages(first_page, n_pages);
|
||||
}
|
||||
shadow_kernel_pages(first_page, n_pages)
|
||||
}
|
||||
|
||||
fn remap_agent_pages(first_page: gpointer, n_pages: guint) -> gpointer {
|
||||
unsafe {
|
||||
let page_size = gum_query_page_size() as usize;
|
||||
let mut virtual_addrs = Vec::with_capacity(n_pages as usize);
|
||||
|
||||
let mut current_page = first_page as u64;
|
||||
for _ in 0..n_pages {
|
||||
virtual_addrs.push(current_page as gpointer);
|
||||
current_page += page_size as u64;
|
||||
}
|
||||
|
||||
_gum_barebone_try_remap_writable_pages(
|
||||
virtual_addrs.as_ptr() as *mut *const core::ffi::c_void,
|
||||
virtual_addrs.len() as guint,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn shadow_kernel_pages(first_page: gpointer, n_pages: guint) -> gpointer {
|
||||
unsafe {
|
||||
let total = n_pages as usize * gum_query_page_size() as usize;
|
||||
let buffer = crate::xnu::kalloc(SHADOW_HEADER + total);
|
||||
*(buffer as *mut u64) = SHADOW_MAGIC;
|
||||
*(buffer.add(8) as *mut u64) = first_page as u64;
|
||||
*(buffer.add(16) as *mut u32) = n_pages;
|
||||
|
||||
let body = buffer.add(SHADOW_HEADER);
|
||||
core::ptr::copy_nonoverlapping(first_page as *const u8, body, total);
|
||||
body as gpointer
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages: guint) {
|
||||
if (writable as u64) < SHADOW_MIN_ADDRESS {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
let buffer = (writable as *mut u8).sub(SHADOW_HEADER);
|
||||
if *(buffer as *const u64) != SHADOW_MAGIC {
|
||||
return;
|
||||
}
|
||||
let first_page = *(buffer.add(8) as *const u64);
|
||||
let n_pages = *(buffer.add(16) as *const u32);
|
||||
let total = n_pages as usize * gum_query_page_size() as usize;
|
||||
|
||||
commit_kernel_patch(first_page, writable as *const u8, total);
|
||||
libc::__clear_cache(first_page as *const u8, (first_page + total as u64) as *const u8);
|
||||
|
||||
crate::xnu::free(buffer, SHADOW_HEADER + total);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn commit_kernel_patch(address: u64, data: *const u8, len: usize) {
|
||||
unsafe {
|
||||
let element_type = g_variant_type_new(c"y".as_ptr());
|
||||
let bytes = g_variant_new_fixed_array(element_type, data as gconstpointer, len as gsize, 1);
|
||||
g_variant_type_free(element_type);
|
||||
|
||||
let payload = g_variant_new(c"(t@ay)".as_ptr(), address, bytes);
|
||||
let reply = host_rpc(FridaCommand::PatchCode, payload);
|
||||
g_variant_unref(reply);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_try_remap_writable_pages(
|
||||
addrs: *const gpointer,
|
||||
n_addrs: guint,
|
||||
) -> gpointer {
|
||||
if !crate::transport_is_up() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
unsafe {
|
||||
let element_type = g_variant_type_new(c"t".as_ptr());
|
||||
let payload = g_variant_new_fixed_array(
|
||||
element_type,
|
||||
addrs as gconstpointer,
|
||||
n_addrs as gsize,
|
||||
size_of::<u64>() as gsize,
|
||||
);
|
||||
g_variant_type_free(element_type);
|
||||
|
||||
let reply = host_rpc(FridaCommand::RemapWritablePages, payload);
|
||||
let virtual_address = g_variant_get_uint64(reply);
|
||||
g_variant_unref(reply);
|
||||
|
||||
virtual_address as gpointer
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_try_mprotect(
|
||||
address: gpointer,
|
||||
size: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gboolean {
|
||||
if !crate::transport_is_up() {
|
||||
return 1;
|
||||
}
|
||||
unsafe {
|
||||
let payload = g_variant_new(c"(ttu)".as_ptr(), address as u64, size as u64, prot as u32);
|
||||
|
||||
let reply = host_rpc(FridaCommand::MemoryProtect, payload);
|
||||
let success = g_variant_get_boolean(reply);
|
||||
g_variant_unref(reply);
|
||||
|
||||
if success != 0 {
|
||||
flush_tlb_range(address as u64, size as u64);
|
||||
}
|
||||
|
||||
success
|
||||
}
|
||||
}
|
||||
|
||||
// The host rewrites our page-table descriptors through the physical-memory
|
||||
// bridge, which leaves this CPU's TLB holding the stale translation. Stalker
|
||||
// flips a slab page RW then RX in place, so without this the freeze never takes
|
||||
// effect and executing the page faults with a permission abort.
|
||||
unsafe fn flush_tlb_range(address: u64, size: u64) {
|
||||
unsafe {
|
||||
let page_size = gum_query_page_size() as u64;
|
||||
let start = address & !(page_size - 1);
|
||||
let end = (address + size + page_size - 1) & !(page_size - 1);
|
||||
|
||||
core::arch::asm!("dsb ish", options(nostack, preserves_flags));
|
||||
let mut va = start;
|
||||
while va < end {
|
||||
core::arch::asm!("tlbi vaae1is, {operand}", operand = in(reg) va >> 12,
|
||||
options(nostack, preserves_flags));
|
||||
va += page_size;
|
||||
}
|
||||
core::arch::asm!("dsb ish", "isb", options(nostack, preserves_flags));
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_allocate(
|
||||
_address: gpointer,
|
||||
size: gsize,
|
||||
_alignment: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gpointer {
|
||||
let ptr = crate::xnu::kalloc(size as usize);
|
||||
unsafe {
|
||||
core::ptr::write_bytes(ptr, 0, size as usize);
|
||||
if (prot & _GumPageProtection_GUM_PAGE_EXECUTE) != 0 {
|
||||
register_slab(ptr as u64, size as usize);
|
||||
gum_mprotect(ptr as gpointer, size, prot);
|
||||
}
|
||||
}
|
||||
ptr as gpointer
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_free(address: gpointer, size: gsize) -> gboolean {
|
||||
// Executable slabs were flipped to RX in the page tables; restore RW before returning them to
|
||||
// the allocator, otherwise the reclaimed pages stay non-writable and the next consumer faults.
|
||||
if is_agent_slab(address as u64) {
|
||||
unsafe {
|
||||
gum_mprotect(
|
||||
address,
|
||||
size,
|
||||
(_GumPageProtection_GUM_PAGE_READ | _GumPageProtection_GUM_PAGE_WRITE)
|
||||
as GumPageProtection,
|
||||
);
|
||||
}
|
||||
unregister_slab(address as u64);
|
||||
}
|
||||
crate::xnu::free(address as *mut u8, size as usize);
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
@@ -107,6 +324,40 @@ pub extern "C" fn gum_tls_key_set_value(key: GumTlsKey, value: gpointer) {
|
||||
gthread::g_private_set(key as *mut GPrivate, value)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
|
||||
let kernel_base = crate::xnu::get_kernel_base();
|
||||
|
||||
unsafe {
|
||||
let module_infos = core::ptr::addr_of!(crate::MODULE_INFO);
|
||||
let module_infos = &*module_infos;
|
||||
|
||||
let mut i = 0;
|
||||
for module_info in module_infos.iter() {
|
||||
let module_base = kernel_base + module_info.offset as u64;
|
||||
|
||||
let module_path = if i == 0 {
|
||||
"/System/Library/Kernels/kernel"
|
||||
} else {
|
||||
&format!(
|
||||
"/System/Library/Extensions/{}.kext/{}",
|
||||
module_info.name, module_info.name
|
||||
)
|
||||
};
|
||||
let module_range = GumMemoryRange {
|
||||
base_address: module_base,
|
||||
size: module_info.size as u64,
|
||||
};
|
||||
|
||||
let module = gum_native_module_new(&module_path, &module_info.version, &module_range);
|
||||
gum_barebone_register_module(registry, module);
|
||||
g_object_unref(module as gpointer);
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct GumNativeModule {
|
||||
parent: GObject,
|
||||
@@ -137,7 +388,7 @@ fn gum_native_module_get_type() -> GType {
|
||||
class_size: core::mem::size_of::<GObjectClass>() as u16,
|
||||
base_init: None,
|
||||
base_finalize: None,
|
||||
class_init: Some(crate::signed_to_be_called_back(gum_native_module_class_init, 0)),
|
||||
class_init: Some(gum_native_module_class_init),
|
||||
class_finalize: None,
|
||||
class_data: core::ptr::null(),
|
||||
instance_size: core::mem::size_of::<GumNativeModule>() as u16,
|
||||
@@ -149,12 +400,12 @@ fn gum_native_module_get_type() -> GType {
|
||||
let new_type = g_type_register_static(g_object_get_type(), type_name, &type_info, 0);
|
||||
|
||||
let interface_info = _GInterfaceInfo {
|
||||
interface_init: Some(crate::signed_to_be_called_back(gum_native_module_iface_init, 0)),
|
||||
interface_init: Some(gum_native_module_iface_init),
|
||||
interface_finalize: None,
|
||||
interface_data: core::ptr::null_mut(),
|
||||
};
|
||||
|
||||
g_type_add_interface_static(new_type, crate::bindings::gum_module_get_type(), &interface_info);
|
||||
g_type_add_interface_static(new_type, gum_module_get_type(), &interface_info);
|
||||
|
||||
g_once_init_leave(
|
||||
core::ptr::addr_of_mut!(GUM_NATIVE_MODULE_TYPE) as *mut ::core::ffi::c_void,
|
||||
@@ -172,24 +423,18 @@ unsafe extern "C" fn gum_native_module_class_init(klass: gpointer, _class_data:
|
||||
|
||||
GUM_NATIVE_MODULE_PARENT_CLASS = g_type_class_peek_parent(klass) as *mut GObjectClass;
|
||||
|
||||
(*object_class).finalize = Some(crate::signed_to_be_called_back(gum_native_module_finalize, 0));
|
||||
(*object_class).finalize = Some(gum_native_module_finalize);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn gum_native_module_iface_init(g_iface: gpointer, _iface_data: gpointer) {
|
||||
unsafe {
|
||||
let iface = g_iface as *mut GumModuleInterface;
|
||||
(*iface).get_name = Some(crate::signed_to_be_called_back(gum_native_module_get_name, 0));
|
||||
(*iface).get_version = Some(crate::signed_to_be_called_back(gum_native_module_get_version, 0));
|
||||
(*iface).get_path = Some(crate::signed_to_be_called_back(gum_native_module_get_path, 0));
|
||||
(*iface).get_range = Some(crate::signed_to_be_called_back(gum_native_module_get_range, 0));
|
||||
(*iface).enumerate_exports = Some(crate::signed_to_be_called_back(gum_native_module_enumerate_exports, 0));
|
||||
(*iface).find_export_by_name = Some(crate::signed_to_be_called_back(gum_native_module_find_export_by_name, 0));
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
{
|
||||
(*iface).enumerate_symbols =
|
||||
Some(crate::signed_to_be_called_back(gum_native_module_enumerate_symbols, 0));
|
||||
}
|
||||
(*iface).get_name = Some(gum_native_module_get_name);
|
||||
(*iface).get_version = Some(gum_native_module_get_version);
|
||||
(*iface).get_path = Some(gum_native_module_get_path);
|
||||
(*iface).get_range = Some(gum_native_module_get_range);
|
||||
(*iface).enumerate_exports = Some(gum_native_module_enumerate_exports);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,36 +448,22 @@ unsafe extern "C" fn gum_native_module_finalize(object: *mut GObject) {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn gum_native_module_new(
|
||||
path: &str,
|
||||
version: &str,
|
||||
range: &GumMemoryRange,
|
||||
) -> *mut GumModule {
|
||||
fn gum_native_module_new(path: &str, version: &str, range: &GumMemoryRange) -> *mut GumModule {
|
||||
unsafe {
|
||||
let path_cstr = CString::new(path).unwrap();
|
||||
let version_cstr = CString::new(version).unwrap();
|
||||
|
||||
let module =
|
||||
g_object_new(gum_native_module_get_type(), ptr::null()) as *mut GumNativeModule;
|
||||
(*module).path = g_strdup(path_cstr.as_ptr());
|
||||
let name_offset = path.rfind(['/', '\\']).map_or(0, |at| at + 1);
|
||||
(*module).name = (*module).path.add(name_offset);
|
||||
(*module).version = version_copied(version);
|
||||
(*module).name = (*module).path.add(path.rfind('/').unwrap() + 1);
|
||||
(*module).version = g_strdup(version_cstr.as_ptr());
|
||||
(*module).range = *range;
|
||||
|
||||
module as *mut GumModule
|
||||
}
|
||||
}
|
||||
|
||||
fn version_copied(version: &str) -> *mut gchar {
|
||||
if version.is_empty() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
let copy = CString::new(version).unwrap();
|
||||
|
||||
unsafe { g_strdup(copy.as_ptr()) }
|
||||
}
|
||||
|
||||
extern "C" fn gum_native_module_get_name(module: *mut GumModule) -> *const gchar {
|
||||
unsafe {
|
||||
let native_module = module as *mut GumNativeModule;
|
||||
@@ -261,34 +492,6 @@ extern "C" fn gum_native_module_get_range(module: *mut GumModule) -> *const GumM
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn gum_native_module_find_export_by_name(
|
||||
self_: *mut GumModule,
|
||||
symbol_name: *const gchar,
|
||||
) -> GumAddress {
|
||||
unsafe {
|
||||
let module = self_ as *mut GumNativeModule;
|
||||
let range = (*module).range;
|
||||
let wanted = CStr::from_ptr(symbol_name);
|
||||
|
||||
let mut found = 0;
|
||||
let mut on_export = |name: *const gchar, address: u64| {
|
||||
if CStr::from_ptr(name) == wanted {
|
||||
found = address;
|
||||
}
|
||||
|
||||
found == 0
|
||||
};
|
||||
|
||||
enumerate_exports_in_range(
|
||||
range.base_address,
|
||||
range.base_address + range.size as u64,
|
||||
&mut on_export,
|
||||
);
|
||||
|
||||
found
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn gum_native_module_enumerate_exports(
|
||||
self_: *mut GumModule,
|
||||
func: GumFoundExportFunc,
|
||||
@@ -296,286 +499,134 @@ unsafe extern "C" fn gum_native_module_enumerate_exports(
|
||||
) {
|
||||
unsafe {
|
||||
let module = self_ as *mut GumNativeModule;
|
||||
let range = (*module).range;
|
||||
let module_range = &(*module).range;
|
||||
|
||||
let symbol_table = core::ptr::addr_of!(crate::SYMBOL_TABLE);
|
||||
let symbol_table = &*symbol_table;
|
||||
|
||||
const N_EXT: u8 = 0x01; // External symbol flag
|
||||
const N_TYPE: u8 = 0x0e; // Type mask
|
||||
const N_SECT: u8 = 0x0e; // Defined in section
|
||||
|
||||
let start_address = module_range.base_address;
|
||||
let end_address = module_range.base_address + module_range.size;
|
||||
|
||||
for symbol_ref in symbol_table.iter_symbols_in_range(start_address, end_address) {
|
||||
let is_external = (symbol_ref.symbol_type() & N_EXT) != 0;
|
||||
let is_defined = (symbol_ref.symbol_type() & N_TYPE) == N_SECT;
|
||||
if !is_external || !is_defined {
|
||||
continue;
|
||||
}
|
||||
|
||||
let export_type = GumExportType_GUM_EXPORT_FUNCTION;
|
||||
|
||||
let mut on_export = |name: *const gchar, address: u64| {
|
||||
let export_details = GumExportDetails {
|
||||
type_: GumExportType_GUM_EXPORT_FUNCTION,
|
||||
name,
|
||||
address,
|
||||
type_: export_type,
|
||||
name: g_strdup(symbol_ref.name_ptr()),
|
||||
address: symbol_ref.address(),
|
||||
size: -1,
|
||||
};
|
||||
|
||||
func.unwrap()(&export_details as *const GumExportDetails, user_data) != 0
|
||||
};
|
||||
|
||||
enumerate_exports_in_range(
|
||||
range.base_address,
|
||||
range.base_address + range.size as u64,
|
||||
&mut on_export,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
unsafe extern "C" fn gum_native_module_enumerate_symbols(
|
||||
self_: *mut GumModule,
|
||||
func: GumFoundSymbolFunc,
|
||||
user_data: gpointer,
|
||||
) {
|
||||
unsafe {
|
||||
let module = self_ as *mut GumNativeModule;
|
||||
let range = (*module).range;
|
||||
|
||||
let mut on_symbol = |name: *const gchar, address: u64, size: u64, kind: u8, global: bool| {
|
||||
let details = GumSymbolDetails {
|
||||
is_global: global as gboolean,
|
||||
type_: match kind {
|
||||
SYMBOL_IS_FUNCTION => GumSymbolType_GUM_SYMBOL_FUNCTION,
|
||||
SYMBOL_IS_OBJECT => GumSymbolType_GUM_SYMBOL_OBJECT,
|
||||
_ => GumSymbolType_GUM_SYMBOL_UNKNOWN,
|
||||
},
|
||||
section: ptr::null(),
|
||||
name,
|
||||
address,
|
||||
size: size as gssize,
|
||||
};
|
||||
|
||||
func.unwrap()(&details as *const GumSymbolDetails, user_data) != 0
|
||||
};
|
||||
|
||||
enumerate_symbols_in_range(
|
||||
range.base_address,
|
||||
range.base_address + range.size as u64,
|
||||
&mut on_symbol,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// What the backends implement for `gum_native_module_enumerate_symbols`: call
|
||||
/// `callback` for every symbol in `[start, end)`, stopping early when it returns
|
||||
/// false.
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
pub(crate) type FoundSymbolCallback<'a> = dyn FnMut(*const gchar, u64, u64, u8, bool) -> bool + 'a;
|
||||
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
const SYMBOL_IS_OBJECT: u8 = 1;
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
const SYMBOL_IS_FUNCTION: u8 = 2;
|
||||
|
||||
/// What the backends implement for `gum_native_module_enumerate_exports`: call
|
||||
/// `callback` for every exported function in `[start, end)`, stopping early when
|
||||
/// it returns false.
|
||||
pub(crate) type FoundExportCallback<'a> = dyn FnMut(*const gchar, u64) -> bool + 'a;
|
||||
|
||||
// Code slabs the agent allocates are normal writable RAM, so Gum can keep a real writable alias to
|
||||
// them across the slab's lifetime. Pre-existing kernel text is write-protected — CTRR-locked on
|
||||
// Apple silicon, STRICT_KERNEL_RWX on Linux — and can only be modified through a separate path, so
|
||||
// we treat it differently. We distinguish the two by tracking the executable ranges we hand out.
|
||||
static SLAB_LOCK: AtomicU32 = AtomicU32::new(0);
|
||||
static mut SLABS: Vec<(u64, u64)> = Vec::new();
|
||||
|
||||
fn slab_lock() {
|
||||
while SLAB_LOCK
|
||||
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
|
||||
.is_err()
|
||||
{
|
||||
kernel::yield_now();
|
||||
}
|
||||
}
|
||||
|
||||
fn slab_unlock() {
|
||||
SLAB_LOCK.store(0, Ordering::Release);
|
||||
}
|
||||
|
||||
pub(crate) fn register_slab(start: u64, size: usize) {
|
||||
slab_lock();
|
||||
unsafe {
|
||||
(*core::ptr::addr_of_mut!(SLABS)).push((start, start + size as u64));
|
||||
}
|
||||
slab_unlock();
|
||||
}
|
||||
|
||||
pub(crate) fn unregister_slab(start: u64) {
|
||||
slab_lock();
|
||||
unsafe {
|
||||
(*core::ptr::addr_of_mut!(SLABS)).retain(|&(begin, _)| begin != start);
|
||||
}
|
||||
slab_unlock();
|
||||
}
|
||||
|
||||
pub(crate) fn is_agent_slab(address: u64) -> bool {
|
||||
slab_lock();
|
||||
let found = unsafe {
|
||||
(*core::ptr::addr_of!(SLABS))
|
||||
.iter()
|
||||
.any(|&(begin, end)| address >= begin && address < end)
|
||||
};
|
||||
slab_unlock();
|
||||
found
|
||||
}
|
||||
|
||||
// The host gives the symbol table, thus this makes a name from an address.
|
||||
#[cfg(feature = "blob")]
|
||||
mod symbolication {
|
||||
use super::*;
|
||||
use crate::bindings::{
|
||||
GArray, GumAddress, GumDebugSymbolDetails, g_array_append_vals, g_array_new, gboolean,
|
||||
gconstpointer,
|
||||
};
|
||||
use core::ffi::CStr;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_symbol_name_from_address(address: gpointer) -> *mut gchar {
|
||||
match closest_to(address) {
|
||||
Some(symbol) => unsafe { g_strdup(symbol.name_ptr()) },
|
||||
None => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_symbol_details_from_address(
|
||||
address: gpointer,
|
||||
details: *mut GumDebugSymbolDetails,
|
||||
) -> gboolean {
|
||||
let Some(symbol) = closest_to(address) else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
let details = unsafe { &mut *details };
|
||||
*details = unsafe { core::mem::zeroed() };
|
||||
details.address = address as u64;
|
||||
put_text(symbol.name(), &mut details.symbol_name);
|
||||
if let Some(module) = module_containing(address as u64) {
|
||||
put_text(&module.name, &mut details.module_name);
|
||||
}
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_module_find_global_export_by_name(name: *const gchar) -> GumAddress {
|
||||
let Some(name) = text_of(name) else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
if crate::running_in_a_process() {
|
||||
return export_of_a_process_module(name);
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-core")]
|
||||
if crate::xnu::in_copy() {
|
||||
return crate::xnu_mapped::export_named(name);
|
||||
}
|
||||
|
||||
let table = unsafe { &*ptr::addr_of!(crate::SYMBOL_TABLE) };
|
||||
|
||||
match table.find_symbol_by_name(name) {
|
||||
Some(symbol) => symbol.address(),
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
// A copy runs in a process, thus a name without a module means one of the modules of that
|
||||
// process.
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
fn export_of_a_process_module(wanted: &str) -> GumAddress {
|
||||
let mut found = 0;
|
||||
|
||||
for module in crate::kernel::enumerate_modules() {
|
||||
crate::kernel::enumerate_exports(module.base as _, &mut |name, address| {
|
||||
if text_of(name as *const gchar) == Some(wanted) {
|
||||
found = address;
|
||||
}
|
||||
found == 0
|
||||
});
|
||||
if found != 0 {
|
||||
let should_continue =
|
||||
func.unwrap()(&export_details as *const GumExportDetails, user_data);
|
||||
if should_continue == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
found
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_function(name: *const gchar) -> gpointer {
|
||||
let Some(name) = text_of(name) else {
|
||||
return ptr::null_mut();
|
||||
};
|
||||
let table = unsafe { &*ptr::addr_of!(crate::SYMBOL_TABLE) };
|
||||
|
||||
match table.find_symbol_by_name(name) {
|
||||
Some(symbol) => symbol.address() as gpointer,
|
||||
None => ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_functions_named(name: *const gchar) -> *mut GArray {
|
||||
let table = unsafe { &*ptr::addr_of!(crate::SYMBOL_TABLE) };
|
||||
let found = text_of(name)
|
||||
.map(|name| table.find_symbols_by_name(name).map(|s| s.address()).collect())
|
||||
.unwrap_or_else(Vec::new);
|
||||
|
||||
pointer_array_of(&found)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_functions_matching(pattern: *const gchar) -> *mut GArray {
|
||||
let table = unsafe { &*ptr::addr_of!(crate::SYMBOL_TABLE) };
|
||||
let found = text_of(pattern)
|
||||
.map(|pattern| {
|
||||
table
|
||||
.find_symbols_matching_glob(pattern)
|
||||
.map(|s| s.address())
|
||||
.collect()
|
||||
})
|
||||
.unwrap_or_else(Vec::new);
|
||||
|
||||
pointer_array_of(&found)
|
||||
}
|
||||
|
||||
fn closest_to(address: gpointer) -> Option<crate::symbols::SymbolRef<'static>> {
|
||||
let table = unsafe { &*ptr::addr_of!(crate::SYMBOL_TABLE) };
|
||||
table.find_closest_symbol_by_address(address as u64)
|
||||
}
|
||||
|
||||
fn module_containing(address: u64) -> Option<&'static crate::ModuleInfo> {
|
||||
let modules = unsafe { &*ptr::addr_of!(crate::MODULE_INFO) };
|
||||
|
||||
modules.iter().find(|m| {
|
||||
address >= m.offset as u64 && address < m.offset as u64 + m.size as u64
|
||||
})
|
||||
}
|
||||
|
||||
fn pointer_array_of(addresses: &[u64]) -> *mut GArray {
|
||||
let pointers: Vec<gpointer> = addresses.iter().map(|a| *a as gpointer).collect();
|
||||
|
||||
unsafe {
|
||||
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
|
||||
g_array_append_vals(
|
||||
array,
|
||||
pointers.as_ptr() as gconstpointer,
|
||||
pointers.len() as guint,
|
||||
);
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
fn text_of(value: *const gchar) -> Option<&'static str> {
|
||||
if value.is_null() {
|
||||
return None;
|
||||
}
|
||||
unsafe { CStr::from_ptr(value) }.to_str().ok()
|
||||
}
|
||||
|
||||
fn put_text(text: &str, out: &mut [gchar]) {
|
||||
let limit = out.len() - 1;
|
||||
for (i, byte) in text.bytes().take(limit).enumerate() {
|
||||
out[i] = byte as gchar;
|
||||
}
|
||||
out[text.len().min(limit)] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_symbol_details_from_address(
|
||||
address: gpointer,
|
||||
details: *mut GumDebugSymbolDetails,
|
||||
) -> gboolean {
|
||||
unsafe {
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
if let Some(symbol) = table.find_symbol_by_address(address as u64) {
|
||||
let name_bytes = symbol.name().as_bytes();
|
||||
let copy_len = core::cmp::min(name_bytes.len(), 2048);
|
||||
core::ptr::copy_nonoverlapping(
|
||||
name_bytes.as_ptr(),
|
||||
(*details).symbol_name.as_mut_ptr() as *mut u8,
|
||||
copy_len,
|
||||
);
|
||||
(*details).symbol_name[copy_len] = 0;
|
||||
|
||||
(*details).address = symbol.address();
|
||||
|
||||
(*details).module_name[0] = 0;
|
||||
(*details).file_name[0] = 0;
|
||||
(*details).line_number = 0;
|
||||
(*details).column = 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_symbol_name_from_address(address: gpointer) -> *mut gchar {
|
||||
unsafe {
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
let name_ptr = table.find_symbol_name_ptr_by_address(address as u64);
|
||||
g_strdup(name_ptr as *const gchar)
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_function(name: *const gchar) -> gpointer {
|
||||
unsafe {
|
||||
let target_name = CStr::from_ptr(name).to_string_lossy();
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
|
||||
if let Some(symbol) = table.find_symbol_by_name(&target_name) {
|
||||
return symbol.address() as gpointer;
|
||||
}
|
||||
|
||||
core::ptr::null_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_functions_named(name: *const gchar) -> *mut GArray {
|
||||
unsafe {
|
||||
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
|
||||
|
||||
let target_name = CStr::from_ptr(name).to_string_lossy();
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
|
||||
let symbols = table.find_symbols_by_name(&target_name);
|
||||
for symbol in symbols {
|
||||
let addr = symbol.address() as gpointer;
|
||||
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
|
||||
}
|
||||
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_functions_matching(pattern: *const gchar) -> *mut GArray {
|
||||
unsafe {
|
||||
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
|
||||
|
||||
let glob_pattern = CStr::from_ptr(pattern).to_string_lossy();
|
||||
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
|
||||
|
||||
let symbols = table.find_symbols_matching_glob(&glob_pattern);
|
||||
for symbol in symbols {
|
||||
let addr = symbol.address() as gpointer;
|
||||
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
|
||||
}
|
||||
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
|
||||
0
|
||||
}
|
||||
@@ -1,393 +0,0 @@
|
||||
use alloc::ffi::CString;
|
||||
use alloc::string::String;
|
||||
use core::ffi::{CStr, c_char};
|
||||
|
||||
use crate::bindings::{
|
||||
GError, GumScriptApiValue, _GumScriptApiType_GUM_SCRIPT_API_BOOLEAN,
|
||||
_GumScriptApiType_GUM_SCRIPT_API_STRING, _GumScriptApiType_GUM_SCRIPT_API_UINT, gboolean,
|
||||
gpointer, gum_script_api_add_function, gum_script_api_new, gum_script_api_registry_add,
|
||||
gum_script_api_registry_obtain, gum_script_api_set_prelude, gum_script_api_unref,
|
||||
};
|
||||
use crate::linux::layout::{
|
||||
enumerate_constants_in, enumerate_fields_in, enumerate_parameters_in, field_offset_in,
|
||||
find_constant, name_of_type, resolve_struct, size_of_struct, size_of_type, types_are_described,
|
||||
};
|
||||
|
||||
pub fn publish() {
|
||||
unsafe {
|
||||
let api = gum_script_api_new(c"Btf".as_ptr());
|
||||
|
||||
gum_script_api_add_function(
|
||||
api,
|
||||
c"_findStruct".as_ptr(),
|
||||
c"s".as_ptr(),
|
||||
_GumScriptApiType_GUM_SCRIPT_API_UINT,
|
||||
Some(crate::signed_to_be_called_back(on_find_struct, 0)),
|
||||
core::ptr::null_mut(),
|
||||
);
|
||||
gum_script_api_add_function(
|
||||
api,
|
||||
c"_sizeOf".as_ptr(),
|
||||
c"u".as_ptr(),
|
||||
_GumScriptApiType_GUM_SCRIPT_API_UINT,
|
||||
Some(crate::signed_to_be_called_back(on_size_of, 0)),
|
||||
core::ptr::null_mut(),
|
||||
);
|
||||
gum_script_api_add_function(
|
||||
api,
|
||||
c"_findOffsetOf".as_ptr(),
|
||||
c"us".as_ptr(),
|
||||
_GumScriptApiType_GUM_SCRIPT_API_UINT,
|
||||
Some(crate::signed_to_be_called_back(on_find_offset, 0)),
|
||||
core::ptr::null_mut(),
|
||||
);
|
||||
gum_script_api_add_function(
|
||||
api,
|
||||
c"_fieldsOf".as_ptr(),
|
||||
c"u".as_ptr(),
|
||||
_GumScriptApiType_GUM_SCRIPT_API_STRING,
|
||||
Some(crate::signed_to_be_called_back(on_fields_of, 0)),
|
||||
core::ptr::null_mut(),
|
||||
);
|
||||
|
||||
gum_script_api_add_function(
|
||||
api,
|
||||
c"_findConstant".as_ptr(),
|
||||
c"s".as_ptr(),
|
||||
_GumScriptApiType_GUM_SCRIPT_API_STRING,
|
||||
Some(crate::signed_to_be_called_back(on_find_constant, 0)),
|
||||
core::ptr::null_mut(),
|
||||
);
|
||||
gum_script_api_add_function(
|
||||
api,
|
||||
c"_constantsOf".as_ptr(),
|
||||
c"s".as_ptr(),
|
||||
_GumScriptApiType_GUM_SCRIPT_API_STRING,
|
||||
Some(crate::signed_to_be_called_back(on_constants_of, 0)),
|
||||
core::ptr::null_mut(),
|
||||
);
|
||||
gum_script_api_add_function(
|
||||
api,
|
||||
c"_signatureOf".as_ptr(),
|
||||
c"s".as_ptr(),
|
||||
_GumScriptApiType_GUM_SCRIPT_API_STRING,
|
||||
Some(crate::signed_to_be_called_back(on_signature_of, 0)),
|
||||
core::ptr::null_mut(),
|
||||
);
|
||||
gum_script_api_add_function(
|
||||
api,
|
||||
c"_isAvailable".as_ptr(),
|
||||
c"".as_ptr(),
|
||||
_GumScriptApiType_GUM_SCRIPT_API_BOOLEAN,
|
||||
Some(crate::signed_to_be_called_back(on_is_available, 0)),
|
||||
core::ptr::null_mut(),
|
||||
);
|
||||
|
||||
gum_script_api_set_prelude(api, PRELUDE.as_ptr());
|
||||
|
||||
gum_script_api_registry_add(gum_script_api_registry_obtain(), api);
|
||||
gum_script_api_unref(api);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_find_struct(
|
||||
args: *const GumScriptApiValue,
|
||||
retval: *mut GumScriptApiValue,
|
||||
_user_data: gpointer,
|
||||
_error: *mut *mut GError,
|
||||
) -> gboolean {
|
||||
let container = unsafe { text_of(args, 0) };
|
||||
|
||||
let handle = resolve_struct(container).map_or(NOTHING, |handle| (handle + 1) as u32);
|
||||
|
||||
unsafe { (*retval).__bindgen_anon_1.u = handle };
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_size_of(
|
||||
args: *const GumScriptApiValue,
|
||||
retval: *mut GumScriptApiValue,
|
||||
_user_data: gpointer,
|
||||
_error: *mut *mut GError,
|
||||
) -> gboolean {
|
||||
let handle = unsafe { handle_of(args, 0) };
|
||||
|
||||
unsafe { (*retval).__bindgen_anon_1.u = size_of_struct(handle) as u32 };
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_find_offset(
|
||||
args: *const GumScriptApiValue,
|
||||
retval: *mut GumScriptApiValue,
|
||||
_user_data: gpointer,
|
||||
_error: *mut *mut GError,
|
||||
) -> gboolean {
|
||||
let handle = unsafe { handle_of(args, 0) };
|
||||
let field = unsafe { text_of(args, 1) };
|
||||
|
||||
let offset = field_offset_in(handle, field).map_or(NOWHERE, |offset| offset as u32);
|
||||
|
||||
unsafe { (*retval).__bindgen_anon_1.u = offset };
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_is_available(
|
||||
_args: *const GumScriptApiValue,
|
||||
retval: *mut GumScriptApiValue,
|
||||
_user_data: gpointer,
|
||||
_error: *mut *mut GError,
|
||||
) -> gboolean {
|
||||
unsafe { (*retval).__bindgen_anon_1.b = types_are_described() as gboolean };
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_fields_of(
|
||||
args: *const GumScriptApiValue,
|
||||
retval: *mut GumScriptApiValue,
|
||||
_user_data: gpointer,
|
||||
_error: *mut *mut GError,
|
||||
) -> gboolean {
|
||||
let handle = unsafe { handle_of(args, 0) };
|
||||
|
||||
let mut described = String::from("{");
|
||||
let mut separator = "";
|
||||
enumerate_fields_in(handle, &mut |name, field| {
|
||||
described.push_str(&alloc::format!(
|
||||
"{separator}\"{name}\":{{\"offset\":{},\"size\":{},\"type\":\"{}\"",
|
||||
field.offset,
|
||||
size_of_type(field.id),
|
||||
name_of_type(field.id)
|
||||
));
|
||||
if field.bit_size != 0 {
|
||||
described.push_str(&alloc::format!(
|
||||
",\"bitOffset\":{},\"bitSize\":{}",
|
||||
field.bit_offset, field.bit_size
|
||||
));
|
||||
}
|
||||
described.push('}');
|
||||
separator = ",";
|
||||
true
|
||||
});
|
||||
described.push('}');
|
||||
|
||||
unsafe { answer_with(retval, described) };
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_find_constant(
|
||||
args: *const GumScriptApiValue,
|
||||
retval: *mut GumScriptApiValue,
|
||||
_user_data: gpointer,
|
||||
_error: *mut *mut GError,
|
||||
) -> gboolean {
|
||||
let name = unsafe { text_of(args, 0) };
|
||||
|
||||
let described = find_constant(name).map_or_else(String::new, |value| alloc::format!("{value}"));
|
||||
|
||||
unsafe { answer_with(retval, described) };
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_constants_of(
|
||||
args: *const GumScriptApiValue,
|
||||
retval: *mut GumScriptApiValue,
|
||||
_user_data: gpointer,
|
||||
_error: *mut *mut GError,
|
||||
) -> gboolean {
|
||||
let container = unsafe { text_of(args, 0) };
|
||||
|
||||
let mut described = String::from("{");
|
||||
let mut separator = "";
|
||||
let found = enumerate_constants_in(container, &mut |name, value| {
|
||||
described.push_str(&alloc::format!("{separator}\"{name}\":{value}"));
|
||||
separator = ",";
|
||||
});
|
||||
described.push('}');
|
||||
|
||||
unsafe { answer_with(retval, if found { described } else { String::new() }) };
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_signature_of(
|
||||
args: *const GumScriptApiValue,
|
||||
retval: *mut GumScriptApiValue,
|
||||
_user_data: gpointer,
|
||||
_error: *mut *mut GError,
|
||||
) -> gboolean {
|
||||
let name = unsafe { text_of(args, 0) };
|
||||
|
||||
let mut parameters = String::from("[");
|
||||
let mut separator = "";
|
||||
let returned = enumerate_parameters_in(name, &mut |name, kind| {
|
||||
parameters.push_str(&alloc::format!(
|
||||
"{separator}{{\"name\":\"{name}\",\"type\":\"{kind}\"}}"
|
||||
));
|
||||
separator = ",";
|
||||
});
|
||||
parameters.push(']');
|
||||
|
||||
let described = match returned {
|
||||
Some(returned) => {
|
||||
alloc::format!("{{\"returns\":\"{returned}\",\"parameters\":{parameters}}}")
|
||||
}
|
||||
None => String::new(),
|
||||
};
|
||||
|
||||
unsafe { answer_with(retval, described) };
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
unsafe fn answer_with(retval: *mut GumScriptApiValue, described: String) {
|
||||
unsafe {
|
||||
let holder = (&raw mut LAST_ANSWER).as_mut().unwrap();
|
||||
*holder = Some(CString::new(described).unwrap());
|
||||
(*retval).__bindgen_anon_1.s = holder.as_ref().unwrap().as_ptr();
|
||||
}
|
||||
}
|
||||
|
||||
static mut LAST_ANSWER: Option<CString> = None;
|
||||
|
||||
const NOTHING: u32 = 0;
|
||||
const NOWHERE: u32 = u32::MAX;
|
||||
|
||||
unsafe fn handle_of(args: *const GumScriptApiValue, index: usize) -> usize {
|
||||
unsafe { (*args.add(index)).__bindgen_anon_1.u as usize - 1 }
|
||||
}
|
||||
|
||||
unsafe fn text_of(args: *const GumScriptApiValue, index: usize) -> &'static str {
|
||||
unsafe {
|
||||
CStr::from_ptr((*args.add(index)).__bindgen_anon_1.s as *const c_char)
|
||||
.to_str()
|
||||
.unwrap_or("")
|
||||
}
|
||||
}
|
||||
|
||||
const PRELUDE: &CStr = c"
|
||||
(() => {
|
||||
const { _findStruct, _sizeOf, _findOffsetOf, _fieldsOf, _findConstant, _constantsOf,
|
||||
_signatureOf, _isAvailable } = Btf;
|
||||
|
||||
const NOWHERE = 0xffffffff;
|
||||
|
||||
class BtfStruct {
|
||||
constructor(name, handle) {
|
||||
this.name = name;
|
||||
this.handle = handle;
|
||||
this.size = _sizeOf(handle);
|
||||
this.known = new Map();
|
||||
}
|
||||
|
||||
getOffsetOf(field) {
|
||||
const offset = this.findOffsetOf(field);
|
||||
if (offset === null)
|
||||
throw new Error(`${this.name}: unable to find field '${field}'`);
|
||||
return offset;
|
||||
}
|
||||
|
||||
findOffsetOf(field) {
|
||||
let offset = this.known.get(field);
|
||||
if (offset === undefined) {
|
||||
offset = _findOffsetOf(this.handle, field);
|
||||
if (offset === NOWHERE)
|
||||
return null;
|
||||
this.known.set(field, offset);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
get fields() {
|
||||
const fields = JSON.parse(_fieldsOf(this.handle));
|
||||
Object.defineProperty(this, 'fields', { value: fields });
|
||||
return fields;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `BtfStruct(${this.name}, size: ${this.size})`;
|
||||
}
|
||||
}
|
||||
|
||||
const resolved = new Map();
|
||||
const functions = new Map();
|
||||
|
||||
Btf.available = _isAvailable();
|
||||
|
||||
Btf.getStruct = name => {
|
||||
const described = Btf.findStruct(name);
|
||||
if (described === null)
|
||||
throw new Error(`unable to find struct '${name}'`);
|
||||
return described;
|
||||
};
|
||||
|
||||
Btf.findStruct = name => {
|
||||
let described = resolved.get(name);
|
||||
if (described === undefined) {
|
||||
const handle = _findStruct(name);
|
||||
if (handle === 0)
|
||||
return null;
|
||||
described = new BtfStruct(name, handle);
|
||||
resolved.set(name, described);
|
||||
}
|
||||
return described;
|
||||
};
|
||||
|
||||
Btf.getConstant = name => {
|
||||
const value = Btf.findConstant(name);
|
||||
if (value === null)
|
||||
throw new Error(`unable to find constant '${name}'`);
|
||||
return value;
|
||||
};
|
||||
|
||||
Btf.findConstant = name => {
|
||||
const value = _findConstant(name);
|
||||
if (value === '')
|
||||
return null;
|
||||
return numberFrom(value);
|
||||
};
|
||||
|
||||
Btf.getEnum = name => {
|
||||
const constants = Btf.findEnum(name);
|
||||
if (constants === null)
|
||||
throw new Error(`unable to find enum '${name}'`);
|
||||
return constants;
|
||||
};
|
||||
|
||||
Btf.findEnum = name => {
|
||||
const constants = _constantsOf(name);
|
||||
if (constants === '')
|
||||
return null;
|
||||
return JSON.parse(constants);
|
||||
};
|
||||
|
||||
Btf.getFunction = name => {
|
||||
const signature = Btf.findFunction(name);
|
||||
if (signature === null)
|
||||
throw new Error(`unable to find function '${name}'`);
|
||||
return signature;
|
||||
};
|
||||
|
||||
Btf.findFunction = name => {
|
||||
let signature = functions.get(name);
|
||||
if (signature === undefined) {
|
||||
const described = _signatureOf(name);
|
||||
if (described === '')
|
||||
return null;
|
||||
signature = JSON.parse(described);
|
||||
functions.set(name, signature);
|
||||
}
|
||||
return signature;
|
||||
};
|
||||
|
||||
function numberFrom(text) {
|
||||
const value = parseInt(text);
|
||||
return Number.isSafeInteger(value) ? value : int64(text);
|
||||
}
|
||||
})();
|
||||
";
|
||||
@@ -1,939 +0,0 @@
|
||||
// Gum's platform backend for an agent the host injected into a kernel that
|
||||
// keeps its page tables and its executable regions to itself. Memory
|
||||
// permissions and kernel-text writes go out over the hostlink, because the
|
||||
// guest cannot perform them itself: the host owns the page tables and the
|
||||
// physical-memory bridge.
|
||||
|
||||
use crate::{
|
||||
FridaCommand,
|
||||
bindings::{
|
||||
_GumPageProtection_GUM_PAGE_EXECUTE, _GumPageProtection_GUM_PAGE_READ,
|
||||
_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::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::mem::size_of;
|
||||
use core::ptr;
|
||||
|
||||
// Where the guest's kernel keeps itself and its modules, which is what Gum
|
||||
// reports as the path of each one.
|
||||
#[cfg(feature = "xnu-core")]
|
||||
const KERNEL_PATH: &str = "/System/Library/Kernels/kernel";
|
||||
#[cfg(feature = "xnu-core")]
|
||||
const MODULE_DIRECTORY: &str = "/System/Library/Extensions/";
|
||||
#[cfg(feature = "xnu-core")]
|
||||
const MODULE_SUFFIX: &str = ".kext";
|
||||
|
||||
const SHADOW_HEADER: usize = 24;
|
||||
|
||||
#[cfg(feature = "xnu-core")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_query_platform() -> *const crate::bindings::gchar {
|
||||
c"darwin".as_ptr() as *const crate::bindings::gchar
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_query_platform() -> *const crate::bindings::gchar {
|
||||
c"linux".as_ptr() as *const crate::bindings::gchar
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_query_stack_size() -> crate::bindings::gsize {
|
||||
if kernel::in_copy() {
|
||||
return if crate::on_js_thread() { crate::linux::STACK_SIZE as crate::bindings::gsize } else { 0 };
|
||||
}
|
||||
|
||||
crate::linux::stack_headroom() as crate::bindings::gsize
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-core")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_query_stack_size() -> crate::bindings::gsize {
|
||||
if crate::on_js_thread() {
|
||||
crate::xnu_injection::STACK as crate::bindings::gsize
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_query_rwx_support() -> GumRwxSupport {
|
||||
#[cfg(feature = "linux-injected")]
|
||||
return _GumRwxSupport_GUM_RWX_NONE;
|
||||
|
||||
#[cfg(not(feature = "linux-injected"))]
|
||||
_GumRwxSupport_GUM_RWX_NONE
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_can_remap_writable() -> gboolean {
|
||||
#[cfg(feature = "linux-injected")]
|
||||
if kernel::in_copy() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-core")]
|
||||
if crate::xnu::in_copy() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_try_remap_writable_pages(
|
||||
first_page: gpointer,
|
||||
n_pages: guint,
|
||||
) -> gpointer {
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
if crate::xnu::the_writable_view_of(first_page as u64) != 0 {
|
||||
return remap_agent_pages(first_page, n_pages);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "linux-injected", not(target_arch = "arm")))]
|
||||
if writable_in_place(first_page, n_pages) {
|
||||
return first_page;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "xnu-kext"))]
|
||||
if gum::is_agent_slab(first_page as u64) {
|
||||
return remap_agent_pages(first_page, n_pages);
|
||||
}
|
||||
|
||||
shadow_kernel_pages(first_page, n_pages)
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
fn remap_agent_pages(first_page: gpointer, _n_pages: guint) -> gpointer {
|
||||
crate::xnu::the_writable_view_of(first_page as u64) as gpointer
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "linux-injected", not(target_arch = "arm")))]
|
||||
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, GUM_PAGE_RWX)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "xnu-kext"))]
|
||||
fn remap_agent_pages(first_page: gpointer, n_pages: guint) -> gpointer {
|
||||
let alias = remap_agent_pages_through_host(first_page, n_pages);
|
||||
if !alias.is_null() {
|
||||
aliases().insert(alias as u64, first_page as u64);
|
||||
}
|
||||
|
||||
alias
|
||||
}
|
||||
|
||||
fn aliases() -> &'static mut BTreeMap<u64, u64> {
|
||||
unsafe { core::ptr::addr_of_mut!(ALIASES).as_mut().unwrap() }
|
||||
}
|
||||
|
||||
static mut ALIASES: BTreeMap<u64, u64> = BTreeMap::new();
|
||||
|
||||
#[cfg(not(feature = "xnu-kext"))]
|
||||
fn remap_agent_pages_through_host(first_page: gpointer, n_pages: guint) -> gpointer {
|
||||
unsafe {
|
||||
let page_size = gum_query_page_size() as usize;
|
||||
let mut virtual_addrs = Vec::with_capacity(n_pages as usize);
|
||||
|
||||
let mut current_page = first_page as u64;
|
||||
for _ in 0..n_pages {
|
||||
virtual_addrs.push(current_page as gpointer);
|
||||
current_page += page_size as u64;
|
||||
}
|
||||
|
||||
_gum_barebone_try_remap_writable_pages(
|
||||
virtual_addrs.as_ptr() as *mut *const core::ffi::c_void,
|
||||
virtual_addrs.len() as guint,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "linux-injected", not(target_arch = "arm")))]
|
||||
const GUM_PAGE_RWX: u32 = 7;
|
||||
|
||||
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) };
|
||||
let ours = unsafe { core::slice::from_raw_parts(shadow, total) };
|
||||
|
||||
let first = live.iter().zip(ours).position(|(a, b)| a != b)?;
|
||||
let last = total - live.iter().zip(ours).rev().position(|(a, b)| a != b).unwrap();
|
||||
|
||||
let start = first & !(WORD_SIZE - 1);
|
||||
let end = (last + WORD_SIZE - 1) & !(WORD_SIZE - 1);
|
||||
|
||||
Some((start, end - start))
|
||||
}
|
||||
|
||||
const WORD_SIZE: usize = 4;
|
||||
|
||||
fn shadow_kernel_pages(first_page: gpointer, n_pages: guint) -> gpointer {
|
||||
unsafe {
|
||||
let total = n_pages as usize * gum_query_page_size() as usize;
|
||||
let buffer = kernel::alloc(SHADOW_HEADER + total);
|
||||
*(buffer.add(8) as *mut u64) = first_page as u64;
|
||||
*(buffer.add(16) as *mut u32) = n_pages;
|
||||
|
||||
let body = buffer.add(SHADOW_HEADER);
|
||||
core::ptr::copy_nonoverlapping(first_page as *const u8, body, total);
|
||||
|
||||
shadows().insert(body as u64);
|
||||
|
||||
body as gpointer
|
||||
}
|
||||
}
|
||||
|
||||
fn shadows() -> &'static mut BTreeSet<u64> {
|
||||
unsafe { core::ptr::addr_of_mut!(SHADOWS).as_mut().unwrap() }
|
||||
}
|
||||
|
||||
static mut SHADOWS: BTreeSet<u64> = BTreeSet::new();
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages: guint) {
|
||||
|
||||
#[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;
|
||||
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;
|
||||
libc::__clear_cache(executable as *const u8, (executable + size as u64) as *const u8);
|
||||
return;
|
||||
}
|
||||
|
||||
if !shadows().remove(&(writable as u64)) {
|
||||
#[cfg(all(feature = "linux-injected", not(target_arch = "arm")))]
|
||||
{
|
||||
let size = _n_pages as usize * unsafe { gum_query_page_size() } as usize;
|
||||
libc::__clear_cache(writable as *const u8,
|
||||
(writable as u64 + size as u64) as *const u8);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
let buffer = (writable as *mut u8).sub(SHADOW_HEADER);
|
||||
let first_page = *(buffer.add(8) as *const u64);
|
||||
let n_pages = *(buffer.add(16) as *const u32);
|
||||
let total = n_pages as usize * gum_query_page_size() as usize;
|
||||
|
||||
if let Some((offset, len)) = what_changed(first_page, writable as *const u8, total) {
|
||||
commit_kernel_patch(first_page + offset as u64, (writable as *const u8).add(offset),
|
||||
len);
|
||||
libc::__clear_cache((first_page + offset as u64) as *const u8,
|
||||
(first_page + (offset + len) as u64) as *const u8);
|
||||
}
|
||||
|
||||
kernel::free(buffer, SHADOW_HEADER + total);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
unsafe fn commit_kernel_patch(address: u64, data: *const u8, len: usize) {
|
||||
crate::xnu::write_through_a_writable_alias(address, data, len);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "linux-injected", not(target_arch = "arm")))]
|
||||
unsafe fn commit_kernel_patch(address: u64, data: *const u8, len: usize) {
|
||||
if kernel::patch_text(address, data, len) {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe { ask_the_host_to_patch(address, data, len) };
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "xnu-kext"), any(not(feature = "linux-injected"), target_arch = "arm")))]
|
||||
unsafe fn commit_kernel_patch(address: u64, data: *const u8, len: usize) {
|
||||
unsafe { ask_the_host_to_patch(address, data, len) };
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "xnu-kext"))]
|
||||
unsafe fn ask_the_host_to_patch(address: u64, data: *const u8, len: usize) {
|
||||
unsafe {
|
||||
let element_type = g_variant_type_new(c"y".as_ptr());
|
||||
let bytes = g_variant_new_fixed_array(element_type, data as gconstpointer, len as gsize, 1);
|
||||
g_variant_type_free(element_type);
|
||||
|
||||
let payload = g_variant_new(c"(t@ay)".as_ptr(), address, bytes);
|
||||
let reply = host_rpc(FridaCommand::PatchCode, payload);
|
||||
g_variant_unref(reply);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_try_remap_writable_pages(
|
||||
addrs: *const gpointer,
|
||||
n_addrs: guint,
|
||||
) -> gpointer {
|
||||
if !crate::transport_is_up() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
unsafe {
|
||||
let mut wide = Vec::with_capacity(n_addrs as usize);
|
||||
for i in 0..n_addrs as usize {
|
||||
wide.push(*addrs.add(i) as u64);
|
||||
}
|
||||
|
||||
let element_type = g_variant_type_new(c"t".as_ptr());
|
||||
let payload = g_variant_new_fixed_array(
|
||||
element_type,
|
||||
wide.as_ptr() as gconstpointer,
|
||||
n_addrs as gsize,
|
||||
size_of::<u64>() as gsize,
|
||||
);
|
||||
g_variant_type_free(element_type);
|
||||
|
||||
let reply = host_rpc(FridaCommand::RemapWritablePages, payload);
|
||||
let virtual_address = g_variant_get_uint64(reply);
|
||||
g_variant_unref(reply);
|
||||
|
||||
virtual_address as gpointer
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_try_mprotect(
|
||||
address: gpointer,
|
||||
size: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gboolean {
|
||||
protect_here(address as u64, size as usize, prot as u32) as gboolean
|
||||
}
|
||||
|
||||
// Where the agent has a half that runs in a process of the target, the protection is that
|
||||
// half's own business: it has an address space it may change itself.
|
||||
#[cfg(feature = "linux-injected")]
|
||||
fn protect_here(address: u64, size: usize, prot: u32) -> bool {
|
||||
kernel::protect(address, size, prot)
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
fn protect_here(address: u64, size: usize, prot: u32) -> bool {
|
||||
if crate::xnu::in_copy() {
|
||||
return kernel::protect(address, size, prot);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "xnu-core", not(feature = "xnu-kext")))]
|
||||
fn protect_here(address: u64, size: usize, prot: u32) -> bool {
|
||||
if crate::xnu::in_copy() {
|
||||
return kernel::protect(address, size, prot);
|
||||
}
|
||||
|
||||
ask_the_host_to_protect(address, size, prot)
|
||||
}
|
||||
|
||||
#[cfg(not(any(feature = "linux-injected", feature = "xnu-core")))]
|
||||
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;
|
||||
}
|
||||
|
||||
let granted = the_host_grants_it(address, size, prot);
|
||||
|
||||
#[cfg(feature = "xnu-core")]
|
||||
if granted {
|
||||
crate::kernel::make_the_machine_agree();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
let reply = host_rpc(FridaCommand::MemoryProtect, payload);
|
||||
let success = g_variant_get_boolean(reply) != 0;
|
||||
g_variant_unref(reply);
|
||||
|
||||
if success {
|
||||
flush_tlb_range(address, size as u64);
|
||||
}
|
||||
|
||||
success
|
||||
}
|
||||
}
|
||||
|
||||
// The host rewrites our page-table descriptors through the physical-memory
|
||||
// bridge, which leaves this CPU's TLB holding the stale translation. Stalker
|
||||
// flips a slab page RW then RX in place, so without this the freeze never takes
|
||||
// effect and executing the page faults with a permission abort.
|
||||
#[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);
|
||||
let end = (address + size + page_size - 1) & !(page_size - 1);
|
||||
|
||||
unsafe { flush_pages(start, end, page_size) };
|
||||
}
|
||||
|
||||
#[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));
|
||||
let mut va = start;
|
||||
while va < end {
|
||||
core::arch::asm!("mcr p15, 0, {operand}, c8, c3, 3", operand = in(reg) va as u32,
|
||||
options(nostack, preserves_flags));
|
||||
va += page_size;
|
||||
}
|
||||
core::arch::asm!("dsb ish", "isb", options(nostack, preserves_flags));
|
||||
}
|
||||
}
|
||||
|
||||
#[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));
|
||||
let mut va = start;
|
||||
while va < end {
|
||||
core::arch::asm!("tlbi vaae1is, {operand}", operand = in(reg) va >> 12,
|
||||
options(nostack, preserves_flags));
|
||||
va += page_size;
|
||||
}
|
||||
core::arch::asm!("dsb ish", "isb", options(nostack, preserves_flags));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
unsafe fn flush_pages(start: u64, end: u64, page_size: u64) {
|
||||
unsafe {
|
||||
let mut va = start;
|
||||
while va < end {
|
||||
core::arch::asm!("invlpg [{operand}]", operand = in(reg) va as usize,
|
||||
options(nostack, preserves_flags));
|
||||
va += page_size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_allocate_near(
|
||||
spec: gconstpointer,
|
||||
size: gsize,
|
||||
alignment: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gpointer {
|
||||
if spec.is_null() {
|
||||
return gum_memory_allocate(ptr::null_mut(), size, alignment, prot);
|
||||
}
|
||||
|
||||
let (wanted, reach) = unsafe {
|
||||
(
|
||||
(*(spec as *const GumAddressSpec)).near_address as u64,
|
||||
(*(spec as *const GumAddressSpec)).max_distance as u64,
|
||||
)
|
||||
};
|
||||
|
||||
let within_reach = |at: gpointer| {
|
||||
!at.is_null()
|
||||
&& (at as u64).abs_diff(wanted).max((at as u64 + size - 1).abs_diff(wanted)) <= reach
|
||||
};
|
||||
|
||||
let close = crate::xnu::kernel_alloc_code_above(wanted.saturating_sub(reach), size as usize)
|
||||
as gpointer;
|
||||
if within_reach(close) {
|
||||
remember_slab_of(close, size);
|
||||
return close;
|
||||
}
|
||||
if !close.is_null() {
|
||||
release_pages_now(close, size);
|
||||
}
|
||||
|
||||
let got = gum_memory_allocate(ptr::null_mut(), size, alignment, prot);
|
||||
if within_reach(got) {
|
||||
return got;
|
||||
}
|
||||
if !got.is_null() {
|
||||
release_pages_now(got, size);
|
||||
}
|
||||
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
fn release_pages_now(at: gpointer, size: gsize) {
|
||||
gum::unregister_slab(at as u64);
|
||||
crate::xnu::forget_what_we_took(at as u64);
|
||||
kernel::free_code(at as *mut u8, size as usize);
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
fn remember_slab_of(at: gpointer, size: gsize) {
|
||||
gum::register_slab(at as u64, size as usize);
|
||||
crate::xnu::remember_what_we_took(at as u64, size as usize, true);
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
#[repr(C)]
|
||||
struct GumAddressSpec {
|
||||
near_address: gpointer,
|
||||
max_distance: gsize,
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_allocate_bookkeeping(size: gsize, _alignment: gsize) -> gpointer {
|
||||
let ptr = crate::xnu::kernel_alloc_pages(size as usize);
|
||||
if !ptr.is_null() {
|
||||
unsafe { core::ptr::write_bytes(ptr, 0, size as usize) };
|
||||
}
|
||||
|
||||
ptr as gpointer
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_allocate(
|
||||
address: gpointer,
|
||||
size: gsize,
|
||||
_alignment: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gpointer {
|
||||
let may_run = (prot & _GumPageProtection_GUM_PAGE_EXECUTE) != 0;
|
||||
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
let ptr = kernel::alloc_code(size as usize);
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
let _ = address;
|
||||
|
||||
#[cfg(all(feature = "xnu-core", not(feature = "xnu-kext")))]
|
||||
let ptr = if crate::xnu::in_copy() {
|
||||
crate::xnu_user_calls::code_memory_near(address as u64, size as usize)
|
||||
} else {
|
||||
kernel::alloc_code(size as usize)
|
||||
};
|
||||
#[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;
|
||||
#[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);
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "xnu-kext"))]
|
||||
if may_run {
|
||||
unsafe { gum_mprotect(ptr as gpointer, size, prot) };
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
crate::xnu::remember_what_we_took(ptr as u64, size as usize, may_run);
|
||||
|
||||
ptr as gpointer
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_free(address: gpointer, size: gsize) -> gboolean {
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
if gum::is_agent_slab(address as u64) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Executable slabs were flipped to RX in the page tables; restore RW before returning them to
|
||||
// the allocator, otherwise the reclaimed pages stay non-writable and the next consumer faults.
|
||||
if gum::is_agent_slab(address as u64) {
|
||||
unsafe {
|
||||
gum_mprotect(
|
||||
address,
|
||||
size,
|
||||
(_GumPageProtection_GUM_PAGE_READ | _GumPageProtection_GUM_PAGE_WRITE)
|
||||
as GumPageProtection,
|
||||
);
|
||||
}
|
||||
gum::unregister_slab(address as u64);
|
||||
}
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
crate::xnu::forget_what_we_took(address as u64);
|
||||
|
||||
kernel::free_code(address as *mut u8, size as usize);
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
|
||||
#[cfg(feature = "linux-injected")]
|
||||
if kernel::in_copy() {
|
||||
kernel::register_what_the_copy_lives_among(registry);
|
||||
kernel::watch_the_loader();
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-core")]
|
||||
if crate::xnu::in_copy() {
|
||||
crate::xnu_mapped::register_what_the_copy_lives_among(registry);
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
{
|
||||
crate::gum_modules::publish(registry);
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "linux-injected"))]
|
||||
{
|
||||
let kernel_base = kernel::get_kernel_base();
|
||||
|
||||
unsafe {
|
||||
let module_infos = core::ptr::addr_of!(crate::MODULE_INFO);
|
||||
let module_infos = &*module_infos;
|
||||
|
||||
let mut i = 0;
|
||||
for module_info in module_infos.iter() {
|
||||
let module_base = kernel_base + module_info.offset as u64;
|
||||
|
||||
let module_path = if i == 0 {
|
||||
KERNEL_PATH
|
||||
} else {
|
||||
&format!("{}{}{}", MODULE_DIRECTORY, module_info.name, MODULE_SUFFIX)
|
||||
};
|
||||
let module_range = GumMemoryRange {
|
||||
base_address: module_base,
|
||||
size: module_info.size as gsize,
|
||||
};
|
||||
|
||||
let module =
|
||||
gum::gum_native_module_new(&module_path, &module_info.version, &module_range);
|
||||
gum_barebone_register_module(registry, module);
|
||||
g_object_unref(module as gpointer);
|
||||
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_on_registry_deactivating(_registry: *mut GumModuleRegistry) {
|
||||
#[cfg(feature = "linux-injected")]
|
||||
crate::gum_modules::unpublish();
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
|
||||
#[unsafe(no_mangle)]
|
||||
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), false);
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_on_thread_registry_deactivating(_registry: *mut GumThreadRegistry) {
|
||||
unsafe { THREAD_REGISTRY = ptr::null_mut() };
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub(crate) fn thread_appeared(id: u32) {
|
||||
announce_thread(id);
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub(crate) fn thread_vanished(id: u32) {
|
||||
let registry = unsafe { THREAD_REGISTRY };
|
||||
if registry.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe { gum_barebone_unregister_thread(registry, id as GumThreadId) };
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
|
||||
fn announce_thread(id: u32) {
|
||||
let registry = unsafe { THREAD_REGISTRY };
|
||||
if registry.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut details: GumThreadDetails = unsafe { core::mem::zeroed() };
|
||||
details.id = id as GumThreadId;
|
||||
|
||||
unsafe { gum_barebone_register_thread(registry, &details) };
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
|
||||
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,
|
||||
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"))]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _gum_process_enumerate_ranges(
|
||||
prot: GumPageProtection,
|
||||
func: GumFoundRangeFunc,
|
||||
user_data: gpointer,
|
||||
) {
|
||||
let Some(emit) = func else {
|
||||
return;
|
||||
};
|
||||
|
||||
let report = |base: u64, size: u64, protection: u32| {
|
||||
if (protection & prot as u32) != prot as u32 {
|
||||
return;
|
||||
}
|
||||
|
||||
let range = GumMemoryRange {
|
||||
base_address: base,
|
||||
size: size as gsize,
|
||||
};
|
||||
let details = GumRangeDetails {
|
||||
range: &range,
|
||||
protection: protection as GumPageProtection,
|
||||
file: ptr::null(),
|
||||
};
|
||||
|
||||
unsafe { emit(&details, user_data) };
|
||||
};
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
if !kernel::in_copy() {
|
||||
each_kernel_module_range(|base, size| report(base, size, KERNEL_RANGE_PROTECTION));
|
||||
return;
|
||||
}
|
||||
|
||||
kernel::enumerate_ranges(&mut |base, size, protection| report(base, size as u64, protection));
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
const KERNEL_RANGE_PROTECTION: u32 = 1 | 2 | 4;
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
fn each_kernel_module_range(mut visit: impl FnMut(u64, u64)) {
|
||||
let kernel_base = kernel::get_kernel_base();
|
||||
let module_infos = unsafe { &*core::ptr::addr_of!(crate::MODULE_INFO) };
|
||||
for module_info in module_infos.iter() {
|
||||
visit(kernel_base + module_info.offset, module_info.size);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
|
||||
#[unsafe(no_mangle)]
|
||||
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() {
|
||||
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,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
found
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn enumerate_exports_in_range(
|
||||
start_address: u64,
|
||||
end_address: u64,
|
||||
callback: &mut FoundExportCallback<'_>,
|
||||
) {
|
||||
#[cfg(feature = "linux-injected")]
|
||||
if kernel::in_copy() {
|
||||
kernel::enumerate_exports_in_range(start_address, end_address, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(feature = "linux-injected")]
|
||||
if crate::gum_modules::enumerate_exports_in_module(start_address, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-core")]
|
||||
if crate::xnu::in_copy() {
|
||||
crate::xnu_mapped::enumerate_exports_in_range(start_address, end_address, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let symbol_table = core::ptr::addr_of!(crate::SYMBOL_TABLE);
|
||||
let symbol_table = &*symbol_table;
|
||||
|
||||
const N_EXT: u8 = 0x01; // External symbol flag
|
||||
const N_TYPE: u8 = 0x0e; // Type mask
|
||||
const N_SECT: u8 = 0x0e; // Defined in section
|
||||
|
||||
for symbol_ref in symbol_table.iter_symbols_in_range(start_address, end_address) {
|
||||
let is_external = (symbol_ref.symbol_type() & N_EXT) != 0;
|
||||
let is_defined = (symbol_ref.symbol_type() & N_TYPE) == N_SECT;
|
||||
if !is_external || !is_defined {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !callback(symbol_ref.name_ptr(), symbol_ref.address()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
|
||||
0
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
// Linux half of Gum's platform backend. Everything is done locally: we are the
|
||||
// kernel, so page permissions go through set_memory_*(), writable aliases of
|
||||
// write-protected text come from vmap(), and symbols come from kallsyms.
|
||||
|
||||
use crate::{
|
||||
bindings::{
|
||||
_GumPageProtection_GUM_PAGE_EXECUTE, _GumPageProtection_GUM_PAGE_READ,
|
||||
_GumPageProtection_GUM_PAGE_WRITE, _GumRwxSupport_GUM_RWX_NONE, GArray,
|
||||
GumDebugSymbolDetails, GumModuleRegistry, GumPageProtection, GumRwxSupport,
|
||||
g_array_append_vals, g_array_new, g_pattern_spec_free, g_pattern_spec_match_string,
|
||||
g_pattern_spec_new, g_strdup, gboolean, gchar, gconstpointer, gpointer, gsize, guint,
|
||||
gum_mprotect,
|
||||
},
|
||||
gum::{self, FoundExportCallback},
|
||||
kernel,
|
||||
};
|
||||
use core::ffi::CStr;
|
||||
use core::ptr;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_query_platform() -> *const crate::bindings::gchar {
|
||||
c"linux".as_ptr() as *const crate::bindings::gchar
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_query_stack_size() -> gsize {
|
||||
crate::linux::stack_headroom() as gsize
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_query_rwx_support() -> GumRwxSupport {
|
||||
_GumRwxSupport_GUM_RWX_NONE
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_can_remap_writable() -> gboolean {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_try_remap_writable_pages(
|
||||
first_page: gpointer,
|
||||
n_pages: guint,
|
||||
) -> gpointer {
|
||||
kernel::remap_writable(first_page as u64, n_pages) as gpointer
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages: guint) {
|
||||
kernel::unmap_writable(writable as *mut core::ffi::c_void);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_try_mprotect(
|
||||
address: gpointer,
|
||||
size: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gboolean {
|
||||
if kernel::protect(address as u64, size as usize, prot as u32) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_allocate(
|
||||
_address: gpointer,
|
||||
size: gsize,
|
||||
_alignment: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gpointer {
|
||||
let ptr = kernel::alloc_code(size as usize);
|
||||
unsafe {
|
||||
core::ptr::write_bytes(ptr, 0, size as usize);
|
||||
if (prot & _GumPageProtection_GUM_PAGE_EXECUTE) != 0 {
|
||||
gum::register_slab(ptr as u64, size as usize);
|
||||
gum_mprotect(ptr as gpointer, size, prot);
|
||||
}
|
||||
}
|
||||
|
||||
ptr as gpointer
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_free(address: gpointer, size: gsize) -> gboolean {
|
||||
// vfree() wants the range writable again: the pages go back to the vmalloc
|
||||
// allocator, and leaving them RX would fault the next consumer.
|
||||
if gum::is_agent_slab(address as u64) {
|
||||
unsafe {
|
||||
gum_mprotect(
|
||||
address,
|
||||
size,
|
||||
(_GumPageProtection_GUM_PAGE_READ | _GumPageProtection_GUM_PAGE_WRITE)
|
||||
as GumPageProtection,
|
||||
);
|
||||
}
|
||||
gum::unregister_slab(address as u64);
|
||||
}
|
||||
kernel::free_code(address as *mut u8, size as usize);
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
|
||||
crate::gum_modules::publish(registry);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_on_registry_deactivating(_registry: *mut GumModuleRegistry) {
|
||||
crate::gum_modules::unpublish();
|
||||
}
|
||||
|
||||
pub(crate) unsafe fn enumerate_exports_in_range(
|
||||
start_address: u64,
|
||||
end_address: u64,
|
||||
callback: &mut FoundExportCallback<'_>,
|
||||
) {
|
||||
if crate::gum_modules::enumerate_exports_in_module(start_address, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
kernel::enumerate_symbols(&mut |name, address| {
|
||||
if address < start_address || address >= end_address {
|
||||
return true;
|
||||
}
|
||||
callback(name.as_ptr() as *const gchar, address)
|
||||
});
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_symbol_details_from_address(
|
||||
address: gpointer,
|
||||
details: *mut GumDebugSymbolDetails,
|
||||
) -> gboolean {
|
||||
let Some((name, symbol_address)) = kernel::symbol_from_address(address as u64) else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let name_bytes = name.as_bytes();
|
||||
let copy_len = core::cmp::min(name_bytes.len(), (*details).symbol_name.len() - 1);
|
||||
core::ptr::copy_nonoverlapping(
|
||||
name_bytes.as_ptr(),
|
||||
(*details).symbol_name.as_mut_ptr() as *mut u8,
|
||||
copy_len,
|
||||
);
|
||||
(*details).symbol_name[copy_len] = 0;
|
||||
|
||||
(*details).address = symbol_address;
|
||||
|
||||
(*details).module_name[0] = 0;
|
||||
(*details).file_name[0] = 0;
|
||||
(*details).line_number = 0;
|
||||
(*details).column = 0;
|
||||
}
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_symbol_name_from_address(address: gpointer) -> *mut gchar {
|
||||
let Some((name, _)) = kernel::symbol_from_address(address as u64) else {
|
||||
return ptr::null_mut();
|
||||
};
|
||||
|
||||
let mut name = name;
|
||||
name.push('\0');
|
||||
unsafe { g_strdup(name.as_ptr() as *const gchar) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_function(name: *const gchar) -> gpointer {
|
||||
let address = kernel::find_function(unsafe { CStr::from_ptr(name) });
|
||||
address as gpointer
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_functions_named(name: *const gchar) -> *mut GArray {
|
||||
let wanted = unsafe { CStr::from_ptr(name) };
|
||||
|
||||
collect_addresses(&mut |candidate, _address| candidate == wanted)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_find_functions_matching(pattern: *const gchar) -> *mut GArray {
|
||||
unsafe {
|
||||
let spec = g_pattern_spec_new(pattern);
|
||||
let array = collect_addresses(&mut |candidate, _address| {
|
||||
g_pattern_spec_match_string(spec, candidate.as_ptr() as *const gchar) != 0
|
||||
});
|
||||
g_pattern_spec_free(spec);
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_addresses(predicate: &mut dyn FnMut(&CStr, u64) -> bool) -> *mut GArray {
|
||||
unsafe {
|
||||
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
|
||||
|
||||
kernel::enumerate_symbols(&mut |name, address| {
|
||||
if predicate(name, address) {
|
||||
let addr = address as gpointer;
|
||||
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
|
||||
}
|
||||
true
|
||||
});
|
||||
|
||||
array
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
|
||||
0
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
use alloc::format;
|
||||
use crate::bindings::{
|
||||
GumMemoryRange, GumModuleRegistry, g_object_unref, gpointer, gsize,
|
||||
gum_barebone_register_module, gum_barebone_unregister_module,
|
||||
};
|
||||
use crate::gum::{self, FoundExportCallback, FoundSymbolCallback};
|
||||
use crate::kernel;
|
||||
use crate::linux::{LoadedModule, ModuleEvent};
|
||||
|
||||
pub fn publish(registry: *mut GumModuleRegistry) {
|
||||
unsafe { REGISTRY = registry };
|
||||
|
||||
let base = kernel::get_kernel_base();
|
||||
if base != 0 {
|
||||
register(registry, KERNEL_PATH, "", base, kernel::get_kernel_size());
|
||||
}
|
||||
|
||||
for m in kernel::enumerate_modules() {
|
||||
register(registry, &path_of(&m.name), &m.version, m.base, m.size);
|
||||
}
|
||||
|
||||
kernel::watch_modules(on_module_event);
|
||||
}
|
||||
|
||||
pub fn enumerate_symbols_in_range(
|
||||
start_address: u64,
|
||||
end_address: u64,
|
||||
callback: &mut FoundSymbolCallback<'_>,
|
||||
) {
|
||||
#[cfg(feature = "linux-injected")]
|
||||
if kernel::in_copy() {
|
||||
kernel::enumerate_symbols_in_range(start_address, end_address, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
kernel::enumerate_module_symbols(start_address, &mut |name, address, size, kind, global| {
|
||||
if address < start_address || address >= end_address {
|
||||
return true;
|
||||
}
|
||||
callback(name as *const _, address, size, kind, global)
|
||||
});
|
||||
}
|
||||
|
||||
pub fn enumerate_exports_in_module(
|
||||
start_address: u64,
|
||||
callback: &mut FoundExportCallback<'_>,
|
||||
) -> bool {
|
||||
kernel::enumerate_module_exports(start_address, &mut |name, address| {
|
||||
callback(name as *const _, address)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn unpublish() {
|
||||
kernel::unwatch_modules();
|
||||
|
||||
unsafe { REGISTRY = core::ptr::null_mut() };
|
||||
}
|
||||
|
||||
fn on_module_event(kind: ModuleEvent, m: LoadedModule) {
|
||||
let registry = unsafe { REGISTRY };
|
||||
if registry.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
match kind {
|
||||
ModuleEvent::Loaded => register(registry, &path_of(&m.name), &m.version, m.base, m.size),
|
||||
ModuleEvent::Unloaded => unsafe { gum_barebone_unregister_module(registry, m.base) },
|
||||
}
|
||||
}
|
||||
|
||||
fn register(
|
||||
registry: *mut GumModuleRegistry,
|
||||
path: &str,
|
||||
version: &str,
|
||||
base: u64,
|
||||
size: u64,
|
||||
) {
|
||||
unsafe {
|
||||
let range = GumMemoryRange {
|
||||
base_address: base,
|
||||
size: size as gsize,
|
||||
};
|
||||
|
||||
let module = gum::gum_native_module_new(path, version, &range);
|
||||
gum_barebone_register_module(registry, module);
|
||||
g_object_unref(module as gpointer);
|
||||
}
|
||||
}
|
||||
|
||||
fn path_of(name: &str) -> alloc::string::String {
|
||||
format!("{}{}{}", MODULE_DIRECTORY, name, MODULE_SUFFIX)
|
||||
}
|
||||
|
||||
static mut REGISTRY: *mut GumModuleRegistry = core::ptr::null_mut();
|
||||
|
||||
const KERNEL_PATH: &str = "/boot/vmlinux";
|
||||
const MODULE_DIRECTORY: &str = "/lib/modules/";
|
||||
const MODULE_SUFFIX: &str = ".ko";
|
||||
@@ -1,678 +0,0 @@
|
||||
// The Windows part of Gum's platform backend, for Win9x and NT. Kernel code is writable on
|
||||
// both, thus this backend needs no alias. Only the position of the modules is different.
|
||||
|
||||
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,
|
||||
gconstpointer, gum_thread_details_copy,
|
||||
},
|
||||
gum::{self, FoundExportCallback},
|
||||
kernel,
|
||||
};
|
||||
use crate::bindings::{GumCpuContext, GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT};
|
||||
#[cfg(not(feature = "winnt"))]
|
||||
use alloc::format;
|
||||
use core::ptr;
|
||||
|
||||
#[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_barebone_query_stack_size() -> crate::bindings::gsize {
|
||||
if crate::on_js_thread() {
|
||||
kernel::THREAD_STACK_SIZE as crate::bindings::gsize
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_query_rwx_support() -> GumRwxSupport {
|
||||
_GumRwxSupport_GUM_RWX_FULL
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_can_remap_writable() -> gboolean {
|
||||
#[cfg(feature = "win9x")]
|
||||
{
|
||||
crate::win9x::in_copy() as gboolean
|
||||
}
|
||||
#[cfg(not(feature = "win9x"))]
|
||||
{
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_try_remap_writable_pages(
|
||||
first_page: gpointer,
|
||||
_n_pages: guint,
|
||||
) -> gpointer {
|
||||
#[cfg(feature = "win9x")]
|
||||
if crate::win9x::in_copy() {
|
||||
return crate::win9x_user::take_writes_on(first_page as *mut u8, _n_pages) as gpointer;
|
||||
}
|
||||
|
||||
first_page
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_dispose_writable_pages(_writable: gpointer, _n_pages: guint) {
|
||||
#[cfg(feature = "win9x")]
|
||||
if crate::win9x::in_copy() {
|
||||
crate::win9x_user::make_the_writes(_writable as *mut u8, _n_pages);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_query_region(
|
||||
address: gconstpointer,
|
||||
range: *mut GumMemoryRange,
|
||||
prot: *mut GumPageProtection,
|
||||
) -> gboolean {
|
||||
let Some(region) = kernel::region_at(address as usize).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
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_try_mprotect(
|
||||
address: gpointer,
|
||||
size: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gboolean {
|
||||
if kernel::protect(address as u64, size as usize, prot as u32) {
|
||||
1
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_allocate(
|
||||
_address: gpointer,
|
||||
size: gsize,
|
||||
_alignment: gsize,
|
||||
prot: GumPageProtection,
|
||||
) -> gpointer {
|
||||
let ptr = kernel::alloc_code(size as usize);
|
||||
if ptr.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
unsafe {
|
||||
core::ptr::write_bytes(ptr, 0, size as usize);
|
||||
if (prot & _GumPageProtection_GUM_PAGE_EXECUTE) != 0 {
|
||||
gum::register_slab(ptr as u64, size as usize);
|
||||
gum_mprotect(ptr as gpointer, size, prot);
|
||||
}
|
||||
}
|
||||
|
||||
ptr as gpointer
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_memory_free(address: gpointer, size: gsize) -> gboolean {
|
||||
if gum::is_agent_slab(address as u64) {
|
||||
gum::unregister_slab(address as u64);
|
||||
}
|
||||
kernel::free_code(address as *mut u8, size as usize);
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
|
||||
if crate::running_in_a_process() {
|
||||
register_the_process_modules(registry);
|
||||
} else {
|
||||
register_the_kernel_modules(registry);
|
||||
}
|
||||
}
|
||||
|
||||
// A script can hook a library the moment it arrives, thus stand in front of the loader and say
|
||||
// what came and what went.
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
pub(crate) fn watch_the_loader() {
|
||||
let Some(entries) = kernel::loader_entry_points() else {
|
||||
return;
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let interceptor = crate::bindings::gum_interceptor_obtain();
|
||||
crate::bindings::gum_interceptor_begin_transaction(interceptor);
|
||||
crate::bindings::gum_interceptor_replace(interceptor, entries.load as gpointer,
|
||||
kernel::on_module_load as gpointer, &raw mut LOADER_LOAD, ptr::null());
|
||||
if entries.load_with_flags != 0 {
|
||||
crate::bindings::gum_interceptor_replace(interceptor,
|
||||
entries.load_with_flags as gpointer, kernel::on_module_load_with_flags as gpointer,
|
||||
&raw mut LOADER_LOAD_WITH_FLAGS, ptr::null());
|
||||
}
|
||||
crate::bindings::gum_interceptor_replace(interceptor, entries.unload as gpointer,
|
||||
kernel::on_module_unload as gpointer, &raw mut LOADER_UNLOAD, ptr::null());
|
||||
|
||||
crate::bindings::gum_interceptor_end_transaction(interceptor);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "winnt")]
|
||||
pub(crate) fn forget_the_loader() {
|
||||
let Some(entries) = kernel::loader_entry_points() else {
|
||||
return;
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let interceptor = crate::bindings::gum_interceptor_obtain();
|
||||
crate::bindings::gum_interceptor_begin_transaction(interceptor);
|
||||
crate::bindings::gum_interceptor_revert(interceptor, entries.load as gpointer);
|
||||
if entries.load_with_flags != 0 {
|
||||
crate::bindings::gum_interceptor_revert(interceptor,
|
||||
entries.load_with_flags as gpointer);
|
||||
}
|
||||
crate::bindings::gum_interceptor_revert(interceptor, entries.unload as gpointer);
|
||||
crate::bindings::gum_interceptor_end_transaction(interceptor);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
pub(crate) fn loader_load() -> gpointer {
|
||||
unsafe { LOADER_LOAD }
|
||||
}
|
||||
|
||||
#[cfg(feature = "win9x")]
|
||||
pub(crate) fn loader_load_with_flags() -> gpointer {
|
||||
unsafe { LOADER_LOAD_WITH_FLAGS }
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
pub(crate) fn loader_unload() -> gpointer {
|
||||
unsafe { LOADER_UNLOAD }
|
||||
}
|
||||
|
||||
#[cfg(feature = "win9x")]
|
||||
pub(crate) fn module_arrived(base: u64, path: &str) {
|
||||
if !known_mut().insert(base) {
|
||||
return;
|
||||
}
|
||||
|
||||
let range = GumMemoryRange {
|
||||
base_address: base,
|
||||
size: kernel::image_size(base as u32) as gsize,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let registry = crate::bindings::gum_module_registry_obtain();
|
||||
crate::bindings::gum_module_registry_lock(registry);
|
||||
|
||||
let native = gum::gum_native_module_new(path, "", &range);
|
||||
gum_barebone_register_module(registry, native);
|
||||
g_object_unref(native as gpointer);
|
||||
|
||||
crate::bindings::gum_module_registry_unlock(registry);
|
||||
}
|
||||
}
|
||||
|
||||
// A module arrived, thus tell the registry where it is.
|
||||
#[cfg(feature = "winnt")]
|
||||
pub(crate) fn module_arrived(base: u64) {
|
||||
if !known_mut().insert(base) {
|
||||
return;
|
||||
}
|
||||
|
||||
let Some(module) = kernel::describe_module(base) else {
|
||||
known_mut().remove(&base);
|
||||
return;
|
||||
};
|
||||
|
||||
let range = GumMemoryRange {
|
||||
base_address: module.base,
|
||||
size: module.size as gsize,
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let registry = crate::bindings::gum_module_registry_obtain();
|
||||
crate::bindings::gum_module_registry_lock(registry);
|
||||
|
||||
let native = gum::gum_native_module_new(&module.path, "", &range);
|
||||
gum_barebone_register_module(registry, native);
|
||||
g_object_unref(native as gpointer);
|
||||
|
||||
crate::bindings::gum_module_registry_unlock(registry);
|
||||
}
|
||||
}
|
||||
|
||||
// A module went away, thus take it out of the registry while its name is still known.
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
pub(crate) fn module_left(base: u64) {
|
||||
if !known_mut().remove(&base) {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let registry = crate::bindings::gum_module_registry_obtain();
|
||||
crate::bindings::gum_module_registry_lock(registry);
|
||||
|
||||
crate::bindings::gum_barebone_unregister_module(registry, base);
|
||||
|
||||
crate::bindings::gum_module_registry_unlock(registry);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
fn known_mut() -> &'static mut alloc::collections::BTreeSet<u64> {
|
||||
unsafe { (&raw mut KNOWN).as_mut().unwrap() }
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
static mut KNOWN: alloc::collections::BTreeSet<u64> = alloc::collections::BTreeSet::new();
|
||||
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
static mut LOADER_LOAD: gpointer = ptr::null_mut();
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
static mut LOADER_LOAD_WITH_FLAGS: gpointer = ptr::null_mut();
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
static mut LOADER_UNLOAD: gpointer = ptr::null_mut();
|
||||
|
||||
fn register_the_process_modules(registry: *mut GumModuleRegistry) {
|
||||
for module in kernel::enumerate_modules() {
|
||||
known_mut().insert(module.base);
|
||||
|
||||
register_module(registry, &module.path, "", module.base, module.size);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _gum_process_enumerate_ranges(
|
||||
prot: GumPageProtection,
|
||||
func: GumFoundRangeFunc,
|
||||
user_data: gpointer,
|
||||
) {
|
||||
let Some(emit) = func else {
|
||||
return;
|
||||
};
|
||||
|
||||
kernel::enumerate_ranges(&mut |base, size, protection| {
|
||||
if (protection & prot as u32) != prot as u32 {
|
||||
return;
|
||||
}
|
||||
|
||||
let range = GumMemoryRange {
|
||||
base_address: base,
|
||||
size: size as gsize,
|
||||
};
|
||||
let details = GumRangeDetails {
|
||||
range: &range,
|
||||
protection: protection as GumPageProtection,
|
||||
file: ptr::null(),
|
||||
};
|
||||
|
||||
unsafe { emit(&details, user_data) };
|
||||
});
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
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), false);
|
||||
|
||||
if kernel::watches_threads() {
|
||||
kernel::watch_threads(announce_thread, announce_thread_is_gone);
|
||||
} else {
|
||||
watch_the_threads();
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_on_thread_registry_deactivating(_registry: *mut GumThreadRegistry) {
|
||||
|
||||
if kernel::watches_threads() {
|
||||
kernel::forget_threads();
|
||||
} else {
|
||||
forget_the_threads();
|
||||
}
|
||||
|
||||
unsafe { THREAD_REGISTRY = ptr::null_mut() };
|
||||
}
|
||||
|
||||
fn watch_the_threads() {
|
||||
let Some(entries) = kernel::thread_entry_points() else {
|
||||
return;
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let interceptor = crate::bindings::gum_interceptor_obtain();
|
||||
crate::bindings::gum_interceptor_begin_transaction(interceptor);
|
||||
crate::bindings::gum_interceptor_replace(interceptor, entries.start as gpointer,
|
||||
kernel::on_thread_start as gpointer, kernel::thread_start_slot(), ptr::null());
|
||||
crate::bindings::gum_interceptor_replace(interceptor, entries.exit as gpointer,
|
||||
kernel::on_thread_exit as gpointer, kernel::thread_exit_slot(), ptr::null());
|
||||
crate::bindings::gum_interceptor_end_transaction(interceptor);
|
||||
}
|
||||
}
|
||||
|
||||
fn forget_the_threads() {
|
||||
let Some(entries) = kernel::thread_entry_points() else {
|
||||
return;
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let interceptor = crate::bindings::gum_interceptor_obtain();
|
||||
crate::bindings::gum_interceptor_begin_transaction(interceptor);
|
||||
crate::bindings::gum_interceptor_revert(interceptor, entries.start as gpointer);
|
||||
crate::bindings::gum_interceptor_revert(interceptor, entries.exit as gpointer);
|
||||
crate::bindings::gum_interceptor_end_transaction(interceptor);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn thread_appeared_at(id: u32, routine: usize, parameter: usize) {
|
||||
let registry = unsafe { THREAD_REGISTRY };
|
||||
if registry.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut details: GumThreadDetails = unsafe { core::mem::zeroed() };
|
||||
details.id = id as GumThreadId;
|
||||
details.entrypoint.routine = routine as u64;
|
||||
details.entrypoint.parameter = parameter as u64;
|
||||
details.flags = crate::bindings::GumThreadFlags_GUM_THREAD_FLAGS_ENTRYPOINT_ROUTINE
|
||||
| crate::bindings::GumThreadFlags_GUM_THREAD_FLAGS_ENTRYPOINT_PARAMETER;
|
||||
|
||||
unsafe { crate::bindings::gum_barebone_register_thread(registry, &details) };
|
||||
}
|
||||
|
||||
pub(crate) fn thread_vanished(id: u32) {
|
||||
announce_thread_is_gone(id);
|
||||
}
|
||||
|
||||
fn announce_thread(id: u32) {
|
||||
let registry = unsafe { THREAD_REGISTRY };
|
||||
if registry.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut details: GumThreadDetails = unsafe { core::mem::zeroed() };
|
||||
details.id = id as GumThreadId;
|
||||
|
||||
unsafe { crate::bindings::gum_barebone_register_thread(registry, &details) };
|
||||
}
|
||||
|
||||
fn announce_thread_is_gone(id: u32) {
|
||||
let registry = unsafe { THREAD_REGISTRY };
|
||||
if registry.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe { crate::bindings::gum_barebone_unregister_thread(registry, id as GumThreadId) };
|
||||
}
|
||||
|
||||
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,
|
||||
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;
|
||||
details.id = thread.id as GumThreadId;
|
||||
|
||||
if let Some(state) = thread.cpu_state {
|
||||
details.flags = GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT;
|
||||
details.cpu_context = cpu_context_from(&state);
|
||||
}
|
||||
|
||||
unsafe { emit(&details, user_data) };
|
||||
}, wanted);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_find_thread_by_id(thread_id: GumThreadId, flags: GumThreadFlags)
|
||||
-> *mut GumThreadDetails {
|
||||
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();
|
||||
};
|
||||
|
||||
let mut details: GumThreadDetails = unsafe { core::mem::zeroed() };
|
||||
details.id = thread.id as GumThreadId;
|
||||
|
||||
if let Some(state) = thread.cpu_state {
|
||||
details.flags = GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT;
|
||||
details.cpu_context = cpu_context_from(&state);
|
||||
}
|
||||
|
||||
unsafe { gum_thread_details_copy(&details) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gum_barebone_modify_thread(thread_id: GumThreadId, func: GumModifyThreadFunc,
|
||||
user_data: gpointer, _flags: GumModifyThreadFlags) -> gboolean {
|
||||
let Some(modify) = func else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
let changed = kernel::modify_thread(thread_id as u32, &mut |state| {
|
||||
let mut context = cpu_context_from(state);
|
||||
unsafe { modify(thread_id, &mut context, user_data) };
|
||||
*state = cpu_state_from(&context);
|
||||
});
|
||||
|
||||
changed as gboolean
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub(crate) 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 = "x86")]
|
||||
pub(crate) fn cpu_state_from(context: &GumCpuContext) -> kernel::CpuState {
|
||||
kernel::CpuState {
|
||||
eip: context.eip,
|
||||
edi: context.edi,
|
||||
esi: context.esi,
|
||||
ebp: context.ebp,
|
||||
esp: context.esp,
|
||||
ebx: context.ebx,
|
||||
edx: context.edx,
|
||||
ecx: context.ecx,
|
||||
eax: context.eax,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub(crate) 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_64")]
|
||||
pub(crate) fn cpu_state_from(context: &GumCpuContext) -> kernel::CpuState {
|
||||
kernel::CpuState {
|
||||
rip: context.rip,
|
||||
r15: context.r15,
|
||||
r14: context.r14,
|
||||
r13: context.r13,
|
||||
r12: context.r12,
|
||||
r11: context.r11,
|
||||
r10: context.r10,
|
||||
r9: context.r9,
|
||||
r8: context.r8,
|
||||
rdi: context.rdi,
|
||||
rsi: context.rsi,
|
||||
rbp: context.rbp,
|
||||
rsp: context.rsp,
|
||||
rbx: context.rbx,
|
||||
rdx: context.rdx,
|
||||
rcx: context.rcx,
|
||||
rax: context.rax,
|
||||
}
|
||||
}
|
||||
|
||||
#[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::*;
|
||||
|
||||
match code {
|
||||
ACCESS_VIOLATION | MISALIGNED_DATA | ARRAY_BOUNDS_EXCEEDED =>
|
||||
_GumExceptionType_GUM_EXCEPTION_ACCESS_VIOLATION,
|
||||
GUARD_PAGE => _GumExceptionType_GUM_EXCEPTION_GUARD_PAGE,
|
||||
ILLEGAL_INSTRUCTION | PRIVILEGED_INSTRUCTION =>
|
||||
_GumExceptionType_GUM_EXCEPTION_ILLEGAL_INSTRUCTION,
|
||||
STACK_OVERFLOW => _GumExceptionType_GUM_EXCEPTION_STACK_OVERFLOW,
|
||||
FLOAT_DENORMAL_OPERAND | FLOAT_DIVIDE_BY_ZERO | FLOAT_INEXACT_RESULT
|
||||
| FLOAT_INVALID_OPERATION | FLOAT_OVERFLOW | FLOAT_STACK_CHECK | FLOAT_UNDERFLOW
|
||||
| INTEGER_DIVIDE_BY_ZERO | INTEGER_OVERFLOW =>
|
||||
_GumExceptionType_GUM_EXCEPTION_ARITHMETIC,
|
||||
BREAKPOINT => _GumExceptionType_GUM_EXCEPTION_BREAKPOINT,
|
||||
SINGLE_STEP => _GumExceptionType_GUM_EXCEPTION_SINGLE_STEP,
|
||||
_ => _GumExceptionType_GUM_EXCEPTION_SYSTEM,
|
||||
}
|
||||
}
|
||||
|
||||
const ACCESS_VIOLATION: u32 = 0xc000_0005;
|
||||
const MISALIGNED_DATA: u32 = 0x8000_0002;
|
||||
const ARRAY_BOUNDS_EXCEEDED: u32 = 0xc000_008c;
|
||||
const GUARD_PAGE: u32 = 0x8000_0001;
|
||||
const ILLEGAL_INSTRUCTION: u32 = 0xc000_001d;
|
||||
const PRIVILEGED_INSTRUCTION: u32 = 0xc000_0096;
|
||||
const STACK_OVERFLOW: u32 = 0xc000_00fd;
|
||||
const FLOAT_DENORMAL_OPERAND: u32 = 0xc000_008d;
|
||||
const FLOAT_DIVIDE_BY_ZERO: u32 = 0xc000_008e;
|
||||
const FLOAT_INEXACT_RESULT: u32 = 0xc000_008f;
|
||||
const FLOAT_INVALID_OPERATION: u32 = 0xc000_0090;
|
||||
const FLOAT_OVERFLOW: u32 = 0xc000_0091;
|
||||
const FLOAT_STACK_CHECK: u32 = 0xc000_0092;
|
||||
const FLOAT_UNDERFLOW: u32 = 0xc000_0093;
|
||||
const INTEGER_DIVIDE_BY_ZERO: u32 = 0xc000_0094;
|
||||
const INTEGER_OVERFLOW: u32 = 0xc000_0095;
|
||||
const BREAKPOINT: u32 = 0x8000_0003;
|
||||
const SINGLE_STEP: u32 = 0x8000_0004;
|
||||
|
||||
pub(crate) unsafe fn enumerate_exports_in_range(
|
||||
start_address: u64,
|
||||
end_address: u64,
|
||||
callback: &mut FoundExportCallback,
|
||||
) {
|
||||
#[cfg(any(feature = "win9x", feature = "winnt"))]
|
||||
if crate::running_in_a_process() {
|
||||
kernel::enumerate_exports(start_address as _, &mut |name, address| {
|
||||
callback(name as *const crate::bindings::gchar, address)
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let symbol_table = &*core::ptr::addr_of!(crate::SYMBOL_TABLE);
|
||||
|
||||
for symbol_ref in symbol_table.iter_symbols_in_range(start_address, end_address) {
|
||||
if !callback(symbol_ref.name_ptr(), symbol_ref.address()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
// The copy's own memory, which is not the C library's: that one asks for memory of its own
|
||||
// while it is taking its lock, and a lock that allocates through the allocator it guards never
|
||||
// finishes the first time it is asked.
|
||||
pub fn take(size: usize) -> *mut u8 {
|
||||
let wanted = wanted_for(size);
|
||||
let class = class_of(wanted);
|
||||
|
||||
held(|| {
|
||||
if let Some(piece) = pop(class) {
|
||||
return piece;
|
||||
}
|
||||
|
||||
carve(class)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn give_back(piece: *mut u8) {
|
||||
if piece.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let class = unsafe { *(piece.sub(HEADER) as *const usize) };
|
||||
|
||||
held(|| push(class, piece));
|
||||
}
|
||||
|
||||
fn pages_for(size: usize) -> *mut u8 {
|
||||
#[cfg(feature = "linux-injected")]
|
||||
{
|
||||
crate::linux::user::map_writable(size)
|
||||
}
|
||||
#[cfg(feature = "xnu-core")]
|
||||
{
|
||||
crate::xnu_user_calls::map_writable(size)
|
||||
}
|
||||
}
|
||||
|
||||
fn pop(class: usize) -> Option<*mut u8> {
|
||||
let free = unsafe { (&raw mut FREE).as_mut().unwrap() };
|
||||
|
||||
let piece = free[class];
|
||||
if piece.is_null() {
|
||||
return None;
|
||||
}
|
||||
free[class] = unsafe { *(piece as *const *mut u8) };
|
||||
|
||||
Some(piece)
|
||||
}
|
||||
|
||||
fn push(class: usize, piece: *mut u8) {
|
||||
let free = unsafe { (&raw mut FREE).as_mut().unwrap() };
|
||||
|
||||
unsafe { *(piece as *mut *mut u8) = free[class] };
|
||||
free[class] = piece;
|
||||
}
|
||||
|
||||
fn carve(class: usize) -> *mut u8 {
|
||||
let wanted = HEADER + (SMALLEST << class);
|
||||
|
||||
let mut edge = unsafe { EDGE };
|
||||
let mut left = unsafe { LEFT };
|
||||
if left < wanted {
|
||||
let asked = if wanted > CHUNK { wanted } else { CHUNK };
|
||||
edge = pages_for(asked) as usize;
|
||||
if edge == 0 {
|
||||
return core::ptr::null_mut();
|
||||
}
|
||||
left = asked;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
EDGE = edge + wanted;
|
||||
LEFT = left - wanted;
|
||||
|
||||
*(edge as *mut usize) = class;
|
||||
}
|
||||
|
||||
(edge + HEADER) as *mut u8
|
||||
}
|
||||
|
||||
fn held<T>(work: impl FnOnce() -> T) -> T {
|
||||
while TAKEN.swap(true, Ordering::Acquire) {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
|
||||
let answer = work();
|
||||
|
||||
TAKEN.store(false, Ordering::Release);
|
||||
|
||||
answer
|
||||
}
|
||||
|
||||
fn wanted_for(size: usize) -> usize {
|
||||
if size < SMALLEST { SMALLEST } else { size }
|
||||
}
|
||||
|
||||
fn class_of(size: usize) -> usize {
|
||||
let mut class = 0;
|
||||
while (SMALLEST << class) < size {
|
||||
class += 1;
|
||||
}
|
||||
|
||||
class
|
||||
}
|
||||
|
||||
static mut FREE: [*mut u8; CLASSES] = [core::ptr::null_mut(); CLASSES];
|
||||
static mut EDGE: usize = 0;
|
||||
static mut LEFT: usize = 0;
|
||||
static TAKEN: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
const HEADER: usize = 16;
|
||||
const SMALLEST: usize = 16;
|
||||
const CLASSES: usize = 32;
|
||||
const CHUNK: usize = 4 * 1024 * 1024;
|
||||
@@ -1,121 +0,0 @@
|
||||
// Linux transport: /dev/frida, framed the same way as the XNU hostlinks (4-byte
|
||||
// little-endian length prefix) so the host-side (yqv) protocol is identical across
|
||||
// every transport.
|
||||
//
|
||||
// The character device itself lives in linux/frida-kmod.c and hands us a byte
|
||||
// stream; whoever opens the device decides how it reaches the host.
|
||||
|
||||
use core::cell::UnsafeCell;
|
||||
use core::ffi::{c_int, c_void};
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
struct Inner {
|
||||
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(on_rx: Option<fn(&[u8])>) -> Result<Self, ()> {
|
||||
if unsafe { frida_kmod_link_open() } != 0 {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
Ok(Hostlink {
|
||||
state: UnsafeCell::new(Inner {
|
||||
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 header = (payload.len() as u32).to_le_bytes();
|
||||
unsafe {
|
||||
frida_kmod_link_send(header.as_ptr() as *const c_void, header.len());
|
||||
frida_kmod_link_send(payload.as_ptr() as *const c_void, payload.len());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process(&self) {
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
loop {
|
||||
if s.rx_lenhave < 4 {
|
||||
let lo = s.rx_lenhave;
|
||||
let n = recv_nonblocking(&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 = recv_nonblocking(&mut s.rx_buf[lo..hi]);
|
||||
if n == 0 {
|
||||
return;
|
||||
}
|
||||
s.rx_have += n;
|
||||
}
|
||||
|
||||
// Detach the frame and reset receive state before dispatching: the callback may
|
||||
// re-enter process() (a synchronous host RPC issued while handling a command), and
|
||||
// it must start from a clean state instead of re-dispatching this same frame.
|
||||
let frame = core::mem::take(&mut s.rx_buf);
|
||||
let need = s.rx_need;
|
||||
s.rx_lenhave = 0;
|
||||
s.rx_have = 0;
|
||||
s.rx_need = 0;
|
||||
|
||||
if let Some(cb) = self.on_rx {
|
||||
cb(&frame[..need]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Hostlink {
|
||||
fn drop(&mut self) {
|
||||
unsafe { frida_kmod_link_close() };
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
pub fn a_turn_is_wanted() -> bool {
|
||||
unsafe { frida_kmod_link_pending() }
|
||||
}
|
||||
|
||||
fn recv_nonblocking(dst: &mut [u8]) -> usize {
|
||||
let n = unsafe { frida_kmod_link_recv(dst.as_mut_ptr() as *mut c_void, dst.len()) };
|
||||
if n <= 0 { 0 } else { n as usize }
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
fn frida_kmod_link_open() -> c_int;
|
||||
fn frida_kmod_link_close();
|
||||
fn frida_kmod_link_send(data: *const c_void, size: usize) -> c_int;
|
||||
fn frida_kmod_link_recv(data: *mut c_void, size: usize) -> isize;
|
||||
#[cfg(feature = "xnu-kext")]
|
||||
fn frida_kmod_link_pending() -> bool;
|
||||
}
|
||||
@@ -1,441 +0,0 @@
|
||||
|
||||
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);
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -8,6 +8,8 @@ use core::sync::atomic::{AtomicU32, Ordering};
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use crate::xnu;
|
||||
|
||||
const AF_VSOCK: c_int = 40;
|
||||
const SOCK_STREAM: c_int = 1;
|
||||
const VMADDR_CID_HOST: u32 = 2;
|
||||
@@ -77,10 +79,6 @@ unsafe extern "C" {
|
||||
|
||||
static UPCALL_PENDING: AtomicU32 = AtomicU32::new(0);
|
||||
|
||||
pub fn a_turn_is_wanted() -> bool {
|
||||
UPCALL_PENDING.load(Ordering::Acquire) != 0
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
so: SocketT,
|
||||
rx_lenbuf: [u8; 4],
|
||||
@@ -155,17 +153,39 @@ impl 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);
|
||||
let len = payload.len() as u32;
|
||||
let mut hdr = [
|
||||
(len & 0xff) as u8,
|
||||
((len >> 8) & 0xff) as u8,
|
||||
((len >> 16) & 0xff) as u8,
|
||||
((len >> 24) & 0xff) as u8,
|
||||
];
|
||||
let mut iov = [
|
||||
Iovec {
|
||||
iov_base: hdr.as_mut_ptr() as *mut c_void,
|
||||
iov_len: 4,
|
||||
},
|
||||
Iovec {
|
||||
iov_base: payload.as_ptr() as *mut c_void,
|
||||
iov_len: payload.len(),
|
||||
},
|
||||
];
|
||||
let msg = Msghdr {
|
||||
msg_name: ptr::null_mut(),
|
||||
msg_namelen: 0,
|
||||
msg_iov: iov.as_mut_ptr(),
|
||||
msg_iovlen: 2,
|
||||
msg_control: ptr::null_mut(),
|
||||
msg_controllen: 0,
|
||||
msg_flags: 0,
|
||||
};
|
||||
let mut sent: usize = 0;
|
||||
let _ = _sock_send(s.so, &msg, 0, &mut sent);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process(&self) {
|
||||
UPCALL_PENDING.store(0, Ordering::Release);
|
||||
|
||||
let s = unsafe { &mut *self.state.get() };
|
||||
loop {
|
||||
if s.rx_lenhave < 4 {
|
||||
@@ -239,32 +259,9 @@ unsafe fn recv_nonblocking(so: SocketT, dst: &mut [u8]) -> usize {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn send_all(so: SocketT, bytes: &[u8]) {
|
||||
let mut gone = 0;
|
||||
while gone < bytes.len() {
|
||||
let mut iov = [Iovec {
|
||||
iov_base: unsafe { bytes.as_ptr().add(gone) } as *mut c_void,
|
||||
iov_len: bytes.len() - gone,
|
||||
}];
|
||||
let msg = Msghdr {
|
||||
msg_name: ptr::null_mut(),
|
||||
msg_namelen: 0,
|
||||
msg_iov: iov.as_mut_ptr(),
|
||||
msg_iovlen: 1,
|
||||
msg_control: ptr::null_mut(),
|
||||
msg_controllen: 0,
|
||||
msg_flags: 0,
|
||||
};
|
||||
|
||||
let mut sent: usize = 0;
|
||||
if unsafe { _sock_send(so, &msg, 0, &mut sent) } != 0 || sent == 0 {
|
||||
return;
|
||||
}
|
||||
gone += sent;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn upcall(_so: SocketT, cookie: *mut c_void, _waitf: c_int) {
|
||||
UPCALL_PENDING.fetch_add(1, Ordering::Release);
|
||||
crate::nudge_the_loop(cookie as *const u8);
|
||||
if !cookie.is_null() {
|
||||
xnu::thread_wakeup(cookie as *const u8);
|
||||
}
|
||||
}
|
||||
@@ -1,377 +0,0 @@
|
||||
|
||||
pub trait Image {
|
||||
fn read_at(&self, position: u32, buffer: &mut [u8]) -> u32;
|
||||
}
|
||||
|
||||
pub fn enumerate(image: &dyn Image, found: &mut dyn FnMut(&[u8])) {
|
||||
let Some(resources) = resource_directory(image) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let Some(group) = resources.first_leaf(RT_GROUP_ICON) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// A group contains each size one time for each color depth. Keep only the best of each size.
|
||||
let count = read_u16(image, group.offset + GROUP_COUNT_OFFSET) as u32;
|
||||
for index in 0..count {
|
||||
if !is_best_of_its_size(image, group.offset, count, index) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let entry = group.offset + GROUP_ENTRIES_OFFSET + index * GROUP_ENTRY_SIZE;
|
||||
let id = read_u16(image, entry + GROUP_ENTRY_ID_OFFSET);
|
||||
if let Some(icon) = resources.leaf(RT_ICON, id) {
|
||||
if let Some(bytes) = read_blob(image, icon.offset, icon.size) {
|
||||
found(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn describe(image: &dyn Image, into: &mut [u8]) -> usize {
|
||||
if read_u16(image, read_u32(image, DOS_HEADERS_OFFSET)) == NE_SIGNATURE {
|
||||
return describe_ne(image, into);
|
||||
}
|
||||
|
||||
string_in_version(image, FILE_DESCRIPTION, into)
|
||||
}
|
||||
|
||||
pub fn identify(image: &dyn Image, into: &mut [u8]) -> usize {
|
||||
if read_u16(image, read_u32(image, DOS_HEADERS_OFFSET)) == NE_SIGNATURE {
|
||||
let mut description = [0u8; 128];
|
||||
let taken = describe_ne(image, &mut description);
|
||||
let described = &description[..taken];
|
||||
let Some(space) = described.iter().position(|b| *b == b' ') else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
let program = &described[space + 1..];
|
||||
if program.iter().filter(|b| b.is_ascii_alphanumeric()).count() > MAX_PROGRAM_WORD {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return write_identity(&described[..space], program, into);
|
||||
}
|
||||
|
||||
let mut company = [0u8; 64];
|
||||
let mut taken_company = string_in_version(image, COMPANY_NAME, &mut company);
|
||||
if taken_company == 0 {
|
||||
taken_company = string_in_version(image, PRODUCT_NAME, &mut company);
|
||||
}
|
||||
|
||||
let mut internal = [0u8; 64];
|
||||
let mut taken_internal = string_in_version(image, INTERNAL_NAME, &mut internal);
|
||||
if taken_internal == 0 {
|
||||
taken_internal = string_in_version(image, ORIGINAL_FILENAME, &mut internal);
|
||||
}
|
||||
|
||||
if taken_company == 0 || taken_internal == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
write_identity(&company[..taken_company], without_extension(&internal[..taken_internal]), into)
|
||||
}
|
||||
|
||||
fn without_extension(name: &[u8]) -> &[u8] {
|
||||
match name.iter().rposition(|b| *b == b'.') {
|
||||
Some(dot) => &name[..dot],
|
||||
None => name,
|
||||
}
|
||||
}
|
||||
|
||||
fn write_identity(company: &[u8], program: &[u8], into: &mut [u8]) -> usize {
|
||||
let maker = match company.iter().position(|b| *b == b' ') {
|
||||
Some(space) => &company[..space],
|
||||
None => company,
|
||||
};
|
||||
|
||||
let mut written = 0;
|
||||
let put = |byte: u8, into: &mut [u8], written: &mut usize| {
|
||||
if *written != into.len() {
|
||||
into[*written] = byte;
|
||||
*written += 1;
|
||||
}
|
||||
};
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
put_word(b"com", into, &mut written);
|
||||
put(b'.', into, &mut written);
|
||||
put_word(maker, into, &mut written);
|
||||
put(b'.', into, &mut written);
|
||||
put_word(program, into, &mut written);
|
||||
|
||||
written
|
||||
}
|
||||
|
||||
fn string_in_version(image: &dyn Image, key: &[u8], into: &mut [u8]) -> usize {
|
||||
let Some(resources) = resource_directory(image) else {
|
||||
return 0;
|
||||
};
|
||||
let Some(version) = resources.first_leaf(RT_VERSION) else {
|
||||
return 0;
|
||||
};
|
||||
let Some(blob) = read_blob(image, version.offset, version.size) else {
|
||||
return 0;
|
||||
};
|
||||
let Some(at) = find(blob, key) else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
// Each string in the block says how long its value is, in the six bytes before its key. A
|
||||
// value of nothing leaves the next key where the value would be.
|
||||
if at < 6 {
|
||||
return 0;
|
||||
}
|
||||
let value_length = u16::from_le_bytes([blob[at - 4], blob[at - 3]]) as usize;
|
||||
if value_length == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut at = at + key.len();
|
||||
while at % 4 != 0 {
|
||||
at += 1;
|
||||
}
|
||||
|
||||
let mut written = 0;
|
||||
while at + 1 < blob.len() && written < into.len() && written < value_length {
|
||||
let unit = blob[at] as u16 | ((blob[at + 1] as u16) << 8);
|
||||
if unit == 0 {
|
||||
break;
|
||||
}
|
||||
into[written] = if unit < 0x80 { unit as u8 } else { b'?' };
|
||||
written += 1;
|
||||
at += 2;
|
||||
}
|
||||
|
||||
written
|
||||
}
|
||||
|
||||
fn describe_ne(image: &dyn Image, into: &mut [u8]) -> usize {
|
||||
let headers = read_u32(image, DOS_HEADERS_OFFSET);
|
||||
let table = read_u32(image, headers + NE_NON_RESIDENT_NAMES_OFFSET);
|
||||
if table == 0 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut length = [0u8; 1];
|
||||
if image.read_at(table, &mut length) != 1 {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let wanted = core::cmp::min(length[0] as usize, into.len());
|
||||
image.read_at(table + 1, &mut into[..wanted]) as usize
|
||||
}
|
||||
|
||||
fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
|
||||
haystack.windows(needle.len()).position(|window| window == needle)
|
||||
}
|
||||
|
||||
fn is_best_of_its_size(image: &dyn Image, group: u32, count: u32, index: u32) -> bool {
|
||||
let mine = entry_shape(image, group, index);
|
||||
|
||||
(0..count).filter(|other| *other != index).all(|other| {
|
||||
let theirs = entry_shape(image, group, other);
|
||||
theirs.0 != mine.0 || theirs.1 != mine.1 || theirs.2 < mine.2
|
||||
|| (theirs.2 == mine.2 && other > index)
|
||||
})
|
||||
}
|
||||
|
||||
fn entry_shape(image: &dyn Image, group: u32, index: u32) -> (u8, u8, u16) {
|
||||
let entry = group + GROUP_ENTRIES_OFFSET + index * GROUP_ENTRY_SIZE;
|
||||
|
||||
(
|
||||
read_u8(image, entry + GROUP_ENTRY_WIDTH_OFFSET),
|
||||
read_u8(image, entry + GROUP_ENTRY_HEIGHT_OFFSET),
|
||||
read_u16(image, entry + GROUP_ENTRY_DEPTH_OFFSET),
|
||||
)
|
||||
}
|
||||
|
||||
// A resource has the address that it gets after the loader maps it. The section gives the
|
||||
// distance from that address to the position in the file.
|
||||
fn resource_directory(image: &dyn Image) -> Option<ResourceDirectory<'_>> {
|
||||
let headers = read_u32(image, DOS_HEADERS_OFFSET);
|
||||
if read_u32(image, headers) != PE_SIGNATURE {
|
||||
return None;
|
||||
}
|
||||
|
||||
let optional = headers + OPTIONAL_HEADER_OFFSET;
|
||||
let directory_rva = read_u32(image, optional + resource_directory_offset(image, optional));
|
||||
if directory_rva == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let sections = optional + read_u16(image, headers + OPTIONAL_HEADER_SIZE_OFFSET) as u32;
|
||||
let section_count = read_u16(image, headers + SECTION_COUNT_OFFSET) as u32;
|
||||
for index in 0..section_count {
|
||||
let section = sections + index * SECTION_SIZE;
|
||||
let virtual_address = read_u32(image, section + SECTION_RVA_OFFSET);
|
||||
let virtual_size = read_u32(image, section + SECTION_VIRTUAL_SIZE_OFFSET);
|
||||
if directory_rva >= virtual_address && directory_rva < virtual_address + virtual_size {
|
||||
let raw_data = read_u32(image, section + SECTION_RAW_DATA_OFFSET);
|
||||
return Some(ResourceDirectory {
|
||||
image,
|
||||
rva: directory_rva,
|
||||
offset: directory_rva - virtual_address + raw_data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
// The magic value gives the form of the optional header, and the two forms have different
|
||||
// widths before the data directories.
|
||||
fn resource_directory_offset(image: &dyn Image, optional: u32) -> u32 {
|
||||
if read_u16(image, optional) == PE32_MAGIC {
|
||||
return PE32_RESOURCE_DIRECTORY_OFFSET;
|
||||
}
|
||||
|
||||
PE32PLUS_RESOURCE_DIRECTORY_OFFSET
|
||||
}
|
||||
|
||||
struct ResourceDirectory<'i> {
|
||||
image: &'i dyn Image,
|
||||
rva: u32,
|
||||
offset: u32,
|
||||
}
|
||||
|
||||
struct Resource {
|
||||
offset: u32,
|
||||
size: u32,
|
||||
}
|
||||
|
||||
impl ResourceDirectory<'_> {
|
||||
fn first_leaf(&self, kind: u16) -> Option<Resource> {
|
||||
let by_name = self.subdirectory(self.offset, kind)?;
|
||||
let by_language = self.first_subdirectory(by_name)?;
|
||||
|
||||
self.leaf_at(self.first_subdirectory(by_language)?)
|
||||
}
|
||||
|
||||
fn leaf(&self, kind: u16, name: u16) -> Option<Resource> {
|
||||
let by_name = self.subdirectory(self.offset, kind)?;
|
||||
let by_language = self.subdirectory(by_name, name)?;
|
||||
|
||||
self.leaf_at(self.first_subdirectory(by_language)?)
|
||||
}
|
||||
|
||||
fn subdirectory(&self, directory: u32, id: u16) -> Option<u32> {
|
||||
for entry in self.entries(directory) {
|
||||
if read_u32(self.image, entry) == id as u32 {
|
||||
return Some(self.child(entry));
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn first_subdirectory(&self, directory: u32) -> Option<u32> {
|
||||
self.entries(directory).next().map(|e| self.child(e))
|
||||
}
|
||||
|
||||
fn entries(&self, directory: u32) -> impl Iterator<Item = u32> + '_ {
|
||||
let named = read_u16(self.image, directory + NAMED_ENTRY_COUNT_OFFSET) as u32;
|
||||
let identified = read_u16(self.image, directory + ID_ENTRY_COUNT_OFFSET) as u32;
|
||||
let first = directory + DIRECTORY_HEADER_SIZE + named * DIRECTORY_ENTRY_SIZE;
|
||||
|
||||
(0..identified).map(move |index| first + index * DIRECTORY_ENTRY_SIZE)
|
||||
}
|
||||
|
||||
fn child(&self, entry: u32) -> u32 {
|
||||
let value = read_u32(self.image, entry + DIRECTORY_ENTRY_CHILD_OFFSET);
|
||||
self.offset + (value & !SUBDIRECTORY_FLAG)
|
||||
}
|
||||
|
||||
fn leaf_at(&self, data_entry: u32) -> Option<Resource> {
|
||||
let data_rva = read_u32(self.image, data_entry);
|
||||
|
||||
Some(Resource {
|
||||
offset: self.offset + data_rva - self.rva,
|
||||
size: read_u32(self.image, data_entry + DATA_ENTRY_SIZE_OFFSET),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn read_u32(image: &dyn Image, position: u32) -> u32 {
|
||||
let mut bytes = [0u8; 4];
|
||||
image.read_at(position, &mut bytes);
|
||||
u32::from_le_bytes(bytes)
|
||||
}
|
||||
|
||||
fn read_u8(image: &dyn Image, position: u32) -> u8 {
|
||||
let mut bytes = [0u8; 1];
|
||||
image.read_at(position, &mut bytes);
|
||||
bytes[0]
|
||||
}
|
||||
|
||||
fn read_u16(image: &dyn Image, position: u32) -> u16 {
|
||||
let mut bytes = [0u8; 2];
|
||||
image.read_at(position, &mut bytes);
|
||||
u16::from_le_bytes(bytes)
|
||||
}
|
||||
|
||||
fn read_blob(image: &dyn Image, position: u32, size: u32) -> Option<&'static [u8]> {
|
||||
let blob = unsafe { &mut *core::ptr::addr_of_mut!(ICON_BUFFER) };
|
||||
if size as usize > blob.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let read = image.read_at(position, &mut blob[..size as usize]);
|
||||
if read != size {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(&blob[..size as usize])
|
||||
}
|
||||
|
||||
static mut ICON_BUFFER: [u8; MAX_ICON_SIZE] = [0; MAX_ICON_SIZE];
|
||||
|
||||
const MAX_ICON_SIZE: usize = 16 * 1024;
|
||||
|
||||
const DOS_HEADERS_OFFSET: u32 = 0x3c;
|
||||
const PE_SIGNATURE: u32 = 0x00004550;
|
||||
const SECTION_COUNT_OFFSET: u32 = 0x06;
|
||||
const OPTIONAL_HEADER_SIZE_OFFSET: u32 = 0x14;
|
||||
const OPTIONAL_HEADER_OFFSET: u32 = 0x18;
|
||||
const PE32_MAGIC: u16 = 0x010b;
|
||||
const PE32_RESOURCE_DIRECTORY_OFFSET: u32 = 0x70;
|
||||
const PE32PLUS_RESOURCE_DIRECTORY_OFFSET: u32 = 0x80;
|
||||
const SECTION_SIZE: u32 = 0x28;
|
||||
const SECTION_VIRTUAL_SIZE_OFFSET: u32 = 0x08;
|
||||
const SECTION_RVA_OFFSET: u32 = 0x0c;
|
||||
const SECTION_RAW_DATA_OFFSET: u32 = 0x14;
|
||||
|
||||
const RT_ICON: u16 = 3;
|
||||
const RT_GROUP_ICON: u16 = 14;
|
||||
const RT_VERSION: u16 = 16;
|
||||
const NE_SIGNATURE: u16 = 0x454e;
|
||||
const MAX_PROGRAM_WORD: usize = 24;
|
||||
const NE_NON_RESIDENT_NAMES_OFFSET: u32 = 0x2c;
|
||||
const COMPANY_NAME: &[u8] = b"C\0o\0m\0p\0a\0n\0y\0N\0a\0m\0e\0\0\0";
|
||||
const INTERNAL_NAME: &[u8] = b"I\0n\0t\0e\0r\0n\0a\0l\0N\0a\0m\0e\0\0\0";
|
||||
const PRODUCT_NAME: &[u8] = b"P\0r\0o\0d\0u\0c\0t\0N\0a\0m\0e\0\0\0";
|
||||
const ORIGINAL_FILENAME: &[u8] =
|
||||
b"O\0r\0i\0g\0i\0n\0a\0l\0F\0i\0l\0e\0n\0a\0m\0e\0\0\0";
|
||||
|
||||
const FILE_DESCRIPTION: &[u8] = b"F\0i\0l\0e\0D\0e\0s\0c\0r\0i\0p\0t\0i\0o\0n\0\0\0";
|
||||
const NAMED_ENTRY_COUNT_OFFSET: u32 = 0x0c;
|
||||
const ID_ENTRY_COUNT_OFFSET: u32 = 0x0e;
|
||||
const DIRECTORY_HEADER_SIZE: u32 = 0x10;
|
||||
const DIRECTORY_ENTRY_SIZE: u32 = 0x08;
|
||||
const DIRECTORY_ENTRY_CHILD_OFFSET: u32 = 0x04;
|
||||
const SUBDIRECTORY_FLAG: u32 = 0x8000_0000;
|
||||
const DATA_ENTRY_SIZE_OFFSET: u32 = 0x04;
|
||||
const GROUP_COUNT_OFFSET: u32 = 0x04;
|
||||
const GROUP_ENTRIES_OFFSET: u32 = 0x06;
|
||||
const GROUP_ENTRY_SIZE: u32 = 0x0e;
|
||||
const GROUP_ENTRY_WIDTH_OFFSET: u32 = 0x00;
|
||||
const GROUP_ENTRY_HEIGHT_OFFSET: u32 = 0x01;
|
||||
const GROUP_ENTRY_DEPTH_OFFSET: u32 = 0x06;
|
||||
const GROUP_ENTRY_ID_OFFSET: u32 = 0x0c;
|
||||
@@ -1,139 +0,0 @@
|
||||
// Facade over the host kernel the agent is running inside of. Both backends
|
||||
// expose the same primitives, so the rest of the agent never names a specific
|
||||
// kernel.
|
||||
//
|
||||
// A kernel is reached from the outside -- a remote stub injects the agent and
|
||||
// patches the addresses it needs into .kernel_addrs -- or from the inside,
|
||||
// where the agent is a kernel module whose glue is an ordinary C translation
|
||||
// unit compiled against the target kernel's headers. Linux is reached either
|
||||
// way; the others only from the outside.
|
||||
|
||||
#[cfg(any(feature = "linux", feature = "linux-injected"))]
|
||||
pub use crate::linux::*;
|
||||
#[cfg(feature = "win9x")]
|
||||
pub use crate::win9x::*;
|
||||
#[cfg(feature = "winnt")]
|
||||
pub use crate::winnt::*;
|
||||
#[cfg(feature = "xnu-core")]
|
||||
pub use crate::xnu::*;
|
||||
#[cfg(feature = "xnu-core")]
|
||||
pub use crate::xnu_applications::*;
|
||||
#[cfg(feature = "xnu-core")]
|
||||
pub use crate::xnu_bell::*;
|
||||
#[cfg(feature = "xnu-core")]
|
||||
pub use crate::xnu_hiding::*;
|
||||
#[cfg(feature = "xnu-core")]
|
||||
pub use crate::xnu_injection::*;
|
||||
#[cfg(feature = "xnu-core")]
|
||||
pub use crate::xnu_processes::*;
|
||||
#[cfg(feature = "xnu-core")]
|
||||
pub use crate::xnu_relay::*;
|
||||
#[cfg(feature = "xnu-core")]
|
||||
pub use crate::xnu_spawn::*;
|
||||
|
||||
use core::ffi::c_void;
|
||||
|
||||
pub type ThreadEntry = unsafe extern "C" fn(parameter: *mut c_void, wait_result: i32);
|
||||
|
||||
pub struct ThreadInfo {
|
||||
pub id: u32,
|
||||
pub state: Option<crate::bindings::GumThreadState>,
|
||||
pub cpu_state: Option<CpuState>,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub struct CpuState {
|
||||
pub rip: u64,
|
||||
pub r15: u64,
|
||||
pub r14: u64,
|
||||
pub r13: u64,
|
||||
pub r12: u64,
|
||||
pub r11: u64,
|
||||
pub r10: u64,
|
||||
pub r9: u64,
|
||||
pub r8: u64,
|
||||
pub rdi: u64,
|
||||
pub rsi: u64,
|
||||
pub rbp: u64,
|
||||
pub rsp: u64,
|
||||
pub rbx: u64,
|
||||
pub rdx: u64,
|
||||
pub rcx: u64,
|
||||
pub rax: u64,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub struct CpuState {
|
||||
pub pc: u32,
|
||||
pub sp: u32,
|
||||
pub cpsr: u32,
|
||||
pub r: [u32; 8],
|
||||
pub r8: u32,
|
||||
pub r9: u32,
|
||||
pub r10: u32,
|
||||
pub r11: u32,
|
||||
pub r12: u32,
|
||||
pub lr: u32,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub struct CpuState {
|
||||
pub pc: u64,
|
||||
pub sp: u64,
|
||||
pub nzcv: u64,
|
||||
pub x: [u64; 29],
|
||||
pub fp: u64,
|
||||
pub lr: u64,
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub struct CpuState {
|
||||
pub eip: u32,
|
||||
pub edi: u32,
|
||||
pub esi: u32,
|
||||
pub ebp: u32,
|
||||
pub esp: u32,
|
||||
pub ebx: u32,
|
||||
pub edx: u32,
|
||||
pub ecx: u32,
|
||||
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) {
|
||||
unsafe { noted_numbers() }.push((alloc::string::String::from(what), number));
|
||||
}
|
||||
|
||||
pub fn noted(what: &str) -> Option<u64> {
|
||||
unsafe { noted_numbers() }
|
||||
.iter()
|
||||
.find(|(name, _)| name == what)
|
||||
.map(|(_, number)| *number)
|
||||
}
|
||||
|
||||
pub fn take_note_of_mapping(address: u64, size: usize, protection: u32) {
|
||||
unsafe { mappings() }.push((address, size, protection));
|
||||
}
|
||||
|
||||
pub fn noted_mappings() -> &'static [(u64, usize, u32)] {
|
||||
unsafe { mappings() }.as_slice()
|
||||
}
|
||||
|
||||
unsafe fn mappings() -> &'static mut alloc::vec::Vec<(u64, usize, u32)> {
|
||||
unsafe { (&raw mut MAPPINGS).as_mut().unwrap() }
|
||||
}
|
||||
|
||||
static mut MAPPINGS: alloc::vec::Vec<(u64, usize, u32)> = alloc::vec::Vec::new();
|
||||
|
||||
unsafe fn noted_numbers() -> &'static mut alloc::vec::Vec<(alloc::string::String, u64)> {
|
||||
unsafe { (&raw mut NOTED).as_mut().unwrap() }
|
||||
}
|
||||
|
||||
static mut NOTED: alloc::vec::Vec<(alloc::string::String, u64)> = alloc::vec::Vec::new();
|
||||
File diff suppressed because it is too large.
Load diff
+81
-520
@@ -1,185 +1,35 @@
|
||||
// The C library's lower edge. Nothing here does real work: the agent has no
|
||||
// filesystem and never exits, so these exist to satisfy references and to give
|
||||
// malloc some memory to hand out.
|
||||
//
|
||||
// The two flavours link against different C libraries, and they disagree on what
|
||||
// this layer is called. newlib asks for the underscore-prefixed syscall names;
|
||||
// picolibc, which the soft-float flavour needs because newlib has no soft-float
|
||||
// build for arm64, calls them without the prefix. The bodies are shared and the
|
||||
// exported names live in the two `exports` modules at the end.
|
||||
|
||||
use core::ptr;
|
||||
use core::sync::atomic::Ordering;
|
||||
|
||||
use crate::bindings::{GMutex, GRecMutex};
|
||||
use crate::gthread;
|
||||
|
||||
// Serialises the C library. Which hooks the library reaches for differs: newlib asks
|
||||
// for __malloc_lock/__malloc_unlock, picolibc for the retargetable lock API below,
|
||||
// whose default implementation is an empty function — so on picolibc malloc runs
|
||||
// unlocked unless we supply these, and any second thread corrupts the heap.
|
||||
static mut LIBC_LOCK: GRecMutex = unsafe { core::mem::zeroed() };
|
||||
static mut MALLOC_LOCK: GRecMutex = unsafe { core::mem::zeroed() };
|
||||
|
||||
fn libc_lock() {
|
||||
gthread::g_rec_mutex_lock(ptr::addr_of_mut!(LIBC_LOCK));
|
||||
}
|
||||
|
||||
fn libc_unlock() {
|
||||
gthread::g_rec_mutex_unlock(ptr::addr_of_mut!(LIBC_LOCK));
|
||||
offer_preemption_point();
|
||||
}
|
||||
|
||||
fn offer_preemption_point() {
|
||||
let total = ALLOCATIONS_TOTAL.fetch_add(1, Ordering::Relaxed) + 1;
|
||||
if total % ALLOCATIONS_PER_YIELD == 0 {
|
||||
crate::kernel::yield_now();
|
||||
}
|
||||
}
|
||||
|
||||
static ALLOCATIONS_TOTAL: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
|
||||
const ALLOCATIONS_PER_YIELD: u32 = 1024;
|
||||
|
||||
// GLib's g_get_num_processors() probes this; report a single CPU so the
|
||||
// parallel scanners fall back to their inline path.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn sysconf(_name: i32) -> isize {
|
||||
1
|
||||
pub extern "C" fn __malloc_lock(_reent: *mut core::ffi::c_void) {
|
||||
gthread::g_rec_mutex_lock(ptr::addr_of_mut!(MALLOC_LOCK));
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __clear_cache(_start: *const u8, _end: *const u8) {}
|
||||
|
||||
#[cfg(all(target_arch = "arm", not(any(feature = "linux", feature = "linux-injected"))))]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __clear_cache(start: *const u8, end: *const u8) {
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"push {{r7}}",
|
||||
"mov r7, {number}",
|
||||
"svc #0",
|
||||
"pop {{r7}}",
|
||||
number = in(reg) 0xf0002usize,
|
||||
in("r0") start,
|
||||
in("r1") end,
|
||||
in("r2") 0,
|
||||
options(nostack),
|
||||
);
|
||||
}
|
||||
pub extern "C" fn __malloc_unlock(_reent: *mut core::ffi::c_void) {
|
||||
gthread::g_rec_mutex_unlock(ptr::addr_of_mut!(MALLOC_LOCK));
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "arm", any(feature = "linux", feature = "linux-injected")))]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __clear_cache(start: *const u8, end: *const u8) {
|
||||
unsafe {
|
||||
let told: u32;
|
||||
core::arch::asm!("mrc p15, 0, {}, c0, c0, 1", out(reg) told, options(nomem, nostack));
|
||||
|
||||
let data_line = 4usize << ((told >> 16) & 0xf);
|
||||
let code_line = 4usize << (told & 0xf);
|
||||
|
||||
let mut at = (start as usize) & !(data_line - 1);
|
||||
while at < end as usize {
|
||||
core::arch::asm!("mcr p15, 0, {}, c7, c11, 1", in(reg) at, options(nostack));
|
||||
at += data_line;
|
||||
}
|
||||
core::arch::asm!("dsb ish", options(nostack));
|
||||
|
||||
let mut at = (start as usize) & !(code_line - 1);
|
||||
while at < end as usize {
|
||||
core::arch::asm!("mcr p15, 0, {}, c7, c5, 1", in(reg) at, options(nostack));
|
||||
at += code_line;
|
||||
}
|
||||
core::arch::asm!("dsb ish", "isb", options(nostack));
|
||||
}
|
||||
pub extern "C" fn _fini() {
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __aeabi_uread4(address: *const u8) -> u32 {
|
||||
let mut bytes = [0u8; 4];
|
||||
for i in 0..4 {
|
||||
bytes[i] = unsafe { address.add(i).read_volatile() };
|
||||
}
|
||||
u32::from_ne_bytes(bytes)
|
||||
pub extern "C" fn _exit(_status: i32) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __aeabi_uread8(address: *const u8) -> u64 {
|
||||
let mut bytes = [0u8; 8];
|
||||
for i in 0..8 {
|
||||
bytes[i] = unsafe { address.add(i).read_volatile() };
|
||||
}
|
||||
u64::from_ne_bytes(bytes)
|
||||
pub extern "C" fn _kill(_pid: i32, _sig: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __aeabi_uwrite4(value: u32, address: *mut u8) -> u32 {
|
||||
let bytes = value.to_ne_bytes();
|
||||
for i in 0..4 {
|
||||
unsafe { address.add(i).write_volatile(bytes[i]) };
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __aeabi_uwrite8(value: u64, address: *mut u8) -> u64 {
|
||||
let bytes = value.to_ne_bytes();
|
||||
for i in 0..8 {
|
||||
unsafe { address.add(i).write_volatile(bytes[i]) };
|
||||
}
|
||||
value
|
||||
}
|
||||
|
||||
// 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(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"))]
|
||||
let told: u64 = {
|
||||
let read: u64;
|
||||
unsafe { core::arch::asm!("mrs {}, ctr_el0", out(reg) read, options(nomem, nostack)) };
|
||||
read
|
||||
};
|
||||
|
||||
let data_line = 4usize << ((told >> 16) & 0xf);
|
||||
let code_line = 4usize << (told & 0xf);
|
||||
let data_is_coherent = (told >> 28) & 1 != 0;
|
||||
let code_is_coherent = (told >> 29) & 1 != 0;
|
||||
|
||||
if !data_is_coherent {
|
||||
let mut at = start as usize & !(data_line - 1);
|
||||
while at < end as usize {
|
||||
unsafe { core::arch::asm!("dc cvau, {}", in(reg) at, options(nostack)) };
|
||||
at += data_line;
|
||||
}
|
||||
}
|
||||
unsafe { core::arch::asm!("dsb ish", options(nomem, nostack)) };
|
||||
|
||||
if !code_is_coherent {
|
||||
let mut at = start as usize & !(code_line - 1);
|
||||
while at < end as usize {
|
||||
unsafe { core::arch::asm!("ic ivau, {}", in(reg) at, options(nostack)) };
|
||||
at += code_line;
|
||||
}
|
||||
unsafe { core::arch::asm!("dsb ish", options(nomem, nostack)) };
|
||||
}
|
||||
|
||||
unsafe { core::arch::asm!("isb", options(nomem, nostack)) };
|
||||
}
|
||||
|
||||
fn sbrk_impl(incr: isize) -> *mut u8 {
|
||||
pub extern "C" fn _sbrk(incr: isize) -> *mut u8 {
|
||||
const HEAP_SIZE: usize = 32 * 1024 * 1024; // 32 MB
|
||||
|
||||
static mut HEAP_START: *mut u8 = ptr::null_mut();
|
||||
@@ -190,7 +40,7 @@ fn sbrk_impl(incr: isize) -> *mut u8 {
|
||||
gthread::g_mutex_lock(ptr::addr_of_mut!(HEAP_MUTEX));
|
||||
|
||||
if HEAP_START.is_null() {
|
||||
HEAP_START = crate::kernel::alloc_heap(HEAP_SIZE);
|
||||
HEAP_START = crate::xnu::kalloc(HEAP_SIZE);
|
||||
if HEAP_START.is_null() {
|
||||
gthread::g_mutex_unlock(ptr::addr_of_mut!(HEAP_MUTEX));
|
||||
panic!("Failed to allocate 32 MB heap");
|
||||
@@ -199,8 +49,7 @@ fn sbrk_impl(incr: isize) -> *mut u8 {
|
||||
}
|
||||
|
||||
let prev_heap_current = HEAP_CURRENT;
|
||||
// Signed: the allocator hands memory back by asking for a negative increment.
|
||||
let new_heap_current = HEAP_CURRENT.offset(incr);
|
||||
let new_heap_current = HEAP_CURRENT.add(incr as usize);
|
||||
|
||||
if new_heap_current > HEAP_START.add(HEAP_SIZE) {
|
||||
gthread::g_mutex_unlock(ptr::addr_of_mut!(HEAP_MUTEX));
|
||||
@@ -216,14 +65,20 @@ fn sbrk_impl(incr: isize) -> *mut u8 {
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _getpid() -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct Timeval {
|
||||
tv_sec: i64,
|
||||
tv_usec: i64,
|
||||
}
|
||||
|
||||
fn gettimeofday_impl(tp: *mut core::ffi::c_void) -> i32 {
|
||||
let (secs, microsecs) = crate::kernel::wall_clock_micros();
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _gettimeofday(tp: *mut core::ffi::c_void, _tzp: *mut core::ffi::c_void) -> i32 {
|
||||
let (secs, microsecs) = crate::xnu::clock_get_calendar_microtime();
|
||||
let timeval = Timeval {
|
||||
tv_sec: secs as i64,
|
||||
tv_usec: microsecs as i64,
|
||||
@@ -238,362 +93,68 @@ fn gettimeofday_impl(tp: *mut core::ffi::c_void) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
fn write_impl(count: usize) -> isize {
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _isatty(_fd: i32) -> i32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _open(_path: *const u8, _flags: i32, _mode: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _close(_fd: i32) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _read(_fd: i32, _buf: *mut u8, _count: usize) -> isize {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _write(_fd: i32, _buf: *const u8, count: usize) -> isize {
|
||||
count as isize
|
||||
}
|
||||
|
||||
mod exports {
|
||||
use super::*;
|
||||
|
||||
// The C library's own allocator sits on a fixed sbrk heap and carries its own
|
||||
// locking; inside a kernel module the kernel's allocator is already there, already
|
||||
// safe to share between threads, and not capped at a size we picked up front.
|
||||
// Every C allocation goes here instead.
|
||||
//
|
||||
// The sixteen bytes before the returned pointer hold the block the kernel gave us
|
||||
// and its total size, so a pointer that was moved forward to satisfy an alignment
|
||||
// request frees exactly like one that was not.
|
||||
use core::ffi::c_void;
|
||||
|
||||
const HEAP_HEADER_SIZE: usize = 16;
|
||||
|
||||
const MALLOC_ALIGNMENT: usize = 2 * core::mem::size_of::<*const u8>();
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn malloc(size: usize) -> *mut c_void {
|
||||
let total = size + HEAP_HEADER_SIZE + MALLOC_ALIGNMENT;
|
||||
let block = crate::kernel::alloc(total);
|
||||
if block.is_null() {
|
||||
return ptr::null_mut();
|
||||
}
|
||||
|
||||
offer_preemption_point();
|
||||
|
||||
unsafe {
|
||||
let lowest = block.add(HEAP_HEADER_SIZE) as usize;
|
||||
let aligned = (lowest + MALLOC_ALIGNMENT - 1) & !(MALLOC_ALIGNMENT - 1);
|
||||
attach_header(block, total, aligned as *mut u8)
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn posix_memalign(
|
||||
result: *mut *mut c_void,
|
||||
alignment: usize,
|
||||
size: usize,
|
||||
) -> i32 {
|
||||
let total = size + alignment + HEAP_HEADER_SIZE;
|
||||
let block = crate::kernel::alloc(total);
|
||||
if block.is_null() {
|
||||
return 12; // ENOMEM
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let lowest = block.add(HEAP_HEADER_SIZE) as usize;
|
||||
let aligned = (lowest + alignment - 1) & !(alignment - 1);
|
||||
*result = attach_header(block, total, aligned as *mut u8);
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn free(p: *mut c_void) {
|
||||
if p.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let (block, total) = header_of(p);
|
||||
crate::kernel::free(block, total);
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn malloc_usable_size(p: *mut c_void) -> usize {
|
||||
unsafe { usable_size(p) }
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn calloc(count: usize, size: usize) -> *mut c_void {
|
||||
let total = count * size;
|
||||
let p = malloc(total);
|
||||
if !p.is_null() {
|
||||
unsafe { ptr::write_bytes(p as *mut u8, 0, total) };
|
||||
}
|
||||
p
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn realloc(p: *mut c_void, size: usize) -> *mut c_void {
|
||||
if p.is_null() {
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
let previous_size = unsafe { usable_size(p) };
|
||||
let grown = malloc(size);
|
||||
if !grown.is_null() {
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(p as *const u8, grown as *mut u8, previous_size.min(size))
|
||||
};
|
||||
free(p);
|
||||
}
|
||||
grown
|
||||
}
|
||||
|
||||
unsafe fn attach_header(block: *mut u8, total: usize, p: *mut u8) -> *mut c_void {
|
||||
unsafe {
|
||||
let header = p.sub(HEAP_HEADER_SIZE) as *mut usize;
|
||||
*header = block as usize;
|
||||
*header.add(1) = total;
|
||||
}
|
||||
p as *mut c_void
|
||||
}
|
||||
|
||||
unsafe fn header_of(p: *mut c_void) -> (*mut u8, usize) {
|
||||
unsafe {
|
||||
let header = (p as *mut u8).sub(HEAP_HEADER_SIZE) as *const usize;
|
||||
(*header as *mut u8, *header.add(1))
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn usable_size(p: *mut c_void) -> usize {
|
||||
unsafe {
|
||||
let (block, total) = header_of(p);
|
||||
total - (p as usize - block as usize)
|
||||
}
|
||||
}
|
||||
|
||||
// One recursive mutex covers every lock the C library asks for. They are coarse
|
||||
// and few, and over-serialising them is safe where getting it wrong is not.
|
||||
type Lock = *mut core::ffi::c_void;
|
||||
|
||||
// The lock object itself, which we define so the C library's own no-op lock
|
||||
// implementation is never pulled out of its archive. Its contents are never
|
||||
// read: the address is just a token, and one mutex covers them all.
|
||||
// Nothing here references it, so without #[used] it is dropped before the linker
|
||||
// ever sees it, and the archive member arrives after all to supply it.
|
||||
#[allow(non_upper_case_globals)]
|
||||
#[unsafe(no_mangle)]
|
||||
#[used]
|
||||
pub static mut __lock___libc_recursive_mutex: u8 = 0;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __retarget_lock_init(_lock: *mut Lock) {}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __retarget_lock_init_recursive(_lock: *mut Lock) {}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __retarget_lock_close(_lock: Lock) {}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __retarget_lock_close_recursive(_lock: Lock) {}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __retarget_lock_acquire(_lock: Lock) {
|
||||
libc_lock();
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __retarget_lock_acquire_recursive(_lock: Lock) {
|
||||
libc_lock();
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __retarget_lock_release(_lock: Lock) {
|
||||
libc_unlock();
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __retarget_lock_release_recursive(_lock: Lock) {
|
||||
libc_unlock();
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn exit(_status: i32) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _exit(_status: i32) -> ! {
|
||||
loop {}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn kill(_pid: i32, _sig: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn sbrk(incr: isize) -> *mut u8 {
|
||||
sbrk_impl(incr)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn getpid() -> i32 {
|
||||
crate::kernel::current_process_id() as i32
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gettimeofday(
|
||||
tp: *mut core::ffi::c_void,
|
||||
_tzp: *mut core::ffi::c_void,
|
||||
) -> i32 {
|
||||
gettimeofday_impl(tp)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn isatty(_fd: i32) -> i32 {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn open(_path: *const u8, _flags: i32, _mode: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn close(_fd: i32) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn read(_fd: i32, _buf: *mut u8, _count: usize) -> isize {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn write(_fd: i32, _buf: *const u8, count: usize) -> isize {
|
||||
write_impl(count)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn lseek(_fd: i32, _offset: isize, _whence: i32) -> isize {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn fstat(_fd: i32, _st: *mut core::ffi::c_void) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn stat(_path: *const u8, _st: *mut core::ffi::c_void) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn unlink(_path: *const u8) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn rename(_old: *const u8, _new: *const u8) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn access(_path: *const u8, _mode: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn chmod(_path: *const u8, _mode: i32) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn chdir(_path: *const u8) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn rmdir(_path: *const u8) -> i32 {
|
||||
-1
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _lseek(_fd: i32, _offset: isize, _whence: i32) -> isize {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _fstat(_fd: i32, _st: *mut core::ffi::c_void) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _link(_old: *const u8, _new: *const u8) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _unlink(_path: *const u8) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
// GLib's g_get_num_processors() probes this; report a single CPU so the
|
||||
// parallel scanners fall back to their inline path.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn sysconf(_name: i32) -> isize {
|
||||
1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn __clear_cache(_start: *const u8, _end: *const u8) {
|
||||
// On AArch64, we need to flush the instruction cache
|
||||
// This is a simplified implementation for the kernel environment
|
||||
unsafe {
|
||||
core::arch::asm!(
|
||||
"ic iallu", // Invalidate all instruction caches
|
||||
"dsb sy", // Data synchronization barrier
|
||||
"isb", // Instruction synchronization barrier
|
||||
options(nomem, nostack),
|
||||
);
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn gethostname(_name: *mut u8, _len: usize) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
pub struct Timespec {
|
||||
tv_sec: i64,
|
||||
tv_nsec: i64,
|
||||
}
|
||||
|
||||
const CLOCK_MONOTONIC: i32 = 1;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn clock_gettime(clock_id: i32, tp: *mut Timespec) -> i32 {
|
||||
let (secs, nsecs) = if clock_id == CLOCK_MONOTONIC {
|
||||
let micros = crate::kernel::monotonic_micros();
|
||||
(micros / 1_000_000, (micros % 1_000_000) * 1000)
|
||||
} else {
|
||||
let (secs, micros) = crate::kernel::wall_clock_micros();
|
||||
(secs as i64, (micros as i64) * 1000)
|
||||
};
|
||||
|
||||
unsafe {
|
||||
(*tp).tv_sec = secs;
|
||||
(*tp).tv_nsec = nsecs;
|
||||
}
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
// picolibc consults this for errno values it does not know itself.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn _user_strerror(_errnum: i32) -> *const core::ffi::c_char {
|
||||
ptr::null()
|
||||
}
|
||||
|
||||
// No dynamic loader here. Gum calls dladdr() to describe an address and already
|
||||
// falls back to printing it raw when the lookup fails.
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn dladdr(_address: *const core::ffi::c_void, _info: *mut core::ffi::c_void) -> i32 {
|
||||
0
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn dlopen(_file: *const u8, _mode: i32) -> *mut core::ffi::c_void {
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn dlsym(
|
||||
_handle: *mut core::ffi::c_void,
|
||||
_name: *const u8,
|
||||
) -> *mut core::ffi::c_void {
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn dlclose(_handle: *mut core::ffi::c_void) -> i32 {
|
||||
-1
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn dlerror() -> *mut core::ffi::c_char {
|
||||
ptr::null_mut()
|
||||
}
|
||||
|
||||
// picolibc declares the standard streams and leaves defining them to the
|
||||
// application. The agent has no I/O — its logging goes to the kernel's own log —
|
||||
// so nothing should ever reach for one of these.
|
||||
#[repr(transparent)]
|
||||
pub struct Stream(*mut core::ffi::c_void);
|
||||
|
||||
unsafe impl Sync for Stream {}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static stdin: Stream = Stream(ptr::null_mut());
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static stdout: Stream = Stream(ptr::null_mut());
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static stderr: Stream = Stream(ptr::null_mut());
|
||||
}
|
||||
@@ -1,237 +0,0 @@
|
||||
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
|
||||
// each side is told only where it begins.
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Arena {
|
||||
begins: usize,
|
||||
}
|
||||
|
||||
impl Arena {
|
||||
pub fn at(begins: usize) -> Self {
|
||||
Arena { begins }
|
||||
}
|
||||
|
||||
// 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();
|
||||
let length = said.iter().position(|byte| *byte == 0).unwrap_or(said.len());
|
||||
let kept = length.min(SAID_SIZE - 1);
|
||||
|
||||
unsafe {
|
||||
core::ptr::copy_nonoverlapping(said.as_ptr(), (self.begins + SAID) as *mut u8, kept);
|
||||
((self.begins + SAID + kept) as *mut u8).write(0);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pub fn leave(&self, offset: usize, value: u64) {
|
||||
unsafe { ((self.begins + offset) as *mut u64).write_volatile(value) };
|
||||
}
|
||||
|
||||
pub fn note(&self, step: u32) {
|
||||
self.word(PROGRESS).store(step, Ordering::Release);
|
||||
}
|
||||
|
||||
pub fn home(&self) -> u32 {
|
||||
self.word(HOME).load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub fn report_home(&self) {
|
||||
self.word(REPORTED).store(self.word(HOME).load(Ordering::Acquire), Ordering::Release);
|
||||
}
|
||||
|
||||
pub fn reachable_at(&self, says: u32, hears: u32) {
|
||||
self.word(SAYS).store(says, Ordering::Release);
|
||||
self.word(HEARS).store(hears, Ordering::Release);
|
||||
}
|
||||
|
||||
pub fn note_a_thread(&self, thread: u32, is_gone: bool) {
|
||||
let head = self.word(THREADS_HEAD).load(Ordering::Relaxed);
|
||||
let tail = self.word(THREADS_TAIL).load(Ordering::Acquire);
|
||||
if head.wrapping_sub(tail) == THREAD_SLOTS {
|
||||
return;
|
||||
}
|
||||
|
||||
let slot = (head % THREAD_SLOTS) as usize;
|
||||
self.word(THREADS + (slot * 4)).store((thread << 1) | is_gone as u32, Ordering::Relaxed);
|
||||
self.word(THREADS_HEAD).store(head.wrapping_add(1), Ordering::Release);
|
||||
}
|
||||
|
||||
pub fn take_a_thread(&self) -> Option<(u32, bool)> {
|
||||
let head = self.word(THREADS_HEAD).load(Ordering::Acquire);
|
||||
let tail = self.word(THREADS_TAIL).load(Ordering::Relaxed);
|
||||
if head == tail {
|
||||
return None;
|
||||
}
|
||||
|
||||
let slot = (tail % THREAD_SLOTS) as usize;
|
||||
let noted = self.word(THREADS + (slot * 4)).load(Ordering::Relaxed);
|
||||
self.word(THREADS_TAIL).store(tail.wrapping_add(1), Ordering::Release);
|
||||
|
||||
Some((noted >> 1, noted & 1 != 0))
|
||||
}
|
||||
|
||||
pub fn holds_a_thread(&self) -> bool {
|
||||
self.word(THREADS_HEAD).load(Ordering::Acquire)
|
||||
!= self.word(THREADS_TAIL).load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
pub fn tell_it_to_go(&self) {
|
||||
self.word(GO).store(1, Ordering::Release);
|
||||
}
|
||||
|
||||
pub fn was_told_to_go(&self) -> bool {
|
||||
self.word(GO).load(Ordering::Acquire) != 0
|
||||
}
|
||||
|
||||
pub fn gone(&self) {
|
||||
self.word(GONE).store(1, Ordering::Release);
|
||||
}
|
||||
|
||||
pub fn has_gone(&self) -> bool {
|
||||
self.word(GONE).load(Ordering::Acquire) != 0
|
||||
}
|
||||
|
||||
pub fn says_through(&self) -> u32 {
|
||||
self.word(SAYS).load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub fn hears_through(&self) -> u32 {
|
||||
self.word(HEARS).load(Ordering::Acquire)
|
||||
}
|
||||
|
||||
pub fn tell_the_kernel_half(&self) {
|
||||
self.word(TO_KERNEL).fetch_add(1, Ordering::AcqRel);
|
||||
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) }
|
||||
}
|
||||
}
|
||||
|
||||
pub const PROGRESS: usize = 20;
|
||||
pub const PAGE_SIZE: usize = 48;
|
||||
pub const FAULT_KIND: usize = 24;
|
||||
pub const FAULT_ADDRESS: usize = 32;
|
||||
pub const FAULT_PC: usize = 40;
|
||||
pub const FAULT_LR: usize = 56;
|
||||
pub const SAID: usize = 64;
|
||||
pub const GO: usize = 288;
|
||||
pub const GONE: usize = 292;
|
||||
const THREADS_HEAD: usize = 296;
|
||||
const THREADS_TAIL: usize = 300;
|
||||
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;
|
||||
@@ -1,143 +0,0 @@
|
||||
use core::ffi::c_void;
|
||||
|
||||
use crate::kernel::ThreadEntry;
|
||||
|
||||
pub fn log(msg: &str) {
|
||||
(primitives().log)(msg)
|
||||
}
|
||||
|
||||
pub fn panic(msg: &str) -> ! {
|
||||
(primitives().panic)(msg)
|
||||
}
|
||||
|
||||
pub fn alloc(size: usize) -> *mut u8 {
|
||||
(primitives().alloc)(size)
|
||||
}
|
||||
|
||||
pub fn free(ptr: *mut u8, size: usize) {
|
||||
(primitives().free)(ptr, size)
|
||||
}
|
||||
|
||||
pub fn alloc_heap(size: usize) -> *mut u8 {
|
||||
(primitives().alloc_heap)(size)
|
||||
}
|
||||
|
||||
pub fn alloc_code(size: usize) -> *mut u8 {
|
||||
(primitives().alloc_code)(size)
|
||||
}
|
||||
|
||||
pub fn free_code(ptr: *mut u8, size: usize) {
|
||||
(primitives().free_code)(ptr, size)
|
||||
}
|
||||
|
||||
pub fn page_size() -> usize {
|
||||
(primitives().page_size)()
|
||||
}
|
||||
|
||||
pub fn protect(address: u64, size: usize, protection: u32) -> bool {
|
||||
(primitives().protect)(address, size, protection)
|
||||
}
|
||||
|
||||
pub fn spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> isize {
|
||||
(primitives().spawn_thread)(entry, parameter)
|
||||
}
|
||||
|
||||
pub fn wait(token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut() -> bool) {
|
||||
(primitives().wait)(token, timeout_us, check)
|
||||
}
|
||||
|
||||
pub fn wake(token: *const u8) {
|
||||
(primitives().wake)(token)
|
||||
}
|
||||
|
||||
pub fn yield_now() {
|
||||
(primitives().yield_now)()
|
||||
}
|
||||
|
||||
pub fn monotonic_micros() -> i64 {
|
||||
(primitives().monotonic_micros)()
|
||||
}
|
||||
|
||||
pub fn wall_clock_micros() -> (u32, u32) {
|
||||
(primitives().wall_clock_micros)()
|
||||
}
|
||||
|
||||
pub fn home_process_id() -> u32 {
|
||||
(primitives().home_process_id)()
|
||||
}
|
||||
|
||||
pub fn current_process_id() -> u32 {
|
||||
(primitives().current_process_id)()
|
||||
}
|
||||
|
||||
pub fn current_thread_id() -> u64 {
|
||||
(primitives().current_thread_id)()
|
||||
}
|
||||
|
||||
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 };
|
||||
}
|
||||
|
||||
pub fn in_copy() -> bool {
|
||||
core::ptr::eq(primitives(), &super::user::USER)
|
||||
}
|
||||
|
||||
fn primitives() -> &'static Primitives {
|
||||
unsafe { ACTIVE }
|
||||
}
|
||||
|
||||
static mut ACTIVE: &Primitives = &KERNEL;
|
||||
|
||||
static KERNEL: Primitives = Primitives {
|
||||
log: super::native::log,
|
||||
panic: super::native::panic,
|
||||
alloc: super::native::alloc,
|
||||
free: super::native::free,
|
||||
alloc_heap: super::native::alloc_heap,
|
||||
alloc_code: super::native::alloc_code,
|
||||
free_code: super::native::free_code,
|
||||
page_size: super::native::page_size,
|
||||
protect: super::native::protect,
|
||||
spawn_thread: super::native::spawn_thread,
|
||||
wait: super::native::wait,
|
||||
wake: super::native::wake,
|
||||
yield_now: super::native::yield_now,
|
||||
monotonic_micros: super::native::monotonic_micros,
|
||||
wall_clock_micros: super::native::wall_clock_micros,
|
||||
home_process_id: super::native::current_process_id,
|
||||
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 {
|
||||
pub log: fn(&str),
|
||||
pub panic: fn(&str) -> !,
|
||||
pub alloc: fn(usize) -> *mut u8,
|
||||
pub free: fn(*mut u8, usize),
|
||||
pub alloc_heap: fn(usize) -> *mut u8,
|
||||
pub alloc_code: fn(usize) -> *mut u8,
|
||||
pub free_code: fn(*mut u8, usize),
|
||||
pub page_size: fn() -> usize,
|
||||
pub protect: fn(u64, usize, u32) -> bool,
|
||||
pub spawn_thread: fn(ThreadEntry, *mut c_void) -> isize,
|
||||
pub wait: fn(*const u8, Option<u64>, &mut dyn FnMut() -> bool),
|
||||
pub wake: fn(*const u8),
|
||||
pub yield_now: fn(),
|
||||
pub monotonic_micros: fn() -> i64,
|
||||
pub wall_clock_micros: fn() -> (u32, u32),
|
||||
pub home_process_id: fn() -> u32,
|
||||
pub current_process_id: fn() -> u32,
|
||||
pub current_thread_id: fn() -> u64,
|
||||
pub install_fault_reporter: fn(),
|
||||
pub poke_loop_wakeup: fn(),
|
||||
}
|
||||
@@ -1,216 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
@@ -1,280 +0,0 @@
|
||||
// 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()>;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,591 +0,0 @@
|
||||
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::{
|
||||
gum_barebone_handle_exception, GumCpuContext,
|
||||
_GumExceptionType_GUM_EXCEPTION_ACCESS_VIOLATION as ACCESS_VIOLATION,
|
||||
};
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
#[repr(C)]
|
||||
struct NotifierBlock {
|
||||
notifier_call: Option<unsafe extern "C" fn(*mut NotifierBlock, c_long, *mut c_void) -> c_int>,
|
||||
next: *mut NotifierBlock,
|
||||
priority: c_int,
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
unsafe impl Sync for NotifierBlock {}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
static mut REPORTER: NotifierBlock =
|
||||
NotifierBlock { notifier_call: None, next: ptr::null_mut(), priority: 0 };
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
#[repr(C)]
|
||||
struct DieArgs {
|
||||
regs: *mut c_void,
|
||||
message: *const u8,
|
||||
err: c_long,
|
||||
trapnr: c_int,
|
||||
signr: c_int,
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
const DIE_OOPS: c_long = 1;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
const NOTIFY_DONE: c_int = 0;
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
const NOTIFY_STOP: c_int = 0x8001;
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
unsafe fn dispatch(action: c_long, data: *mut c_void) -> c_int {
|
||||
if action != DIE_OOPS {
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
let regs = unsafe { (*(data as *const DieArgs)).regs };
|
||||
if unsafe { recovered(regs) } {
|
||||
return NOTIFY_STOP;
|
||||
}
|
||||
NOTIFY_DONE
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
static mut RESUME_CONTEXT: GumCpuContext = unsafe { core::mem::zeroed() };
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
static mut RESUME_STUB: usize = 0;
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
const RESUME_SCRATCH_SIZE: usize = 512;
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
static mut RESUME_SCRATCH: [u8; RESUME_SCRATCH_SIZE] = [0; RESUME_SCRATCH_SIZE];
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub fn install() {
|
||||
unsafe { build_resume_stub() };
|
||||
unsafe {
|
||||
(&raw mut REPORTER).write(NotifierBlock {
|
||||
notifier_call: Some(frida_kcb_die),
|
||||
next: ptr::null_mut(),
|
||||
priority: 0,
|
||||
});
|
||||
frida_k_register_die_notifier(&raw mut REPORTER as *mut c_void);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
pub fn uninstall() {
|
||||
unsafe { frida_k_unregister_die_notifier(&raw mut REPORTER as *mut c_void) };
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
unsafe fn build_resume_stub() {
|
||||
let stub = crate::kernel::alloc_code(16);
|
||||
if stub.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
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 *const () as usize).wrapping_sub(stub as usize + 10) as u32;
|
||||
(stub.add(6) as *mut u32).write(rel);
|
||||
RESUME_STUB = stub as usize;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
unsafe fn recovered(regs: *mut c_void) -> bool {
|
||||
let faulted_at = unsafe { (regs as *const u32).add(12).read() as usize };
|
||||
let (base, size) = crate::own_range();
|
||||
if faulted_at < base || faulted_at >= base + size {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut context = unsafe { context_of(regs) };
|
||||
let handled = unsafe {
|
||||
gum_barebone_handle_exception(
|
||||
ACCESS_VIOLATION,
|
||||
faulted_at as *mut c_void,
|
||||
faulting_address() as *mut c_void,
|
||||
&mut context,
|
||||
)
|
||||
};
|
||||
if handled == 0 || unsafe { RESUME_STUB } == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
(&raw mut RESUME_CONTEXT).write(context);
|
||||
(regs as *mut u32).add(12).write(RESUME_STUB as u32);
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
fn faulting_address() -> usize {
|
||||
let value: usize;
|
||||
unsafe { core::arch::asm!("mov {}, cr2", out(reg) value, options(nomem, nostack)) };
|
||||
value
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
unsafe fn context_of(regs: *mut c_void) -> GumCpuContext {
|
||||
let r = regs as *const u32;
|
||||
let mut c: GumCpuContext = unsafe { core::mem::zeroed() };
|
||||
unsafe {
|
||||
c.ebx = r.add(0).read();
|
||||
c.ecx = r.add(1).read();
|
||||
c.edx = r.add(2).read();
|
||||
c.esi = r.add(3).read();
|
||||
c.edi = r.add(4).read();
|
||||
c.ebp = r.add(5).read();
|
||||
c.eax = r.add(6).read();
|
||||
c.eip = r.add(12).read();
|
||||
c.esp = ((&raw mut RESUME_SCRATCH) as usize + RESUME_SCRATCH_SIZE - 16) as u32;
|
||||
}
|
||||
c
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
#[unsafe(naked)]
|
||||
unsafe extern "C" fn frida_i386_resume() {
|
||||
core::arch::naked_asm!(
|
||||
"mov esi, [edi + 8]",
|
||||
"mov ebp, [edi + 12]",
|
||||
"mov ebx, [edi + 20]",
|
||||
"mov edx, [edi + 24]",
|
||||
"mov ecx, [edi + 28]",
|
||||
"mov eax, [edi + 32]",
|
||||
"mov esp, [edi + 16]",
|
||||
"push dword ptr [edi]",
|
||||
"mov edi, [edi + 4]",
|
||||
"ret",
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
#[unsafe(no_mangle)]
|
||||
unsafe extern "C" fn frida_cb_die(_nb: *mut NotifierBlock, action: c_long, data: *mut c_void) -> c_int {
|
||||
unsafe { dispatch(action, data) }
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
unsafe extern "C" {
|
||||
fn frida_kcb_die(nb: *mut NotifierBlock, action: c_long, data: *mut c_void) -> c_int;
|
||||
fn frida_k_register_die_notifier(nb: *mut c_void) -> c_int;
|
||||
fn frida_k_unregister_die_notifier(nb: *mut c_void) -> c_int;
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn install() {
|
||||
unsafe {
|
||||
(&raw mut REPORTER).write(NotifierBlock {
|
||||
notifier_call: Some(on_die),
|
||||
next: ptr::null_mut(),
|
||||
priority: 0,
|
||||
});
|
||||
_register_die_notifier(&raw mut REPORTER as *mut c_void);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
pub fn uninstall() {
|
||||
unsafe { _unregister_die_notifier(&raw mut REPORTER as *mut c_void) };
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
unsafe extern "C" fn on_die(_nb: *mut NotifierBlock, action: c_long, data: *mut c_void) -> c_int {
|
||||
unsafe { dispatch(action, data) }
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
unsafe fn recovered(regs: *mut c_void) -> bool {
|
||||
let faulted_at = unsafe { program_counter(regs) };
|
||||
let (base, size) = crate::own_range();
|
||||
if faulted_at < base || faulted_at >= base + size {
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut context = unsafe { context_of(regs) };
|
||||
let handled = unsafe {
|
||||
gum_barebone_handle_exception(
|
||||
ACCESS_VIOLATION,
|
||||
faulted_at as *mut c_void,
|
||||
faulting_address() as *mut c_void,
|
||||
&mut context,
|
||||
)
|
||||
};
|
||||
if handled == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsafe { restore_context(regs, &context) };
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
fn faulting_address() -> usize {
|
||||
let value: usize;
|
||||
unsafe { core::arch::asm!("mov {}, cr2", out(reg) value, options(nomem, nostack)) };
|
||||
value
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
unsafe fn program_counter(regs: *mut c_void) -> usize {
|
||||
unsafe { (regs as *const u64).add(16).read() as usize }
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
unsafe fn context_of(regs: *mut c_void) -> GumCpuContext {
|
||||
let r = regs as *const u64;
|
||||
let mut c: GumCpuContext = unsafe { core::mem::zeroed() };
|
||||
unsafe {
|
||||
c.r15 = r.add(0).read();
|
||||
c.r14 = r.add(1).read();
|
||||
c.r13 = r.add(2).read();
|
||||
c.r12 = r.add(3).read();
|
||||
c.rbp = r.add(4).read();
|
||||
c.rbx = r.add(5).read();
|
||||
c.r11 = r.add(6).read();
|
||||
c.r10 = r.add(7).read();
|
||||
c.r9 = r.add(8).read();
|
||||
c.r8 = r.add(9).read();
|
||||
c.rax = r.add(10).read();
|
||||
c.rcx = r.add(11).read();
|
||||
c.rdx = r.add(12).read();
|
||||
c.rsi = r.add(13).read();
|
||||
c.rdi = r.add(14).read();
|
||||
c.rip = r.add(16).read();
|
||||
c.rsp = r.add(19).read();
|
||||
}
|
||||
c
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
unsafe fn restore_context(regs: *mut c_void, c: &GumCpuContext) {
|
||||
let r = regs as *mut u64;
|
||||
unsafe {
|
||||
r.add(0).write(c.r15);
|
||||
r.add(1).write(c.r14);
|
||||
r.add(2).write(c.r13);
|
||||
r.add(3).write(c.r12);
|
||||
r.add(4).write(c.rbp);
|
||||
r.add(5).write(c.rbx);
|
||||
r.add(6).write(c.r11);
|
||||
r.add(7).write(c.r10);
|
||||
r.add(8).write(c.r9);
|
||||
r.add(9).write(c.r8);
|
||||
r.add(10).write(c.rax);
|
||||
r.add(11).write(c.rcx);
|
||||
r.add(12).write(c.rdx);
|
||||
r.add(13).write(c.rsi);
|
||||
r.add(14).write(c.rdi);
|
||||
r.add(16).write(c.rip);
|
||||
r.add(19).write(c.rsp);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86_64")]
|
||||
unsafe extern "C" {
|
||||
static _register_die_notifier: unsafe extern "C" fn(*mut c_void) -> c_int;
|
||||
static _unregister_die_notifier: unsafe extern "C" fn(*mut c_void) -> c_int;
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
const PROLOGUE: [u32; 2] = [0xe92d4010, 0xe52de004];
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
static mut ORIGINAL_STUB: usize = 0;
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub fn install() {
|
||||
let target = unsafe { _fixup_exception } as *const u32;
|
||||
for slot in 0..2 {
|
||||
if unsafe { target.add(slot).read_volatile() } != PROLOGUE[slot] {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe { build_original_stub(target) };
|
||||
if unsafe { ORIGINAL_STUB } == 0 {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let addr = target as *mut 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);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
pub fn uninstall() {
|
||||
let target = unsafe { _fixup_exception } as *mut u32;
|
||||
unsafe {
|
||||
_patch_text(target.add(1) as *mut c_void, PROLOGUE[1]);
|
||||
_patch_text(target as *mut c_void, PROLOGUE[0]);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
unsafe fn build_original_stub(target: *const u32) {
|
||||
let stub = crate::kernel::alloc_code(16) as *mut u32;
|
||||
if stub.is_null() {
|
||||
return;
|
||||
}
|
||||
unsafe {
|
||||
stub.add(0).write(PROLOGUE[0]);
|
||||
stub.add(1).write(PROLOGUE[1]);
|
||||
stub.add(2).write(0xe51ff004);
|
||||
stub.add(3).write(target.add(2) as u32);
|
||||
ORIGINAL_STUB = stub as usize;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
unsafe extern "C" fn frida_arm_fixup(regs: *mut c_void) -> c_int {
|
||||
if unsafe { recovered(regs) } {
|
||||
return 1;
|
||||
}
|
||||
let original: unsafe extern "C" fn(*mut c_void) -> c_int =
|
||||
unsafe { core::mem::transmute(ORIGINAL_STUB) };
|
||||
unsafe { original(regs) }
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
unsafe fn recovered(regs: *mut c_void) -> bool {
|
||||
let faulted_at = unsafe { (regs as *const u32).add(15).read() as usize };
|
||||
let (base, size) = crate::own_range();
|
||||
if faulted_at < base || faulted_at >= base + size {
|
||||
return false;
|
||||
}
|
||||
|
||||
let accessed: usize;
|
||||
unsafe { core::arch::asm!("mrc p15, 0, {}, c6, c0, 0", out(reg) accessed, options(nomem, nostack)) };
|
||||
|
||||
let mut context = unsafe { context_of(regs) };
|
||||
let handled = unsafe {
|
||||
gum_barebone_handle_exception(
|
||||
ACCESS_VIOLATION,
|
||||
faulted_at as *mut c_void,
|
||||
accessed as *mut c_void,
|
||||
&mut context,
|
||||
)
|
||||
};
|
||||
if handled == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
unsafe { restore_context(regs, &context) };
|
||||
true
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
unsafe fn context_of(regs: *mut c_void) -> GumCpuContext {
|
||||
let g = regs as *const u32;
|
||||
let mut r = [0u32; 8];
|
||||
for slot in 0..8 {
|
||||
r[slot] = unsafe { g.add(slot).read() };
|
||||
}
|
||||
let mut c: GumCpuContext = unsafe { core::mem::zeroed() };
|
||||
c.r = r;
|
||||
unsafe {
|
||||
c.r8 = g.add(8).read();
|
||||
c.r9 = g.add(9).read();
|
||||
c.r10 = g.add(10).read();
|
||||
c.r11 = g.add(11).read();
|
||||
c.r12 = g.add(12).read();
|
||||
c.sp = g.add(13).read();
|
||||
c.lr = g.add(14).read();
|
||||
c.pc = g.add(15).read();
|
||||
c.cpsr = g.add(16).read();
|
||||
}
|
||||
c
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
unsafe fn restore_context(regs: *mut c_void, c: &GumCpuContext) {
|
||||
let g = regs as *mut u32;
|
||||
unsafe {
|
||||
for slot in 0..8 {
|
||||
g.add(slot).write(c.r[slot]);
|
||||
}
|
||||
g.add(8).write(c.r8);
|
||||
g.add(9).write(c.r9);
|
||||
g.add(10).write(c.r10);
|
||||
g.add(11).write(c.r11);
|
||||
g.add(12).write(c.r12);
|
||||
g.add(13).write(c.sp);
|
||||
g.add(14).write(c.lr);
|
||||
g.add(15).write(c.pc);
|
||||
g.add(16).write(c.cpsr);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "arm")]
|
||||
unsafe extern "C" {
|
||||
static _fixup_exception: unsafe extern "C" fn();
|
||||
static _patch_text: unsafe extern "C" fn(*mut c_void, u32);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
const PROLOGUE: [u32; 4] = [0xd503233f, 0xa9be7bfd, 0x910003fd, 0xa90153f3];
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
#[unsafe(no_mangle)]
|
||||
static mut FRIDA_ARM64_ORIGINAL_CONT: u64 = 0;
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub fn install() {
|
||||
let target = unsafe { _fixup_exception } as *const u32;
|
||||
for slot in 0..4 {
|
||||
if unsafe { target.add(slot).read_volatile() } != PROLOGUE[slot] {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
unsafe { FRIDA_ARM64_ORIGINAL_CONT = target.add(4) as u64 };
|
||||
|
||||
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]);
|
||||
_aarch64_insn_patch_text_nosync(addr.add(3) as *mut c_void, jump[3]);
|
||||
_aarch64_insn_patch_text_nosync(addr.add(1) as *mut c_void, jump[1]);
|
||||
_aarch64_insn_patch_text_nosync(addr as *mut c_void, jump[0]);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
pub fn uninstall() {
|
||||
let target = unsafe { _fixup_exception } as *mut u32;
|
||||
unsafe {
|
||||
for slot in 0..4 {
|
||||
_aarch64_insn_patch_text_nosync(target.add(slot) as *mut c_void, PROLOGUE[slot]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
fn detour_to(destination: usize) -> [u32; 4] {
|
||||
[0x58000050, 0xd61f0200, destination as u32, (destination >> 32) as u32]
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
core::arch::global_asm!(
|
||||
".globl frida_arm64_call_original",
|
||||
"frida_arm64_call_original:",
|
||||
"paciasp",
|
||||
"stp x29, x30, [sp, #-32]!",
|
||||
"mov x29, sp",
|
||||
"stp x19, x20, [sp, #16]",
|
||||
"adrp x16, {cont}",
|
||||
"add x16, x16, :lo12:{cont}",
|
||||
"ldr x16, [x16]",
|
||||
"br x16",
|
||||
cont = sym FRIDA_ARM64_ORIGINAL_CONT,
|
||||
);
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
unsafe extern "C" {
|
||||
fn frida_arm64_call_original(regs: *mut c_void) -> c_int;
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
unsafe extern "C" fn frida_arm64_fixup(regs: *mut c_void) -> c_int {
|
||||
if unsafe { recovered(regs) } {
|
||||
return 1;
|
||||
}
|
||||
unsafe { frida_arm64_call_original(regs) }
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
unsafe fn recovered(regs: *mut c_void) -> bool {
|
||||
let faulted_at = unsafe { (regs as *const u64).add(32).read() as usize };
|
||||
let (base, size) = crate::own_range();
|
||||
if faulted_at < base || faulted_at >= base + size {
|
||||
return false;
|
||||
}
|
||||
|
||||
let accessed: usize;
|
||||
unsafe { core::arch::asm!("mrs {}, far_el1", out(reg) accessed, options(nomem, nostack)) };
|
||||
|
||||
let mut context = unsafe { context_of(regs) };
|
||||
let handled = unsafe {
|
||||
gum_barebone_handle_exception(
|
||||
ACCESS_VIOLATION,
|
||||
faulted_at as *mut c_void,
|
||||
accessed as *mut c_void,
|
||||
&mut context,
|
||||
)
|
||||
};
|
||||
if handled == 0 {
|
||||
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;
|
||||
let mut x = [0u64; 29];
|
||||
for slot in 0..29 {
|
||||
x[slot] = unsafe { r.add(slot).read() };
|
||||
}
|
||||
GumCpuContext {
|
||||
pc: unsafe { r.add(32).read() },
|
||||
sp: unsafe { r.add(31).read() },
|
||||
nzcv: unsafe { r.add(33).read() },
|
||||
x,
|
||||
fp: unsafe { r.add(29).read() },
|
||||
lr: unsafe { r.add(30).read() },
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
unsafe fn restore_context(regs: *mut c_void, c: &GumCpuContext) {
|
||||
let r = regs as *mut u64;
|
||||
unsafe {
|
||||
for slot in 0..29 {
|
||||
r.add(slot).write(c.x[slot]);
|
||||
}
|
||||
r.add(29).write(c.fp);
|
||||
r.add(30).write(c.lr);
|
||||
r.add(31).write(c.sp);
|
||||
r.add(32).write(c.pc);
|
||||
r.add(33).write(c.nzcv);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "aarch64")]
|
||||
unsafe extern "C" {
|
||||
static _fixup_exception: unsafe extern "C" fn();
|
||||
static _aarch64_insn_patch_text_nosync: unsafe extern "C" fn(*mut c_void, u32) -> c_int;
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "arm", target_arch = "aarch64")))]
|
||||
pub fn install() {}
|
||||
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "arm", target_arch = "aarch64")))]
|
||||
pub fn uninstall() {}
|
||||
@@ -1,357 +0,0 @@
|
||||
// The agent as a kernel module: every primitive is an ordinary call into
|
||||
// linux/frida-kmod.c, compiled by kbuild against the target kernel's headers,
|
||||
// which is what keeps us out of the business of guessing struct layouts,
|
||||
// config-dependent macros and per-version symbol names.
|
||||
|
||||
use core::ffi::{CStr, c_char, c_int, c_void};
|
||||
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use super::{LoadedModule, ModuleEvent};
|
||||
use crate::kernel::ThreadEntry;
|
||||
|
||||
pub fn log(msg: &str) {
|
||||
unsafe { frida_kmod_log(msg.as_ptr() as *const c_char) };
|
||||
}
|
||||
|
||||
pub fn panic(msg: &str) -> ! {
|
||||
unsafe { frida_kmod_panic(msg.as_ptr() as *const c_char) }
|
||||
}
|
||||
|
||||
pub fn run_when_ready(action: fn()) {
|
||||
action();
|
||||
}
|
||||
|
||||
pub fn spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> isize {
|
||||
unsafe { frida_kmod_spawn_thread(entry, parameter) as isize }
|
||||
}
|
||||
|
||||
pub fn install_hooks() {
|
||||
unsafe { frida_kmod_install_hooks() };
|
||||
}
|
||||
|
||||
pub fn alloc(size: usize) -> *mut u8 {
|
||||
unsafe { frida_kmod_alloc(size) }
|
||||
}
|
||||
|
||||
pub fn free(ptr: *mut u8, size: usize) {
|
||||
unsafe { frida_kmod_free(ptr, size) };
|
||||
}
|
||||
|
||||
pub fn alloc_code(size: usize) -> *mut u8 {
|
||||
unsafe { frida_kmod_alloc_code(size) }
|
||||
}
|
||||
|
||||
pub fn free_code(ptr: *mut u8, size: usize) {
|
||||
unsafe { frida_kmod_free_code(ptr, size) };
|
||||
}
|
||||
|
||||
pub fn page_size() -> usize {
|
||||
crate::gum::page_size_the_kernel_runs_with()
|
||||
}
|
||||
|
||||
pub fn alloc_heap(size: usize) -> *mut u8 {
|
||||
alloc(size)
|
||||
}
|
||||
|
||||
pub fn alloc_dma(size: usize) -> *mut u8 {
|
||||
alloc_code(size)
|
||||
}
|
||||
|
||||
pub fn free_dma(ptr: *mut u8, size: usize) {
|
||||
free_code(ptr, size);
|
||||
}
|
||||
|
||||
pub fn own_range() -> (u64, u64) {
|
||||
let mut base: u64 = 0;
|
||||
let mut size: u64 = 0;
|
||||
unsafe { frida_kmod_own_range(&mut base, &mut size) };
|
||||
(base, size)
|
||||
}
|
||||
|
||||
// The sequence number is taken before `check` runs, so a wakeup racing with the
|
||||
// condition we are about to observe still makes the commit return immediately.
|
||||
pub fn wait(_token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut() -> bool) {
|
||||
let seq = unsafe { frida_kmod_wait_prepare() };
|
||||
|
||||
if check() {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe { frida_kmod_wait_commit(seq, timeout_us.map_or(-1, |us| us as i64)) };
|
||||
}
|
||||
|
||||
pub fn poke_loop_wakeup() {}
|
||||
|
||||
pub fn wake(_token: *const u8) {
|
||||
unsafe { frida_kmod_wake() };
|
||||
}
|
||||
|
||||
/// Gives the scheduler a chance to run something else. A kernel thread that loops
|
||||
/// without doing this is a hard lockup waiting to happen.
|
||||
pub fn yield_now() {
|
||||
unsafe { frida_kmod_yield() };
|
||||
}
|
||||
|
||||
pub fn monotonic_micros() -> i64 {
|
||||
unsafe { frida_kmod_monotonic_micros() }
|
||||
}
|
||||
|
||||
pub fn wall_clock_micros() -> (u32, u32) {
|
||||
let mut secs: u32 = 0;
|
||||
let mut micros: u32 = 0;
|
||||
unsafe { frida_kmod_wall_clock_micros(&mut secs, &mut micros) };
|
||||
(secs, micros)
|
||||
}
|
||||
|
||||
pub fn current_process_id() -> u32 {
|
||||
KERNEL_PROCESS
|
||||
}
|
||||
|
||||
pub fn current_thread_id() -> u64 {
|
||||
unsafe { frida_kmod_current_thread_id() }
|
||||
}
|
||||
|
||||
pub fn protect(address: u64, size: usize, gum_prot: u32) -> bool {
|
||||
unsafe { frida_kmod_protect(address, size, gum_prot) != 0 }
|
||||
}
|
||||
|
||||
pub fn remap_writable(first_page: u64, n_pages: u32) -> *mut c_void {
|
||||
unsafe { frida_kmod_remap_writable(first_page, n_pages) }
|
||||
}
|
||||
|
||||
/// Tears down an alias handed out by [`remap_writable`] and flushes the icache
|
||||
/// over the range it aliased; the shim remembers which range that was.
|
||||
pub fn unmap_writable(mapping: *mut c_void) {
|
||||
unsafe { frida_kmod_unmap_writable(mapping) };
|
||||
}
|
||||
|
||||
pub fn get_kernel_base() -> u64 {
|
||||
unsafe { frida_kmod_kernel_base() }
|
||||
}
|
||||
|
||||
pub fn get_kernel_size() -> u64 {
|
||||
unsafe { frida_kmod_kernel_size() }
|
||||
}
|
||||
|
||||
pub fn enumerate_modules() -> Vec<LoadedModule> {
|
||||
unsafe extern "C" fn on_module(
|
||||
name: *const c_char,
|
||||
version: *const c_char,
|
||||
base: u64,
|
||||
size: u64,
|
||||
user_data: *mut c_void,
|
||||
) -> c_int {
|
||||
unsafe {
|
||||
let modules = &mut *(user_data as *mut Vec<LoadedModule>);
|
||||
modules.push(LoadedModule {
|
||||
name: String::from(CStr::from_ptr(name).to_str().unwrap_or("?")),
|
||||
version: String::from(CStr::from_ptr(version).to_str().unwrap_or("")),
|
||||
base,
|
||||
size,
|
||||
});
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
let mut modules: Vec<LoadedModule> = Vec::new();
|
||||
unsafe {
|
||||
frida_kmod_enumerate_modules(on_module, &mut modules as *mut _ as *mut c_void);
|
||||
}
|
||||
modules
|
||||
}
|
||||
|
||||
pub fn watch_modules(on_event: fn(ModuleEvent, LoadedModule)) {
|
||||
unsafe extern "C" fn on_module_event(
|
||||
loaded: c_int,
|
||||
name: *const c_char,
|
||||
version: *const c_char,
|
||||
base: u64,
|
||||
size: u64,
|
||||
user_data: *mut c_void,
|
||||
) {
|
||||
unsafe {
|
||||
let on_event: fn(ModuleEvent, LoadedModule) = core::mem::transmute(user_data);
|
||||
on_event(
|
||||
if loaded != 0 {
|
||||
ModuleEvent::Loaded
|
||||
} else {
|
||||
ModuleEvent::Unloaded
|
||||
},
|
||||
LoadedModule {
|
||||
name: String::from(CStr::from_ptr(name).to_str().unwrap_or("?")),
|
||||
version: String::from(CStr::from_ptr(version).to_str().unwrap_or("")),
|
||||
base,
|
||||
size,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
frida_kmod_watch_modules(on_module_event, on_event as *mut c_void);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwatch_modules() {
|
||||
unsafe { frida_kmod_unwatch_modules() };
|
||||
}
|
||||
|
||||
pub fn enumerate_module_symbols(
|
||||
base: u64,
|
||||
on_symbol: &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool,
|
||||
) -> bool {
|
||||
unsafe extern "C" fn on_module_symbol(
|
||||
name: *const c_char,
|
||||
address: u64,
|
||||
size: u64,
|
||||
kind: u8,
|
||||
is_global: c_int,
|
||||
user_data: *mut c_void,
|
||||
) -> c_int {
|
||||
unsafe {
|
||||
let callback =
|
||||
&mut *(user_data as *mut &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool);
|
||||
if callback(name, address, size, kind, is_global != 0) { 1 } else { 0 }
|
||||
}
|
||||
}
|
||||
|
||||
let mut boxed: &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool = on_symbol;
|
||||
unsafe {
|
||||
frida_kmod_enumerate_module_symbols(base, on_module_symbol, &mut boxed as *mut _ as *mut c_void) != 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enumerate_module_exports(
|
||||
base: u64,
|
||||
on_export: &mut dyn FnMut(*const c_char, u64) -> bool,
|
||||
) -> bool {
|
||||
unsafe extern "C" fn on_module_export(
|
||||
name: *const c_char,
|
||||
address: u64,
|
||||
user_data: *mut c_void,
|
||||
) -> c_int {
|
||||
unsafe {
|
||||
let callback = &mut *(user_data as *mut &mut dyn FnMut(*const c_char, u64) -> bool);
|
||||
if callback(name, address) { 1 } else { 0 }
|
||||
}
|
||||
}
|
||||
|
||||
let mut boxed: &mut dyn FnMut(*const c_char, u64) -> bool = on_export;
|
||||
unsafe {
|
||||
frida_kmod_enumerate_module_exports(base, on_module_export, &mut boxed as *mut _ as *mut c_void) != 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_symbol(name: &CStr) -> u64 {
|
||||
unsafe { frida_kmod_find_symbol(name.as_ptr()) }
|
||||
}
|
||||
|
||||
pub fn find_function(name: &CStr) -> u64 {
|
||||
unsafe { frida_kmod_find_function(name.as_ptr()) }
|
||||
}
|
||||
|
||||
/// Resolves `address` to `(name, symbol_address)`, or `None` when it falls
|
||||
/// outside every known symbol.
|
||||
pub fn symbol_from_address(address: u64) -> Option<(String, u64)> {
|
||||
const NAME_CAPACITY: usize = 256;
|
||||
|
||||
let mut buffer = [0u8; NAME_CAPACITY];
|
||||
let mut symbol_address: u64 = 0;
|
||||
let found = unsafe {
|
||||
frida_kmod_symbol_name_from_address(
|
||||
address,
|
||||
buffer.as_mut_ptr() as *mut c_char,
|
||||
NAME_CAPACITY,
|
||||
&mut symbol_address,
|
||||
)
|
||||
};
|
||||
if found == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = CStr::from_bytes_until_nul(&buffer).unwrap();
|
||||
Some((String::from(name.to_str().unwrap()), symbol_address))
|
||||
}
|
||||
|
||||
/// Visits every symbol the running kernel knows about. Returns false when
|
||||
/// kallsyms is unavailable, which is the case on kernels built without
|
||||
/// CONFIG_KALLSYMS or when the shim could not resolve its entry points.
|
||||
pub fn enumerate_symbols(callback: &mut dyn FnMut(&CStr, u64) -> bool) -> bool {
|
||||
unsafe extern "C" fn on_symbol(
|
||||
name: *const c_char,
|
||||
address: u64,
|
||||
user_data: *mut c_void,
|
||||
) -> c_int {
|
||||
unsafe {
|
||||
let callback = &mut *(user_data as *mut &mut dyn FnMut(&CStr, u64) -> bool);
|
||||
if callback(CStr::from_ptr(name), address) { 1 } else { 0 }
|
||||
}
|
||||
}
|
||||
|
||||
let mut boxed: &mut dyn FnMut(&CStr, u64) -> bool = callback;
|
||||
unsafe { frida_kmod_enumerate_symbols(on_symbol, &mut boxed as *mut _ as *mut c_void) != 0 }
|
||||
}
|
||||
|
||||
const KERNEL_PROCESS: u32 = 0;
|
||||
|
||||
type FoundSymbolFunc =
|
||||
unsafe extern "C" fn(name: *const c_char, address: u64, user_data: *mut c_void) -> c_int;
|
||||
type ModuleEventFunc = unsafe extern "C" fn(c_int, *const c_char, *const c_char, u64, u64, *mut c_void);
|
||||
type FoundModuleSymbolFunc =
|
||||
unsafe extern "C" fn(*const c_char, u64, u64, u8, c_int, *mut c_void) -> c_int;
|
||||
type FoundModuleExportFunc = unsafe extern "C" fn(*const c_char, u64, *mut c_void) -> c_int;
|
||||
type FoundModuleFunc = unsafe extern "C" fn(
|
||||
name: *const c_char,
|
||||
version: *const c_char,
|
||||
base: u64,
|
||||
size: u64,
|
||||
user_data: *mut c_void,
|
||||
) -> c_int;
|
||||
|
||||
unsafe extern "C" {
|
||||
fn frida_kmod_log(message: *const c_char);
|
||||
fn frida_kmod_panic(message: *const c_char) -> !;
|
||||
fn frida_kmod_spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> c_int;
|
||||
fn frida_kmod_install_hooks();
|
||||
fn frida_kmod_alloc(size: usize) -> *mut u8;
|
||||
fn frida_kmod_free(ptr: *mut u8, size: usize);
|
||||
fn frida_kmod_alloc_code(size: usize) -> *mut u8;
|
||||
fn frida_kmod_free_code(ptr: *mut u8, size: usize);
|
||||
fn frida_kmod_own_range(base: *mut u64, size: *mut u64);
|
||||
fn frida_kmod_wait_prepare() -> u32;
|
||||
fn frida_kmod_wait_commit(seq: u32, timeout_us: i64);
|
||||
fn frida_kmod_wake();
|
||||
fn frida_kmod_yield();
|
||||
fn frida_kmod_monotonic_micros() -> i64;
|
||||
fn frida_kmod_wall_clock_micros(secs: *mut u32, micros: *mut u32);
|
||||
fn frida_kmod_current_thread_id() -> u64;
|
||||
fn frida_kmod_protect(address: u64, size: usize, gum_prot: u32) -> c_int;
|
||||
fn frida_kmod_remap_writable(first_page: u64, n_pages: u32) -> *mut c_void;
|
||||
fn frida_kmod_unmap_writable(mapping: *mut c_void);
|
||||
fn frida_kmod_kernel_base() -> u64;
|
||||
fn frida_kmod_kernel_size() -> u64;
|
||||
fn frida_kmod_enumerate_modules(func: FoundModuleFunc, user_data: *mut c_void);
|
||||
fn frida_kmod_watch_modules(func: ModuleEventFunc, user_data: *mut c_void);
|
||||
fn frida_kmod_unwatch_modules();
|
||||
fn frida_kmod_enumerate_module_symbols(
|
||||
base: u64,
|
||||
func: FoundModuleSymbolFunc,
|
||||
user_data: *mut c_void,
|
||||
) -> c_int;
|
||||
fn frida_kmod_enumerate_module_exports(
|
||||
base: u64,
|
||||
func: FoundModuleExportFunc,
|
||||
user_data: *mut c_void,
|
||||
) -> c_int;
|
||||
fn frida_kmod_find_symbol(name: *const c_char) -> u64;
|
||||
fn frida_kmod_find_function(name: *const c_char) -> u64;
|
||||
fn frida_kmod_symbol_name_from_address(
|
||||
address: u64,
|
||||
buffer: *mut c_char,
|
||||
size: usize,
|
||||
symbol_address: *mut u64,
|
||||
) -> c_int;
|
||||
fn frida_kmod_enumerate_symbols(func: FoundSymbolFunc, user_data: *mut c_void) -> c_int;
|
||||
}
|
||||
@@ -1,622 +0,0 @@
|
||||
use alloc::collections::BTreeMap;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use core::ffi::c_void;
|
||||
|
||||
pub fn field_offset(container: &str, field: &str) -> Option<usize> {
|
||||
let types = described_types()?;
|
||||
let described = types.named(container)?;
|
||||
|
||||
types.offset_of(&described, field).map(|bits| bits / 8)
|
||||
}
|
||||
|
||||
pub fn resolve_struct(container: &str) -> Option<usize> {
|
||||
let described = described_types()?.named(container)?;
|
||||
|
||||
let resolved = unsafe { (&raw mut RESOLVED).as_mut().unwrap() };
|
||||
resolved.push(described);
|
||||
|
||||
Some(resolved.len() - 1)
|
||||
}
|
||||
|
||||
pub fn size_of_struct(handle: usize) -> usize {
|
||||
resolved_struct(handle).size as usize
|
||||
}
|
||||
|
||||
pub fn field_offset_in(handle: usize, field: &str) -> Option<usize> {
|
||||
let types = described_types()?;
|
||||
|
||||
types
|
||||
.offset_of(&resolved_struct(handle), field)
|
||||
.map(|bits| bits / 8)
|
||||
}
|
||||
|
||||
pub fn enumerate_fields_in(handle: usize, on_field: &mut dyn FnMut(&str, &Field) -> bool) {
|
||||
let Some(types) = described_types() else {
|
||||
return;
|
||||
};
|
||||
|
||||
types.visit_fields(&resolved_struct(handle), 0, on_field);
|
||||
}
|
||||
|
||||
pub fn find_constant(name: &str) -> Option<i64> {
|
||||
described_types()?.constant_named(name)
|
||||
}
|
||||
|
||||
pub fn enumerate_constants_in(container: &str, on_constant: &mut dyn FnMut(&str, i64)) -> bool {
|
||||
let Some(types) = described_types() else {
|
||||
return false;
|
||||
};
|
||||
let Some(described) = types.enum_named(container) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
types.visit_constants(&described, &mut |name, value| {
|
||||
on_constant(name, value);
|
||||
true
|
||||
});
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn enumerate_parameters_in(
|
||||
name: &str,
|
||||
on_parameter: &mut dyn FnMut(&str, &str),
|
||||
) -> Option<String> {
|
||||
let types = described_types()?;
|
||||
let described = types.function_named(name)?;
|
||||
let signature = types.with_id(described.size)?;
|
||||
|
||||
types.visit_parameters(&signature, on_parameter);
|
||||
|
||||
Some(types.render(signature.size, 0))
|
||||
}
|
||||
|
||||
pub fn name_of_type(id: u32) -> String {
|
||||
described_types().map_or_else(String::new, |types| types.render(id, 0))
|
||||
}
|
||||
|
||||
pub fn size_of_type(id: u32) -> usize {
|
||||
described_types().map_or(0, |types| types.span_of(id, 0))
|
||||
}
|
||||
|
||||
pub struct Field {
|
||||
pub offset: usize,
|
||||
pub bit_offset: usize,
|
||||
pub bit_size: usize,
|
||||
pub id: u32,
|
||||
}
|
||||
|
||||
fn resolved_struct(handle: usize) -> Described {
|
||||
unsafe { (&raw const RESOLVED).as_ref().unwrap()[handle].clone() }
|
||||
}
|
||||
|
||||
pub fn types_are_described() -> bool {
|
||||
described_types().is_some()
|
||||
}
|
||||
|
||||
pub fn struct_size(container: &str) -> Option<usize> {
|
||||
let types = described_types()?;
|
||||
|
||||
Some(types.named(container)?.size as usize)
|
||||
}
|
||||
|
||||
struct DescribedTypes {
|
||||
types: &'static [u8],
|
||||
names: &'static [u8],
|
||||
}
|
||||
|
||||
impl DescribedTypes {
|
||||
fn named(&self, container: &str) -> Option<Described> {
|
||||
let index = self.index();
|
||||
|
||||
if let Some(offset) = index.containers.get(container) {
|
||||
return self.at(*offset as usize);
|
||||
}
|
||||
|
||||
while let Some(described) = self.take_in_next_type(index) {
|
||||
if self.names_container(&described, container) {
|
||||
return Some(described);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn with_id(&self, wanted: u32) -> Option<Described> {
|
||||
if wanted == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let index = self.index();
|
||||
let checkpoint = (wanted as usize - FIRST_ID) / CHECKPOINT_SPAN;
|
||||
|
||||
while index.checkpoints.len() <= checkpoint {
|
||||
self.take_in_next_type(index)?;
|
||||
}
|
||||
|
||||
let mut described = self.at(index.checkpoints[checkpoint] as usize)?;
|
||||
let mut id = (checkpoint * CHECKPOINT_SPAN + FIRST_ID) as u32;
|
||||
while id != wanted {
|
||||
described = self.at(described.body + described.body_size)?;
|
||||
id += 1;
|
||||
}
|
||||
|
||||
Some(described)
|
||||
}
|
||||
|
||||
fn take_in_next_type(&self, index: &mut Index) -> Option<Described> {
|
||||
let described = self.at(index.offset)?;
|
||||
|
||||
if (index.id as usize - FIRST_ID) % CHECKPOINT_SPAN == 0 {
|
||||
index.checkpoints.push(index.offset as u32);
|
||||
}
|
||||
|
||||
if self.holds_fields(&described) && described.name != 0 {
|
||||
index
|
||||
.containers
|
||||
.insert(self.name_of(described.name), index.offset as u32);
|
||||
}
|
||||
|
||||
index.offset = described.body + described.body_size;
|
||||
index.id += 1;
|
||||
|
||||
Some(described)
|
||||
}
|
||||
|
||||
fn names_container(&self, described: &Described, container: &str) -> bool {
|
||||
self.holds_fields(described)
|
||||
&& described.name != 0
|
||||
&& self.name_of(described.name) == container
|
||||
}
|
||||
|
||||
fn holds_fields(&self, described: &Described) -> bool {
|
||||
described.kind == KIND_STRUCT || described.kind == KIND_UNION
|
||||
}
|
||||
|
||||
fn index(&self) -> &'static mut Index {
|
||||
let known = unsafe { (&raw mut INDEX).as_mut().unwrap() };
|
||||
if known.is_none() {
|
||||
*known = Some(Index {
|
||||
containers: BTreeMap::new(),
|
||||
checkpoints: Vec::new(),
|
||||
offset: 0,
|
||||
id: FIRST_ID as u32,
|
||||
});
|
||||
}
|
||||
|
||||
known.as_mut().unwrap()
|
||||
}
|
||||
|
||||
fn visit_fields(
|
||||
&self,
|
||||
described: &Described,
|
||||
base: usize,
|
||||
on_field: &mut dyn FnMut(&str, &Field) -> bool,
|
||||
) -> bool {
|
||||
for index in 0..described.members {
|
||||
let member = described.body + (index * MEMBER_SIZE);
|
||||
let name = self.name_of(word_at(self.types, member));
|
||||
let placement = base + self.placement_of(described, member);
|
||||
|
||||
if name.is_empty() {
|
||||
let Some(within) = self.with_id(word_at(self.types, member + 4)) else {
|
||||
continue;
|
||||
};
|
||||
if within.kind == KIND_STRUCT || within.kind == KIND_UNION {
|
||||
if !self.visit_fields(&within, placement, on_field) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let field = Field {
|
||||
offset: placement / 8,
|
||||
bit_offset: placement,
|
||||
bit_size: self.bits_of(described, member),
|
||||
id: word_at(self.types, member + 4),
|
||||
};
|
||||
if !on_field(name, &field) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn function_named(&self, wanted: &str) -> Option<Described> {
|
||||
let known = self.functions();
|
||||
|
||||
let at = known
|
||||
.binary_search_by(|entry| self.name_of(entry.name).cmp(wanted))
|
||||
.ok()?;
|
||||
|
||||
self.at(known[at].offset as usize)
|
||||
}
|
||||
|
||||
fn functions(&self) -> &'static [Named] {
|
||||
let known = unsafe { (&raw mut FUNCTIONS).as_mut().unwrap() };
|
||||
if known.is_none() {
|
||||
let mut found = Vec::new();
|
||||
|
||||
self.visit_every_type_at(&mut |offset, described| {
|
||||
if described.kind == KIND_FUNCTION && described.name != 0 {
|
||||
found.push(Named {
|
||||
name: described.name,
|
||||
offset: offset as u32,
|
||||
});
|
||||
}
|
||||
|
||||
true
|
||||
});
|
||||
|
||||
found.sort_unstable_by(|a, b| self.name_of(a.name).cmp(self.name_of(b.name)));
|
||||
|
||||
*known = Some(found);
|
||||
}
|
||||
|
||||
known.as_ref().unwrap()
|
||||
}
|
||||
|
||||
fn visit_parameters(&self, signature: &Described, on_parameter: &mut dyn FnMut(&str, &str)) {
|
||||
for index in 0..signature.members {
|
||||
let parameter = signature.body + (index * PARAMETER_SIZE);
|
||||
let name = self.name_of(word_at(self.types, parameter));
|
||||
let id = word_at(self.types, parameter + 4);
|
||||
|
||||
if id == 0 {
|
||||
on_parameter("", "...");
|
||||
continue;
|
||||
}
|
||||
|
||||
on_parameter(name, &self.render(id, 0));
|
||||
}
|
||||
}
|
||||
|
||||
fn render(&self, id: u32, depth: usize) -> String {
|
||||
if depth == MAX_DEPTH {
|
||||
return String::from("...");
|
||||
}
|
||||
|
||||
let Some(described) = self.with_id(id) else {
|
||||
return String::from("void");
|
||||
};
|
||||
let named = self.name_of(described.name);
|
||||
|
||||
match described.kind {
|
||||
KIND_POINTER => alloc::format!("{} *", self.render(described.size, depth + 1)),
|
||||
KIND_ARRAY => {
|
||||
let element = word_at(self.types, described.body);
|
||||
let count = word_at(self.types, described.body + 8);
|
||||
alloc::format!("{}[{}]", self.render(element, depth + 1), count)
|
||||
}
|
||||
KIND_STRUCT => alloc::format!("struct {named}"),
|
||||
KIND_UNION => alloc::format!("union {named}"),
|
||||
KIND_ENUM | KIND_WIDE_ENUM => alloc::format!("enum {named}"),
|
||||
KIND_CONSTANT => alloc::format!("const {}", self.render(described.size, depth + 1)),
|
||||
KIND_VOLATILE => alloc::format!("volatile {}", self.render(described.size, depth + 1)),
|
||||
KIND_RESTRICT => alloc::format!("{} restrict", self.render(described.size, depth + 1)),
|
||||
KIND_SIGNATURE => self.render_signature(&described, depth),
|
||||
KIND_LOOKING_AHEAD => alloc::format!("struct {named}"),
|
||||
_ => String::from(named),
|
||||
}
|
||||
}
|
||||
|
||||
fn render_signature(&self, described: &Described, depth: usize) -> String {
|
||||
let mut rendered = alloc::format!("{} (", self.render(described.size, depth + 1));
|
||||
|
||||
for index in 0..described.members {
|
||||
let parameter = described.body + (index * PARAMETER_SIZE);
|
||||
if index != 0 {
|
||||
rendered.push_str(", ");
|
||||
}
|
||||
rendered.push_str(&self.render(word_at(self.types, parameter + 4), depth + 1));
|
||||
}
|
||||
|
||||
rendered.push(')');
|
||||
|
||||
rendered
|
||||
}
|
||||
|
||||
fn span_of(&self, id: u32, depth: usize) -> usize {
|
||||
if depth == MAX_DEPTH {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let Some(described) = self.with_id(id) else {
|
||||
return 0;
|
||||
};
|
||||
|
||||
match described.kind {
|
||||
KIND_POINTER => core::mem::size_of::<usize>(),
|
||||
KIND_ARRAY => {
|
||||
let element = word_at(self.types, described.body);
|
||||
let count = word_at(self.types, described.body + 8) as usize;
|
||||
self.span_of(element, depth + 1) * count
|
||||
}
|
||||
KIND_INT | KIND_STRUCT | KIND_UNION | KIND_ENUM | KIND_WIDE_ENUM | KIND_FLOAT => {
|
||||
described.size as usize
|
||||
}
|
||||
KIND_TYPEDEF | KIND_CONSTANT | KIND_VOLATILE | KIND_RESTRICT | KIND_TAGGED_TYPE => {
|
||||
self.span_of(described.size, depth + 1)
|
||||
}
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn bits_of(&self, described: &Described, member: usize) -> usize {
|
||||
if !described.members_carry_bitfields {
|
||||
return 0;
|
||||
}
|
||||
|
||||
(word_at(self.types, member + 8) >> 24) as usize
|
||||
}
|
||||
|
||||
fn constant_named(&self, wanted: &str) -> Option<i64> {
|
||||
let known = unsafe { (&raw mut CONSTANTS).as_mut().unwrap() };
|
||||
if let Some(value) = known.get(wanted) {
|
||||
return Some(*value);
|
||||
}
|
||||
|
||||
let mut found = None;
|
||||
self.visit_every_type(&mut |described| {
|
||||
if !self.holds_constants(described) {
|
||||
return true;
|
||||
}
|
||||
|
||||
self.visit_constants(described, &mut |name, value| {
|
||||
if name == wanted {
|
||||
found = Some(value);
|
||||
}
|
||||
found.is_none()
|
||||
});
|
||||
|
||||
found.is_none()
|
||||
});
|
||||
|
||||
if let Some(value) = found {
|
||||
known.insert(String::from(wanted), value);
|
||||
}
|
||||
|
||||
found
|
||||
}
|
||||
|
||||
fn enum_named(&self, wanted: &str) -> Option<Described> {
|
||||
let mut found = None;
|
||||
self.visit_every_type(&mut |described| {
|
||||
if self.holds_constants(described)
|
||||
&& described.name != 0
|
||||
&& self.name_of(described.name) == wanted
|
||||
{
|
||||
found = Some(described.clone());
|
||||
}
|
||||
|
||||
found.is_none()
|
||||
});
|
||||
|
||||
found
|
||||
}
|
||||
|
||||
fn visit_constants(&self, described: &Described, on_constant: &mut dyn FnMut(&str, i64) -> bool) {
|
||||
let wide = described.kind == KIND_WIDE_ENUM;
|
||||
let stride = if wide { 12 } else { 8 };
|
||||
|
||||
for index in 0..described.members {
|
||||
let constant = described.body + (index * stride);
|
||||
let name = self.name_of(word_at(self.types, constant));
|
||||
|
||||
let value = if wide {
|
||||
((word_at(self.types, constant + 8) as u64) << 32
|
||||
| word_at(self.types, constant + 4) as u64) as i64
|
||||
} else {
|
||||
word_at(self.types, constant + 4) as i32 as i64
|
||||
};
|
||||
|
||||
if !on_constant(name, value) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_every_type(&self, on_type: &mut dyn FnMut(&Described) -> bool) {
|
||||
self.visit_every_type_at(&mut |_offset, described| on_type(described));
|
||||
}
|
||||
|
||||
fn visit_every_type_at(&self, on_type: &mut dyn FnMut(usize, &Described) -> bool) {
|
||||
let mut offset = 0;
|
||||
while let Some(described) = self.at(offset) {
|
||||
if !on_type(offset, &described) {
|
||||
return;
|
||||
}
|
||||
|
||||
offset = described.body + described.body_size;
|
||||
}
|
||||
}
|
||||
|
||||
fn holds_constants(&self, described: &Described) -> bool {
|
||||
described.kind == KIND_ENUM || described.kind == KIND_WIDE_ENUM
|
||||
}
|
||||
|
||||
// A structure the kernel lays out itself keeps its fields in a member with no name of its
|
||||
// own, so a field is looked for through those as well, and answers where it sits in the
|
||||
// structure the question was asked about.
|
||||
fn offset_of(&self, described: &Described, field: &str) -> Option<usize> {
|
||||
for index in 0..described.members {
|
||||
let member = described.body + (index * MEMBER_SIZE);
|
||||
let name = self.name_of(word_at(self.types, member));
|
||||
let placement = self.placement_of(described, member);
|
||||
|
||||
if name == field {
|
||||
return Some(placement);
|
||||
}
|
||||
|
||||
if name.is_empty() {
|
||||
let within = self.with_id(word_at(self.types, member + 4))?;
|
||||
if within.kind == KIND_STRUCT || within.kind == KIND_UNION {
|
||||
if let Some(deeper) = self.offset_of(&within, field) {
|
||||
return Some(placement + deeper);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn placement_of(&self, described: &Described, member: usize) -> usize {
|
||||
let placement = word_at(self.types, member + 8);
|
||||
|
||||
let bits = if described.members_carry_bitfields {
|
||||
placement & 0x00ff_ffff
|
||||
} else {
|
||||
placement
|
||||
};
|
||||
|
||||
bits as usize
|
||||
}
|
||||
|
||||
fn at(&self, offset: usize) -> Option<Described> {
|
||||
if offset + DESCRIPTION_SIZE > self.types.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let name = word_at(self.types, offset);
|
||||
let info = word_at(self.types, offset + 4);
|
||||
let size = word_at(self.types, offset + 8);
|
||||
|
||||
let kind = (info >> 24) & 0x1f;
|
||||
let members = (info & 0xffff) as usize;
|
||||
|
||||
Some(Described {
|
||||
name,
|
||||
kind,
|
||||
size,
|
||||
members,
|
||||
members_carry_bitfields: (info >> 31) != 0,
|
||||
body: offset + DESCRIPTION_SIZE,
|
||||
body_size: body_size_of(kind, members),
|
||||
})
|
||||
}
|
||||
|
||||
fn name_of(&self, offset: u32) -> &'static str {
|
||||
let start = offset as usize;
|
||||
let end = self.names[start..]
|
||||
.iter()
|
||||
.position(|letter| *letter == 0)
|
||||
.map(|length| start + length)
|
||||
.unwrap_or(self.names.len());
|
||||
|
||||
core::str::from_utf8(&self.names[start..end]).unwrap_or("")
|
||||
}
|
||||
}
|
||||
|
||||
static mut CONSTANTS: BTreeMap<String, i64> = BTreeMap::new();
|
||||
|
||||
static mut FUNCTIONS: Option<Vec<Named>> = None;
|
||||
|
||||
static mut INDEX: Option<Index> = None;
|
||||
|
||||
static mut RESOLVED: Vec<Described> = Vec::new();
|
||||
|
||||
struct Named {
|
||||
name: u32,
|
||||
offset: u32,
|
||||
}
|
||||
|
||||
struct Index {
|
||||
containers: BTreeMap<&'static str, u32>,
|
||||
checkpoints: Vec<u32>,
|
||||
offset: usize,
|
||||
id: u32,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Described {
|
||||
name: u32,
|
||||
kind: u32,
|
||||
size: u32,
|
||||
members: usize,
|
||||
members_carry_bitfields: bool,
|
||||
body: usize,
|
||||
body_size: usize,
|
||||
}
|
||||
|
||||
fn described_types() -> Option<DescribedTypes> {
|
||||
let start = unsafe { ___start_BTF } as usize;
|
||||
let stop = unsafe { ___stop_BTF } as usize;
|
||||
if start == 0 || stop <= start {
|
||||
return None;
|
||||
}
|
||||
|
||||
let described = unsafe { core::slice::from_raw_parts(start as *const u8, stop - start) };
|
||||
if u16::from_ne_bytes([described[0], described[1]]) != DESCRIPTION_MAGIC {
|
||||
return None;
|
||||
}
|
||||
|
||||
let header_size = word_at(described, 4) as usize;
|
||||
let types = header_size + word_at(described, 8) as usize;
|
||||
let types_size = word_at(described, 12) as usize;
|
||||
let names = header_size + word_at(described, 16) as usize;
|
||||
let names_size = word_at(described, 20) as usize;
|
||||
if names + names_size > described.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(DescribedTypes {
|
||||
types: &described[types..types + types_size],
|
||||
names: &described[names..names + names_size],
|
||||
})
|
||||
}
|
||||
|
||||
fn body_size_of(kind: u32, members: usize) -> usize {
|
||||
match kind {
|
||||
KIND_INT | KIND_VARIABLE | KIND_TAG => 4,
|
||||
KIND_ARRAY => 12,
|
||||
KIND_STRUCT | KIND_UNION => members * MEMBER_SIZE,
|
||||
KIND_ENUM | KIND_SIGNATURE => members * 8,
|
||||
KIND_SECTION | KIND_WIDE_ENUM => members * 12,
|
||||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn word_at(bytes: &[u8], offset: usize) -> u32 {
|
||||
u32::from_ne_bytes([
|
||||
bytes[offset],
|
||||
bytes[offset + 1],
|
||||
bytes[offset + 2],
|
||||
bytes[offset + 3],
|
||||
])
|
||||
}
|
||||
|
||||
const DESCRIPTION_MAGIC: u16 = 0xeb9f;
|
||||
const DESCRIPTION_SIZE: usize = 12;
|
||||
const FIRST_ID: usize = 1;
|
||||
const CHECKPOINT_SPAN: usize = 64;
|
||||
const MEMBER_SIZE: usize = 12;
|
||||
|
||||
const KIND_INT: u32 = 1;
|
||||
const KIND_POINTER: u32 = 2;
|
||||
const KIND_ARRAY: u32 = 3;
|
||||
const KIND_STRUCT: u32 = 4;
|
||||
const KIND_UNION: u32 = 5;
|
||||
const KIND_ENUM: u32 = 6;
|
||||
const KIND_FUNCTION: u32 = 12;
|
||||
const KIND_LOOKING_AHEAD: u32 = 7;
|
||||
const KIND_TYPEDEF: u32 = 8;
|
||||
const KIND_VOLATILE: u32 = 9;
|
||||
const KIND_CONSTANT: u32 = 10;
|
||||
const KIND_RESTRICT: u32 = 11;
|
||||
const KIND_SIGNATURE: u32 = 13;
|
||||
const KIND_VARIABLE: u32 = 14;
|
||||
const KIND_SECTION: u32 = 15;
|
||||
const KIND_TAG: u32 = 17;
|
||||
const KIND_FLOAT: u32 = 16;
|
||||
const KIND_TAGGED_TYPE: u32 = 18;
|
||||
const KIND_WIDE_ENUM: u32 = 19;
|
||||
const PARAMETER_SIZE: usize = 8;
|
||||
const MAX_DEPTH: usize = 8;
|
||||
|
||||
unsafe extern "C" {
|
||||
static ___start_BTF: *const c_void;
|
||||
static ___stop_BTF: *const c_void;
|
||||
}
|
||||
@@ -1,370 +0,0 @@
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use core::ffi::c_void;
|
||||
use core::ptr;
|
||||
|
||||
use crate::bindings::{
|
||||
GError, GumElfModule, GumExportDetails, GumMemoryRange, GumModuleRegistry, gboolean,
|
||||
g_bytes_new, g_bytes_unref, g_clear_error, g_object_unref, gconstpointer, gpointer, gsize,
|
||||
gum_barebone_register_module, gum_barebone_unregister_module,
|
||||
gum_elf_module_enumerate_exports, gum_elf_module_enumerate_symbols,
|
||||
gum_elf_module_get_preferred_address, gum_elf_module_new_from_blob, gum_interceptor_begin_transaction,
|
||||
gum_interceptor_end_transaction, gum_interceptor_obtain, gum_interceptor_replace,
|
||||
};
|
||||
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};
|
||||
|
||||
pub fn register_what_the_copy_lives_among(registry: *mut GumModuleRegistry) {
|
||||
unsafe { REGISTRY = registry };
|
||||
|
||||
for image in mapped_images() {
|
||||
announce(&image);
|
||||
unsafe { known() }.push(image);
|
||||
}
|
||||
}
|
||||
|
||||
// The loader calls the same empty function every time it has changed what is mapped -- that is
|
||||
// what a debugger watches -- so the copy asks again from there.
|
||||
pub fn watch_the_loader() {
|
||||
let Some(rendezvous) = where_the_loader_says_so() else {
|
||||
return;
|
||||
};
|
||||
|
||||
unsafe {
|
||||
let interceptor = gum_interceptor_obtain();
|
||||
gum_interceptor_begin_transaction(interceptor);
|
||||
gum_interceptor_replace(interceptor, rendezvous as gpointer,
|
||||
the_loader_changed_something as gpointer, &raw mut LOADER_SAYS_SO,
|
||||
ptr::null_mut());
|
||||
gum_interceptor_end_transaction(interceptor);
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "C" fn the_loader_changed_something() {
|
||||
let said_so: unsafe extern "C" fn() = unsafe { core::mem::transmute(LOADER_SAYS_SO) };
|
||||
unsafe { said_so() };
|
||||
|
||||
look_again();
|
||||
}
|
||||
|
||||
fn look_again() {
|
||||
let registry = unsafe { REGISTRY };
|
||||
if registry.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mapped = mapped_images();
|
||||
let known = unsafe { known() };
|
||||
|
||||
for image in &mapped {
|
||||
if !known.iter().any(|seen| seen.base == image.base) {
|
||||
announce(image);
|
||||
}
|
||||
}
|
||||
for seen in known.iter() {
|
||||
if !mapped.iter().any(|image| image.base == seen.base) {
|
||||
unsafe { gum_barebone_unregister_module(registry, seen.base) };
|
||||
}
|
||||
}
|
||||
|
||||
*known = mapped;
|
||||
}
|
||||
|
||||
fn announce(image: &Image) {
|
||||
let range = GumMemoryRange {
|
||||
base_address: image.base,
|
||||
size: image.size as gsize,
|
||||
};
|
||||
|
||||
let module = gum::gum_native_module_new(&image.path, "", &range);
|
||||
unsafe {
|
||||
gum_barebone_register_module(REGISTRY, module);
|
||||
g_object_unref(module as gpointer);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enumerate_exports_in_range(from: u64, to: u64, found: &mut FoundExportCallback<'_>) {
|
||||
let Some(image) = mapped_images().into_iter().find(|image| image.base == from) else {
|
||||
return;
|
||||
};
|
||||
let Some(module) = read_the_image(&image) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut asking = Asking {
|
||||
found,
|
||||
slide: slide_of(&image, module),
|
||||
from,
|
||||
to,
|
||||
};
|
||||
unsafe {
|
||||
gum_elf_module_enumerate_exports(
|
||||
module,
|
||||
Some(crate::signed_to_be_called_back(note_an_export, 0)),
|
||||
&mut asking as *mut Asking<'_, '_> as gpointer,
|
||||
);
|
||||
g_object_unref(module as gpointer);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
let Some(module) = read_the_image(&image) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut asking = AskingForSymbols {
|
||||
found,
|
||||
slide: slide_of(&image, module),
|
||||
from,
|
||||
to,
|
||||
};
|
||||
unsafe {
|
||||
gum_elf_module_enumerate_symbols(
|
||||
module,
|
||||
Some(crate::signed_to_be_called_back(note_a_symbol, 0)),
|
||||
&mut asking as *mut AskingForSymbols<'_, '_> as gpointer,
|
||||
);
|
||||
g_object_unref(module as gpointer);
|
||||
}
|
||||
}
|
||||
|
||||
struct AskingForSymbols<'a, 'b> {
|
||||
found: &'a mut FoundSymbolCallback<'b>,
|
||||
slide: u64,
|
||||
from: u64,
|
||||
to: u64,
|
||||
}
|
||||
|
||||
unsafe extern "C" fn note_a_symbol(
|
||||
details: *const GumElfSymbolDetails,
|
||||
asking: gpointer,
|
||||
) -> gboolean {
|
||||
let asking = unsafe { &mut *(asking as *mut AskingForSymbols<'_, '_>) };
|
||||
let details = unsafe { &*details };
|
||||
|
||||
if details.address == 0 {
|
||||
return 1;
|
||||
}
|
||||
|
||||
let address = details.address.wrapping_add(asking.slide);
|
||||
if address < asking.from || address >= asking.to {
|
||||
return 1;
|
||||
}
|
||||
|
||||
(asking.found)(
|
||||
details.name,
|
||||
address,
|
||||
details.size as u64,
|
||||
details.type_ as u8,
|
||||
details.bind != GumElfSymbolBind_GUM_ELF_BIND_LOCAL,
|
||||
) as gboolean
|
||||
}
|
||||
|
||||
fn slide_of(image: &Image, module: *mut GumElfModule) -> u64 {
|
||||
image
|
||||
.base
|
||||
.wrapping_sub(unsafe { gum_elf_module_get_preferred_address(module) })
|
||||
}
|
||||
|
||||
fn read_the_image(image: &Image) -> Option<*mut GumElfModule> {
|
||||
let path = CString::new(image.path.as_str()).ok()?;
|
||||
let data = contents_of(&path);
|
||||
if data.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let blob = g_bytes_new(data.as_ptr() as gconstpointer, data.len() as gsize);
|
||||
|
||||
let mut error: *mut GError = ptr::null_mut();
|
||||
let module = gum_elf_module_new_from_blob(blob, &mut error);
|
||||
g_bytes_unref(blob);
|
||||
|
||||
if module.is_null() {
|
||||
g_clear_error(&mut error);
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(module)
|
||||
}
|
||||
}
|
||||
|
||||
struct Asking<'a, 'b> {
|
||||
found: &'a mut FoundExportCallback<'b>,
|
||||
slide: u64,
|
||||
from: u64,
|
||||
to: u64,
|
||||
}
|
||||
|
||||
unsafe extern "C" fn note_an_export(details: *const GumExportDetails, asking: gpointer) -> gboolean {
|
||||
let asking = unsafe { &mut *(asking as *mut Asking<'_, '_>) };
|
||||
let details = unsafe { &*details };
|
||||
|
||||
let address = details.address.wrapping_add(asking.slide);
|
||||
if address < asking.from || address >= asking.to {
|
||||
return 1;
|
||||
}
|
||||
|
||||
(asking.found)(details.name, address) as gboolean
|
||||
}
|
||||
|
||||
fn where_the_loader_says_so() -> Option<u64> {
|
||||
let mut found = 0u64;
|
||||
for image in mapped_images() {
|
||||
let mut on_export = |name: *const crate::bindings::gchar, address: u64| {
|
||||
if unsafe { core::ffi::CStr::from_ptr(name) } == c"_dl_debug_state" {
|
||||
found = address;
|
||||
}
|
||||
|
||||
found == 0
|
||||
};
|
||||
enumerate_exports_in_range(image.base, image.base + image.size, &mut on_export);
|
||||
if found != 0 {
|
||||
return Some(found);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
unsafe fn known() -> &'static mut Vec<Image> {
|
||||
unsafe { (&raw mut KNOWN).as_mut().unwrap() }
|
||||
}
|
||||
|
||||
static mut KNOWN: Vec<Image> = Vec::new();
|
||||
static mut REGISTRY: *mut GumModuleRegistry = ptr::null_mut();
|
||||
static mut LOADER_SAYS_SO: *mut c_void = ptr::null_mut();
|
||||
|
||||
pub fn enumerate_ranges(found: &mut dyn FnMut(u64, u64, u32)) {
|
||||
let listed = contents_of(c"/proc/self/maps");
|
||||
|
||||
for line in listed.split(|byte| *byte == b'\n') {
|
||||
let Some(mapping) = mapping_in(line) else {
|
||||
continue;
|
||||
};
|
||||
if super::user::range_is_ours(mapping.start) {
|
||||
continue;
|
||||
}
|
||||
|
||||
found(mapping.start, mapping.end - mapping.start, mapping.protection);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn region_at(address: u64) -> Option<MemoryRegion> {
|
||||
if super::user::range_is_ours(address) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let listed = contents_of(c"/proc/self/maps");
|
||||
|
||||
for line in listed.split(|byte| *byte == b'\n') {
|
||||
let Some(mapping) = mapping_in(line) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if address >= mapping.start && address < mapping.end {
|
||||
return Some(MemoryRegion {
|
||||
base: mapping.start,
|
||||
size: mapping.end - mapping.start,
|
||||
protection: mapping.protection,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn mapped_images() -> Vec<Image> {
|
||||
let listed = contents_of(c"/proc/self/maps");
|
||||
|
||||
let mut images: Vec<Image> = Vec::new();
|
||||
for line in listed.split(|byte| *byte == b'\n') {
|
||||
let Some(mapping) = mapping_in(line) else {
|
||||
continue;
|
||||
};
|
||||
if mapping.path.is_empty() || super::user::range_is_ours(mapping.start) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let holds_code = mapping.protection & EXECUTABLE as u32 != 0;
|
||||
match images.last_mut() {
|
||||
Some(image) if image.path.as_bytes() == mapping.path => {
|
||||
image.size = mapping.end - image.base;
|
||||
image.holds_code |= holds_code;
|
||||
}
|
||||
_ => images.push(Image {
|
||||
path: String::from_utf8_lossy(mapping.path).into_owned(),
|
||||
base: mapping.start,
|
||||
size: mapping.end - mapping.start,
|
||||
holds_code,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
images.retain(|image| image.holds_code);
|
||||
|
||||
images
|
||||
}
|
||||
|
||||
fn mapping_in(line: &[u8]) -> Option<Mapping<'_>> {
|
||||
let boundary = line.iter().position(|byte| *byte == b'-')?;
|
||||
let start = number_in(&line[..boundary])?;
|
||||
|
||||
let rest = &line[boundary + 1..];
|
||||
let mut fields = rest.split(|byte| *byte == b' ');
|
||||
let end = number_in(fields.next()?)?;
|
||||
let how = fields.next()?;
|
||||
|
||||
Some(Mapping {
|
||||
path: line[..].iter().position(|byte| *byte == b'/').map_or(&[], |at| &line[at..]),
|
||||
start,
|
||||
end,
|
||||
protection: protection_of(how),
|
||||
})
|
||||
}
|
||||
|
||||
fn protection_of(how: &[u8]) -> u32 {
|
||||
let mut protection = 0;
|
||||
if how[0] == b'r' {
|
||||
protection |= READABLE as u32;
|
||||
}
|
||||
if how[1] == b'w' {
|
||||
protection |= WRITABLE as u32;
|
||||
}
|
||||
if how[2] == b'x' {
|
||||
protection |= EXECUTABLE as u32;
|
||||
}
|
||||
|
||||
protection
|
||||
}
|
||||
|
||||
fn number_in(digits: &[u8]) -> Option<u64> {
|
||||
let mut value = 0u64;
|
||||
for digit in digits {
|
||||
value = (value << 4) | (digit.to_ascii_lowercase() as char).to_digit(16)? as u64;
|
||||
}
|
||||
|
||||
Some(value)
|
||||
}
|
||||
|
||||
struct Image {
|
||||
path: String,
|
||||
base: u64,
|
||||
size: u64,
|
||||
holds_code: bool,
|
||||
}
|
||||
|
||||
struct Mapping<'a> {
|
||||
path: &'a [u8],
|
||||
start: u64,
|
||||
end: u64,
|
||||
protection: u32,
|
||||
}
|
||||
@@ -1,94 +0,0 @@
|
||||
// Linux backend. The agent reaches the kernel in one of two ways, and the rest
|
||||
// of the agent cannot tell which: loaded from the inside as a kernel module,
|
||||
// where every primitive is a call into a shim kbuild compiled against the
|
||||
// target's headers; or injected from the outside like XNU and Windows, where
|
||||
// the host patches the addresses it needs into .kernel_addrs and the memory it
|
||||
// cannot touch is reached over the hostlink.
|
||||
|
||||
use alloc::string::String;
|
||||
|
||||
pub fn stack_headroom() -> usize {
|
||||
let marker = 0usize;
|
||||
let here = &marker as *const usize as usize;
|
||||
|
||||
here - (here & !(STACK_SPAN - 1))
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
|
||||
pub(crate) const STACK_SPAN: usize = 16 * 1024;
|
||||
|
||||
#[cfg(any(target_arch = "arm", target_arch = "x86"))]
|
||||
pub(crate) const STACK_SPAN: usize = 8 * 1024;
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
mod kmod;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
mod arena;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
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")]
|
||||
pub(crate) mod layout;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
mod mapped;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
mod modules;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
mod kfault;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
mod native;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
mod relay;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
mod spawn;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
mod threads;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub(crate) mod user;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
mod processes;
|
||||
|
||||
#[cfg(feature = "linux")]
|
||||
pub use self::kmod::*;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub use self::injection::*;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub use self::relay::*;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub use self::facade::*;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub use self::native::{
|
||||
alloc_dma, free_dma, get_kernel_base, get_kernel_size, install_interrupt_handler, map_io,
|
||||
map_pages,
|
||||
mmio_interrupt, pci_interrupt, release_fault_reporter, release_interrupt, run_when_ready,
|
||||
patch_text, set_kernel_base, set_protection, virt_to_phys,
|
||||
};
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub use self::mapped::*;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub use self::modules::*;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub use self::processes::*;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub use self::spawn::*;
|
||||
#[cfg(feature = "linux-injected")]
|
||||
pub use self::threads::*;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum ModuleEvent {
|
||||
Loaded,
|
||||
Unloaded,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LoadedModule {
|
||||
pub name: String,
|
||||
pub version: String,
|
||||
pub base: u64,
|
||||
pub size: u64,
|
||||
}
|
||||
@@ -1,788 +0,0 @@
|
||||
use alloc::boxed::Box;
|
||||
use alloc::string::String;
|
||||
use alloc::vec::Vec;
|
||||
use core::ffi::{c_char, c_int, c_long, c_void};
|
||||
use core::ptr;
|
||||
|
||||
use super::layout::{field_offset, struct_size};
|
||||
use crate::bindings::{g_free, gsize, gum_memory_read};
|
||||
use super::{LoadedModule, ModuleEvent};
|
||||
|
||||
pub fn enumerate_modules() -> Vec<LoadedModule> {
|
||||
let Some(layout) = module_layout() else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let head = unsafe { _modules } as usize;
|
||||
if head == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut modules = Vec::new();
|
||||
let mut node = word_at(head);
|
||||
let mut left = MAX_MODULES;
|
||||
while node != head && node != 0 && left != 0 {
|
||||
let module = node - layout.list;
|
||||
if is_live(module, &layout) {
|
||||
modules.push(describe(module, &layout));
|
||||
}
|
||||
|
||||
node = word_at(node);
|
||||
left -= 1;
|
||||
}
|
||||
|
||||
modules
|
||||
}
|
||||
|
||||
pub fn enumerate_module_symbols(
|
||||
base: u64,
|
||||
on_symbol: &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool,
|
||||
) -> bool {
|
||||
let Some(layout) = module_layout() else {
|
||||
return false;
|
||||
};
|
||||
let Some(kallsyms) = layout.kallsyms.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
let Some(module) = module_at(base, layout) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let table = word_at(module + kallsyms.at);
|
||||
if table == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let symbols = word_at(table + kallsyms.symtab);
|
||||
let names = word_at(table + kallsyms.strtab);
|
||||
let count = u32_at(table + kallsyms.num_symtab) as usize;
|
||||
if symbols == 0 || names == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
for index in 0..count {
|
||||
let symbol = symbols + index * SYMBOL_SIZE;
|
||||
let value = symbol_value(symbol);
|
||||
if value == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let info = symbol_info(symbol);
|
||||
let name = (names + u32_at(symbol) as usize) as *const c_char;
|
||||
if !on_symbol(
|
||||
name,
|
||||
value as u64,
|
||||
symbol_size(symbol),
|
||||
info & 0xf,
|
||||
info >> 4 != LOCAL,
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn enumerate_module_exports(
|
||||
base: u64,
|
||||
on_export: &mut dyn FnMut(*const c_char, u64) -> bool,
|
||||
) -> bool {
|
||||
let Some(layout) = module_layout() else {
|
||||
return false;
|
||||
};
|
||||
let Some(exports) = layout.exports.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
let Some(module) = module_at(base, layout) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let table = word_at(module + exports.at);
|
||||
let count = u32_at(module + exports.count) as usize;
|
||||
if table == 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
for index in 0..count {
|
||||
let entry = table + index * exports.stride;
|
||||
let (name, address) = if exports.relative {
|
||||
(
|
||||
relative_target(entry + OFFSET_SIZE),
|
||||
relative_target(entry) as u64,
|
||||
)
|
||||
} else {
|
||||
(word_at(entry + WORD), word_at(entry) as u64)
|
||||
};
|
||||
if name == 0 || address == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !on_export(name as *const c_char, address) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
fn module_at(base: u64, layout: &Layout) -> Option<usize> {
|
||||
let head = unsafe { _modules } as usize;
|
||||
if head == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut node = word_at(head);
|
||||
let mut left = MAX_MODULES;
|
||||
while node != head && node != 0 && left != 0 {
|
||||
let module = node - layout.list;
|
||||
if describe(module, layout).base == base {
|
||||
return Some(module);
|
||||
}
|
||||
|
||||
node = word_at(node);
|
||||
left -= 1;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn relative_target(at: usize) -> usize {
|
||||
let offset = unsafe { (at as *const i32).read_volatile() };
|
||||
|
||||
at.wrapping_add_signed(offset as isize)
|
||||
}
|
||||
|
||||
pub fn watch_modules(on_event: fn(ModuleEvent, LoadedModule)) {
|
||||
if module_layout().is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
if _register_module_notifier.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
WATCHER = Some(on_event);
|
||||
NOTIFIER = Box::into_raw(Box::new(NotifierBlock {
|
||||
notifier_call: Some(NOTIFIER_CALL),
|
||||
next: ptr::null_mut(),
|
||||
priority: 0,
|
||||
}));
|
||||
|
||||
register_notifier(NOTIFIER as *mut c_void);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwatch_modules() {
|
||||
unsafe {
|
||||
if (&raw const WATCHER).read().is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
if _unregister_module_notifier.is_some() {
|
||||
unregister_notifier(NOTIFIER as *mut c_void);
|
||||
}
|
||||
|
||||
drop(Box::from_raw(NOTIFIER));
|
||||
NOTIFIER = ptr::null_mut();
|
||||
WATCHER = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
unsafe fn register_notifier(block: *mut c_void) {
|
||||
unsafe {
|
||||
if let Some(register) = _register_module_notifier {
|
||||
register(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
unsafe fn unregister_notifier(block: *mut c_void) {
|
||||
unsafe {
|
||||
if let Some(unregister) = _unregister_module_notifier {
|
||||
unregister(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
unsafe fn register_notifier(block: *mut c_void) {
|
||||
unsafe { frida_k_register_module_notifier(block) };
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
unsafe fn unregister_notifier(block: *mut c_void) {
|
||||
unsafe { frida_k_unregister_module_notifier(block) };
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
const NOTIFIER_CALL: unsafe extern "C" fn(*mut NotifierBlock, c_long, *mut c_void) -> c_int =
|
||||
on_module_state;
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
const NOTIFIER_CALL: unsafe extern "C" fn(*mut NotifierBlock, c_long, *mut c_void) -> c_int =
|
||||
frida_kcb_module_state;
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
#[unsafe(no_mangle)]
|
||||
unsafe extern "C" fn frida_cb_module_state(
|
||||
block: *mut NotifierBlock,
|
||||
action: c_long,
|
||||
module: *mut c_void,
|
||||
) -> c_int {
|
||||
unsafe { on_module_state(block, action, module) }
|
||||
}
|
||||
|
||||
unsafe extern "C" fn on_module_state(
|
||||
_block: *mut NotifierBlock,
|
||||
action: c_long,
|
||||
module: *mut c_void,
|
||||
) -> c_int {
|
||||
let kind = match action {
|
||||
MODULE_STATE_LIVE => ModuleEvent::Loaded,
|
||||
MODULE_STATE_GOING => ModuleEvent::Unloaded,
|
||||
_ => return NOTIFY_DONE,
|
||||
};
|
||||
|
||||
let Some(on_event) = (unsafe { WATCHER }) else {
|
||||
return NOTIFY_DONE;
|
||||
};
|
||||
let Some(layout) = module_layout() else {
|
||||
return NOTIFY_DONE;
|
||||
};
|
||||
|
||||
on_event(kind, describe(module as usize, &layout));
|
||||
|
||||
NOTIFY_DONE
|
||||
}
|
||||
|
||||
fn describe(module: usize, layout: &Layout) -> LoadedModule {
|
||||
let (base, size) = match layout.memory {
|
||||
Memory::Described { at, base, size } => (word_at(module + at + base), word_at(module + at + size)),
|
||||
Memory::Split { base, size } => (word_at(module + base), word_at(module + size)),
|
||||
};
|
||||
|
||||
LoadedModule {
|
||||
name: text_at(module + layout.name),
|
||||
version: layout
|
||||
.version
|
||||
.map(|at| text_at(word_at(module + at)))
|
||||
.unwrap_or_default(),
|
||||
base: base as u64,
|
||||
size: (size & 0xffff_ffff) as u64,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_live(module: usize, layout: &Layout) -> bool {
|
||||
let Some(at) = layout.state else {
|
||||
return true;
|
||||
};
|
||||
|
||||
let state = unsafe { ((module + at) as *const u32).read_volatile() };
|
||||
|
||||
state == MODULE_STATE_LIVE as u32
|
||||
}
|
||||
|
||||
fn module_layout() -> Option<&'static Layout> {
|
||||
unsafe {
|
||||
let known = (&raw mut LAYOUT).as_mut().unwrap();
|
||||
if known.is_none() {
|
||||
*known = discover_layout();
|
||||
}
|
||||
known.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
static mut LAYOUT: Option<Layout> = None;
|
||||
|
||||
struct Layout {
|
||||
list: usize,
|
||||
name: usize,
|
||||
version: Option<usize>,
|
||||
state: Option<usize>,
|
||||
memory: Memory,
|
||||
kallsyms: Option<Kallsyms>,
|
||||
exports: Option<Exports>,
|
||||
}
|
||||
|
||||
struct Kallsyms {
|
||||
at: usize,
|
||||
symtab: usize,
|
||||
num_symtab: usize,
|
||||
strtab: usize,
|
||||
}
|
||||
|
||||
struct Exports {
|
||||
at: usize,
|
||||
count: usize,
|
||||
stride: usize,
|
||||
relative: bool,
|
||||
}
|
||||
|
||||
enum Memory {
|
||||
Described { at: usize, base: usize, size: usize },
|
||||
Split { base: usize, size: usize },
|
||||
}
|
||||
|
||||
fn discover_layout() -> Option<Layout> {
|
||||
layout_from_types().or_else(layout_from_probing)
|
||||
}
|
||||
|
||||
fn layout_from_types() -> Option<Layout> {
|
||||
let memory = match field_offset("module", "mem") {
|
||||
Some(at) => Memory::Described {
|
||||
at,
|
||||
base: field_offset("module_memory", "base")?,
|
||||
size: field_offset("module_memory", "size")?,
|
||||
},
|
||||
None => match field_offset("module", "core_layout") {
|
||||
Some(at) => Memory::Described {
|
||||
at,
|
||||
base: field_offset("module_layout", "base")?,
|
||||
size: field_offset("module_layout", "size")?,
|
||||
},
|
||||
None => Memory::Split {
|
||||
base: field_offset("module", "module_core")?,
|
||||
size: field_offset("module", "core_size")?,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let list = field_offset("module", "list")?;
|
||||
|
||||
Some(Layout {
|
||||
list,
|
||||
name: field_offset("module", "name")?,
|
||||
version: field_offset("module", "version"),
|
||||
state: field_offset("module", "state"),
|
||||
memory,
|
||||
kallsyms: kallsyms_from_types(),
|
||||
exports: exports_from_types(list),
|
||||
})
|
||||
}
|
||||
|
||||
fn layout_from_probing() -> Option<Layout> {
|
||||
let list = WORD;
|
||||
let name = list + 2 * WORD;
|
||||
let start = (name + MODULE_NAME_LEN + WORD - 1) & !(WORD - 1);
|
||||
let (at, size) = probe_memory(list, start)?;
|
||||
let memory = Memory::Described { at, base: 0, size };
|
||||
|
||||
Some(Layout {
|
||||
list,
|
||||
name,
|
||||
version: None,
|
||||
state: Some(0),
|
||||
kallsyms: probe_kallsyms(list, start, &memory),
|
||||
exports: probe_exports(list, start, &memory),
|
||||
memory,
|
||||
})
|
||||
}
|
||||
|
||||
fn probe_memory(list: usize, start: usize) -> Option<(usize, usize)> {
|
||||
let head = unsafe { _modules } as usize;
|
||||
if head == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
for at in (start..PROBE_SPAN).step_by(WORD) {
|
||||
for size in (WORD..=MAX_SIZE_DISTANCE).step_by(WORD) {
|
||||
if each_module(head, list, |module| describes_memory(module, at, size)) {
|
||||
return Some((at, size));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn describes_memory(module: usize, at: usize, size: usize) -> bool {
|
||||
let base = word_at(module + at);
|
||||
let size = unsafe { ((module + at + size) as *const u32).read_volatile() } as usize;
|
||||
|
||||
base != 0
|
||||
&& base & (PAGE_SIZE - 1) == 0
|
||||
&& size != 0
|
||||
&& size & (PAGE_SIZE - 1) == 0
|
||||
&& size <= MAX_MODULE_SIZE
|
||||
&& base.abs_diff(module) <= MODULE_REACH
|
||||
}
|
||||
|
||||
fn each_module(head: usize, list: usize, is_sane: impl Fn(usize) -> bool) -> bool {
|
||||
let mut node = word_at(head);
|
||||
let mut seen = 0;
|
||||
let mut left = MAX_MODULES;
|
||||
while node != head && node != 0 && left != 0 {
|
||||
if !is_sane(node - list) {
|
||||
return false;
|
||||
}
|
||||
seen += 1;
|
||||
|
||||
node = word_at(node);
|
||||
left -= 1;
|
||||
}
|
||||
|
||||
seen != 0
|
||||
}
|
||||
|
||||
fn kallsyms_from_types() -> Option<Kallsyms> {
|
||||
Some(Kallsyms {
|
||||
at: field_offset("module", "kallsyms")?,
|
||||
symtab: field_offset("mod_kallsyms", "symtab")?,
|
||||
num_symtab: field_offset("mod_kallsyms", "num_symtab")?,
|
||||
strtab: field_offset("mod_kallsyms", "strtab")?,
|
||||
})
|
||||
}
|
||||
|
||||
fn exports_from_types(_list: usize) -> Option<Exports> {
|
||||
let at = field_offset("module", "syms")?;
|
||||
let count = field_offset("module", "num_syms")?;
|
||||
let relative = field_offset("kernel_symbol", "value_offset").is_some();
|
||||
|
||||
Some(Exports {
|
||||
at,
|
||||
count,
|
||||
stride: struct_size("kernel_symbol").unwrap_or(stride_of_exports(relative)),
|
||||
relative,
|
||||
})
|
||||
}
|
||||
|
||||
fn probe_kallsyms(list: usize, start: usize, memory: &Memory) -> Option<Kallsyms> {
|
||||
let head = unsafe { _modules } as usize;
|
||||
|
||||
for at in (start..PROBE_SPAN).step_by(WORD) {
|
||||
if each_module(head, list, |module| describes_kallsyms(module, at, memory)) {
|
||||
return Some(Kallsyms {
|
||||
at,
|
||||
symtab: 0,
|
||||
num_symtab: WORD,
|
||||
strtab: 2 * WORD,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn describes_kallsyms(module: usize, at: usize, memory: &Memory) -> bool {
|
||||
let owned = regions_of(module, memory);
|
||||
|
||||
let table = word_at(module + at);
|
||||
if !owned.holds(table, 3 * WORD) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some(symbols) = try_read_word(table) else {
|
||||
return false;
|
||||
};
|
||||
let Some(count) = try_read_u32(table + WORD) else {
|
||||
return false;
|
||||
};
|
||||
let Some(names) = try_read_word(table + 2 * WORD) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
let count = count as usize;
|
||||
if count == 0 || count > MAX_SYMBOLS {
|
||||
return false;
|
||||
}
|
||||
if !owned.holds(symbols, count * SYMBOL_SIZE) || !owned.holds(names, 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try_read_byte(names) == Some(0)
|
||||
}
|
||||
|
||||
fn probe_exports(list: usize, start: usize, memory: &Memory) -> Option<Exports> {
|
||||
let head = unsafe { _modules } as usize;
|
||||
|
||||
for at in (start..PROBE_SPAN).step_by(WORD) {
|
||||
for count in counts_near(at, start) {
|
||||
for relative in [true, false] {
|
||||
let stride = stride_of_exports(relative);
|
||||
if exports_shaped(head, list, at, count, stride, relative, memory) {
|
||||
return Some(Exports {
|
||||
at,
|
||||
count,
|
||||
stride,
|
||||
relative,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn exports_shaped(
|
||||
head: usize,
|
||||
list: usize,
|
||||
at: usize,
|
||||
count: usize,
|
||||
stride: usize,
|
||||
relative: bool,
|
||||
memory: &Memory,
|
||||
) -> bool {
|
||||
let mut evidence = false;
|
||||
let mut node = word_at(head);
|
||||
let mut left = MAX_MODULES;
|
||||
while node != head && node != 0 && left != 0 {
|
||||
let module = node - list;
|
||||
let table = word_at(module + at);
|
||||
let exported = u32_at(module + count) as usize;
|
||||
|
||||
if table == 0 || exported == 0 {
|
||||
if table != 0 || exported != 0 {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if exported > MAX_EXPORTS {
|
||||
return false;
|
||||
}
|
||||
|
||||
let owned = regions_of(module, memory);
|
||||
if !owned.holds(table, exported * stride) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for index in 0..exported.min(EXPORTS_SAMPLED) {
|
||||
if !names_an_export(table + index * stride, relative, &owned) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
evidence = true;
|
||||
}
|
||||
|
||||
node = word_at(node);
|
||||
left -= 1;
|
||||
}
|
||||
|
||||
evidence
|
||||
}
|
||||
|
||||
fn names_an_export(entry: usize, relative: bool, owned: &Regions) -> bool {
|
||||
let name = if relative {
|
||||
let Some(offset) = try_read_u32(entry + OFFSET_SIZE) else {
|
||||
return false;
|
||||
};
|
||||
(entry + OFFSET_SIZE).wrapping_add_signed(offset as i32 as isize)
|
||||
} else {
|
||||
match try_read_word(entry + WORD) {
|
||||
Some(name) => name,
|
||||
None => return false,
|
||||
}
|
||||
};
|
||||
if !owned.holds(name, 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
match try_read_byte(name) {
|
||||
Some(first) => first.is_ascii_alphabetic() || first == b'_',
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn counts_near(at: usize, start: usize) -> impl Iterator<Item = usize> {
|
||||
let after = (WORD..=MAX_COUNT_DISTANCE).step_by(WORD).map(move |d| at + d);
|
||||
let before = (WORD..=MAX_COUNT_DISTANCE)
|
||||
.step_by(WORD)
|
||||
.filter_map(move |d| at.checked_sub(d).filter(|&count| count >= start));
|
||||
|
||||
after.chain(before)
|
||||
}
|
||||
|
||||
fn stride_of_exports(relative: bool) -> usize {
|
||||
if relative { 3 * OFFSET_SIZE } else { 3 * WORD }
|
||||
}
|
||||
|
||||
fn regions_of(module: usize, memory: &Memory) -> Regions {
|
||||
let Memory::Described { at, base, size } = *memory else {
|
||||
return Regions::default();
|
||||
};
|
||||
|
||||
let mut owned = Regions::default();
|
||||
for offset in (0..REGION_WINDOW).step_by(WORD) {
|
||||
let entry = module + at + offset;
|
||||
let start = word_at(entry + base);
|
||||
let span = u32_at(entry + size) as usize;
|
||||
|
||||
if start != 0
|
||||
&& start & (PAGE_SIZE - 1) == 0
|
||||
&& span != 0
|
||||
&& span & (PAGE_SIZE - 1) == 0
|
||||
&& span <= MAX_MODULE_SIZE
|
||||
&& start.abs_diff(module) <= MODULE_REACH
|
||||
{
|
||||
owned.add(start, span);
|
||||
}
|
||||
}
|
||||
|
||||
owned
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Regions {
|
||||
entries: [(usize, usize); MAX_REGIONS],
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl Regions {
|
||||
fn add(&mut self, base: usize, size: usize) {
|
||||
if self.count == MAX_REGIONS {
|
||||
return;
|
||||
}
|
||||
|
||||
self.entries[self.count] = (base, size);
|
||||
self.count += 1;
|
||||
}
|
||||
|
||||
fn holds(&self, address: usize, span: usize) -> bool {
|
||||
let Some(end) = address.checked_add(span) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
self.entries[..self.count]
|
||||
.iter()
|
||||
.any(|&(base, size)| address >= base && end <= base + size)
|
||||
}
|
||||
}
|
||||
|
||||
fn try_read_word(address: usize) -> Option<usize> {
|
||||
Some(usize::from_ne_bytes(try_read(address)?))
|
||||
}
|
||||
|
||||
fn try_read_u32(address: usize) -> Option<u32> {
|
||||
let bytes = try_read::<4>(address)?;
|
||||
Some(u32::from_ne_bytes(bytes))
|
||||
}
|
||||
|
||||
fn try_read_byte(address: usize) -> Option<u8> {
|
||||
Some(try_read::<1>(address)?[0])
|
||||
}
|
||||
|
||||
fn try_read<const N: usize>(address: usize) -> Option<[u8; N]> {
|
||||
unsafe {
|
||||
let mut read: gsize = 0;
|
||||
let data = gum_memory_read(address as *const c_void, N as gsize, &mut read);
|
||||
if data.is_null() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut bytes = [0u8; N];
|
||||
core::ptr::copy_nonoverlapping(data as *const u8, bytes.as_mut_ptr(), N);
|
||||
g_free(data as *mut c_void);
|
||||
|
||||
Some(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
fn word_at(address: usize) -> usize {
|
||||
unsafe { (address as *const usize).read_volatile() }
|
||||
}
|
||||
|
||||
fn u32_at(address: usize) -> u32 {
|
||||
unsafe { (address as *const u32).read_volatile() }
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
fn symbol_value(symbol: usize) -> usize {
|
||||
word_at(symbol + 8)
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
fn symbol_size(symbol: usize) -> u64 {
|
||||
word_at(symbol + 16) as u64
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "64")]
|
||||
fn symbol_info(symbol: usize) -> u8 {
|
||||
unsafe { ((symbol + 4) as *const u8).read_volatile() }
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
fn symbol_value(symbol: usize) -> usize {
|
||||
word_at(symbol + 4)
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
fn symbol_size(symbol: usize) -> u64 {
|
||||
word_at(symbol + 8) as u64
|
||||
}
|
||||
|
||||
#[cfg(target_pointer_width = "32")]
|
||||
fn symbol_info(symbol: usize) -> u8 {
|
||||
unsafe { ((symbol + 12) as *const u8).read_volatile() }
|
||||
}
|
||||
|
||||
fn text_at(address: usize) -> String {
|
||||
if address == 0 {
|
||||
return String::new();
|
||||
}
|
||||
|
||||
let mut text = String::new();
|
||||
for i in 0..MAX_NAME {
|
||||
let byte = unsafe { ((address + i) as *const u8).read_volatile() };
|
||||
if byte == 0 {
|
||||
break;
|
||||
}
|
||||
text.push(byte as char);
|
||||
}
|
||||
|
||||
text
|
||||
}
|
||||
|
||||
static mut WATCHER: Option<fn(ModuleEvent, LoadedModule)> = None;
|
||||
|
||||
static mut NOTIFIER: *mut NotifierBlock = ptr::null_mut();
|
||||
|
||||
#[repr(C)]
|
||||
struct NotifierBlock {
|
||||
notifier_call:
|
||||
Option<unsafe extern "C" fn(*mut NotifierBlock, c_long, *mut c_void) -> c_int>,
|
||||
next: *mut NotifierBlock,
|
||||
priority: c_int,
|
||||
}
|
||||
|
||||
const MODULE_STATE_LIVE: c_long = 0;
|
||||
const MODULE_STATE_GOING: c_long = 2;
|
||||
const NOTIFY_DONE: c_int = 0;
|
||||
const MAX_MODULES: usize = 4096;
|
||||
const WORD: usize = core::mem::size_of::<usize>();
|
||||
const MODULE_NAME_LEN: usize = 64 - WORD;
|
||||
const PAGE_SIZE: usize = 4096;
|
||||
const PROBE_SPAN: usize = 1024;
|
||||
const MAX_MODULE_SIZE: usize = 256 * 1024 * 1024;
|
||||
const MODULE_REACH: usize = 512 * 1024 * 1024;
|
||||
const MAX_SIZE_DISTANCE: usize = 3 * WORD;
|
||||
const MAX_COUNT_DISTANCE: usize = 3 * WORD;
|
||||
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 MAX_SYMBOLS: usize = 1 << 20;
|
||||
const MAX_EXPORTS: usize = 1 << 16;
|
||||
const EXPORTS_SAMPLED: usize = 64;
|
||||
const LOCAL: u8 = 0;
|
||||
const MAX_NAME: usize = 64;
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
unsafe extern "C" {
|
||||
fn frida_kcb_module_state(
|
||||
block: *mut NotifierBlock,
|
||||
action: c_long,
|
||||
module: *mut c_void,
|
||||
) -> c_int;
|
||||
fn frida_k_register_module_notifier(block: *mut c_void) -> c_int;
|
||||
fn frida_k_unregister_module_notifier(block: *mut c_void) -> c_int;
|
||||
}
|
||||
|
||||
unsafe extern "C" {
|
||||
static _modules: *const c_void;
|
||||
static _register_module_notifier: Option<unsafe extern "C" fn(*mut c_void) -> c_int>;
|
||||
#[allow(dead_code)]
|
||||
static _unregister_module_notifier: Option<unsafe extern "C" fn(*mut c_void) -> c_int>;
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,790 +0,0 @@
|
||||
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;
|
||||
|
||||
pub struct ProcessInfo {
|
||||
pub id: u32,
|
||||
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)) {
|
||||
let Some(layout) = task_layout() else {
|
||||
return;
|
||||
};
|
||||
|
||||
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) {
|
||||
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) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn running_task_ids() -> Vec<u32> {
|
||||
let Some(layout) = task_layout() else {
|
||||
return Vec::new();
|
||||
};
|
||||
|
||||
let flags = lock_tasklist();
|
||||
let ids = tasks_of(layout.init, layout.list)
|
||||
.map(|task| read_id(task, layout))
|
||||
.collect();
|
||||
unlock_tasklist(flags);
|
||||
|
||||
ids
|
||||
}
|
||||
|
||||
pub unsafe fn let_go_of(file: *mut c_void) {
|
||||
unsafe { _fput(file) };
|
||||
}
|
||||
|
||||
pub fn task_with_id(id: u32) -> Option<usize> {
|
||||
let layout = task_layout()?;
|
||||
|
||||
let flags = lock_tasklist();
|
||||
let found = tasks_of(layout.init, layout.list).find(|task| read_id(*task, layout) == id);
|
||||
unlock_tasklist(flags);
|
||||
|
||||
found
|
||||
}
|
||||
|
||||
pub fn describe_process(process: &ProcessInfo) -> *const u8 {
|
||||
process.name
|
||||
}
|
||||
|
||||
pub fn enumerate_icons(_path: *const u8, _found: &mut dyn FnMut(&[u8])) {}
|
||||
|
||||
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> {
|
||||
let mut snapshot = Vec::with_capacity(tasks_of(layout.init, layout.list).count() + SNAPSHOT_HEADROOM);
|
||||
|
||||
let flags = lock_tasklist();
|
||||
for task in tasks_of(layout.init, layout.list) {
|
||||
if snapshot.len() == snapshot.capacity() {
|
||||
break;
|
||||
}
|
||||
snapshot.push(read_task(task, layout));
|
||||
}
|
||||
unlock_tasklist(flags);
|
||||
|
||||
snapshot
|
||||
}
|
||||
|
||||
fn read_task(task: usize, layout: &Layout) -> Task {
|
||||
let mut name = [0u8; NAME_SIZE + 1];
|
||||
read_kernel(task + layout.name, &mut name[..NAME_SIZE]);
|
||||
|
||||
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_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)
|
||||
}
|
||||
|
||||
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 {
|
||||
if executable.is_null() {
|
||||
return ptr::null();
|
||||
}
|
||||
|
||||
let path = unsafe {
|
||||
_file_path(
|
||||
executable,
|
||||
buffer.as_mut_ptr() as *mut c_char,
|
||||
buffer.len() as c_int,
|
||||
)
|
||||
};
|
||||
if (path as usize) >= ERROR_POINTER_START {
|
||||
return ptr::null();
|
||||
}
|
||||
|
||||
path as *const u8
|
||||
}
|
||||
|
||||
fn tasks_of(init: usize, list: usize) -> TaskList {
|
||||
TaskList {
|
||||
head: init + list,
|
||||
node: init + list,
|
||||
list,
|
||||
left: MAX_TASKS,
|
||||
}
|
||||
}
|
||||
|
||||
struct TaskList {
|
||||
head: usize,
|
||||
node: usize,
|
||||
list: usize,
|
||||
left: usize,
|
||||
}
|
||||
|
||||
impl Iterator for TaskList {
|
||||
type Item = usize;
|
||||
|
||||
fn next(&mut self) -> Option<usize> {
|
||||
if self.left == 0 {
|
||||
return None;
|
||||
}
|
||||
self.left -= 1;
|
||||
|
||||
let node = read_kernel_word(self.node)?;
|
||||
if node == self.head {
|
||||
return None;
|
||||
}
|
||||
self.node = node;
|
||||
|
||||
Some(node - self.list)
|
||||
}
|
||||
}
|
||||
|
||||
fn task_layout() -> Option<&'static Layout> {
|
||||
unsafe {
|
||||
let known = (&raw mut LAYOUT).as_mut().unwrap();
|
||||
if known.is_none() {
|
||||
*known = discover_layout();
|
||||
}
|
||||
known.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
static mut LAYOUT: Option<Layout> = None;
|
||||
|
||||
struct Layout {
|
||||
init: usize,
|
||||
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> {
|
||||
let init = unsafe { _init_task } as usize;
|
||||
|
||||
if let Some(layout) = described_layout(init) {
|
||||
return Some(layout);
|
||||
}
|
||||
|
||||
let image = read_task_image(init);
|
||||
|
||||
let name = find(&image, IDLE_TASK_NAME)?;
|
||||
let (list, id) = find_task_list(init, &image, name)?;
|
||||
|
||||
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: 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"),
|
||||
})
|
||||
}
|
||||
|
||||
fn read_task_image(task: usize) -> Vec<u8> {
|
||||
let mut image = Vec::new();
|
||||
image.resize(MAX_TASK_SIZE, 0u8);
|
||||
|
||||
let mut readable = 0;
|
||||
while readable != image.len() {
|
||||
if !read_kernel(task + readable, &mut image[readable..readable + READ_CHUNK]) {
|
||||
break;
|
||||
}
|
||||
readable += READ_CHUNK;
|
||||
}
|
||||
image.truncate(readable);
|
||||
|
||||
image
|
||||
}
|
||||
|
||||
fn find(image: &[u8], text: &[u8]) -> Option<usize> {
|
||||
image.windows(text.len()).position(|window| window == text)
|
||||
}
|
||||
|
||||
fn find_task_list(init: usize, image: &[u8], name: usize) -> Option<(usize, usize)> {
|
||||
let mut longest: Option<(usize, usize, usize)> = None;
|
||||
|
||||
for list in (0..image.len() - LIST_SIZE).step_by(WORD_SIZE) {
|
||||
if !heads_a_circular_list(init, image, list) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(tasks) = sample_tasks(init, list, name) else {
|
||||
continue;
|
||||
};
|
||||
let Some(id) = find_identifier(&tasks) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let longer = match longest {
|
||||
Some((sampled, _, _)) => tasks.len() > sampled,
|
||||
None => true,
|
||||
};
|
||||
if longer {
|
||||
longest = Some((tasks.len(), list, id));
|
||||
}
|
||||
}
|
||||
|
||||
longest.map(|(_, list, id)| (list, id))
|
||||
}
|
||||
|
||||
fn heads_a_circular_list(init: usize, image: &[u8], list: usize) -> bool {
|
||||
let head = init + list;
|
||||
let next = word_in(image, list);
|
||||
let previous = word_in(image, list + WORD_SIZE);
|
||||
|
||||
let kernel_space = kernel_space_holding(init);
|
||||
if !is_kernel_address(next, kernel_space) || !is_kernel_address(previous, kernel_space) || next == head {
|
||||
return false;
|
||||
}
|
||||
|
||||
read_kernel_word(next + WORD_SIZE) == Some(head) && read_kernel_word(previous) == Some(head)
|
||||
}
|
||||
|
||||
fn sample_tasks(init: usize, list: usize, name: usize) -> Option<Vec<usize>> {
|
||||
let mut tasks = Vec::new();
|
||||
|
||||
for task in tasks_of(init, list) {
|
||||
if !names_a_task(task + name) {
|
||||
return None;
|
||||
}
|
||||
|
||||
tasks.push(task);
|
||||
if tasks.len() == MAX_SAMPLED_TASKS {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if tasks.len() < MIN_SAMPLED_TASKS {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(tasks)
|
||||
}
|
||||
|
||||
fn names_a_task(name: usize) -> bool {
|
||||
let mut text = [0u8; NAME_SIZE];
|
||||
if !read_kernel(name, &mut text) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let Some(end) = text.iter().position(|letter| *letter == 0) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
end != 0 && text[..end].iter().all(|letter| *letter >= 0x20 && *letter < 0x7f)
|
||||
}
|
||||
|
||||
fn find_identifier(tasks: &[usize]) -> Option<usize> {
|
||||
let mut identifiers = Vec::with_capacity(tasks.len());
|
||||
|
||||
for id in (0..MAX_TASK_SIZE - 8).step_by(4) {
|
||||
identifiers.clear();
|
||||
|
||||
let leads_every_task = tasks.iter().all(|task| {
|
||||
let Some((own, group)) = read_kernel_pair(*task + id) else {
|
||||
return false;
|
||||
};
|
||||
if own != group || own >= MAX_IDENTIFIER || identifiers.contains(&own) {
|
||||
return false;
|
||||
}
|
||||
identifiers.push(own);
|
||||
|
||||
true
|
||||
});
|
||||
|
||||
if leads_every_task {
|
||||
return Some(id);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn read_kernel_pair(address: usize) -> Option<(u32, u32)> {
|
||||
let mut bytes = [0u8; 8];
|
||||
if !read_kernel(address, &mut bytes) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((
|
||||
u32::from_ne_bytes(bytes[..4].try_into().unwrap()),
|
||||
u32::from_ne_bytes(bytes[4..].try_into().unwrap()),
|
||||
))
|
||||
}
|
||||
|
||||
fn read_kernel_word(address: usize) -> Option<usize> {
|
||||
let mut bytes = [0u8; WORD_SIZE];
|
||||
if !read_kernel(address, &mut bytes) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(usize::from_ne_bytes(bytes))
|
||||
}
|
||||
|
||||
fn read_kernel(address: usize, destination: &mut [u8]) -> bool {
|
||||
let read = unsafe {
|
||||
_copy_from_kernel_nofault(
|
||||
destination.as_mut_ptr() as *mut c_void,
|
||||
address as *const c_void,
|
||||
destination.len(),
|
||||
)
|
||||
};
|
||||
|
||||
read == 0
|
||||
}
|
||||
|
||||
fn word_in(image: &[u8], offset: usize) -> usize {
|
||||
usize::from_ne_bytes(image[offset..offset + WORD_SIZE].try_into().unwrap())
|
||||
}
|
||||
|
||||
fn is_kernel_address(address: usize, kernel_space: usize) -> bool {
|
||||
address >= kernel_space && address % WORD_SIZE == 0
|
||||
}
|
||||
|
||||
fn kernel_space_holding(address: usize) -> usize {
|
||||
address & !(SPLIT_GRANULARITY - 1)
|
||||
}
|
||||
|
||||
const SPLIT_GRANULARITY: usize = 1 << 30;
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
fn lock_tasklist() -> usize {
|
||||
unsafe { frida_k_lock_tasklist() }
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
fn lock_tasklist() -> usize {
|
||||
unsafe {
|
||||
match (__raw_read_lock_irqsave, __raw_read_unlock_irqrestore) {
|
||||
(Some(lock), Some(_)) => lock(_tasklist_lock),
|
||||
_ => {
|
||||
__raw_read_lock.unwrap()(_tasklist_lock);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
fn unlock_tasklist(flags: usize) {
|
||||
unsafe { frida_k_unlock_tasklist(flags) };
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
fn unlock_tasklist(flags: usize) {
|
||||
unsafe {
|
||||
match (__raw_read_lock_irqsave, __raw_read_unlock_irqrestore) {
|
||||
(Some(_), Some(unlock)) => unlock(_tasklist_lock, flags),
|
||||
_ => __raw_read_unlock.unwrap()(_tasklist_lock),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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"),
|
||||
super::layout::field_offset("task_struct", "thread_node"),
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
|
||||
let mut freed: [*mut c_void; PIDTYPE_MAX] = [ptr::null_mut(); PIDTYPE_MAX];
|
||||
|
||||
let flags = write_lock_tasklist();
|
||||
unsafe {
|
||||
detach_pid(freed.as_mut_ptr(), task, PIDTYPE_TGID);
|
||||
unlink(task as usize + tasks);
|
||||
unlink(task as usize + sibling);
|
||||
unlink(task as usize + thread_node);
|
||||
}
|
||||
write_unlock_tasklist(flags);
|
||||
|
||||
unsafe { free_pids(freed.as_mut_ptr()) };
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
unsafe fn detach_pid(pids: *mut *mut c_void, task: *mut c_void, kind: c_int) {
|
||||
unsafe { frida_k_detach_pid(pids, task, kind) };
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
unsafe fn detach_pid(pids: *mut *mut c_void, task: *mut c_void, kind: c_int) {
|
||||
unsafe { _detach_pid(pids, task, kind) };
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
unsafe fn free_pids(pids: *mut *mut c_void) {
|
||||
unsafe { frida_k_free_pids(pids) };
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
unsafe fn free_pids(pids: *mut *mut c_void) {
|
||||
unsafe { _free_pids(pids) };
|
||||
}
|
||||
|
||||
unsafe fn unlink(node: usize) {
|
||||
let next = unsafe { (node as *const usize).read() };
|
||||
let prev = unsafe { ((node + WORD_SIZE) as *const usize).read() };
|
||||
unsafe {
|
||||
(prev as *mut usize).write(next);
|
||||
((next + WORD_SIZE) as *mut usize).write(prev);
|
||||
(node as *mut usize).write(node);
|
||||
((node + WORD_SIZE) as *mut usize).write(node);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
fn write_lock_tasklist() -> usize {
|
||||
unsafe { frida_k_write_lock_tasklist() }
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
fn write_lock_tasklist() -> usize {
|
||||
unsafe {
|
||||
match (__raw_write_lock_irqsave, __raw_write_unlock_irqrestore) {
|
||||
(Some(lock), Some(_)) => lock(_tasklist_lock),
|
||||
_ => {
|
||||
__raw_write_lock.unwrap()(_tasklist_lock);
|
||||
0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
fn write_unlock_tasklist(flags: usize) {
|
||||
unsafe { frida_k_write_unlock_tasklist(flags) };
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
fn write_unlock_tasklist(flags: usize) {
|
||||
unsafe {
|
||||
match (__raw_write_lock_irqsave, __raw_write_unlock_irqrestore) {
|
||||
(Some(_), Some(unlock)) => unlock(_tasklist_lock, flags),
|
||||
_ => __raw_write_unlock.unwrap()(_tasklist_lock),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
const WORD_SIZE: usize = size_of::<usize>();
|
||||
const LIST_SIZE: usize = 2 * WORD_SIZE;
|
||||
|
||||
const MAX_TASKS: usize = 8192;
|
||||
const SNAPSHOT_HEADROOM: usize = 64;
|
||||
const MIN_SAMPLED_TASKS: usize = 4;
|
||||
const MAX_SAMPLED_TASKS: usize = 32;
|
||||
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;
|
||||
static __raw_read_lock: Option<unsafe extern "C" fn(*mut c_void)>;
|
||||
static __raw_read_unlock: Option<unsafe extern "C" fn(*mut c_void)>;
|
||||
static __raw_read_lock_irqsave: Option<unsafe extern "C" fn(*mut c_void) -> usize>;
|
||||
static __raw_read_unlock_irqrestore: Option<unsafe extern "C" fn(*mut c_void, usize)>;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _detach_pid: unsafe extern "C" fn(*mut *mut c_void, *mut c_void, c_int);
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _free_pids: unsafe extern "C" fn(*mut *mut c_void);
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static __raw_write_lock: Option<unsafe extern "C" fn(*mut c_void)>;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static __raw_write_unlock: Option<unsafe extern "C" fn(*mut c_void)>;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static __raw_write_lock_irqsave: Option<unsafe extern "C" fn(*mut c_void) -> usize>;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static __raw_write_unlock_irqrestore: Option<unsafe extern "C" fn(*mut c_void, usize)>;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _get_task_exe_file: unsafe extern "C" fn(*mut c_void) -> *mut c_void;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _file_path: unsafe extern "C" fn(*mut c_void, *mut c_char, c_int) -> *const c_char;
|
||||
#[cfg(not(target_arch = "x86"))]
|
||||
static _fput: unsafe extern "C" fn(*mut c_void);
|
||||
#[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"]
|
||||
fn _copy_from_kernel_nofault(a0: *mut c_void, a1: *const c_void, a2: usize) -> c_long;
|
||||
fn frida_k_lock_tasklist() -> usize;
|
||||
fn frida_k_unlock_tasklist(flags: usize);
|
||||
fn frida_k_write_lock_tasklist() -> usize;
|
||||
fn frida_k_write_unlock_tasklist(flags: usize);
|
||||
fn frida_k_detach_pid(pids: *mut *mut c_void, task: *mut c_void, kind: c_int);
|
||||
fn frida_k_free_pids(pids: *mut *mut c_void);
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "x86")]
|
||||
unsafe extern "C" {
|
||||
#[link_name = "frida_k_get_task_exe_file"]
|
||||
fn _get_task_exe_file(a0: *mut c_void) -> *mut c_void;
|
||||
#[link_name = "frida_k_file_path"]
|
||||
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);
|
||||
}
|
||||
Loaded 100 of 285 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user