Compare commits

...
48 Commits
Author SHA1 Message Date
Ole André Vadla Ravnås 8c62357b94 subprojects: Prepare for release 2026-01-23 22:31:22 +01:00
Ole André Vadla Ravnås af43551888 subprojects: Bump outdated 2026-01-23 22:30:22 +01:00
Francesco Tamagni f332bddbfc fruity: Fix PortableCoreDevice flush infinite loop (#1207)
By reviving the call to `handle_events_completed` during FLUSHING state,
so that `pending_usb_ops` can get emptied eventually.
2026-01-23 22:29:05 +01:00
NSEcho e1bb883ba0 compiler: Rename conflictful Go symbols (#1205) 2026-01-23 12:40:11 +01:00
Ole André Vadla Ravnås a341cd1292 compiler: Load backend through memfd with fallback
So we retain the benefits of avoiding temporary files when possible.
2026-01-22 15:18:48 +01:00
Ole André Vadla Ravnås 70881a4e40 linux: Fix system session when memfd is restricted
Observed to happen in the Termux environment on Android.
2026-01-22 14:31:19 +01:00
Ole André Vadla Ravnås e0cae3fded subprojects: Prepare for release 2026-01-20 23:12:46 +01:00
Ole André Vadla Ravnås ff28ff33d9 subprojects: Bump outdated 2026-01-20 23:09:17 +01:00
Ole André Vadla Ravnås d072d48dfa subprojects: Bump outdated 2026-01-20 20:26:34 +01:00
Ole André Vadla Ravnås d8557b2c86 submodules: Bump releng 2026-01-20 20:26:34 +01:00
Ole André Vadla Ravnås de8a63b1f3 subprojects: Bump outdated 2026-01-20 18:54:21 +01:00
Ole André Vadla Ravnås 0f53d1b2ee submodules: Bump releng 2026-01-20 18:54:20 +01:00
Ole André Vadla Ravnås a638218794 compiler: Fix loading of backend on Android
The memfd path would typically fail because we were trying to open the
same underlying file with more restrictive permissions.

Avoid this and simplify things by using a temporary file even on systems
where memfd is supported.
2026-01-20 18:48:41 +01:00
Ole André Vadla Ravnås 5ea6e8507a file-descriptor: Add pwrite_all() 2026-01-20 18:45:16 +01:00
Ole André Vadla Ravnås 0ef2a0859b file-descriptor: Fix a stylistic inconsistency 2026-01-20 18:45:06 +01:00
Ole André Vadla Ravnås 01af5b3041 android: Place zymbiote payload on a better range
Picking a range not used by Zygote. The forked app/service might use it,
but by then we will have reverted it.
2026-01-20 13:02:54 +01:00
Ole André Vadla RavnåsandHåvard Sørbø 3df3834f17 android: Fix zymbiote payload on BTI systems
The zymbiote payload must be built with BTI enabled on arm64, otherwise
indirect branches into it will fault.

Co-authored-by: Håvard Sørbø <[email protected]>
2026-01-20 01:12:56 +01:00
Ole André Vadla RavnåsandHåvard Sørbø 31d11965e0 linux: Fix alignment in the helper linker script
Ensure .payload is 4 KiB–aligned to match ADRP page semantics on arm64.

Co-authored-by: Håvard Sørbø <[email protected]>
2026-01-20 01:10:06 +01:00
Ole André Vadla Ravnås 2e705e3f44 subprojects: Prepare for release 2026-01-17 08:43:29 +01:00
Ole André Vadla Ravnås 13c3e00be3 subprojects: Bump outdated 2026-01-17 00:24:19 +01:00
Ole André Vadla Ravnås 90164ccf7a subprojects: Bump outdated 2026-01-16 15:31:46 +01:00
Ole André Vadla Ravnås 3f9159e80a libc-shim: Revive FreeBSD support 2026-01-16 12:58:13 +00:00
Ole André Vadla Ravnås d4ca46a213 subprojects: Bump outdated 2026-01-16 13:44:36 +01:00
Ole André Vadla Ravnås 126ab29dce submodules: Bump releng 2026-01-16 13:44:36 +01:00
Ole André Vadla Ravnås 5104b0d08e libc-shim: Fix portability regressions
- Make stdio handling compatible with musl by treating FILE as
  opaque and fixing stdin/stdout/stderr definitions.
- Improve Linux compatibility.
- Add Darwin dirent shim.
- Fix MSVC support.
2026-01-16 13:34:11 +01:00
Ole André Vadla Ravnås 55c8bcb8ec fruity: Fix USB startup/shutdown race
Fix a race where stopping shortly after startup could leave the USB
worker blocked inside libusb_handle_events_completed().

Restructure the USB event loop to observe state transitions before
entering blocking libusb calls, ensuring FLUSHING and STOPPING are
handled deterministically.

Make initial USB enumeration during startup robust by gating completion
on a startup barrier and tracking in-flight arrivals, deduplicated by
device ID. This accounts for libusb HOTPLUG_ENUMERATE semantics where
arrival callbacks may be duplicated or delayed.

Together these changes ensure startup only completes once all initially
present devices are known, and shutdown cannot deadlock the USB thread.
2026-01-16 10:47:21 +01:00
Ole André Vadla Ravnås 4fbfae9b92 subprojects: Bump outdated 2026-01-15 18:07:56 +01:00
Ole André Vadla Ravnås 4d1e9f1113 submodules: Bump releng 2026-01-15 18:07:56 +01:00
Ole André Vadla RavnåsandHåvard Sørbø 3e778a99c0 android: Move to light-weight Zygote hooking
Instead of injecting an internal agent into the Zygote/USAP processes
and using child gating, rethink this completely:

1. Write a tiny payload into a code region through /proc/$pid/mem.
2. Patch the ArtMethod of android.os.Process.setArgV0Native() so it
   jumps to the payload.
3. The payload acts as a proxy, chaining to the original setArgV0Native.
4. It also makes a UNIX socket connection back to frida-core, giving it
   a chance to instrument it.

Co-authored-by: Håvard Sørbø <[email protected]>
2026-01-15 18:06:05 +01:00
Ole André Vadla Ravnås 2d2a944d8e file-descriptor: Add pread{,_all}() and pwrite() 2026-01-15 18:06:00 +01:00
Ole André Vadla Ravnås 0ba8b05fef linux: Fix the WaitResult constructor
Early returns from a struct constructor results in the Vala compiler
generating invalid C code. We'll just avoid this pattern for now.
2026-01-15 18:05:56 +01:00
Ole André Vadla Ravnås 783626e8fb android: Ignore noisy frida-helper.dex warnings
Coming from the dynamic linker on older Android OSes.
2026-01-15 12:53:39 +01:00
Ole André Vadla Ravnås 7399cc1092 android: Improve frida-helper.dex Android compat
- Support Android OS versions as far back as 4.4.
- Improve get_frontmost_application() to support a wider range of
  versions, not just Android >= 10.
2026-01-15 12:53:34 +01:00
Ole André Vadla Ravnås cefa0d0567 libc-shim: Improve stylistic consistency 2026-01-15 12:02:13 +01:00
Ole André Vadla Ravnås 0824ee85d9 libc-shim: Fix Android SELinux getline() UB
Fix a long-standing heap corruption/UB on Android caused by the SELinux
userspace library running a constructor that calls getline() and then
free().

Only free() went through our libc shim, while getline() allocated using
libc's heap, so the pointer was freed by the wrong allocator.

Add a minimal stdio shim (including getline()/getdelim()) and related
FILE*/__sF handling so allocations stay on the shim heap and are cleaned
up correctly.
2026-01-15 01:24:32 +01:00
Ole André Vadla Ravnås bea37618d1 android: Fix app spawn() on older Android versions 2026-01-13 22:45:36 +01:00
Ole André Vadla Ravnås cae7be99f8 meson: Fix Android dirty build from backend output 2026-01-11 14:21:56 +01:00
Ole André Vadla Ravnås 4406c8d146 android: Unlink frida-helper.dex on early error 2026-01-11 14:21:56 +01:00
Ole André Vadla Ravnås c89190e4f6 android: Explicitly set frida-helper.dex file mode 2026-01-11 14:21:56 +01:00
Ole André Vadla Ravnås dd68e84217 android: Improve frida-helper.dex spawn logic
- Include stderr and logcat output when unable to spawn.
- Process stdout and stderr. There shouldn't be any output on either,
  so if there is we definitely want to see it. This also makes it easier
  to add temporary logging to the helper.
2026-01-11 14:21:56 +01:00
Ole André Vadla Ravnås dc56b2eb16 subprojects: Bump outdated 2026-01-11 00:48:04 +01:00
Ole André Vadla Ravnås 1b908e69f3 android: Migrate to frida-helper.dex
Previously this helper was only used on non-rooted systems.
We now use it universally, which lets us stop injecting code
into system_server and drop the dependency on frida-java-bridge.

The only functionality lost is launch-timeout suppression.
2026-01-11 00:14:55 +01:00
Ole André Vadla Ravnås fae32353e1 linux: Fix ptrace signal waiter forwarding
Signal wait helpers could incorrectly forward ptrace-internal stops
(e.g. event SIGTRAPs) or syscall-stops back into the tracee while
waiting for a specific signal. This can perturb execution and break
spawn/attach flows.

Use WaitResult.should_deliver_to_tracee to gate signal reinjection.
Waiters now always satisfy the requested stop first, and only pass
through unmatched stops when they represent a real deliverable
signal.
2026-01-08 14:08:01 +01:00
Ole André Vadla Ravnås babe40274f linux: Fix ptrace syscall tracing desync
Syscall tracing could become desynchronized when the tracee stopped for
non-syscall reasons (e.g. PTRACE_EVENT_EXEC or real signals). These
SIGTRAP stops were incorrectly treated as syscall entry/exit points,
leading to bogus syscall IDs and eventual timeouts.

Fix this by decoding the raw waitpid() status into a structured
WaitResult, distinguishing syscall-stops, ptrace event stops, and real
signal stops. The syscall entry/exit phase is now advanced only on true
syscall-stops, and ptrace-internal traps are no longer reinjected into
the tracee.
2026-01-08 13:42:14 +01:00
Ole André Vadla Ravnås d19036305d subprojects: Bump outdated 2026-01-06 22:40:20 +01:00
Ole André Vadla Ravnås 1572fb6594 submodules: Bump releng 2026-01-06 22:40:19 +01:00
wyxhjr 747a9cd172 Add preliminary support for RISC-V (#1201) 2026-01-06 20:06:58 +01:00
Francesco Tamagni 8ff3a58612 fruity: Throw CLOSED on tcp write in closed state
In this way the stream stops being polled and an infinite loop is
avoided.
2026-01-06 20:03:50 +01:00
54 changed files with 5523 additions and 4164 deletions

No files matched your search

+1 -1
View File
@@ -1,5 +1,6 @@
/build/
/deps/
/src/android-helper/build/
/src/barebone/agent/target/
/src/barebone/agent/xpacks/
/src/barebone/helpers/memory-scanner/target/
@@ -11,7 +12,6 @@
/src/darwin/agent/osanalytics/osanalytics.js
/src/darwin/agent/reportcrash/node_modules/
/src/darwin/agent/reportcrash/reportcrash.js
/src/droidy/helper/build/
/src/fruity/helpers/build/
/src/linux/agent/system-server/node_modules/
/src/linux/agent/system-server/system-server.js
+5 -5
View File
@@ -24,7 +24,9 @@ _frida_agent_environment_init (void)
been_here = TRUE;
#endif
gum_init_embedded ();
#ifdef _MSC_VER
frida_libc_shim_init ();
#endif
gio_init ();
g_thread_set_garbage_handler (_frida_agent_on_pending_thread_garbage, NULL);
@@ -54,13 +56,11 @@ _frida_agent_environment_deinit (void)
glib_shutdown ();
gio_deinit ();
gum_deinit_embedded ();
frida_run_atexit_handlers ();
# ifdef HAVE_DARWIN
/* Do what frida_deinit_memory() does on the other platforms. */
gum_internal_heap_unref ();
# if defined (_MSC_VER) || defined (HAVE_DARWIN)
frida_libc_shim_deinit ();
# endif
#endif
}
+2 -2
View File
@@ -50,9 +50,9 @@ raw_agent = shared_library('frida-agent-raw', agent_sources,
dependencies: [gio_dep, gee_dep, gum_dep, gumjs_dep, base_dep, pipe_dep, tls_provider_dep, payload_dep],
)
operations = ['--move', 'constructor', 'frida_init_memory', 'first']
operations = ['--move', 'constructor', 'frida_libc_shim_init', 'first']
if host_os_family != 'darwin'
operations += ['--move', 'destructor', 'frida_deinit_memory', 'first']
operations += ['--move', 'destructor', 'frida_libc_shim_deinit', 'first']
endif
modulated_agent = custom_target('frida-agent-modulated',
input: raw_agent,
+10
View File
@@ -56,6 +56,16 @@
# else
# error Unexpected MIPS ABI
# endif
#elif defined (HAVE_RISCV)
# if GLIB_SIZEOF_VOID_P == 8
# define SYS_process_vm_readv 270
# define SYS_process_vm_writev 271
# define SYS_memfd_create 279
# define SYS_pidfd_open 434
# define SYS_pidfd_getfd 438
# else
# error RISC-V 32-bit not yet supported
# endif
#else
# error FIXME
#endif
+70
View File
@@ -54,6 +54,76 @@ namespace Frida {
public int get_fd () {
return handle;
}
public size_t pread (uint8[] buf, uint64 offset) throws Error {
while (true) {
ssize_t res = Posix.pread (handle, buf, buf.length, (Posix.off_t) offset);
if (res == -1) {
if (errno == Posix.EINTR)
continue;
throw new Error.TRANSPORT ("%s", strerror (errno));
}
return res;
}
}
public void pread_all (uint8[] buf, uint64 offset) throws Error {
size_t total = 0;
while (total != buf.length) {
void * dst = (uint8 *) buf + total;
size_t remaining = buf.length - total;
while (true) {
ssize_t res = Posix.pread (handle, dst, remaining, (Posix.off_t) (offset + total));
if (res == -1) {
if (errno == Posix.EINTR)
continue;
throw new Error.TRANSPORT ("%s", strerror (errno));
}
if (res == 0)
throw new Error.TRANSPORT ("Unexpected EOF: read %zu of %zu bytes", total, buf.length);
total += (size_t) res;
break;
}
}
}
public size_t pwrite (uint8[] buf, uint64 offset) throws Error {
while (true) {
ssize_t res = Posix.pwrite (handle, buf, buf.length, (Posix.off_t) offset);
if (res == -1) {
if (errno == Posix.EINTR)
continue;
throw new Error.TRANSPORT ("%s", strerror (errno));
}
return res;
}
}
public void pwrite_all (uint8[] buf, uint64 offset) throws Error {
size_t total = 0;
while (total != buf.length) {
uint8[] chunk = buf[total : buf.length];
while (true) {
ssize_t res = Posix.pwrite (handle, chunk, chunk.length, (Posix.off_t) (offset + total));
if (res == -1) {
if (errno == Posix.EINTR)
continue;
throw new Error.TRANSPORT ("%s", strerror (errno));
}
total += (size_t) res;
break;
}
}
}
}
public StdioPipes? make_stdio_pipes (Stdio stdio, bool in_supported, out FileDescriptor? in_fd, out string? in_name,
+5 -5
View File
@@ -84,7 +84,9 @@ frida_on_unload (void)
void
frida_gadget_environment_init (void)
{
gum_init_embedded ();
#ifdef _MSC_VER
frida_libc_shim_init ();
#endif
gio_init ();
g_thread_set_garbage_handler (_frida_gadget_on_pending_thread_garbage, NULL);
@@ -136,13 +138,11 @@ frida_gadget_environment_deinit (void)
glib_shutdown ();
gio_deinit ();
gum_deinit_embedded ();
frida_run_atexit_handlers ();
#ifdef HAVE_DARWIN
/* Do what frida_deinit_memory() does on the other platforms. */
gum_internal_heap_unref ();
#if defined (_MSC_VER) || defined (HAVE_DARWIN)
frida_libc_shim_deinit ();
#endif
}
+2 -2
View File
@@ -69,12 +69,12 @@ raw_gadget = shared_library('frida-gadget-raw', gadget_sources,
)
operations = [
'--move', 'constructor', 'frida_init_memory', 'first',
'--move', 'constructor', 'frida_libc_shim_init', 'first',
'--move', 'constructor', 'frida_on_load', 'last',
]
if host_os_family != 'darwin'
operations += [
'--move', 'destructor', 'frida_deinit_memory', 'first',
'--move', 'destructor', 'frida_libc_shim_deinit', 'first',
'--move', 'destructor', 'frida_on_unload', 'last',
]
endif
+1692 -68
View File
File diff suppressed because it is too large. Load diff
+3
View File
@@ -1,4 +1,7 @@
namespace Frida {
#if DARWIN
public extern void libc_shim_deinit ();
#endif
public extern void run_atexit_handlers ();
public extern uint get_process_id ();
+7
View File
@@ -108,6 +108,12 @@ elif host_machine.cpu_family() == 'mips64'
else
host_abi = 'mips64'
endif
elif host_machine.cpu_family() == 'riscv32'
host_arch = 'riscv'
host_abi = 'riscv32'
elif host_machine.cpu_family() == 'riscv64'
host_arch = 'riscv'
host_abi = 'riscv64'
else
host_arch = host_machine.cpu_family()
host_abi = host_arch
@@ -252,6 +258,7 @@ cpu_defines = [
['arm', 'HAVE_ARM'],
['arm64', 'HAVE_ARM64'],
['mips', 'HAVE_MIPS'],
['riscv', 'HAVE_RISCV'],
]
foreach d : cpu_defines
if d.get(0) == host_arch
+1 -1
Submodule releng updated: c144673b9d...60585cf5cf.
@@ -1,11 +1,15 @@
ANDROID_API_LEVEL ?= 21
ANDROID_SDK_ROOT ?= $(shell echo ~/Library/Android/sdk)
ANDROID_API_LEVEL ?= 19
ANDROID_SDK_ROOT ?= $(shell echo ~/Android/Sdk)
android_platform := android-$(ANDROID_API_LEVEL)
android_jar := $(ANDROID_SDK_ROOT)/platforms/$(android_platform)/android.jar
build/frida-helper.dex: build/frida-helper.jar
dx --dex --output=$@ $<
helper.dex: build/frida-helper.jar
d8 \
--classpath $(android_jar) \
--output build/ \
$<
mv build/classes.dex $@
build/frida-helper.jar: re/frida/Helper.java
javac \
+738
View File
@@ -0,0 +1,738 @@
namespace Frida {
public sealed class AndroidHelperClient : Object, AsyncInitable {
public signal void closed ();
public IOStream stream {
get;
construct;
}
private BufferedInputStream input;
private OutputStream output;
private Cancellable io_cancellable = new Cancellable ();
private State state = CLOSED;
private Gee.Queue<Request> pending_requests = new Gee.ArrayQueue<Request> ();
private ByteArray pending_output = new ByteArray ();
private bool writing = false;
private enum State {
CLOSED,
OPEN
}
private const uint32 MAX_RESPONSE_SIZE = 100 * 1024 * 1024;
public AndroidHelperClient (IOStream stream) {
Object (stream: stream);
}
construct {
input = (BufferedInputStream) Object.new (typeof (BufferedInputStream),
"base-stream", stream.get_input_stream (),
"close-base-stream", false,
"buffer-size", 128 * 1024);
output = stream.get_output_stream ();
state = OPEN;
process_incoming_responses.begin ();
}
public async void close (Cancellable? cancellable) throws IOError {
io_cancellable.cancel ();
var source = new IdleSource ();
source.set_callback (close.callback);
source.attach (MainContext.get_thread_default ());
yield;
try {
yield stream.close_async (Priority.DEFAULT, cancellable);
} catch (IOError e) {
}
ensure_closed ();
}
private void ensure_closed () {
if (state == CLOSED)
return;
state = CLOSED;
closed ();
}
public async HostApplicationInfo get_frontmost_application (FrontmostQueryOptions options,
Cancellable? cancellable) throws Error, IOError {
var stanza = new Json.Builder ();
stanza
.begin_array ()
.add_string_value ("get-frontmost-application")
.add_string_value (options.scope.to_nick ())
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
if (response.is_null ())
return HostApplicationInfo.empty ();
Json.Reader reader = new Json.Reader (response);
string? identifier = null;
string? name = null;
uint pid = 0;
HashTable<string, Variant> parameters = make_parameters_dict ();
if (reader.read_element (0)) {
identifier = reader.get_string_value ();
reader.end_element ();
}
if (reader.read_element (1)) {
name = reader.get_string_value ();
reader.end_element ();
}
if (reader.read_element (2)) {
pid = (uint) reader.get_int_value ();
reader.end_element ();
}
if (reader.read_element (3)) {
if (reader.is_object ())
add_parameters_from_json (parameters, reader);
reader.end_element ();
}
GLib.Error? error = reader.get_error ();
if (error != null)
throw new Error.PROTOCOL ("%s", error.message);
return HostApplicationInfo (identifier, name, pid, (owned) parameters);
}
public async HostApplicationInfo[] enumerate_applications (ApplicationQueryOptions options, Cancellable? cancellable)
throws Error, IOError {
var stanza = new Json.Builder ();
stanza
.begin_array ()
.add_string_value ("enumerate-applications");
stanza.begin_array ();
options.enumerate_selected_identifiers (identifier => stanza.add_string_value (identifier));
stanza.end_array ();
stanza
.add_string_value (options.scope.to_nick ())
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
Json.Reader reader = new Json.Reader (response);
int num_apps = reader.count_elements ();
if (num_apps == -1)
throw new Error.PROTOCOL ("Invalid response from helper service");
var result = new HostApplicationInfo[0];
for (int i = 0; i != num_apps; i++) {
reader.read_element (i);
string? identifier = null;
string? name = null;
uint pid = 0;
HashTable<string, Variant> parameters = make_parameters_dict ();
if (reader.read_element (0)) {
identifier = reader.get_string_value ();
reader.end_element ();
}
if (reader.read_element (1)) {
name = reader.get_string_value ();
reader.end_element ();
}
if (reader.read_element (2)) {
pid = (uint) reader.get_int_value ();
reader.end_element ();
}
if (reader.read_element (3)) {
if (reader.is_object ())
add_parameters_from_json (parameters, reader);
reader.end_element ();
}
GLib.Error? error = reader.get_error ();
if (error != null)
throw new Error.PROTOCOL ("%s", error.message);
result += HostApplicationInfo (identifier, name, pid, (owned) parameters);
reader.end_element ();
}
return result;
}
public async HostProcessInfo[] enumerate_processes (ProcessQueryOptions options, Cancellable? cancellable)
throws Error, IOError {
var stanza = new Json.Builder ();
stanza
.begin_array ()
.add_string_value ("enumerate-processes");
stanza.begin_array ();
options.enumerate_selected_pids (pid => stanza.add_int_value (pid));
stanza.end_array ();
stanza
.add_string_value (options.scope.to_nick ())
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
Json.Reader reader = new Json.Reader (response);
int num_processes = reader.count_elements ();
if (num_processes == -1)
throw new Error.PROTOCOL ("Invalid response from helper service");
var result = new HostProcessInfo[0];
for (int i = 0; i != num_processes; i++) {
reader.read_element (i);
uint pid = 0;
string? name = null;
HashTable<string, Variant> parameters = make_parameters_dict ();
if (reader.read_element (0)) {
pid = (uint) reader.get_int_value ();
reader.end_element ();
}
if (reader.read_element (1)) {
name = reader.get_string_value ();
reader.end_element ();
}
if (reader.read_element (2)) {
if (reader.is_object ())
add_parameters_from_json (parameters, reader);
reader.end_element ();
}
GLib.Error? error = reader.get_error ();
if (error != null)
throw new Error.PROTOCOL ("%s", error.message);
result += HostProcessInfo (pid, name, (owned) parameters);
reader.end_element ();
}
return result;
}
public async string get_process_name (string package, int uid, Cancellable? cancellable) throws Error, IOError {
var stanza = new Json.Builder ();
stanza
.begin_array ()
.add_string_value ("get-process-name")
.add_string_value (package)
.add_int_value (uid)
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
var r = new Json.Reader (response);
parse_envelope_or_throw (r);
r.read_element (1);
string? process_name = r.get_string_value ();
if (process_name == null)
throw new Error.PROTOCOL ("Malformed response");
return process_name;
}
public async void start_package (string package, PackageEntrypoint entrypoint, Cancellable? cancellable)
throws Error, IOError {
if (entrypoint is DefaultActivityEntrypoint) {
var stanza = new Json.Builder ();
stanza
.begin_array ()
.add_string_value ("start-activity")
.add_string_value (package)
.add_null_value ()
.add_int_value (entrypoint.uid)
.end_array ();
yield request_ok (stanza, cancellable);
return;
}
if (entrypoint is ActivityEntrypoint) {
var e = (ActivityEntrypoint) entrypoint;
var stanza = new Json.Builder ();
stanza
.begin_array ()
.add_string_value ("start-activity")
.add_string_value (package)
.add_string_value (e.activity)
.add_int_value (entrypoint.uid)
.end_array ();
yield request_ok (stanza, cancellable);
return;
}
if (entrypoint is BroadcastReceiverEntrypoint) {
var e = (BroadcastReceiverEntrypoint) entrypoint;
var stanza = new Json.Builder ();
stanza
.begin_array ()
.add_string_value ("send-broadcast")
.add_string_value (package)
.add_string_value (e.receiver)
.add_string_value (e.action)
.add_int_value (entrypoint.uid)
.end_array ();
yield request_ok (stanza, cancellable);
return;
}
assert_not_reached ();
}
public async void stop_package (string package, int uid, Cancellable? cancellable) throws Error, IOError {
var stanza = new Json.Builder ();
stanza
.begin_array ()
.add_string_value ("stop-package")
.add_string_value (package)
.add_int_value (uid)
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
var r = new Json.Reader (response);
parse_envelope_or_throw (r);
}
public async bool try_stop_package_by_pid (uint pid, Cancellable? cancellable) throws Error, IOError {
var stanza = new Json.Builder ();
stanza
.begin_array ()
.add_string_value ("try-stop-package-by-pid")
.add_int_value ((int) pid)
.end_array ();
Json.Node response = yield request (stanza.get_root (), cancellable);
var r = new Json.Reader (response);
parse_envelope_or_throw (r);
r.read_element (1);
var val = r.get_value ();
if (val == null || val.get_value_type () != typeof (bool))
throw new Error.PROTOCOL ("Malformed response");
return r.get_boolean_value ();
}
private void parse_envelope_or_throw (Json.Reader r) throws Error {
r.read_element (0);
string? kind = r.get_string_value ();
if (kind == null)
throw new Error.PROTOCOL ("Malformed response");
r.end_element ();
if (kind == "ok")
return;
if (kind == "error") {
r.read_element (1);
string? code = r.get_string_value ();
if (code == null)
throw new Error.PROTOCOL ("Malformed response");
r.end_element ();
r.read_element (2);
string? message = r.get_string_value ();
if (message == null)
throw new Error.PROTOCOL ("Malformed response");
r.end_element ();
if (code == "INVALID_ARGUMENT")
throw new Error.INVALID_ARGUMENT ("%s", message);
throw new Error.NOT_SUPPORTED ("%s", message);
}
throw new Error.PROTOCOL ("Malformed response");
}
private static void add_parameters_from_json (HashTable<string, Variant> parameters, Json.Reader reader) throws Error {
foreach (string name in reader.list_members ()) {
reader.read_member (name);
if (reader.is_value ()) {
Json.Node val = reader.get_value ();
parameters[name] = variant_from_json_value (val);
} else if (reader.is_array ()) {
int length = reader.count_elements ();
if (length == 0)
throw new Error.PROTOCOL ("Unexpected JSON array element shape");
if (name == "$icons") {
var icons = new VariantBuilder (new VariantType.array (VariantType.VARDICT));
for (int i = 0; i != length; i++) {
reader.read_element (i);
string? png_str = reader.get_string_value ();
if (png_str == null)
throw new Error.PROTOCOL ("Unexpected JSON icon type");
var png = new Bytes.take (Base64.decode (png_str));
icons.open (VariantType.VARDICT);
icons.add ("{sv}", "format", new Variant.string ("png"));
icons.add ("{sv}", "image", Variant.new_from_data (new VariantType ("ay"),
png.get_data (), true, png));
icons.close ();
reader.end_element ();
}
parameters["icons"] = icons.end ();
} else {
reader.read_element (0);
if (!reader.is_value ())
throw new Error.PROTOCOL ("Unexpected JSON array element type");
Variant first_element = variant_from_json_value (reader.get_value ());
reader.end_element ();
var builder = new VariantBuilder (new VariantType.array (first_element.get_type ()));
builder.add_value (first_element);
for (int i = 1; i != length; i++) {
reader.read_element (i);
if (!reader.is_value ())
throw new Error.PROTOCOL ("Unexpected JSON array element type");
builder.add_value (variant_from_json_value (reader.get_value ()));
reader.end_element ();
}
parameters[name] = builder.end ();
}
} else {
throw new Error.PROTOCOL ("Unexpected JSON type");
}
reader.end_member ();
}
}
private static Variant variant_from_json_value (Json.Node node) throws Error {
Type type = node.get_value_type ();
if (type == typeof (string))
return new Variant.string (node.get_string ());
if (type == typeof (int64))
return new Variant.int64 (node.get_int ());
if (type == typeof (bool))
return new Variant.boolean (node.get_boolean ());
throw new Error.PROTOCOL ("Unexpected JSON type: %s", type.name ());
}
public async Json.Node request (Json.Node stanza, Cancellable? cancellable) throws Error, IOError {
if (state == CLOSED)
throw new Error.INVALID_OPERATION ("Helper client is closed");
var r = new Request (request.callback);
pending_requests.offer (r);
var cancel_source = new CancellableSource (cancellable);
cancel_source.set_callback (() => {
r.complete_with_error (new IOError.CANCELLED ("Operation was cancelled"));
return false;
});
cancel_source.attach (MainContext.get_thread_default ());
write_request (stanza);
yield;
cancel_source.destroy ();
cancellable.set_error_if_cancelled ();
GLib.Error? e = r.error;
if (e != null) {
if (e is Error)
throw (Error) e;
if (e is IOError.CANCELLED)
throw (IOError) e;
throw new Error.TRANSPORT ("%s", e.message);
}
return r.response;
}
private async void request_ok (Json.Builder stanza, Cancellable? cancellable) throws Error, IOError {
Json.Node response = yield request (stanza.get_root (), cancellable);
var r = new Json.Reader (response);
parse_envelope_or_throw (r);
}
private void write_request (Json.Node request) {
string request_str = Json.to_string (request, false);
uint8[] request_data = request_str.data;
uint offset = pending_output.len;
pending_output.set_size ((uint) (offset + sizeof (uint32) + request_data.length));
uint8 * blob = (uint8 *) pending_output.data + offset;
uint32 * size = blob;
*size = request_data.length.to_big_endian ();
Memory.copy (blob + 4, request_data, request_data.length);
if (!writing) {
writing = true;
var source = new IdleSource ();
source.set_callback (() => {
process_pending_output.begin ();
return false;
});
source.attach (MainContext.get_thread_default ());
}
}
private async void process_pending_output () {
while (pending_output.len > 0) {
uint8[] batch = pending_output.steal ();
size_t bytes_written;
try {
yield output.write_all_async (batch, Priority.DEFAULT, io_cancellable, out bytes_written);
} catch (GLib.Error e) {
break;
}
}
writing = false;
}
private async void process_incoming_responses () {
while (true) {
try {
Json.Node response = yield read_response ();
Request? request = pending_requests.poll ();
if (request == null)
throw new Error.PROTOCOL ("Unexpected response");
request.complete_with_response (response);
} catch (GLib.Error e) {
foreach (Request r in pending_requests)
r.complete_with_error (e);
pending_requests.clear ();
ensure_closed ();
return;
}
}
}
private async Json.Node read_response () throws GLib.Error {
size_t header_size = sizeof (uint32);
if (input.get_available () < header_size)
yield fill_until_n_bytes_available (header_size, io_cancellable);
uint32 response_size = 0;
unowned uint8[] response_size_buf = ((uint8[]) &response_size)[0:4];
input.peek (response_size_buf);
response_size = uint32.from_big_endian (response_size);
if (response_size < 1 || response_size > MAX_RESPONSE_SIZE)
throw new Error.PROTOCOL ("Invalid response size");
size_t frame_size = header_size + response_size;
if (input.get_available () < frame_size)
yield fill_until_n_bytes_available (frame_size, io_cancellable);
var response_data = new uint8[response_size + 1];
response_data.length = (int) response_size;
input.peek (response_data, header_size);
input.skip (frame_size, io_cancellable);
return Json.from_string ((string) response_data);
}
private async void fill_until_n_bytes_available (size_t minimum, Cancellable? cancellable) throws GLib.Error {
size_t available = input.get_available ();
while (available < minimum) {
if (input.get_buffer_size () < minimum)
input.set_buffer_size (minimum);
ssize_t n = yield input.fill_async ((ssize_t) (input.get_buffer_size () - available),
Priority.DEFAULT, cancellable);
if (n == 0)
throw new IOError.CONNECTION_CLOSED ("Connection closed");
available += n;
}
}
private class Request {
private SourceFunc? handler;
public Json.Node? response;
public GLib.Error? error;
public Request (owned SourceFunc handler) {
this.handler = (owned) handler;
}
public void complete_with_response (Json.Node r) {
if (handler == null)
return;
response = r;
handler ();
handler = null;
}
public void complete_with_error (GLib.Error e) {
if (handler == null)
return;
error = e;
handler ();
handler = null;
}
}
}
public abstract class PackageEntrypoint : Object {
public int uid {
get;
set;
}
public static PackageEntrypoint parse (string package, HostSpawnOptions options) throws Error {
PackageEntrypoint? entrypoint = null;
HashTable<string, Variant> aux = options.aux;
Variant? activity_value = aux["activity"];
if (activity_value != null) {
if (!activity_value.is_of_type (VariantType.STRING))
throw new Error.INVALID_ARGUMENT ("The 'activity' option must be a string");
string activity = canonicalize_class_name (activity_value.get_string (), package);
if (aux.contains ("action")) {
throw new Error.INVALID_ARGUMENT (
"The 'action' option should only be specified when a 'receiver' is specified");
}
entrypoint = new ActivityEntrypoint (activity);
}
Variant? receiver_value = aux["receiver"];
if (receiver_value != null) {
if (!receiver_value.is_of_type (VariantType.STRING))
throw new Error.INVALID_ARGUMENT ("The 'receiver' option must be a string");
string receiver = canonicalize_class_name (receiver_value.get_string (), package);
if (entrypoint != null) {
throw new Error.INVALID_ARGUMENT (
"Only one of 'activity' or 'receiver' (with 'action') may be specified");
}
Variant? action_value = aux["action"];
if (action_value == null)
throw new Error.INVALID_ARGUMENT ("The 'action' option is required when 'receiver' is specified");
if (!action_value.is_of_type (VariantType.STRING))
throw new Error.INVALID_ARGUMENT ("The 'action' option must be a string");
string action = action_value.get_string ();
entrypoint = new BroadcastReceiverEntrypoint (receiver, action);
}
if (entrypoint == null)
entrypoint = new DefaultActivityEntrypoint ();
Variant? uid_value = aux["uid"];
if (uid_value != null) {
if (!uid_value.is_of_type (VariantType.INT64))
throw new Error.INVALID_ARGUMENT ("The 'uid' option must be an integer");
entrypoint.uid = (int) uid_value.get_int64 ();
}
return entrypoint;
}
}
public sealed class DefaultActivityEntrypoint : PackageEntrypoint {
public DefaultActivityEntrypoint () {
Object ();
}
}
public sealed class ActivityEntrypoint : PackageEntrypoint {
public string activity {
get;
construct;
}
public ActivityEntrypoint (string activity) {
Object (activity: activity);
}
}
public sealed class BroadcastReceiverEntrypoint : PackageEntrypoint {
public string receiver {
get;
construct;
}
public string action {
get;
construct;
}
public BroadcastReceiverEntrypoint (string receiver, string action) {
Object (receiver: receiver, action: action);
}
}
private static string canonicalize_class_name (string klass, string package) {
var result = new StringBuilder (klass);
if (klass.has_prefix (".")) {
result.prepend (package);
} else if (klass.index_of (".") == -1) {
result.prepend_c ('.');
result.prepend (package);
}
return result.str;
}
}
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
backend_sources += files('client.vala')
android_helper_dex = files('helper.dex')
File diff suppressed because it is too large. Load diff
File renamed without changes.
+25 -45
View File
@@ -85,8 +85,31 @@ def build_backend(
)
if mode == "c-archive":
symbols_to_scramble = detect_conflictful_symbols_in_archive(backend_a, config["nm"])
scramble_symbols_in_archive(backend_a, symbols_to_scramble, config["ranlib"])
symbol_dest = priv_dir / "symbol-replacer"
symbol_dest.mkdir(parents=True, exist_ok=True)
src_dir = base_dir / "symbol-replacer"
for name in ("main.go", "trie.go", "go.mod"):
shutil.copy(src_dir / name, symbol_dest)
env_copy = config["env"].copy()
env_copy.pop("GOOS", None)
env_copy.pop("GOARCH", None)
symbol_replacer_name = "frida-symbol-replacer"
subprocess.run(
[go, "build", "-buildvcs=false", "-o", symbol_replacer_name, "."],
cwd=symbol_dest,
env=env_copy,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
encoding="utf-8",
check=True,
)
run(priv_dir / "symbol-replacer" / symbol_replacer_name,
backend_a.name, *config["nm"], *config["ranlib"], config["env"]["CC"])
if (mingw := config.get("mingw")) is not None and (abi := config["abi"]) in {
"x86",
@@ -133,49 +156,6 @@ def build_backend(
shutil.copy(priv_dir / backend_h.name, output_dir / backend_h.name)
def detect_conflictful_symbols_in_archive(
archive: Path, nm_cmd_array: List[str]
) -> List[str]:
result = []
for line in subprocess.run(
[*nm_cmd_array, str(archive)], capture_output=True, encoding="utf-8", check=True
).stdout.splitlines():
tokens = line.lstrip().split(" ")
if len(tokens) >= 3 and tokens[1] in {"S", "T"}:
symbol = tokens[2]
if all(sub not in symbol for sub in ("/", ".", ":", "frida")):
result.append(symbol)
return result
def scramble_symbols_in_archive(
archive: Path, symbols: List[str], ranlib_cmd_array: List[str]
):
data = archive.read_bytes()
for old_sym in symbols:
new_sym = roll_first_alpha(old_sym)
data = data.replace(old_sym.encode("ascii"), new_sym.encode("ascii"))
archive.write_bytes(data)
subprocess.run([*ranlib_cmd_array, str(archive)], check=True)
def roll_first_alpha(s: str) -> str:
chars = list(s)
for i, c in enumerate(chars):
if c.isalpha():
if c == "z":
chars[i] = "a"
elif c == "Z":
chars[i] = "A"
else:
chars[i] = chr(ord(c) + 1)
break
return "".join(chars)
# Work around the Go toolchain's almost-MSVC-compatible object files
# until our patch is merged upstream:
# https://go-review.googlesource.com/c/go/+/678795
+22 -16
View File
@@ -243,30 +243,36 @@ namespace Frida {
#else
unowned uint8[] backend_so = Frida.Data.Compiler.get_frida_compiler_backend_so_blob ().data;
FileDescriptor fd;
Module? backend = null;
if (MemoryFileDescriptor.is_supported ()) {
fd = MemoryFileDescriptor.from_bytes ("frida-compiler-backend.so", new Bytes.static (backend_so));
} else {
string name_used;
var fd = MemoryFileDescriptor.from_bytes ("frida-compiler-backend.so", new Bytes.static (backend_so));
try {
fd = new FileDescriptor (FileUtils.open_tmp ("frida-compiler-backend-XXXXXX.so", out name_used));
backend = new Module ("/proc/self/fd/%d".printf (fd.handle), LOCAL);
} catch (ModuleError e) {
}
}
if (backend == null) {
try {
string name_used;
{
var fd = new FileDescriptor (FileUtils.open_tmp ("frida-compiler-backend-XXXXXX.so", out name_used));
fd.pwrite_all (backend_so, 0);
}
try {
backend = new Module (name_used, LOCAL);
} catch (ModuleError e) {
assert_not_reached ();
}
FileUtils.unlink (name_used);
var output = new UnixOutputStream (fd.handle, false);
output.write_all (backend_so, null);
Posix.lseek (fd.handle, 0, Posix.SEEK_SET);
} catch (GLib.Error e) {
assert_not_reached ();
}
}
Module backend;
try {
backend = new Module ("/proc/self/fd/%d".printf (fd.handle), LOCAL);
} catch (ModuleError e) {
assert_not_reached ();
}
build = resolve_symbol (backend, "_frida_compiler_backend_build");
watch = resolve_symbol (backend, "_frida_compiler_backend_watch");
WatchSession.dispose = resolve_symbol (backend, "_frida_compiler_backend_watch_session_dispose");
+3 -2
View File
@@ -4,6 +4,7 @@ compiler_sources = files(
)
if have_compiler_backend
output_suffix = compiler_backend_static_compilation ? '' : '-raw'
compiler_backend = custom_target('frida-compiler-backend',
input: [
'go.mod',
@@ -17,8 +18,8 @@ if have_compiler_backend
'package-lock.json',
],
output: [
'frida-compiler-backend' + (compiler_backend_static_compilation ? '.a' : ('-raw' + shlib_suffix)),
'frida-compiler-backend.h',
'frida-compiler-backend' + output_suffix + (compiler_backend_static_compilation ? '.a' : shlib_suffix),
'frida-compiler-backend' + output_suffix + '.h',
],
command: [
python,
+3
View File
@@ -0,0 +1,3 @@
module github.com/frida/frida-symbol-replacer
go 1.24.4
+135
View File
@@ -0,0 +1,135 @@
package main
import (
"bytes"
"io"
"os"
"os/exec"
"runtime"
"strings"
"unicode"
)
var (
excludeSymbols = [][]byte{
[]byte("fprintf"),
[]byte("pthread"),
[]byte("cas"),
}
)
func main() {
archive := os.Args[1]
nm := os.Args[2]
ranlib := os.Args[3]
cc := os.Args[4]
buf := new(bytes.Buffer)
c := exec.Command(nm, archive)
c.Stdout = buf
if err := c.Run(); err != nil {
panic(err)
}
t := newTrie()
t.insert([]byte("internal/"), flipAlpha([]byte("internal/")))
var content [][]byte
if runtime.GOOS == "windows" {
if strings.Contains(cc, "clangarm64") {
content = bytes.Split(buf.Bytes(), []byte{'\n'})
} else {
content = bytes.Split(buf.Bytes(), []byte("\r\n"))
}
} else {
content = bytes.Split(buf.Bytes(), []byte{'\n'})
}
for _, line := range content {
splitted := bytes.Split(line, []byte{' '})
if len(splitted) >= 3 {
symbolType := splitted[1]
symbol := splitted[2]
symbolLower := bytes.ToLower(symbol)
if bytes.ContainsAny(symbolType, "TSt") {
if symbolIsOkay(symbolLower) {
t.insert(symbol, flipAlpha(symbol))
}
} else if bytes.ContainsAny(symbolType, "Rrd") {
if bytes.HasPrefix(symbolLower, []byte("go:")) {
t.insert(symbol, flipAlpha(symbol))
}
}
}
}
f, err := os.OpenFile(archive, os.O_RDWR, 0755)
if err != nil {
panic(err)
}
data, _ := io.ReadAll(f)
modifiedData := make([]byte, len(data))
for i := 0; i < len(data); {
if l, replacement, ok := t.search(data, i); ok {
copy(modifiedData[i:], replacement)
i += l
} else {
modifiedData[i] = data[i]
i++
}
}
f.Truncate(0)
f.Seek(0, 0)
f.Write(modifiedData)
f.Close()
ranl := exec.Command(ranlib, archive)
if err := ranl.Run(); err != nil {
panic(err)
}
}
func flipAlpha(s []byte) []byte {
dt := make([]byte, len(s))
copy(dt, s)
for i, r := range s {
if unicode.IsLetter(rune(r)) || unicode.IsNumber(rune(r)) {
if r == 'z' {
dt[i] = 'a'
} else if r == 'Z' {
dt[i] = 'A'
} else {
dt[i] = r + 1
}
break
}
}
return dt
}
func symbolIsOkay(symbol []byte) bool {
switch {
case bytes.Contains(symbol, []byte("frida")):
return false
case bytes.Contains(symbol, []byte("rt0")):
return true
case bytes.Contains(symbol, []byte("cgo")):
return true
case bytes.ContainsAny(symbol, "./"):
return true
default:
for _, s := range excludeSymbols {
if bytes.Contains(symbol, s) {
return false
}
}
return true
}
}
+52
View File
@@ -0,0 +1,52 @@
package main
type trieNode struct {
children map[byte]*trieNode
isEnd bool
replacement []byte
}
type trie struct {
root *trieNode
}
func newTrie() *trie {
return &trie{
root: &trieNode{
children: make(map[byte]*trieNode),
},
}
}
func (t *trie) insert(pattern, value []byte) {
node := t.root
for _, b := range pattern {
if node.children[b] == nil {
node.children[b] = &trieNode{children: make(map[byte]*trieNode)}
}
node = node.children[b]
}
node.isEnd = true
node.replacement = value
}
func (t *trie) search(data []byte, start int) (int, []byte, bool) {
node := t.root
maxLen := 0
var match []byte
for i := start; i < len(data); i++ {
b := data[i]
node = node.children[b]
if node == nil {
break
}
if node.isEnd {
maxLen = i - start + 1
match = node.replacement
}
}
if maxLen > 0 {
return maxLen, match, true
}
return 0, nil, false
}
File diff suppressed because it is too large. Load diff
-703
View File
@@ -1,703 +0,0 @@
package re.frida;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityManager.RunningTaskInfo;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.net.LocalServerSocket;
import android.net.LocalSocket;
import android.os.Looper;
import android.os.Process;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
import android.util.Base64;
import android.util.Base64OutputStream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.Class;
import java.lang.Exception;
import java.lang.Object;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class Helper {
public static void main(String[] args) {
if (args.length != 1) {
System.err.println("Usage: frida-helper <instance-id>");
System.exit(1);
return;
}
String instanceId = args[0];
new File("/data/local/tmp/frida-helper-" + instanceId + ".dex").delete();
LocalServerSocket socket;
try {
socket = new LocalServerSocket("/frida-helper-" + instanceId);
} catch (IOException e) {
System.err.println(e);
System.exit(2);
return;
}
Looper.prepare();
Context context;
try {
Class<?> ActivityThread = Class.forName("android.app.ActivityThread");
Object activityThread = ActivityThread.getDeclaredMethod("systemMain").invoke(null);
context = (Context) ActivityThread.getDeclaredMethod("getSystemContext").invoke(activityThread);
} catch (InvocationTargetException e) {
System.err.println(e.getCause());
System.exit(1);
return;
} catch (Exception e) {
System.err.println(e);
System.exit(1);
return;
}
new Helper(socket, context).run();
}
private PackageManager mPackageManager;
private ActivityManager mActivityManager;
private Field mTopActivityField;
private String mLauncherPkgName;
private static Pattern sStatusUidPattern = Pattern.compile("^Uid:\\s+\\d+\\s+(\\d+)\\s+\\d+\\s+\\d+$", Pattern.MULTILINE);
private long mSystemBootTime;
private long mMillisecondsPerJiffy;
private SimpleDateFormat mIso8601;
private Method mGetpwuid;
private Field mPwnameField;
private LocalServerSocket mSocket;
private Thread mWorker;
private final int MAX_REQUEST_SIZE = 128 * 1024;
public Helper(LocalServerSocket socket, Context ctx) {
mPackageManager = ctx.getPackageManager();
mActivityManager = (ActivityManager) ctx.getSystemService(Context.ACTIVITY_SERVICE);
try {
mTopActivityField = Class.forName("android.app.TaskInfo").getDeclaredField("topActivity");
} catch (Exception e) {
}
mLauncherPkgName = detectLauncherPackageName();
mSystemBootTime = querySystemBootTime();
mMillisecondsPerJiffy = 1000 / Os.sysconf(OsConstants._SC_CLK_TCK);
mIso8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
mIso8601.setTimeZone(TimeZone.getTimeZone("UTC"));
try {
mGetpwuid = Class.forName("android.system.Os").getDeclaredMethod("getpwuid", int.class);
mPwnameField = Class.forName("android.system.StructPasswd").getDeclaredField("pw_name");
} catch (Exception e) {
throw new RuntimeException(e);
}
mSocket = socket;
mWorker = new Thread("Connection Listener") {
public void run() {
handleIncomingConnections();
}
};
}
private void run() {
mWorker.start();
Looper.loop();
}
private void handleIncomingConnections() {
System.out.println("READY.");
while (true) {
try {
LocalSocket client = mSocket.accept();
Thread handler = new Thread("Connection Handler") {
public void run() {
handleConnection(client);
}
};
handler.start();
} catch (IOException e) {
break;
}
}
}
protected void handleConnection(LocalSocket client) {
DataInputStream input;
DataOutputStream output;
try {
input = new DataInputStream(new BufferedInputStream(client.getInputStream()));
output = new DataOutputStream(new BufferedOutputStream(client.getOutputStream()));
} catch (IOException e) {
return;
}
while (true) {
try {
int requestSize = input.readInt();
if (requestSize < 1 || requestSize > MAX_REQUEST_SIZE) {
break;
}
byte[] rawRequest = new byte[requestSize];
input.readFully(rawRequest);
JSONArray request = new JSONArray(new String(rawRequest));
JSONArray response;
String type = request.getString(0);
if (type.equals("get-frontmost-application")) {
response = getFrontmostApplication(request);
} else if (type.equals("enumerate-applications")) {
response = enumerateApplications(request);
} else if (type.equals("enumerate-processes")) {
response = enumerateProcesses(request);
} else {
break;
}
byte[] rawResponse = (response != null)
? response.toString().getBytes()
: JSONObject.NULL.toString().getBytes();
output.writeInt(rawResponse.length);
output.write(rawResponse);
output.flush();
} catch (JSONException e) {
break;
} catch (EOFException e) {
break;
} catch (IOException e) {
break;
}
}
try {
client.close();
} catch (IOException e) {
}
}
private JSONArray getFrontmostApplication(JSONArray request) throws JSONException {
Scope scope = Scope.valueOf(request.getString(1).toUpperCase());
String pkgName = getFrontmostPackageName();
if (pkgName == null) {
return null;
}
ApplicationInfo appInfo;
try {
appInfo = mPackageManager.getApplicationInfo(pkgName, 0);
} catch (NameNotFoundException e) {
return null;
}
CharSequence appLabel = appInfo.loadLabel(mPackageManager);
int pid = 0;
List<RunningAppProcessInfo> pkgProcesses = getAppProcesses().get(pkgName);
if (pkgProcesses != null) {
pid = pkgProcesses.get(0).pid;
}
JSONObject parameters = null;
if (scope != Scope.MINIMAL) {
try {
parameters = fetchAppParameters(appInfo, scope);
} catch (NameNotFoundException e) {
return null;
}
if (pid != 0) {
try {
addProcessMetadata(parameters, pid);
} catch (IOException e) {
return null;
}
}
}
JSONArray app = new JSONArray();
app.put(pkgName);
app.put(appLabel);
app.put(pid);
app.put(parameters);
return app;
}
private JSONArray enumerateApplications(JSONArray request) throws JSONException {
JSONArray identifiersValue = request.getJSONArray(1);
Scope scope = Scope.valueOf(request.getString(2).toUpperCase());
List<ApplicationInfo> apps;
int numIdentifiers = identifiersValue.length();
if (numIdentifiers > 0) {
apps = new ArrayList<ApplicationInfo>();
for (int i = 0; i != numIdentifiers; i++) {
String pkgName = identifiersValue.getString(i);
try {
apps.add(mPackageManager.getApplicationInfo(pkgName, 0));
} catch (NameNotFoundException e) {
}
}
} else {
apps = getLauncherApplications();
}
JSONArray result = new JSONArray();
Map<String, List<RunningAppProcessInfo>> processes = getAppProcesses();
String frontmostPkgName = (scope != Scope.MINIMAL) ? getFrontmostPackageName() : null;
for (ApplicationInfo appInfo : apps) {
String pkgName = appInfo.packageName;
CharSequence appLabel = appInfo.loadLabel(mPackageManager);
int pid = 0;
List<RunningAppProcessInfo> pkgProcesses = processes.get(pkgName);
if (pkgProcesses != null) {
pid = pkgProcesses.get(0).pid;
}
JSONObject parameters = null;
if (scope != Scope.MINIMAL) {
try {
parameters = fetchAppParameters(appInfo, scope);
} catch (NameNotFoundException e) {
continue;
}
if (pid != 0) {
try {
addProcessMetadata(parameters, pid);
} catch (IOException e) {
pid = 0;
}
}
if (pid != 0 && pkgName.equals(frontmostPkgName)) {
parameters.put("frontmost", true);
}
}
JSONArray app = new JSONArray();
app.put(pkgName);
app.put(appLabel);
app.put(pid);
app.put(parameters);
result.put(app);
}
return result;
}
private JSONArray enumerateProcesses(JSONArray request) throws JSONException {
JSONArray pidsValue = request.getJSONArray(1);
Scope scope = Scope.valueOf(request.getString(2).toUpperCase());
int numPids = pidsValue.length();
List<Integer> pids = new ArrayList<Integer>(numPids);
if (numPids > 0) {
for (int i = 0; i != numPids; i++) {
pids.add(pidsValue.getInt(i));
}
} else {
int myPid = Process.myPid();
for (File candidate : new File("/proc").listFiles()) {
if (!candidate.isDirectory()) {
continue;
}
int pid;
try {
pid = Integer.parseInt(candidate.getName());
} catch (NumberFormatException e) {
continue;
}
if (pid == myPid) {
continue;
}
pids.add(pid);
}
}
JSONArray result = new JSONArray();
Map<String, List<RunningAppProcessInfo>> appProcessByPkgName = getAppProcesses();
Map<Integer, RunningAppProcessInfo> appProcessByPid = new HashMap<Integer, RunningAppProcessInfo>();
for (List<RunningAppProcessInfo> processes : appProcessByPkgName.values()) {
for (RunningAppProcessInfo process : processes) {
appProcessByPid.put(process.pid, process);
}
}
Map<String, ApplicationInfo> appInfoByPkgName = new HashMap<String, ApplicationInfo>();
for (ApplicationInfo appInfo : getLauncherApplications()) {
appInfoByPkgName.put(appInfo.packageName, appInfo);
}
Map<Integer, ApplicationInfo> appInfoByPid = new HashMap<Integer, ApplicationInfo>();
for (List<RunningAppProcessInfo> processes : appProcessByPkgName.values()) {
RunningAppProcessInfo mostImportantProcess = processes.get(0);
for (String pkgName : mostImportantProcess.pkgList) {
ApplicationInfo appInfo = appInfoByPkgName.get(pkgName);
if (appInfo != null) {
appInfoByPid.put(mostImportantProcess.pid, appInfo);
break;
}
}
}
int frontmostPid = -1;
if (scope != Scope.MINIMAL) {
String frontmostPkgName = getFrontmostPackageName();
if (frontmostPkgName != null) {
List<RunningAppProcessInfo> frontmostProcesses = appProcessByPkgName.get(frontmostPkgName);
if (frontmostProcesses != null) {
frontmostPid = frontmostProcesses.get(0).pid;
}
}
}
for (Integer pid : pids) {
File procDir = new File("/proc", pid.toString());
ApplicationInfo appInfo = appInfoByPid.get(pid);
CharSequence name;
if (appInfo != null) {
name = appInfo.loadLabel(mPackageManager);
} else {
String cmdline;
try {
cmdline = getFileContentsAsString(new File(procDir, "cmdline"));
} catch (IOException e) {
continue;
}
boolean isKernelProcess = cmdline.isEmpty();
if (isKernelProcess) {
continue;
}
name = deriveProcessNameFromCmdline(cmdline);
}
JSONObject parameters = null;
if (scope != Scope.MINIMAL) {
parameters = new JSONObject();
try {
File program = new File(Os.readlink(new File(procDir, "exe").getAbsolutePath()));
parameters.put("path", program.getAbsolutePath());
} catch (ErrnoException e) {
}
try {
addProcessMetadata(parameters, pid);
} catch (IOException e) {
continue;
}
RunningAppProcessInfo appProcess = appProcessByPid.get(pid);
if (appProcess != null) {
JSONArray ids = new JSONArray();
for (String pkgName : appProcess.pkgList) {
ids.put(pkgName);
}
parameters.put("applications", ids);
}
if (scope == Scope.FULL && appInfo != null) {
JSONArray icons = new JSONArray();
icons.put(fetchAppIcon(appInfo));
parameters.put("$icons", icons);
}
if (pid == frontmostPid) {
parameters.put("frontmost", true);
}
}
JSONArray process = new JSONArray();
process.put(pid);
process.put(name);
process.put(parameters);
result.put(process);
}
return result;
}
@SuppressWarnings("deprecation")
private String getFrontmostPackageName() {
if (mTopActivityField == null) {
return null;
}
List<RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
if (tasks.isEmpty()) {
return null;
}
RunningTaskInfo task = tasks.get(0);
ComponentName name;
try {
name = (ComponentName) mTopActivityField.get(task);
} catch (Exception e) {
throw new RuntimeException(e);
}
String pkgName = name.getPackageName();
if (pkgName.equals(mLauncherPkgName)) {
return null;
}
return pkgName;
}
private List<ApplicationInfo> getLauncherApplications() {
List<ApplicationInfo> apps = new ArrayList<ApplicationInfo>();
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
for (ResolveInfo resolveInfo : mPackageManager.queryIntentActivities(intent, 0)) {
apps.add(resolveInfo.activityInfo.applicationInfo);
}
return apps;
}
private JSONObject fetchAppParameters(ApplicationInfo appInfo, Scope scope) throws NameNotFoundException {
JSONObject parameters = new JSONObject();
PackageInfo packageInfo = mPackageManager.getPackageInfo(appInfo.packageName, 0);
try {
parameters.put("version", packageInfo.versionName);
parameters.put("build", Integer.toString(packageInfo.versionCode));
parameters.put("sources", fetchAppSources(appInfo));
parameters.put("data-dir", appInfo.dataDir);
parameters.put("target-sdk", appInfo.targetSdkVersion);
if ((appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
parameters.put("debuggable", true);
}
if (scope == Scope.FULL) {
JSONArray icons = new JSONArray();
icons.put(fetchAppIcon(appInfo));
parameters.put("$icons", icons);
}
} catch (JSONException e) {
throw new RuntimeException(e);
}
return parameters;
}
private static JSONArray fetchAppSources(ApplicationInfo appInfo) {
JSONArray sources = new JSONArray();
sources.put(appInfo.publicSourceDir);
String[] splitDirs = appInfo.splitPublicSourceDirs;
if (splitDirs != null) {
for (String splitDir : splitDirs) {
sources.put(splitDir);
}
}
return sources;
}
private String fetchAppIcon(ApplicationInfo appInfo) {
Drawable icon = mPackageManager.getApplicationIcon(appInfo);
int width = icon.getIntrinsicWidth();
int height = icon.getIntrinsicHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
icon.setBounds(0, 0, width, height);
icon.draw(canvas);
ByteArrayOutputStream output = new ByteArrayOutputStream();
bitmap.compress(CompressFormat.PNG, 100, new Base64OutputStream(output, Base64.NO_WRAP));
return output.toString();
}
private Map<String, List<RunningAppProcessInfo>> getAppProcesses() {
Map<String, List<RunningAppProcessInfo>> processes = new HashMap<String, List<RunningAppProcessInfo>>();
for (RunningAppProcessInfo processInfo : mActivityManager.getRunningAppProcesses()) {
for (String pkgName : processInfo.pkgList) {
List<RunningAppProcessInfo> entries = processes.get(pkgName);
if (entries == null) {
entries = new ArrayList<RunningAppProcessInfo>();
processes.put(pkgName, entries);
}
entries.add(processInfo);
if (entries.size() > 1) {
Collections.sort(entries, new Comparator<RunningAppProcessInfo>() {
@Override
public int compare(RunningAppProcessInfo a, RunningAppProcessInfo b) {
return a.importance - b.importance;
}
});
}
}
}
return processes;
}
private static String deriveProcessNameFromCmdline(String cmdline) {
String str = cmdline;
int spaceDashOffset = str.indexOf(" -");
if (spaceDashOffset != -1) {
str = str.substring(0, spaceDashOffset);
}
return new File(str).getName();
}
private void addProcessMetadata(JSONObject parameters, int pid) throws IOException {
File procNode = new File("/proc/" + Integer.toString(pid));
String status = getFileContentsAsString(new File(procNode, "status"));
Matcher m = sStatusUidPattern.matcher(status);
m.find();
int uid = Integer.parseInt(m.group(1));
String stat = getFileContentsAsString(new File(procNode, "stat"));
int commFieldEndOffset = stat.indexOf(')');
int stateFieldStartOffset = commFieldEndOffset + 2;
String[] statFields = stat.substring(stateFieldStartOffset).split(" ");
int manPageFieldIdOffset = 3;
int ppid = Integer.parseInt(statFields[4 - manPageFieldIdOffset]);
long startTimeDeltaInJiffies = Long.parseLong(statFields[22 - manPageFieldIdOffset]);
long startTimeDeltaInMilliseconds = startTimeDeltaInJiffies * mMillisecondsPerJiffy;
Date started = new Date(mSystemBootTime + startTimeDeltaInMilliseconds);
try {
parameters.put("user", resolveUserIdToName(uid));
parameters.put("ppid", ppid);
parameters.put("started", mIso8601.format(started));
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
private static long querySystemBootTime() {
String stat;
try {
stat = getFileContentsAsString(new File("/proc/stat"));
} catch (IOException e) {
throw new RuntimeException(e);
}
Matcher m = Pattern.compile("^btime (\\d+)$", Pattern.MULTILINE).matcher(stat);
m.find();
return Long.parseLong(m.group(1)) * 1000;
}
private String resolveUserIdToName(int uid) {
try {
return (String) mPwnameField.get(mGetpwuid.invoke(null, uid));
} catch (IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
private String detectLauncherPackageName() {
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
List<ResolveInfo> launchers = mPackageManager.queryIntentActivities(intent, 0);
if (launchers.isEmpty()) {
return null;
}
return launchers.get(0).activityInfo.packageName;
}
private static String getFileContentsAsString(File file) throws IOException {
ByteArrayOutputStream result = new ByteArrayOutputStream();
FileInputStream input = new FileInputStream(file);
try {
byte[] buffer = new byte[64 * 1024];
while (true) {
int n = input.read(buffer);
if (n == -1) {
break;
}
result.write(buffer, 0, n);
}
} finally {
input.close();
}
return result.toString();
}
}
enum Scope {
MINIMAL,
METADATA,
FULL;
}
+89 -24
View File
@@ -961,7 +961,10 @@ namespace Frida.Fruity {
private LibUSB.Context? usb_context;
private LibUSB.HotCallbackHandle iphone_callback;
private LibUSB.HotCallbackHandle ipad_callback;
private uint pending_usb_device_arrivals = 0;
private Gee.Set<uint32> startup_counted = new Gee.HashSet<uint32> ();
private uint startup_inflight = 0;
private uint startup_barrier_reached = 0;
private uint usb_started_resolved = 0;
private Gee.Map<uint32, LibUSB.Device> polled_usb_devices = new Gee.HashMap<uint32, LibUSB.Device> ();
private Source? polled_usb_timer;
private uint polled_usb_outdated = 0;
@@ -1099,14 +1102,17 @@ namespace Frida.Fruity {
return;
}
AtomicUint.inc (ref pending_usb_device_arrivals);
bool callbacks_registered = true;
bool callbacks_registered = false;
if (LibUSB.has_capability (HAS_HOTPLUG) != 0) {
usb_context.hotplug_register_callback (DEVICE_ARRIVED | DEVICE_LEFT, ENUMERATE, VENDOR_ID_APPLE,
PRODUCT_ID_IPHONE, LibUSB.HotPlugEvent.MATCH_ANY, on_usb_hotplug_event, out iphone_callback);
usb_context.hotplug_register_callback (DEVICE_ARRIVED | DEVICE_LEFT, ENUMERATE, VENDOR_ID_APPLE,
PRODUCT_ID_IPAD, LibUSB.HotPlugEvent.MATCH_ANY, on_usb_hotplug_event, out ipad_callback);
callbacks_registered = true;
usb_context.interrupt_event_handler ();
int completed = 0;
usb_context.handle_events_completed (out completed);
} else {
refresh_polled_usb_devices ();
@@ -1120,21 +1126,17 @@ namespace Frida.Fruity {
polled_usb_timer = source;
}
if (AtomicUint.dec_and_test (ref pending_usb_device_arrivals)) {
schedule_on_frida_thread (() => {
usb_started.resolve (true);
return Source.REMOVE;
});
}
schedule_startup_barrier_close ();
while (state != STOPPING) {
int completed = 0;
usb_context.handle_events_completed (out completed);
while (true) {
State s;
lock (state)
s = state;
if (AtomicUint.compare_and_exchange (ref polled_usb_outdated, 1, 0))
refresh_polled_usb_devices ();
if (s == STOPPING)
break;
if (state == FLUSHING) {
if (s == FLUSHING) {
if (callbacks_registered) {
usb_context.hotplug_deregister_callback (iphone_callback);
usb_context.hotplug_deregister_callback (ipad_callback);
@@ -1147,10 +1149,18 @@ namespace Frida.Fruity {
}
lock (state) {
if (pending_usb_ops.is_empty)
if (pending_usb_ops.is_empty) {
state = STOPPING;
break;
}
}
}
int completed = 0;
usb_context.handle_events_completed (out completed);
if (s != FLUSHING && AtomicUint.compare_and_exchange (ref polled_usb_outdated, 1, 0))
refresh_polled_usb_devices ();
}
schedule_on_frida_thread (() => {
@@ -1168,9 +1178,22 @@ namespace Frida.Fruity {
}
private void on_usb_device_arrived (LibUSB.Device device) {
AtomicUint.inc (ref pending_usb_device_arrivals);
var desc = LibUSB.DeviceDescriptor (device);
uint32 id = make_usb_device_id (device, desc);
bool counted_for_startup = false;
lock (state) {
if (state == STARTING && AtomicUint.get (ref startup_barrier_reached) == 0) {
if (!startup_counted.contains (id)) {
startup_counted.add (id);
AtomicUint.inc (ref startup_inflight);
counted_for_startup = true;
}
}
}
schedule_on_frida_thread (() => {
handle_usb_device_arrival.begin (device);
handle_usb_device_arrival.begin (device, counted_for_startup, id);
return Source.REMOVE;
});
}
@@ -1182,7 +1205,7 @@ namespace Frida.Fruity {
});
}
private async void handle_usb_device_arrival (LibUSB.Device raw_device) {
private async void handle_usb_device_arrival (LibUSB.Device raw_device, bool counted_for_startup, uint32 counted_id) {
try {
UsbDevice usb_device;
try {
@@ -1199,7 +1222,11 @@ namespace Frida.Fruity {
var transport = usb_transports.first_match (t => t.udid == udid);
bool may_need_time_to_settle = state != STARTING && (transport == null || transport.modeswitch_in_progress);
bool starting;
lock (state)
starting = state == STARTING;
bool may_need_time_to_settle = !starting && (transport == null || transport.modeswitch_in_progress);
if (may_need_time_to_settle) {
try {
yield sleep (250, io_cancellable);
@@ -1219,7 +1246,11 @@ namespace Frida.Fruity {
transport = new PortableCoreDeviceUsbTransport (this, usb_device, pairing_store);
usb_transports.add (transport);
if (state != STARTING) {
bool open_now;
lock (state)
open_now = state != STARTING;
if (open_now) {
try {
yield transport.open (io_cancellable);
} catch (GLib.Error e) {
@@ -1229,11 +1260,19 @@ namespace Frida.Fruity {
transport_attached (transport);
}
} finally {
if (AtomicUint.dec_and_test (ref pending_usb_device_arrivals) && state == STARTING)
usb_started.resolve (true);
if (counted_for_startup)
resolve_startup_device (counted_id);
}
}
private void resolve_startup_device (uint32 id) {
lock (state)
startup_counted.remove (id);
if (AtomicUint.dec_and_test (ref startup_inflight))
maybe_resolve_usb_started ();
}
private async void handle_usb_device_departure (LibUSB.Device raw_device) {
var transport = usb_transports.first_match (t => t.usb_device.raw_device == raw_device && !t.modeswitch_in_progress);
if (transport == null)
@@ -1274,6 +1313,32 @@ namespace Frida.Fruity {
polled_usb_devices = current_devices;
}
private void maybe_resolve_usb_started () {
if (AtomicUint.get (ref startup_barrier_reached) == 0)
return;
if (AtomicUint.get (ref startup_inflight) != 0)
return;
if (!AtomicUint.compare_and_exchange (ref usb_started_resolved, 0, 1))
return;
schedule_on_frida_thread (() => {
usb_started.resolve (true);
return Source.REMOVE;
});
}
private void schedule_startup_barrier_close () {
var source = new IdleSource ();
source.set_priority (Priority.LOW);
source.set_callback (() => {
AtomicUint.set (ref startup_barrier_reached, 1);
maybe_resolve_usb_started ();
return Source.REMOVE;
});
source.attach (main_context);
}
private UsbOperation allocate_usb_operation () throws Error {
var op = new PendingUsbOperation (this);
+4 -1
View File
@@ -760,8 +760,11 @@ namespace Frida.Fruity {
return OK;
}));
if (n == 0)
if (n == 0) {
if (state == CLOSED)
throw new IOError.CLOSED ("Connection is closed");
throw new IOError.WOULD_BLOCK ("Resource temporarily unavailable");
}
return n;
}
-22
View File
@@ -1,22 +0,0 @@
import esbuild from 'esbuild';
import alias from 'esbuild-plugin-alias';
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const BUFFER_SHIM = require.resolve('@frida/buffer');
esbuild.build({
entryPoints: ['index.js'],
outfile: 'system-server.js',
bundle: true,
platform: 'node',
target: ['es2022'],
legalComments: 'none',
minify: true,
plugins: [
alias({
'buffer': BUFFER_SHIM,
'node:buffer': BUFFER_SHIM,
}),
],
}).catch(() => process.exit(1));
-492
View File
@@ -1,492 +0,0 @@
import Java from 'frida-java-bridge';
let ApplicationInfo, Base64OutputStream, Bitmap, ByteArrayOutputStream, Canvas, ComponentName, ContextWrapper, Intent, ResolveInfo,
RunningAppProcessInfo, RunningTaskInfo, UserHandle;
let ACTION_MAIN, ARGB_8888, CATEGORY_HOME, CATEGORY_LAUNCHER, GET_ACTIVITIES, FLAG_ACTIVITY_NEW_TASK, FLAG_DEBUGGABLE, NO_WRAP, PNG;
let context, packageManager, activityManager, loadAppLabel, loadResolveInfoLabel;
let multiUserSupported;
let launcherPkgName;
const pendingLaunches = new Map();
function init() {
const ActivityManager = Java.use('android.app.ActivityManager');
const ActivityThread = Java.use('android.app.ActivityThread');
ApplicationInfo = Java.use('android.content.pm.ApplicationInfo');
const Base64 = Java.use('android.util.Base64');
Base64OutputStream = Java.use('android.util.Base64OutputStream');
Bitmap = Java.use('android.graphics.Bitmap');
const BitmapCompressFormat = Java.use('android.graphics.Bitmap$CompressFormat');
const BitmapConfig = Java.use('android.graphics.Bitmap$Config');
ByteArrayOutputStream = Java.use('java.io.ByteArrayOutputStream');
Canvas = Java.use('android.graphics.Canvas');
ComponentName = Java.use('android.content.ComponentName');
ContextWrapper = Java.use('android.content.ContextWrapper');
Intent = Java.use('android.content.Intent');
const Context = Java.use('android.content.Context');
const PackageManager = Java.use('android.content.pm.PackageManager');
ResolveInfo = Java.use('android.content.pm.ResolveInfo');
RunningAppProcessInfo = Java.use('android.app.ActivityManager$RunningAppProcessInfo');
RunningTaskInfo = Java.use('android.app.ActivityManager$RunningTaskInfo');
UserHandle = Java.use('android.os.UserHandle');
const ACTIVITY_SERVICE = Context.ACTIVITY_SERVICE.value;
ACTION_MAIN = Intent.ACTION_MAIN.value;
ARGB_8888 = BitmapConfig.ARGB_8888.value;
CATEGORY_HOME = Intent.CATEGORY_HOME.value;
CATEGORY_LAUNCHER = Intent.CATEGORY_LAUNCHER.value;
GET_ACTIVITIES = PackageManager.GET_ACTIVITIES.value;
FLAG_ACTIVITY_NEW_TASK = Intent.FLAG_ACTIVITY_NEW_TASK.value;
FLAG_DEBUGGABLE = ApplicationInfo.FLAG_DEBUGGABLE.value;
NO_WRAP = Base64.NO_WRAP.value;
PNG = BitmapCompressFormat.PNG.value;
context = ActivityThread.currentApplication();
packageManager = context.getPackageManager();
activityManager = Java.cast(context.getSystemService(ACTIVITY_SERVICE), ActivityManager);
loadAppLabel = ApplicationInfo.loadUnsafeLabel ?? ApplicationInfo.loadLabel;
multiUserSupported = 'getApplicationInfoAsUser' in PackageManager;
launcherPkgName = detectLauncherPackageName();
installLaunchTimeoutRemovalInstrumentation();
}
rpc.exports = {
getFrontmostApplication(scope) {
return performOnJavaVM(() => {
const pkgName = getFrontmostPackageName();
if (pkgName === null)
return null;
const appInfo = packageManager.getApplicationInfo(pkgName, 0);
const appLabel = loadAppLabel.call(appInfo, packageManager).toString();
const pid = computeAppPids(getAppProcesses()).get(pkgName) ?? 0;
const parameters = (scope !== 'minimal') ? fetchAppParameters(pkgName, appInfo, scope) : null;
return [pkgName, appLabel, pid, parameters];
});
},
enumerateApplications(identifiers, scope) {
return performOnJavaVM(() => {
const apps = [];
if (identifiers.length > 0) {
for (const pkgName of identifiers) {
try {
apps.push([pkgName, packageManager.getApplicationInfo(pkgName, 0)]);
} catch (e) {
}
}
} else {
for (const appInfo of getLauncherApplications()) {
apps.push([appInfo.packageName.value, appInfo]);
}
}
const result = [];
const pids = computeAppPids(getAppProcesses());
const includeParameters = scope !== 'minimal';
const frontmostPkgName = includeParameters ? getFrontmostPackageName() : null;
for (const [pkgName, appInfo] of apps) {
const appLabel = loadAppLabel.call(appInfo, packageManager).toString();
const pid = pids.get(pkgName) ?? 0;
let parameters = null;
if (includeParameters) {
parameters = fetchAppParameters(pkgName, appInfo, scope);
if (pkgName === frontmostPkgName)
parameters.frontmost = true;
}
result.push([pkgName, appLabel, pid, parameters]);
}
return result;
});
},
getProcessName(pkgName, uid) {
checkUidOptionSupported(uid);
return performOnJavaVM(() => {
try {
return getAppInfo(pkgName, uid).processName.value;
} catch (e) {
throw new Error(`Unable to find application with identifier '${pkgName}'${(uid !== 0) ? ' belonging to uid ' + uid : ''}`);
}
});
},
getProcessParameters(pids, scope) {
const result = {};
const appProcesses = getAppProcesses();
const appPidByPkgName = computeAppPids(appProcesses);
const appProcessByPid = new Map();
for (const process of appProcesses)
appProcessByPid.set(process.pid, process);
const appInfoByPkgName = new Map();
for (const appInfo of getLauncherApplications())
appInfoByPkgName.set(appInfo.packageName.value, appInfo);
const appInfoByPid = new Map();
for (const [pkgName, appPid] of appPidByPkgName.entries()) {
const appInfo = appInfoByPkgName.get(pkgName);
if (appInfo !== undefined)
appInfoByPid.set(appPid, appInfo);
}
let frontmostPid = -1;
const frontmostPkgName = getFrontmostPackageName();
if (frontmostPkgName !== null) {
frontmostPid = appPidByPkgName.get(frontmostPkgName) ?? -1;
}
const includeParameters = scope !== 'minimal';
const includeIcons = scope === 'full';
for (const pid of pids) {
const parameters = {};
const appInfo = appInfoByPid.get(pid);
if (appInfo !== undefined) {
parameters.$name = loadAppLabel.call(appInfo, packageManager).toString()
if (includeIcons)
parameters.$icon = fetchAppIcon(appInfo);
}
if (includeParameters) {
const appProcess = appProcessByPid.get(pid);
if (appProcess !== undefined) {
parameters.applications = appProcess.pkgList;
}
if (pid === frontmostPid) {
parameters.frontmost = true;
}
}
if (Object.keys(parameters).length !== 0) {
result[pid] = parameters;
}
}
return result;
},
startActivity(pkgName, activity, uid) {
checkUidOptionSupported(uid);
return performOnJavaVM(() => {
let user, ctx, pm;
if (uid !== 0) {
user = UserHandle.of(uid);
ctx = context.createPackageContextAsUser(pkgName, 0, user);
pm = ctx.getPackageManager();
} else {
user = null;
ctx = context;
pm = packageManager;
}
let appInstalled = false;
const apps = (uid !== 0)
? pm.getInstalledApplicationsAsUser(0, uid)
: pm.getInstalledApplications(0);
const numApps = apps.size();
for (let i = 0; i !== numApps; i++) {
const appInfo = Java.cast(apps.get(i), ApplicationInfo);
if (appInfo.packageName.value === pkgName) {
appInstalled = true;
break;
}
}
if (!appInstalled)
throw new Error("Unable to find application with identifier '" + pkgName + "'");
let intent = pm.getLaunchIntentForPackage(pkgName);
if (intent === null && 'getLeanbackLaunchIntentForPackage' in pm)
intent = pm.getLeanbackLaunchIntentForPackage(pkgName);
if (intent === null && activity === null)
throw new Error('Unable to find a front-door activity');
if (intent === null) {
intent = Intent.$new();
intent.setFlags(FLAG_ACTIVITY_NEW_TASK);
}
if (activity !== null) {
const pkgInfo = (uid !== 0)
? pm.getPackageInfoAsUser(pkgName, GET_ACTIVITIES, uid)
: pm.getPackageInfo(pkgName, GET_ACTIVITIES);
const activities = pkgInfo.activities.value.map(activityInfo => activityInfo.name.value);
if (!activities.includes(activity))
throw new Error("Unable to find activity with identifier '" + activity + "'");
intent.setClassName(pkgName, activity);
}
performLaunchOperation(pkgName, uid, () => {
if (user !== null)
ContextWrapper.$new(ctx).startActivityAsUser(intent, user);
else
ctx.startActivity(intent);
});
});
},
sendBroadcast(pkgName, receiver, action, uid) {
checkUidOptionSupported(uid);
return performOnJavaVM(() => {
const intent = Intent.$new();
intent.setComponent(ComponentName.$new(pkgName, receiver));
intent.setAction(action);
performLaunchOperation(pkgName, uid, () => {
if (uid !== 0)
ContextWrapper.$new(context).sendBroadcastAsUser(intent, UserHandle.of(uid));
else
context.sendBroadcast(intent);
});
});
},
stopPackage(pkgName, uid) {
checkUidOptionSupported(uid);
return performOnJavaVM(() => {
if (uid !== 0)
activityManager.forceStopPackageAsUser(pkgName, uid);
else
activityManager.forceStopPackage(pkgName);
});
},
tryStopPackageByPid(pid) {
return performOnJavaVM(() => {
const processes = activityManager.getRunningAppProcesses();
const numProcesses = processes.size();
for (let i = 0; i !== numProcesses; i++) {
const process = Java.cast(processes.get(i), RunningAppProcessInfo);
if (process.pid.value === pid) {
for (const pkgName of process.pkgList.value) {
activityManager.forceStopPackage(pkgName);
}
return true;
}
}
return false;
});
},
};
function getFrontmostPackageName() {
const tasks = activityManager.getRunningTasks(1);
if (tasks.isEmpty())
return null;
const task = Java.cast(tasks.get(0), RunningTaskInfo);
if (task.topActivity === undefined)
return null;
const name = task.topActivity.value.getPackageName();
if (name === launcherPkgName)
return null;
return name;
}
function getLauncherApplications() {
const intent = Intent.$new(ACTION_MAIN);
intent.addCategory(CATEGORY_LAUNCHER);
const activities = packageManager.queryIntentActivities(intent, 0);
const result = [];
const n = activities.size();
for (let i = 0; i !== n; i++) {
const resolveInfo = Java.cast(activities.get(i), ResolveInfo);
result.push(resolveInfo.activityInfo.value.applicationInfo.value);
}
return result;
}
function getAppInfo(pkgName, uid) {
return (uid !== 0)
? packageManager.getApplicationInfoAsUser(pkgName, 0, uid)
: packageManager.getApplicationInfo(pkgName, 0);
}
function fetchAppParameters(pkgName, appInfo, scope) {
const pkgInfo = packageManager.getPackageInfo(pkgName, 0);
const parameters = {
'version': pkgInfo.versionName.value,
'build': pkgInfo.versionCode.value.toString(),
'sources': [appInfo.publicSourceDir.value].concat(appInfo.splitPublicSourceDirs?.value ?? []),
'data-dir': appInfo.dataDir.value,
'target-sdk': appInfo.targetSdkVersion.value,
};
if ((appInfo.flags.value & FLAG_DEBUGGABLE) !== 0)
parameters.debuggable = true;
if (scope === 'full')
parameters.$icon = fetchAppIcon(appInfo);
return parameters;
}
function fetchAppIcon(appInfo) {
const icon = packageManager.getApplicationIcon(appInfo);
const width = icon.getIntrinsicWidth();
const height = icon.getIntrinsicHeight();
const bitmap = Bitmap.createBitmap(width, height, ARGB_8888);
const canvas = Canvas.$new(bitmap);
icon.setBounds(0, 0, width, height);
icon.draw(canvas);
const output = ByteArrayOutputStream.$new();
bitmap.compress(PNG, 100, Base64OutputStream.$new(output, NO_WRAP));
return output.toString('US-ASCII');
}
function getAppProcesses() {
const result = [];
const processes = activityManager.getRunningAppProcesses();
const n = processes.size();
for (let i = 0; i !== n; i++) {
const process = Java.cast(processes.get(i), RunningAppProcessInfo);
result.push({
pid: process.pid.value,
importance: process.importance.value,
pkgList: process.pkgList.value
});
}
return result;
}
function computeAppPids(processes) {
const pids = new Map();
for (const { pid, importance, pkgList } of processes) {
for (const pkgName of pkgList) {
let entries = pids.get(pkgName);
if (entries === undefined) {
entries = [];
pids.set(pkgName, entries);
}
entries.push([ pid, importance ]);
if (entries.length > 1) {
entries.sort((a, b) => a[1] - b[1]);
}
}
}
return new Map(Array.from(pids.entries()).map(([k, v]) => [k, v[0][0]]));
}
function detectLauncherPackageName() {
const intent = Intent.$new(ACTION_MAIN);
intent.addCategory(CATEGORY_HOME);
const launchers = packageManager.queryIntentActivities(intent, 0);
if (launchers.isEmpty())
return null;
const launcher = Java.cast(launchers.get(0), ResolveInfo);
return launcher.activityInfo.value.packageName.value;
}
function checkUidOptionSupported(uid) {
if (uid !== 0 && !multiUserSupported)
throw new Error('The “uid” option is not supported on the current Android OS version');
}
function installLaunchTimeoutRemovalInstrumentation() {
const Handler = Java.use('android.os.Handler');
const OSProcess = Java.use('android.os.Process');
const pendingStartRequests = new Set();
const start = OSProcess.start;
start.implementation = function (processClass, niceName) {
const result = start.apply(this, arguments);
if (tryFinishLaunch(niceName)) {
pendingStartRequests.add(Process.getCurrentThreadId());
}
return result;
};
const sendMessageDelayed = Handler.sendMessageDelayed;
sendMessageDelayed.implementation = function (msg, delayMillis) {
const tid = Process.getCurrentThreadId();
if (pendingStartRequests.has(tid)) {
pendingStartRequests.delete(tid);
msg.recycle();
return true;
}
return sendMessageDelayed.call(this, msg, delayMillis);
};
}
function performLaunchOperation(pkgName, uid, operation) {
const processName = getAppInfo(pkgName, uid).processName.value;
tryFinishLaunch(processName);
const timer = setTimeout(() => {
if (pendingLaunches.get(processName) === timer)
tryFinishLaunch(processName);
}, 10000);
pendingLaunches.set(processName, timer);
try {
return operation();
} catch (e) {
tryFinishLaunch(processName);
throw e;
}
}
function tryFinishLaunch(processName) {
const timer = pendingLaunches.get(processName);
if (timer === undefined)
return false;
pendingLaunches.delete(processName);
clearTimeout(timer);
return true;
}
function performOnJavaVM(task) {
return new Promise((resolve, reject) => {
Java.perform(() => {
try {
const result = task();
resolve(result);
} catch (e) {
reject(e);
}
});
});
}
Java.perform(init);
-19
View File
@@ -1,19 +0,0 @@
system_server_js = custom_target('system-server.js',
input: [
'package.json',
'package-lock.json',
'build.js',
'index.js',
],
output: [
'system-server.js',
],
command: [
python,
compile_js_agent,
'@OUTPUT@',
'@PRIVATE_DIR@',
npm,
'@INPUT@',
],
)
-563
View File
@@ -1,563 +0,0 @@
{
"name": "frida-system-server-agent",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "frida-system-server-agent",
"version": "1.0.0",
"dependencies": {
"@frida/buffer": "^7.0.4",
"frida-java-bridge": "^7.0.10"
},
"devDependencies": {
"esbuild": "^0.25.5",
"esbuild-plugin-alias": "^0.2.1"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz",
"integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"aix"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz",
"integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz",
"integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/android-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz",
"integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz",
"integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/darwin-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz",
"integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz",
"integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/freebsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz",
"integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz",
"integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz",
"integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ia32": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz",
"integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-loong64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz",
"integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==",
"cpu": [
"loong64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-mips64el": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz",
"integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==",
"cpu": [
"mips64el"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-ppc64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz",
"integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==",
"cpu": [
"ppc64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-riscv64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz",
"integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==",
"cpu": [
"riscv64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-s390x": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz",
"integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==",
"cpu": [
"s390x"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/linux-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz",
"integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz",
"integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/netbsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz",
"integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"netbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz",
"integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/openbsd-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz",
"integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"openbsd"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/sunos-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz",
"integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"sunos"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-arm64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz",
"integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==",
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-ia32": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz",
"integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==",
"cpu": [
"ia32"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@esbuild/win32-x64": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz",
"integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==",
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=18"
}
},
"node_modules/@frida/buffer": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/@frida/buffer/-/buffer-7.0.4.tgz",
"integrity": "sha512-RxQ1lZRRiCJj7nhcCiD8xeJx0NsLpGGnjqsmTg7jShGmbnVFMN5W7+J+3gqdPSQhc/IxNBIWc6zRXVp4+qnYHg==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.5.1",
"ieee754": "^1.2.1"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/esbuild": {
"version": "0.25.5",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz",
"integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"@esbuild/aix-ppc64": "0.25.5",
"@esbuild/android-arm": "0.25.5",
"@esbuild/android-arm64": "0.25.5",
"@esbuild/android-x64": "0.25.5",
"@esbuild/darwin-arm64": "0.25.5",
"@esbuild/darwin-x64": "0.25.5",
"@esbuild/freebsd-arm64": "0.25.5",
"@esbuild/freebsd-x64": "0.25.5",
"@esbuild/linux-arm": "0.25.5",
"@esbuild/linux-arm64": "0.25.5",
"@esbuild/linux-ia32": "0.25.5",
"@esbuild/linux-loong64": "0.25.5",
"@esbuild/linux-mips64el": "0.25.5",
"@esbuild/linux-ppc64": "0.25.5",
"@esbuild/linux-riscv64": "0.25.5",
"@esbuild/linux-s390x": "0.25.5",
"@esbuild/linux-x64": "0.25.5",
"@esbuild/netbsd-arm64": "0.25.5",
"@esbuild/netbsd-x64": "0.25.5",
"@esbuild/openbsd-arm64": "0.25.5",
"@esbuild/openbsd-x64": "0.25.5",
"@esbuild/sunos-x64": "0.25.5",
"@esbuild/win32-arm64": "0.25.5",
"@esbuild/win32-ia32": "0.25.5",
"@esbuild/win32-x64": "0.25.5"
}
},
"node_modules/esbuild-plugin-alias": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/esbuild-plugin-alias/-/esbuild-plugin-alias-0.2.1.tgz",
"integrity": "sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==",
"dev": true,
"license": "MIT"
},
"node_modules/frida-java-bridge": {
"version": "7.0.10",
"resolved": "https://registry.npmjs.org/frida-java-bridge/-/frida-java-bridge-7.0.10.tgz",
"integrity": "sha512-AtjgAHE7a2wV6M/8tZvjVqda0T8YGmSoXqkbD0r1Eq6XTFDKYbClKbZqMYHpI9OjxtkIx+xPqkHWWE3+0fDH7w==",
"license": "LGPL-2.0 WITH WxWindows-exception-3.1"
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
}
}
}
@@ -1,17 +0,0 @@
{
"name": "frida-system-server-agent",
"version": "1.0.0",
"description": "Agent used by Frida's Linux backend on Android",
"type": "module",
"scripts": {
"build": "node build.js"
},
"devDependencies": {
"esbuild": "^0.25.5",
"esbuild-plugin-alias": "^0.2.1"
},
"dependencies": {
"@frida/buffer": "^7.0.4",
"frida-java-bridge": "^7.0.10"
}
}
+219 -104
View File
@@ -741,25 +741,38 @@ namespace Frida {
public async void wait_for_syscall (LinuxSyscall mask, Cancellable? cancellable) throws Error, IOError {
bool on_syscall_entry = true;
int pending_signal = 0;
do {
while (state != SATISFIED) {
ptrace (SYSCALL, tid, null, (void *) pending_signal);
pending_signal = 0;
Posix.Signal sig = yield wait_for_next_signal (cancellable);
if (sig != (TRAP | 0x80)) {
on_syscall_entry = !on_syscall_entry;
pending_signal = sig;
var wr = yield ChildProcess.wait_for_next_stop (tid, cancellable);
wr.check_stopped ();
if (wr.is_ptrace_event_stop) {
if (wr.ptrace_event == PtraceEvent.EXEC)
on_syscall_entry = true;
continue;
}
if (on_syscall_entry) {
get_regs (&saved_regs);
if (_syscall_satisfies (get_syscall_id (saved_regs), mask))
state = SATISFIED;
if (wr.is_syscall_stop) {
if (on_syscall_entry) {
get_regs (&saved_regs);
var id = get_syscall_id (saved_regs);
if (_syscall_satisfies (id, mask))
state = SATISFIED;
}
on_syscall_entry = !on_syscall_entry;
continue;
}
on_syscall_entry = !on_syscall_entry;
} while (state != SATISFIED);
if (wr.should_deliver_to_tracee)
pending_signal = wr.stop_signal;
else
pending_signal = 0;
}
}
public async void interrupt (Cancellable? cancellable) throws Error, IOError {
@@ -2017,6 +2030,10 @@ namespace Frida {
return yield ChildProcess.wait_for_next_signal (tid, cancellable);
}
public async WaitResult wait_for_next_stop (Cancellable? cancellable) throws Error, IOError {
return yield ChildProcess.wait_for_next_stop (tid, cancellable);
}
public void get_regs (GPRegs * regs) throws Error {
#if !MIPS
if (regset_supported) {
@@ -3018,30 +3035,53 @@ namespace Frida {
namespace ChildProcess {
private async void wait_for_early_signal (uint pid, Posix.Signal sig, Cancellable? cancellable) throws Error, IOError {
while (true) {
Posix.Signal next_signal = yield wait_for_next_signal (pid, cancellable);
if (next_signal == sig)
return;
ptrace (CONT, pid);
}
yield wait_for_signals_common (pid, { sig }, suppress_unmatched, cancellable);
}
private async void wait_for_signal (uint pid, Posix.Signal sig, Cancellable? cancellable) throws Error, IOError {
yield wait_for_signals (pid, { sig }, cancellable);
}
private async Posix.Signal wait_for_signals (uint pid, Posix.Signal[] sigs, Cancellable? cancellable) throws Error, IOError {
while (true) {
Posix.Signal next_signal = yield wait_for_next_signal (pid, cancellable);
if (next_signal in sigs)
return next_signal;
private async Posix.Signal wait_for_signals (uint pid, Posix.Signal[] sigs, Cancellable? cancellable)
throws Error, IOError {
return yield wait_for_signals_common (pid, sigs, pass_through_unmatched, cancellable);
}
ptrace (CONT, pid, null, (void *) next_signal);
private async Posix.Signal wait_for_signals_common (uint pid, Posix.Signal[] sigs, UnmatchedStopHandler on_unmatched,
Cancellable? cancellable) throws Error, IOError {
while (true) {
var wr = yield wait_for_next_stop (pid, cancellable);
wr.check_stopped ();
if (wr.stop_signal in sigs)
return wr.stop_signal;
if (!wr.should_deliver_to_tracee) {
ptrace (CONT, pid);
continue;
}
on_unmatched (pid, wr);
}
}
private delegate void UnmatchedStopHandler (uint pid, WaitResult wr) throws Error;
private static void pass_through_unmatched (uint pid, WaitResult wr) throws Error {
ptrace (CONT, pid, null, (void *) wr.stop_signal);
}
private static void suppress_unmatched (uint pid, WaitResult wr) throws Error {
ptrace (CONT, pid);
}
private async Posix.Signal wait_for_next_signal (uint pid, Cancellable? cancellable) throws Error, IOError {
var wr = yield wait_for_next_stop (pid, cancellable);
wr.check_stopped ();
return wr.stop_signal;
}
private async WaitResult wait_for_next_stop (uint pid, Cancellable? cancellable) throws Error, IOError {
var main_context = MainContext.get_thread_default ();
bool timed_out = false;
@@ -3054,21 +3094,23 @@ namespace Frida {
int status = 0;
uint[] delays = { 0, 1, 2, 5, 10, 20, 50, 250 };
try {
for (uint i = 0; !timed_out && !cancellable.set_error_if_cancelled (); i++) {
int res = Posix.waitpid ((Posix.pid_t) pid, out status, Posix.WNOHANG);
if (res == -1)
throw new Error.NOT_SUPPORTED ("Unable to wait for next signal: %s", strerror (errno));
throw new Error.NOT_SUPPORTED ("Unable to wait for next stop: %s", strerror (errno));
if (res != 0)
break;
uint delay_ms = (i < delays.length) ? delays[i] : delays[delays.length - 1];
var delay_source = new TimeoutSource (delay_ms);
delay_source.set_callback (wait_for_next_signal.callback);
delay_source.set_callback (wait_for_next_stop.callback);
delay_source.attach (main_context);
var cancel_source = new CancellableSource (cancellable);
cancel_source.set_callback (wait_for_next_signal.callback);
cancel_source.set_callback (wait_for_next_stop.callback);
cancel_source.attach (main_context);
yield;
@@ -3081,21 +3123,79 @@ namespace Frida {
}
if (timed_out)
throw new Error.TIMED_OUT ("Unexpectedly timed out while waiting for signal from process with PID %u", pid);
throw new Error.TIMED_OUT ("Unexpectedly timed out while waiting for stop from process with PID %u", pid);
return WaitResult (pid, status);
}
}
public enum WaitKind {
EXITED,
SIGNALED,
STOPPED,
OTHER
}
public struct WaitResult {
public uint pid;
public int status;
public WaitKind kind;
public uint exit_status;
public Posix.Signal term_signal;
public Posix.Signal stop_signal;
public PtraceEvent ptrace_event;
public bool is_ptrace_event_stop;
public bool is_syscall_stop;
public bool should_deliver_to_tracee;
public WaitResult (uint pid, int status) {
this.pid = pid;
this.status = status;
kind = WaitKind.OTHER;
exit_status = 0;
term_signal = 0;
stop_signal = 0;
ptrace_event = PtraceEvent.NONE;
is_ptrace_event_stop = false;
is_syscall_stop = false;
should_deliver_to_tracee = false;
if (PosixStatus.is_exit (status)) {
throw new Error.NOT_SUPPORTED ("Target exited with status %u",
PosixStatus.parse_exit_status (status));
}
kind = WaitKind.EXITED;
exit_status = PosixStatus.parse_exit_status (status);
} else if (PosixStatus.is_signaled (status)) {
kind = WaitKind.SIGNALED;
term_signal = PosixStatus.parse_termination_signal (status);
} else if (PosixStatus.is_stopped (status)) {
kind = WaitKind.STOPPED;
if (PosixStatus.is_signaled (status)) {
throw new Error.NOT_SUPPORTED ("Target terminated with signal %u",
PosixStatus.parse_termination_signal (status));
}
stop_signal = PosixStatus.parse_stop_signal (status);
ptrace_event = PosixStatus.ptrace_event (status);
if (!PosixStatus.is_stopped (status))
throw new Error.NOT_SUPPORTED ("Unexpected status: 0x%08x", status);
return PosixStatus.parse_stop_signal (status);
is_ptrace_event_stop = (stop_signal == Posix.Signal.TRAP) && (ptrace_event != NONE);
is_syscall_stop = (stop_signal == (Posix.Signal.TRAP | 0x80)) && (ptrace_event == NONE);
should_deliver_to_tracee = !is_syscall_stop && !is_ptrace_event_stop && stop_signal != Posix.Signal.TRAP;
}
}
public void check_stopped () throws Error {
switch (kind) {
case WaitKind.EXITED:
throw new Error.NOT_SUPPORTED ("Target exited with status %u", exit_status);
case WaitKind.SIGNALED:
throw new Error.NOT_SUPPORTED ("Target terminated with signal %u", term_signal);
case WaitKind.STOPPED:
return;
default:
throw new Error.NOT_SUPPORTED ("Unexpected status: 0x%08x", status);
}
}
}
@@ -3117,6 +3217,21 @@ namespace Frida {
[CCode (cname = "WSTOPSIG", cheader_filename = "sys/wait.h")]
private extern Posix.Signal parse_stop_signal (int status);
private PtraceEvent ptrace_event (int status) {
return ((uint) status) >> 16;
}
}
public enum PtraceEvent {
NONE,
FORK,
VFORK,
CLONE,
EXEC,
VFORK_DONE,
EXIT,
SECCOMP,
}
private int tgkill (uint tgid, uint tid, Posix.Signal sig) {
@@ -3125,7 +3240,7 @@ namespace Frida {
public extern bool _syscall_satisfies (int syscall_id, LinuxSyscall mask);
private sealed class ProcMapsSoEntry {
public class ProcMapsSoEntry {
public uint64 base_address;
public string path;
public string identity;
@@ -3137,7 +3252,7 @@ namespace Frida {
}
public static ProcMapsSoEntry? find_by_address (uint pid, uint64 address) {
var iter = MapsIter.for_pid (pid);
var iter = ProcMapsIter.for_pid (pid);
while (iter.next ()) {
uint64 start = iter.start_address;
uint64 end = iter.end_address;
@@ -3151,7 +3266,7 @@ namespace Frida {
public static ProcMapsSoEntry? find_by_path (uint pid, string path) {
var candidates = new Gee.ArrayList<Candidate> ();
Candidate? latest_candidate = null;
var iter = MapsIter.for_pid (pid);
var iter = ProcMapsIter.for_pid (pid);
#if ANDROID
unowned string libc_path = Gum.Process.get_libc_module ().path;
#endif
@@ -3207,80 +3322,80 @@ namespace Frida {
return result;
}
}
}
private class MapsIter {
private string? contents;
private MatchInfo? info;
private uint offset = 0;
public sealed class ProcMapsIter {
private string? contents;
private MatchInfo? info;
private uint offset = 0;
public uint64 start_address {
get {
return uint64.parse (info.fetch (1), 16);
}
public uint64 start_address {
get {
return uint64.parse (info.fetch (1), 16);
}
}
public uint64 end_address {
get {
return uint64.parse (info.fetch (2), 16);
}
}
public string flags {
owned get {
return info.fetch (3);
}
}
public uint64 file_offset {
get {
return uint64.parse (info.fetch (4), 16);
}
}
public string identity {
owned get {
return info.fetch (5);
}
}
public string path {
owned get {
return info.fetch (6);
}
}
public static ProcMapsIter for_pid (uint pid) {
return new ProcMapsIter (pid);
}
private ProcMapsIter (uint pid) {
try {
FileUtils.get_contents ("/proc/%u/maps".printf (pid), out contents);
} catch (FileError e) {
return;
}
public uint64 end_address {
get {
return uint64.parse (info.fetch (2), 16);
}
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)) {
assert_not_reached ();
}
}
public string flags {
owned get {
return info.fetch (3);
}
}
public bool next () {
if (info == null)
return false;
public uint64 file_offset {
get {
return uint64.parse (info.fetch (4), 16);
}
}
public string identity {
owned get {
return info.fetch (5);
}
}
public string path {
owned get {
return info.fetch (6);
}
}
public static MapsIter for_pid (uint pid) {
return new MapsIter (pid);
}
private MapsIter (uint pid) {
if (offset > 0) {
try {
FileUtils.get_contents ("/proc/%u/maps".printf (pid), out contents);
} catch (FileError e) {
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)) {
assert_not_reached ();
}
}
public bool next () {
if (info == null)
info.next ();
} catch (RegexError e) {
return false;
if (offset > 0) {
try {
info.next ();
} catch (RegexError e) {
return false;
}
}
offset++;
return info.matches ();
}
offset++;
return info.matches ();
}
}
+3 -2
View File
@@ -17,8 +17,9 @@ build: ext/linux/tools/include/nolibc/nolibc.h
rm -rf build
$(MESON) setup --cross-file $(crossfile) -Db_lto=true build
$(MESON) compile -C build
cp build/bootstrapper.bin bootstrapper-$(host_arch).bin
cp build/loader.bin loader-$(host_arch).bin
install -m644 build/bootstrapper.bin bootstrapper-$(host_arch).bin
install -m644 build/loader.bin loader-$(host_arch).bin
[ -f build/zymbiote.bin ] && install -m644 build/zymbiote.bin zymbiote-$(host_arch).bin || true
ext/linux/tools/include/nolibc/nolibc.h: nolibc-tweaks.patch
if [ -d ext/linux ]; then \
+9
View File
@@ -865,6 +865,15 @@ frida_try_load_libc_and_raise (FridaBootstrapContext * ctx)
"r" (entrypoint)
: "memory"
);
#elif defined (__riscv)
asm volatile (
"mv sp, %0\n\t"
"jr %1\n\t"
:
: "r" (&params),
"r" (entrypoint)
: "memory"
);
#endif
}
}
+1 -1
View File
@@ -9,7 +9,7 @@
#endif
#ifndef __ELF_NATIVE_CLASS
# if defined (__x86_64__) || (defined (__aarch64__) && !defined (__ILP32__)) || defined (__mips64)
# if defined (__x86_64__) || (defined (__aarch64__) && !defined (__ILP32__)) || defined (__mips64) || (defined (__riscv) && __riscv_xlen == 64)
# define __ELF_NATIVE_CLASS 64
# else
# define __ELF_NATIVE_CLASS 32
+13 -24
View File
@@ -1,29 +1,18 @@
SECTIONS {
.text : {
*(.text.entrypoint)
. = 0;
.payload : ALIGN(4096)
{
KEEP(*(.text.entrypoint))
*(.text*)
*(.rodata*)
}
.dynstr : {
*(.dynstr)
}
.dynsym : {
*(.dynsym)
}
.gnu.hash : {
*(.gnu.hash)
}
.hash : {
*(.hash)
}
.note.gnu.build-id : {
*(.note.gnu.build-id)
}
.note.android.ident : {
*(.note.android.ident)
}
.got : ALIGN(64) {
*(.got*)
. = ALIGN(64);
}
.dynstr : { *(.dynstr) }
.dynsym : { *(.dynsym) }
.gnu.hash : { *(.gnu.hash) }
.hash : { *(.hash) }
.note.gnu.build-id : { *(.note.gnu.build-id) }
.note.android.ident : { *(.note.android.ident) }
.got : ALIGN(64) { *(.got*) . = ALIGN(64); }
}
+24 -21
View File
@@ -28,27 +28,10 @@ if host_machine.cpu_family() == 'x86'
extra_flags += '-fno-stack-protector'
endif
common_objcopy_flags = [
objcopy_flags = [
'-O', 'binary',
'-S',
'-R', '.plt',
'-R', '.rel.dyn',
'-R', '.rela.dyn',
'-R', '.dynamic',
'-R', '.dynstr',
'-R', '.dynsym',
'-R', '.gnu.version',
'-R', '.gnu.version_r',
'-R', '.gnu.hash',
'-R', '.hash',
'-R', '.note.*',
'-j', '.payload',
]
if not host_machine.cpu_family().startswith('mips')
common_objcopy_flags += [
'-R', '.got',
'-R', '.got.plt',
]
endif
helper_lds = meson.current_source_dir() / 'helper.lds'
@@ -80,7 +63,7 @@ bootstrapper = shared_module('bootstrapper', bootstrapper_sources,
custom_target('bootstrapper-bin',
input: [bootstrapper],
output: ['bootstrapper.bin'],
command: [objcopy, common_objcopy_flags, '@INPUT0@', '@OUTPUT0@'],
command: [objcopy, objcopy_flags, '@INPUT0@', '@OUTPUT0@'],
install_dir: 'share' / 'frida-helpers',
install: true,
)
@@ -99,7 +82,27 @@ loader = shared_module('loader', ['loader.c', 'syscall.c'],
custom_target('loader-bin',
input: [loader],
output: ['loader.bin'],
command: [objcopy, common_objcopy_flags, '@INPUT0@', '@OUTPUT0@'],
command: [objcopy, objcopy_flags, '@INPUT0@', '@OUTPUT0@'],
install_dir: 'share' / 'frida-helpers',
install: true,
)
if host_machine.system() == 'android'
zymbiote_flags = []
if host_machine.cpu_family() == 'aarch64'
zymbiote_flags += '-mbranch-protection=bti'
endif
zymbiote = shared_module('zymbiote', ['zymbiote.c'],
name_prefix: '',
c_args: zymbiote_flags + extra_flags,
link_args: extra_link_args,
link_depends: [helper_lds],
)
custom_target('zymbiote-bin',
input: [zymbiote],
output: ['zymbiote.bin'],
command: [objcopy, objcopy_flags, '@INPUT0@', '@OUTPUT0@'],
install_dir: 'share' / 'frida-helpers',
install: true,
)
endif
+20
View File
@@ -138,6 +138,26 @@ frida_syscall_4 (size_t n, size_t a, size_t b, size_t c, size_t d)
result = (status == 0) ? retval : -retval;
}
#elif defined (__riscv)
{
register ssize_t a7 asm ("a7") = n;
register size_t a0 asm ("a0") = a;
register size_t a1 asm ("a1") = b;
register size_t a2 asm ("a2") = c;
register size_t a3 asm ("a3") = d;
asm volatile (
"ecall\n\t"
: "+r" (a0)
: "r" (a1),
"r" (a2),
"r" (a3),
"r" (a7)
: "memory"
);
result = a0;
}
#endif
return result;
+3 -1
View File
@@ -1,7 +1,9 @@
#ifndef __FRIDA_SYSCALL_H__
#define __FRIDA_SYSCALL_H__
#include <unistd.h>
#ifndef NOLIBC
# include <unistd.h>
#endif
#include <sys/syscall.h>
#define frida_syscall_0(n) frida_syscall_4 (n, 0, 0, 0, 0)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+295
View File
@@ -0,0 +1,295 @@
#include <errno.h>
#include <jni.h>
#include <signal.h>
#include <stdbool.h>
#include <sys/socket.h>
#include <sys/un.h>
typedef struct _FridaApi FridaApi;
struct _FridaApi
{
char name[64];
void ** art_method_slot;
void (* original_set_argv0) (JNIEnv * env, jobject clazz, jstring name);
int (* socket) (int domain, int type, int protocol);
int (* connect) (int sockfd, const struct sockaddr * addr, socklen_t addrlen);
int * (* __errno) (void);
pid_t (* getpid) (void);
pid_t (* getppid) (void);
ssize_t (* sendmsg) (int sockfd, const struct msghdr * msg, int flags);
ssize_t (* recv) (int sockfd, void * buf, size_t len, int flags);
int (* close) (int fd);
int (* raise) (int sig);
};
static volatile const FridaApi frida =
{
.name = "/frida-zymbiote-00000000000000000000000000000000",
};
static int frida_stop_and_return (JNIEnv * env, jobject clazz, jstring name);
static int frida_get_errno (void);
static int frida_connect (int sockfd, const struct sockaddr * addr, socklen_t addrlen);
static ssize_t frida_sendmsg (int sockfd, const struct msghdr * msg, int flags);
static bool frida_sendmsg_all (int sockfd, struct iovec * iov, size_t iovlen, int flags);
static ssize_t frida_recv (int sockfd, void * buf, size_t len, int flags);
__attribute__ ((section (".text.entrypoint")))
__attribute__ ((visibility ("default")))
int
frida_zymbiote_replacement_set_argv0 (JNIEnv * env, jobject clazz, jstring name)
{
bool success = false;
int fd;
struct sockaddr_un addr;
socklen_t addrlen;
unsigned int name_len;
*frida.art_method_slot = frida.original_set_argv0;
frida.original_set_argv0 (env, clazz, name);
fd = frida.socket (AF_UNIX, SOCK_STREAM, 0);
if (fd == -1)
goto beach;
addr.sun_family = AF_UNIX;
addr.sun_path[0] = '\0';
name_len = 0;
for (unsigned int i = 0; i != sizeof (frida.name); i++)
{
if (frida.name[i] == '\0')
break;
if (1u + i >= sizeof (addr.sun_path))
break;
addr.sun_path[1u + i] = frida.name[i];
name_len++;
}
addrlen = (socklen_t) (offsetof (struct sockaddr_un, sun_path) + 1u + name_len);
if (frida_connect (fd, (const struct sockaddr *) &addr, addrlen) == -1)
goto beach;
{
const char * name_utf8;
struct
{
uint32_t pid;
uint32_t ppid;
uint32_t package_name_len;
} header;
struct iovec iov[2];
name_utf8 = (*env)->GetStringUTFChars (env, name, NULL);
header.pid = frida.getpid ();
header.ppid = frida.getppid ();
header.package_name_len = 0;
while (name_utf8[header.package_name_len] != '\0')
header.package_name_len++;
iov[0].iov_base = &header;
iov[0].iov_len = sizeof (header);
iov[1].iov_base = (void *) name_utf8;
iov[1].iov_len = header.package_name_len;
if (!frida_sendmsg_all (fd, iov, 2, MSG_NOSIGNAL))
goto beach;
(*env)->ReleaseStringUTFChars (env, name, name_utf8);
}
{
uint8_t rx;
if (frida_recv (fd, &rx, 1, 0) != 1)
goto beach;
}
success = true;
beach:
if (fd != -1)
frida.close (fd);
if (success)
{
__attribute__ ((musttail))
return frida_stop_and_return (env, clazz, name);
}
return 0;
}
__attribute__ ((naked, noinline))
static int
frida_stop_and_return (JNIEnv * env, jobject clazz, jstring name)
{
#if defined (__i386__)
__asm__ __volatile__ (
"movl $%c[sig], 4(%%esp)\n"
"call 1f\n"
"1: pop %%eax\n"
"addl $(frida-1b), %%eax\n"
"movl %c[raise_off](%%eax), %%eax\n"
"jmp *%%eax\n"
:
: [sig] "i" (SIGSTOP),
[raise_off] "i" (offsetof (FridaApi, raise))
: "eax", "memory");
#elif defined (__x86_64__)
__asm__ __volatile__ (
"mov $%c[sig], %%edi\n"
"leaq frida(%%rip), %%r11\n"
"movq %c[raise_off](%%r11), %%r11\n"
"jmp *%%r11\n"
:
: [sig] "i" (SIGSTOP),
[raise_off] "i" (offsetof (FridaApi, raise))
: "r11", "rdi", "memory");
#elif defined (__arm__)
__asm__ __volatile__ (
"mov r0, %[sig]\n"
"adr r12, frida\n"
"ldr r12, [r12, %[raise_off]]\n"
"bx r12\n"
:
: [sig] "i" (SIGSTOP),
[raise_off] "i" (offsetof (FridaApi, raise))
: "r12", "memory");
#elif defined (__aarch64__)
__asm__ __volatile__ (
"mov w0, #%[sig]\n"
"adrp x16, frida\n"
"add x16, x16, :lo12:frida\n"
"ldr x16, [x16, %[raise_off]]\n"
"br x16\n"
:
: [sig] "i" (SIGSTOP),
[raise_off] "i" (offsetof (FridaApi, raise))
: "x16", "memory");
#else
# error Unsupported architecture
#endif
}
static int
frida_get_errno (void)
{
return *frida.__errno ();
}
static int
frida_connect (int sockfd, const struct sockaddr * addr, socklen_t addrlen)
{
for (;;)
{
if (frida.connect (sockfd, addr, addrlen) == 0)
return 0;
if (frida_get_errno () == EINTR)
continue;
return -1;
}
}
static ssize_t
frida_sendmsg (int sockfd, const struct msghdr * msg, int flags)
{
for (;;)
{
ssize_t n = frida.sendmsg (sockfd, msg, flags);
if (n != -1)
return n;
if (frida_get_errno () == EINTR)
continue;
return -1;
}
}
static bool
frida_sendmsg_all (int sockfd, struct iovec * iov, size_t iovlen, int flags)
{
size_t idx = 0;
size_t off = 0;
while (idx != iovlen)
{
struct msghdr m;
m.msg_name = NULL;
m.msg_namelen = 0;
m.msg_iov = &iov[idx];
m.msg_iovlen = iovlen - idx;
m.msg_control = NULL;
m.msg_controllen = 0;
m.msg_flags = 0;
ssize_t n = frida_sendmsg (sockfd, &m, flags);
if (n == -1)
return false;
size_t remaining = n;
while (remaining != 0)
{
size_t avail = iov[idx].iov_len - off;
if (remaining < avail)
{
iov[idx].iov_base = iov[idx].iov_base + remaining;
iov[idx].iov_len -= remaining;
remaining = 0;
}
else
{
remaining -= avail;
idx++;
off = 0;
if (idx == iovlen)
break;
}
}
}
return true;
}
static ssize_t
frida_recv (int sockfd, void * buf, size_t len, int flags)
{
for (;;)
{
ssize_t n = frida.recv (sockfd, buf, len, flags);
if (n != -1)
return n;
if (frida_get_errno () == EINTR)
continue;
return -1;
}
}
File diff suppressed because it is too large. Load diff
+32 -22
View File
@@ -29,6 +29,38 @@ backend_vala_args_private = []
backend_libs = []
backend_deps = []
if host_os == 'android' or have_droidy_backend
subdir('android-helper')
android_data = custom_target('frida-data-android',
input: [
'android.resources',
android_helper_dex,
'linux' / 'helpers' / f'zymbiote-x86.bin',
'linux' / 'helpers' / f'zymbiote-x86_64.bin',
'linux' / 'helpers' / f'zymbiote-arm.bin',
'linux' / 'helpers' / f'zymbiote-arm64.bin',
],
output: [
'frida-data-android.vapi',
'frida-data-android.h',
'frida-data-android.c',
'frida-data-android-blob' + resource_blob_extension,
],
command: [
resource_compiler_cmd_array,
'-c', '@INPUT0@',
'-o', meson.current_build_dir() / 'frida-data-android',
'@INPUT1@',
'@INPUT2@',
'@INPUT3@',
'@INPUT4@',
'@INPUT5@',
],
)
backend_sources += [android_data]
endif
if have_local_backend
post_process_helper = post_process + ['executable', 're.frida.Helper']
@@ -287,31 +319,9 @@ if have_local_backend
]
if host_os == 'android'
subdir('linux' / 'agent' / 'system-server')
backend_vala_args_private += frida_selinux_vala_args
backend_libs += frida_selinux
backend_deps += frida_selinux_dep
android_data = custom_target('frida-data-android',
input: [
'linux' / 'frida-android.resources',
system_server_js,
],
output: [
'frida-data-android.vapi',
'frida-data-android.h',
'frida-data-android.c',
'frida-data-android-blob' + resource_blob_extension,
],
command: [
resource_compiler_cmd_array,
'-c', '@INPUT0@',
'-o', meson.current_build_dir() / 'frida-data-android',
'@INPUT1@',
],
)
backend_sources += [android_data]
endif
helper_backend_sources = [
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/frida-gum.git
revision = 17.5.2
revision = 17.6.2
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/lwip.git
revision = 4fe7223c2e80dc328266fb2483e789ee5fad7c79
revision = 334ebb20a22023db2f0d5017e96eb0c7798082fd
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/openssl.git
revision = 5029c4562d23547d8e29768e1b0de573c52bf3fc
revision = fa60a1c8c704e4ca0cc0dcb289c3be1fea1b50ff
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/selinux.git
revision = 6d5513fd8069e9ff9b7aa10970d34457b32970c8
revision = b7c8cd70d821bb3fc69500e6273149299b80b923
depth = 1
[provide]
+2 -1
View File
@@ -1,7 +1,8 @@
[wrap-git]
url = https://github.com/frida/termux-elf-cleaner.git
revision = 8e572cb066907cfc8ac1f73d511683844de906ee
revision = ab933ec128c6538067cdf3907ebbbc3bc35f1c55
depth = 1
[provide]
program_names = termux-elf-cleaner
+6
View File
@@ -108,6 +108,12 @@ frida_test_cpu (void)
# else
return FRIDA_TEST_CPU_MIPS;
# endif
#elif defined (HAVE_RISCV)
# if GLIB_SIZEOF_VOID_P == 8
return FRIDA_TEST_CPU_RISCV64;
# else
return FRIDA_TEST_CPU_RISCV32;
# endif
#endif
}
+3 -1
View File
@@ -231,7 +231,9 @@ namespace Frida.Test {
ARM_32,
ARM_64,
MIPS,
MIPSEL
MIPSEL,
RISCV32,
RISCV64
}
public enum Arch {