Compare commits

...
11 Commits
Author SHA1 Message Date
Ole André Vadla Ravnås 6b54eeb7d8 subprojects: Prepare for release 2026-02-14 10:52:09 +01:00
Ole André Vadla Ravnås 08de3d3d07 subprojects: Bump outdated 2026-02-14 10:50:25 +01:00
Ole André Vadla Ravnås 174b93cbd4 xpc-service: Drop support for redundant casts 2026-02-14 10:44:29 +01:00
Ole André Vadla Ravnås d0f8162a65 android: Fix a stylistic inconsistency 2026-02-13 22:53:59 +01:00
Ole André Vadla Ravnås a3427baeb3 subprojects: Prepare for release 2026-02-13 22:48:30 +01:00
Ole André Vadla Ravnås c792a09002 subprojects: Bump outdated 2026-02-13 22:47:31 +01:00
Ole André Vadla Ravnås 27605f7fde android: Defer spawn completion until setArgV0()
Only record the package name in selinux_android_setcontext(), and delay
contacting RoboLauncher until setArgV0(). This ensures ART is ready so
frida-java-bridge can attach to the VM.

Also refactor Zymbiote code and improve naming.
2026-02-13 22:46:03 +01:00
Ole André Vadla Ravnås ba8784fe8c subprojects: Prepare for release 2026-02-13 15:12:30 +01:00
Ole André Vadla Ravnås 0a6c2103fa subprojects: Bump outdated 2026-02-13 15:09:43 +01:00
Ole André Vadla Ravnås 14de0ef567 android: Fix spawn package name logic on new OSes
Where we should be hooking selinux_android_setcontext() to infer it,
like we used to.
2026-02-13 14:39:22 +01:00
Ole André Vadla Ravnås 4165572919 linux: Avoid repeated /proc/pid/maps parsing
By folding the ProcMapsSoEntry functionality into ProcMapsSnapshot.
2026-02-13 14:39:17 +01:00
19 changed files with 487 additions and 278 deletions

No files matched your search

-16
View File
@@ -2462,26 +2462,10 @@ namespace Frida {
private static void add_variant_value_of_type (Variant val, string type, Fruity.XpcBodyBuilder builder) throws Error {
switch (type) {
case "bool":
check_type (val, VariantType.BOOLEAN);
builder.add_bool_value (val.get_boolean ());
break;
case "int64":
check_type (val, VariantType.INT64);
builder.add_int64_value (val.get_int64 ());
break;
case "uint64":
check_type (val, VariantType.UINT64);
builder.add_uint64_value (val.get_uint64 ());
break;
case "data":
check_type (val, new VariantType ("ay"));
builder.add_data_value (val.get_data_as_bytes ());
break;
case "string":
check_type (val, VariantType.STRING);
builder.add_string_value (val.get_string ());
break;
case "uuid":
check_type (val, new VariantType ("ay"));
if (val.get_size () != 16)
+17 -13
View File
@@ -813,22 +813,23 @@ namespace Frida {
private const uint64 SOCK_CLOEXEC = 0x80000;
private sealed class InjectSession : SeizeSession {
private static ProcMapsSoEntry local_libc;
private static ProcMapsSnapshot.Mapping local_libc;
private static uint64 mmap_offset;
private static uint64 munmap_offset;
private static string fallback_ld;
private static string fallback_libc;
private static ProcMapsSoEntry? local_android_ld;
private static ProcMapsSnapshot.Mapping? local_android_ld;
static construct {
var libc = Gum.Process.get_libc_module ();
uint local_pid = Posix.getpid ();
local_libc = ProcMapsSoEntry.find_by_path (local_pid, libc.path);
var local_maps = ProcMapsSnapshot.from_pid (local_pid);
local_libc = local_maps.find_module_by_path (libc.path);
assert (local_libc != null);
mmap_offset = (uint64) (uintptr) libc.find_export_by_name ("mmap") - local_libc.base_address;
munmap_offset = (uint64) (uintptr) libc.find_export_by_name ("munmap") - local_libc.base_address;
mmap_offset = (uint64) (uintptr) libc.find_export_by_name ("mmap") - local_libc.start;
munmap_offset = (uint64) (uintptr) libc.find_export_by_name ("munmap") - local_libc.start;
try {
var program = new Gum.ElfModule.from_file ("/proc/self/exe");
@@ -846,7 +847,7 @@ namespace Frida {
}
#if ANDROID
local_android_ld = ProcMapsSoEntry.find_by_path (local_pid, fallback_ld);
local_android_ld = local_maps.find_module_by_path (fallback_ld);
#endif
}
@@ -1051,9 +1052,10 @@ namespace Frida {
uint64 allocation_base = 0;
size_t allocation_size = size_t.max (bootstrapper_size, loader_size) + stack_size;
var remote_maps = ProcMapsSnapshot.from_pid (pid);
var remote_libc = remote_maps.find_module_by_path (local_libc.path);
uint64 remote_mmap = 0;
uint64 remote_munmap = 0;
ProcMapsSoEntry? remote_libc = ProcMapsSoEntry.find_by_path (pid, local_libc.path);
#if ANDROID
bool same_libc = remote_libc != null
&& remote_libc.device.major == local_libc.device.major
@@ -1066,8 +1068,8 @@ namespace Frida {
&& remote_libc.path == local_libc.path;
#endif
if (same_libc) {
remote_mmap = remote_libc.base_address + mmap_offset;
remote_munmap = remote_libc.base_address + munmap_offset;
remote_mmap = remote_libc.start + mmap_offset;
remote_munmap = remote_libc.start + munmap_offset;
}
if (remote_mmap != 0) {
@@ -1165,7 +1167,9 @@ namespace Frida {
result.context.libc = &result.libc;
if (result.context.rtld_flavor == ANDROID && result.libc.dlopen == null) {
ProcMapsSoEntry? remote_ld = ProcMapsSoEntry.find_by_address (pid, (uintptr) result.context.rtld_base);
if (restart_after_libc_load)
remote_maps = ProcMapsSnapshot.from_pid (pid);
var remote_ld = remote_maps.find_mapping ((uintptr) result.context.rtld_base);
bool same_ld = remote_ld != null && local_android_ld != null
&& remote_ld.device.equals (local_android_ld.device)
&& remote_ld.inode == local_android_ld.inode;
@@ -1217,9 +1221,9 @@ namespace Frida {
}
}
private static void * rebase_pointer (uintptr local_ptr, ProcMapsSoEntry local_module, ProcMapsSoEntry remote_module) {
var offset = local_ptr - local_module.base_address;
return (void *) (remote_module.base_address + offset);
private static void * rebase_pointer (uintptr local_ptr, ProcMapsSnapshot.Mapping local, ProcMapsSnapshot.Mapping remote) {
var offset = local_ptr - local.start;
return (void *) (remote.start + offset);
}
private static string make_fallback_address () {
+5 -5
View File
@@ -49,11 +49,11 @@ bpf_arch_pairs := \
build-native: ext/linux/tools/include/nolibc/nolibc.h
rm -rf build
$(MESON) setup --cross-file $(crossfile) -Db_lto=true build
$(MESON) compile -C build
install -m644 build/bootstrapper.bin $(native_outdir)/bootstrapper.bin
install -m644 build/loader.bin $(native_outdir)/loader.bin
[ -f build/zymbiote.bin ] && install -m644 build/zymbiote.bin $(native_outdir)/zymbiote.bin || true
$(MESON) setup --prefix /usr --cross-file $(crossfile) -Db_lto=true build
DESTDIR=$(abspath build/dist) $(MESON) install -C build
for f in build/dist/usr/share/frida-helpers/*; do \
install -m644 $$f $(native_outdir)/; \
done
bpf_targets :=
bpf_obj := $(build_dir)/module.o
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -6,6 +6,7 @@ SECTIONS {
KEEP(*(.text.entrypoint))
*(.text*)
*(.rodata*)
*(.data*)
}
.dynstr : { *(.dynstr) }
+1 -5
View File
@@ -94,14 +94,10 @@ if host_machine.system() == 'android'
endif
zymbiote = shared_module('zymbiote', ['zymbiote.c'],
name_prefix: '',
name_suffix: 'elf',
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,
)
+155 -83
View File
@@ -2,18 +2,28 @@
#include <jni.h>
#include <signal.h>
#include <stdbool.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/un.h>
typedef struct _FridaApi FridaApi;
typedef struct _ZymbioteContext ZymbioteContext;
struct _FridaApi
struct _ZymbioteContext
{
char name[64];
char socket_path[64];
void ** art_method_slot;
void * payload_base;
size_t payload_size;
size_t payload_original_protection;
char * package_name;
int (* original_setcontext) (uid_t uid, bool is_system_server, const char * seinfo, const char * name);
void (* original_set_argv0) (JNIEnv * env, jobject clazz, jstring name);
int (* mprotect) (void * addr, size_t len, int prot);
char * (* strdup) (const char * s);
void (* free) (void * ptr);
int (* socket) (int domain, int type, int protocol);
int (* connect) (int sockfd, const struct sockaddr * addr, socklen_t addrlen);
int * (* __errno) (void);
@@ -25,12 +35,17 @@ struct _FridaApi
int (* raise) (int sig);
};
static volatile const FridaApi frida =
ZymbioteContext zymbiote =
{
.name = "/frida-zymbiote-00000000000000000000000000000000",
.socket_path = "/frida-zymbiote-00000000000000000000000000000000",
};
static int frida_stop_and_return (JNIEnv * env, jobject clazz, jstring name);
int frida_zymbiote_replacement_setargv0 (JNIEnv * env, jobject clazz, jstring name);
int frida_zymbiote_replacement_setcontext (uid_t uid, bool is_system_server, const char * seinfo, const char * name);
static void frida_wait_for_permission_to_resume (const char * package_name, bool * revert_now);
static int frida_stop_and_return_from_setargv0 (JNIEnv * env, jobject clazz, jstring name);
static int frida_get_errno (void);
@@ -42,19 +57,71 @@ 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)
frida_zymbiote_replacement_setcontext (uid_t uid, bool is_system_server, const char * seinfo, const char * name)
{
int res;
res = zymbiote.original_setcontext (uid, is_system_server, seinfo, name);
if (res == -1)
return -1;
if (zymbiote.package_name == NULL)
{
zymbiote.mprotect (zymbiote.payload_base, zymbiote.payload_size, PROT_READ | PROT_WRITE | PROT_EXEC);
zymbiote.package_name = zymbiote.strdup (name);
}
return res;
}
__attribute__ ((section (".text.entrypoint")))
__attribute__ ((visibility ("default")))
int
frida_zymbiote_replacement_setargv0 (JNIEnv * env, jobject clazz, jstring name)
{
const char * name_utf8;
bool revert_now;
zymbiote.original_set_argv0 (env, clazz, name);
if (zymbiote.package_name != NULL)
name_utf8 = zymbiote.package_name;
else
name_utf8 = (*env)->GetStringUTFChars (env, name, NULL);
frida_wait_for_permission_to_resume (name_utf8, &revert_now);
if (zymbiote.package_name != NULL)
{
zymbiote.free (zymbiote.package_name);
zymbiote.package_name = NULL;
zymbiote.mprotect (zymbiote.payload_base, zymbiote.payload_size, zymbiote.payload_original_protection);
}
else
{
(*env)->ReleaseStringUTFChars (env, name, name_utf8);
}
if (revert_now)
{
__attribute__ ((musttail))
return frida_stop_and_return_from_setargv0 (env, clazz, name);
}
return 0;
}
static void
frida_wait_for_permission_to_resume (const char * package_name, bool * revert_now)
{
bool success = false;
int fd;
struct sockaddr_un addr;
socklen_t addrlen;
unsigned int name_len;
*frida.art_method_slot = frida.original_set_argv0;
*revert_now = false;
frida.original_set_argv0 (env, clazz, name);
fd = frida.socket (AF_UNIX, SOCK_STREAM, 0);
fd = zymbiote.socket (AF_UNIX, SOCK_STREAM, 0);
if (fd == -1)
goto beach;
@@ -62,15 +129,15 @@ frida_zymbiote_replacement_set_argv0 (JNIEnv * env, jobject clazz, jstring name)
addr.sun_path[0] = '\0';
name_len = 0;
for (unsigned int i = 0; i != sizeof (frida.name); i++)
for (unsigned int i = 0; i != sizeof (zymbiote.socket_path); i++)
{
if (frida.name[i] == '\0')
if (zymbiote.socket_path[i] == '\0')
break;
if (1u + i >= sizeof (addr.sun_path))
break;
addr.sun_path[1u + i] = frida.name[i];
addr.sun_path[1u + i] = zymbiote.socket_path[i];
name_len++;
}
@@ -80,7 +147,6 @@ frida_zymbiote_replacement_set_argv0 (JNIEnv * env, jobject clazz, jstring name)
goto beach;
{
const char * name_utf8;
struct
{
uint32_t pid;
@@ -89,25 +155,21 @@ frida_zymbiote_replacement_set_argv0 (JNIEnv * env, jobject clazz, jstring name)
} header;
struct iovec iov[2];
name_utf8 = (*env)->GetStringUTFChars (env, name, NULL);
header.pid = frida.getpid ();
header.ppid = frida.getppid ();
header.pid = zymbiote.getpid ();
header.ppid = zymbiote.getppid ();
header.package_name_len = 0;
while (name_utf8[header.package_name_len] != '\0')
while (package_name[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_base = (void *) package_name;
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);
}
{
@@ -117,86 +179,96 @@ frida_zymbiote_replacement_set_argv0 (JNIEnv * env, jobject clazz, jstring name)
goto beach;
}
success = true;
*revert_now = true;
beach:
if (fd != -1)
frida.close (fd);
if (success)
{
__attribute__ ((musttail))
return frida_stop_and_return (env, clazz, name);
}
return 0;
zymbiote.close (fd);
}
__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"
# define FRIDA_TAILCALL_TO_RAISE_SIGSTOP() \
__asm__ __volatile__ ( \
"movl $%c[sig], 4(%%esp)\n" \
\
"call 1f\n" \
"1: pop %%eax\n" \
\
"addl $(zymbiote-1b), %%eax\n" \
"movl %c[raise_off](%%eax), %%eax\n" \
\
"jmp *%%eax\n" \
: \
: [sig] "i" (SIGSTOP), \
[raise_off] "i" (offsetof (ZymbioteContext, raise)) \
: "eax", "memory" \
)
"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"
# define FRIDA_TAILCALL_TO_RAISE_SIGSTOP() \
__asm__ __volatile__ ( \
"mov $%c[sig], %%edi\n" \
\
"leaq zymbiote(%%rip), %%r11\n" \
"movq %c[raise_off](%%r11), %%r11\n" \
\
"jmp *%%r11\n" \
: \
: [sig] "i" (SIGSTOP), \
[raise_off] "i" (offsetof (ZymbioteContext, raise)) \
: "r11", "rdi", "memory" \
)
"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"
# define FRIDA_TAILCALL_TO_RAISE_SIGSTOP() \
__asm__ __volatile__ ( \
"mov r0, %[sig]\n" \
\
"adr r12, zymbiote\n" \
"ldr r12, [r12, %[raise_off]]\n" \
\
"bx r12\n" \
: \
: [sig] "i" (SIGSTOP), \
[raise_off] "i" (offsetof (ZymbioteContext, raise)) \
: "r12", "memory" \
)
"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"
# define FRIDA_TAILCALL_TO_RAISE_SIGSTOP() \
__asm__ __volatile__ ( \
"mov w0, #%[sig]\n" \
\
"adrp x16, zymbiote\n" \
"add x16, x16, :lo12:zymbiote\n" \
"ldr x16, [x16, %[raise_off]]\n" \
\
"br x16\n" \
: \
: [sig] "i" (SIGSTOP), \
[raise_off] "i" (offsetof (ZymbioteContext, raise)) \
: "x16", "memory" \
)
"br x16\n"
:
: [sig] "i" (SIGSTOP),
[raise_off] "i" (offsetof (FridaApi, raise))
: "x16", "memory");
#else
# error Unsupported architecture
#endif
__attribute__ ((naked, noinline))
static int
frida_stop_and_return_from_setargv0 (JNIEnv * env, jobject clazz, jstring name)
{
FRIDA_TAILCALL_TO_RAISE_SIGSTOP ();
}
static int
frida_get_errno (void)
{
return *frida.__errno ();
return *zymbiote.__errno ();
}
static int
@@ -204,7 +276,7 @@ frida_connect (int sockfd, const struct sockaddr * addr, socklen_t addrlen)
{
for (;;)
{
if (frida.connect (sockfd, addr, addrlen) == 0)
if (zymbiote.connect (sockfd, addr, addrlen) == 0)
return 0;
if (frida_get_errno () == EINTR)
@@ -219,7 +291,7 @@ frida_sendmsg (int sockfd, const struct msghdr * msg, int flags)
{
for (;;)
{
ssize_t n = frida.sendmsg (sockfd, msg, flags);
ssize_t n = zymbiote.sendmsg (sockfd, msg, flags);
if (n != -1)
return n;
@@ -283,7 +355,7 @@ frida_recv (int sockfd, void * buf, size_t len, int flags)
{
for (;;)
{
ssize_t n = frida.recv (sockfd, buf, len, flags);
ssize_t n = zymbiote.recv (sockfd, buf, len, flags);
if (n != -1)
return n;
+194 -73
View File
@@ -900,10 +900,19 @@ namespace Frida {
}
if (prep.already_patched) {
patches.apply (prep.replaced_ptr, prep.process_memory, prep.art_method_slot,
new Bytes (prep.original_ptr));
patches.apply (prep.replaced_setargv0_ptr, prep.process_memory, prep.setargv0_slot,
new Bytes (prep.original_setargv0_ptr));
} else {
patches.apply (prep.replaced_ptr, prep.process_memory, prep.art_method_slot);
patches.apply (prep.replaced_setargv0_ptr, prep.process_memory, prep.setargv0_slot);
}
if (prep.setcontext_slot != 0) {
if (prep.already_patched) {
patches.apply (prep.replaced_setcontext_ptr, prep.process_memory, prep.setcontext_slot,
new Bytes (prep.original_setcontext_ptr));
} else {
patches.apply (prep.replaced_setcontext_ptr, prep.process_memory, prep.setcontext_slot);
}
}
zymbiote_patches[pid] = patches;
@@ -916,9 +925,13 @@ namespace Frida {
public FileDescriptor process_memory;
public bool already_patched;
public uint64 art_method_slot;
public uint8[] original_ptr;
public uint8[] replaced_ptr;
public uint64 setargv0_slot;
public uint8[] original_setargv0_ptr;
public uint8[] replaced_setargv0_ptr;
public uint64 setcontext_slot;
public uint8[] original_setcontext_ptr;
public uint8[] replaced_setcontext_ptr;
public Bytes payload;
public uint64 payload_base;
@@ -952,35 +965,38 @@ namespace Frida {
private static ZymbiotePrepResult do_prepare_zymbiote_injection (uint pid, string server_name) throws Error, IOError {
uint64 payload_base = 0;
string? payload_path = null;
int payload_original_protection = 0;
unowned string? payload_path = null;
uint64 payload_file_offset = 0;
string? libc_path = null;
string? runtime_path = null;
Gee.List<Gum.MemoryRange?> heap_candidates = new Gee.ArrayList<Gum.MemoryRange?> ();
unowned string? libc_path = null;
unowned string? libselinux_path = null;
unowned string? runtime_path = null;
Gee.List<ProcMapsSnapshot.Mapping> heap_candidates = new Gee.ArrayList<ProcMapsSnapshot.Mapping> ();
var iter = ProcMapsIter.for_pid (pid);
while (iter.next ()) {
string path = iter.path;
string flags = iter.flags;
if (path.has_suffix ("/libstagefright.so") && "x" in flags) {
var maps = ProcMapsSnapshot.from_pid (pid);
foreach (var m in maps) {
unowned string path = m.path;
if (path.has_suffix ("/libstagefright.so") && m.readable && m.executable) {
if (payload_base == 0) {
payload_base = iter.end_address - Gum.query_page_size ();
payload_base = m.end - Gum.query_page_size ();
payload_original_protection = Posix.PROT_READ | Posix.PROT_EXEC;
if (m.writable)
payload_original_protection |= Posix.PROT_WRITE;
payload_path = path;
payload_file_offset = iter.file_offset;
payload_file_offset = m.file_offset;
}
} else if (path.has_suffix ("/libc.so")) {
if (libc_path == null)
libc_path = path;
} else if (path.has_suffix ("/libselinux.so")) {
if (libselinux_path == null)
libselinux_path = path;
} else if (path.has_suffix ("/libandroid_runtime.so")) {
if (runtime_path == null)
runtime_path = path;
} else if (flags == "rw-p" && is_boot_heap (path)) {
uint64 start = iter.start_address;
uint64 end = iter.end_address;
heap_candidates.add (Gum.MemoryRange () {
base_address = start,
size = (size_t) (end - start),
});
} else if (is_boot_heap (m)) {
heap_candidates.add (m);
}
}
@@ -993,81 +1009,182 @@ namespace Frida {
if (heap_candidates.is_empty)
throw new Error.NOT_SUPPORTED ("Unable to detect any VM heap candidates");
var libc_entry = ProcMapsSoEntry.find_by_path (pid, libc_path);
if (libc_entry == null)
throw new Error.NOT_SUPPORTED ("Unable to detect libc.so entry");
var libc_mapping = maps.find_module_by_path (libc_path);
if (libc_mapping == null)
throw new Error.NOT_SUPPORTED ("Unable to detect mapping for %s", libc_path);
var runtime_entry = ProcMapsSoEntry.find_by_path (pid, runtime_path);
if (runtime_entry == null)
throw new Error.NOT_SUPPORTED ("Unable to detect libandroid_runtime.so entry");
var runtime_mapping = maps.find_module_by_path (runtime_path);
if (runtime_mapping == null)
throw new Error.NOT_SUPPORTED ("Unable to detect mapping for %s", runtime_path);
Gum.ElfModule libc;
try {
libc = new Gum.ElfModule.from_file (libc_path);
} catch (Gum.Error e) {
throw new Error.NOT_SUPPORTED ("Unable to parse libc.so: %s", e.message);
throw new Error.NOT_SUPPORTED ("Unable to parse %s: %s", libc_path, e.message);
}
uint64 original_setcontext = 0;
if (libselinux_path != null) {
Gum.ElfModule? libselinux;
try {
libselinux = new Gum.ElfModule.from_file (libselinux_path);
} catch (Gum.Error e) {
throw new Error.NOT_SUPPORTED ("Unable to parse %s: %s", libselinux_path, e.message);
}
var libselinux_mapping = maps.find_module_by_path (libselinux_path);
if (libselinux_mapping == null)
throw new Error.NOT_SUPPORTED ("Unable to detect mapping for %s", libselinux_path);
libselinux.enumerate_exports (e => {
if (e.name == "selinux_android_setcontext") {
original_setcontext = libselinux_mapping.start + e.address;
return false;
}
return true;
});
}
Gum.ElfModule runtime;
try {
runtime = new Gum.ElfModule.from_file (runtime_path);
} catch (Gum.Error e) {
throw new Error.NOT_SUPPORTED ("Unable to parse libandroid_runtime.so: %s", e.message);
throw new Error.NOT_SUPPORTED ("Unable to parse %s: %s", runtime_path, e.message);
}
uint64 set_argv0_address = 0;
uint64 original_setargv0 = 0;
uint64 setcontext_slot = 0;
runtime.enumerate_exports (e => {
if (e.name == "_Z27android_os_Process_setArgV0P7_JNIEnvP8_jobjectP8_jstring") {
set_argv0_address = runtime_entry.base_address + e.address;
original_setargv0 = runtime_mapping.start + e.address;
return false;
}
return true;
});
if (set_argv0_address == 0)
if (original_setcontext != 0) {
runtime.enumerate_imports (i => {
if (i.name == "selinux_android_setcontext") {
setcontext_slot = runtime_mapping.start + i.slot;
return false;
}
return true;
});
}
if (original_setargv0 == 0)
throw new Error.NOT_SUPPORTED ("Unable to locate android.os.Process.setArgV0(); please file a bug");
uint pointer_size = ("/lib64/" in libc_path) ? 8 : 4;
uint pointer_size = libc.pointer_size;
var original_ptr = new uint8[pointer_size];
var replaced_ptr = new uint8[pointer_size];
(new Buffer (new Bytes.static (original_ptr), ByteOrder.HOST, pointer_size)).write_pointer (0, set_argv0_address);
(new Buffer (new Bytes.static (replaced_ptr), ByteOrder.HOST, pointer_size)).write_pointer (0, payload_base);
uint64 replacement_setargv0;
uint64 replacement_setcontext;
Bytes payload = make_zymbiote_payload (server_name, payload_base, payload_original_protection, libc, libc_mapping,
original_setargv0, original_setcontext, out replacement_setargv0, out replacement_setcontext);
var fd = open_process_memory (pid);
uint64 art_method_slot = 0;
var original_setargv0_ptr = new uint8[pointer_size];
var original_setcontext_ptr = new uint8[pointer_size];
var replaced_setargv0_ptr = new uint8[pointer_size];
var replaced_setcontext_ptr = new uint8[pointer_size];
(new Buffer (new Bytes.static (original_setargv0_ptr), ByteOrder.HOST, pointer_size))
.write_pointer (0, original_setargv0);
(new Buffer (new Bytes.static (original_setcontext_ptr), ByteOrder.HOST, pointer_size))
.write_pointer (0, original_setcontext);
(new Buffer (new Bytes.static (replaced_setargv0_ptr), ByteOrder.HOST, pointer_size))
.write_pointer (0, replacement_setargv0);
(new Buffer (new Bytes.static (replaced_setcontext_ptr), ByteOrder.HOST, pointer_size))
.write_pointer (0, replacement_setcontext);
uint64 setargv0_slot = 0;
bool already_patched = false;
foreach (var candidate in heap_candidates) {
var heap = new uint8[candidate.size];
var n = fd.pread (heap, candidate.base_address);
var n = fd.pread (heap, candidate.start);
if (n != heap.length)
throw new Error.NOT_SUPPORTED ("Short read");
void * p = memmem (heap, original_ptr);
void * p = memmem (heap, original_setargv0_ptr);
if (p == null) {
p = memmem (heap, replaced_ptr);
p = memmem (heap, replaced_setargv0_ptr);
already_patched = p != null;
}
if (p != null) {
art_method_slot = candidate.base_address + ((uint8 *) p - (uint8 *) heap);
setargv0_slot = candidate.start + ((uint8 *) p - (uint8 *) heap);
break;
}
}
if (art_method_slot == 0)
throw new Error.NOT_SUPPORTED ("Unable to locate method slot; please file a bug");
if (setargv0_slot == 0)
throw new Error.NOT_SUPPORTED ("Unable to locate android.os.Process.setArgV0() slot; please file a bug");
return new ZymbiotePrepResult () {
process_memory = fd,
already_patched = already_patched,
setargv0_slot = setargv0_slot,
original_setargv0_ptr = original_setargv0_ptr,
replaced_setargv0_ptr = replaced_setargv0_ptr,
setcontext_slot = setcontext_slot,
original_setcontext_ptr = original_setcontext_ptr,
replaced_setcontext_ptr = replaced_setcontext_ptr,
payload = payload,
payload_base = payload_base,
payload_path = payload_path,
payload_file_offset = payload_file_offset,
};
}
private static Bytes make_zymbiote_payload (string server_name, uint64 payload_base, int payload_original_protection,
Gum.ElfModule libc, ProcMapsSnapshot.Mapping libc_mapping, uint64 original_setargv0,
uint64 original_setcontext, out uint64 replacement_setargv0, out uint64 replacement_setcontext) {
var pointer_size = libc.pointer_size;
var blob = (pointer_size == 8)
#if ARM || ARM64
? Frida.Data.Android.get_zymbiote_arm64_bin_blob ()
: Frida.Data.Android.get_zymbiote_arm_bin_blob ();
? Frida.Data.Android.get_zymbiote_arm64_elf_blob ()
: Frida.Data.Android.get_zymbiote_arm_elf_blob ();
#else
? Frida.Data.Android.get_zymbiote_x86_64_bin_blob ()
: Frida.Data.Android.get_zymbiote_x86_bin_blob ();
? Frida.Data.Android.get_zymbiote_x86_64_elf_blob ()
: Frida.Data.Android.get_zymbiote_x86_elf_blob ();
#endif
unowned uint8[] payload_template = blob.data;
Gum.ElfModule zymbiote;
try {
zymbiote = new Gum.ElfModule.from_blob (new Bytes.static (blob.data));
} catch (Gum.Error e) {
assert_not_reached ();
}
Gum.ElfSegmentDetails? text = null;
zymbiote.enumerate_segments (s => {
if ((s.protection & Gum.PageProtection.EXECUTE) != 0) {
text = s;
return false;
}
return true;
});
assert (text != null);
uint64 setargv0 = 0;
uint64 setcontext = 0;
zymbiote.enumerate_exports (e => {
if (e.name == "frida_zymbiote_replacement_setargv0")
setargv0 = payload_base + (e.address - text.vm_address);
else if (e.name == "frida_zymbiote_replacement_setcontext")
setcontext = payload_base + (e.address - text.vm_address);
return true;
});
assert (setargv0 != 0);
assert (setcontext != 0);
replacement_setargv0 = setargv0;
replacement_setcontext = setcontext;
unowned uint8[] payload_template = blob.data[text.file_offset:text.file_offset + text.file_size];
void * p = memmem (payload_template, "/frida-zymbiote-00000000000000000000000000000000".data);
assert (p != null);
size_t data_offset = (uint8 *) p - (uint8 *) payload_template;
@@ -1078,13 +1195,27 @@ namespace Frida {
payload.write_string (cursor, server_name);
cursor += 64;
payload.write_pointer (cursor, art_method_slot);
payload.write_pointer (cursor, payload_base);
cursor += pointer_size;
payload.write_pointer (cursor, set_argv0_address);
payload.write_pointer (cursor, payload_template.length);
cursor += pointer_size;
payload.write_pointer (cursor, payload_original_protection);
cursor += pointer_size;
cursor += pointer_size;
payload.write_pointer (cursor, original_setcontext);
cursor += pointer_size;
payload.write_pointer (cursor, original_setargv0);
cursor += pointer_size;
string[] wanted = {
"mprotect",
"strdup",
"free",
"socket",
"connect",
"__errno",
@@ -1105,7 +1236,7 @@ namespace Frida {
libc.enumerate_exports (e => {
if (index_of.has_key (e.name)) {
int idx = index_of[e.name];
addrs[idx] = libc_entry.base_address + e.address;
addrs[idx] = libc_mapping.start + e.address;
pending--;
}
return pending != 0;
@@ -1117,26 +1248,16 @@ namespace Frida {
cursor += pointer_size;
}
return new ZymbiotePrepResult () {
process_memory = fd,
already_patched = already_patched,
art_method_slot = art_method_slot,
original_ptr = original_ptr,
replaced_ptr = replaced_ptr,
payload = payload.bytes,
payload_base = payload_base,
payload_path = payload_path,
payload_file_offset = payload_file_offset,
};
return payload.bytes;
}
private static bool is_boot_heap (string path) {
private static bool is_boot_heap (ProcMapsSnapshot.Mapping m) {
if (!m.readable || !m.writable || m.executable || m.shared)
return false;
return
"boot.art" in path ||
"boot-framework.art" in path ||
"dalvik-LinearAlloc" in path;
"boot.art" in m.path ||
"boot-framework.art" in m.path ||
"dalvik-LinearAlloc" in m.path;
}
[CCode (cname = "memmem", cheader_filename = "string.h")]
+100 -71
View File
@@ -8,27 +8,42 @@ namespace Frida {
private Gee.List<Mapping> maps = new Gee.ArrayList<Mapping> ();
private const uint64 VM_READ = 1;
private const uint64 VM_WRITE = 2;
private const uint64 VM_EXEC = 4;
private const uint64 VM_SHARED = 8;
public class Mapping {
public uint64 start;
public uint64 end;
public size_t size {
get {
return (size_t) (end - start);
}
}
public uint64 file_offset;
public bool readable;
public bool writable;
public bool executable;
public bool shared;
public DevId device;
public uint64 inode;
public string module_id;
public string path;
public uint32 start_gen;
public uint32 end_gen;
}
public static ProcMapsSnapshot from_pid (uint32 pid) throws Error {
public static ProcMapsSnapshot from_pid (uint32 pid) {
var snap = new ProcMapsSnapshot ();
snap.build_from_pid (pid);
return snap;
}
private void build_from_pid (uint32 pid) throws Error {
private void build_from_pid (uint32 pid) {
maps.clear ();
var it = ProcMapsIter.for_pid (pid);
@@ -40,10 +55,16 @@ namespace Frida {
m.end = it.end_address;
m.file_offset = it.file_offset;
string flags = it.flags;
m.readable = flags[0] == 'r';
m.writable = flags[1] == 'w';
m.executable = flags[2] == 'x';
m.shared = flags[3] == 's';
m.device = it.device;
m.inode = it.inode;
m.module_id = normalize_module_id (it.path);
m.path = normalize_path (it.path);
m.start_gen = 1;
m.end_gen = 0;
@@ -52,17 +73,26 @@ namespace Frida {
}
}
public void apply_create (uint32 new_gen, uint64 start, uint64 end, uint64 file_offset, DevId device, uint64 inode,
string module_id) {
public void apply_create (uint32 new_gen, uint64 start, uint64 end, uint64 file_offset, uint64 vm_flags, DevId device,
uint64 inode, string path) {
end_range_at_gen (start, end, new_gen);
var m = new Mapping ();
m.start = start;
m.end = end;
m.file_offset = file_offset;
m.readable = (vm_flags & VM_READ) != 0;
m.writable = (vm_flags & VM_WRITE) != 0;
m.executable = (vm_flags & VM_EXEC) != 0;
m.shared = (vm_flags & VM_SHARED) != 0;
m.device = device;
m.inode = inode;
m.module_id = module_id;
m.path = path;
m.start_gen = new_gen;
m.end_gen = 0;
@@ -108,7 +138,7 @@ namespace Frida {
dead.file_offset = m.file_offset;
dead.device = m.device;
dead.inode = m.inode;
dead.module_id = m.module_id;
dead.path = m.path;
dead.start_gen = m.start_gen;
dead.end_gen = gen;
@@ -128,7 +158,7 @@ namespace Frida {
dead.file_offset = m.file_offset + (start - m.start);
dead.device = m.device;
dead.inode = m.inode;
dead.module_id = m.module_id;
dead.path = m.path;
dead.start_gen = m.start_gen;
dead.end_gen = gen;
@@ -146,7 +176,7 @@ namespace Frida {
dead.file_offset = m.file_offset + (start - m.start);
dead.device = m.device;
dead.inode = m.inode;
dead.module_id = m.module_id;
dead.path = m.path;
dead.start_gen = m.start_gen;
dead.end_gen = gen;
@@ -156,7 +186,7 @@ namespace Frida {
right.file_offset = m.file_offset + (end - m.start);
right.device = m.device;
right.inode = m.inode;
right.module_id = m.module_id;
right.path = m.path;
right.start_gen = m.start_gen;
right.end_gen = 0;
@@ -168,13 +198,21 @@ namespace Frida {
}
}
private static string normalize_module_id (string path) {
private static string normalize_path (string path) {
const string suffix = " (deleted)";
if (path.has_suffix (suffix))
return path.substring (0, path.length - suffix.length);
return path;
}
public Gee.Iterator<Mapping> iterator () {
return maps.iterator ();
}
public Mapping? find_mapping (uint64 addr) {
return find_mapping_at_gen (addr, gen);
}
public Mapping? find_mapping_at_gen (uint64 addr, uint32 gen) {
int lo = 0;
int hi = maps.size - 1;
@@ -224,92 +262,83 @@ namespace Frida {
bool not_ended = m.end_gen == 0 || gen < m.end_gen;
return started && not_ended;
}
}
public class ProcMapsSoEntry {
public uint64 base_address;
public string path;
public DevId device;
public uint64 inode;
private ProcMapsSoEntry (uint64 base_address, string path, DevId device, uint64 inode) {
this.base_address = base_address;
this.path = path;
this.device = device;
this.inode = inode;
public Mapping? find_module_by_path (string path) {
return find_module_by_path_at_gen (path, gen);
}
public static ProcMapsSoEntry? find_by_address (uint pid, uint64 address) {
var iter = ProcMapsIter.for_pid (pid);
while (iter.next ()) {
uint64 start = iter.start_address;
uint64 end = iter.end_address;
if (address >= start && address < end)
return new ProcMapsSoEntry (start, iter.path, iter.device, iter.inode);
}
public Mapping? find_module_by_path_at_gen (string path, uint32 at_gen) {
Mapping? best_base = null;
int best_score = int.MIN;
return null;
}
Mapping? cur_base = null;
uint cur_total = 0;
uint cur_exec = 0;
public static ProcMapsSoEntry? find_by_path (uint pid, string path) {
var candidates = new Gee.ArrayList<Candidate> ();
Candidate? latest_candidate = null;
var iter = ProcMapsIter.for_pid (pid);
#if ANDROID
unowned string libc_path = Gum.Process.get_libc_module ().path;
#endif
while (iter.next ()) {
string current_path = iter.path;
if (current_path == "[page size compat]")
for (int i = 0; i < maps.size; i++) {
var m = maps[i];
if (!mapping_alive_at (m, at_gen))
continue;
if (current_path != path) {
latest_candidate = null;
unowned string mp = m.path;
if (mp == "[page size compat]")
continue;
if (mp != path) {
consider_candidate (ref best_base, ref best_score, cur_base, cur_total, cur_exec);
cur_base = null;
cur_total = 0;
cur_exec = 0;
continue;
}
string flags = iter.flags;
#if ANDROID
if (current_path == libc_path && flags[3] == 's')
if (mp == libc_path && m.shared)
continue;
#endif
if (iter.file_offset == 0) {
latest_candidate = new Candidate () {
entry = new ProcMapsSoEntry (iter.start_address, current_path, iter.device, iter.inode),
total_ranges = 0,
executable_ranges = 0,
};
candidates.add (latest_candidate);
if (m.file_offset == 0) {
consider_candidate (ref best_base, ref best_score, cur_base, cur_total, cur_exec);
cur_base = m;
cur_total = 0;
cur_exec = 0;
}
if (latest_candidate != null) {
latest_candidate.total_ranges++;
if (flags[2] == 'x')
latest_candidate.executable_ranges++;
if (cur_base != null) {
cur_total++;
if (m.executable)
cur_exec++;
}
}
candidates.sort ((a, b) => b.score () - a.score ());
consider_candidate (ref best_base, ref best_score, cur_base, cur_total, cur_exec);
if (candidates.is_empty)
return null;
return candidates.first ().entry;
return best_base;
}
private class Candidate {
public ProcMapsSoEntry entry;
public uint total_ranges;
public uint executable_ranges;
private static void consider_candidate (ref Mapping? best_base, ref int best_score, Mapping? candidate_base, uint total,
uint exec) {
if (candidate_base == null)
return;
public int score () {
int result = (int) total_ranges;
if (executable_ranges == 0)
result = -result;
return result;
int score = score_candidate (total, exec);
if (score > best_score) {
best_score = score;
best_base = candidate_base;
}
}
private static int score_candidate (uint total, uint exec) {
int score = (int) total;
if (exec == 0)
score = -score;
return score;
}
}
public sealed class ProcMapsIter {
+9 -7
View File
@@ -12,6 +12,7 @@ public sealed class Frida.SymbolResolver : Object {
public uint64 end;
public uint64 file_offset;
public uint64 vm_flags;
public DevId device;
public uint64 inode;
public string? path;
@@ -34,12 +35,12 @@ public sealed class Frida.SymbolResolver : Object {
}
uint mod_idx;
if (!used_index.has_key (m.module_id)) {
if (!used_index.has_key (m.path)) {
mod_idx = used_modules.size;
used_index[m.module_id] = mod_idx;
used_modules.add (m.module_id);
used_index[m.path] = mod_idx;
used_modules.add (m.path);
} else {
mod_idx = used_index[m.module_id];
mod_idx = used_index[m.path];
}
uint64 rel64 = (addr - m.start) + m.file_offset;
@@ -92,6 +93,7 @@ public sealed class Frida.SymbolResolver : Object {
ev.start = start;
ev.end = end;
ev.file_offset = file_offset;
ev.vm_flags = vm_flags;
ev.device = device;
ev.inode = inode;
ev.path = path;
@@ -143,8 +145,8 @@ public sealed class Frida.SymbolResolver : Object {
q.unset (next_gen);
if (ev.is_create) {
string module_id = make_module_id (ev.device, ev.inode, ev.path);
snap.apply_create (next_gen, ev.start, ev.end, ev.file_offset, ev.device, ev.inode, module_id);
string path = make_path (ev.device, ev.inode, ev.path);
snap.apply_create (next_gen, ev.start, ev.end, ev.file_offset, ev.vm_flags, ev.device, ev.inode, path);
} else {
snap.apply_destroy_range (next_gen, ev.start, ev.end);
}
@@ -159,7 +161,7 @@ public sealed class Frida.SymbolResolver : Object {
cache.clear ();
}
private static string make_module_id (DevId device, uint64 inode, string? path) {
private static string make_path (DevId device, uint64 inode, string? path) {
if (path != null)
return path;
+4 -4
View File
@@ -38,10 +38,10 @@ if host_os == 'android' or have_droidy_backend
input: [
'android.resources',
android_helper_dex,
fs.copyfile(helpers_native_prefix / 'x86' / 'zymbiote.bin', 'zymbiote-x86.bin'),
fs.copyfile(helpers_native_prefix / 'x86_64' / 'zymbiote.bin', 'zymbiote-x86_64.bin'),
fs.copyfile(helpers_native_prefix / 'arm' / 'zymbiote.bin', 'zymbiote-arm.bin'),
fs.copyfile(helpers_native_prefix / 'arm64' / 'zymbiote.bin', 'zymbiote-arm64.bin'),
fs.copyfile(helpers_native_prefix / 'x86' / 'zymbiote.elf', 'zymbiote-x86.elf'),
fs.copyfile(helpers_native_prefix / 'x86_64' / 'zymbiote.elf', 'zymbiote-x86_64.elf'),
fs.copyfile(helpers_native_prefix / 'arm' / 'zymbiote.elf', 'zymbiote-arm.elf'),
fs.copyfile(helpers_native_prefix / 'arm64' / 'zymbiote.elf', 'zymbiote-arm64.elf'),
],
output: [
'frida-data-android.vapi',
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/frida-gum.git
revision = bda339b66655f29b5e82277b68235addfbd8fac7
revision = 17.7.2
depth = 1
[provide]