Compare commits

...
26 Commits
Author SHA1 Message Date
Ole André Vadla Ravnås 2a41a960fe subprojects: Prepare for release 2026-11-07 15:45:32 +01:00
Ole André Vadla Ravnås 25bd49299a subprojects: Bump outdated 2026-11-07 15:44:13 +01:00
tubibibo c84323b6ac linux: Don't pass our stdin to crash logcat
Under `adb shell`, frida-server's stdin and stdout are the same socket.
The crash monitor's logcat inherited it, so when frida-server died
abnormally, logcat kept it open and `adb shell` never saw EOF.

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

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

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

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

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

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

Depends on the ThreadProvider interface in frida-gum.
2026-11-03 23:11:43 +01:00
Francesco Tamagni 8734d9875a fruity: Fix kill() fallback (#1282)
LLDB errors are Efni.Error since the move to idun, so catching
Frida.Error skipped the ProcessControlService fallback.
2026-11-03 17:35:54 +01:00
Francesco Tamagni d043c66c75 channel: Keep streams alive until linked
The registry only holds a weak reference, so a stream nobody else owns,
such as one relayed by idund, was gone before link().
2026-11-03 17:29:07 +01:00
Ole André Vadla Ravnås 317363ce80 subprojects: Prepare for release 2026-11-02 22:10:32 +01:00
Ole André Vadla Ravnås f1aca3182f subprojects: Bump outdated 2026-11-02 22:08:48 +01:00
Ole André Vadla Ravnås 92bc074641 linux: Skip libraries of a foreign ABI in mapper
On x86_64 Android with a native bridge, the arm64 guest libraries may
be mapped before the host ones, and since the mapper looks up
dependencies by basename, the x86_64 agent got bound to the guest libc.

Only accept libraries whose machine and pointer size match the agent.

Fixes #1281.
2026-11-02 21:01:10 +01:00
Ole André Vadla Ravnås 8c98e9b6c6 linux: Find module bases using the ELF layout
Since 00d8ba24, the mapper picks library bases through
find_module_by_path(), whose heuristic starts a new candidate at every
mapping with file offset 0 and prefers executable ones. This breaks
libraries whose code shares file page 0 with their headers, such as
Android's libdl.so on x86_64, where the code mapping was picked as the
base, one page too high.

Instead, accept a mapping at file offset 0 as the base only if the
library's code segments are mapped executable where the linker would
have put them. This also rejects stray mappings of the file, so the
Android-specific libc workaround is no longer needed.

Fixes #1280.
2026-11-02 21:00:51 +01:00
20 changed files with 496 additions and 163 deletions

No files matched your search

+4 -1
View File
@@ -451,7 +451,10 @@ jobs:
cd build
cp -a tests/frida-tests tests/labrats lib/agent/frida-agent.so /tmp/pkg/
tar -C /tmp/pkg -cf /tmp/runner.tar .
/opt/sabrelite/run.sh /tmp/runner.tar /opt/frida/frida-tests
/opt/sabrelite/run.sh /tmp/runner.tar \
"cp /opt/frida/frida-tests /opt/frida/frida-agent.so /tmp/ && \
ln -sP /opt/frida/labrats /tmp/labrats && \
/tmp/frida-tests"
# This job is used to check that the Linux helpers are built correctly for all
# supported architectures. We do this because the binary artifacts are checked
+248
View File
@@ -103,6 +103,9 @@ namespace Frida.Agent {
private SpawnMonitor? spawn_monitor;
private ThreadSuspendMonitor? thread_suspend_monitor;
private UnwindSitter? unwind_sitter;
#if ANDROID && (ARM || ARM64)
private NativeCloakMirror? native_cloak_mirror;
#endif
private delegate void CompletionNotify ();
@@ -350,6 +353,9 @@ namespace Frida.Agent {
bool enable_unwind_broker = true;
bool enable_exit_monitor = true;
bool enable_thread_suspend_monitor = true;
#if ANDROID && (ARM || ARM64)
bool in_emulated_realm = false;
#endif
foreach (unowned string option in tokens[1:]) {
if (option == "eternal")
ensure_eternalized ();
@@ -365,6 +371,10 @@ namespace Frida.Agent {
enable_exit_monitor = false;
else if (option == "thread-suspend-monitor:off")
enable_thread_suspend_monitor = false;
#if ANDROID && (ARM || ARM64)
else if (option == "realm:emulated")
in_emulated_realm = true;
#endif
}
if (exceptor_mode != FULL)
@@ -374,6 +384,10 @@ namespace Frida.Agent {
try {
yield setup_connection_with_transport_uri (transport_uri);
#if ANDROID && (ARM || ARM64)
if (in_emulated_realm)
yield connect_to_native_realm ();
#endif
} catch (Error e) {
start_error = e;
main_loop.quit ();
@@ -406,6 +420,23 @@ namespace Frida.Agent {
padder = null;
}
#if ANDROID && (ARM || ARM64)
private async void connect_to_native_realm () throws Error {
try {
NativeCloak cloak = yield connection.get_proxy (null, ObjectPath.NATIVE_CLOAK, DO_NOT_LOAD_PROPERTIES,
null);
native_cloak_mirror = new NativeCloakMirror (cloak);
native_cloak_mirror.activate ();
NativeThreadRegistry registry = yield connection.get_proxy (null, ObjectPath.NATIVE_THREAD_REGISTRY,
DO_NOT_LOAD_PROPERTIES, null);
Gum.ThreadRegistry.set_provider (new NativeThreadProvider (registry));
} catch (GLib.Error e) {
throw new Error.TRANSPORT ("%s", e.message);
}
}
#endif
private static bool can_allocate_code () {
size_t page_size = Gum.query_page_size ();
@@ -1478,6 +1509,8 @@ namespace Frida.Agent {
private Socket? emulated_socket;
private BridgeState? emulated_bridge_state;
private Thread<void>? emulated_worker;
private NativeCloakService? native_cloak;
private NativeThreadRegistryService? native_thread_registry;
private async AgentSessionProvider? try_get_emulated_provider (Cancellable? cancellable) throws IOError {
if (get_emulated_request == null)
@@ -1517,6 +1550,8 @@ namespace Frida.Agent {
emulated_agent = nb_api.load_library_ext (emulated_agent_path, RTLD_LAZY, classloader_namespace);
} else {
emulated_agent = nb_api.load_library (emulated_agent_path, RTLD_LAZY);
if (emulated_agent == null && nb_api.load_library_ext != null)
emulated_agent = nb_api.load_library_ext (emulated_agent_path, RTLD_LAZY, null);
}
if (emulated_agent == null)
throw new Error.NOT_SUPPORTED ("Process is not using emulation");
@@ -1556,6 +1591,7 @@ namespace Frida.Agent {
* code being discarded, which seems like an edge-case the translator doesn't handle.
*/
parameters.append ("|exceptor:off|exit-monitor:off");
parameters.append ("|realm:emulated");
emulated_bridge_state = new BridgeState (parameters.str);
@@ -1564,6 +1600,13 @@ namespace Frida.Agent {
var connection = yield new DBusConnection (stream, ServerGuid.HOST_SESSION_SERVICE,
AUTHENTICATION_SERVER | AUTHENTICATION_ALLOW_ANONYMOUS, null, cancellable);
native_cloak = new NativeCloakService ();
connection.register_object (ObjectPath.NATIVE_CLOAK, (NativeCloak) native_cloak);
native_thread_registry = new NativeThreadRegistryService ();
connection.register_object (ObjectPath.NATIVE_THREAD_REGISTRY,
(NativeThreadRegistry) native_thread_registry);
AgentSessionProvider provider = yield connection.get_proxy (null, ObjectPath.AGENT_SESSION_PROVIDER,
DO_NOT_LOAD_PROPERTIES, cancellable);
@@ -1611,6 +1654,16 @@ namespace Frida.Agent {
emulated_worker = null;
}
if (native_thread_registry != null) {
native_thread_registry.close ();
native_thread_registry = null;
}
if (native_cloak != null) {
native_cloak.close ();
native_cloak = null;
}
emulated_socket = null;
if (emulated_agent != null) {
@@ -1771,6 +1824,201 @@ namespace Frida.Agent {
}
#endif
#if ANDROID && (X86 || X86_64)
private sealed class NativeCloakService : Object, NativeCloak, Gum.Cloak.Observer {
private MainContext main_context = MainContext.ref_thread_default ();
private bool observing = false;
public uint[] activate (Cancellable? cancellable) {
Gum.Cloak.add_observer (this);
observing = true;
uint[] threads = {};
Gum.Cloak.enumerate_threads (id => {
threads += (uint) id;
return true;
});
return threads;
}
public void close () {
if (!observing)
return;
Gum.Cloak.remove_observer (this);
observing = false;
}
public void thread_added (Gum.ThreadId id) {
schedule (() => {
thread_cloaked ((uint) id);
return Source.REMOVE;
});
}
public void thread_removed (Gum.ThreadId id) {
schedule (() => {
thread_uncloaked ((uint) id);
return Source.REMOVE;
});
}
private void schedule (owned SourceFunc function) {
var source = new IdleSource ();
source.set_callback ((owned) function);
source.attach (main_context);
}
}
private sealed class NativeThreadRegistryService : Object, NativeThreadRegistry {
private unowned Gum.ThreadRegistry? registry;
private ulong added_handler = 0;
private ulong removed_handler = 0;
private ulong renamed_handler = 0;
public NativeThreadInfo[] activate (Cancellable? cancellable) {
registry = Gum.ThreadRegistry.obtain ();
NativeThreadInfo[] threads = {};
registry.lock ();
added_handler = registry.thread_added.connect (on_thread_added);
removed_handler = registry.thread_removed.connect (on_thread_removed);
renamed_handler = registry.thread_renamed.connect (on_thread_renamed);
registry.enumerate_threads (thread => {
threads += NativeThreadInfo ((uint) thread.id, thread.name ?? "");
return true;
});
registry.unlock ();
return threads;
}
public void deactivate (Cancellable? cancellable) {
close ();
}
public void close () {
if (registry == null)
return;
registry.disconnect (added_handler);
registry.disconnect (removed_handler);
registry.disconnect (renamed_handler);
registry = null;
}
private void on_thread_added (Gum.ThreadDetails thread) {
thread_added (NativeThreadInfo ((uint) thread.id, thread.name ?? ""));
}
private void on_thread_removed (Gum.ThreadDetails thread) {
thread_removed ((uint) thread.id);
}
private void on_thread_renamed (Gum.ThreadDetails thread, string? previous_name) {
thread_renamed ((uint) thread.id, thread.name ?? "");
}
}
#endif
#if ANDROID && (ARM || ARM64)
private sealed class NativeCloakMirror : Object {
public NativeCloak native_cloak {
get;
construct;
}
public NativeCloakMirror (NativeCloak native_cloak) {
Object (native_cloak: native_cloak);
}
public void activate () throws GLib.Error {
native_cloak.thread_cloaked.connect (on_thread_cloaked);
native_cloak.thread_uncloaked.connect (on_thread_uncloaked);
foreach (uint id in native_cloak.activate (null))
on_thread_cloaked (id);
}
private void on_thread_cloaked (uint id) {
if (!Gum.Cloak.has_thread (id))
Gum.Cloak.add_thread (id);
}
private void on_thread_uncloaked (uint id) {
Gum.Cloak.remove_thread (id);
}
}
private sealed class NativeThreadProvider : Object, Gum.ThreadProvider {
public NativeThreadRegistry native_registry {
get;
construct;
}
private unowned Gum.ThreadRegistry registry;
private ulong added_handler = 0;
private ulong removed_handler = 0;
private ulong renamed_handler = 0;
public NativeThreadProvider (NativeThreadRegistry native_registry) {
Object (native_registry: native_registry);
}
public void activate (Gum.ThreadRegistry registry) {
this.registry = registry;
added_handler = native_registry.thread_added.connect (add_thread);
removed_handler = native_registry.thread_removed.connect (on_thread_removed);
renamed_handler = native_registry.thread_renamed.connect (on_thread_renamed);
NativeThreadInfo[] threads;
try {
threads = native_registry.activate (null);
} catch (GLib.Error e) {
return;
}
foreach (var thread in threads)
add_thread (thread);
}
public void deactivate () {
native_registry.disconnect (added_handler);
native_registry.disconnect (removed_handler);
native_registry.disconnect (renamed_handler);
try {
native_registry.deactivate (null);
} catch (GLib.Error e) {
}
}
private void add_thread (NativeThreadInfo thread) {
var details = Gum.ThreadDetails ();
details.id = thread.id;
if (thread.name != "") {
details.name = thread.name;
details.flags = NAME;
}
registry.register (details);
}
private void on_thread_removed (uint id) {
registry.unregister (id);
}
private void on_thread_renamed (uint id, string name) {
registry.rename (id, (name != "") ? name : null);
}
}
#endif
private sealed class LiveAgentSession : BaseAgentSession {
public uint registration_id {
get;
+12 -2
View File
@@ -6,6 +6,8 @@ namespace Frida {
private Gee.Map<ChannelId?, weak IOStream> channels =
new Gee.HashMap<ChannelId?, weak IOStream> (ChannelId.hash, ChannelId.equal);
private Gee.Map<ChannelId?, IOStream> unlinked_channels =
new Gee.HashMap<ChannelId?, IOStream> (ChannelId.hash, ChannelId.equal);
~ChannelRegistry () {
clear ();
@@ -18,25 +20,33 @@ namespace Frida {
channel_stream.abandon ();
}
channels.clear ();
unlinked_channels.clear ();
}
public void register (ChannelId id, IOStream stream) {
stream.set_data ("channel-registry-entry", new Entry (this, id));
lock (channels)
lock (channels) {
channels[id] = stream;
unlinked_channels[id] = stream;
}
}
public IOStream link (ChannelId id) throws Error {
IOStream? stream;
lock (channels)
IOStream? unlinked_stream = null;
lock (channels) {
stream = channels[id];
unlinked_channels.unset (id, out unlinked_stream);
}
if (stream == null)
throw new Error.INVALID_ARGUMENT ("Invalid channel ID");
return stream;
}
public void unlink (ChannelId id) {
IOStream? unlinked_stream = null;
lock (channels) {
unlinked_channels.unset (id, out unlinked_stream);
if (!channels.unset (id))
return;
}
+32
View File
@@ -133,6 +133,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,
@@ -2802,6 +2832,8 @@ namespace Frida {
public const string AGENT_SESSION_PROVIDER = "/re/frida/AgentSessionProvider";
public const string AGENT_SESSION = "/re/frida/AgentSession";
public const string AGENT_CONTROLLER = "/re/frida/AgentController";
public const string NATIVE_THREAD_REGISTRY = "/re/frida/NativeThreadRegistry";
public const string NATIVE_CLOAK = "/re/frida/NativeCloak";
public const string AGENT_MESSAGE_SINK = "/re/frida/AgentMessageSink";
public const string GADGET_SESSION = "/re/frida/GadgetSession";
public const string CHANNEL = "/re/frida/Channel";
+1 -1
Submodule releng updated: 564352a3ea...164a6a4117.
+4 -4
View File
@@ -8,7 +8,7 @@
"name": "frida-barebone-script-runtime",
"version": "1.0.0",
"devDependencies": {
"@types/frida-gum": "^19.12.0",
"@types/frida-gum": "^19.13.0",
"esbuild": "^0.25.5",
"typescript": "^5.8.3"
}
@@ -439,9 +439,9 @@
}
},
"node_modules/@types/frida-gum": {
"version": "19.12.0",
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.12.0.tgz",
"integrity": "sha512-fqXtwFA8AwE89tSdNHlaY+hNWI5j62bcO1xk5w0NSuHBIH6KRxYzIK6rp/JP7TXtRKU2ewBwx81FrHTfGF0q7w==",
"version": "19.13.0",
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.13.0.tgz",
"integrity": "sha512-tbmEZj9JUt3S6FqFE4XJPi/WhQuUU0/sM1IWS1KDKqvy6077hwNjG1v4MC3wpk+FO/WzfZ8G1gAKqAKJosfj1Q==",
"dev": true,
"license": "MIT"
},
+1 -1
View File
@@ -9,7 +9,7 @@
"watch": "tsc -w"
},
"devDependencies": {
"@types/frida-gum": "^19.12.0",
"@types/frida-gum": "^19.13.0",
"esbuild": "^0.25.5",
"typescript": "^5.8.3"
}
+4 -4
View File
@@ -36,7 +36,7 @@
"@frida/util": "^1.0.3",
"@frida/vm": "^2.0.0",
"@frida/web-streams": "^1.0.0",
"@types/frida-gum": "^19.12.0",
"@types/frida-gum": "^19.13.0",
"@types/node": "18.19.103",
"frida-fs": "7.1.1"
}
@@ -270,9 +270,9 @@
"license": "MIT"
},
"node_modules/@types/frida-gum": {
"version": "19.12.0",
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.12.0.tgz",
"integrity": "sha512-fqXtwFA8AwE89tSdNHlaY+hNWI5j62bcO1xk5w0NSuHBIH6KRxYzIK6rp/JP7TXtRKU2ewBwx81FrHTfGF0q7w==",
"version": "19.13.0",
"resolved": "https://registry.npmjs.org/@types/frida-gum/-/frida-gum-19.13.0.tgz",
"integrity": "sha512-tbmEZj9JUt3S6FqFE4XJPi/WhQuUU0/sM1IWS1KDKqvy6077hwNjG1v4MC3wpk+FO/WzfZ8G1gAKqAKJosfj1Q==",
"license": "MIT"
},
"node_modules/@types/node": {
+1 -1
View File
@@ -32,7 +32,7 @@
"@frida/util": "^1.0.3",
"@frida/vm": "^2.0.0",
"@frida/web-streams": "^1.0.0",
"@types/frida-gum": "^19.12.0",
"@types/frida-gum": "^19.13.0",
"@types/node": "18.19.103",
"frida-fs": "7.1.1"
}
+3 -1
View File
@@ -1074,7 +1074,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);
}
+1 -1
View File
@@ -2433,7 +2433,7 @@ namespace Frida {
Gum.Linux.enumerate_ranges ((Posix.pid_t) session.pid, READ | EXECUTE, d => {
unowned Gum.FileMapping? file = d.file;
if (file != null && file.path.has_prefix ("memfd:"))
if (file != null && file.path.has_prefix ("/memfd:"))
return true;
if (d.range.size >= code.length) {
code_start = d.range.base_address + d.range.size - round_size_to_page_size (code.length);
+10 -6
View File
@@ -1791,7 +1791,11 @@ namespace Frida {
}
MatchInfo info;
if (!/^([0-9a-f]+)-([0-9a-f]+) rw-p 00000000 00:00 0 *$/m.match (raw_maps, 0, out info)) {
try {
var anon_rw_pattern = new Regex ("^([0-9a-f]+)-([0-9a-f]+) rw-p 00000000 00:00 0 *$", MULTILINE | RAW);
if (!anon_rw_pattern.match (raw_maps, 0, out info))
assert_not_reached ();
} catch (RegexError e) {
assert_not_reached ();
}
assert (info != null);
@@ -2478,7 +2482,7 @@ namespace Frida {
if (stdout_pipe == null) {
try {
var process = new Subprocess.newv ({ "logcat", "-b", "crash", "-B" },
STDIN_INHERIT | STDOUT_PIPE | STDERR_SILENCE);
STDOUT_PIPE | STDERR_SILENCE);
logcat = process;
stdout_pipe = process.get_stdout_pipe ();
@@ -2894,7 +2898,7 @@ namespace Frida {
},
module_list => {
foreach (var path in module_list)
modules.add_value (new Variant.variant (new Variant.tuple ({ path })));
modules.add_value (new Variant.variant (new Variant.tuple ({ path.make_valid () })));
});
vardict_add (reply, "modules", modules.end ());
@@ -3111,7 +3115,7 @@ namespace Frida {
switch (attachment_type) {
case SyscallTracer.AttachmentType.STRING:
return (string) data;
return ((string) data).make_valid ();
case SyscallTracer.AttachmentType.BYTES: {
if (arg_type != null) {
Variant? v = try_decode_timespec (arg_type, data, len);
@@ -3208,7 +3212,7 @@ namespace Frida {
if (name_len != 0)
Memory.copy (str, path + 1, name_len);
return new Variant.tuple ({ "unix-abstract", (string) str });
return new Variant.tuple ({ "unix-abstract", ((string) str).make_valid () });
}
size_t n = 1;
@@ -3218,7 +3222,7 @@ namespace Frida {
var str = new uint8[n + 1];
Memory.copy (str, path, n);
return new Variant.tuple ({ "unix", (string) str });
return new Variant.tuple ({ "unix", ((string) str).make_valid () });
}
default:
return null;
+21 -71
View File
@@ -21,7 +21,7 @@ namespace Frida {
}
public Bytes make_request () {
Gee.List<string> missing = new TargetLibraries (pid).find_missing (module);
Gee.List<string> missing = new TargetLibraries (pid, module).find_missing ();
var header = HelperMappedAgentRequest ();
header.size = capacity;
@@ -40,10 +40,10 @@ namespace Frida {
public Bytes map (uint64 base_address, string entrypoint) throws Error {
try {
var libraries = new TargetLibraries (pid);
var libraries = new TargetLibraries (pid, module);
var memory = new MappedAgentMemory (pid, blob, module, base_address, capacity);
var mapper = Gum.Mapper.new_take_blob ("frida-agent.so", blob, libraries.make_resolver (module));
var mapper = Gum.Mapper.new_take_blob ("frida-agent.so", blob, libraries.make_resolver ());
mapper.set_target (memory);
Gum.Address extra_address;
@@ -61,23 +61,21 @@ namespace Frida {
}
private sealed class TargetLibraries {
private uint pid;
private Gum.ElfModule agent;
private ProcMapsSnapshot maps;
private Gee.List<ProcMapsSnapshot.Mapping> library_starts = new Gee.ArrayList<ProcMapsSnapshot.Mapping> ();
private Gee.Map<string, Gum.ElfModule> modules = new Gee.HashMap<string, Gum.ElfModule> ();
public TargetLibraries (uint pid) {
this.pid = pid;
public TargetLibraries (uint pid, Gum.ElfModule agent) {
this.agent = agent;
this.maps = ProcMapsSnapshot.from_pid (pid);
var paths = new Gee.HashSet<string> ();
foreach (ProcMapsSnapshot.Mapping m in maps) {
if (is_library_start (m) && paths.add (m.path))
library_starts.add (maps.find_module_by_path (m.path));
if (is_library_start (m))
library_starts.add (m);
}
}
public Gee.List<string> find_missing (Gum.ElfModule agent) {
public Gee.List<string> find_missing () {
var missing = new Gee.ArrayList<string> ();
agent.enumerate_dependencies (d => {
if (find (d.name) == null)
@@ -87,7 +85,7 @@ namespace Frida {
return missing;
}
public Gum.ElfModuleResolver make_resolver (Gum.ElfModule agent) throws Error {
public Gum.ElfModuleResolver make_resolver () throws Error {
var resolver = new Gum.ElfModuleResolver ();
var pending = new Gee.ArrayQueue<string> ();
@@ -105,7 +103,7 @@ namespace Frida {
if (!seen.add (mapping.path))
continue;
Gum.ElfModule library = load (mapping);
Gum.ElfModule library = maps.load_image (mapping);
resolver.add_module (library, mapping.start);
library.enumerate_dependencies (d => {
@@ -119,13 +117,13 @@ namespace Frida {
private ProcMapsSnapshot.Mapping? find (string name) {
foreach (ProcMapsSnapshot.Mapping m in library_starts) {
if (library_name_matches (name, Path.get_basename (m.path)))
if (library_name_matches (name, Path.get_basename (m.path)) && is_compatible_image (m))
return m;
}
foreach (ProcMapsSnapshot.Mapping m in library_starts) {
try {
if (load (m).soname == name)
if (maps.load_image (m).soname == name && is_compatible_image (m))
return m;
} catch (Error e) {
}
@@ -149,65 +147,17 @@ namespace Frida {
}
}
private Gum.ElfModule load (ProcMapsSnapshot.Mapping mapping) throws Error {
Gum.ElfModule? module = modules[mapping.path];
if (module == null) {
module = open_mapped_file (mapping);
if (module == null)
module = copy_from_target (mapping);
modules[mapping.path] = module;
}
return module;
}
private Gum.ElfModule? open_mapped_file (ProcMapsSnapshot.Mapping mapping) {
private bool is_compatible_image (ProcMapsSnapshot.Mapping start) {
Gum.ElfModule library;
try {
return new Gum.ElfModule.from_file (("/proc/%u/map_files/%" + uint64.FORMAT_MODIFIER + "x-%" +
uint64.FORMAT_MODIFIER + "x").printf (pid, mapping.start, mapping.end));
} catch (Gum.Error e) {
library = maps.load_image (start);
} catch (Error e) {
return false;
}
string path = "/proc/%u/root%s".printf (pid, mapping.path);
Posix.Stat st;
if (Posix.stat (path, out st) != 0 || st.st_ino != mapping.inode)
return null;
try {
return new Gum.ElfModule.from_file (path);
} catch (Gum.Error e) {
return null;
}
}
private Gum.ElfModule copy_from_target (ProcMapsSnapshot.Mapping first) throws Error {
var segments = new Gee.ArrayList<ProcMapsSnapshot.Mapping> ();
uint64 end = first.end;
foreach (ProcMapsSnapshot.Mapping m in maps) {
if (m.path == first.path && m.inode == first.inode && m.readable) {
segments.add (m);
end = uint64.max (end, m.end);
}
}
var image = new uint8[end - first.start];
var mem = new FileDescriptor (Posix.open ("/proc/%u/mem".printf (pid), Posix.O_RDONLY));
if (mem.handle == -1)
throw new Error.PERMISSION_DENIED ("Unable to read memory of process %u", pid);
foreach (ProcMapsSnapshot.Mapping m in segments)
mem.pread (image[m.start - first.start:m.end - first.start], m.start);
var blob = new Bytes.take ((owned) image);
var module = (Gum.ElfModule) Object.new (typeof (Gum.ElfModule),
"base-address", (Gum.Address) first.start,
"source-path", first.path,
"source-blob", blob,
"source-mode", Gum.ElfSourceMode.ONLINE);
try {
module.load ();
} catch (Gum.Error e) {
throw new Error.NOT_SUPPORTED ("Unable to parse %s: %s", first.path, e.message);
}
return module;
return library.machine == agent.machine &&
library.pointer_size == agent.pointer_size &&
maps.is_image_base (start);
}
}
+101 -64
View File
@@ -6,7 +6,9 @@ namespace Frida {
default = 1;
}
private uint32 pid;
private Gee.List<Mapping> maps = new Gee.ArrayList<Mapping> ();
private Gee.Map<string, Gum.ElfModule> images = new Gee.HashMap<string, Gum.ElfModule> ();
private const uint64 VM_READ = 1;
private const uint64 VM_WRITE = 2;
@@ -44,6 +46,7 @@ namespace Frida {
}
private void build_from_pid (uint32 pid) {
this.pid = pid;
maps.clear ();
var it = ProcMapsIter.for_pid (pid);
@@ -268,76 +271,105 @@ namespace Frida {
}
public Mapping? find_module_by_path_at_gen (string path, uint32 at_gen) {
Mapping? best_base = null;
int best_score = int.MIN;
Mapping? cur_base = null;
uint cur_total = 0;
uint cur_exec = 0;
#if ANDROID
unowned string libc_path = Gum.Process.get_libc_module ().path;
#endif
for (int i = 0; i < maps.size; i++) {
var m = maps[i];
if (!mapping_alive_at (m, at_gen))
continue;
unowned string mp = m.path;
if (mp == "[page size compat]")
continue;
if (mp != path) {
consider_candidate (ref best_base, ref best_score, cur_base, cur_total, cur_exec);
cur_base = null;
cur_total = 0;
cur_exec = 0;
continue;
}
#if ANDROID
if (mp == libc_path && m.shared)
continue;
#endif
if (m.file_offset == 0) {
consider_candidate (ref best_base, ref best_score, cur_base, cur_total, cur_exec);
cur_base = m;
cur_total = 0;
cur_exec = 0;
}
if (cur_base != null) {
cur_total++;
if (m.executable)
cur_exec++;
}
foreach (Mapping m in maps) {
if (m.path == path && m.file_offset == 0 && mapping_alive_at (m, at_gen) && is_image_base_at_gen (m, at_gen))
return m;
}
consider_candidate (ref best_base, ref best_score, cur_base, cur_total, cur_exec);
return best_base;
return null;
}
private static void consider_candidate (ref Mapping? best_base, ref int best_score, Mapping? candidate_base, uint total,
uint exec) {
if (candidate_base == null)
return;
public bool is_image_base (Mapping start) {
return is_image_base_at_gen (start, gen);
}
int score = score_candidate (total, exec);
if (score > best_score) {
best_score = score;
best_base = candidate_base;
private bool is_image_base_at_gen (Mapping start, uint32 at_gen) {
Gum.ElfModule image;
try {
image = load_image (start);
} catch (Error e) {
return false;
}
uint64 page_mask = ~((uint64) Gum.query_page_size () - 1);
bool mapped = true;
image.enumerate_segments (s => {
if (s.file_size == 0 || (s.protection & Gum.PageProtection.EXECUTE) == 0)
return true;
uint64 address = start.start + ((s.vm_address - image.preferred_address) & page_mask);
Mapping? m = find_mapping_at_gen (address, at_gen);
mapped = m != null &&
m.executable &&
!m.shared &&
m.inode == start.inode &&
m.path == start.path &&
m.file_offset + (address - m.start) == (s.file_offset & page_mask);
return mapped;
});
return mapped;
}
public Gum.ElfModule load_image (Mapping start) throws Error {
Gum.ElfModule? image = images[start.path];
if (image == null) {
image = open_mapped_file (start);
if (image == null)
image = copy_from_process (start);
images[start.path] = image;
}
return image;
}
private Gum.ElfModule? open_mapped_file (Mapping mapping) {
try {
return new Gum.ElfModule.from_file (("/proc/%u/map_files/%" + uint64.FORMAT_MODIFIER + "x-%" +
uint64.FORMAT_MODIFIER + "x").printf (pid, mapping.start, mapping.end));
} catch (Gum.Error e) {
}
string path = "/proc/%u/root%s".printf (pid, mapping.path);
Posix.Stat st;
if (Posix.stat (path, out st) != 0 || st.st_ino != mapping.inode)
return null;
try {
return new Gum.ElfModule.from_file (path);
} catch (Gum.Error e) {
return null;
}
}
private static int score_candidate (uint total, uint exec) {
int score = (int) total;
if (exec == 0)
score = -score;
return score;
private Gum.ElfModule copy_from_process (Mapping first) throws Error {
var segments = new Gee.ArrayList<Mapping> ();
uint64 end = first.end;
foreach (Mapping m in maps) {
if (m.path == first.path && m.inode == first.inode && m.readable && m.start >= first.start &&
mapping_alive_at (m, gen)) {
segments.add (m);
end = uint64.max (end, m.end);
}
}
var image = new uint8[end - first.start];
var mem = new FileDescriptor (Posix.open ("/proc/%u/mem".printf (pid), Posix.O_RDONLY));
if (mem.handle == -1)
throw new Error.PERMISSION_DENIED ("Unable to read memory of process %u", pid);
foreach (Mapping m in segments)
mem.pread (image[m.start - first.start:m.end - first.start], m.start);
var blob = new Bytes.take ((owned) image);
var module = (Gum.ElfModule) Object.new (typeof (Gum.ElfModule),
"base-address", (Gum.Address) first.start,
"source-path", first.path,
"source-blob", blob,
"source-mode", Gum.ElfSourceMode.ONLINE);
try {
module.load ();
} catch (Gum.Error e) {
throw new Error.NOT_SUPPORTED ("Unable to parse %s: %s", first.path, e.message);
}
return module;
}
}
@@ -417,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 ();
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/efni.git
revision = 329dd8e94505e95e066251e3d4dfe25d0e55eec3
revision = 72f90afcc1f7c663db4de934e9f7ab9d3f67d4b1
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/frida-gum.git
revision = 17.25.1
revision = 17.26.2
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida-forks/glib.git
revision = c366177b9292432a26f6903fa82a71d83e4927eb
revision = f11db6352345ad21deb5f5783bb988bfca2a3d85
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/idun.git
revision = d9098a1e335f2421cf8bddf9b15a8aa82d134c7d
revision = 59f682f4aba229ca73d175f66f3fa6d9e2f4b7a0
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida-forks/lwip.git
revision = f8424fd177ae3654fa040b8b6bc0c32de7fd6ad9
revision = 6ab74462bd4a5afb13394942ab031d7bee005b4c
depth = 1
[provide]
+48 -1
View File
@@ -22,6 +22,10 @@ namespace Frida.InjectorTest {
GLib.Test.add_func ("/Injector/resource-leaks", test_resource_leaks);
#if LINUX
GLib.Test.add_func ("/Injector/Linux/inject-with-non-utf8-mapping", test_injection_with_non_utf8_mapping);
#endif
#if DARWIN
GLib.Test.add_func ("/Injector/suspended-injection-current-arch", () => {
test_suspended_injection (Frida.Test.Arch.CURRENT);
@@ -149,6 +153,45 @@ namespace Frida.InjectorTest {
rat.close ();
}
#if LINUX
private void test_injection_with_non_utf8_mapping () {
var logfile = File.new_for_path (Frida.Test.path_to_temporary_file ("non-utf8-mapping.log"));
try {
logfile.delete ();
} catch (GLib.Error delete_error) {
}
var envp = new string[] {
"FRIDA_LABRAT_LOGFILE=" + logfile.get_path ()
};
var sleeper = File.new_for_path (Frida.Test.Labrats.path_to_executable ("sleeper"));
var sleeper_copy = File.new_for_path (Frida.Test.path_to_temporary_file ("sleeper-\xff"));
try {
sleeper.copy (sleeper_copy, OVERWRITE);
} catch (GLib.Error e) {
assert_not_reached ();
}
FileUtils.chmod (sleeper_copy.get_path (), 0700);
var rat = new Labrat.from_path (sleeper_copy.get_path (), envp);
rat.inject ("simple-agent", "");
rat.wait_for_uninject ();
assert_true (content_of (logfile) == ">m<");
try {
rat.process.kill ();
logfile.delete ();
sleeper_copy.delete ();
} catch (GLib.Error e) {
assert_not_reached ();
}
rat.close ();
}
#endif
#if DARWIN
private void test_suspended_injection (Frida.Test.Arch arch) {
var logfile = File.new_for_path (Frida.Test.path_to_temporary_file ("suspended-injection.log"));
@@ -193,8 +236,12 @@ namespace Frida.InjectorTest {
private PendingUninject? pending_uninject;
public Labrat (string name, string[] envp, Frida.Test.Arch arch = Frida.Test.Arch.CURRENT) {
this.from_path (Frida.Test.Labrats.path_to_executable (name), envp, arch);
}
public Labrat.from_path (string path, string[] envp, Frida.Test.Arch arch = Frida.Test.Arch.CURRENT) {
try {
process = Frida.Test.Process.start (Frida.Test.Labrats.path_to_executable (name), null, envp, arch);
process = Frida.Test.Process.start (path, null, envp, arch);
} catch (Error e) {
printerr ("\nFAIL: %s\n\n", e.message);
assert_not_reached ();