Compare commits

...
30 Commits
Author SHA1 Message Date
Francesco Tamagni a11ee1c3f7 fruity: Add dump_backtraces() for jailed iOS
Capture the backtraces of all threads through the debugger, so it works
even when all of Frida's threads in the target are blocked. Frames are
expressed as module path plus offset so they can be symbolicated
offline.

The result is a dictionary with a `threads` array, one entry per thread:

```json
{
  "threads": [
    {
      "id": "0x4dee1",
      "name": "gum-js-loop",
      "registers": { "x0": "0x...", ..., "cpsr": "0x..." },
      "frames": [
        { "address": "0x1a2b3c4d0", "module": "/a/b", "offset": 46 },
        { "address": "0x110a28210" }
      ]
    }
  ]
}
```

Addresses, thread IDs and register values are hex strings, so the result
survives JSON serialization. Frames are innermost first: the PC, the LR,
then the frame-pointer chain. A frame has `module` and `offset` only if
it falls inside a known module. Frames inside Gadget are attributed to
the local Gadget file that was injected, or to `FridaGadget.dylib` if it
was already loaded. The registers are the general-purpose ones reported
by debugserver. If a thread can't be fully walked, it also has an
`error` and keeps the frames collected so far.

Gadget only routes `EXC_BREAKPOINT` to the debugger, so debugserver's
interrupt never arrives once it's loaded. When spawned with the new
`interruptible` option, Gadget therefore starts a thread that executes
a breakpoint whenever someone connects to it, passing along its own
address range so its frames can be attributed. The injector finds the
thread through its name. It makes its socket calls as raw syscalls and
guards its state with atomics, so hooks on libc functions can't drag it
into a blocked Frida thread.

The breakpoint is disarmed when Gadget detaches, so it can't fire
without a debugger to handle it, and the injector re-arms it when
reattaching to an existing Gadget.
2026-11-12 13:24:57 +01:00
Ole André Vadla Ravnås 2faca1f013 barebone: Enter agent code with an aligned stack
Wherever the agent is started without a call, the stack it is given has
to look the way a call would have left it. On x86 and x86_64 several
such entries were off: the win9x trampoline, NT user threads, the Linux
userland copy and the x86 Linux thread start. On arm, a host-side
invoke borrowed whatever SP the interrupted code had.

Guest code that relies on alignment then faults, e.g. musl's fstatat on
x86_64 Alpine, which made every lstat() from an injected agent crash.
2026-11-11 12:43:13 +01:00
Ole André Vadla Ravnås 588f79c5ec fruity: Install idund beside other assets
A shared build loads its assets from disk, so idund was neither
embedded nor installed -- installs skip subprojects -- and idun's guess
at its location next to the library found nothing. Install it into
the plugins directory and point the daemon locator there.
2026-11-11 11:43:22 +01:00
Ole André Vadla Ravnås 588ac3b3de subprojects: Bump outdated 2026-11-09 22:03:43 +01:00
Ole André Vadla Ravnås d0615e7574 submodules: Bump releng 2026-11-09 22:03:42 +01:00
Kaz Wolfe 1b169dec1c exit-monitor: Pass function sizes to Interceptor
On QNX 6.5 ARM, libc's _exit() is a lone BL into __exit(), and the
Interceptor cannot tell where it ends. Look up the symbol sizes and pass
them as redirect_space_hint so the redirect stays within the function.
2026-11-09 21:59:38 +01:00
Kaz Wolfe 0e61ae2762 qnx: Set PC when emitting the ARM payload
The writer assumed the code would run where it was generated, so calls
to libc functions within BL range of the local buffer were encoded with
offsets that are wrong in the target process.
2026-11-09 21:59:38 +01:00
Kaz Wolfe 6a445d9dea qnx: Add x86 support to the injector
Mirror the ARM payload and remote call using cdecl.
2026-11-09 20:34:01 +01:00
Ole André Vadla Ravnås 2a41a960fe subprojects: Prepare for release 2026-11-07 15:45:32 +01:00
Ole André Vadla Ravnås 25bd49299a subprojects: Bump outdated 2026-11-07 15:44:13 +01:00
tubibibo c84323b6ac linux: Don't pass our stdin to crash logcat
Under `adb shell`, frida-server's stdin and stdout are the same socket.
The crash monitor's logcat inherited it, so when frida-server died
abnormally, logcat kept it open and `adb shell` never saw EOF.

Give logcat /dev/null for stdin instead.
2026-11-07 15:38:00 +01:00
Ole André Vadla Ravnås 45aef70f35 subprojects: Prepare for release 2026-11-06 13:15:10 +01:00
Ole André Vadla Ravnås eb8efdf40a subprojects: Bump outdated 2026-11-06 12:19:36 +01:00
Ole André Vadla Ravnås 025e5c433d submodules: Bump releng 2026-11-06 12:19:35 +01:00
Ole André Vadla Ravnås f9314073dc subprojects: Prepare for release 2026-11-05 22:45:53 +01:00
Ole André Vadla Ravnås 4011e34449 ci: Run QNX tests out of /tmp
On the emulated QNX 6.5 sabrelite board, threads that concurrently
take first-touch page faults on code mapped from the boot image's
/opt sometimes get SIGSEGV with SEGV_MAPERR, even though the pages
are mapped. A standalone dlopen() test with two or more threads
calling into a fresh library crashes on the first iteration, and
passes when the library lives in /tmp. This made the agent tests
crash intermittently once the reloaded agent spun up its threads.
2026-11-05 22:43:11 +01:00
Ole André Vadla Ravnås 2fef8d7d20 subprojects: Bump outdated 2026-11-05 21:59:57 +01:00
Ole André Vadla Ravnås 9f681e26f1 subprojects: Bump outdated 2026-11-05 21:00:52 +01:00
Ole André Vadla Ravnås 18a156817f barebone: Bump @types/frida-gum to 19.13.0 2026-11-05 20:58:58 +01:00
Ole André Vadla Ravnås 2ea8b6278e compiler: Bump @types/frida-gum to 19.13.0 2026-11-05 20:58:58 +01:00
washedbeast 40ce660b54 linux: Fix memfd check in code swap fallback
Gum.Linux.enumerate_ranges() gives paths as they appear in the maps,
like "/memfd:jit-zygote-cache (deleted)", so the "memfd:" prefix check
from 72a96226 never matched. When libc could not be found in the
target, the code swap then picked ART's read-only JIT zygote cache and
the first write failed with "unable to perform ptrace pokedata: I/O
error".
2026-11-05 11:43:12 +01:00
washedbeast 7913d880c2 linux: Parse /proc/<pid>/maps as raw bytes
Mapping names are not always valid UTF-8: file paths may hold any
bytes, and on Android kernels before 5.17 an anonymous mapping's name
is read from a user pointer whenever the maps are read, so a name whose
string got unmapped shows up as random bytes. We hit this with Magisk's
Zygisk, where apps inherit [anon:System property context nodes] from
zygote with a garbage name.

GRegex rejects a subject that is not valid UTF-8 unless the pattern is
raw, so one such line made ProcMapsIter yield no mappings at all. The
injector then could not find libc in the target, and attach failed.
2026-11-05 11:43:12 +01:00
washedbeast 34e718e4a3 linux: Make syscall-trace strings valid UTF-8
g_variant_new_string() returns NULL when given invalid UTF-8, and the
g_variant_ref_sink() that Vala adds around it then dereferences the
NULL, crashing frida-server.

Module paths in resolve-symbols, and string arguments and AF_UNIX
socket paths in read-events, come from /proc/<pid>/maps and from the
kernel, so they can hold any byte. Replace invalid sequences before
they go into a GVariant.
2026-11-05 11:39:13 +01:00
tubibibo 10d6f866b1 agent: Retry emulated load through LoadLibraryExt
Houdini v3 rejects the deprecated NativeBridgeLoadLibrary() entrypoint
and returns NULL, which surfaces as "Process is not using emulation".
When the plain call fails and the extended entrypoint is available,
retry through it with a null namespace.
2026-11-05 11:31:24 +01:00
Ole André Vadla Ravnås d5bc484072 subprojects: Bump outdated 2026-11-04 12:53:53 +01:00
Ole André Vadla Ravnås 04bd09b7b7 submodules: Bump releng 2026-11-04 12:53:52 +01:00
Ole André Vadla Ravnås ef7dd9701c agent: Mirror native cloak in emulated realm
The emulated agent sees the native agent's threads when it walks the
host's thread list, as they are only cloaked in the native agent's Gum.

Have the native agent share its cloaked threads with the emulated agent,
as a snapshot followed by changes, applied to the emulated agent's own
cloak.

Depends on the CloakObserver interface in frida-gum.
2026-11-03 23:11:43 +01:00
Ole André Vadla Ravnås 8777bbe8af agent: Feed emulated thread registry from host
On Android's native bridge, threads are created and torn down by the
host's Bionic, which the emulated agent's Interceptor cannot hook. This
broke Process.attachThreadObserver() in the emulated realm.

Have the native agent expose its own thread registry to the emulated
agent, which plugs it into Gum as a ThreadProvider: a snapshot on
activation, followed by added, removed and renamed events.

Depends on the ThreadProvider interface in frida-gum.
2026-11-03 23:11:43 +01:00
Francesco Tamagni 8734d9875a fruity: Fix kill() fallback (#1282)
LLDB errors are Efni.Error since the move to idun, so catching
Frida.Error skipped the ProcessControlService fallback.
2026-11-03 17:35:54 +01:00
Francesco Tamagni d043c66c75 channel: Keep streams alive until linked
The registry only holds a weak reference, so a stream nobody else owns,
such as one relayed by idund, was gone before link().
2026-11-03 17:29:07 +01:00
40 changed files with 1852 additions and 151 deletions

No files matched your search

+4 -1
View File
@@ -451,7 +451,10 @@ jobs:
cd build
cp -a tests/frida-tests tests/labrats lib/agent/frida-agent.so /tmp/pkg/
tar -C /tmp/pkg -cf /tmp/runner.tar .
/opt/sabrelite/run.sh /tmp/runner.tar /opt/frida/frida-tests
/opt/sabrelite/run.sh /tmp/runner.tar \
"cp /opt/frida/frida-tests /opt/frida/frida-agent.so /tmp/ && \
ln -sP /opt/frida/labrats /tmp/labrats && \
/tmp/frida-tests"
# This job is used to check that the Linux helpers are built correctly for all
# supported architectures. We do this because the binary artifacts are checked
+248
View File
@@ -103,6 +103,9 @@ namespace Frida.Agent {
private SpawnMonitor? spawn_monitor;
private ThreadSuspendMonitor? thread_suspend_monitor;
private UnwindSitter? unwind_sitter;
#if ANDROID && (ARM || ARM64)
private NativeCloakMirror? native_cloak_mirror;
#endif
private delegate void CompletionNotify ();
@@ -350,6 +353,9 @@ namespace Frida.Agent {
bool enable_unwind_broker = true;
bool enable_exit_monitor = true;
bool enable_thread_suspend_monitor = true;
#if ANDROID && (ARM || ARM64)
bool in_emulated_realm = false;
#endif
foreach (unowned string option in tokens[1:]) {
if (option == "eternal")
ensure_eternalized ();
@@ -365,6 +371,10 @@ namespace Frida.Agent {
enable_exit_monitor = false;
else if (option == "thread-suspend-monitor:off")
enable_thread_suspend_monitor = false;
#if ANDROID && (ARM || ARM64)
else if (option == "realm:emulated")
in_emulated_realm = true;
#endif
}
if (exceptor_mode != FULL)
@@ -374,6 +384,10 @@ namespace Frida.Agent {
try {
yield setup_connection_with_transport_uri (transport_uri);
#if ANDROID && (ARM || ARM64)
if (in_emulated_realm)
yield connect_to_native_realm ();
#endif
} catch (Error e) {
start_error = e;
main_loop.quit ();
@@ -406,6 +420,23 @@ namespace Frida.Agent {
padder = null;
}
#if ANDROID && (ARM || ARM64)
private async void connect_to_native_realm () throws Error {
try {
NativeCloak cloak = yield connection.get_proxy (null, ObjectPath.NATIVE_CLOAK, DO_NOT_LOAD_PROPERTIES,
null);
native_cloak_mirror = new NativeCloakMirror (cloak);
native_cloak_mirror.activate ();
NativeThreadRegistry registry = yield connection.get_proxy (null, ObjectPath.NATIVE_THREAD_REGISTRY,
DO_NOT_LOAD_PROPERTIES, null);
Gum.ThreadRegistry.set_provider (new NativeThreadProvider (registry));
} catch (GLib.Error e) {
throw new Error.TRANSPORT ("%s", e.message);
}
}
#endif
private static bool can_allocate_code () {
size_t page_size = Gum.query_page_size ();
@@ -1478,6 +1509,8 @@ namespace Frida.Agent {
private Socket? emulated_socket;
private BridgeState? emulated_bridge_state;
private Thread<void>? emulated_worker;
private NativeCloakService? native_cloak;
private NativeThreadRegistryService? native_thread_registry;
private async AgentSessionProvider? try_get_emulated_provider (Cancellable? cancellable) throws IOError {
if (get_emulated_request == null)
@@ -1517,6 +1550,8 @@ namespace Frida.Agent {
emulated_agent = nb_api.load_library_ext (emulated_agent_path, RTLD_LAZY, classloader_namespace);
} else {
emulated_agent = nb_api.load_library (emulated_agent_path, RTLD_LAZY);
if (emulated_agent == null && nb_api.load_library_ext != null)
emulated_agent = nb_api.load_library_ext (emulated_agent_path, RTLD_LAZY, null);
}
if (emulated_agent == null)
throw new Error.NOT_SUPPORTED ("Process is not using emulation");
@@ -1556,6 +1591,7 @@ namespace Frida.Agent {
* code being discarded, which seems like an edge-case the translator doesn't handle.
*/
parameters.append ("|exceptor:off|exit-monitor:off");
parameters.append ("|realm:emulated");
emulated_bridge_state = new BridgeState (parameters.str);
@@ -1564,6 +1600,13 @@ namespace Frida.Agent {
var connection = yield new DBusConnection (stream, ServerGuid.HOST_SESSION_SERVICE,
AUTHENTICATION_SERVER | AUTHENTICATION_ALLOW_ANONYMOUS, null, cancellable);
native_cloak = new NativeCloakService ();
connection.register_object (ObjectPath.NATIVE_CLOAK, (NativeCloak) native_cloak);
native_thread_registry = new NativeThreadRegistryService ();
connection.register_object (ObjectPath.NATIVE_THREAD_REGISTRY,
(NativeThreadRegistry) native_thread_registry);
AgentSessionProvider provider = yield connection.get_proxy (null, ObjectPath.AGENT_SESSION_PROVIDER,
DO_NOT_LOAD_PROPERTIES, cancellable);
@@ -1611,6 +1654,16 @@ namespace Frida.Agent {
emulated_worker = null;
}
if (native_thread_registry != null) {
native_thread_registry.close ();
native_thread_registry = null;
}
if (native_cloak != null) {
native_cloak.close ();
native_cloak = null;
}
emulated_socket = null;
if (emulated_agent != null) {
@@ -1771,6 +1824,201 @@ namespace Frida.Agent {
}
#endif
#if ANDROID && (X86 || X86_64)
private sealed class NativeCloakService : Object, NativeCloak, Gum.Cloak.Observer {
private MainContext main_context = MainContext.ref_thread_default ();
private bool observing = false;
public uint[] activate (Cancellable? cancellable) {
Gum.Cloak.add_observer (this);
observing = true;
uint[] threads = {};
Gum.Cloak.enumerate_threads (id => {
threads += (uint) id;
return true;
});
return threads;
}
public void close () {
if (!observing)
return;
Gum.Cloak.remove_observer (this);
observing = false;
}
public void thread_added (Gum.ThreadId id) {
schedule (() => {
thread_cloaked ((uint) id);
return Source.REMOVE;
});
}
public void thread_removed (Gum.ThreadId id) {
schedule (() => {
thread_uncloaked ((uint) id);
return Source.REMOVE;
});
}
private void schedule (owned SourceFunc function) {
var source = new IdleSource ();
source.set_callback ((owned) function);
source.attach (main_context);
}
}
private sealed class NativeThreadRegistryService : Object, NativeThreadRegistry {
private unowned Gum.ThreadRegistry? registry;
private ulong added_handler = 0;
private ulong removed_handler = 0;
private ulong renamed_handler = 0;
public NativeThreadInfo[] activate (Cancellable? cancellable) {
registry = Gum.ThreadRegistry.obtain ();
NativeThreadInfo[] threads = {};
registry.lock ();
added_handler = registry.thread_added.connect (on_thread_added);
removed_handler = registry.thread_removed.connect (on_thread_removed);
renamed_handler = registry.thread_renamed.connect (on_thread_renamed);
registry.enumerate_threads (thread => {
threads += NativeThreadInfo ((uint) thread.id, thread.name ?? "");
return true;
});
registry.unlock ();
return threads;
}
public void deactivate (Cancellable? cancellable) {
close ();
}
public void close () {
if (registry == null)
return;
registry.disconnect (added_handler);
registry.disconnect (removed_handler);
registry.disconnect (renamed_handler);
registry = null;
}
private void on_thread_added (Gum.ThreadDetails thread) {
thread_added (NativeThreadInfo ((uint) thread.id, thread.name ?? ""));
}
private void on_thread_removed (Gum.ThreadDetails thread) {
thread_removed ((uint) thread.id);
}
private void on_thread_renamed (Gum.ThreadDetails thread, string? previous_name) {
thread_renamed ((uint) thread.id, thread.name ?? "");
}
}
#endif
#if ANDROID && (ARM || ARM64)
private sealed class NativeCloakMirror : Object {
public NativeCloak native_cloak {
get;
construct;
}
public NativeCloakMirror (NativeCloak native_cloak) {
Object (native_cloak: native_cloak);
}
public void activate () throws GLib.Error {
native_cloak.thread_cloaked.connect (on_thread_cloaked);
native_cloak.thread_uncloaked.connect (on_thread_uncloaked);
foreach (uint id in native_cloak.activate (null))
on_thread_cloaked (id);
}
private void on_thread_cloaked (uint id) {
if (!Gum.Cloak.has_thread (id))
Gum.Cloak.add_thread (id);
}
private void on_thread_uncloaked (uint id) {
Gum.Cloak.remove_thread (id);
}
}
private sealed class NativeThreadProvider : Object, Gum.ThreadProvider {
public NativeThreadRegistry native_registry {
get;
construct;
}
private unowned Gum.ThreadRegistry registry;
private ulong added_handler = 0;
private ulong removed_handler = 0;
private ulong renamed_handler = 0;
public NativeThreadProvider (NativeThreadRegistry native_registry) {
Object (native_registry: native_registry);
}
public void activate (Gum.ThreadRegistry registry) {
this.registry = registry;
added_handler = native_registry.thread_added.connect (add_thread);
removed_handler = native_registry.thread_removed.connect (on_thread_removed);
renamed_handler = native_registry.thread_renamed.connect (on_thread_renamed);
NativeThreadInfo[] threads;
try {
threads = native_registry.activate (null);
} catch (GLib.Error e) {
return;
}
foreach (var thread in threads)
add_thread (thread);
}
public void deactivate () {
native_registry.disconnect (added_handler);
native_registry.disconnect (removed_handler);
native_registry.disconnect (renamed_handler);
try {
native_registry.deactivate (null);
} catch (GLib.Error e) {
}
}
private void add_thread (NativeThreadInfo thread) {
var details = Gum.ThreadDetails ();
details.id = thread.id;
if (thread.name != "") {
details.name = thread.name;
details.flags = NAME;
}
registry.register (details);
}
private void on_thread_removed (uint id) {
registry.unregister (id);
}
private void on_thread_renamed (uint id, string name) {
registry.rename (id, (name != "") ? name : null);
}
}
#endif
private sealed class LiveAgentSession : BaseAgentSession {
public uint registration_id {
get;
+12 -2
View File
@@ -6,6 +6,8 @@ namespace Frida {
private Gee.Map<ChannelId?, weak IOStream> channels =
new Gee.HashMap<ChannelId?, weak IOStream> (ChannelId.hash, ChannelId.equal);
private Gee.Map<ChannelId?, IOStream> unlinked_channels =
new Gee.HashMap<ChannelId?, IOStream> (ChannelId.hash, ChannelId.equal);
~ChannelRegistry () {
clear ();
@@ -18,25 +20,33 @@ namespace Frida {
channel_stream.abandon ();
}
channels.clear ();
unlinked_channels.clear ();
}
public void register (ChannelId id, IOStream stream) {
stream.set_data ("channel-registry-entry", new Entry (this, id));
lock (channels)
lock (channels) {
channels[id] = stream;
unlinked_channels[id] = stream;
}
}
public IOStream link (ChannelId id) throws Error {
IOStream? stream;
lock (channels)
IOStream? unlinked_stream = null;
lock (channels) {
stream = channels[id];
unlinked_channels.unset (id, out unlinked_stream);
}
if (stream == null)
throw new Error.INVALID_ARGUMENT ("Invalid channel ID");
return stream;
}
public void unlink (ChannelId id) {
IOStream? unlinked_stream = null;
lock (channels) {
unlinked_channels.unset (id, out unlinked_stream);
if (!channels.unset (id))
return;
}
+42
View File
@@ -35,6 +35,8 @@ namespace Frida {
public abstract async ServiceSessionId open_service (string address, Cancellable? cancellable) throws GLib.Error;
public abstract async HashTable<string, Variant> dump_backtraces (uint pid, Cancellable? cancellable) throws GLib.Error;
public signal void spawn_gating_disabled (SpawnGatingDisabledReason reason);
public signal void spawn_added (HostSpawnInfo info);
public signal void spawn_removed (HostSpawnInfo info);
@@ -133,6 +135,36 @@ namespace Frida {
Cancellable? cancellable) throws GLib.Error;
}
#if ANDROID
[DBus (name = "re.frida.NativeThreadRegistry17")]
public interface NativeThreadRegistry : Object {
public abstract NativeThreadInfo[] activate (Cancellable? cancellable) throws GLib.Error;
public abstract void deactivate (Cancellable? cancellable) throws GLib.Error;
public signal void thread_added (NativeThreadInfo thread);
public signal void thread_removed (uint id);
public signal void thread_renamed (uint id, string name);
}
[DBus (name = "re.frida.NativeCloak17")]
public interface NativeCloak : Object {
public abstract uint[] activate (Cancellable? cancellable) throws GLib.Error;
public signal void thread_cloaked (uint id);
public signal void thread_uncloaked (uint id);
}
public struct NativeThreadInfo {
public uint id;
public string name;
public NativeThreadInfo (uint id, string name) {
this.id = id;
this.name = name;
}
}
#endif
[DBus (name = "re.frida.AgentMessageSink17")]
public interface AgentMessageSink : Object {
public abstract async void post_messages (AgentMessage[] messages, uint batch_id,
@@ -787,6 +819,10 @@ namespace Frida {
* Apply the page plan.
*/
PAGE_PLAN,
/**
* Let the debugger inspect the process, then resume execution.
*/
INTERRUPT,
}
[DBus (name = "re.frida.Channel17")]
@@ -972,6 +1008,10 @@ namespace Frida {
public async ServiceSessionId open_service (string address, Cancellable? cancellable) throws Error, IOError {
throw_not_authorized ();
}
public async HashTable<string, Variant> dump_backtraces (uint pid, Cancellable? cancellable) throws Error, IOError {
throw_not_authorized ();
}
}
public sealed class UnauthorizedPortalSession : Object, PortalSession {
@@ -2802,6 +2842,8 @@ namespace Frida {
public const string AGENT_SESSION_PROVIDER = "/re/frida/AgentSessionProvider";
public const string AGENT_SESSION = "/re/frida/AgentSession";
public const string AGENT_CONTROLLER = "/re/frida/AgentController";
public const string NATIVE_THREAD_REGISTRY = "/re/frida/NativeThreadRegistry";
public const string NATIVE_CLOAK = "/re/frida/NativeCloak";
public const string AGENT_MESSAGE_SINK = "/re/frida/AgentMessageSink";
public const string GADGET_SESSION = "/re/frida/GadgetSession";
public const string CHANNEL = "/re/frida/Channel";
+315
View File
@@ -11,15 +11,50 @@
#include "frida-base.h"
#import <Foundation/Foundation.h>
#include <arpa/inet.h>
#include <errno.h>
#include <gum/gumdarwin.h>
#include <mach-o/loader.h>
#include <netinet/in.h>
#include <objc/runtime.h>
#include <pthread.h>
#include <stdio.h>
#include <sys/socket.h>
#include <sys/syscall.h>
static void frida_on_breakpoints_steal_attempt (GumInvocationContext * ic, gpointer user_data);
static void frida_wait_until_interrupt_listener_named (void);
static void frida_wake_interrupt_listener (void);
static void * frida_run_interrupt_listener (void * data);
static void frida_announce_interrupt_listener_named (void);
static void frida_break_and_interrupt_if_armed (void);
static void frida_lock_interrupt_arming (void);
static void frida_unlock_interrupt_arming (void);
static void frida_break_and_interrupt (void);
static void frida_init_loopback_address (struct sockaddr_in * addr, guint16 port);
static int frida_open_tcp_socket (void);
static int frida_bind (int fd, const struct sockaddr_in * addr);
static int frida_listen (int fd, int backlog);
static int frida_get_bound_address (int fd, struct sockaddr_in * addr);
static int frida_accept (int fd);
static int frida_connect (int fd, const struct sockaddr_in * addr);
static void frida_close (int fd);
static gssize frida_syscall (gsize number, gsize a0, gsize a1, gsize a2);
static GumInvocationListener * frida_dont_steal_my_breakpoints;
static pthread_t frida_interrupt_listener_thread;
static int frida_interrupt_listener_fd = -1;
static guint16 frida_interrupt_listener_port;
static volatile gboolean frida_interrupt_listener_stopping;
static pthread_mutex_t frida_interrupt_listener_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t frida_interrupt_listener_cond = PTHREAD_COND_INITIALIZER;
static gboolean frida_interrupt_listener_named;
static volatile guint8 frida_interrupt_listener_armed;
static GumAddress frida_our_base_address;
static gsize frida_our_size;
static volatile gint frida_interrupt_arming_lock;
gchar *
frida_gadget_environment_detect_bundle_id (void)
{
@@ -179,6 +214,286 @@ frida_on_breakpoints_steal_attempt (GumInvocationContext * ic, gpointer user_dat
gum_invocation_context_replace_nth_argument (ic, 1, GSIZE_TO_POINTER (exception_mask));
}
void
frida_gadget_environment_start_interrupt_listener (GumAddress our_base_address, gsize our_size)
{
int fd;
struct sockaddr_in addr;
if (frida_interrupt_listener_fd != -1)
return;
frida_our_base_address = our_base_address;
frida_our_size = our_size;
fd = frida_open_tcp_socket ();
if (fd < 0)
return;
frida_init_loopback_address (&addr, 0);
if (frida_bind (fd, &addr) != 0 ||
frida_listen (fd, 1) != 0 ||
frida_get_bound_address (fd, &addr) != 0)
{
frida_close (fd);
return;
}
frida_interrupt_listener_fd = fd;
frida_interrupt_listener_port = ntohs (addr.sin_port);
frida_interrupt_listener_stopping = FALSE;
frida_interrupt_listener_named = FALSE;
frida_interrupt_listener_armed = TRUE;
if (pthread_create (&frida_interrupt_listener_thread, NULL, frida_run_interrupt_listener, NULL) != 0)
{
frida_close (fd);
frida_interrupt_listener_fd = -1;
return;
}
frida_wait_until_interrupt_listener_named ();
}
void
frida_gadget_environment_stop_interrupt_listener (void)
{
if (frida_interrupt_listener_fd == -1)
return;
frida_interrupt_listener_stopping = TRUE;
frida_wake_interrupt_listener ();
pthread_join (frida_interrupt_listener_thread, NULL);
frida_close (frida_interrupt_listener_fd);
frida_interrupt_listener_fd = -1;
}
void
frida_gadget_environment_disarm_interrupt_listener (void)
{
if (frida_interrupt_listener_fd == -1)
return;
frida_lock_interrupt_arming ();
frida_interrupt_listener_armed = FALSE;
frida_unlock_interrupt_arming ();
}
static void
frida_wait_until_interrupt_listener_named (void)
{
pthread_mutex_lock (&frida_interrupt_listener_mutex);
while (!frida_interrupt_listener_named)
pthread_cond_wait (&frida_interrupt_listener_cond, &frida_interrupt_listener_mutex);
pthread_mutex_unlock (&frida_interrupt_listener_mutex);
}
static void
frida_wake_interrupt_listener (void)
{
int fd;
struct sockaddr_in addr;
fd = frida_open_tcp_socket ();
if (fd < 0)
return;
frida_init_loopback_address (&addr, frida_interrupt_listener_port);
frida_connect (fd, &addr);
frida_close (fd);
}
static void *
frida_run_interrupt_listener (void * data)
{
char name[64];
GumThreadId self;
snprintf (name, sizeof (name), "frida-gadget-interrupt-%u-%" G_GINT64_MODIFIER "x", frida_interrupt_listener_port,
(guint64) GPOINTER_TO_SIZE (&frida_interrupt_listener_armed));
pthread_setname_np (name);
self = gum_process_get_current_thread_id ();
gum_cloak_add_thread (self);
frida_announce_interrupt_listener_named ();
while (TRUE)
{
int client = frida_accept (frida_interrupt_listener_fd);
if (client < 0)
{
if (client == -EINTR || client == -ECONNABORTED)
continue;
break;
}
if (!frida_interrupt_listener_stopping)
frida_break_and_interrupt_if_armed ();
frida_close (client);
if (frida_interrupt_listener_stopping)
break;
}
gum_cloak_remove_thread (self);
return NULL;
}
static void
frida_announce_interrupt_listener_named (void)
{
pthread_mutex_lock (&frida_interrupt_listener_mutex);
frida_interrupt_listener_named = TRUE;
pthread_cond_signal (&frida_interrupt_listener_cond);
pthread_mutex_unlock (&frida_interrupt_listener_mutex);
}
static void
frida_break_and_interrupt_if_armed (void)
{
frida_lock_interrupt_arming ();
if (frida_interrupt_listener_armed)
frida_break_and_interrupt ();
frida_unlock_interrupt_arming ();
}
static void
frida_lock_interrupt_arming (void)
{
while (__atomic_exchange_n (&frida_interrupt_arming_lock, 1, __ATOMIC_ACQUIRE) != 0)
;
}
static void
frida_unlock_interrupt_arming (void)
{
__atomic_store_n (&frida_interrupt_arming_lock, 0, __ATOMIC_RELEASE);
}
static void
frida_break_and_interrupt (void)
{
#ifdef HAVE_ARM64
asm volatile (
"mov x1, #1337\n\t"
"mov x2, #1337\n\t"
"mov x3, %0\n\t"
"mov x4, %1\n\t"
"mov x5, %2\n\t"
"brk #1337\n\t"
:
: "r" ((gsize) FRIDA_GADGET_BREAKPOINT_ACTION_INTERRUPT),
"r" ((gsize) frida_our_base_address),
"r" (frida_our_size)
: "x1", "x2", "x3", "x4", "x5"
);
#endif
}
static void
frida_init_loopback_address (struct sockaddr_in * addr, guint16 port)
{
*addr = (struct sockaddr_in) {
.sin_len = sizeof (struct sockaddr_in),
.sin_family = AF_INET,
.sin_port = htons (port),
.sin_addr.s_addr = htonl (INADDR_LOOPBACK),
};
}
static int
frida_open_tcp_socket (void)
{
return frida_syscall (SYS_socket, AF_INET, SOCK_STREAM, 0);
}
static int
frida_bind (int fd, const struct sockaddr_in * addr)
{
return frida_syscall (SYS_bind, fd, GPOINTER_TO_SIZE (addr), sizeof (struct sockaddr_in));
}
static int
frida_listen (int fd, int backlog)
{
return frida_syscall (SYS_listen, fd, backlog, 0);
}
static int
frida_get_bound_address (int fd, struct sockaddr_in * addr)
{
socklen_t addr_len = sizeof (struct sockaddr_in);
return frida_syscall (SYS_getsockname, fd, GPOINTER_TO_SIZE (addr), GPOINTER_TO_SIZE (&addr_len));
}
static int
frida_accept (int fd)
{
return frida_syscall (SYS_accept, fd, 0, 0);
}
static int
frida_connect (int fd, const struct sockaddr_in * addr)
{
return frida_syscall (SYS_connect, fd, GPOINTER_TO_SIZE (addr), sizeof (struct sockaddr_in));
}
static void
frida_close (int fd)
{
frida_syscall (SYS_close, fd, 0, 0);
}
static gssize
frida_syscall (gsize number, gsize a0, gsize a1, gsize a2)
{
#ifdef HAVE_ARM64
register gsize x0 asm ("x0") = a0;
register gsize x1 asm ("x1") = a1;
register gsize x2 asm ("x2") = a2;
register gsize x16 asm ("x16") = number;
gsize failed;
asm volatile (
"svc 0x80\n\t"
"cset %2, cs\n\t"
: "+r" (x0),
"+r" (x1),
"=&r" (failed)
: "r" (x2),
"r" (x16)
: "cc", "memory"
);
return failed ? -(gssize) x0 : (gssize) x0;
#else
register gsize rdi asm ("rdi") = a0;
register gsize rsi asm ("rsi") = a1;
register gsize rdx asm ("rdx") = a2;
register gsize rax asm ("rax") = 0x2000000 | number;
guint8 failed;
asm volatile (
"syscall\n\t"
"setc %2\n\t"
: "+r" (rax),
"+r" (rdx),
"=&q" (failed)
: "r" (rdi),
"r" (rsi)
: "rcx", "r11", "cc", "memory"
);
return failed ? -(gssize) rax : (gssize) rax;
#endif
}
void
frida_gadget_environment_break_and_resume (void)
{
+20 -1
View File
@@ -26,6 +26,12 @@ namespace Frida.Gadget {
default = Gum.CodeSigningPolicy.OPTIONAL;
}
public bool interruptible {
get;
set;
default = false;
}
public bool deserialize_property (string property_name, out Value value, ParamSpec pspec, Json.Node property_node) {
if (property_name == "interaction" && property_node.get_node_type () == Json.NodeType.OBJECT) {
var interaction_node = property_node.get_object ();
@@ -409,8 +415,11 @@ namespace Frida.Gadget {
interceptor.begin_transaction ();
#if DARWIN
if (mapped_range != null)
if (mapped_range != null) {
Environment.ensure_debugger_breakpoints_only ();
if (config.interruptible)
Environment.start_interrupt_listener (location.range.base_address, location.range.size);
}
#endif
exceptor = Gum.Exceptor.obtain ();
@@ -634,6 +643,7 @@ namespace Frida.Gadget {
exceptor = null;
#if DARWIN
Environment.stop_interrupt_listener ();
Environment.allow_stolen_breakpoints ();
#endif
@@ -1886,6 +1896,11 @@ namespace Frida.Gadget {
throw new Error.NOT_SUPPORTED ("Unable to open services when embedded");
}
public async HashTable<string, Variant> dump_backtraces (uint pid, Cancellable? cancellable)
throws Error, IOError {
throw new Error.NOT_SUPPORTED ("Unable to dump backtraces when embedded");
}
public async void break_and_resume (Cancellable? cancellable) throws Error, IOError {
#if DARWIN
Environment.break_and_resume ();
@@ -1896,6 +1911,7 @@ namespace Frida.Gadget {
public async void break_and_detach (Cancellable? cancellable) throws Error, IOError {
#if DARWIN
Environment.disarm_interrupt_listener ();
Environment.break_and_detach ();
#else
throw new Error.NOT_SUPPORTED ("This API is only applicable on Darwin");
@@ -2142,6 +2158,9 @@ namespace Frida.Gadget {
ref string? our_path, ref Gum.MemoryRange? our_range);
private extern void ensure_debugger_breakpoints_only ();
private extern void allow_stolen_breakpoints ();
private extern void start_interrupt_listener (Gum.Address our_base_address, size_t our_size);
private extern void stop_interrupt_listener ();
private extern void disarm_interrupt_listener ();
private extern void break_and_resume ();
private extern void break_and_detach ();
#endif
+18 -2
View File
@@ -46,8 +46,24 @@ namespace Frida {
}
#else
var libc = Gum.Process.get_libc_module ();
foreach (var symbol in apis) {
interceptor.attach ((void *) libc.find_export_by_name (symbol), listener);
var targets = new Gum.Address[apis.length];
for (var i = 0; i != apis.length; i++)
targets[i] = libc.find_export_by_name (apis[i]);
var sizes = new uint[apis.length];
#if ARM
libc.enumerate_symbols (s => {
for (var i = 0; i != targets.length; i++) {
if (s.address == targets[i] && s.size > 0)
sizes[i] = (uint) s.size;
}
return true;
});
#endif
for (var i = 0; i != apis.length; i++) {
var options = Gum.AttachOptions ();
options.instrumentation.redirect_space_hint = sizes[i];
interceptor.attach ((void *) targets[i], listener, options);
}
#endif
#endif
+9 -4
View File
@@ -732,17 +732,22 @@ openssl_dep = dependency('openssl',
)
idun_libs = []
have_idund_blob = false
have_idund = false
if have_fruity_backend
idun_dep = dependency('idun-1.0', default_options: ['default_library=static'])
backend_reqs_private += 'idun-1.0'
if idun_dep.type_name() == 'internal'
idun_sp = subproject('idun')
idun_libs += idun_sp.get_variable('idun')
if assets_mode == 'embedded' and idun_sp.get_variable('build_daemon')
have_idund = idun_sp.get_variable('build_daemon')
if have_idund
idund_raw = idun_sp.get_variable('idund')
have_idund_blob = true
vala_flags += '--define=HAVE_IDUND_BLOB'
if assets_mode == 'embedded'
vala_flags += '--define=HAVE_EMBEDDED_IDUND'
else
vala_flags += '--define=HAVE_INSTALLED_IDUND'
cdata.set_quoted('FRIDA_IDUND_NAME', 'idund' + exe_suffix)
endif
endif
endif
idun_vapidir = idun_dep.get_variable('vapidir', default_value: '')
+1 -1
Submodule releng updated: 564352a3ea...fe7d31dfbc.
+16 -10
View File
@@ -293,10 +293,6 @@ fn give(destination: usize, source: usize, size: usize) -> Option<()> {
unsafe { copy_to_user(destination as *mut c_void, source as *const c_void, size) };
unsafe { close_user_access(opened) };
if left != 0 {
if left == size {
} else if left < size {
} else {
}
return None;
}
@@ -707,15 +703,25 @@ unsafe fn write_selectors(_registers: usize, _places: &Places) {}
#[cfg(target_arch = "x86")]
fn hand_over_argument(stack: usize, arena: usize) -> usize {
let frame = [0usize, arena];
let sp = stack - size_of::<[usize; 2]>();
let aligned_argument = stack - 16;
let sp = aligned_argument - size_of::<usize>();
match give(sp, frame.as_ptr() as usize, size_of::<[usize; 2]>()) {
Some(()) => sp,
None => stack,
}
give(sp, frame.as_ptr() as usize, size_of::<[usize; 2]>());
sp
}
#[cfg(not(target_arch = "x86"))]
#[cfg(target_arch = "x86_64")]
fn hand_over_argument(stack: usize, _arena: usize) -> usize {
let return_address = 0usize;
let sp = stack - size_of::<usize>();
give(sp, &return_address as *const usize as usize, size_of::<usize>());
sp
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
fn hand_over_argument(stack: usize, _arena: usize) -> usize {
stack
}
+2 -1
View File
@@ -459,7 +459,8 @@ unsafe extern "C" {
#[cfg(target_arch = "x86")]
unsafe fn start_thread(stack: usize, carried: usize) -> isize {
let launch = stack - 8;
let aligned_argument = stack - 16;
let launch = aligned_argument - 4;
unsafe {
(launch as *mut usize).write(enter_thread as *const () as usize);
((launch + 4) as *mut usize).write(carried);
+3 -1
View File
@@ -1059,7 +1059,9 @@ fn write_trampoline(trampoline: u32, entry: u32, stack_top: u32) {
0xff, 0xd2,
];
let stack_bottom = stack_top + 0x40 - INJECTION_STACK_SIZE as u32;
code[5..9].copy_from_slice(&stack_top.to_le_bytes());
let aligned_argument = stack_top - 16;
let sp_before_push = aligned_argument + 4;
code[5..9].copy_from_slice(&sp_before_push.to_le_bytes());
code[11..15].copy_from_slice(&entry.to_le_bytes());
code[32..36].copy_from_slice(&stack_bottom.to_le_bytes());
code[39..43].copy_from_slice(&kernel32_export(b"ExitThread").to_le_bytes());
+15 -8
View File
@@ -3338,18 +3338,12 @@ const BRANCH_TO_STEP_TARGET: u32 = 0xd61f_0200;
#[cfg(target_arch = "aarch64")]
const STATUS_NOT_FOUND: i32 = -1_073_741_275;
// The argument travels in a register here, and the stack only has to be aligned the way a call
// would have left it.
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
fn seed_stack(_process: *mut c_void, top: u64, _argument: u64) -> u64 {
(top - 64) & !15
}
// A cdecl entry looks for its argument above the return address, thus the stack is laid out
// before the thread runs, in the process that will read it.
#[cfg(target_arch = "x86")]
fn seed_stack(process: *mut c_void, top: u64, argument: u64) -> u64 {
let sp = (top - 32) & !15;
let aligned_argument = (top - 32) & !15;
let sp = aligned_argument - 4;
unsafe {
let mut apc_state = [0usize; APC_STATE_WORDS];
@@ -3362,6 +3356,19 @@ fn seed_stack(process: *mut c_void, top: u64, argument: u64) -> u64 {
sp
}
// The argument travels in a register here, and the stack only has to be aligned the way a call
// would have left it.
#[cfg(target_arch = "x86_64")]
fn seed_stack(_process: *mut c_void, top: u64, _argument: u64) -> u64 {
let before_call = (top - 64) & !15;
before_call - size_of::<u64>() as u64
}
#[cfg(target_arch = "aarch64")]
fn seed_stack(_process: *mut c_void, top: u64, _argument: u64) -> u64 {
(top - 64) & !15
}
fn free_in_process(process_handle: *mut c_void, address: u64) {
let mut base = address as *mut u8;
let mut region = 0usize;
+2
View File
@@ -705,6 +705,8 @@ namespace Frida.Barebone {
uint64 landing_zone = (call_landing_zone != 0) ? call_landing_zone : saved_regs["pc"].get_uint64 ();
uint64 doubleword_aligned_sp = saved_regs["sp"].get_uint64 () & ~7ULL;
regs["sp"] = doubleword_aligned_sp;
regs["pc"] = address_from_funcptr (impl);
regs["lr"] = landing_zone;
regs["cpsr"] = instruction_set_of (impl, saved_regs["cpsr"].get_uint64 ());
+4
View File
@@ -1104,6 +1104,10 @@ namespace Frida {
throw new Error.NOT_SUPPORTED ("Services are not supported by this backend");
}
public async HashTable<string, Variant> dump_backtraces (uint pid, Cancellable? cancellable) throws Error, IOError {
throw new Error.NOT_SUPPORTED ("Backtrace dumping is not supported by this backend");
}
private void on_agent_session_closed (BareboneAgentSession session) {
AgentSessionId id = session.id;
+4 -4
View File
@@ -8,7 +8,7 @@
"name": "frida-barebone-script-runtime",
"version": "1.0.0",
"devDependencies": {
"@types/frida-gum": "^19.12.0",
"@types/frida-gum": "^19.13.0",
"esbuild": "^0.25.5",
"typescript": "^5.8.3"
}
@@ -439,9 +439,9 @@
}
},
"node_modules/@types/frida-gum": {
"version": "19.12.0",
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.12.0.tgz",
"integrity": "sha512-fqXtwFA8AwE89tSdNHlaY+hNWI5j62bcO1xk5w0NSuHBIH6KRxYzIK6rp/JP7TXtRKU2ewBwx81FrHTfGF0q7w==",
"version": "19.13.0",
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.13.0.tgz",
"integrity": "sha512-tbmEZj9JUt3S6FqFE4XJPi/WhQuUU0/sM1IWS1KDKqvy6077hwNjG1v4MC3wpk+FO/WzfZ8G1gAKqAKJosfj1Q==",
"dev": true,
"license": "MIT"
},
+1 -1
View File
@@ -9,7 +9,7 @@
"watch": "tsc -w"
},
"devDependencies": {
"@types/frida-gum": "^19.12.0",
"@types/frida-gum": "^19.13.0",
"esbuild": "^0.25.5",
"typescript": "^5.8.3"
}
+4 -4
View File
@@ -36,7 +36,7 @@
"@frida/util": "^1.0.3",
"@frida/vm": "^2.0.0",
"@frida/web-streams": "^1.0.0",
"@types/frida-gum": "^19.12.0",
"@types/frida-gum": "^19.13.0",
"@types/node": "18.19.103",
"frida-fs": "7.1.1"
}
@@ -270,9 +270,9 @@
"license": "MIT"
},
"node_modules/@types/frida-gum": {
"version": "19.12.0",
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.12.0.tgz",
"integrity": "sha512-fqXtwFA8AwE89tSdNHlaY+hNWI5j62bcO1xk5w0NSuHBIH6KRxYzIK6rp/JP7TXtRKU2ewBwx81FrHTfGF0q7w==",
"version": "19.13.0",
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.13.0.tgz",
"integrity": "sha512-tbmEZj9JUt3S6FqFE4XJPi/WhQuUU0/sM1IWS1KDKqvy6077hwNjG1v4MC3wpk+FO/WzfZ8G1gAKqAKJosfj1Q==",
"license": "MIT"
},
"node_modules/@types/node": {
+1 -1
View File
@@ -32,7 +32,7 @@
"@frida/util": "^1.0.3",
"@frida/vm": "^2.0.0",
"@frida/web-streams": "^1.0.0",
"@types/frida-gum": "^19.12.0",
"@types/frida-gum": "^19.13.0",
"@types/node": "18.19.103",
"frida-fs": "7.1.1"
}
+4
View File
@@ -1026,6 +1026,10 @@ namespace Frida {
return yield parent.open_service (address, this, cancellable);
}
public async HashTable<string, Variant> dump_backtraces (uint pid, Cancellable? cancellable) throws GLib.Error {
return yield parent.host_session.dump_backtraces (pid, cancellable);
}
private async void open_tcp_transport (AgentSessionId id, Cancellable? cancellable, out uint16 port,
out string token) throws Error {
parent.open_tcp_transport (id, cancellable, out port, out token);
+9
View File
@@ -796,6 +796,15 @@ namespace Frida {
}
}
public async HashTable<string, Variant> dump_backtraces (uint pid, Cancellable? cancellable) throws Error, IOError {
var server = yield get_remote_server (cancellable);
try {
return yield server.session.dump_backtraces (pid, cancellable);
} catch (GLib.Error e) {
throw_dbus_error (e);
}
}
public async ServiceSession link_service_session (ServiceSessionId id, Cancellable? cancellable) throws Error, IOError {
var server = yield get_remote_server (cancellable);
ServiceSession session = yield server.connection.get_proxy (null, ObjectPath.for_service_session (id),
+55
View File
@@ -1586,6 +1586,52 @@ namespace Frida {
}
}
/**
* Captures the backtraces of all threads in a process, for debugging purposes.
*
* Works out-of-band through the debugger, so it doesn't need any cooperation from the process, nor
* from Frida's own threads in it. The process is briefly suspended while this happens.
*
* Only supported on jailed iOS for now. Once Gadget is injected, the process must have been spawned with
* the `interruptible` option set to `true`, so Gadget keeps a thread around that can stop it in the
* debugger even if all of Frida's threads are blocked.
*
* The result has a `threads` array, where each thread has an `id`, an optional `name`, its general-purpose
* `registers`, and `frames`.
* Each frame has an `address`, plus `module` and `offset` when it is inside a known module, so it can
* be symbolicated offline. Frames inside Gadget are attributed to the local Gadget file that was injected,
* or to `FridaGadget.dylib` if it was already loaded. If a thread couldn't be fully walked it also has an
* `error`.
*
* @param pid the process ID to capture backtraces from
* @return a table with the threads and their backtraces
*/
public async HashTable<string, Variant> dump_backtraces (uint pid, Cancellable? cancellable = null) throws Error, IOError {
check_open ();
var host_session = yield get_host_session (cancellable);
try {
return yield host_session.dump_backtraces (pid, cancellable);
} catch (GLib.Error e) {
throw_dbus_error (e);
}
}
public HashTable<string, Variant> dump_backtraces_sync (uint pid, Cancellable? cancellable = null) throws Error, IOError {
var task = create<DumpBacktracesTask> ();
task.pid = pid;
return task.execute (cancellable);
}
private class DumpBacktracesTask : DeviceTask<HashTable<string, Variant>> {
public uint pid;
protected override async HashTable<string, Variant> perform_operation () throws Error, IOError {
return yield parent.dump_backtraces (pid, cancellable);
}
}
/**
* Attaches to a process, giving a {@link Session} through which scripts
* can be created and run inside it.
@@ -5821,6 +5867,9 @@ namespace Frida {
#if COMPILER_BACKEND_INSTALLED_LIBRARY || COMPILER_BACKEND_INSTALLED_EXECUTABLE
internal string compiler_backend_path;
#endif
#if HAVE_INSTALLED_IDUND
internal string idund_path;
#endif
public void _init_asset_paths () {
var location = AssetLocation.detect ();
@@ -5828,6 +5877,9 @@ namespace Frida {
agent_path = location.derive_asset_path ("<arch>", Config.FRIDA_AGENT_NAME);
#if COMPILER_BACKEND_INSTALLED_LIBRARY || COMPILER_BACKEND_INSTALLED_EXECUTABLE
compiler_backend_path = location.derive_plugin_path (Config.FRIDA_COMPILER_BACKEND_NAME);
#endif
#if HAVE_INSTALLED_IDUND
idund_path = location.derive_plugin_path (Config.FRIDA_IDUND_NAME);
#endif
}
@@ -5836,6 +5888,9 @@ namespace Frida {
agent_path = null;
#if COMPILER_BACKEND_INSTALLED_LIBRARY || COMPILER_BACKEND_INSTALLED_EXECUTABLE
compiler_backend_path = null;
#endif
#if HAVE_INSTALLED_IDUND
idund_path = null;
#endif
}
#endif
+403 -42
View File
@@ -8,7 +8,7 @@ namespace Frida {
new Gee.HashMap<Idun.Device, FruityHostSessionProvider> ();
private Cancellable io_cancellable = new Cancellable ();
#if HAVE_IDUND_BLOB
#if HAVE_EMBEDDED_IDUND
private TemporaryDirectory daemon_dir = new TemporaryDirectory ();
private TemporaryFile? daemon_file;
@@ -28,8 +28,10 @@ namespace Frida {
#if !MACOS
migrate_pairing_store ();
#endif
#if HAVE_IDUND_BLOB
#if HAVE_EMBEDDED_IDUND
device_monitor.set_daemon_locator (extract_daemon);
#elif HAVE_INSTALLED_IDUND
device_monitor.set_daemon_locator (() => Frida.idund_path);
#endif
try {
yield device_monitor.start (cancellable);
@@ -38,7 +40,7 @@ namespace Frida {
}
}
#if HAVE_IDUND_BLOB
#if HAVE_EMBEDDED_IDUND
private string extract_daemon () throws Efni.Error {
#if WINDOWS
var blob = Frida.Data.Idund.get_idund_exe_blob ();
@@ -945,6 +947,14 @@ namespace Frida {
gadget_path = gadget_value.get_string ();
}
bool interruptible = false;
Variant? interruptible_value = aux["interruptible"];
if (interruptible_value != null) {
if (!interruptible_value.is_of_type (VariantType.BOOLEAN))
throw new Error.INVALID_ARGUMENT ("The 'interruptible' option must be a boolean");
interruptible = interruptible_value.get_boolean ();
}
string[] argv = { app.path };
if (options.has_argv) {
var provided_argv = options.argv;
@@ -967,6 +977,7 @@ namespace Frida {
}
var session = new LLDBSession (lldb, process, gadget_path, ExceptionHandlingStatus.LIMITED, device);
session.interruptible = interruptible;
add_lldb_session (session);
return process.pid;
@@ -1074,7 +1085,9 @@ namespace Frida {
lldb_session = new LLDBSession (lldb, process, null, ExceptionHandlingStatus.FULL, device);
yield lldb_session.kill (cancellable);
yield lldb_session.close (cancellable);
} catch (Error e) {
} catch (GLib.Error e) {
if (e is IOError.CANCELLED)
throw (IOError) e;
var process_control = yield Idun.ProcessControlService.open (device, cancellable);
yield process_control.kill (pid, cancellable);
}
@@ -1083,6 +1096,32 @@ namespace Frida {
}
}
public async HashTable<string, Variant> dump_backtraces (uint pid, Cancellable? cancellable) throws Error, IOError {
try {
var lldb_session = lldb_sessions[pid];
if (lldb_session != null)
return yield lldb_session.dump_backtraces (cancellable);
var lldb = yield start_lldb_service (cancellable);
try {
yield lldb.attach_by_pid (pid, cancellable);
var backtraces = yield BacktraceCollector.collect (lldb, null, cancellable);
yield lldb.detach (cancellable);
yield lldb.close (cancellable);
return backtraces;
} catch (GLib.Error e) {
lldb.close.begin (null);
throw e;
}
} catch (GLib.Error e) {
if (e is Error)
throw (Error) e;
if (e is IOError)
throw (IOError) e;
throw new Error.NOT_SUPPORTED ("%s", e.message);
}
}
public async AgentSessionId attach (uint pid, HashTable<string, Variant> options,
Cancellable? cancellable) throws Error, IOError {
try {
@@ -1663,14 +1702,23 @@ namespace Frida {
public uint attach_count = 0;
public bool interruptible = false;
private Promise<Fruity.Injector.GadgetDetails>? gadget_request;
private Fruity.Injector.GadgetDetails? gadget;
private string? gadget_module_name;
private bool early_instrumentation = false;
private ulong exception_handler = 0;
private string? main_thread_id;
private bool resumed = false;
private bool dumping_backtraces = false;
private Promise<Idun.LLDB.Exception>? pending_interrupt;
private Cancellable io_cancellable = new Cancellable ();
private const size_t PAGE_SIZE = 0x4000;
private const uint INTERRUPT_TIMEOUT_SECONDS = 5;
public LLDBSession (LLDB.Client lldb, LLDB.Process process, string? gadget_path, ExceptionHandlingStatus ehs,
Idun.ChannelProvider channel_provider) {
@@ -1706,48 +1754,51 @@ namespace Frida {
private async void handle_exception (DebuggerException exception) throws Error, Efni.Error, IOError {
var e = (Idun.LLDB.Exception) exception;
var sig = (LLDB.Signal) e.signum;
var medata = e.medata;
if (sig == SIGTRAP && e.metype == EXC_BREAKPOINT && medata.size == 2) {
uint64 x1 = e.context["x1"];
uint64 x2 = e.context["x2"];
if (pending_interrupt != null && !pending_interrupt.future.ready && is_interrupt (e)) {
pending_interrupt.resolve (e);
return;
}
if (is_gadget_breakpoint (e)) {
var action = (GadgetBreakpointAction) e.context["x3"];
if (x1 == x2 == 1337) {
uint64 pc = e.context["pc"];
var thread = e.thread;
yield thread.write_register ("pc", pc + 4, io_cancellable);
switch (action) {
case RESUME: {
resumed = true;
yield lldb.resume (io_cancellable);
uint64 pc = e.context["pc"];
var thread = e.thread;
yield thread.write_register ("pc", pc + 4, io_cancellable);
return;
}
case DETACH: {
yield lldb.detach (io_cancellable);
detached ();
switch (action) {
case RESUME: {
resumed = true;
yield lldb.resume (io_cancellable);
break;
}
case PAGE_PLAN: {
var plan_size = (size_t) e.context["x4"];
var plan_address = e.context["x5"];
yield DebuggerMappings.handle_page_plan (lldb, plan_address, plan_size,
PAGE_SIZE, io_cancellable);
yield thread.write_register ("x0", 0x1337, io_cancellable);
if (resumed)
yield lldb.resume (io_cancellable);
else
yield continue_gadget_threads (io_cancellable);
return;
}
default:
break;
return;
}
case DETACH: {
yield lldb.detach (io_cancellable);
detached ();
break;
}
case PAGE_PLAN: {
var plan_size = (size_t) e.context["x4"];
var plan_address = e.context["x5"];
yield DebuggerMappings.handle_page_plan (lldb, plan_address, plan_size,
PAGE_SIZE, io_cancellable);
yield thread.write_register ("x0", 0x1337, io_cancellable);
if (resumed)
yield lldb.resume (io_cancellable);
else
yield continue_gadget_threads (io_cancellable);
return;
}
case INTERRUPT:
yield restore_execution (io_cancellable);
return;
default:
break;
}
}
@@ -1756,6 +1807,146 @@ namespace Frida {
yield lldb.close (io_cancellable);
}
private static bool is_gadget_breakpoint (Idun.LLDB.Exception e) {
var sig = (LLDB.Signal) e.signum;
if (!(sig == SIGTRAP && e.metype == EXC_BREAKPOINT && e.medata.size == 2))
return false;
uint64 x1 = e.context["x1"];
uint64 x2 = e.context["x2"];
return x1 == x2 == 1337;
}
private static bool is_interrupt (Idun.LLDB.Exception e) {
return is_gadget_breakpoint (e) && (GadgetBreakpointAction) e.context["x3"] == INTERRUPT;
}
public async HashTable<string, Variant> dump_backtraces (Cancellable? cancellable) throws Error, Efni.Error, IOError {
if (dumping_backtraces)
throw new Error.INVALID_OPERATION ("Already dumping backtraces");
if (gadget_request != null && gadget == null)
throw new Error.INVALID_OPERATION ("Gadget is being injected; try again later");
dumping_backtraces = true;
try {
if (gadget == null) {
if (lldb.state != STOPPED)
throw new Error.INVALID_OPERATION ("Process is busy; try again later");
return yield BacktraceCollector.collect (lldb, null, cancellable);
}
return yield interrupt_and_dump_backtraces (cancellable);
} finally {
dumping_backtraces = false;
}
}
private async HashTable<string, Variant> interrupt_and_dump_backtraces (Cancellable? cancellable)
throws Error, Efni.Error, IOError {
if (lldb.state != RUNNING)
throw new Error.INVALID_OPERATION ("Process is busy handling an exception; try again later");
uint16 port = gadget.interrupt_port;
if (port == 0) {
throw new Error.NOT_SUPPORTED ("Gadget can't be interrupted; spawn with the 'interruptible' option " +
"set to true, using a Gadget that supports it");
}
var request = new Promise<Idun.LLDB.Exception> ();
pending_interrupt = request;
IOStream? channel = null;
var timeout = new TimeoutSource.seconds (INTERRUPT_TIMEOUT_SECONDS);
timeout.set_callback (() => {
fail_pending_interrupt (new Error.TIMED_OUT ("Timed out waiting for Gadget's interrupt listener"));
return Source.REMOVE;
});
timeout.attach (MainContext.get_thread_default ());
Idun.LLDB.Exception? interrupt = null;
try {
channel = yield channel_provider.open_channel (("tcp:%" + uint16.FORMAT_MODIFIER + "u").printf (port),
cancellable);
interrupt = yield request.future.wait_async (cancellable);
} catch (GLib.Error e) {
close_channel.begin (channel);
yield resume_if_interrupted (request);
throw_as_lldb_session_error (e);
} finally {
timeout.destroy ();
pending_interrupt = null;
}
HashTable<string, Variant>? backtraces = null;
GLib.Error? pending_error = null;
try {
backtraces = yield BacktraceCollector.collect (lldb, make_gadget_range ((!) interrupt), cancellable);
} catch (GLib.Error e) {
pending_error = e;
}
try {
yield handle_exception ((!) interrupt);
} catch (GLib.Error e) {
if (pending_error == null)
pending_error = e;
}
close_channel.begin (channel);
if (pending_error != null)
throw_as_lldb_session_error (pending_error);
return backtraces;
}
private async void resume_if_interrupted (Promise<Idun.LLDB.Exception> request) {
var interrupt = request.future.value;
if (interrupt == null)
return;
try {
yield handle_exception (interrupt);
} catch (GLib.Error e) {
}
}
private BacktraceCollector.ModuleRange make_gadget_range (Idun.LLDB.Exception interrupt) {
var context = interrupt.context;
return new BacktraceCollector.ModuleRange (gadget_module_name, context["x4"], context["x5"]);
}
private void fail_pending_interrupt (Error error) {
if (pending_interrupt != null && !pending_interrupt.future.ready)
pending_interrupt.reject (error);
}
private static async void close_channel (IOStream? channel) {
if (channel == null)
return;
try {
yield channel.close_async ();
} catch (IOError e) {
}
}
private static void throw_as_lldb_session_error (GLib.Error e) throws Error, Efni.Error, IOError {
if (e is Error)
throw (Error) e;
if (e is Efni.Error)
throw (Efni.Error) e;
if (e is IOError)
throw (IOError) e;
throw new Error.TRANSPORT ("%s", e.message);
}
private async void restore_execution (Cancellable? cancellable) throws Error, Efni.Error, IOError {
if (resumed || !early_instrumentation)
yield lldb.resume (cancellable);
else
yield continue_gadget_threads (cancellable);
}
private async void continue_gadget_threads (Cancellable? cancellable) throws Error, Efni.Error, IOError {
var gadget_threads = new Gee.ArrayList<LLDB.Thread> ();
yield lldb.enumerate_threads (thread => {
@@ -1808,9 +1999,10 @@ namespace Frida {
var module = new Gum.MachoModule.from_file (path, Gum.CpuType.ARM64, ptrauth_support);
Fruity.Injector.Transaction transaction =
yield Fruity.Injector.inject ((owned) module, lldb, channel_provider, cancellable);
yield Fruity.Injector.inject ((owned) module, lldb, channel_provider, interruptible, cancellable);
Fruity.Injector.GadgetDetails gadget = transaction.gadget;
main_thread_id = transaction.main_thread_id;
early_instrumentation = transaction.is_early_instrumentation;
exception_handler = lldb.notify["exception"].connect ((obj, pspec) => {
var exception = lldb.exception;
@@ -1820,6 +2012,8 @@ namespace Frida {
yield transaction.commit (cancellable);
this.gadget = gadget;
gadget_module_name = transaction.loaded_gadget ? path : "FridaGadget.dylib";
gadget_request.resolve (gadget);
return gadget;
@@ -1844,6 +2038,7 @@ namespace Frida {
}
private void on_lldb_closed () {
fail_pending_interrupt (new Error.TRANSPORT ("Debugger connection closed while waiting for Gadget's interrupt"));
closed ();
}
@@ -1857,6 +2052,172 @@ namespace Frida {
LIMITED,
}
private class BacktraceCollector {
public static async HashTable<string, Variant> collect (LLDB.Client lldb, ModuleRange? extra_module,
Cancellable? cancellable) throws Efni.Error, IOError {
var modules = new Gee.ArrayList<ModuleRange> ();
if (extra_module != null)
modules.add (extra_module);
yield lldb.enumerate_modules (m => {
var text = m.segments.first_match (s => s.name == "__TEXT");
if (text != null)
modules.add (new ModuleRange (m.pathname, m.load_address, text.vmsize));
return true;
}, cancellable);
var threads = yield fetch_threads (lldb, cancellable);
var register_names = map_register_names (lldb);
var threads_builder = new VariantBuilder (new VariantType ("aa{sv}"));
foreach (var thread in threads)
threads_builder.add_value (yield describe_thread (lldb, thread, register_names, modules, cancellable));
var result = make_parameters_dict ();
result["threads"] = threads_builder.end ();
return result;
}
private static async Gee.List<ThreadState> fetch_threads (LLDB.Client lldb, Cancellable? cancellable)
throws Efni.Error, IOError {
var response = yield lldb.query_simple ("jThreadsInfo", cancellable);
Json.Node root;
try {
root = Json.from_string (response.payload);
} catch (GLib.Error e) {
throw new Efni.Error.PROTOCOL ("Invalid jThreadsInfo response");
}
if (root.get_node_type () != Json.NodeType.ARRAY)
throw new Efni.Error.PROTOCOL ("Invalid jThreadsInfo response");
var threads = new Gee.ArrayList<ThreadState> ();
foreach (unowned Json.Node element in root.get_array ().get_elements ()) {
unowned Json.Object? info = element.get_object ();
if (info == null || !info.has_member ("tid"))
continue;
int64 tid = info.get_int_member ("tid");
if (tid == 0)
continue;
string? name = info.has_member ("name") ? info.get_string_member ("name") : null;
var thread = new ThreadState (new LLDB.Thread (("%" + int64.FORMAT_MODIFIER + "x").printf (tid), name, lldb));
if (info.has_member ("registers")) {
unowned Json.Object registers = info.get_object_member ("registers");
foreach (unowned string regnum in registers.get_members ())
thread.little_endian_registers[(uint) uint64.parse (regnum)] = registers.get_string_member (regnum);
}
threads.add (thread);
}
return threads;
}
private static Gee.Map<uint, string> map_register_names (LLDB.Client lldb) {
var names = new Gee.HashMap<uint, string> ();
try {
for (uint i = 0; true; i++) {
var reg = lldb.get_register_by_index (i);
names[reg.id] = reg.name;
}
} catch (Efni.Error e) {
}
return names;
}
private static async Variant describe_thread (LLDB.Client lldb, ThreadState state, Gee.Map<uint, string> register_names,
Gee.List<ModuleRange> modules, Cancellable? cancellable) throws IOError {
var thread = state.thread;
var builder = new VariantBuilder (VariantType.VARDICT);
builder.add ("{sv}", "id", new Variant.string ("0x" + thread.id));
unowned string? name = thread.name;
if (name != null)
builder.add ("{sv}", "name", new Variant.string (name));
if (!state.little_endian_registers.is_empty)
builder.add ("{sv}", "registers", describe_registers (state, register_names));
var frames = new VariantBuilder (new VariantType ("aa{sv}"));
try {
uint64 pc = lldb.strip_code_address (yield thread.read_register ("pc", cancellable));
frames.add_value (describe_address (pc, modules));
foreach (var frame in yield thread.generate_backtrace (null, cancellable)) {
if (frame.address == 0)
continue;
frames.add_value (describe_address (frame.address, modules));
}
} catch (Efni.Error e) {
builder.add ("{sv}", "error", new Variant.string (e.message));
}
builder.add ("{sv}", "frames", frames.end ());
return builder.end ();
}
private static Variant describe_registers (ThreadState state, Gee.Map<uint, string> register_names) {
var regnums = new Gee.ArrayList<uint> ();
regnums.add_all (state.little_endian_registers.keys);
regnums.sort ((a, b) => (a < b) ? -1 : (a > b) ? 1 : 0);
var builder = new VariantBuilder (VariantType.VARDICT);
foreach (uint regnum in regnums) {
string? name = register_names[regnum];
builder.add ("{sv}", (name != null) ? name : regnum.to_string (),
new Variant.string (format_little_endian_hex (state.little_endian_registers[regnum])));
}
return builder.end ();
}
private static string format_little_endian_hex (string little_endian_hex) {
var result = new StringBuilder.sized (2 + little_endian_hex.length);
result.append ("0x");
for (int i = little_endian_hex.length - 2; i >= 0; i -= 2)
result.append (little_endian_hex.substring (i, 2));
return result.str;
}
private static Variant describe_address (uint64 address, Gee.List<ModuleRange> modules) {
var builder = new VariantBuilder (VariantType.VARDICT);
string address_str = ("0x%" + uint64.FORMAT_MODIFIER + "x").printf (address);
builder.add ("{sv}", "address", new Variant.string (address_str));
foreach (var m in modules) {
if (address >= m.base_address && address < m.base_address + m.size) {
uint64 offset = address - m.base_address;
if (offset <= uint32.MAX) {
builder.add ("{sv}", "module", new Variant.string (m.path));
builder.add ("{sv}", "offset", new Variant.uint32 ((uint32) offset));
}
break;
}
}
return builder.end ();
}
private class ThreadState {
public LLDB.Thread thread;
public Gee.Map<uint, string> little_endian_registers = new Gee.HashMap<uint, string> ();
public ThreadState (LLDB.Thread thread) {
this.thread = thread;
}
}
public class ModuleRange {
public string path;
public uint64 base_address;
public uint64 size;
public ModuleRange (string path, uint64 base_address, uint64 size) {
this.path = path;
this.base_address = base_address;
this.size = size;
}
}
}
private class GadgetEntry : Object {
public signal void detached (SessionDetachReason reason);
+102 -29
View File
@@ -4,8 +4,9 @@ using Idun;
[CCode (gir_namespace = "FridaFruityInjector", gir_version = "1.0")]
namespace Frida.Fruity.Injector {
public static async Transaction inject (owned Gum.MachoModule module, LLDB.Client lldb, Idun.ChannelProvider channel_provider,
Cancellable? cancellable) throws Error, IOError {
bool interruptible, Cancellable? cancellable) throws Error, IOError {
var session = new Session (module, lldb, channel_provider);
session.interruptible = interruptible;
return yield session.run (cancellable);
}
@@ -17,12 +18,23 @@ namespace Frida.Fruity.Injector {
public string main_thread_id;
private bool is_early_instrumentation;
public bool is_early_instrumentation {
get;
private set;
}
public bool loaded_gadget {
get;
private set;
}
private LLDB.Client lldb;
internal Transaction (GadgetDetails gadget, bool is_early_instrumentation, string main_thread_id, LLDB.Client lldb) {
internal Transaction (GadgetDetails gadget, bool loaded_gadget, bool is_early_instrumentation, string main_thread_id,
LLDB.Client lldb) {
Object (gadget: gadget);
this.loaded_gadget = loaded_gadget;
this.is_early_instrumentation = is_early_instrumentation;
this.main_thread_id = main_thread_id;
this.lldb = lldb;
@@ -49,8 +61,13 @@ namespace Frida.Fruity.Injector {
construct;
}
public GadgetDetails (uint16 port) {
Object (port: port);
public uint16 interrupt_port {
get;
construct;
}
public GadgetDetails (uint16 port, uint16 interrupt_port = 0) {
Object (port: port, interrupt_port: interrupt_port);
}
}
@@ -102,6 +119,8 @@ namespace Frida.Fruity.Injector {
private size_t module_size;
public bool interruptible = false;
public Session (Gum.MachoModule module, LLDB.Client lldb, Idun.ChannelProvider channel_provider) {
Object (
module: module,
@@ -117,7 +136,7 @@ namespace Frida.Fruity.Injector {
var existing_gadget = yield setup (cancellable);
if (existing_gadget != null) {
is_early_instrumentation = false;
return new Transaction (existing_gadget, is_early_instrumentation, main_thread.id, lldb);
return new Transaction (existing_gadget, false, is_early_instrumentation, main_thread.id, lldb);
}
is_early_instrumentation = !libsystem_initialized;
@@ -127,42 +146,40 @@ namespace Frida.Fruity.Injector {
yield teardown (cancellable);
return new Transaction (gadget, is_early_instrumentation, main_thread.id, lldb);
return new Transaction (gadget, true, is_early_instrumentation, main_thread.id, lldb);
} catch (GLib.Error e) {
throw new Error.NOT_SUPPORTED ("%s", e.message);
}
}
private async GadgetDetails? setup (Cancellable? cancellable) throws GLib.Error {
GadgetDetails? existing_gadget = null;
uint16 existing_port = 0;
uint16 existing_interrupt_port = 0;
uint64 existing_interrupt_arm_address = 0;
yield lldb.enumerate_threads (thread => {
if (main_thread == null)
main_thread = thread;
unowned string? name = thread.name;
if (name == null)
return true;
MatchInfo info;
if (/^frida-gadget-tcp-(\d+)$/.match (name, 0, out info)) {
string raw_port = info.fetch (1);
try {
uint64 port;
const uint radix = 10;
uint64.from_string (raw_port, out port, radix, 1, uint16.MAX);
existing_gadget = new GadgetDetails ((uint16) port);
return false;
} catch (NumberParserError e) {
if (name != null) {
uint16 port;
uint64 arm_address;
if (try_parse_gadget_port (name, out port)) {
if (existing_port == 0)
existing_port = port;
} else if (try_parse_interrupt_listener (name, out port, out arm_address)) {
existing_interrupt_port = port;
existing_interrupt_arm_address = arm_address;
}
}
return true;
}, cancellable);
if (existing_gadget != null)
return existing_gadget;
if (existing_port != 0) {
if (existing_interrupt_port != 0)
yield lldb.write_bool (existing_interrupt_arm_address, true, cancellable);
return new GadgetDetails (existing_port, existing_interrupt_port);
}
yield save_main_thread_state (cancellable);
jit_page = yield lldb.allocate (page_size, "rx", cancellable);
@@ -243,7 +260,58 @@ namespace Frida.Fruity.Injector {
var gadget_port = yield upload (buffer, threaded_items.build (symbols), chained_fixups.build (),
new UploadSymbols.from_set (symbols), cancellable);
return new GadgetDetails (gadget_port);
uint16 interrupt_port = 0;
if (interruptible) {
yield lldb.enumerate_threads (thread => {
unowned string? name = thread.name;
uint64 arm_address;
return name == null || !try_parse_interrupt_listener (name, out interrupt_port, out arm_address);
}, cancellable);
}
return new GadgetDetails (gadget_port, interrupt_port);
}
private static bool try_parse_gadget_port (string thread_name, out uint16 port) {
port = 0;
MatchInfo info;
if (!/^frida-gadget-tcp-(\d+)$/.match (thread_name, 0, out info))
return false;
return try_parse_port (info.fetch (1), out port);
}
private static bool try_parse_interrupt_listener (string thread_name, out uint16 port, out uint64 arm_address) {
port = 0;
arm_address = 0;
MatchInfo info;
if (!/^frida-gadget-interrupt-(\d+)-([0-9a-f]+)$/.match (thread_name, 0, out info))
return false;
try {
const uint radix = 16;
uint64.from_string (info.fetch (2), out arm_address, radix);
} catch (NumberParserError e) {
return false;
}
return try_parse_port (info.fetch (1), out port);
}
private static bool try_parse_port (string raw_port, out uint16 port) {
port = 0;
try {
uint64 val;
const uint radix = 10;
uint64.from_string (raw_port, out val, radix, 1, uint16.MAX);
port = (uint16) val;
return true;
} catch (NumberParserError e) {
return false;
}
}
private void perform_rebase_operations (Buffer buffer) throws GLib.Error {
@@ -388,8 +456,13 @@ namespace Frida.Fruity.Injector {
.add_string_value ("resume")
.end_object ()
.set_member_name ("teardown")
.add_string_value ("full")
.end_object ();
.add_string_value ("full");
if (interruptible) {
config
.set_member_name ("interruptible")
.add_boolean_value (true);
}
config.end_object ();
string raw_config = Json.to_string (config.get_root (), false);
string config_param = "frida_gadget_config=" + Base64.encode (raw_config.data);
+5
View File
@@ -1021,6 +1021,11 @@ namespace Frida {
throw new Error.NOT_SUPPORTED ("Services are not supported by this backend");
}
public virtual async HashTable<string, Variant> dump_backtraces (uint pid, Cancellable? cancellable)
throws Error, IOError {
throw new Error.NOT_SUPPORTED ("Backtrace dumping is not supported by this backend");
}
public ServiceSession link_service_session (ServiceSessionId id) throws Error {
return service_session_registry.link (id);
}
+1 -1
View File
@@ -2433,7 +2433,7 @@ namespace Frida {
Gum.Linux.enumerate_ranges ((Posix.pid_t) session.pid, READ | EXECUTE, d => {
unowned Gum.FileMapping? file = d.file;
if (file != null && file.path.has_prefix ("memfd:"))
if (file != null && file.path.has_prefix ("/memfd:"))
return true;
if (d.range.size >= code.length) {
code_start = d.range.base_address + d.range.size - round_size_to_page_size (code.length);
+10 -6
View File
@@ -1791,7 +1791,11 @@ namespace Frida {
}
MatchInfo info;
if (!/^([0-9a-f]+)-([0-9a-f]+) rw-p 00000000 00:00 0 *$/m.match (raw_maps, 0, out info)) {
try {
var anon_rw_pattern = new Regex ("^([0-9a-f]+)-([0-9a-f]+) rw-p 00000000 00:00 0 *$", MULTILINE | RAW);
if (!anon_rw_pattern.match (raw_maps, 0, out info))
assert_not_reached ();
} catch (RegexError e) {
assert_not_reached ();
}
assert (info != null);
@@ -2478,7 +2482,7 @@ namespace Frida {
if (stdout_pipe == null) {
try {
var process = new Subprocess.newv ({ "logcat", "-b", "crash", "-B" },
STDIN_INHERIT | STDOUT_PIPE | STDERR_SILENCE);
STDOUT_PIPE | STDERR_SILENCE);
logcat = process;
stdout_pipe = process.get_stdout_pipe ();
@@ -2894,7 +2898,7 @@ namespace Frida {
},
module_list => {
foreach (var path in module_list)
modules.add_value (new Variant.variant (new Variant.tuple ({ path })));
modules.add_value (new Variant.variant (new Variant.tuple ({ path.make_valid () })));
});
vardict_add (reply, "modules", modules.end ());
@@ -3111,7 +3115,7 @@ namespace Frida {
switch (attachment_type) {
case SyscallTracer.AttachmentType.STRING:
return (string) data;
return ((string) data).make_valid ();
case SyscallTracer.AttachmentType.BYTES: {
if (arg_type != null) {
Variant? v = try_decode_timespec (arg_type, data, len);
@@ -3208,7 +3212,7 @@ namespace Frida {
if (name_len != 0)
Memory.copy (str, path + 1, name_len);
return new Variant.tuple ({ "unix-abstract", (string) str });
return new Variant.tuple ({ "unix-abstract", ((string) str).make_valid () });
}
size_t n = 1;
@@ -3218,7 +3222,7 @@ namespace Frida {
var str = new uint8[n + 1];
Memory.copy (str, path, n);
return new Variant.tuple ({ "unix", (string) str });
return new Variant.tuple ({ "unix", ((string) str).make_valid () });
}
default:
return null;
+7 -2
View File
@@ -449,8 +449,13 @@ namespace Frida {
return;
}
if (!/^([0-9a-f]+)-([0-9a-f]+) (\S{4}) ([0-9a-f]+) ([0-9a-f]{2,}:[0-9a-f]{2,}) (\d+) *([^\n]*)$/m.match (
contents, 0, out info)) {
try {
var line_pattern = new Regex (
"""^([0-9a-f]+)-([0-9a-f]+) (\S{4}) ([0-9a-f]+) ([0-9a-f]{2,}:[0-9a-f]{2,}) (\d+) *([^\n]*)$""",
MULTILINE | RAW);
if (!line_pattern.match (contents, 0, out info))
assert_not_reached ();
} catch (RegexError e) {
assert_not_reached ();
}
}
+25 -20
View File
@@ -597,31 +597,36 @@ if have_fruity_backend
]
backend_deps += idun_dep
if have_idund_blob
if have_idund
idund = custom_target('frida-idund',
input: idund_raw,
output: 'idund' + exe_suffix,
command: post_process + ['executable', 're.frida.Idund'],
install: assets_mode == 'installed',
install_dir: get_option('libdir') / frida_libdir_name / 'plugins',
)
idund_data = custom_target('frida-data-idund',
input: [
'fruity' / 'idund.resources',
idund,
],
output: [
'frida-data-idund.vapi',
'frida-data-idund.h',
'frida-data-idund.c',
'frida-data-idund-blob' + resource_blob_extension,
],
command: [
resource_compiler_cmd_array,
'-c', '@INPUT0@',
'-o', meson.current_build_dir() / 'frida-data-idund',
'@INPUT1@',
],
)
backend_sources += [idund_data]
if assets_mode == 'embedded'
idund_data = custom_target('frida-data-idund',
input: [
'fruity' / 'idund.resources',
idund,
],
output: [
'frida-data-idund.vapi',
'frida-data-idund.h',
'frida-data-idund.c',
'frida-data-idund-blob' + resource_blob_extension,
],
command: [
resource_compiler_cmd_array,
'-c', '@INPUT0@',
'-o', meson.current_build_dir() / 'frida-data-idund',
'@INPUT1@',
],
)
backend_sources += [idund_data]
endif
endif
endif
+5
View File
@@ -1482,6 +1482,11 @@ namespace Frida {
public async ServiceSessionId open_service (string address, Cancellable? cancellable) throws Error, IOError {
throw new Error.NOT_SUPPORTED ("Services are not supported by this backend");
}
public async HashTable<string, Variant> dump_backtraces (uint pid, Cancellable? cancellable)
throws Error, IOError {
throw new Error.NOT_SUPPORTED ("Backtrace dumping is not supported by this backend");
}
}
private class BusService : Object, BusSession {
+173 -4
View File
@@ -1,8 +1,12 @@
#include "frida-core.h"
#include <gio/gunixinputstream.h>
#include <gum/arch-arm/gumarmwriter.h>
#include <gum/arch-arm/gumthumbwriter.h>
#if defined (HAVE_I386)
# include <gum/arch-x86/gumx86writer.h>
#elif defined (HAVE_ARM)
# include <gum/arch-arm/gumarmwriter.h>
# include <gum/arch-arm/gumthumbwriter.h>
#endif
#include <gum/gum.h>
#include <dlfcn.h>
#include <errno.h>
@@ -22,6 +26,7 @@
#endif
#include <sys/wait.h>
#ifdef HAVE_ARM
enum {
GUM_QNX_ARM_REG_PC = ARM_REG_PC,
GUM_QNX_ARM_REG_LR = ARM_REG_LR,
@@ -52,6 +57,7 @@ enum {
#undef ARM_REG_PC
#define PSR_T_BIT (1 << 5)
#endif
#define CHECK_OS_RESULT(n1, cmp, n2, op) \
if (!(n1 cmp n2)) \
@@ -60,6 +66,14 @@ enum {
goto os_failure; \
}
#if defined (HAVE_I386)
# define FRIDA_QNX_GREG_PC(r) ((r)->x86.eip)
# define FRIDA_QNX_GREG_RETVAL(r) ((r)->x86.eax)
#elif defined (HAVE_ARM)
# define FRIDA_QNX_GREG_PC(r) ((r)->arm.gpr[GUM_QNX_ARM_REG_PC])
# define FRIDA_QNX_GREG_RETVAL(r) ((r)->arm.gpr[GUM_QNX_ARM_REG_R0])
#endif
#define FRIDA_REMOTE_PAYLOAD_SIZE (8192)
#define FRIDA_REMOTE_DATA_OFFSET (512)
#define FRIDA_REMOTE_STACK_OFFSET (FRIDA_REMOTE_PAYLOAD_SIZE - 512)
@@ -294,6 +308,138 @@ frida_emit_and_remote_execute (FridaEmitFunc func, const FridaInjectionParams *
return TRUE;
}
#if defined (HAVE_I386)
#define ARG_IMM(value) \
GUM_ARG_ADDRESS, GUM_ADDRESS (value)
#define ARG_REG(reg) \
GUM_ARG_REGISTER, GUM_X86_##reg
#define EMIT_CALL_IMM(func, n_args, ...) \
gum_x86_writer_put_call_address_with_arguments (&cw, GUM_CALL_CAPI, func, n_args, __VA_ARGS__)
#define EMIT_CALL_REG(reg, n_args, ...) \
gum_x86_writer_put_call_reg_with_arguments (&cw, GUM_CALL_CAPI, GUM_X86_##reg, n_args, __VA_ARGS__)
#define EMIT_LOAD_FIELD(reg, field) \
gum_x86_writer_put_mov_reg_address (&cw, GUM_X86_##reg, FRIDA_REMOTE_DATA_FIELD (field)); \
gum_x86_writer_put_mov_reg_reg_ptr (&cw, GUM_X86_##reg, GUM_X86_##reg)
#define EMIT_STORE_FIELD(field, reg) \
gum_x86_writer_put_mov_reg_address (&cw, GUM_X86_EAX, FRIDA_REMOTE_DATA_FIELD (field)); \
gum_x86_writer_put_mov_reg_ptr_reg (&cw, GUM_X86_EAX, GUM_X86_##reg)
static void
frida_emit_payload_code (const FridaInjectionParams * params, GumAddress remote_address, FridaCodeChunk * code)
{
GumX86Writer cw;
const gchar * skip_dlopen = "skip_dlopen";
const gchar * skip_dlclose = "skip_dlclose";
const gchar * skip_detach = "skip_detach";
gum_x86_writer_init (&cw, code->cur);
cw.pc = remote_address;
gum_x86_writer_put_push_reg (&cw, GUM_X86_EBP);
gum_x86_writer_put_mov_reg_reg (&cw, GUM_X86_EBP, GUM_X86_ESP);
gum_x86_writer_put_push_reg (&cw, GUM_X86_EBX);
gum_x86_writer_put_push_reg (&cw, GUM_X86_ESI);
gum_x86_writer_put_push_reg (&cw, GUM_X86_EDI);
gum_x86_writer_put_sub_reg_imm (&cw, GUM_X86_ESP, 4 + 8);
EMIT_CALL_IMM (frida_resolve_remote_libc_function (params->pid, "ConnectAttach_r"),
5,
ARG_IMM (ND_LOCAL_NODE),
ARG_IMM (getpid ()),
ARG_IMM (params->channel_id),
ARG_IMM (_NTO_SIDE_CHANNEL),
ARG_IMM (_NTO_COF_CLOEXEC));
gum_x86_writer_put_mov_reg_reg (&cw, GUM_X86_EDI, GUM_X86_EAX);
gum_x86_writer_put_call_address_with_arguments (&cw, GUM_CALL_CAPI,
frida_resolve_remote_libc_function (params->pid, "gettid"), 0);
EMIT_CALL_IMM (frida_resolve_remote_libc_function (params->pid, "MsgSendPulse_r"),
4,
ARG_REG (EDI),
ARG_IMM (-1),
ARG_IMM (FRIDA_QNX_PULSE_CODE_HELLO),
ARG_REG (EAX));
EMIT_LOAD_FIELD (ESI, module_handle);
gum_x86_writer_put_test_reg_reg (&cw, GUM_X86_ESI, GUM_X86_ESI);
gum_x86_writer_put_jcc_near_label (&cw, X86_INS_JNE, skip_dlopen, GUM_NO_HINT);
{
EMIT_CALL_IMM (frida_resolve_remote_libc_function (params->pid, "dlopen"),
2,
ARG_IMM (FRIDA_REMOTE_DATA_FIELD (so_path)),
ARG_IMM (RTLD_LAZY));
gum_x86_writer_put_mov_reg_reg (&cw, GUM_X86_ESI, GUM_X86_EAX);
EMIT_STORE_FIELD (module_handle, ESI);
}
gum_x86_writer_put_label (&cw, skip_dlopen);
EMIT_CALL_IMM (frida_resolve_remote_libc_function (params->pid, "dlsym"),
2,
ARG_REG (ESI),
ARG_IMM (FRIDA_REMOTE_DATA_FIELD (entrypoint_name)));
gum_x86_writer_put_mov_reg_reg (&cw, GUM_X86_EBX, GUM_X86_EAX);
gum_x86_writer_put_mov_reg_offset_ptr_u32 (&cw, GUM_X86_ESP, 0, FRIDA_UNLOAD_POLICY_IMMEDIATE);
gum_x86_writer_put_mov_reg_offset_ptr_reg (&cw, GUM_X86_ESP, 4, GUM_X86_EDI);
gum_x86_writer_put_mov_reg_reg (&cw, GUM_X86_EAX, GUM_X86_ESP);
gum_x86_writer_put_lea_reg_reg_offset (&cw, GUM_X86_EDX, GUM_X86_ESP, 4);
EMIT_CALL_REG (EBX,
3,
ARG_IMM (FRIDA_REMOTE_DATA_FIELD (entrypoint_data)),
ARG_REG (EAX),
ARG_REG (EDX));
gum_x86_writer_put_mov_reg_reg_ptr (&cw, GUM_X86_EAX, GUM_X86_ESP);
gum_x86_writer_put_cmp_reg_i32 (&cw, GUM_X86_EAX, FRIDA_UNLOAD_POLICY_IMMEDIATE);
gum_x86_writer_put_jcc_near_label (&cw, X86_INS_JNE, skip_dlclose, GUM_NO_HINT);
{
EMIT_CALL_IMM (frida_resolve_remote_libc_function (params->pid, "dlclose"),
1,
ARG_REG (ESI));
}
gum_x86_writer_put_label (&cw, skip_dlclose);
gum_x86_writer_put_mov_reg_reg_ptr (&cw, GUM_X86_EAX, GUM_X86_ESP);
gum_x86_writer_put_cmp_reg_i32 (&cw, GUM_X86_EAX, FRIDA_UNLOAD_POLICY_DEFERRED);
gum_x86_writer_put_jcc_near_label (&cw, X86_INS_JE, skip_detach, GUM_NO_HINT);
{
EMIT_LOAD_FIELD (EAX, worker_thread);
EMIT_CALL_IMM (frida_resolve_remote_libc_function (params->pid, "pthread_detach"),
1,
ARG_REG (EAX));
}
gum_x86_writer_put_label (&cw, skip_detach);
gum_x86_writer_put_mov_reg_reg_ptr (&cw, GUM_X86_EAX, GUM_X86_ESP);
EMIT_CALL_IMM (frida_resolve_remote_libc_function (params->pid, "MsgSendPulse_r"),
4,
ARG_REG (EDI),
ARG_IMM (-1),
ARG_IMM (FRIDA_QNX_PULSE_CODE_BYE),
ARG_REG (EAX));
EMIT_CALL_IMM (frida_resolve_remote_libc_function (params->pid, "ConnectDetach_r"),
1,
ARG_REG (EDI));
gum_x86_writer_put_add_reg_imm (&cw, GUM_X86_ESP, 4 + 8);
gum_x86_writer_put_pop_reg (&cw, GUM_X86_EDI);
gum_x86_writer_put_pop_reg (&cw, GUM_X86_ESI);
gum_x86_writer_put_pop_reg (&cw, GUM_X86_EBX);
gum_x86_writer_put_pop_reg (&cw, GUM_X86_EBP);
gum_x86_writer_put_xor_reg_reg (&cw, GUM_X86_EAX, GUM_X86_EAX);
gum_x86_writer_put_ret (&cw);
gum_x86_writer_flush (&cw);
code->cur = gum_x86_writer_cur (&cw);
code->size += gum_x86_writer_offset (&cw);
gum_x86_writer_clear (&cw);
}
#elif defined (HAVE_ARM)
#define EMIT_MOVE(dst, src) \
gum_arm_writer_put_mov_reg_reg (&cw, ARM_REG_##dst, ARM_REG_##src)
#define EMIT_ADD(dst, src, offset) \
@@ -333,6 +479,7 @@ frida_emit_payload_code (const FridaInjectionParams * params, GumAddress remote_
const gchar * skip_detach = "skip_detach";
gum_arm_writer_init (&cw, code->cur);
cw.pc = remote_address;
gum_arm_writer_put_push_regs (&cw, 4, ARM_REG_R5, ARM_REG_R6, ARM_REG_R7, ARM_REG_LR);
@@ -427,6 +574,8 @@ frida_emit_payload_code (const FridaInjectionParams * params, GumAddress remote_
gum_arm_writer_clear (&cw);
}
#endif
static GumAddress
frida_remote_alloc (pid_t pid, size_t size, int prot, GError ** error)
{
@@ -649,6 +798,25 @@ frida_remote_call (pid_t pid, GumAddress func, const GumAddress * args, gint arg
memcpy (&modified_registers, &saved_registers, sizeof (saved_registers));
#if defined (HAVE_I386)
{
guint32 sp, frame[1 + 16];
gint n = 0;
g_assert (args_length <= 16);
frame[n++] = 0xfffffff0;
for (i = 0; i != args_length; i++)
frame[n++] = args[i];
sp = ((modified_registers.x86.esp - 128 - (args_length * 4)) & ~15) - 4;
if (!frida_remote_write_fd (fd, sp, frame, n * 4, error))
goto beach;
modified_registers.x86.esp = sp;
modified_registers.x86.eip = func;
}
#elif defined (HAVE_ARM)
/*
* Set the PC to be the function address and SP to the stack address.
*/
@@ -681,11 +849,12 @@ frida_remote_call (pid_t pid, GumAddress func, const GumAddress * args, gint arg
* Set the LR to be a dummy address which will trigger a pagefault.
*/
modified_registers.arm.gpr[GUM_QNX_ARM_REG_LR] = 0xfffffff0;
#endif
ret = devctl (fd, DCMD_PROC_SETGREG, &modified_registers, sizeof (modified_registers), 0);
CHECK_OS_RESULT (ret, ==, 0, "DCMD_PROC_SETGREG");
while (modified_registers.arm.gpr[GUM_QNX_ARM_REG_PC] != 0xfffffff0)
while (FRIDA_QNX_GREG_PC (&modified_registers) != 0xfffffff0)
{
/*
* Continue the process, watching for FLTPAGE which should trigger when
@@ -713,7 +882,7 @@ frida_remote_call (pid_t pid, GumAddress func, const GumAddress * args, gint arg
}
if (retval != NULL)
*retval = modified_registers.arm.gpr[GUM_QNX_ARM_REG_R0];
*retval = FRIDA_QNX_GREG_RETVAL (&modified_registers);
/*
* Restore the registers and continue the process:
+4
View File
@@ -436,6 +436,10 @@ namespace Frida {
throw_not_supported ();
}
public async HashTable<string, Variant> dump_backtraces (uint pid, Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
public ServiceSession link_service_session (ServiceSessionId id) throws Error {
throw_not_supported ();
}
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/efni.git
revision = 329dd8e94505e95e066251e3d4dfe25d0e55eec3
revision = 3bcd99ff81b7b648ff4d784fb3154e55e7950c20
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/frida-gum.git
revision = 17.25.2
revision = c8f22bb1db7904adcc4ad046c0948ee9597a95fa
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida-forks/glib.git
revision = c366177b9292432a26f6903fa82a71d83e4927eb
revision = c6df0665dab0f971f66c5c46055b7d0b6085d1b1
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/idun.git
revision = 6248f41225e1111735e5b4281c1e0cc53f00ff17
revision = 6040e17b1ef871ff7ae5b18fffbe5f0396a0810c
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida-forks/lwip.git
revision = f8424fd177ae3654fa040b8b6bc0c32de7fd6ad9
revision = 857ac9ef0de0e47c8b8f7a0b66f09673238368d0
depth = 1
[provide]
+274
View File
@@ -69,6 +69,21 @@ namespace Frida.HostSessionTest {
var h = new Harness ((h) => Fruity.Manual.Xpc.launch.begin (h as Harness));
h.run ();
});
GLib.Test.add_func ("/HostSession/Fruity/Manual/DumpBacktraces/blocked-frida-threads", () => {
var h = new Harness ((h) => Fruity.Manual.DumpBacktraces.blocked_frida_threads.begin (h as Harness));
h.run ();
});
GLib.Test.add_func ("/HostSession/Fruity/Manual/DumpBacktraces/reattach", () => {
var h = new Harness ((h) => Fruity.Manual.DumpBacktraces.reattach.begin (h as Harness));
h.run ();
});
GLib.Test.add_func ("/HostSession/Fruity/Manual/DumpBacktraces/not-interruptible", () => {
var h = new Harness ((h) => Fruity.Manual.DumpBacktraces.not_interruptible.begin (h as Harness));
h.run ();
});
#endif
#if HAVE_DROIDY_BACKEND
@@ -4304,6 +4319,265 @@ namespace Frida.HostSessionTest {
}
namespace DumpBacktraces {
private async void blocked_frida_threads (Harness h) {
if (!GLib.Test.slow ()) {
stdout.printf ("<skipping, run in slow mode with iOS device connected> ");
h.done ();
return;
}
var target = Target.from_environment ();
if (target == null) {
stdout.printf ("<skipping, set %s> ", Target.REQUIRED_VARIABLES);
h.done ();
return;
}
h.disable_timeout ();
var device_manager = new DeviceManager ();
try {
var device = yield device_manager.get_device_by_id (target.device_id, 5000);
var pid = yield spawn_with_gadget (device, target, true);
var session = yield device.attach (pid);
yield device.resume (pid);
var script = yield session.create_script ("""
const hits = { accept: 0, close: 0 };
function hijack(name, retType, argTypes) {
const target = Module.getGlobalExportByName(name);
const original = new NativeFunction(target, retType, argTypes);
Interceptor.replace(target, new NativeCallback((...args) => {
hits[name]++;
return original(...args);
}, retType, argTypes));
}
hijack('accept', 'int', ['int', 'pointer', 'pointer']);
hijack('close', 'int', ['int']);
const closeHitsBefore = hits.close;
new NativeFunction(Module.getGlobalExportByName('close'), 'int', ['int'])(-1);
send(hits.close > closeHitsBefore);
setTimeout(() => {
const end = Date.now() + 60000;
while (Date.now() < end)
;
}, 100);
""");
var hook_check = new Promise<string> ();
var message_handler = script.message.connect ((json, data) => {
if (!hook_check.future.ready)
hook_check.resolve (json);
});
yield script.load ();
var hook_check_message = yield hook_check.future.wait_async (null);
script.disconnect (message_handler);
assert_true (hook_check_message == "{\"type\":\"send\",\"payload\":true}");
yield sleep (500);
for (uint i = 0; i != 3; i++)
assert_backtraces_include_gadget (yield device.dump_backtraces (pid), target.gadget_path);
yield device.kill (pid);
} catch (GLib.Error e) {
printerr ("\nFAIL: %s\n\n", e.message);
assert_not_reached ();
}
yield close_device_manager (device_manager);
h.done ();
}
private async void reattach (Harness h) {
if (!GLib.Test.slow ()) {
stdout.printf ("<skipping, run in slow mode with iOS device connected> ");
h.done ();
return;
}
var target = Target.from_environment ();
if (target == null) {
stdout.printf ("<skipping, set %s> ", Target.REQUIRED_VARIABLES);
h.done ();
return;
}
h.disable_timeout ();
var device_manager = new DeviceManager ();
try {
var device = yield device_manager.get_device_by_id (target.device_id, 5000);
var pid = yield spawn_with_gadget (device, target, true);
var session = yield device.attach (pid);
yield device.resume (pid);
var interrupt_port = find_interrupt_port (yield device.dump_backtraces (pid));
assert_true (interrupt_port != 0);
yield session.detach ();
yield connect_and_hang_up (device, interrupt_port);
yield sleep (1000);
session = yield device.attach (pid);
var backtraces = yield device.dump_backtraces (pid);
assert_backtraces_include_gadget (backtraces, "FridaGadget.dylib");
assert_true (find_interrupt_port (backtraces) == interrupt_port);
yield session.detach ();
yield device.kill (pid);
} catch (GLib.Error e) {
printerr ("\nFAIL: %s\n\n", e.message);
assert_not_reached ();
}
yield close_device_manager (device_manager);
h.done ();
}
private async void not_interruptible (Harness h) {
if (!GLib.Test.slow ()) {
stdout.printf ("<skipping, run in slow mode with iOS device connected> ");
h.done ();
return;
}
var target = Target.from_environment ();
if (target == null) {
stdout.printf ("<skipping, set %s> ", Target.REQUIRED_VARIABLES);
h.done ();
return;
}
h.disable_timeout ();
var device_manager = new DeviceManager ();
try {
var device = yield device_manager.get_device_by_id (target.device_id, 5000);
var pid = yield spawn_with_gadget (device, target, false);
yield device.attach (pid);
yield device.resume (pid);
try {
yield device.dump_backtraces (pid);
assert_not_reached ();
} catch (Error e) {
assert_true (e is Error.NOT_SUPPORTED);
}
yield device.kill (pid);
} catch (GLib.Error e) {
printerr ("\nFAIL: %s\n\n", e.message);
assert_not_reached ();
}
yield close_device_manager (device_manager);
h.done ();
}
private async uint spawn_with_gadget (Device device, Target target, bool interruptible) throws GLib.Error {
var options = new SpawnOptions ();
options.aux["gadget"] = target.gadget_path;
if (interruptible)
options.aux["interruptible"] = true;
return yield device.spawn (target.app_id, options);
}
private async void connect_and_hang_up (Device device, uint16 port) {
try {
var connection = yield device.open_channel ("tcp:%u".printf (port));
yield connection.close_async ();
} catch (GLib.Error e) {
}
}
private void assert_backtraces_include_gadget (HashTable<string, Variant> backtraces, string gadget_module) {
var threads = backtraces["threads"];
assert_true (threads.n_children () > 0);
bool found_interrupt_listener = false;
uint gadget_frames = 0;
foreach (var thread in threads) {
var thread_dict = new VariantDict (thread);
string name;
if (thread_dict.lookup ("name", "s", out name) && name.has_prefix ("frida-gadget-interrupt-"))
found_interrupt_listener = true;
foreach (var frame in thread_dict.lookup_value ("frames", new VariantType ("aa{sv}"))) {
string module;
if (new VariantDict (frame).lookup ("module", "s", out module) && module == gadget_module)
gadget_frames++;
}
}
assert_true (found_interrupt_listener);
assert_true (gadget_frames > 0);
}
private uint16 find_interrupt_port (HashTable<string, Variant> backtraces) {
foreach (var thread in backtraces["threads"]) {
string name;
if (new VariantDict (thread).lookup ("name", "s", out name) &&
name.has_prefix ("frida-gadget-interrupt-")) {
return (uint16) uint.parse (name.split ("-")[3]);
}
}
return 0;
}
private class Target {
public const string REQUIRED_VARIABLES = "FRIDA_FRUITY_DEVICE_ID, FRIDA_FRUITY_APP_ID and FRIDA_FRUITY_GADGET";
public string device_id;
public string app_id;
public string gadget_path;
public static Target? from_environment () {
unowned string? device_id = Environment.get_variable ("FRIDA_FRUITY_DEVICE_ID");
unowned string? app_id = Environment.get_variable ("FRIDA_FRUITY_APP_ID");
unowned string? gadget_path = Environment.get_variable ("FRIDA_FRUITY_GADGET");
if (device_id == null || app_id == null || gadget_path == null)
return null;
var target = new Target ();
target.device_id = device_id;
target.app_id = app_id;
target.gadget_path = gadget_path;
return target;
}
}
private async void sleep (uint milliseconds) {
Timeout.add (milliseconds, sleep.callback);
yield;
}
private async void close_device_manager (DeviceManager device_manager) {
try {
yield device_manager.close ();
} catch (IOError e) {
assert_not_reached ();
}
}
}
}
}
+48 -1
View File
@@ -22,6 +22,10 @@ namespace Frida.InjectorTest {
GLib.Test.add_func ("/Injector/resource-leaks", test_resource_leaks);
#if LINUX
GLib.Test.add_func ("/Injector/Linux/inject-with-non-utf8-mapping", test_injection_with_non_utf8_mapping);
#endif
#if DARWIN
GLib.Test.add_func ("/Injector/suspended-injection-current-arch", () => {
test_suspended_injection (Frida.Test.Arch.CURRENT);
@@ -149,6 +153,45 @@ namespace Frida.InjectorTest {
rat.close ();
}
#if LINUX
private void test_injection_with_non_utf8_mapping () {
var logfile = File.new_for_path (Frida.Test.path_to_temporary_file ("non-utf8-mapping.log"));
try {
logfile.delete ();
} catch (GLib.Error delete_error) {
}
var envp = new string[] {
"FRIDA_LABRAT_LOGFILE=" + logfile.get_path ()
};
var sleeper = File.new_for_path (Frida.Test.Labrats.path_to_executable ("sleeper"));
var sleeper_copy = File.new_for_path (Frida.Test.path_to_temporary_file ("sleeper-\xff"));
try {
sleeper.copy (sleeper_copy, OVERWRITE);
} catch (GLib.Error e) {
assert_not_reached ();
}
FileUtils.chmod (sleeper_copy.get_path (), 0700);
var rat = new Labrat.from_path (sleeper_copy.get_path (), envp);
rat.inject ("simple-agent", "");
rat.wait_for_uninject ();
assert_true (content_of (logfile) == ">m<");
try {
rat.process.kill ();
logfile.delete ();
sleeper_copy.delete ();
} catch (GLib.Error e) {
assert_not_reached ();
}
rat.close ();
}
#endif
#if DARWIN
private void test_suspended_injection (Frida.Test.Arch arch) {
var logfile = File.new_for_path (Frida.Test.path_to_temporary_file ("suspended-injection.log"));
@@ -193,8 +236,12 @@ namespace Frida.InjectorTest {
private PendingUninject? pending_uninject;
public Labrat (string name, string[] envp, Frida.Test.Arch arch = Frida.Test.Arch.CURRENT) {
this.from_path (Frida.Test.Labrats.path_to_executable (name), envp, arch);
}
public Labrat.from_path (string path, string[] envp, Frida.Test.Arch arch = Frida.Test.Arch.CURRENT) {
try {
process = Frida.Test.Process.start (Frida.Test.Labrats.path_to_executable (name), null, envp, arch);
process = Frida.Test.Process.start (path, null, envp, arch);
} catch (Error e) {
printerr ("\nFAIL: %s\n\n", e.message);
assert_not_reached ();
+1
View File
@@ -6,4 +6,5 @@ namespace Config {
public const string FRIDA_HELPER_NAME;
public const string FRIDA_AGENT_NAME;
public const string FRIDA_COMPILER_BACKEND_NAME;
public const string FRIDA_IDUND_NAME;
}