mirror of
https://github.com/frida/frida-core.git
synced 2026-11-12 11:27:43 -10:00
Compare commits
30
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a11ee1c3f7 | ||
|
|
2faca1f013 | ||
|
|
588f79c5ec | ||
|
|
588ac3b3de | ||
|
|
d0615e7574 | ||
|
|
1b169dec1c | ||
|
|
0e61ae2762 | ||
|
|
6a445d9dea | ||
|
|
2a41a960fe | ||
|
|
25bd49299a | ||
|
|
c84323b6ac | ||
|
|
45aef70f35 | ||
|
|
eb8efdf40a | ||
|
|
025e5c433d | ||
|
|
f9314073dc | ||
|
|
4011e34449 | ||
|
|
2fef8d7d20 | ||
|
|
9f681e26f1 | ||
|
|
18a156817f | ||
|
|
2ea8b6278e | ||
|
|
40ce660b54 | ||
|
|
7913d880c2 | ||
|
|
34e718e4a3 | ||
|
|
10d6f866b1 | ||
|
|
d5bc484072 | ||
|
|
04bd09b7b7 | ||
|
|
ef7dd9701c | ||
|
|
8777bbe8af | ||
|
|
8734d9875a | ||
|
|
d043c66c75 |
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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.
@@ -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
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 ());
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Generated
+4
-4
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
Generated
+4
-4
@@ -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": {
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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,6 +1,6 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/frida/efni.git
|
||||
revision = 329dd8e94505e95e066251e3d4dfe25d0e55eec3
|
||||
revision = 3bcd99ff81b7b648ff4d784fb3154e55e7950c20
|
||||
depth = 1
|
||||
|
||||
[provide]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/frida/frida-gum.git
|
||||
revision = 17.25.2
|
||||
revision = c8f22bb1db7904adcc4ad046c0948ee9597a95fa
|
||||
depth = 1
|
||||
|
||||
[provide]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/frida-forks/glib.git
|
||||
revision = c366177b9292432a26f6903fa82a71d83e4927eb
|
||||
revision = c6df0665dab0f971f66c5c46055b7d0b6085d1b1
|
||||
depth = 1
|
||||
|
||||
[provide]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/frida/idun.git
|
||||
revision = 6248f41225e1111735e5b4281c1e0cc53f00ff17
|
||||
revision = 6040e17b1ef871ff7ae5b18fffbe5f0396a0810c
|
||||
depth = 1
|
||||
|
||||
[provide]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[wrap-git]
|
||||
url = https://github.com/frida-forks/lwip.git
|
||||
revision = f8424fd177ae3654fa040b8b6bc0c32de7fd6ad9
|
||||
revision = 857ac9ef0de0e47c8b8f7a0b66f09673238368d0
|
||||
depth = 1
|
||||
|
||||
[provide]
|
||||
|
||||
@@ -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 ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 ();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user