Compare commits

...
47 Commits
Author SHA1 Message Date
Ole André Vadla Ravnås 9f04675b87 subprojects: Prepare for release 2025-10-29 16:47:53 +01:00
Ole André Vadla Ravnås cd8bda0513 subprojects: Bump outdated 2025-10-29 16:45:40 +01:00
Ole André Vadla RavnåsandHåvard Sørbø e09e09fb85 simmy: Wire up application icons
Co-authored-by: Håvard Sørbø <[email protected]>
2025-10-29 15:08:50 +01:00
Ole André Vadla RavnåsandHåvard Sørbø c24a5da82c simmy: Implement get_frontmost_application()
Co-authored-by: Håvard Sørbø <[email protected]>
2025-10-29 15:01:15 +01:00
Ole André Vadla RavnåsandHåvard Sørbø 05ec6563d1 internal-agent: Support non-local HostSession
So we can use it in the Simmy backend, too.

Co-authored-by: Håvard Sørbø <[email protected]>
2025-10-28 14:12:01 +01:00
Ole André Vadla RavnåsandHåvard Sørbø 9dc9d30fa4 simmy: Fix the reported hardware.product
In query_system_parameters().

So it's e.g. `iPhone18,2` instead of `iPhone 17 Pro Max`.

Co-authored-by: Håvard Sørbø <[email protected]>
2025-10-28 13:15:38 +01:00
Ole André Vadla Ravnås 0b57936285 subprojects: Prepare for release 2025-10-24 15:25:15 +02:00
Ole André Vadla Ravnås 41be857a91 subprojects: Bump outdated 2025-10-24 15:23:37 +02:00
Ole André Vadla Ravnås afa847b898 submodules: Bump releng 2025-10-24 15:23:37 +02:00
Ole André Vadla Ravnås 3e43478de0 android: Bump frida-java-bridge in system-server 2025-10-24 15:22:18 +02:00
Ole André Vadla Ravnås a847a012c9 freebsd: Wire up PTY support 2025-10-12 08:11:13 +02:00
Ole André Vadla Ravnås c34fad1ff0 freebsd: Port remaining bits to the new stdio API 2025-10-12 07:50:00 +02:00
Ole André Vadla Ravnås ceef1d696b darwin: Fix a stylistic inconsistency 2025-10-12 07:47:09 +02:00
Ole André Vadla Ravnås d0cfe69dba subprojects: Prepare for release 2025-10-11 19:22:49 +02:00
Ole André Vadla Ravnås a35aff1865 api: Exclude HostSessionHub from .gir
As it's an internal interface.
2025-10-11 11:46:28 +02:00
Ole André Vadla Ravnås be8a59607a android: Bump frida-java-bridge in system-server 2025-10-11 11:07:51 +02:00
Ole André Vadla Ravnås 726740f96a simmy: Move to CoreSimulator.framework 2025-10-10 12:34:41 +02:00
Ole André Vadla Ravnås 9e11d57a76 Add new Simmy backend
For interacting with Apple's simulators on macOS.
2025-10-10 12:34:41 +02:00
Ole André Vadla Ravnås d8c3c9bfb5 darwin: Support early instrumentation of dyld_sim 2025-10-10 12:34:41 +02:00
Ole André Vadla Ravnås 2720968791 base: Add StdioPipes and FileDescriptor helpers
Factored out of the Darwin and Linux backends.
2025-10-10 12:34:41 +02:00
Ole André Vadla Ravnås b8998e52cd host-session: Support cross-backend communication 2025-10-10 12:34:41 +02:00
Ole André Vadla Ravnås c23f046968 value: Add VariantReader.list_members() 2025-10-10 12:34:41 +02:00
Francesco Tamagni 2d521bbeb7 fruity: Unpair on InvalidHostID
So the error doesn't become persistent, and the
device can be paired again.
2025-10-07 11:37:57 +02:00
Ole André Vadla Ravnås cdae790ba9 darwin: Port to the latest Gum.Darwin API 2025-10-01 10:52:33 +02:00
Ole André Vadla Ravnås 90d2d93799 subprojects: Bump outdated 2025-10-01 10:49:57 +02:00
Ole André Vadla Ravnås 61ad9a0f9f subprojects: Prepare for release 2025-09-19 17:16:18 +02:00
Ole André Vadla Ravnås 48af36f4d1 subprojects: Bump outdated 2025-09-19 17:14:21 +02:00
Ole André Vadla Ravnås 053b34c5fd ncm: Speed up TX packing with fixed-slot NDP
Switch to a fixed-slot NDP layout to eliminate the slow O(k²) "shrink
until it fits" algorithm. The new packer runs in O(k) time and stops
adding datagrams when the NTB max size would be exceeded.

This greatly reduces CPU time spent building NTBs, dropping total layout
time from ~1.1s to negligible levels for a 256 MiB transfer on a modern
desktop.
2025-09-19 17:01:32 +02:00
Ole André Vadla Ravnås 010d5ee500 ncm: Improve TX scheduling to keep USB pipe full
Rework host->device transfer logic to remove the single `writing` gate
as a throughput bottleneck. Manage OUT transfers with a rolling window
and refill as soon as any transfer completes instead of waiting for all
in-flight transfers to finish.

Reduce idle gaps between URBs and keep the USB bulk pipe continuously
fed, improving HS throughput from ~29 MB/s to ~34 MB/s in testing.
Maintain bounded memory usage via transfer and byte window limits.
2025-09-19 17:01:32 +02:00
Ole André Vadla Ravnås 2fa39e28ed fruity: Optimize datagram handling
Deliver datagrams in batches when we can, to make cross-thread delivery
more deterministic.
2025-09-19 16:58:32 +02:00
Ole André Vadla Ravnås 250dc83016 subprojects: Prepare for release 2025-09-18 07:39:38 +02:00
Ole André Vadla Ravnås eec4fc0883 subprojects: Bump outdated 2025-09-17 22:10:20 +02:00
Ole André Vadla Ravnås 4535e3dfd4 submodules: Bump releng 2025-09-17 22:10:20 +02:00
Ole André Vadla Ravnås 01203eab3b meson: Remove lwIP TCP options
The defaults are now tuned to our use-case, so we can avoid duplicating
those in two different places.
2025-09-17 13:56:33 +02:00
Ole André Vadla Ravnås a43311da90 subprojects: Prepare for release 2025-09-15 22:57:00 +02:00
Ole André Vadla Ravnås 204e0ed565 barebone: Move non-Windows-only bits behind #if
To fix warning regressions on Windows.
2025-09-15 22:09:32 +02:00
Ole André Vadla Ravnås 8fe7c13c27 meson: Ensure lzfse ends up in .pc
To fix linking in static consumers when the Barebone backend is enabled.
2025-09-15 22:08:45 +02:00
vinaykumar🍁 123ea7c004 fruity: Fall back to usbmux when tunnel times out
And other such network-/transport-errors.
2025-09-15 21:39:33 +02:00
Ole André Vadla Ravnås 3968ac635b subprojects: Bump outdated 2025-09-11 10:31:54 +02:00
Håvard Sørbø bc8c7cde1b fruity: Handle CoreDevice pairing events
Our request_pairing_data() was previously assuming request/response
semantics, which is incorrect.
2025-09-11 10:19:22 +02:00
Håvard Sørbø 7c3b4e900c fruity: Match pairing request/response using FIFO
Instead of using the sequence number.
2025-09-11 10:19:22 +02:00
Francesco Tamagni bf7558197c fruity: Handle edge cases on teardown 2025-09-11 09:01:11 +02:00
Ole André Vadla RavnåsandHåvard Sørbø e95fea688b barebone: Add basic support for XNU injection
Tested on iOS 14.0 running in QEMU. For now this depends on my
micro-fork of QEMUAppleSilicon, which adds the hostlink device
that we depend on for communicating with the agent.

Co-authored-by: Håvard Sørbø <[email protected]>
2025-09-08 12:42:07 +02:00
Ole André Vadla Ravnås 8cbbe21192 barebone: Expose underscore-prefixed CModule syms 2025-09-08 12:42:06 +02:00
Ole André Vadla Ravnås a49f5e4b26 meson: Require json-glib >= 1.8
Needed for Json.Reader.get_current_node().
2025-09-08 12:42:06 +02:00
Ole André Vadla Ravnås 87b3c76fa2 subprojects: Prepare for release 2025-08-20 12:07:16 +02:00
Ole André Vadla Ravnås 9c3cfeda62 subprojects: Bump outdated 2025-08-20 12:06:02 +02:00
91 changed files with 11324 additions and 758 deletions

No files matched your search

+2
View File
@@ -1,5 +1,7 @@
/build/
/deps/
/src/barebone/agent/target/
/src/barebone/agent/xpacks/
/src/barebone/helpers/memory-scanner/target/
/src/barebone/helpers/target/
/src/barebone/node_modules/
-23
View File
@@ -11,29 +11,6 @@ namespace Frida {
return (linux_major == major && linux_minor >= minor) || linux_major > major;
}
public class FileDescriptor : Object, FileDescriptorBased {
public int handle;
public FileDescriptor (int handle) {
this.handle = handle;
}
~FileDescriptor () {
if (handle != -1)
Posix.close (handle);
}
public int steal () {
int result = handle;
handle = -1;
return result;
}
public int get_fd () {
return handle;
}
}
public sealed class PidFileDescriptor : FileDescriptor {
private uint pid;
+12 -1
View File
@@ -19,6 +19,13 @@ base_sources = [
extra_vala_args = []
if host_os == 'windows'
base_sources += 'windows.vala'
else
base_sources += 'posix.vala'
extra_vala_args += '--pkg=posix'
endif
if host_os == 'macos'
base_sources += [
'xpc.vala',
@@ -28,6 +35,7 @@ endif
if host_os_family == 'darwin'
base_sources += 'corefoundation.vala'
extra_vala_args += '--pkg=darwin-xnu'
endif
if host_os_family == 'linux'
@@ -35,10 +43,13 @@ if host_os_family == 'linux'
extra_vala_args += [
'--pkg=linux',
'--pkg=linux-system',
'--pkg=posix',
]
endif
if host_os_family == 'freebsd'
extra_vala_args += '--pkg=freebsd'
endif
extra_deps = []
if host_os_family != 'windows'
extra_vala_args += '--pkg=gio-unix-2.0'
+153
View File
@@ -0,0 +1,153 @@
namespace Frida {
public sealed class StdioPipes : Object {
public OutputStream? input {
get;
construct;
}
public InputStream output {
get;
construct;
}
public InputStream error {
get;
construct;
}
public StdioPipes (FileDescriptor? input, FileDescriptor output, FileDescriptor error) {
Object (
input: (input != null) ? new UnixOutputStream (ensure_nonblocking (input.steal ()), true) : null,
output: new UnixInputStream (ensure_nonblocking (output.steal ()), true),
error: new UnixInputStream (ensure_nonblocking (error.steal ()), true)
);
}
private static int ensure_nonblocking (int fd) {
try {
Unix.set_fd_nonblocking (fd, true);
return fd;
} catch (GLib.Error e) {
assert_not_reached ();
}
}
}
public class FileDescriptor : Object, FileDescriptorBased {
public int handle;
public FileDescriptor (int handle) {
this.handle = handle;
}
~FileDescriptor () {
if (handle != -1)
Posix.close (handle);
}
public int steal () {
int result = handle;
handle = -1;
return result;
}
public int get_fd () {
return handle;
}
}
public StdioPipes? make_stdio_pipes (Stdio stdio, bool in_supported, out FileDescriptor? in_fd, out string? in_name,
out FileDescriptor? out_fd, out string? out_name, out FileDescriptor? err_fd, out string? err_name) throws Error {
if (stdio == PIPE) {
FileDescriptor? in_write = null;
FileDescriptor? out_read, err_read;
if (in_supported) {
make_stdio_pipe (out in_fd, out in_write, out in_name);
} else {
in_fd = null;
in_name = null;
}
make_stdio_pipe (out out_read, out out_fd, out out_name);
make_stdio_pipe (out err_read, out err_fd, out err_name);
return new StdioPipes (in_write, out_read, err_read);
} else {
in_fd = null;
in_name = null;
out_fd = null;
out_name = null;
err_fd = null;
err_name = null;
return null;
}
}
public void make_stdio_pipe (out FileDescriptor read, out FileDescriptor write, out string? name = null) throws Error {
#if HAVE_OPENPTY
int rfd = -1, wfd = -1;
char buf[Posix.Limits.PATH_MAX];
var res =
#if DARWIN
Darwin.XNU.openpty (out rfd, out wfd, buf, null, null)
#elif LINUX
Linux.openpty (out rfd, out wfd, buf, null, null)
#elif FREEBSD
FreeBSD.openpty (out rfd, out wfd, buf, null, null)
#endif
;
if (res == -1)
throw new Error.NOT_SUPPORTED ("Unable to open PTY: %s", strerror (errno));
name = (string) buf;
enable_close_on_exec (rfd);
enable_close_on_exec (wfd);
disable_sigpipe (rfd);
disable_sigpipe (wfd);
configure_terminal_attributes (rfd);
read = new FileDescriptor (rfd);
write = new FileDescriptor (wfd);
#else
name = null;
try {
int fds[2];
Unix.open_pipe (fds, Posix.FD_CLOEXEC);
read = new FileDescriptor (fds[0]);
write = new FileDescriptor (fds[1]);
} catch (GLib.Error e) {
throw new Error.NOT_SUPPORTED ("Unable to open pipe: %s", e.message);
}
#endif
}
#if HAVE_OPENPTY
private void enable_close_on_exec (int fd) {
Posix.fcntl (fd, Posix.F_SETFD, Posix.fcntl (fd, Posix.F_GETFD) | Posix.FD_CLOEXEC);
}
private void disable_sigpipe (int fd) {
#if DARWIN
Posix.fcntl (fd, Darwin.XNU.F_SETNOSIGPIPE, true);
#endif
}
private void configure_terminal_attributes (int fd) {
var tios = Posix.termios ();
Posix.tcgetattr (fd, out tios);
tios.c_oflag &= ~Posix.ONLCR;
tios.c_cflag = (tios.c_cflag & Posix.CLOCAL) | Posix.CS8 | Posix.CREAD | Posix.HUPCL;
tios.c_lflag &= ~Posix.ECHO;
Posix.tcsetattr (fd, 0, tios);
}
#endif
}
+16
View File
@@ -57,6 +57,22 @@ namespace Frida {
push_scope (v);
}
public string[] list_members () throws Error {
var scope = peek_scope ();
if (scope.dict == null)
throw new Error.PROTOCOL ("Dictionary expected, but at %s", scope.val.print (true));
var members = new Gee.ArrayList<string> ();
foreach (var item in scope.val) {
string k;
Variant v;
item.get ("{sv}", out k, out v);
members.add (k);
}
return members.to_array ();
}
public bool has_member (string name) throws Error {
var scope = peek_scope ();
if (scope.dict == null)
+22
View File
@@ -0,0 +1,22 @@
namespace Frida {
public sealed class StdioPipes : Object {
public OutputStream input {
get;
construct;
}
public InputStream output {
get;
construct;
}
public InputStream error {
get;
construct;
}
public StdioPipes (OutputStream input, InputStream output, InputStream error) {
Object (input: input, output: output, error: error);
}
}
}
+18 -6
View File
@@ -320,7 +320,7 @@ gobject_dep = dependency('gobject-2.0')
gmodule_dep = dependency('gmodule-2.0')
gio_dep = dependency('gio-2.0')
gee_dep = dependency('gee-0.8', default_options: gee_options)
json_glib_dep = dependency('json-glib-1.0', default_options: [
json_glib_dep = dependency('json-glib-1.0', version: '>=1.8', default_options: [
'introspection=disabled',
'gtk_doc=disabled',
'tests=false',
@@ -344,6 +344,7 @@ quickjs_dep = dependency('quickjs', required: get_option('barebone_backend'), de
'atomics=disabled',
'stack_check=disabled',
])
lzfse_dep = dependency('lzfse', required: get_option('barebone_backend'))
native_glib_dep = dependency('glib-2.0', version: '>=2.74', native: true, default_options: glib_options)
native_gio_dep = dependency('gio-2.0', native: true)
@@ -402,6 +403,10 @@ else
endif
have_local_backend = get_option('local_backend').allowed()
have_simmy_backend = get_option('simmy_backend') \
.disable_auto_if(host_os != 'macos') \
.require(host_os == 'macos', error_message: 'Simmy backend is only available on macOS') \
.allowed()
have_fruity_backend = get_option('fruity_backend') \
.disable_auto_if(not host_docks_mobile_devices) \
.allowed()
@@ -410,7 +415,7 @@ have_droidy_backend = get_option('droidy_backend') \
.allowed()
have_socket_backend = get_option('socket_backend').allowed()
have_barebone_backend = get_option('barebone_backend') \
.disable_if(not quickjs_dep.found()) \
.disable_if(not (quickjs_dep.found() and lzfse_dep.found())) \
.disable_auto_if(not host_docks_mobile_devices) \
.allowed()
compiler_backend_opt = get_option('compiler_backend') \
@@ -490,6 +495,7 @@ build_tests = get_option('tests') \
.allowed()
foreach b : [['local', have_local_backend],
['simmy', have_simmy_backend],
['fruity', have_fruity_backend],
['droidy', have_droidy_backend],
['socket', have_socket_backend],
@@ -560,9 +566,6 @@ if have_fruity_backend
'dns=disabled',
'arp=disabled',
'ethernet=enabled',
'tcp_mss=4036',
'tcp_snd_buf=65535',
'tcp_wnd=65535',
]
if optimize_for_prod
lwip_options += 'lwip_debug=disabled'
@@ -578,7 +581,12 @@ if quickjs_dep.found()
backend_reqs_private += 'quickjs'
endif
if host_os_family == 'linux' and cc.has_function('openpty', prefix: '#include <pty.h>')
if lzfse_dep.found()
backend_deps_private += lzfse_dep
backend_reqs_private += 'lzfse'
endif
if host_os_family in ['darwin', 'freebsd'] or (host_os_family == 'linux' and cc.has_function('openpty', prefix: '#include <pty.h>'))
vala_flags += '--define=HAVE_OPENPTY'
endif
@@ -650,6 +658,9 @@ endif
if host_os in ['ios', 'tvos']
backend_libs_private += ['-Wl,-framework,CoreGraphics', '-Wl,-framework,UIKit']
endif
if host_os_family == 'freebsd'
backend_libs_private += ['-lutil']
endif
if get_option('devkits').length() != 0
if get_option('default_library') != 'static'
@@ -783,6 +794,7 @@ endif
summary(
{
'local': have_local_backend,
'simmy': have_simmy_backend,
'fruity': have_fruity_backend,
'droidy': have_droidy_backend,
'socket': have_socket_backend,
+6
View File
@@ -10,6 +10,12 @@ option('local_backend',
description: 'Include backend for the local system',
)
option('simmy_backend',
type: 'feature',
value: 'auto',
description: 'Include the iDevice Simulator backend',
)
option('fruity_backend',
type: 'feature',
value: 'auto',
+1 -1
Submodule releng updated: 2b34e1c008...800f2385c3.
+3 -1
View File
@@ -238,7 +238,7 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path) -> st
for elem in core_elements + base_elements:
if tag_name == "class":
for child in list(elem):
if (child.tag == CORE_TAG_IMPLEMENTS and child.get("name") == "FridaBase.AgentMessageSink") \
if (child.tag == CORE_TAG_IMPLEMENTS and child.get("name") in {"Frida.HostSessionHub", "FridaBase.AgentMessageSink"}) \
or child.tag == CORE_TAG_FIELD \
or child.get("name").startswith("_"):
elem.remove(child)
@@ -512,6 +512,8 @@ def function_is_public(name):
"parse_socket_address",
"negotiate_connection",
"check_kernel_version",
"make_stdio_pipes",
"make_stdio_pipe",
]
def parse_vala_object_types(source) -> List[ApiObjectType]:
+503
View File
@@ -0,0 +1,503 @@
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
namespace Frida.Barebone {
public sealed class AgentConnection : Object, AsyncInitable {
public signal void script_message (AgentScriptId id, string json, Bytes? data);
private Cancellable io_cancellable = new Cancellable ();
private SocketConnection hostlink;
private BufferedInputStream input;
private OutputStream output;
private AgentConfig agent_config;
private ImageConfig? image_config;
private Machine machine;
private Allocator allocator;
private Allocation elf_allocation;
private Allocation config_allocation;
private Callback mprotect_callback;
private Callback remap_writable_pages_callback;
private Gee.Map<uint16, Promise<Variant>> pending_requests = new Gee.HashMap<uint16, Promise<Variant>> ();
private uint16 next_request_id = 1;
private const int COMMAND_TIMEOUT_MS = 25000;
public static async AgentConnection open (AgentConfig agent_config, ImageConfig? image_config, Machine machine,
Allocator allocator, Cancellable? cancellable) throws Error, IOError {
var connection = new AgentConnection () {
agent_config = agent_config,
image_config = image_config,
machine = machine,
allocator = allocator,
};
try {
yield connection.init_async (Priority.DEFAULT, cancellable);
} catch (GLib.Error e) {
throw_api_error (e);
}
return connection;
}
private async bool init_async (int io_priority, Cancellable? cancellable) throws Error, IOError {
HostlinkTransportConfig? transport_config = agent_config.transport as HostlinkTransportConfig;
if (transport_config == null)
throw new Error.NOT_SUPPORTED ("Unsupported transport config: only hostlink is supported for now");
var qmp = yield QmpClient.open (transport_config.qmp, 0, cancellable);
var link = yield qmp.open_hostlink (cancellable);
hostlink = link.connection;
input = (BufferedInputStream) Object.new (typeof (BufferedInputStream),
"base-stream", hostlink.get_input_stream (),
"close-base-stream", false,
"buffer-size", 128 * 1024);
output = hostlink.get_output_stream ();
var gdb = machine.gdb;
ByteOrder byte_order = gdb.byte_order;
uint pointer_size = gdb.pointer_size;
uint64 kernel_base;
Layout layout;
if (image_config != null) {
kernel_base = image_config.base.address;
layout = yield Layout.load_from_symbol_source (File.new_for_path (image_config.file), kernel_base,
byte_order, pointer_size, cancellable);
} else {
kernel_base = 0;
layout = new Layout.empty ();
}
if (kernel_base == 0)
throw new Error.NOT_SUPPORTED ("Missing kernel_base");
var symbols = new Gee.HashMap<string, SymbolInfo> ();
var hash_builder = new SymbolHashBuilder ();
foreach (var s in layout.symbols) {
symbols[s.name] = s;
hash_builder.add_symbol (s);
}
if (image_config != null) {
foreach (var e in image_config.symbols.entries) {
unowned string name = e.key;
if (!symbols.has_key (name)) {
var s = new SymbolInfo () {
name = name,
offset = (uint32) e.value,
symbol_type = 0xf,
section = 0x10, // FIXME
};
symbols[name] = s;
hash_builder.add_symbol (s);
}
}
}
SymbolInfo? thread_block = symbols["thread_block"];
if (thread_block == null)
throw new Error.NOT_SUPPORTED ("Missing symbol for thread_block");
var config_builder = new VariantBuilder (new VariantType ("(tuta(ssuuuu)ay)"));
config_builder.add ("t", link.mmio);
config_builder.add ("u", link.irq);
config_builder.add ("t", kernel_base);
config_builder.open (new VariantType ("a(ssuuuu)"));
foreach (var m in layout.modules) {
config_builder.add ("(ssuuuu)",
m.name,
m.version,
m.offset,
m.size,
m.start_func_offset,
m.stop_func_offset
);
}
config_builder.close ();
Bytes symbol_data = hash_builder.build (byte_order);
config_builder.add_value (Variant.new_from_data (new VariantType ("ay"), symbol_data.get_data (), true,
symbol_data));
Gum.ElfModule elf;
try {
elf = new Gum.ElfModule.from_file (agent_config.path);
} catch (Gum.Error e) {
throw new Error.INVALID_ARGUMENT ("%s", e.message);
}
var raw_elf = gdb.make_buffer (new Bytes (elf.get_file_data ()));
var missing_symbols = new Gee.ArrayList<string> ();
elf.enumerate_symbols (s => {
unowned Gum.ElfSectionDetails? sect = s.section;
if (sect != null && sect.name == ".kernel_addrs") {
string name = s.name[1:];
SymbolInfo? info = symbols[name];
if (info != null) {
size_t file_offset = (size_t) (sect.offset + (s.address - sect.address));
raw_elf.write_pointer (file_offset, kernel_base + info.offset);
} else {
missing_symbols.add (name);
}
}
return true;
});
if (!missing_symbols.is_empty) {
throw new Error.INVALID_ARGUMENT ("Missing symbols for: %s",
string.joinv (", ", missing_symbols.to_array ()));
}
yield machine.enter_exception_level (1, 1000, cancellable);
var bp = yield gdb.add_breakpoint (SOFT, kernel_base + thread_block.offset, 4, cancellable);
GDB.Breakpoint? hit_breakpoint = null;
do {
var exception = yield gdb.continue_until_exception (cancellable);
hit_breakpoint = exception.breakpoint;
} while (hit_breakpoint != bp);
yield bp.remove (cancellable);
size_t page_size = yield machine.query_page_size (cancellable);
elf_allocation = yield inject_elf (elf, raw_elf.bytes, page_size, machine, allocator, cancellable);
uint64 start_address = 0;
uint64 remap_writable_pages_address = 0;
uint64 mprotect_address = 0;
uint64 base_va = elf_allocation.virtual_address;
elf.enumerate_symbols (e => {
if (e.name == "_start")
start_address = base_va + e.address;
else if (e.name == "gum_barebone_try_remap_writable_pages")
remap_writable_pages_address = base_va + e.address;
else if (e.name == "gum_try_mprotect")
mprotect_address = base_va + e.address;
else
return true;
return start_address == 0 || mprotect_address == 0 || remap_writable_pages_address == 0;
});
if (start_address == 0)
throw new Error.INVALID_ARGUMENT ("Invalid agent: no _start symbol found");
if (remap_writable_pages_address == 0)
throw new Error.INVALID_ARGUMENT ("Invalid agent: no gum_barebone_try_remap_writable_pages symbol found");
if (mprotect_address == 0)
throw new Error.INVALID_ARGUMENT ("Invalid agent: no gum_try_mprotect symbol found");
remap_writable_pages_callback = yield new Callback (remap_writable_pages_address,
new RemapWritablePagesHandler (machine), machine, cancellable);
mprotect_callback = yield new Callback (mprotect_address, new MemoryProtectHandler (machine), machine, cancellable);
var config_blob = config_builder.end ().get_data_as_bytes ();
config_allocation = yield allocator.allocate (config_blob.get_size (), 8, cancellable);
yield gdb.write_byte_array (config_allocation.virtual_address, config_blob, cancellable);
yield machine.invoke (start_address, {
config_allocation.virtual_address,
config_allocation.size
},
cancellable);
yield gdb.continue (cancellable);
process_incoming_messages.begin ();
return true;
}
public async void close (Cancellable? cancellable) throws IOError {
io_cancellable.cancel ();
}
public async AgentScriptId create_script (string source, Cancellable? cancellable) throws Error, IOError {
var payload = new Variant ("s", source);
var response = yield execute_command (Command.CREATE_SCRIPT, payload, cancellable);
if (!response.check_format_string ("u", false))
throw new Error.PROTOCOL ("Invalid create_script response format");
uint32 script_handle;
response.get ("u", out script_handle);
return AgentScriptId (script_handle);
}
public async void load_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
var payload = new Variant ("u", script_id.handle);
yield execute_command (Command.LOAD_SCRIPT, payload, cancellable);
}
public async void destroy_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
var payload = new Variant ("u", script_id.handle);
yield execute_command (Command.DESTROY_SCRIPT, payload, cancellable);
}
public async void post_script_message (AgentScriptId script_id, string message, Bytes? data, Cancellable? cancellable)
throws Error, IOError {
var payload = new Variant ("(us)", script_id.handle, message);
// TODO: Include data.
yield execute_command (Command.POST_SCRIPT_MESSAGE, payload, cancellable);
}
private async Variant execute_command (Command command, Variant payload, Cancellable? cancellable) throws Error, IOError {
uint16 request_id = next_request_id++;
var command_message = new Variant ("(yqv)", (uint8) command, request_id, payload);
if (machine.gdb.byte_order != ByteOrder.HOST)
command_message = command_message.byteswap ();
var command_bytes = command_message.get_data_as_bytes ();
var builder = machine.gdb.make_buffer_builder ();
Bytes frame = builder
.append_uint32 ((uint32) command_bytes.get_size ())
.append_bytes (command_bytes)
.build ();
var promise = new Promise<Variant> ();
pending_requests[request_id] = promise;
try {
yield output.write_all_async (frame.get_data (), Priority.DEFAULT, cancellable, null);
} catch (GLib.Error e) {
pending_requests.unset (request_id);
throw new Error.TRANSPORT ("%s", e.message);
}
var timeout_source = new TimeoutSource (COMMAND_TIMEOUT_MS);
timeout_source.set_callback (() => {
Promise<Variant>? p;
if (pending_requests.unset (request_id, out p))
p.reject (new Error.TIMED_OUT ("Command timed out"));
return Source.REMOVE;
});
timeout_source.attach (MainContext.get_thread_default ());
try {
return yield promise.future.wait_async (cancellable);
} finally {
timeout_source.destroy ();
}
}
private async void process_incoming_messages () {
var byte_order = machine.gdb.byte_order;
try {
while (true) {
size_t header_size = 4;
if (input.get_available () < header_size)
yield fill_until_n_bytes_available (header_size);
uint32 body_size = 0;
unowned uint8[] size_buf = ((uint8[]) &body_size)[:4];
input.peek (size_buf);
body_size = uint32.from_little_endian (body_size);
size_t full_size = header_size + body_size;
if (input.get_available () < full_size)
yield fill_until_n_bytes_available (full_size);
var body = new uint8[body_size];
input.peek (body, header_size);
input.skip (full_size, io_cancellable);
var raw_message = new Bytes.take ((owned) body);
var message = Variant.new_from_data (new VariantType ("(yqv)"), raw_message.get_data (), false,
raw_message);
if (byte_order != ByteOrder.HOST)
message = message.byteswap ();
if (!message.check_format_string ("(yqv)", false))
throw new Error.PROTOCOL ("Invalid message format");
uint8 command_code;
uint16 request_id;
Variant payload;
message.get ("(yqv)", out command_code, out request_id, out payload);
if (command_code == Command.SCRIPT_MESSAGE) {
if (!payload.check_format_string ("(us)", false))
throw new Error.PROTOCOL ("Invalid script message payload format");
uint32 script_handle;
unowned string json;
payload.get ("(u&s)", out script_handle, out json);
script_message (AgentScriptId (script_handle), json, null);
} else if (command_code == Command.REPLY) {
Promise<Variant>? promise;
if (pending_requests.unset (request_id, out promise))
promise.resolve (payload);
}
}
} catch (GLib.Error e) {
}
}
private async void fill_until_n_bytes_available (size_t minimum) throws Error, IOError {
size_t available = input.get_available ();
while (available < minimum) {
if (input.get_buffer_size () < minimum)
input.set_buffer_size (minimum);
ssize_t n;
try {
n = yield input.fill_async ((ssize_t) (input.get_buffer_size () - available), Priority.DEFAULT,
io_cancellable);
} catch (GLib.Error e) {
throw new Error.TRANSPORT ("Connection closed");
}
if (n == 0)
throw new Error.TRANSPORT ("Connection closed");
available += n;
}
}
private class RemapWritablePagesHandler : Object, CallbackHandler {
public signal void output (string message);
public uint arity {
get { return 2; }
}
private Machine machine;
public RemapWritablePagesHandler (Machine machine) {
this.machine = machine;
}
public async uint64 handle_invocation (uint64[] args, CallFrame frame, Cancellable? cancellable)
throws Error, IOError {
var pages = args[0];
var num_pages = (uint) args[1];
var physical_addresses = new Gee.ArrayList<uint64?> ();
var gdb = machine.gdb;
var reader = new BufferReader (yield gdb.read_buffer (pages, num_pages * gdb.pointer_size, cancellable));
for (uint i = 0; i != num_pages; i++)
physical_addresses.add (reader.read_pointer ());
Allocation allocation = yield machine.allocate_pages (physical_addresses, cancellable);
// TODO: Handle cleanup.
return allocation.virtual_address;
}
}
private class MemoryProtectHandler : Object, CallbackHandler {
public signal void output (string message);
public uint arity {
get { return 3; }
}
private Machine machine;
public MemoryProtectHandler (Machine machine) {
this.machine = machine;
}
public async uint64 handle_invocation (uint64[] args, CallFrame frame, Cancellable? cancellable)
throws Error, IOError {
var address = args[0];
var size = (size_t) args[1];
var prot = (Gum.PageProtection) args[2];
try {
yield machine.protect_pages (address, size, prot, cancellable);
return 1;
} catch (GLib.Error e) {
return 0;
}
}
}
private enum Command {
CREATE_SCRIPT = 1,
LOAD_SCRIPT = 2,
DESTROY_SCRIPT = 3,
POST_SCRIPT_MESSAGE = 4,
REPLY = 128,
SCRIPT_MESSAGE = 129
}
private enum Status {
IDLE,
BUSY,
DATA_READY,
ERROR
}
}
private class SymbolHashBuilder : Object {
private Gee.Map<string, Gee.List<SymbolInfo>> symbol_table = new Gee.TreeMap<string, Gee.List<SymbolInfo>> ();
public void add_symbol (SymbolInfo symbol) {
var symbol_list = symbol_table[symbol.name];
if (symbol_list == null) {
symbol_list = new Gee.ArrayList<SymbolInfo> ();
symbol_table[symbol.name] = symbol_list;
}
symbol_list.add (symbol);
}
public Bytes build (ByteOrder byte_order) {
var builder = new BufferBuilder (byte_order);
var all_symbols = new Gee.ArrayList<SymbolInfo> ();
foreach (var entry in symbol_table.entries) {
foreach (var symbol in entry.value)
all_symbols.add (symbol);
}
uint total_symbols = all_symbols.size;
builder.append_uint32 (total_symbols);
var name_index_offset = builder.offset;
builder.skip (total_symbols * 4);
var addr_index_offset = builder.offset;
builder.skip (total_symbols * 4);
var symbol_offsets = new uint32[total_symbols];
for (uint i = 0; i != total_symbols; i++) {
var symbol = all_symbols[(int) i];
builder.align (4);
symbol_offsets[i] = (uint32) builder.offset;
builder.append_uint32 (symbol.offset);
// TODO: Only include details we need.
builder.append_uint8 (symbol.symbol_type);
builder.append_uint8 (symbol.section);
builder.append_uint16 (symbol.description);
builder.append_string (symbol.name, StringTerminator.NUL);
}
for (uint i = 0; i != total_symbols; i++)
builder.write_uint32 (name_index_offset + (i * 4), symbol_offsets[i]);
var addr_sorted_symbols = new Gee.ArrayList<int> ();
for (uint i = 0; i != total_symbols; i++)
addr_sorted_symbols.add ((int) i);
addr_sorted_symbols.sort ((a, b) => {
var symbol_a = all_symbols[a];
var symbol_b = all_symbols[b];
if (symbol_a.offset < symbol_b.offset)
return -1;
if (symbol_a.offset > symbol_b.offset)
return 1;
return 0;
});
for (uint i = 0; i != total_symbols; i++) {
int original_index = addr_sorted_symbols[(int) i];
uint symbol_data_offset = symbol_offsets[original_index];
builder.write_uint32 (addr_index_offset + (i * 4), symbol_data_offset);
}
return builder.build ();
}
}
}
+2
View File
@@ -0,0 +1,2 @@
[build]
target = "aarch64-unknown-none"
+296
View File
@@ -0,0 +1,296 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "bindgen"
version = "0.72.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f"
dependencies = [
"bitflags",
"cexpr",
"clang-sys",
"itertools",
"log",
"prettyplease",
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"shlex",
"syn",
]
[[package]]
name = "bitflags"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
[[package]]
name = "cc"
version = "1.2.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc"
dependencies = [
"shlex",
]
[[package]]
name = "cexpr"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
dependencies = [
"nom",
]
[[package]]
name = "cfg-if"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "clang-sys"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
"libloading",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "frida-barebone-agent"
version = "1.0.0"
dependencies = [
"bindgen",
"cc",
]
[[package]]
name = "glob"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]]
name = "libc"
version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "libloading"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07033963ba89ebaf1584d767badaa2e8fcec21aedea6b8c0346d487d49c28667"
dependencies = [
"cfg-if",
"windows-targets",
]
[[package]]
name = "log"
version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "memchr"
version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "prettyplease"
version = "0.2.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a"
dependencies = [
"proc-macro2",
"syn",
]
[[package]]
name = "proc-macro2"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
version = "2.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "windows-targets"
version = "0.53.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c66f69fcc9ce11da9966ddb31a40968cad001c5bedeb5c2b82ede4253ab48aef"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
[[package]]
name = "windows_i686_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
[[package]]
name = "windows_i686_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
+19
View File
@@ -0,0 +1,19 @@
[package]
name = "frida-barebone-agent"
version = "1.0.0"
edition = "2024"
build = "build.rs"
[profile.release]
panic = "abort"
opt-level = "z"
overflow-checks = false
lto = true
codegen-units = 1
[profile.dev]
panic = "abort"
[build-dependencies]
cc = "1.2"
bindgen = "0.72.0"
+43
View File
@@ -0,0 +1,43 @@
## Getting the C toolchain
npm install -g xpm
xpm install
export PATH=$PWD/xpacks/.bin:$PATH
## How to build Gum
./configure \
--host=aarch64-none-elf \
--enable-gumjs \
--with-devkits=gum,gumjs \
--with-devkit-symbol-scope=original
make
export GUMJS_DEVKIT_DIR=$PWD/build/bindings/gumjs/devkit
## Building
export PATH=$PWD/xpacks/.bin:$PATH
export CC_aarch64_unknown_none=aarch64-none-elf-gcc
export AR_aarch64_unknown_none=aarch64-none-elf-ar
export RANLIB_aarch64_unknown_none=aarch64-none-elf-ranlib
## Development loop
export FRIDA_BAREBONE_CONFIG=$PWD/etc/xnu.json
cargo build --release && make -C ~/src/frida-python && killall -9 qemu-system-aarch64 && sleep 2 && frida -D barebone -p 0
## Speeding up loop
./configure \
-- \
-Dfrida-core:compat=disabled \
-Dfrida-core:local_backend=disabled \
-Dfrida-core:fruity_backend=disabled \
-Dfrida-core:droidy_backend=disabled \
-Dfrida-core:socket_backend=disabled \
-Dfrida-core:compiler_backend=disabled \
-Dfrida-core:gadget=disabled \
-Dfrida-core:server=disabled \
-Dfrida-core:portal=disabled \
-Dfrida-core:inject=disabled \
-Dfrida-core:tests=enabled
+51
View File
@@ -0,0 +1,51 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(8) {
. = ALIGN(8);
_panic = .; . += 8;
_IOLog = .; . += 8;
_kalloc = .; . += 8;
_kfree = .; . += 8;
_kernel_thread_start = .; . += 8;
_assert_wait = .; . += 8;
_assert_wait_timeout = .; . += 8;
_thread_block = .; . += 8;
_thread_wakeup = .; . += 8;
_mach_absolute_time = .; . += 8;
_absolutetime_to_nanoseconds = .; . += 8;
_clock_get_calendar_microtime = .; . += 8;
_ml_io_map = .; . += 8;
_ml_vtophys = .; . += 8;
__ZN9IOService11getPlatformEv = .; . += 8;
__ZN9IOServiceC2Ev = .; . += 8;
__ZN8OSSymbol17withCStringNoCopyEPKc = .; . += 8;
__ZN6OSData9withBytesEPKvj = .; . += 8;
}
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.bss : {
*(.bss*)
}
. = ALIGN(8);
_heap_start = .;
}
VERSION {
{
global:
_start;
local:
*;
};
}
+126
View File
@@ -0,0 +1,126 @@
use std::{
env, fs,
path::{Path, PathBuf},
process::{Command, Stdio},
};
fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let devkit_dir = PathBuf::from(env::var("GUMJS_DEVKIT_DIR").unwrap());
let cc_str = env::var("CC_aarch64_unknown_none")
.or_else(|_| env::var("CC"))
.unwrap_or_else(|_| "cc".to_string());
let cc = Path::new(&cc_str);
let cc_include_paths = detect_gcc_include_paths(cc);
let cc_library_paths = detect_gcc_library_paths(cc);
let cc_clang_args: Vec<String> = cc_include_paths
.iter()
.flat_map(|path| vec!["-isystem".to_string(), path.to_string_lossy().into_owned()])
.collect();
let bindings = bindgen::Builder::default()
.use_core()
.header(devkit_dir.join("frida-gumjs.h").to_str().unwrap())
.clang_arg("-nostdinc")
.clang_arg("-target")
.clang_arg("aarch64-none-elf")
.clang_args(cc_clang_args)
.clang_arg(format!("-I{}", devkit_dir.to_str().unwrap()))
.merge_extern_blocks(true)
.generate()
.expect("Unable to generate bindings");
bindings
.write_to_file(out_dir.join("bindings.rs"))
.expect("Couldn't write bindings");
println!(
"cargo:rustc-link-search=native={}",
devkit_dir.to_str().unwrap()
);
println!("cargo:rustc-link-lib=static=frida-gumjs");
for path in cc_library_paths {
println!("cargo:rustc-link-search=native={}", path.to_string_lossy());
}
println!("cargo:rustc-link-lib=static=c");
println!("cargo:rustc-link-lib=static=m");
println!("cargo:rustc-link-arg=--export-dynamic");
println!("cargo:rustc-link-arg=--emit-relocs");
println!("cargo:rustc-link-arg=--script=agent.lds");
println!("cargo:rustc-link-arg=--gc-sections");
println!("cargo:rerun-if-changed=build.rs");
}
pub fn detect_gcc_include_paths(gcc: &Path) -> Vec<PathBuf> {
let out = Command::new(gcc)
.args(["-xc", "-E", "-v", "-"])
.stdin(Stdio::null())
.output()
.expect("Failed to execute GCC to detect include paths");
let stderr = String::from_utf8_lossy(&out.stderr);
let mut grab = false;
let mut paths = Vec::<PathBuf>::new();
for line in stderr.lines() {
if line.starts_with("#include <...> search starts here:") {
grab = true;
continue;
}
if line.starts_with("End of search list.") {
break;
}
if !grab {
continue;
}
let raw = line.trim();
if raw.is_empty() {
continue;
}
let p = Path::new(raw);
if p.exists() {
let canonical = fs::canonicalize(p).expect("Failed to canonicalize include path");
if !paths.contains(&canonical) {
paths.push(canonical);
}
}
}
paths
}
pub fn detect_gcc_library_paths(gcc: &Path) -> Vec<PathBuf> {
let out = Command::new(gcc).arg("-print-search-dirs").output()
.expect("Failed to execute GCC to detect library paths");
let stdout = String::from_utf8_lossy(&out.stdout);
let line = stdout.lines().find(|l| l.starts_with("libraries:"))
.expect("Failed to find libraries line in GCC output");
let raw_dirs = line.trim_start_matches("libraries: =");
let sep = if cfg!(windows) { ';' } else { ':' };
let mut paths = Vec::new();
for raw in raw_dirs.split(sep) {
if raw.is_empty() {
continue;
}
let p = Path::new(raw);
if !p.exists() {
continue;
}
let canonical = fs::canonicalize(p).expect("Failed to canonicalize library path");
if !paths.contains(&canonical) {
paths.push(canonical);
}
}
paths
}
+8
View File
@@ -0,0 +1,8 @@
export FRIDA_BAREBONE_CONFIG=$PWD/etc/xnu.json
export PYTHONPATH=$HOME/src/frida-python
export PATH=$PWD/xpacks/.bin:$PATH
export CC_aarch64_unknown_none=aarch64-none-elf-gcc
export AR_aarch64_unknown_none=aarch64-none-elf-ar
export RANLIB_aarch64_unknown_none=aarch64-none-elf-ranlib
export GUMJS_DEVKIT_DIR=/home/oleavr/src/frida-gum/build/bindings/gumjs/devkit
+6
View File
@@ -0,0 +1,6 @@
{
"connection": {
"host": "127.0.0.1",
"port": 9000
}
}
+18
View File
@@ -0,0 +1,18 @@
{
"connection": {
"host": "127.0.0.1",
"port": 9000
},
"allocator": {
"mode": "physical",
"physical_base": "0x8ec9b4000"
},
"agent": {
"path": "/Users/oleavr/src/frida-core/src/barebone/agent/target/aarch64-unknown-none/debug/frida-barebone-agent",
"transport": {
"type": "ram",
"path": "/Volumes/RAM Disk/ios-dram",
"base_address": "0x800000000"
}
}
}
+42
View File
@@ -0,0 +1,42 @@
{
"connection": {
"host": "127.0.0.1",
"port": 9000
},
"allocator": {
"mode": "target-functions",
"alloc_function": "0xfffffff007a3c278",
"free_function": "0xfffffff007a3c338"
},
"agent": {
"path": "/Users/oleavr/src/frida-core/src/barebone/agent/target/aarch64-unknown-none/release/frida-barebone-agent",
"transport": {
"type": "hostlink",
"qmp": "unix:/Users/oleavr/src/frida-barebone-ios/qmp.sock"
}
},
"image": {
"file": "/Users/oleavr/src/frida-barebone-ios/iPhone11_8_iPhone12_1_14.0_18A5351d_Restore/kernelcache.research.iphone12b",
"base": "0xfffffff007004000",
"symbols": {
"IOLog": "0xfedb68",
"_ZN6OSData9withBytesEPKvj": "0xf88588",
"_ZN8OSSymbol17withCStringNoCopyEPKc": "0xfc05dc",
"_ZN9IOService11getPlatformEv": "0x101ad48",
"_ZN9IOServiceC2Ev": "0x1018318",
"absolutetime_to_nanoseconds": "0xb5d1e4",
"assert_wait": "0xa4d294",
"assert_wait_timeout": "0xa4d430",
"clock_get_calendar_microtime": "0xa1f32c",
"kalloc": "0xa38278",
"kernel_thread_start": "0xa70674",
"kfree": "0xa38338",
"mach_absolute_time": "0xb5ccc0",
"ml_io_map": "0xb57a04",
"ml_vtophys": "0xb584a0",
"panic": "0x27d7944",
"thread_block": "0xa51728",
"thread_wakeup": "0xa54ea0"
}
}
}
@@ -0,0 +1,34 @@
const _copyinstr = new NativeFunction(
DebugSymbol.getFunctionByName('copyinstr'),
'int',
['pointer', 'pointer', 'size_t', 'pointer']);
const { pointerSize } = Process;
Interceptor.attach(DebugSymbol.getFunctionByName('__mac_execve'), {
onEnter(args) {
const fname = copyinstr(args[1].readPointer());
console.log(`>>> __mac_execve() fname=${fname}`);
},
onLeave(retval) {
console.log(`<<< __mac_execve() => ${retval.toInt32()}`);
}
});
Interceptor.attach(DebugSymbol.getFunctionByName('posix_spawn'), {
onEnter(args) {
const path = copyinstr(args[1].add(pointerSize).readPointer());
console.log(`>>> posix_spawn() path=${path}`);
},
onLeave(retval) {
console.log(`<<< posix_spawn() => ${retval.toInt32()}`);
}
});
function copyinstr(userAddr) {
const bufSize = 4096;
const buf = Memory.alloc(bufSize);
const actual = Memory.alloc(pointerSize);
if (_copyinstr(userAddr, buf, bufSize, actual) !== 0)
throw new Error('copyinstr failed');
return buf.readUtf8String();
}
+10
View File
@@ -0,0 +1,10 @@
console.log("Agent loading");
const cm = new CModule(File.readAllBytes("./target/aarch64-unknown-none/release/frida-barebone-agent"));
//const cm = new CModule(File.readAllBytes("./target/aarch64-unknown-none/debug/frida-barebone-agent"));
const start = new NativeFunction(cm._start, "pointer", []);
const bufferPhysicalAddress = start();
console.log("Ready! Buffer is at physical address:", bufferPhysicalAddress);
$gdb.continue();
Object.assign(globalThis, { cm, start });
+19
View File
@@ -0,0 +1,19 @@
{
"name": "frida-barebone-agent",
"version": "1.0.0",
"description": "xPack configuration for frida-barebone-agent",
"xpack": {
"minimumXpmRequired": "0.20.8",
"dependencies": {},
"devDependencies": {
"@xpack-dev-tools/aarch64-none-elf-gcc": {
"specifier": "14.2.1-1.1.1",
"local": "link",
"platforms": "all"
}
},
"properties": {},
"actions": {},
"buildConfigurations": {}
}
}
+46
View File
@@ -0,0 +1,46 @@
use core::ptr;
use crate::bindings::{g_wait_is_set, gint64, gpointer};
use crate::xnu;
const G_WAIT_INFINITE: gint64 = -1;
pub static mut WAKEUP_TOKEN: u64 = 0;
#[unsafe(no_mangle)]
pub extern "C" fn g_get_monotonic_time() -> gint64 {
let abstime = xnu::mach_absolute_time();
let time = xnu::absolutetime_to_nanoseconds(abstime);
(time / 1000) as gint64
}
#[unsafe(no_mangle)]
pub extern "C" fn g_wait_sleep(token: gpointer, timeout_us: gint64) {
let wait_event = ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8;
let wait_result = if timeout_us == G_WAIT_INFINITE {
xnu::assert_wait(wait_event, xnu::THREAD_INTERRUPTIBLE)
} else {
xnu::assert_wait_timeout(
wait_event,
xnu::THREAD_INTERRUPTIBLE,
(timeout_us * 1000) as u32,
1, // TODO
)
};
if wait_result != xnu::THREAD_WAITING {
panic!("assert_wait_timeout failed: {}", wait_result);
}
if unsafe { g_wait_is_set(token) != 0 } {
xnu::thread_wakeup(wait_event);
return;
}
xnu::thread_block(None);
}
#[unsafe(no_mangle)]
pub extern "C" fn g_wait_wake(_token: gpointer) {
xnu::thread_wakeup(ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8);
}
+609
View File
@@ -0,0 +1,609 @@
use crate::bindings::GCond;
use crate::bindings::GMutex;
use crate::bindings::GPrivate;
use crate::bindings::GRWLock;
use crate::bindings::GRecMutex;
use crate::bindings::GSystemThread;
use crate::bindings::GThreadFunc;
use crate::bindings::{gpointer, gulong};
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use core::arch::asm;
use core::ffi::c_void;
use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
// Our implementation structures that overlay the opaque GLib structures
#[repr(C, align(8))]
struct MutexImpl {
lock: AtomicU32,
_padding: [u8; core::mem::size_of::<GMutex>() - core::mem::size_of::<AtomicU32>()],
}
#[repr(C, align(8))]
struct RecMutexImpl {
owner: AtomicU64, // Thread ID of the owner (uses the 'p' field)
count: AtomicU32, // Recursion count (uses i[0])
_unused: u32, // Unused (uses i[1])
}
#[repr(C, align(8))]
struct RWLockImpl {
state: AtomicU32, // Lock state (readers count + writer bit)
_padding: [u8; core::mem::size_of::<GRWLock>() - core::mem::size_of::<AtomicU32>()],
}
#[repr(C, align(8))]
struct CondImpl {
signal: AtomicU32, // Signal state
_padding: [u8; core::mem::size_of::<GCond>() - core::mem::size_of::<AtomicU32>()],
}
const _: () = assert!(core::mem::size_of::<MutexImpl>() == core::mem::size_of::<GMutex>());
const _: () = assert!(core::mem::align_of::<MutexImpl>() == core::mem::align_of::<GMutex>());
const _: () = assert!(core::mem::size_of::<RecMutexImpl>() == core::mem::size_of::<GRecMutex>());
const _: () = assert!(core::mem::align_of::<RecMutexImpl>() == core::mem::align_of::<GRecMutex>());
const _: () = assert!(core::mem::size_of::<RWLockImpl>() == core::mem::size_of::<GRWLock>());
const _: () = assert!(core::mem::align_of::<RWLockImpl>() == core::mem::align_of::<GRWLock>());
const _: () = assert!(core::mem::size_of::<CondImpl>() == core::mem::size_of::<GCond>());
const _: () = assert!(core::mem::align_of::<CondImpl>() == core::mem::align_of::<GCond>());
// System thread implementation
#[repr(C)]
struct SystemThreadImpl {
mutex: GMutex, // Protects the thread state
cond: GCond, // Signals thread completion
thread_id: AtomicU64, // XNU thread ID
func: GThreadFunc, // Thread function
data: gpointer, // User data
finished: AtomicU32, // Whether thread has finished (0 = running, 1 = finished)
detached: AtomicU32, // Whether thread is detached (1) or joinable (0)
}
// Thread wrapper data passed to XNU kernel thread
#[repr(C)]
struct ThreadWrapperData {
func: GThreadFunc,
data: gpointer,
system_thread: *mut SystemThreadImpl,
}
unsafe fn mutex_impl<'a>(mutex: *mut GMutex) -> &'a mut MutexImpl {
unsafe { &mut *(mutex as *mut MutexImpl) }
}
unsafe fn rec_mutex_impl<'a>(rec_mutex: *mut GRecMutex) -> &'a mut RecMutexImpl {
unsafe { &mut *(rec_mutex as *mut RecMutexImpl) }
}
unsafe fn rw_lock_impl<'a>(rw_lock: *mut GRWLock) -> &'a mut RWLockImpl {
unsafe { &mut *(rw_lock as *mut RWLockImpl) }
}
unsafe fn cond_impl<'a>(cond: *mut GCond) -> &'a mut CondImpl {
unsafe { &mut *(cond as *mut CondImpl) }
}
// TODO: Clean up Thread-Local Storage (TLS) entries when threads exit
static mut TLS_STORAGE: Option<Box<BTreeMap<(u64, *mut GPrivate), *mut c_void>>> = None;
static TLS_LOCK: AtomicU32 = AtomicU32::new(0);
#[unsafe(no_mangle)]
pub extern "C" fn g_mutex_init(mutex: *mut GMutex) {
unsafe {
let impl_ = mutex_impl(mutex);
impl_.lock.store(0, Ordering::Relaxed);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_mutex_clear(_mutex: *mut GMutex) {}
#[unsafe(no_mangle)]
pub extern "C" fn g_mutex_lock(mutex: *mut GMutex) {
unsafe {
let impl_ = mutex_impl(mutex);
lock_acquire(&impl_.lock);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_mutex_unlock(mutex: *mut GMutex) {
unsafe {
let impl_ = mutex_impl(mutex);
lock_release(&impl_.lock);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_mutex_trylock(mutex: *mut GMutex) -> u32 {
unsafe {
let impl_ = mutex_impl(mutex);
if lock_try_acquire(&impl_.lock) {
1
} else {
0
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_rec_mutex_init(rec_mutex: *mut GRecMutex) {
unsafe {
let impl_ = rec_mutex_impl(rec_mutex);
impl_.owner.store(0, Ordering::Relaxed);
impl_.count.store(0, Ordering::Relaxed);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_rec_mutex_clear(_rec_mutex: *mut GRecMutex) {
}
#[unsafe(no_mangle)]
pub extern "C" fn g_rec_mutex_lock(rec_mutex: *mut GRecMutex) {
unsafe {
let impl_ = rec_mutex_impl(rec_mutex);
let current_thread = get_current_thread_id();
loop {
if rec_mutex_try_acquire(impl_, current_thread) {
break;
}
rec_mutex_wait(impl_);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_rec_mutex_trylock(rec_mutex: *mut GRecMutex) -> u32 {
unsafe {
let impl_ = rec_mutex_impl(rec_mutex);
let current_thread = get_current_thread_id();
if rec_mutex_try_acquire(impl_, current_thread) {
1
} else {
0
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_rec_mutex_unlock(rec_mutex: *mut GRecMutex) {
unsafe {
let impl_ = rec_mutex_impl(rec_mutex);
loop {
let current_count = impl_.count.load(Ordering::Relaxed);
if current_count == 1 {
if impl_.count
.compare_exchange_weak(1, 0, Ordering::Release, Ordering::Relaxed)
.is_ok()
{
impl_.owner.store(0, Ordering::Relaxed);
lock_notify_all();
break;
}
} else {
if impl_.count
.compare_exchange_weak(current_count, current_count - 1, Ordering::Release, Ordering::Relaxed)
.is_ok()
{
break;
}
}
}
}
}
unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl, current_thread: u64) -> bool {
let current_count = impl_.count.load(Ordering::Relaxed);
if current_count == 0 {
if impl_.count
.compare_exchange_weak(0, 1, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
impl_.owner.store(current_thread, Ordering::Relaxed);
return true;
}
} else {
let current_owner = impl_.owner.load(Ordering::Relaxed);
if current_owner == current_thread {
if impl_.count
.compare_exchange_weak(
current_count,
current_count + 1,
Ordering::Acquire,
Ordering::Relaxed,
)
.is_ok()
{
return true;
}
}
}
false
}
unsafe fn rec_mutex_wait(impl_: &mut RecMutexImpl) {
while impl_.count.load(Ordering::Relaxed) != 0 {
unsafe { lock_wait_primitive() };
}
}
// Read-Write Lock implementation
// We use a simple scheme where the atomic value represents:
// - 0: unlocked
// - 1-0x7FFFFFFF: number of readers holding the lock
// - 0x80000000: writer lock held
const RW_WRITER_LOCK_BIT: u32 = 0x80000000;
#[unsafe(no_mangle)]
pub extern "C" fn g_rw_lock_init(rw_lock: *mut GRWLock) {
unsafe {
let impl_ = rw_lock_impl(rw_lock);
impl_.state.store(0, Ordering::Relaxed);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_rw_lock_clear(_rw_lock: *mut GRWLock) {}
#[unsafe(no_mangle)]
pub extern "C" fn g_rw_lock_writer_lock(rw_lock: *mut GRWLock) {
unsafe {
let impl_ = rw_lock_impl(rw_lock);
loop {
if rw_lock_writer_try_acquire(impl_) {
break;
}
rw_lock_wait_for_all(impl_);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_rw_lock_writer_trylock(rw_lock: *mut GRWLock) -> u32 {
unsafe {
let impl_ = rw_lock_impl(rw_lock);
if rw_lock_writer_try_acquire(impl_) {
1
} else {
0
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_rw_lock_writer_unlock(rw_lock: *mut GRWLock) {
unsafe {
let impl_ = rw_lock_impl(rw_lock);
impl_.state.store(0, Ordering::Release);
lock_notify_all();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_rw_lock_reader_lock(rw_lock: *mut GRWLock) {
unsafe {
let impl_ = rw_lock_impl(rw_lock);
loop {
let current = impl_.state.load(Ordering::Relaxed);
if current & RW_WRITER_LOCK_BIT != 0 {
rw_lock_wait_for_writers(impl_);
continue;
}
if impl_.state
.compare_exchange_weak(current, current + 1, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
break;
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_rw_lock_reader_trylock(rw_lock: *mut GRWLock) -> u32 {
unsafe {
let impl_ = rw_lock_impl(rw_lock);
if rw_lock_reader_try_acquire(impl_) {
1
} else {
0
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_rw_lock_reader_unlock(rw_lock: *mut GRWLock) {
unsafe {
let impl_ = rw_lock_impl(rw_lock);
loop {
let current = impl_.state.load(Ordering::Relaxed);
if impl_.state
.compare_exchange_weak(current, current - 1, Ordering::Release, Ordering::Relaxed)
.is_ok()
{
if current - 1 == 0 {
lock_notify_all();
}
break;
}
}
}
}
unsafe fn rw_lock_writer_try_acquire(impl_: &mut RWLockImpl) -> bool {
impl_.state
.compare_exchange_weak(0, RW_WRITER_LOCK_BIT, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}
unsafe fn rw_lock_reader_try_acquire(impl_: &mut RWLockImpl) -> bool {
let current = impl_.state.load(Ordering::Relaxed);
if current & RW_WRITER_LOCK_BIT != 0 {
return false;
}
impl_.state
.compare_exchange_weak(current, current + 1, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}
unsafe fn rw_lock_wait_for_writers(impl_: &mut RWLockImpl) {
while impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT != 0 {
unsafe { lock_wait_primitive() };
}
}
unsafe fn rw_lock_wait_for_all(impl_: &mut RWLockImpl) {
while impl_.state.load(Ordering::Relaxed) != 0 {
unsafe { lock_wait_primitive() };
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_cond_init(cond: *mut GCond) {
unsafe {
let impl_ = cond_impl(cond);
impl_.signal.store(0, Ordering::Relaxed);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_cond_clear(_cond: *mut GCond) {}
#[unsafe(no_mangle)]
pub extern "C" fn g_cond_wait(cond: *mut GCond, mutex: *mut GMutex) {
unsafe {
g_mutex_unlock(mutex);
let impl_ = cond_impl(cond);
while impl_.signal.load(Ordering::Acquire) == 0 {
lock_wait_primitive();
}
g_mutex_lock(mutex);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_cond_signal(cond: *mut GCond) {
unsafe {
let impl_ = cond_impl(cond);
impl_.signal.store(1, Ordering::Release);
lock_notify_all();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_cond_broadcast(cond: *mut GCond) {
unsafe {
let impl_ = cond_impl(cond);
impl_.signal.store(1, Ordering::Release);
lock_notify_all();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_cond_wait_until(cond: *mut GCond, mutex: *mut GMutex, _end_time: i64) -> u32 {
// TODO: Implement proper timeout handling
g_cond_wait(cond, mutex);
1
}
#[unsafe(no_mangle)]
pub extern "C" fn g_private_get(key: *mut GPrivate) -> *mut c_void {
unsafe {
let thread_id = get_current_thread_id();
lock_acquire(&TLS_LOCK);
ensure_tls_storage();
let storage_ptr = core::ptr::addr_of_mut!(TLS_STORAGE);
let storage = (*storage_ptr).as_ref().unwrap();
let value = storage
.get(&(thread_id, key))
.copied()
.unwrap_or(core::ptr::null_mut());
lock_release(&TLS_LOCK);
value
}
}
#[unsafe(no_mangle)]
pub extern "C" fn g_private_set(key: *mut GPrivate, value: *mut c_void) {
unsafe {
let thread_id = get_current_thread_id();
lock_acquire(&TLS_LOCK);
ensure_tls_storage();
let storage_ptr = core::ptr::addr_of_mut!(TLS_STORAGE);
let storage = (*storage_ptr).as_mut().unwrap();
storage.insert((thread_id, key), value);
lock_release(&TLS_LOCK);
}
}
unsafe fn ensure_tls_storage() {
let storage_ptr = core::ptr::addr_of_mut!(TLS_STORAGE);
unsafe {
if (*storage_ptr).is_none() {
*storage_ptr = Some(Box::new(BTreeMap::new()));
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _g_system_thread_create(
_stack_size: gulong,
_name: *const core::ffi::c_char,
func: GThreadFunc,
data: gpointer,
) -> *mut GSystemThread {
unsafe {
let system_thread = crate::xnu::kalloc(core::mem::size_of::<SystemThreadImpl>()) as *mut SystemThreadImpl;
g_mutex_init(&mut (*system_thread).mutex);
g_cond_init(&mut (*system_thread).cond);
(*system_thread).thread_id.store(0, Ordering::Relaxed);
(*system_thread).func = func;
(*system_thread).data = data;
(*system_thread).finished.store(0, Ordering::Relaxed);
(*system_thread).detached.store(0, Ordering::Relaxed);
let wrapper_data = crate::xnu::kalloc(core::mem::size_of::<ThreadWrapperData>()) as *mut ThreadWrapperData;
(*wrapper_data).func = func;
(*wrapper_data).data = data;
(*wrapper_data).system_thread = system_thread;
let _xnu_result = crate::xnu::kernel_thread_start(thread_wrapper, wrapper_data as *mut c_void);
system_thread as *mut GSystemThread
}
}
extern "C" fn thread_wrapper(parameter: *mut c_void, _wait_result: i32) {
unsafe {
let wrapper_data = parameter as *mut ThreadWrapperData;
let func = (*wrapper_data).func;
let data = (*wrapper_data).data;
let system_thread = (*wrapper_data).system_thread;
let current_thread_id = get_current_thread_id();
(*system_thread).thread_id.store(current_thread_id, Ordering::Relaxed);
func.unwrap()(data);
g_mutex_lock(&mut (*system_thread).mutex);
(*system_thread).finished.store(1, Ordering::Relaxed);
g_cond_signal(&mut (*system_thread).cond);
let detached = (*system_thread).detached.load(Ordering::Relaxed);
g_mutex_unlock(&mut (*system_thread).mutex);
if detached != 0 {
cleanup_system_thread(system_thread);
}
cleanup_wrapper_data(wrapper_data);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _g_system_thread_detach(thread: *mut GSystemThread) {
unsafe {
let system_thread = thread as *mut SystemThreadImpl;
g_mutex_lock(&mut (*system_thread).mutex);
(*system_thread).detached.store(1, Ordering::Relaxed);
let finished = (*system_thread).finished.load(Ordering::Relaxed);
g_mutex_unlock(&mut (*system_thread).mutex);
if finished != 0 {
cleanup_system_thread(system_thread);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _g_system_thread_wait(thread: *mut GSystemThread) {
unsafe {
let system_thread = thread as *mut SystemThreadImpl;
loop {
let thread_id = (*system_thread).thread_id.load(Ordering::Acquire);
if thread_id != 0 {
break;
}
core::hint::spin_loop();
}
g_mutex_lock(&mut (*system_thread).mutex);
while (*system_thread).finished.load(Ordering::Relaxed) == 0 {
g_cond_wait(&mut (*system_thread).cond, &mut (*system_thread).mutex);
}
g_mutex_unlock(&mut (*system_thread).mutex);
cleanup_system_thread(system_thread);
}
}
unsafe fn cleanup_system_thread(system_thread: *mut SystemThreadImpl) {
crate::xnu::free(system_thread as *mut u8, core::mem::size_of::<SystemThreadImpl>());
}
unsafe fn cleanup_wrapper_data(wrapper_data: *mut ThreadWrapperData) {
crate::xnu::free(wrapper_data as *mut u8, core::mem::size_of::<ThreadWrapperData>());
}
unsafe fn lock_acquire(lock: &AtomicU32) {
loop {
if unsafe { lock_try_acquire(lock) } {
break;
}
unsafe { lock_wait(lock) };
}
}
unsafe fn lock_try_acquire(lock: &AtomicU32) -> bool {
lock.compare_exchange_weak(0, 1, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
}
unsafe fn lock_release(lock: &AtomicU32) {
lock.store(0, Ordering::Release);
unsafe { lock_notify_all() };
}
unsafe fn lock_wait(lock: &AtomicU32) {
while lock.load(Ordering::Relaxed) != 0 {
unsafe { lock_wait_primitive() };
}
}
unsafe fn lock_wait_primitive() {
unsafe {
asm!("wfe", options(nomem, nostack));
}
}
unsafe fn lock_notify_all() {
unsafe {
asm!("sev", options(nomem, nostack));
}
}
pub unsafe fn get_current_thread_id() -> u64 {
let thread_ptr: u64;
unsafe {
asm!("mrs {}, tpidr_el1", out(reg) thread_ptr, options(nomem, nostack));
}
thread_ptr
}
+457
View File
@@ -0,0 +1,457 @@
use crate::{
bindings::{
_GInterfaceInfo, _GTypeInfo, _GumPageProtection_GUM_PAGE_EXECUTE,
_GumRwxSupport_GUM_RWX_NONE, GArray, GObject, GObjectClass, GPrivate, GType,
GumDebugSymbolDetails, GumExportDetails, GumExportType_GUM_EXPORT_FUNCTION,
GumFoundExportFunc, GumMemoryRange, GumModule, GumModuleInterface, GumModuleRegistry,
GumPageProtection, GumRwxSupport, GumThreadId, GumTlsKey, g_array_append_vals, g_array_new,
g_free, g_object_get_type, g_object_new, g_object_unref, g_once_init_enter,
g_once_init_leave, g_strdup, g_type_add_interface_static, g_type_class_peek_parent,
g_type_register_static, gboolean, gchar, gconstpointer, gpointer, gsize, guint,
gum_barebone_register_module,
gum_barebone_try_remap_writable_pages as _gum_barebone_try_remap_writable_pages,
gum_module_get_type, gum_mprotect, gum_query_page_size,
},
gthread, libc, xnu,
};
use alloc::boxed::Box;
use alloc::ffi::CString;
use alloc::format;
use core::ptr;
use core::{ffi::CStr, ptr::read_volatile};
#[unsafe(no_mangle)]
pub extern "C" fn gum_process_get_current_thread_id() -> GumThreadId {
unsafe { gthread::get_current_thread_id() }
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_page_size() -> guint {
unsafe {
let tcr_el1: u64;
core::arch::asm!("mrs {}, tcr_el1", out(reg) tcr_el1, options(nomem, nostack));
let tg0 = (tcr_el1 >> 14) & 0x3;
match tg0 {
0b00 => 4096,
0b01 => 65536,
0b10 => 16384,
_ => 4096,
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_query_rwx_support() -> GumRwxSupport {
_GumRwxSupport_GUM_RWX_NONE
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_can_remap_writable() -> gboolean {
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_try_remap_writable_pages(
first_page: gpointer,
n_pages: guint,
) -> gpointer {
unsafe {
let page_size = gum_query_page_size() as usize;
let mut physical_addrs = alloc::vec::Vec::with_capacity(n_pages as usize);
let mut current_page = first_page as u64;
for _ in 0..n_pages {
let phys_addr = xnu::ml_vtophys(current_page);
physical_addrs.push(phys_addr as gpointer);
current_page += page_size as u64;
}
_gum_barebone_try_remap_writable_pages(
physical_addrs.as_ptr() as *mut *const core::ffi::c_void,
physical_addrs.len() as guint,
)
}
}
#[unsafe(no_mangle)]
#[inline(never)]
pub extern "C" fn gum_barebone_try_remap_writable_pages(
addrs: *const gpointer,
n_addrs: guint,
) -> gpointer {
unsafe {
read_volatile(&n_addrs);
read_volatile(addrs)
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_try_mprotect(
_address: gpointer,
_size: gsize,
_prot: GumPageProtection,
) -> gboolean {
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_allocate(
_address: gpointer,
size: gsize,
_alignment: gsize,
prot: GumPageProtection,
) -> gpointer {
let ptr = crate::xnu::kalloc(size as usize);
unsafe {
core::ptr::write_bytes(ptr, 0, size as usize);
if (prot & _GumPageProtection_GUM_PAGE_EXECUTE) != 0 {
gum_mprotect(ptr as gpointer, size, prot);
}
}
ptr as gpointer
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_free(address: gpointer, size: gsize) -> gboolean {
crate::xnu::free(address as *mut u8, size as usize);
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_clear_cache(address: gpointer, size: gsize) {
unsafe {
let start = address as *const u8;
let end = start.add(size as usize);
libc::__clear_cache(start, end);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_tls_key_new() -> GumTlsKey {
Box::into_raw(Box::new(unsafe { core::mem::zeroed::<GPrivate>() })) as GumTlsKey
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_tls_key_free(key: GumTlsKey) {
unsafe {
let _ = Box::from_raw(key as *mut GPrivate);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_tls_key_get_value(key: GumTlsKey) -> gpointer {
gthread::g_private_get(key as *mut GPrivate)
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_tls_key_set_value(key: GumTlsKey, value: gpointer) {
gthread::g_private_set(key as *mut GPrivate, value)
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
let kernel_base = crate::xnu::get_kernel_base();
unsafe {
let module_infos = core::ptr::addr_of!(crate::MODULE_INFO);
let module_infos = &*module_infos;
let mut i = 0;
for module_info in module_infos.iter() {
let module_base = kernel_base + module_info.offset as u64;
let module_path = if i == 0 {
"/System/Library/Kernels/kernel"
} else {
&format!(
"/System/Library/Extensions/{}.kext/{}",
module_info.name, module_info.name
)
};
let module_range = GumMemoryRange {
base_address: module_base,
size: module_info.size as u64,
};
let module = gum_native_module_new(&module_path, &module_info.version, &module_range);
gum_barebone_register_module(registry, module);
g_object_unref(module as gpointer);
i += 1;
}
}
}
#[repr(C)]
pub struct GumNativeModule {
parent: GObject,
name: *mut gchar,
version: *mut gchar,
path: *mut gchar,
range: GumMemoryRange,
}
#[repr(C)]
#[allow(dead_code)]
pub struct GumNativeModuleClass {
parent_class: GObjectClass,
}
static mut GUM_NATIVE_MODULE_TYPE: gsize = 0;
static mut GUM_NATIVE_MODULE_PARENT_CLASS: *mut GObjectClass = core::ptr::null_mut();
fn gum_native_module_get_type() -> GType {
unsafe {
if g_once_init_enter(
core::ptr::addr_of_mut!(GUM_NATIVE_MODULE_TYPE) as *mut ::core::ffi::c_void
) != 0
{
let type_name = c"GumNativeModule".as_ptr() as *const gchar;
let type_info = _GTypeInfo {
class_size: core::mem::size_of::<GObjectClass>() as u16,
base_init: None,
base_finalize: None,
class_init: Some(gum_native_module_class_init),
class_finalize: None,
class_data: core::ptr::null(),
instance_size: core::mem::size_of::<GumNativeModule>() as u16,
n_preallocs: 0,
instance_init: None,
value_table: core::ptr::null(),
};
let new_type = g_type_register_static(g_object_get_type(), type_name, &type_info, 0);
let interface_info = _GInterfaceInfo {
interface_init: Some(gum_native_module_iface_init),
interface_finalize: None,
interface_data: core::ptr::null_mut(),
};
g_type_add_interface_static(new_type, gum_module_get_type(), &interface_info);
g_once_init_leave(
core::ptr::addr_of_mut!(GUM_NATIVE_MODULE_TYPE) as *mut ::core::ffi::c_void,
new_type as gsize,
);
}
GUM_NATIVE_MODULE_TYPE as GType
}
}
unsafe extern "C" fn gum_native_module_class_init(klass: gpointer, _class_data: gpointer) {
unsafe {
let object_class = klass as *mut GObjectClass;
GUM_NATIVE_MODULE_PARENT_CLASS = g_type_class_peek_parent(klass) as *mut GObjectClass;
(*object_class).finalize = Some(gum_native_module_finalize);
}
}
extern "C" fn gum_native_module_iface_init(g_iface: gpointer, _iface_data: gpointer) {
unsafe {
let iface = g_iface as *mut GumModuleInterface;
(*iface).get_name = Some(gum_native_module_get_name);
(*iface).get_version = Some(gum_native_module_get_version);
(*iface).get_path = Some(gum_native_module_get_path);
(*iface).get_range = Some(gum_native_module_get_range);
(*iface).enumerate_exports = Some(gum_native_module_enumerate_exports);
}
}
unsafe extern "C" fn gum_native_module_finalize(object: *mut GObject) {
unsafe {
let module = object as *mut GumNativeModule;
g_free((*module).path as gpointer);
(*GUM_NATIVE_MODULE_PARENT_CLASS).finalize.unwrap()(object);
}
}
fn gum_native_module_new(path: &str, version: &str, range: &GumMemoryRange) -> *mut GumModule {
unsafe {
let path_cstr = CString::new(path).unwrap();
let version_cstr = CString::new(version).unwrap();
let module =
g_object_new(gum_native_module_get_type(), ptr::null()) as *mut GumNativeModule;
(*module).path = g_strdup(path_cstr.as_ptr());
(*module).name = (*module).path.add(path.rfind('/').unwrap() + 1);
(*module).version = g_strdup(version_cstr.as_ptr());
(*module).range = *range;
module as *mut GumModule
}
}
extern "C" fn gum_native_module_get_name(module: *mut GumModule) -> *const gchar {
unsafe {
let native_module = module as *mut GumNativeModule;
(*native_module).name as *const gchar
}
}
extern "C" fn gum_native_module_get_version(module: *mut GumModule) -> *const gchar {
unsafe {
let native_module = module as *mut GumNativeModule;
(*native_module).version as *const gchar
}
}
extern "C" fn gum_native_module_get_path(module: *mut GumModule) -> *const gchar {
unsafe {
let native_module = module as *mut GumNativeModule;
(*native_module).path as *const gchar
}
}
extern "C" fn gum_native_module_get_range(module: *mut GumModule) -> *const GumMemoryRange {
unsafe {
let native_module = module as *mut GumNativeModule;
&(*native_module).range as *const GumMemoryRange
}
}
unsafe extern "C" fn gum_native_module_enumerate_exports(
self_: *mut GumModule,
func: GumFoundExportFunc,
user_data: gpointer,
) {
unsafe {
let module = self_ as *mut GumNativeModule;
let module_range = &(*module).range;
let symbol_table = core::ptr::addr_of!(crate::SYMBOL_TABLE);
let symbol_table = &*symbol_table;
const N_EXT: u8 = 0x01; // External symbol flag
const N_TYPE: u8 = 0x0e; // Type mask
const N_SECT: u8 = 0x0e; // Defined in section
let start_address = module_range.base_address;
let end_address = module_range.base_address + module_range.size;
for symbol_ref in symbol_table.iter_symbols_in_range(start_address, end_address) {
let is_external = (symbol_ref.symbol_type() & N_EXT) != 0;
let is_defined = (symbol_ref.symbol_type() & N_TYPE) == N_SECT;
if !is_external || !is_defined {
continue;
}
let export_type = GumExportType_GUM_EXPORT_FUNCTION;
let export_details = GumExportDetails {
type_: export_type,
name: g_strdup(symbol_ref.name_ptr()),
address: symbol_ref.address(),
};
let should_continue =
func.unwrap()(&export_details as *const GumExportDetails, user_data);
if should_continue == 0 {
break;
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_symbol_details_from_address(
address: gpointer,
details: *mut GumDebugSymbolDetails,
) -> gboolean {
unsafe {
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
if let Some(symbol) = table.find_symbol_by_address(address as u64) {
let name_bytes = symbol.name().as_bytes();
let copy_len = core::cmp::min(name_bytes.len(), 2048);
core::ptr::copy_nonoverlapping(
name_bytes.as_ptr(),
(*details).symbol_name.as_mut_ptr() as *mut u8,
copy_len,
);
(*details).symbol_name[copy_len] = 0;
(*details).address = symbol.address();
(*details).module_name[0] = 0;
(*details).file_name[0] = 0;
(*details).line_number = 0;
(*details).column = 0;
return 1;
}
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_symbol_name_from_address(address: gpointer) -> *mut gchar {
unsafe {
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
let name_ptr = table.find_symbol_name_ptr_by_address(address as u64);
g_strdup(name_ptr as *const gchar)
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_function(name: *const gchar) -> gpointer {
unsafe {
let target_name = CStr::from_ptr(name).to_string_lossy();
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
if let Some(symbol) = table.find_symbol_by_name(&target_name) {
return symbol.address() as gpointer;
}
core::ptr::null_mut()
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_functions_named(name: *const gchar) -> *mut GArray {
unsafe {
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
let target_name = CStr::from_ptr(name).to_string_lossy();
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
let symbols = table.find_symbols_by_name(&target_name);
for symbol in symbols {
let addr = symbol.address() as gpointer;
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
}
array
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_functions_matching(pattern: *const gchar) -> *mut GArray {
unsafe {
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
let glob_pattern = CStr::from_ptr(pattern).to_string_lossy();
let table = core::ptr::addr_of!(crate::SYMBOL_TABLE).read();
let symbols = table.find_symbols_matching_glob(&glob_pattern);
for symbol in symbols {
let addr = symbol.address() as gpointer;
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
}
array
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
0
}
+763
View File
@@ -0,0 +1,763 @@
use core::cell::UnsafeCell;
use core::ffi::c_void;
use core::mem::size_of;
use core::ptr::{read_volatile, write_volatile};
use core::sync::atomic::{AtomicUsize, Ordering};
use crate::gum::gum_barebone_query_page_size;
use crate::xnu;
const MMIO_SIZE: u64 = 0x200;
static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);
const QSZ: u16 = 64;
const MAGIC: usize = 0x000;
const VERSION: usize = 0x004;
const DEVICE: usize = 0x008;
const STATUS: usize = 0x070;
const DEVFEAT: usize = 0x010;
const DEVFEAT_SEL: usize = 0x014;
const DRVFEAT: usize = 0x020;
const DRVFEAT_SEL: usize = 0x024;
const QSEL: usize = 0x030;
const QNUM_MAX: usize = 0x034;
const QNUM: usize = 0x038;
const QREADY: usize = 0x044;
const QDESC_LO: usize = 0x080;
const QDESC_HI: usize = 0x084;
const QAVAIL_LO: usize = 0x090;
const QAVAIL_HI: usize = 0x094;
const QUSED_LO: usize = 0x0a0;
const QUSED_HI: usize = 0x0a4;
const QNOTIFY: usize = 0x050;
const ISR: usize = 0x060;
const ISR_ACK: usize = 0x064;
const ST_ACK: u32 = 1;
const ST_DRV: u32 = 2;
const ST_DRV_OK: u32 = 4;
const ST_FEAT_OK: u32 = 8;
const ST_FAILED: u32 = 0x80;
const DEV_ID_CONSOLE: u32 = 3;
const F_VERSION_1: u64 = 1u64 << 32;
const F_MULTIPORT: u64 = 1u64 << 1;
const INT_VRING: u32 = 1;
const Q_RX0: u16 = 0;
const Q_TX0: u16 = 1;
const Q_CTRL_RX: u16 = 2;
const Q_CTRL_TX: u16 = 3;
#[repr(C)]
#[derive(Copy, Clone)]
struct VConsCtrl {
id: u32,
event: u16,
value: u16,
}
const EV_DEVICE_READY: u16 = 0;
const EV_DEVICE_ADD: u16 = 1;
const EV_PORT_READY: u16 = 3;
const EV_PORT_OPEN: u16 = 6;
const EV_CONSOLE_PORT: u16 = 4;
#[repr(C)]
struct Desc {
addr: u64,
len: u32,
flags: u16,
next: u16,
}
const D_NEXT: u16 = 1;
const D_WRITE: u16 = 2;
#[repr(C)]
#[derive(Copy, Clone)]
struct Avail {
flags: u16,
idx: u16,
}
#[repr(C)]
#[derive(Copy, Clone)]
struct UsedElem {
id: u32,
len: u32,
}
#[repr(C)]
#[derive(Copy, Clone)]
struct Used {
flags: u16,
idx: u16,
}
#[derive(Copy, Clone)]
struct DmaPage {
va: *mut u8,
pa: u64,
}
fn dma_page_alloc() -> DmaPage {
let len = PAGE_SIZE.load(Ordering::Relaxed);
let va = xnu::kalloc(len);
let pa = xnu::ml_vtophys(va as u64);
DmaPage { va, pa }
}
fn dma_page_free(p: DmaPage) {
let len = PAGE_SIZE.load(Ordering::Relaxed);
xnu::free(p.va, len);
}
struct Vq {
sel: u16,
size: u16,
desc_va: *mut u8,
avail_va: *mut u8,
used_va: *mut u8,
avail_idx: u16,
used_idx: u16,
free_head: u16,
free_cnt: u16,
}
impl Vq {
fn new(mmio: *mut u8, sel: u16, size: u16) -> Self {
w32(mmio, QSEL, sel as u32);
let max = r32(mmio, QNUM_MAX) as u16;
debug_assert!(max >= size && max != 0);
w32(mmio, QNUM, size as u32);
let d = dma_page_alloc();
let a = dma_page_alloc();
let u = dma_page_alloc();
let ps = PAGE_SIZE.load(core::sync::atomic::Ordering::Relaxed);
unsafe {
core::ptr::write_bytes(d.va, 0, ps);
core::ptr::write_bytes(a.va, 0, ps);
core::ptr::write_bytes(u.va, 0, ps);
}
w64(mmio, QDESC_LO, QDESC_HI, d.pa);
w64(mmio, QAVAIL_LO, QAVAIL_HI, a.pa);
w64(mmio, QUSED_LO, QUSED_HI, u.pa);
let dp = d.va as *mut Desc;
for i in 0..size {
unsafe {
(*dp.add(i as usize)).flags = 0;
(*dp.add(i as usize)).next = if i + 1 < size { i + 1 } else { 0xFFFF };
}
}
w32(mmio, QREADY, 1);
Self {
sel,
size,
desc_va: d.va,
avail_va: a.va,
used_va: u.va,
avail_idx: 0,
used_idx: 0,
free_head: 0,
free_cnt: size,
}
}
fn alloc(&mut self) -> u16 {
debug_assert!(self.free_cnt > 0);
let h = self.free_head;
let dp = self.desc_va as *mut Desc;
unsafe {
self.free_head = (*dp.add(h as usize)).next;
}
self.free_cnt -= 1;
h
}
fn free_chain(&mut self, mut idx: u16) {
let dp = self.desc_va as *mut Desc;
loop {
self.free_cnt += 1;
let (flags, next) = unsafe {
let p = dp.add(idx as usize);
((*p).flags, (*p).next)
};
if (flags & D_NEXT) == 0 {
break;
}
idx = next;
}
unsafe {
(*dp.add(idx as usize)).next = self.free_head;
}
self.free_head = idx;
}
fn push_avail(&mut self, head: u16) {
let ap = self.avail_va as *mut Avail;
let ring = unsafe { (ap as *mut u8).add(size_of::<Avail>()) as *mut u16 };
let slot = (self.avail_idx % self.size) as usize;
unsafe {
*ring.add(slot) = head;
}
wmb();
self.avail_idx = self.avail_idx.wrapping_add(1);
unsafe {
(*ap).idx = self.avail_idx;
}
}
fn pop_used(&mut self) -> Option<UsedElem> {
let up = self.used_va as *mut Used;
if unsafe { (*up).idx } == self.used_idx {
return None;
}
let ring = unsafe { (up as *mut u8).add(size_of::<Used>()) as *mut UsedElem };
let elem = unsafe { *ring.add((self.used_idx % self.size) as usize) };
self.used_idx = self.used_idx.wrapping_add(1);
Some(elem)
}
}
struct Inner {
mmio: *mut u8,
ctrl_rx: Vq,
ctrl_tx: Vq,
port_id: Option<u32>,
rx: Option<Vq>,
tx: Option<Vq>,
rx_need: usize,
rx_have: usize,
rx_lenbuf: [u8; 4],
rx_lenhave: usize,
rx_buf: Option<&'static mut [u8]>,
tx_head: *mut TxNode,
tx_tail: *mut TxNode,
ctrl_rx_pages: [Option<DmaPage>; QSZ as usize],
data_rx_pages: [Option<DmaPage>; QSZ as usize],
tx_pages: [Option<DmaPage>; QSZ as usize],
wake_token: *const u8,
on_rx: Option<fn(&[u8])>,
}
#[repr(C)]
struct TxNode {
next: *mut TxNode,
frame: &'static [u8],
}
pub struct Hostlink {
state: UnsafeCell<Inner>,
}
unsafe impl Send for Hostlink {}
impl Hostlink {
pub fn init(mmio_base: u64, irq_line: u32, on_rx: Option<fn(&[u8])>, wake_token: *const u8) -> Result<Self, ()> {
let page_size = gum_barebone_query_page_size();
PAGE_SIZE.store(page_size as usize, Ordering::Relaxed);
let mmio = xnu::ml_io_map(mmio_base, MMIO_SIZE) as *mut u8;
if mmio.is_null() {
return Err(());
}
w32(mmio, STATUS, 0);
w32(mmio, STATUS, ST_ACK | ST_DRV);
let magic_ok = r32(mmio, MAGIC) == 0x7472_6976;
let version_ok = r32(mmio, VERSION) == 2;
let device_ok = r32(mmio, DEVICE) == DEV_ID_CONSOLE;
if !(magic_ok && version_ok && device_ok) {
w32(mmio, STATUS, ST_FAILED);
return Err(());
}
let dev_lo = feat_get(mmio, 0) as u64;
let dev_hi = (feat_get(mmio, 1) as u64) << 32;
let mut drv: u64 = 0;
if (dev_hi & F_VERSION_1) != 0 {
drv |= F_VERSION_1;
}
if (dev_lo & F_MULTIPORT) != 0 {
drv |= F_MULTIPORT;
}
feat_set(mmio, 0, (drv & 0xffff_ffff) as u32);
feat_set(mmio, 1, (drv >> 32) as u32);
w32(mmio, STATUS, r32(mmio, STATUS) | ST_FEAT_OK);
let feats_ok = (r32(mmio, STATUS) & ST_FEAT_OK) != 0;
if !feats_ok {
w32(mmio, STATUS, ST_FAILED);
return Err(());
}
let ctrl_rx = Vq::new(mmio, Q_CTRL_RX, QSZ);
let ctrl_tx = Vq::new(mmio, Q_CTRL_TX, QSZ);
w32(mmio, STATUS, r32(mmio, STATUS) | ST_DRV_OK);
xnu::install_interrupt_handler(
irq_line,
wake_token as *mut c_void,
isr_wake,
core::ptr::null_mut(),
);
let inner = Inner {
mmio,
ctrl_rx,
ctrl_tx,
port_id: None,
rx: None,
tx: None,
rx_need: 0,
rx_have: 0,
rx_lenbuf: [0; 4],
rx_lenhave: 0,
rx_buf: None,
tx_head: core::ptr::null_mut(),
tx_tail: core::ptr::null_mut(),
ctrl_rx_pages: [None; QSZ as usize],
data_rx_pages: [None; QSZ as usize],
tx_pages: [None; QSZ as usize],
wake_token,
on_rx,
};
let hl = Hostlink {
state: UnsafeCell::new(inner),
};
hl.ctrl_prime_rx(8);
hl.ctrl_send(VConsCtrl {
id: 0,
event: EV_DEVICE_READY,
value: 1,
});
Ok(hl)
}
pub fn send(&self, payload: &[u8]) {
let s = unsafe { &mut *self.state.get() };
let total = 4 + payload.len();
let buf = xnu::kalloc(total);
unsafe {
let len = payload.len() as u32;
*buf.add(0) = (len & 0xFF) as u8;
*buf.add(1) = ((len >> 8) & 0xFF) as u8;
*buf.add(2) = ((len >> 16) & 0xFF) as u8;
*buf.add(3) = ((len >> 24) & 0xFF) as u8;
core::ptr::copy_nonoverlapping(payload.as_ptr(), buf.add(4), payload.len());
}
let frame: &'static [u8] = unsafe { core::slice::from_raw_parts(buf, total) };
let node = xnu::kalloc(size_of::<TxNode>()) as *mut TxNode;
unsafe {
(*node).next = core::ptr::null_mut();
(*node).frame = frame;
}
if s.tx_tail.is_null() {
s.tx_head = node;
s.tx_tail = node;
} else {
unsafe {
(*s.tx_tail).next = node;
}
s.tx_tail = node;
}
xnu::thread_wakeup(s.wake_token);
}
pub fn process(&self) {
let s = unsafe { &mut *self.state.get() };
if (r32(s.mmio, ISR) & INT_VRING) != 0 {
w32(s.mmio, ISR_ACK, INT_VRING);
}
self.ctrl_complete();
self.ctrl_prime_rx(QSZ as usize);
self.data_rx_complete();
self.data_tx_complete();
self.data_tx_push();
self.data_rx_refill();
}
fn ctrl_prime_rx(&self, count: usize) {
let s = unsafe { &mut *self.state.get() };
let mut posted = 0usize;
while posted < count {
if s.ctrl_rx.free_cnt == 0 {
break;
}
let pg = dma_page_alloc();
let h = s.ctrl_rx.alloc();
let d = s.ctrl_rx.desc_va as *mut Desc;
unsafe {
(*d.add(h as usize)).addr = pg.pa;
(*d.add(h as usize)).len = PAGE_SIZE.load(Ordering::Relaxed) as u32;
(*d.add(h as usize)).flags = D_WRITE;
(*d.add(h as usize)).next = 0;
}
s.ctrl_rx_pages[h as usize] = Some(pg);
s.ctrl_rx.push_avail(h);
posted += 1;
}
let sel = s.ctrl_rx.sel;
self.kick(sel);
}
fn ctrl_send(&self, msg: VConsCtrl) {
let s = unsafe { &mut *self.state.get() };
let pg = dma_page_alloc();
unsafe {
core::ptr::write(pg.va as *mut VConsCtrl, msg);
}
let h = s.ctrl_tx.alloc();
let d = s.ctrl_tx.desc_va as *mut Desc;
unsafe {
(*d.add(h as usize)).addr = pg.pa;
(*d.add(h as usize)).len = size_of::<VConsCtrl>() as u32;
(*d.add(h as usize)).flags = 0;
(*d.add(h as usize)).next = 0;
}
s.tx_pages[h as usize] = Some(pg);
s.ctrl_tx.push_avail(h);
let sel = s.ctrl_tx.sel;
self.kick(sel);
}
fn ctrl_complete(&self) {
let s = unsafe { &mut *self.state.get() };
while let Some(u) = s.ctrl_rx.pop_used() {
let h = u.id as u16;
if let Some(pg) = s.ctrl_rx_pages[h as usize].take() {
let ev = unsafe { *(pg.va as *const VConsCtrl) };
dma_page_free(pg);
match ev.event {
EV_DEVICE_ADD | EV_CONSOLE_PORT => {
if s.port_id.is_none() {
self.setup_data_port(ev.id);
self.ctrl_send(VConsCtrl {
id: ev.id,
event: EV_PORT_READY,
value: 1,
});
self.ctrl_send(VConsCtrl {
id: ev.id,
event: EV_PORT_OPEN,
value: 1,
});
}
}
_ => {}
}
let pg2 = dma_page_alloc();
let d = s.ctrl_rx.desc_va as *mut Desc;
unsafe {
(*d.add(h as usize)).addr = pg2.pa;
(*d.add(h as usize)).len = PAGE_SIZE.load(Ordering::Relaxed) as u32;
(*d.add(h as usize)).flags = D_WRITE;
(*d.add(h as usize)).next = 0;
}
s.ctrl_rx_pages[h as usize] = Some(pg2);
s.ctrl_rx.push_avail(h);
}
}
let sel = s.ctrl_rx.sel;
self.kick(sel);
while let Some(u) = s.ctrl_tx.pop_used() {
let head = u.id as u16;
if let Some(pg) = s.tx_pages[head as usize].take() {
dma_page_free(pg);
}
s.ctrl_tx.free_chain(head);
}
}
fn setup_data_port(&self, id: u32) {
let s = unsafe { &mut *self.state.get() };
if s.port_id.is_some() {
return;
}
let (rx_i, tx_i) = if id == 0 {
(Q_RX0, Q_TX0)
} else {
let base = 4 + ((id as u16 - 1) * 2);
(base, base + 1)
};
let rx = Vq::new(s.mmio, rx_i, QSZ);
let tx = Vq::new(s.mmio, tx_i, QSZ);
s.rx = Some(rx);
s.tx = Some(tx);
s.port_id = Some(id);
self.data_rx_refill();
}
fn data_rx_refill(&self) {
let s = unsafe { &mut *self.state.get() };
let Some(rxq) = s.rx.as_mut() else {
return;
};
while rxq.free_cnt > 0 {
let h = rxq.alloc();
let pg = dma_page_alloc();
let d = rxq.desc_va as *mut Desc;
unsafe {
(*d.add(h as usize)).addr = pg.pa;
(*d.add(h as usize)).len = PAGE_SIZE.load(Ordering::Relaxed) as u32;
(*d.add(h as usize)).flags = D_WRITE;
(*d.add(h as usize)).next = 0;
}
s.data_rx_pages[h as usize] = Some(pg);
rxq.push_avail(h);
}
let sel = rxq.sel;
self.kick(sel);
}
fn data_rx_complete(&self) {
let s = unsafe { &mut *self.state.get() };
let Some(rxq) = s.rx.as_mut() else {
return;
};
while let Some(u) = rxq.pop_used() {
let h = u.id as u16;
let n = u.len as usize;
if let Some(pg) = s.data_rx_pages[h as usize].take() {
{
let bytes = unsafe { core::slice::from_raw_parts(pg.va, n) };
self.feed_rx_stream(bytes);
}
dma_page_free(pg);
let pg2 = dma_page_alloc();
let d = rxq.desc_va as *mut Desc;
unsafe {
(*d.add(h as usize)).addr = pg2.pa;
(*d.add(h as usize)).len = PAGE_SIZE.load(Ordering::Relaxed) as u32;
(*d.add(h as usize)).flags = D_WRITE;
(*d.add(h as usize)).next = 0;
}
s.data_rx_pages[h as usize] = Some(pg2);
rxq.push_avail(h);
}
}
let sel = rxq.sel;
self.kick(sel);
}
fn feed_rx_stream(&self, mut chunk: &[u8]) {
let s = unsafe { &mut *self.state.get() };
while !chunk.is_empty() {
if s.rx_lenhave < 4 {
let need = 4 - s.rx_lenhave;
let take = core::cmp::min(need, chunk.len());
s.rx_lenbuf[s.rx_lenhave..s.rx_lenhave + take].copy_from_slice(&chunk[..take]);
s.rx_lenhave += take;
chunk = &chunk[take..];
if s.rx_lenhave < 4 {
return;
}
let len = (s.rx_lenbuf[0] as usize)
| ((s.rx_lenbuf[1] as usize) << 8)
| ((s.rx_lenbuf[2] as usize) << 16)
| ((s.rx_lenbuf[3] as usize) << 24);
if s.rx_buf.is_none() && len > 0 {
let buf = xnu::kalloc(len);
let slice: &'static mut [u8] =
unsafe { core::slice::from_raw_parts_mut(buf, len) };
s.rx_buf = Some(slice);
s.rx_need = len;
s.rx_have = 0;
}
}
let need = s.rx_need.saturating_sub(s.rx_have);
let take = core::cmp::min(need, chunk.len());
if let Some(ref mut buf) = s.rx_buf {
unsafe {
core::ptr::copy_nonoverlapping(
chunk.as_ptr(),
buf.as_mut_ptr().wrapping_add(s.rx_have),
take,
);
}
}
s.rx_have += take;
chunk = &chunk[take..];
if s.rx_have == s.rx_need {
if let (Some(cb), Some(buf)) = (s.on_rx, &s.rx_buf) {
cb(&buf[..]);
}
s.rx_buf = None;
s.rx_need = 0;
s.rx_have = 0;
s.rx_lenhave = 0;
}
}
}
fn data_tx_complete(&self) {
let s = unsafe { &mut *self.state.get() };
let Some(txq) = s.tx.as_mut() else {
return;
};
while let Some(u) = txq.pop_used() {
let mut i = u.id as u16;
loop {
if let Some(pg) = s.tx_pages[i as usize].take() {
dma_page_free(pg);
}
let d = txq.desc_va as *mut Desc;
let (f, n) = unsafe {
let p = d.add(i as usize);
((*p).flags, (*p).next)
};
if (f & D_NEXT) == 0 {
break;
}
i = n;
}
txq.free_chain(u.id as u16);
}
}
fn data_tx_push(&self) {
let s = unsafe { &mut *self.state.get() };
let Some(txq) = s.tx.as_mut() else {
return;
};
loop {
let node = if s.tx_head.is_null() {
core::ptr::null_mut()
} else {
let n = s.tx_head;
s.tx_head = unsafe { (*n).next };
if s.tx_head.is_null() {
s.tx_tail = core::ptr::null_mut();
}
n
};
if node.is_null() {
break;
}
let frame = unsafe { (*node).frame };
let mut off = 0usize;
let mut head: Option<u16> = None;
let mut prev = 0u16;
while off < frame.len() {
if txq.free_cnt == 0 {
break;
}
let page = PAGE_SIZE.load(Ordering::Relaxed);
let chunk = core::cmp::min(page, frame.len() - off);
let pg = dma_page_alloc();
unsafe {
core::ptr::copy_nonoverlapping(frame.as_ptr().add(off), pg.va, chunk);
}
let i = txq.alloc();
let d = txq.desc_va as *mut Desc;
unsafe {
(*d.add(i as usize)).addr = pg.pa;
(*d.add(i as usize)).len = chunk as u32;
(*d.add(i as usize)).flags = 0;
(*d.add(i as usize)).next = 0;
}
s.tx_pages[i as usize] = Some(pg);
if let Some(_h) = head {
unsafe {
(*d.add(prev as usize)).flags |= D_NEXT;
(*d.add(prev as usize)).next = i;
}
prev = i;
} else {
head = Some(i);
prev = i;
}
off += chunk;
}
if let Some(h) = head {
let sel = txq.sel;
txq.push_avail(h);
self.kick(sel);
}
xnu::free(frame.as_ptr() as *mut u8, frame.len());
xnu::free(node as *mut u8, core::mem::size_of::<TxNode>());
}
}
fn kick(&self, sel: u16) {
let s = unsafe { &*self.state.get() };
wmb();
w32(s.mmio, QSEL, sel as u32);
w32(s.mmio, QNOTIFY, sel as u32);
}
}
extern "C" fn isr_wake(token: *mut c_void, _refcon: *mut c_void, _nub: *mut c_void, _src: i32) {
xnu::thread_wakeup(token as *const u8);
}
fn r32(mmio: *mut u8, off: usize) -> u32 {
unsafe { read_volatile(mmio.add(off) as *const u32) }
}
fn w32(mmio: *mut u8, off: usize, val: u32) {
unsafe { write_volatile(mmio.add(off) as *mut u32, val) }
}
fn w64(mmio: *mut u8, lo: usize, hi: usize, v: u64) {
w32(mmio, lo, (v & 0xffff_ffff) as u32);
w32(mmio, hi, (v >> 32) as u32);
}
fn feat_get(mmio: *mut u8, sel: u32) -> u32 {
w32(mmio, DEVFEAT_SEL, sel);
r32(mmio, DEVFEAT)
}
fn feat_set(mmio: *mut u8, sel: u32, v: u32) {
w32(mmio, DRVFEAT_SEL, sel);
w32(mmio, DRVFEAT, v)
}
fn wmb() {
unsafe { core::arch::asm!("dmb ishst", options(nostack, preserves_flags)) }
}
+153
View File
@@ -0,0 +1,153 @@
use core::ptr;
use crate::bindings::{GMutex, GRecMutex};
use crate::gthread;
static mut MALLOC_LOCK: GRecMutex = unsafe { core::mem::zeroed() };
#[unsafe(no_mangle)]
pub extern "C" fn __malloc_lock(_reent: *mut core::ffi::c_void) {
gthread::g_rec_mutex_lock(ptr::addr_of_mut!(MALLOC_LOCK));
}
#[unsafe(no_mangle)]
pub extern "C" fn __malloc_unlock(_reent: *mut core::ffi::c_void) {
gthread::g_rec_mutex_unlock(ptr::addr_of_mut!(MALLOC_LOCK));
}
#[unsafe(no_mangle)]
pub extern "C" fn _fini() {
}
#[unsafe(no_mangle)]
pub extern "C" fn _exit(_status: i32) -> ! {
loop {}
}
#[unsafe(no_mangle)]
pub extern "C" fn _kill(_pid: i32, _sig: i32) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn _sbrk(incr: isize) -> *mut u8 {
const HEAP_SIZE: usize = 32 * 1024 * 1024; // 32 MB
static mut HEAP_START: *mut u8 = ptr::null_mut();
static mut HEAP_CURRENT: *mut u8 = ptr::null_mut();
static mut HEAP_MUTEX: GMutex = unsafe { core::mem::zeroed() };
unsafe {
gthread::g_mutex_lock(ptr::addr_of_mut!(HEAP_MUTEX));
if HEAP_START.is_null() {
HEAP_START = crate::xnu::kalloc(HEAP_SIZE);
if HEAP_START.is_null() {
gthread::g_mutex_unlock(ptr::addr_of_mut!(HEAP_MUTEX));
panic!("Failed to allocate 32 MB heap");
}
HEAP_CURRENT = HEAP_START;
}
let prev_heap_current = HEAP_CURRENT;
let new_heap_current = HEAP_CURRENT.add(incr as usize);
if new_heap_current > HEAP_START.add(HEAP_SIZE) {
gthread::g_mutex_unlock(ptr::addr_of_mut!(HEAP_MUTEX));
panic!("Heap exhausted: requested {} bytes, {} bytes remaining",
incr,
HEAP_START.add(HEAP_SIZE).offset_from(HEAP_CURRENT));
}
HEAP_CURRENT = new_heap_current;
gthread::g_mutex_unlock(ptr::addr_of_mut!(HEAP_MUTEX));
prev_heap_current
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _getpid() -> i32 {
0
}
#[repr(C)]
struct Timeval {
tv_sec: i64,
tv_usec: i64,
}
#[unsafe(no_mangle)]
pub extern "C" fn _gettimeofday(tp: *mut core::ffi::c_void, _tzp: *mut core::ffi::c_void) -> i32 {
let (secs, microsecs) = crate::xnu::clock_get_calendar_microtime();
let timeval = Timeval {
tv_sec: secs as i64,
tv_usec: microsecs as i64,
};
unsafe {
ptr::copy_nonoverlapping(
&timeval as *const Timeval as *const u8,
tp as *mut u8,
core::mem::size_of::<Timeval>(),
);
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn _isatty(_fd: i32) -> i32 {
1
}
#[unsafe(no_mangle)]
pub extern "C" fn _open(_path: *const u8, _flags: i32, _mode: i32) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn _close(_fd: i32) -> i32 {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn _read(_fd: i32, _buf: *mut u8, _count: usize) -> isize {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn _write(_fd: i32, _buf: *const u8, count: usize) -> isize {
count as isize
}
#[unsafe(no_mangle)]
pub extern "C" fn _lseek(_fd: i32, _offset: isize, _whence: i32) -> isize {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn _fstat(_fd: i32, _st: *mut core::ffi::c_void) -> i32 {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn _link(_old: *const u8, _new: *const u8) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn _unlink(_path: *const u8) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn __clear_cache(_start: *const u8, _end: *const u8) {
// On AArch64, we need to flush the instruction cache
// This is a simplified implementation for the kernel environment
unsafe {
core::arch::asm!(
"ic iallu", // Invalidate all instruction caches
"dsb sy", // Data synchronization barrier
"isb", // Instruction synchronization barrier
options(nomem, nostack),
);
}
}
+544
View File
@@ -0,0 +1,544 @@
#![no_main]
#![no_std]
use alloc::boxed::Box;
use alloc::collections::BTreeMap;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use core::alloc::{GlobalAlloc, Layout};
use core::ffi::{CStr, c_void};
use core::ptr;
use core::ptr::null_mut;
use core::sync::atomic::{AtomicU32, Ordering};
use bindings::{
GBytes, GCancellable, GError, GVariant, GVariantIter, GumScript, g_error_free, g_free,
g_main_context_default, g_main_context_iteration, g_memdup2, g_object_unref,
g_variant_check_format_string, g_variant_get, g_variant_get_child_value, g_variant_get_data,
g_variant_get_size, g_variant_get_string, g_variant_get_uint32, g_variant_get_uint64,
g_variant_iter_init, g_variant_iter_next, g_variant_new, g_variant_new_from_data,
g_variant_new_string, g_variant_new_tuple, g_variant_new_uint32, g_variant_type_free,
g_variant_type_new, g_variant_unref, gchar, gpointer, gsize, gum_script_backend_create_sync,
gum_script_backend_obtain_qjs, gum_script_load_sync, gum_script_post,
gum_script_set_message_handler, gum_script_unload_sync,
};
use hostlink_virtio::Hostlink;
use symbols::SymbolTable;
mod glib;
mod gthread;
mod gum;
mod hostlink_virtio;
mod libc;
mod pac;
mod symbols;
mod xnu;
mod bindings {
#![allow(
dead_code,
improper_ctypes,
non_camel_case_types,
non_snake_case,
non_upper_case_globals,
unused_imports
)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
}
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum FridaCommand {
CreateScript = 1,
LoadScript = 2,
DestroyScript = 3,
PostScriptMessage = 4,
Reply = 128,
ScriptMessage = 129,
}
impl core::fmt::Display for FridaCommand {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
FridaCommand::CreateScript => write!(f, "CreateScript"),
FridaCommand::LoadScript => write!(f, "LoadScript"),
FridaCommand::DestroyScript => write!(f, "DestroyScript"),
FridaCommand::PostScriptMessage => write!(f, "PostScriptMessage"),
FridaCommand::Reply => write!(f, "Reply"),
FridaCommand::ScriptMessage => write!(f, "ScriptMessage"),
}
}
}
#[derive(Debug)]
struct HandlerResponse {
variant: *mut GVariant,
}
impl HandlerResponse {
fn success(variant: *mut GVariant) -> Self {
Self { variant }
}
fn success_empty() -> Self {
let variant = unsafe { g_variant_new_tuple(ptr::null(), 0) };
Self { variant }
}
fn error(message: &str) -> Self {
let mut c_message = String::from(message);
c_message.push('\0');
let error_variant = unsafe { g_variant_new_string(c_message.as_ptr()) };
Self {
variant: error_variant,
}
}
}
static mut CONFIG_DATA: &'static [u8] = &[];
pub static mut MODULE_INFO: Vec<ModuleInfo> = Vec::new();
pub static mut SYMBOL_TABLE: SymbolTable = SymbolTable::empty();
static mut TRANSPORT_DRIVER: *mut Hostlink = core::ptr::null_mut();
#[inline(always)]
fn transport_set(driver: Hostlink) {
unsafe {
let boxed = Box::into_raw(Box::new(driver));
TRANSPORT_DRIVER = boxed;
}
}
#[inline(always)]
fn transport_get_unchecked() -> &'static Hostlink {
unsafe {
debug_assert!(!TRANSPORT_DRIVER.is_null());
&*TRANSPORT_DRIVER
}
}
static mut SCRIPTS: BTreeMap<u32, *mut GumScript> = BTreeMap::new();
static NEXT_SCRIPT_ID: AtomicU32 = AtomicU32::new(1);
#[derive(Debug, Clone)]
pub struct ModuleInfo {
pub name: String,
pub version: String,
pub offset: u32,
pub size: u32,
pub start_func_offset: u32,
pub stop_func_offset: u32,
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn _start(config_data: *const u8, config_size: usize) {
unsafe {
CONFIG_DATA = core::slice::from_raw_parts(config_data, config_size);
xnu::kernel_thread_start(frida_agent_worker, 12345usize as *mut core::ffi::c_void);
}
}
unsafe extern "C" fn frida_agent_worker(_parameter: *mut core::ffi::c_void, _wait_result: i32) {
unsafe {
bindings::g_set_panic_handler(Some(frida_panic_handler), ptr::null_mut());
bindings::gum_init_embedded();
bindings::g_log_set_default_handler(Some(frida_log_handler), ptr::null_mut());
let (mmio, irq, kernel_base, module_info, symbol_table) =
parse_config(core::ptr::addr_of!(CONFIG_DATA).read());
xnu::set_kernel_base(kernel_base);
MODULE_INFO = module_info;
SYMBOL_TABLE = symbol_table;
transport_set(
Hostlink::init(
mmio,
irq,
Some(on_frame_from_host),
ptr::addr_of_mut!(glib::WAKEUP_TOKEN) as *const u8,
)
.unwrap(),
);
let main_context = g_main_context_default();
loop {
transport_get_unchecked().process();
g_main_context_iteration(main_context, 1);
}
}
}
fn on_frame_from_host(frame: &[u8]) {
if let Some(variant) = deserialize_message(&frame) {
process_incoming_message(variant);
unsafe {
g_variant_unref(variant);
}
}
}
unsafe fn parse_config(config: &[u8]) -> (u64, u32, u64, Vec<ModuleInfo>, SymbolTable) {
unsafe {
let type_string = c"(tuta(ssuuuu)ay)".as_ptr() as *const gchar;
let variant_type = g_variant_type_new(type_string);
let root_variant = g_variant_new_from_data(
variant_type,
config.as_ptr() as *const core::ffi::c_void,
config.len() as gsize,
1,
None,
ptr::null_mut(),
);
let hostlink_mmio_variant = g_variant_get_child_value(root_variant, 0);
let hostlink_mmio = g_variant_get_uint64(hostlink_mmio_variant);
let hostlink_irq_variant = g_variant_get_child_value(root_variant, 1);
let hostlink_irq = g_variant_get_uint32(hostlink_irq_variant);
let kernel_base_variant = g_variant_get_child_value(root_variant, 2);
let kernel_base = g_variant_get_uint64(kernel_base_variant);
xnu::set_kernel_base(kernel_base);
let module_info_variant = g_variant_get_child_value(root_variant, 3);
let mut iter: GVariantIter = core::mem::zeroed();
g_variant_iter_init(&mut iter as *mut GVariantIter, module_info_variant);
let mut module_info: Vec<ModuleInfo> = Vec::new();
let mut raw_name: *mut gchar = null_mut();
let mut raw_version: *mut gchar = null_mut();
let mut offset: u32 = 0;
let mut size: u32 = 0;
let mut start_func_offset: u32 = 0;
let mut stop_func_offset: u32 = 0;
while g_variant_iter_next(
&mut iter as *mut GVariantIter,
c"(&s&suuuu)".as_ptr(),
&mut raw_name,
&mut raw_version,
&mut offset,
&mut size,
&mut start_func_offset,
&mut stop_func_offset,
) != 0
{
module_info.push(ModuleInfo {
name: String::from(CStr::from_ptr(raw_name).to_str().unwrap()),
version: String::from(CStr::from_ptr(raw_version).to_str().unwrap()),
offset,
size,
start_func_offset,
stop_func_offset,
});
}
let symbol_array_variant = g_variant_get_child_value(root_variant, 4);
let symbol_data_ptr = g_variant_get_data(symbol_array_variant) as *const u8;
let symbol_data_size = g_variant_get_size(symbol_array_variant) as usize;
let symbol_table = SymbolTable::new(core::slice::from_raw_parts(
symbol_data_ptr,
symbol_data_size,
));
g_variant_unref(symbol_array_variant);
g_variant_unref(module_info_variant);
g_variant_unref(kernel_base_variant);
g_variant_unref(hostlink_irq_variant);
g_variant_unref(hostlink_mmio_variant);
g_variant_unref(root_variant);
g_variant_type_free(variant_type);
(
hostlink_mmio,
hostlink_irq,
kernel_base,
module_info,
symbol_table,
)
}
}
unsafe fn serialize_message(variant: *mut GVariant) -> Option<Vec<u8>> {
unsafe {
let size = g_variant_get_size(variant) as usize;
if size == 0 {
return None;
}
let data_ptr = g_variant_get_data(variant) as *const u8;
let mut result = Vec::with_capacity(size);
result.resize(size, 0);
core::ptr::copy_nonoverlapping(data_ptr, result.as_mut_ptr(), size);
Some(result)
}
}
fn deserialize_message(data: &[u8]) -> Option<*mut GVariant> {
unsafe {
if data.is_empty() {
return None;
}
let variant_type = g_variant_type_new(c"(yqv)".as_ptr());
let data_copy = g_memdup2(data.as_ptr() as *const c_void, data.len() as u64);
let variant = g_variant_new_from_data(
variant_type,
data_copy,
data.len() as u64,
0,
Some(g_free),
data_copy,
);
g_variant_type_free(variant_type);
if variant.is_null() {
None
} else {
Some(variant)
}
}
}
fn process_incoming_message(variant: *mut GVariant) {
{
let mut cmd_value: u8 = 0;
let mut request_id: u16 = 0;
let mut payload_variant: *mut GVariant = ptr::null_mut();
let cmd = unsafe {
g_variant_get(
variant,
c"(yqv)".as_ptr(),
&mut cmd_value,
&mut request_id,
&mut payload_variant,
);
core::mem::transmute::<u8, FridaCommand>(cmd_value)
};
let response = match cmd {
FridaCommand::CreateScript => handle_create_script(payload_variant),
FridaCommand::LoadScript => handle_load_script(payload_variant),
FridaCommand::DestroyScript => handle_destroy_script(payload_variant),
FridaCommand::PostScriptMessage => handle_post_script_message(payload_variant),
_ => HandlerResponse::error("Unknown command"),
};
send_command_reply(request_id, response);
unsafe { g_variant_unref(payload_variant) };
}
}
fn send_command_reply(request_id: u16, response: HandlerResponse) {
unsafe {
let message = g_variant_new(
c"(yqv)".as_ptr(),
FridaCommand::Reply as u8 as u32,
request_id as u32,
response.variant,
);
if let Some(serialized) = serialize_message(message) {
transport_get_unchecked().send(&serialized);
}
g_variant_unref(message);
}
}
fn handle_create_script(payload_variant: *mut GVariant) -> HandlerResponse {
unsafe {
if g_variant_check_format_string(payload_variant, c"s".as_ptr(), 0) == 0 {
return HandlerResponse::error("Invalid payload format: expected string");
}
let source = g_variant_get_string(payload_variant, core::ptr::null_mut());
let backend = gum_script_backend_obtain_qjs();
let cancellable: *mut GCancellable = ptr::null_mut();
let mut error: *mut GError = ptr::null_mut();
let script = gum_script_backend_create_sync(
backend,
c"agent.js".as_ptr(),
source,
ptr::null_mut(),
cancellable,
&mut error,
);
if !error.is_null() {
let error_msg = CStr::from_ptr((*error).message).to_str().unwrap();
let error_string = String::from(error_msg);
g_error_free(error);
return HandlerResponse::error(&error_string);
}
let script_id = NEXT_SCRIPT_ID.fetch_add(1, Ordering::Relaxed);
let script_id_ptr = Box::into_raw(Box::new(script_id));
gum_script_set_message_handler(
script,
Some(frida_message_handler),
script_id_ptr as *mut c_void,
None,
);
core::ptr::addr_of_mut!(SCRIPTS)
.as_mut()
.unwrap()
.insert(script_id, script);
HandlerResponse::success(g_variant_new_uint32(script_id))
}
}
unsafe extern "C" fn frida_message_handler(
message: *const gchar,
_data: *mut GBytes,
user_data: gpointer,
) {
unsafe {
let script_id = *(user_data as *const u32);
let message_variant = g_variant_new(
c"(yqv)".as_ptr(),
FridaCommand::ScriptMessage as u8 as u32,
0u32,
g_variant_new(c"(us)".as_ptr(), script_id, message),
);
if let Some(serialized) = serialize_message(message_variant) {
transport_get_unchecked().send(&serialized);
}
g_variant_unref(message_variant);
}
}
fn handle_load_script(payload_variant: *mut GVariant) -> HandlerResponse {
unsafe {
if g_variant_check_format_string(payload_variant, c"u".as_ptr(), 0) == 0 {
return HandlerResponse::error("Invalid payload format: expected uint32");
}
let script_id = g_variant_get_uint32(payload_variant);
let Some(script) = get_script_by_id(script_id) else {
return HandlerResponse::error(&format!("Script with ID {} not found", script_id));
};
gum_script_load_sync(script, ptr::null_mut());
HandlerResponse::success_empty()
}
}
fn handle_destroy_script(payload_variant: *mut GVariant) -> HandlerResponse {
unsafe {
if g_variant_check_format_string(payload_variant, c"u".as_ptr(), 0) == 0 {
return HandlerResponse::error("Invalid payload format: expected uint32");
}
let script_id = g_variant_get_uint32(payload_variant);
let scripts = core::ptr::addr_of_mut!(SCRIPTS).as_mut().unwrap();
let Some(script) = scripts.remove(&script_id) else {
return HandlerResponse::error(&format!("Script with ID {} not found", script_id));
};
gum_script_unload_sync(script, ptr::null_mut());
g_object_unref(script as *mut c_void);
HandlerResponse::success_empty()
}
}
fn handle_post_script_message(payload_variant: *mut GVariant) -> HandlerResponse {
unsafe {
if g_variant_check_format_string(payload_variant, c"(us)".as_ptr(), 0) == 0 {
return HandlerResponse::error("Invalid payload format: expected (uint32, string)");
}
let mut script_id: u32 = 0;
let mut message: *const gchar = ptr::null();
g_variant_get(
payload_variant,
c"(u&s)".as_ptr(),
&mut script_id,
&mut message,
);
let Some(script) = get_script_by_id(script_id) else {
return HandlerResponse::error(&format!("Script with ID {} not found", script_id));
};
gum_script_post(script, message, ptr::null_mut());
HandlerResponse::success_empty()
}
}
unsafe fn get_script_by_id(script_id: u32) -> Option<*mut GumScript> {
unsafe {
let scripts = core::ptr::addr_of_mut!(SCRIPTS).as_mut().unwrap();
scripts.get(&script_id).copied()
}
}
unsafe extern "C" fn frida_panic_handler(message: *const u8, _user_data: *mut core::ffi::c_void) {
let msg = unsafe { core::ffi::CStr::from_ptr(message).to_str().unwrap() };
panic!("[Frida] {}", msg);
}
unsafe extern "C" fn frida_log_handler(
_log_domain: *const core::ffi::c_char,
_log_level: i32,
message: *const core::ffi::c_char,
_user_data: *mut core::ffi::c_void,
) {
let msg = unsafe { core::ffi::CStr::from_ptr(message).to_str().unwrap() };
kprintln!("[Frida] {}", msg);
}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo<'_>) -> ! {
let mut s = format!("{}", info);
s.push('\0');
xnu::panic(s.as_str());
loop {}
}
pub struct XnuAllocator;
unsafe impl GlobalAlloc for XnuAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
xnu::kalloc(layout.size())
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
xnu::free(ptr, layout.size());
}
}
#[global_allocator]
static GLOBAL: XnuAllocator = XnuAllocator;
extern crate alloc;
#[macro_export]
macro_rules! kprintln {
($($arg:tt)*) => {{
use core::fmt::Write;
let mut buf = alloc::string::String::new();
write!(&mut buf, $($arg)*).unwrap();
buf.push('\n');
buf.push('\0');
crate::xnu::io_log(&buf)
}};
}
+28
View File
@@ -0,0 +1,28 @@
use core::arch::asm;
pub unsafe fn ptrauth_sign(ptr: *const u8, discriminator: usize) -> *const u8 {
let signed: usize;
unsafe {
asm!(
".inst 0xdac10020", // pacia x0, x1
in("x0") ptr as usize,
in("x1") discriminator,
lateout("x0") signed,
options(nomem, nostack),
);
}
signed as *const u8
}
pub unsafe fn ptrauth_strip_data(ptr: *const u8) -> *const u8 {
let stripped: usize;
unsafe {
asm!(
".inst 0xdac143e0", // xpaci x0
in("x0") ptr as usize,
lateout("x0") stripped,
options(nomem, nostack),
);
}
stripped as *const u8
}
+443
View File
@@ -0,0 +1,443 @@
use crate::bindings::{
GPatternSpec, g_pattern_spec_free, g_pattern_spec_match_string, g_pattern_spec_new,
};
use crate::xnu::get_kernel_base;
use alloc::ffi::CString;
use core::cmp::Ordering;
use core::ffi::{CStr, c_char};
use core::mem::size_of;
use core::ptr;
pub struct SymbolTable {
data: &'static [u8],
symbol_count: usize,
}
impl SymbolTable {
pub const fn empty() -> Self {
Self {
data: &[],
symbol_count: 0,
}
}
pub fn new(data: &'static [u8]) -> Self {
if data.len() < 4 {
return Self::empty();
}
let symbol_count = unsafe { *(data.as_ptr() as *const u32) as usize };
Self { data, symbol_count }
}
pub fn is_empty(&self) -> bool {
self.symbol_count == 0
}
pub fn symbol_count(&self) -> usize {
self.symbol_count
}
pub fn find_symbol_by_name(&self, name: &str) -> Option<SymbolRef> {
let (_, entry) = self.binary_search_by_name(name)?;
Some(SymbolRef {
symbol_table: self,
entry,
kernel_base: get_kernel_base(),
})
}
pub fn find_symbols_by_name(&self, name: &str) -> SymbolsByNameIterator {
let (found_index, _) = match self.binary_search_by_name(name) {
Some(result) => result,
None => return SymbolsByNameIterator::empty(self),
};
let mut start = found_index;
let mut end = found_index;
while start > 0 {
let entry = self.get_symbol_entry_by_name_index(start - 1);
let symbol_name = entry.name(self);
if symbol_name == name {
start -= 1;
} else {
break;
}
}
while end + 1 < self.symbol_count {
let entry = self.get_symbol_entry_by_name_index(end + 1);
let symbol_name = entry.name(self);
if symbol_name == name {
end += 1;
} else {
break;
}
}
SymbolsByNameIterator {
symbol_table: self,
current_index: start,
end_index: end + 1,
kernel_base: get_kernel_base(),
}
}
pub fn find_symbols_matching_glob(&self, pattern: &str) -> SymbolsMatchingIterator {
if self.is_empty() {
return SymbolsMatchingIterator::empty(self);
}
let pattern_cstr = CString::new(pattern).unwrap();
let pspec = unsafe { g_pattern_spec_new(pattern_cstr.as_ptr()) };
SymbolsMatchingIterator {
symbol_table: self,
current_index: 0,
end_index: self.symbol_count,
pspec,
kernel_base: get_kernel_base(),
}
}
pub fn find_symbol_by_address(&self, address: u64) -> Option<SymbolRef> {
let target_offset = (address - get_kernel_base()) as u32;
let (_, entry) = self.binary_search_by_address(target_offset)?;
Some(SymbolRef {
symbol_table: self,
entry,
kernel_base: get_kernel_base(),
})
}
pub fn find_symbol_name_ptr_by_address(&self, address: u64) -> *const c_char {
let target_offset = (address - get_kernel_base()) as u32;
if let Some((_, entry)) = self.binary_search_by_address(target_offset) {
entry.name_ptr(self)
} else {
ptr::null()
}
}
pub fn find_closest_symbol_by_address(&self, address: u64) -> Option<SymbolRef> {
let kernel_base = get_kernel_base();
let target_offset = (address - kernel_base) as u32;
let (_, entry) = self.binary_search_closest_by_address(target_offset)?;
Some(SymbolRef {
symbol_table: self,
entry,
kernel_base,
})
}
pub fn iter_symbols_in_range(&self, start_address: u64, end_address: u64) -> SymbolsInRangeIterator {
if self.is_empty() {
return SymbolsInRangeIterator {
symbol_table: self,
current_index: 0,
end_index: 0,
end_offset: 0,
kernel_base: get_kernel_base(),
};
}
let kernel_base = get_kernel_base();
let start_offset = (start_address - kernel_base) as u32;
let end_offset = (end_address - kernel_base) as u32;
let mut left = 0;
let mut right = self.symbol_count;
while left < right {
let mid = left + (right - left) / 2;
let entry = self.get_symbol_entry_by_address_index(mid);
if entry.address_offset < start_offset {
left = mid + 1;
} else {
right = mid;
}
}
while left > 0 {
let prev_entry = self.get_symbol_entry_by_address_index(left - 1);
if prev_entry.address_offset >= start_offset {
left -= 1;
} else {
break;
}
}
SymbolsInRangeIterator {
symbol_table: self,
current_index: left,
end_index: self.symbol_count,
end_offset,
kernel_base,
}
}
fn get_symbol_entry_by_name_index(&self, index: usize) -> &SymbolEntry {
let offset = self.name_index_start() + index * 4;
let symbol_offset = unsafe { *(self.data.as_ptr().add(offset) as *const u32) as usize };
self.symbol_entry_at(symbol_offset)
}
fn get_symbol_entry_by_address_index(&self, index: usize) -> &SymbolEntry {
let offset = self.address_index_start() + index * 4;
let symbol_offset = unsafe { *(self.data.as_ptr().add(offset) as *const u32) as usize };
self.symbol_entry_at(symbol_offset)
}
fn symbol_entry_at(&self, offset: usize) -> &SymbolEntry {
unsafe { &*(self.data.as_ptr().add(offset) as *const SymbolEntry) }
}
fn name_index_start(&self) -> usize {
4
}
fn address_index_start(&self) -> usize {
4 + self.symbol_count * 4
}
fn binary_search_by_name(&self, name: &str) -> Option<(usize, &SymbolEntry)> {
self.binary_search(
|table, mid| table.get_symbol_entry_by_name_index(mid),
|entry| entry.name(self).cmp(name),
false,
)
}
fn binary_search_by_address(&self, target_offset: u32) -> Option<(usize, &SymbolEntry)> {
self.binary_search(
|table, mid| table.get_symbol_entry_by_address_index(mid),
|entry| entry.address_offset.cmp(&target_offset),
false,
)
}
fn binary_search_closest_by_address(
&self,
target_offset: u32,
) -> Option<(usize, &SymbolEntry)> {
self.binary_search(
|table, mid| table.get_symbol_entry_by_address_index(mid),
|entry| entry.address_offset.cmp(&target_offset),
true,
)
}
fn binary_search<F, C>(
&self,
get_entry_fn: F,
compare_fn: C,
find_closest: bool,
) -> Option<(usize, &SymbolEntry)>
where
F: Fn(&Self, usize) -> &SymbolEntry,
C: Fn(&SymbolEntry) -> Ordering,
{
if self.is_empty() {
return None;
}
let mut left = 0;
let mut right = self.symbol_count;
let mut best_match: Option<(usize, &SymbolEntry)> = None;
while left < right {
let mid = left + (right - left) / 2;
let entry = get_entry_fn(self, mid);
match compare_fn(entry) {
Ordering::Equal => return Some((mid, entry)),
Ordering::Less => {
if find_closest {
best_match = Some((mid, entry));
}
left = mid + 1;
}
Ordering::Greater => right = mid,
}
}
best_match
}
}
#[repr(C)]
struct SymbolEntry {
address_offset: u32,
symbol_type: u8,
section: u8,
description: u16,
}
impl SymbolEntry {
fn name<'a>(&self, symbol_table: &'a SymbolTable) -> &'a str {
unsafe {
CStr::from_ptr(self.name_ptr(symbol_table))
.to_str()
.unwrap()
}
}
fn name_ptr(&self, symbol_table: &SymbolTable) -> *const c_char {
let entry_ptr = self as *const SymbolEntry as *const u8;
let data_start = symbol_table.data.as_ptr();
let entry_offset = unsafe { entry_ptr.offset_from(data_start) as usize };
let name_start = entry_offset + size_of::<SymbolEntry>();
unsafe { symbol_table.data.as_ptr().add(name_start) as *const c_char }
}
}
pub struct SymbolRef<'a> {
symbol_table: &'a SymbolTable,
entry: &'a SymbolEntry,
kernel_base: u64,
}
impl<'a> SymbolRef<'a> {
pub fn name(&self) -> &'a str {
self.entry.name(self.symbol_table)
}
pub fn name_ptr(&self) -> *const c_char {
self.entry.name_ptr(self.symbol_table)
}
pub fn address(&self) -> u64 {
self.kernel_base + self.entry.address_offset as u64
}
pub fn symbol_type(&self) -> u8 {
self.entry.symbol_type
}
pub fn section(&self) -> u8 {
self.entry.section
}
pub fn description(&self) -> u16 {
self.entry.description
}
}
pub struct SymbolsByNameIterator<'a> {
symbol_table: &'a SymbolTable,
current_index: usize,
end_index: usize,
kernel_base: u64,
}
impl<'a> SymbolsByNameIterator<'a> {
fn empty(symbol_table: &'a SymbolTable) -> Self {
Self {
symbol_table,
current_index: 0,
end_index: 0,
kernel_base: get_kernel_base(),
}
}
}
impl<'a> Iterator for SymbolsByNameIterator<'a> {
type Item = SymbolRef<'a>;
fn next(&mut self) -> Option<Self::Item> {
if self.current_index == self.end_index {
return None;
}
let entry = self.symbol_table.get_symbol_entry_by_name_index(self.current_index);
self.current_index += 1;
Some(SymbolRef {
symbol_table: self.symbol_table,
entry,
kernel_base: self.kernel_base,
})
}
}
pub struct SymbolsMatchingIterator<'a> {
symbol_table: &'a SymbolTable,
current_index: usize,
end_index: usize,
pspec: *mut GPatternSpec,
kernel_base: u64,
}
impl<'a> SymbolsMatchingIterator<'a> {
fn empty(symbol_table: &'a SymbolTable) -> Self {
Self {
symbol_table,
current_index: 0,
end_index: 0,
pspec: ptr::null_mut(),
kernel_base: get_kernel_base(),
}
}
}
impl<'a> Iterator for SymbolsMatchingIterator<'a> {
type Item = SymbolRef<'a>;
fn next(&mut self) -> Option<Self::Item> {
while self.current_index != self.end_index {
let entry = self.symbol_table.get_symbol_entry_by_name_index(self.current_index);
self.current_index += 1;
if unsafe { g_pattern_spec_match_string(self.pspec, entry.name_ptr(self.symbol_table)) } != 0 {
return Some(SymbolRef {
symbol_table: self.symbol_table,
entry,
kernel_base: self.kernel_base,
});
}
}
None
}
}
impl<'a> Drop for SymbolsMatchingIterator<'a> {
fn drop(&mut self) {
if !self.pspec.is_null() {
unsafe { g_pattern_spec_free(self.pspec) };
}
}
}
pub struct SymbolsInRangeIterator<'a> {
symbol_table: &'a SymbolTable,
current_index: usize,
end_index: usize,
end_offset: u32,
kernel_base: u64,
}
impl<'a> Iterator for SymbolsInRangeIterator<'a> {
type Item = SymbolRef<'a>;
fn next(&mut self) -> Option<Self::Item> {
while self.current_index != self.end_index {
let entry = self.symbol_table.get_symbol_entry_by_address_index(self.current_index);
self.current_index += 1;
if entry.address_offset >= self.end_offset {
break;
}
return Some(SymbolRef {
symbol_table: self.symbol_table,
entry,
kernel_base: self.kernel_base,
});
}
None
}
}
+239
View File
@@ -0,0 +1,239 @@
use core::ffi::c_void;
use core::sync::atomic::{AtomicU64, Ordering};
static KERNEL_BASE: AtomicU64 = AtomicU64::new(0);
pub fn get_kernel_base() -> u64 {
KERNEL_BASE.load(Ordering::Relaxed)
}
pub fn set_kernel_base(base: u64) {
KERNEL_BASE.store(base, Ordering::Relaxed);
}
type ContinuationFn = unsafe extern "C" fn(_parameter: *mut c_void, _wait_result: i32);
unsafe extern "C" {
static _panic: unsafe extern "C" fn(*const u8);
static _IOLog: unsafe extern "C" fn(*const u8, ...);
static _kalloc: unsafe extern "C" fn(usize) -> *mut u8;
static _kfree: unsafe extern "C" fn(*mut u8, usize) -> *mut u8;
static _kernel_thread_start:
unsafe extern "C" fn(*const (), *mut c_void, *mut *mut c_void) -> isize;
static _assert_wait: unsafe extern "C" fn(*const u8, u32) -> i32;
static _assert_wait_timeout: unsafe extern "C" fn(*const u8, u32, u32, u32) -> i32;
static _thread_block: unsafe extern "C" fn(Option<ContinuationFn>) -> i32;
static _thread_wakeup: unsafe extern "C" fn(*const u8) -> i32;
static _mach_absolute_time: unsafe extern "C" fn() -> u64;
static _absolutetime_to_nanoseconds: unsafe extern "C" fn(u64, *mut u64);
static _clock_get_calendar_microtime: unsafe extern "C" fn(*mut u32, *mut u32);
static _ml_io_map: unsafe extern "C" fn(u64, u64) -> *mut c_void;
static _ml_vtophys: unsafe extern "C" fn(u64) -> u64;
static __ZN9IOService11getPlatformEv: unsafe extern "C" fn() -> *mut c_void;
static __ZN9IOServiceC2Ev: unsafe extern "C" fn(*mut core::ffi::c_void);
static __ZN8OSSymbol17withCStringNoCopyEPKc:
unsafe extern "C" fn(*const core::ffi::c_char) -> *const OSSymbol;
static __ZN6OSData9withBytesEPKvj:
unsafe extern "C" fn(*const core::ffi::c_void, u32) -> *mut OSData;
}
const IO_SERVICE_VTABLE_LENGTH: isize = 168;
const VT_LOOKUP_IC: isize = IO_SERVICE_VTABLE_LENGTH + 25; // IOPlatformExpert
const VT_REGISTER_INT: isize = IO_SERVICE_VTABLE_LENGTH + 0; // IOInterruptController
const VT_ENABLE_INT: isize = IO_SERVICE_VTABLE_LENGTH + 3; // IOInterruptController
pub fn panic(msg: &str) {
unsafe { _panic(msg.as_ptr()) };
}
pub fn io_log(msg: &str) {
unsafe {
_IOLog(msg.as_ptr());
}
}
pub fn kalloc(size: usize) -> *mut u8 {
unsafe { _kalloc(size) }
}
pub fn free(ptr: *mut u8, size: usize) {
unsafe {
_kfree(ptr, size);
}
}
pub fn kernel_thread_start(continuation: ContinuationFn, thread_parameter: *mut c_void) -> isize {
let mut new_thread: *mut c_void = core::ptr::null_mut();
return unsafe {
let ptr = crate::pac::ptrauth_sign(continuation as *const u8, 0xd507);
_kernel_thread_start(
core::mem::transmute(ptr),
thread_parameter,
&mut new_thread as *mut *mut c_void,
)
// TODO: Call thread_deallocate()
};
}
pub const THREAD_INTERRUPTIBLE: u32 = 1;
pub const THREAD_WAITING: i32 = -1;
pub fn assert_wait(event: *const u8, interruptible: u32) -> i32 {
unsafe { _assert_wait(event, interruptible) }
}
pub fn assert_wait_timeout(
event: *const u8,
interruptible: u32,
interval: u32,
scale_factor: u32,
) -> i32 {
unsafe { _assert_wait_timeout(event, interruptible, interval, scale_factor) }
}
pub fn thread_block(continuation: Option<ContinuationFn>) -> i32 {
unsafe { _thread_block(continuation) }
}
pub fn thread_wakeup(event: *const u8) -> i32 {
unsafe { _thread_wakeup(event) }
}
pub fn mach_absolute_time() -> u64 {
unsafe { _mach_absolute_time() }
}
pub fn absolutetime_to_nanoseconds(abstime: u64) -> u64 {
let mut nanoseconds: u64 = 0;
unsafe {
_absolutetime_to_nanoseconds(abstime, &mut nanoseconds);
}
nanoseconds
}
pub fn clock_get_calendar_microtime() -> (u32, u32) {
let mut secs: u32 = 0;
let mut microsecs: u32 = 0;
unsafe {
_clock_get_calendar_microtime(&mut secs, &mut microsecs);
}
(secs, microsecs)
}
pub fn ml_io_map(phys_addr: u64, size: u64) -> *mut c_void {
unsafe { _ml_io_map(phys_addr, size) }
}
pub fn ml_vtophys(vaddr: u64) -> u64 {
unsafe { _ml_vtophys(vaddr) }
}
pub type IOInterruptHandler =
extern "C" fn(target: *mut c_void, refcon: *mut c_void, nub: *mut c_void, source: i32);
pub fn install_interrupt_handler(
irq: u32,
target: *mut c_void,
handler: IOInterruptHandler,
refcon: *mut c_void,
) -> i32 {
let pe = unsafe { __ZN9IOService11getPlatformEv() };
let name =
unsafe { __ZN8OSSymbol17withCStringNoCopyEPKc(c"IOInterruptController0000001A".as_ptr()) };
let lookup: extern "C" fn(*mut IOPlatformExpert, *mut OSSymbol) -> *mut IOInterruptController =
vf(pe as _, VT_LOOKUP_IC);
let ic = lookup(pe as *mut IOPlatformExpert, name as *mut OSSymbol);
if ic.is_null() {
panic!("Failed to lookup IOInterruptController");
}
let nub = kalloc(0x88);
unsafe {
core::ptr::write_bytes(nub, 0, 0x88);
__ZN9IOServiceC2Ev(nub as *mut core::ffi::c_void);
}
type IOServiceInitFn = unsafe extern "C" fn(*mut c_void, *mut c_void) -> bool;
let init_fn: IOServiceInitFn = vf(nub as *mut c_void, 21);
unsafe { init_fn(nub as *mut c_void, core::ptr::null_mut()) };
let interrupt_sources =
kalloc(core::mem::size_of::<IOInterruptSource>()) as *mut IOInterruptSource;
let source_bytes = irq.to_ne_bytes();
let vector_data =
unsafe { __ZN6OSData9withBytesEPKvj(source_bytes.as_ptr() as *const core::ffi::c_void, 4) };
unsafe {
(*interrupt_sources).interrupt_controller = ic;
(*interrupt_sources).vector_data = vector_data;
}
unsafe {
let interrupt_sources_ptr = (nub as *mut u8).offset(0x80) as *mut *mut IOInterruptSource;
*interrupt_sources_ptr = interrupt_sources;
}
let reg: extern "C" fn(
*mut _,
*mut c_void,
i32,
*mut c_void,
IOInterruptHandler,
*mut c_void,
) -> i32 = vf(ic as _, VT_REGISTER_INT);
let signed_handler = unsafe {
let handler_ptr = crate::pac::ptrauth_sign(handler as *const u8, 0xd36);
core::mem::transmute::<*const u8, IOInterruptHandler>(handler_ptr)
};
let kr = reg(ic, nub as *mut c_void, 0, target, signed_handler, refcon);
if kr != 0 {
return kr;
}
let en: extern "C" fn(*mut _, *mut c_void, i32) -> i32 = vf(ic as _, VT_ENABLE_INT);
en(ic, nub as *mut c_void, 0)
}
#[repr(C)]
struct IOPlatformExpert {
_p: [u8; 0],
}
#[repr(C)]
struct IOInterruptController {
_p: [u8; 0],
}
#[repr(C)]
struct OSSymbol {
_p: [u8; 0],
}
#[repr(C)]
struct OSData {
_p: [u8; 0],
}
#[repr(C)]
struct IOInterruptSource {
interrupt_controller: *mut IOInterruptController,
vector_data: *mut OSData,
}
#[inline(always)]
fn vf<T>(obj: *mut c_void, slot: isize) -> T
where
T: Copy,
{
let vtable_ptr = unsafe { *(obj as *const *const usize) };
let vtable = unsafe { crate::pac::ptrauth_strip_data(vtable_ptr as *const u8) as *const usize };
let entry = unsafe { *vtable.offset(slot) };
let entry_ptr = unsafe { crate::pac::ptrauth_strip_data(entry as *const u8) };
unsafe { core::mem::transmute_copy::<*const u8, T>(&entry_ptr) }
}
+22
View File
@@ -0,0 +1,22 @@
#!/bin/bash
QEMUAppleSilicon/build/qemu-system-aarch64 \
-M t8030,memory-backend=ram,trustcache=iPhone11_8_iPhone12_1_14.0_18A5351d_Restore/Firmware/038-44135-124.dmg.trustcache,ticket=root_ticket.der,sep-fw=sep-firmware.n104.RELEASE.new.img4,sep-rom=AppleSEPROM-Cebu-B1,kaslr-off=true,usb-conn-type=unix,usb-conn-addr=/Users/oleavr/src/frida-barebone-ios/usb.socket \
-object memory-backend-file,id=ram,size=4G,mem-path="/Volumes/RAM Disk/ios-dram",share=on \
-kernel iPhone11_8_iPhone12_1_14.0_18A5351d_Restore/kernelcache.research.iphone12b \
-dtb iPhone11_8_iPhone12_1_14.0_18A5351d_Restore/Firmware/all_flash/DeviceTree.n104ap.im4p \
-append "tlto_us=-1 mtxspin=-1 agm-genuine=1 agm-authentic=1 agm-trusted=1 serial=3 launchd_unsecure_cache=1 wdt=-1" \
-smp 7 \
-drive file=sep_nvram,if=pflash,format=qcow2 \
-drive file=sep_ssc,if=pflash,format=qcow2 \
-drive file=nvme.1,format=qcow2,if=none,id=drive.1 -device nvme-ns,drive=drive.1,bus=nvme-bus.0,nsid=1,nstype=1,logical_block_size=4096,physical_block_size=4096 \
-drive file=nvme.2,format=qcow2,if=none,id=drive.2 -device nvme-ns,drive=drive.2,bus=nvme-bus.0,nsid=2,nstype=2,logical_block_size=4096,physical_block_size=4096 \
-drive file=nvme.3,format=qcow2,if=none,id=drive.3 -device nvme-ns,drive=drive.3,bus=nvme-bus.0,nsid=3,nstype=3,logical_block_size=4096,physical_block_size=4096 \
-drive file=nvme.4,format=qcow2,if=none,id=drive.4 -device nvme-ns,drive=drive.4,bus=nvme-bus.0,nsid=4,nstype=4,logical_block_size=4096,physical_block_size=4096 \
-drive file=nvram,if=none,format=qcow2,id=nvram -device apple-nvram,drive=nvram,bus=nvme-bus.0,nsid=5,nstype=5,id=nvram,logical_block_size=4096,physical_block_size=4096 \
-drive file=nvme.6,format=qcow2,if=none,id=drive.6 -device nvme-ns,drive=drive.6,bus=nvme-bus.0,nsid=6,nstype=6,logical_block_size=4096,physical_block_size=4096 \
-drive file=nvme.7,format=qcow2,if=none,id=drive.7 -device nvme-ns,drive=drive.7,bus=nvme-bus.0,nsid=7,nstype=8,logical_block_size=4096,physical_block_size=4096 \
-initrd iPhone11_8_iPhone12_1_14.0_18A5351d_Restore/038-44135-124.dmg \
-serial mon:stdio \
-monitor tcp:127.0.0.1:9001,server,nowait \
-gdb tcp::9000
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,40 @@
<dict ID="0">
<key>IOPlatformInterruptController</key>
<string ID="1">IOCPUInterruptController is not serializable</string>
<key>IOInterruptController0000001A</key>
<string ID="2">AppleInterruptController is not serializable</string>
<key>IOInterruptController00000048</key>
<string ID="3">AppleS5L8960XGPIOIC is not serializable</string>
<key>IOInterruptController00000047</key>
<string ID="4">AppleS5L8960XGPIOIC is not serializable</string>
<key>IOInterruptController00000023</key>
<string ID="5">AppleS5L8960XGPIOIC is not serializable</string>
<key>IOInterruptController00000049</key>
<string ID="6">AppleS5L8960XGPIOIC is not serializable</string>
<key>IOInterruptController0000008B</key>
<string ID="7">AppleT8015SPMIController is not serializable</string>
<key>IOInterruptController00000084</key>
<string ID="8">AppleT8015SPMIController is not serializable</string>
<key>ApplePCIEMSIController-apcie</key>
<string ID="9">ApplePCIEMSIController is not serializable</string>
<key>AppleEmbeddedPCIEPort2</key>
<string ID="10">AppleT8030PCIePort is not serializable</string>
<key>AppleEmbeddedPCIEPort3</key>
<string ID="11">AppleT8030PCIePort is not serializable</string>
<key>IOInterruptController00000087</key>
<string ID="12">AppleT8015SPMIController is not serializable</string>
<key>IOInterruptController0000005F</key>
<string ID="13">AppleSMCPMU is not serializable</string>
</dict>
+33
View File
@@ -0,0 +1,33 @@
const osSerialize_withCapacity = new NativeFunction(ptr('0xfffffff007fbf074'), 'pointer', ['uint']);
const UNPAC_MASK = ptr('0xfffffff000000000');
const serializer = osSerialize_withCapacity(4096);
const gIOInterruptControllers = ptr('0xfffffff009a3f090').readPointer();
const serialize = new NativeFunction(implFor(6, vtableOf(gIOInterruptControllers)), 'uint' /* 'bool' */, ['pointer', 'pointer']);
console.log('serialize() is at:', serialize.handle);
const result = serialize(gIOInterruptControllers, serializer);
console.log('result returned:', result);
const getText = new NativeFunction(implFor(15, vtableOf(serializer)), 'pointer', ['pointer']);
const text = getText(serializer);
console.log('Got text:', text.readUtf8String(text));
const release = new NativeFunction(implFor(5, vtableOf(serializer)), 'void', ['pointer']);
release(serializer);
Object.assign(globalThis, { osSerialize_withCapacity });
function implFor(n, vtable) {
return unpac(vtable.add(n * Process.pointerSize).readPointer());
}
function vtableOf(instance) {
return unpac(instance.readPointer());
}
function unpac(p) {
return p.or(UNPAC_MASK);
}
@@ -0,0 +1,37 @@
import os
import r2pipe
def scan_files_for_pattern(directory, hex_pattern, file_extension=None):
search_command = "/xj " + hex_pattern.strip()
for root, _, files in os.walk(directory):
for filename in files:
if file_extension and not filename.endswith(file_extension):
continue
file_path = os.path.join(root, filename)
try:
r2 = r2pipe.open(file_path, flags=["-n"])
r2.cmd("e io.cache=true")
result = r2.cmdj(search_command)
if result:
print(f"Found in {file_path}:")
for match in result:
print(f" - Offset: 0x{match['offset']:x}")
r2.quit()
except Exception as e:
print(f" Error processing {file_path}: {e}")
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Scan dump files for a hex pattern using r2pipe.")
parser.add_argument("directory", help="Directory containing dump files")
parser.add_argument("pattern", help="Hex pattern to search (e.g. 'ff ff ff ff')")
parser.add_argument("--ext", help="Only scan files with this extension", default=None)
args = parser.parse_args()
scan_files_for_pattern(args.directory, args.pattern, args.ext)
+140
View File
@@ -0,0 +1,140 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse
import re
import subprocess
import sys
def main():
parser = argparse.ArgumentParser(description="Symbolicate panic backtrace from kernel log")
parser.add_argument("--log-file", type=argparse.FileType("r"), required=True,
help="Path to the kernel log file")
parser.add_argument("--elf-file", type=str, required=True,
help="Path to the ELF file for symbolication")
parser.add_argument("--base-address", type=lambda x: int(x, 16), required=True,
help="Base address where the ELF was loaded (e.g., 0xffffffe8195f0000)")
parser.add_argument("--verbose", "-v", action="store_true",
help="Show all backtrace frames, not just symbolicated ones")
args = parser.parse_args()
log_content = args.log_file.read()
args.log_file.close()
frames = find_last_panic(log_content)
if not frames:
print("No panic found in the log file", file=sys.stderr)
return 1
print(f"Found last panic with {len(frames)} backtrace frames")
print(f"ELF base address: 0x{args.base_address:x}")
print(f"ELF file: {args.elf_file}")
print()
symbolicated_count = 0
for i, (lr_str, fp_str) in enumerate(frames):
lr_addr = int(lr_str, 16)
#fp_addr = int(fp_str, 16)
if lr_addr >= args.base_address:
function, location = symbolicate_address(args.elf_file, lr_addr, args.base_address)
offset = lr_addr - args.base_address
print(f"Frame {i:2d}: lr: {lr_str} (offset: 0x{offset:x}) fp: {fp_str}")
# Color the function name based on resolution status
if function == "<unknown>":
colored_function = Colors.bold_yellow(function)
elif function == "<error>":
colored_function = Colors.bold_red(function)
else:
colored_function = Colors.bold_green(function)
print(f" -> {colored_function}")
print(f" {location}")
print()
symbolicated_count += 1
elif args.verbose:
print(f"Frame {i:2d}: lr: {lr_str} fp: {fp_str} (outside ELF range)")
if symbolicated_count == 0:
print("No addresses found within the ELF address range")
if not args.verbose:
print("Use --verbose to see all frames")
else:
print(f"Symbolicated {symbolicated_count} frame(s)")
return 0
def find_last_panic(log_content: str):
panic_pattern = re.compile(
r"panic\(.*?\n.*?Panicked thread:.*?\n((?:\s*lr:\s*0x[0-9a-fA-F]+\s*fp:\s*0x[0-9a-fA-F]+\n)*)",
re.DOTALL
)
panics = list(panic_pattern.finditer(log_content))
if not panics:
return None
last_panic = panics[-1]
backtrace_section = last_panic.group(1)
lr_fp_pattern = re.compile(r"lr:\s*(0x[0-9a-fA-F]+)\s*fp:\s*(0x[0-9a-fA-F]+)")
frames = lr_fp_pattern.findall(backtrace_section)
return frames
def symbolicate_address(elf_path: str, address: int, base_address: int):
try:
offset = address - base_address
result = subprocess.run([
"aarch64-none-elf-addr2line", "-e", elf_path, "-f", "-C", f"0x{offset:x}"
], capture_output=True, text=True, check=True)
lines = result.stdout.strip().split("\n")
if len(lines) >= 2:
function = lines[0]
location = lines[1]
if function == "??":
function = "<unknown>"
if location == "??:0" or location == "??:?":
location = "<unknown>"
return function, location
else:
return "<unknown>", "<unknown>"
except (subprocess.CalledProcessError, FileNotFoundError):
return "<error>", "<error>"
class Colors:
BOLD = "\033[1m"
GREEN = "\033[32m"
YELLOW = "\033[33m"
RED = "\033[31m"
RESET = "\033[0m"
@staticmethod
def bold_green(text: str):
return f"{Colors.BOLD}{Colors.GREEN}{text}{Colors.RESET}"
@staticmethod
def bold_yellow(text: str):
return f"{Colors.BOLD}{Colors.YELLOW}{text}{Colors.RESET}"
@staticmethod
def bold_red(text: str):
return f"{Colors.BOLD}{Colors.RED}{text}{Colors.RESET}"
if __name__ == "__main__":
sys.exit(main())
+8 -4
View File
@@ -54,7 +54,7 @@ namespace Frida.Barebone {
public PhysicalAllocator (Machine machine, size_t page_size, PhysicalAllocatorConfig config) {
this.machine = machine;
this._page_size = page_size;
this.cursor = config.physical_base;
this.cursor = config.physical_base.address;
}
public async Allocation allocate (size_t size, size_t alignment, Cancellable? cancellable) throws Error, IOError {
@@ -65,7 +65,11 @@ namespace Frida.Barebone {
uint num_pages = (uint) (vm_size / _page_size);
Allocation page_allocation = yield machine.allocate_pages (address_pa, num_pages, cancellable);
var physical_addresses = new Gee.ArrayList<uint64?> ();
for (uint i = 0; i != num_pages; i++)
physical_addresses.add (address_pa + (i * _page_size));
Allocation page_allocation = yield machine.allocate_pages (physical_addresses, cancellable);
return new PhysicalAllocation (page_allocation);
}
@@ -114,7 +118,7 @@ namespace Frida.Barebone {
}
public async Allocation allocate (size_t size, size_t alignment, Cancellable? cancellable) throws Error, IOError {
uint64 address = yield machine.invoke (config.alloc_function, { size }, cancellable);
uint64 address = yield machine.invoke (config.alloc_function.address, { size }, cancellable);
// TODO: Handle alignment.
@@ -147,7 +151,7 @@ namespace Frida.Barebone {
}
public async void deallocate (Cancellable? cancellable) throws Error, IOError {
yield machine.invoke (config.free_function, { _virtual_address, size }, cancellable);
yield machine.invoke (config.free_function.address, { _virtual_address, size }, cancellable);
}
}
}
+5 -1
View File
@@ -26,12 +26,16 @@ namespace Frida.Barebone {
return 4096;
}
public async uint query_exception_level (Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
public async void enumerate_ranges (Gum.PageProtection prot, FoundRangeFunc func, Cancellable? cancellable)
throws Error, IOError {
throw_not_supported ();
}
public async Allocation allocate_pages (uint64 physical_address, uint num_pages, Cancellable? cancellable)
public async Allocation allocate_pages (Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
throws Error, IOError {
throw_not_supported ();
}
+28 -13
View File
@@ -42,6 +42,17 @@ namespace Frida.Barebone {
return p.granule;
}
public async uint query_exception_level (Cancellable? cancellable) throws Error, IOError {
GDB.Exception? exception = gdb.exception;
if (exception == null)
throw new Error.INVALID_OPERATION ("Unable to query in current state");
GDB.Thread thread = exception.thread;
var cpsr = yield thread.read_register ("cpsr", cancellable);
return (uint) ((cpsr >> 2) & 3);
}
public async void enumerate_ranges (Gum.PageProtection prot, FoundRangeFunc func, Cancellable? cancellable)
throws Error, IOError {
Gee.List<RangeDetails> ranges = ("corellium" in gdb.features)
@@ -152,14 +163,14 @@ namespace Frida.Barebone {
return result;
}
public async Allocation allocate_pages (uint64 physical_address, uint num_pages, Cancellable? cancellable)
public async Allocation allocate_pages (Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
throws Error, IOError {
MMUParameters p = yield MMUParameters.load (gdb, cancellable);
yield set_addressing_mode (gdb, PHYSICAL, cancellable);
try {
Allocation? allocation = yield maybe_insert_descriptor_in_table (physical_address, num_pages, p.tt1,
p.first_level, p.upper_bits, p, cancellable);
Allocation? allocation = yield maybe_insert_descriptor_in_table (physical_addresses, p.tt1, p.first_level,
p.upper_bits, p, cancellable);
if (allocation == null)
throw new Error.NOT_SUPPORTED ("Unable to insert page table mapping; please file a bug");
@@ -169,7 +180,7 @@ namespace Frida.Barebone {
}
}
private async Allocation? maybe_insert_descriptor_in_table (uint64 physical_address, uint num_pages,
private async Allocation? maybe_insert_descriptor_in_table (Gee.List<uint64?> physical_addresses,
uint64 table_address, uint level, uint64 upper_bits, MMUParameters p, Cancellable? cancellable)
throws Error, IOError {
uint max_entries = compute_max_entries (level, p);
@@ -180,6 +191,7 @@ namespace Frida.Barebone {
uint num_available_slots = 0;
uint chunk_max_size = (level < 3) ? 4 : 64;
uint chunk_offset = 0;
uint num_pages = physical_addresses.size;
while (chunk_offset != max_entries && num_available_slots != num_pages) {
uint64 chunk_base_address = table_address + (chunk_offset * Descriptor.SIZE);
uint chunk_size = uint.min (chunk_max_size, max_entries - chunk_offset);
@@ -199,8 +211,8 @@ namespace Frida.Barebone {
continue;
if ((desc.flags & DescriptorFlags.PXNTABLE) != 0)
continue;
Allocation? allocation = yield maybe_insert_descriptor_in_table (physical_address,
num_pages, desc.target_address, level + 1, address, p, cancellable);
Allocation? allocation = yield maybe_insert_descriptor_in_table (physical_addresses,
desc.target_address, level + 1, address, p, cancellable);
if (allocation != null)
return allocation;
continue;
@@ -231,11 +243,8 @@ namespace Frida.Barebone {
var builder = gdb.make_buffer_builder ();
for (uint i = 0; i != num_available_slots; i++) {
uint64 cur_physical_address = physical_address + (i * p.granule);
uint64 base_descriptor = cur_physical_address | 0x403ULL;
uint64 base_descriptor = physical_addresses[(int) i] | 0x403ULL;
uint64 new_descriptor = apply_protection_bits (base_descriptor, page_prot, p);
builder.append_uint64 (new_descriptor);
}
Bytes new_descriptors = builder.build ();
@@ -422,9 +431,11 @@ namespace Frida.Barebone {
Cancellable? cancellable) throws Error, IOError {
unowned uint8[] scanner_blob = Data.Barebone.get_memory_scanner_arm64_elf_blob ().data;
var raw_scanner = new Bytes.static (scanner_blob);
Gum.ElfModule scanner;
try {
scanner = new Gum.ElfModule.from_blob (new Bytes.static (scanner_blob));
scanner = new Gum.ElfModule.from_blob (raw_scanner);
} catch (Gum.Error e) {
throw new Error.NOT_SUPPORTED ("%s", e.message);
}
@@ -483,13 +494,17 @@ namespace Frida.Barebone {
uint64 module_page_pa = page_start (module_pa, page_size);
Allocation module_allocation = yield allocate_pages (module_page_pa, num_pages, cancellable);
var module_pas = new Gee.ArrayList<uint64?> ();
for (uint i = 0; i != num_pages; i++)
module_pas.add (module_page_pa + (i * page_size)); // FIXME: This assumes a contiguous range.
Allocation module_allocation = yield allocate_pages (module_pas, cancellable);
size_t page_offset = (size_t) (module_pa - module_page_pa);
uint64 module_va = module_allocation.virtual_address + page_offset;
uint64 data_va = module_va + data_offset;
Bytes scanner_module = relocate (scanner, module_va);
Bytes scanner_module = relocate (scanner, raw_scanner, module_va);
Bytes original_memory = yield gdb.read_byte_array (module_va, vm_size, cancellable);
yield gdb.write_byte_array (module_va, scanner_module, cancellable);
+5 -1
View File
@@ -22,11 +22,15 @@ namespace Frida.Barebone {
return 4096;
}
public async uint query_exception_level (Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
public async void enumerate_ranges (Gum.PageProtection prot, FoundRangeFunc func, Cancellable? cancellable)
throws Error, IOError {
}
public async Allocation allocate_pages (uint64 physical_address, uint num_pages, Cancellable? cancellable)
public async Allocation allocate_pages (Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
throws Error, IOError {
throw_not_supported ();
}
+5 -1
View File
@@ -22,12 +22,16 @@ namespace Frida.Barebone {
return 4096;
}
public async uint query_exception_level (Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
public async void enumerate_ranges (Gum.PageProtection prot, FoundRangeFunc func, Cancellable? cancellable)
throws Error, IOError {
throw_not_supported ();
}
public async Allocation allocate_pages (uint64 physical_address, uint num_pages, Cancellable? cancellable)
public async Allocation allocate_pages (Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
throws Error, IOError {
throw_not_supported ();
}
+5 -1
View File
@@ -24,12 +24,16 @@ namespace Frida.Barebone {
return 4096;
}
public async uint query_exception_level (Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
public async void enumerate_ranges (Gum.PageProtection prot, FoundRangeFunc func, Cancellable? cancellable)
throws Error, IOError {
throw_not_supported ();
}
public async Allocation allocate_pages (uint64 physical_address, uint num_pages, Cancellable? cancellable)
public async Allocation allocate_pages (Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
throws Error, IOError {
throw_not_supported ();
}
+194 -99
View File
@@ -40,7 +40,8 @@ namespace Frida {
}
}
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
throws Error, IOError {
if (host_session != null)
throw new Error.INVALID_OPERATION ("Already created");
@@ -49,7 +50,9 @@ namespace Frida {
if (config_path != null) {
try {
var config_data = yield FS.read_all_text (File.new_for_path (config_path), cancellable);
config = (Barebone.Config) Json.gobject_from_data (typeof (Barebone.Config), config_data);
var cfg = (Barebone.Config) Json.gobject_from_data (typeof (Barebone.Config), config_data);
cfg.check ();
config = cfg;
} catch (GLib.Error e) {
throw new Error.INVALID_ARGUMENT ("Unable to load %s: %s", config_path, e.message);
}
@@ -106,8 +109,8 @@ namespace Frida {
}
Barebone.Allocator allocator;
Barebone.AllocatorConfig ac = config.allocator;
if (ac is Barebone.NoAllocatorConfig) {
Barebone.AllocatorConfig? ac = config.allocator;
if (ac == null) {
allocator = new Barebone.NullAllocator (page_size);
} else if (ac is Barebone.PhysicalAllocatorConfig) {
allocator = new Barebone.PhysicalAllocator (machine, page_size,
@@ -119,11 +122,18 @@ namespace Frida {
assert_not_reached ();
}
Barebone.AgentConnection? agent_connection = null;
Barebone.AgentConfig? agent_config = config.agent;
if (agent_config != null) {
agent_connection = yield Barebone.AgentConnection.open (agent_config, config.image, machine, allocator,
cancellable);
}
var interceptor = new Barebone.Interceptor (machine, allocator);
var services = new Barebone.Services (machine, allocator, interceptor);
host_session = new BareboneHostSession (services);
host_session = new BareboneHostSession (agent_connection, services);
host_session.agent_session_detached.connect (on_agent_session_detached);
return host_session;
@@ -175,6 +185,11 @@ namespace Frida {
}
public sealed class BareboneHostSession : Object, HostSession {
public Barebone.AgentConnection? connection {
get;
construct;
}
public Barebone.Services services {
get;
construct;
@@ -183,8 +198,8 @@ namespace Frida {
private Gee.Map<AgentSessionId?, BareboneAgentSession> agent_sessions =
new Gee.HashMap<AgentSessionId?, BareboneAgentSession> (AgentSessionId.hash, AgentSessionId.equal);
public BareboneHostSession (Barebone.Services services) {
Object (services: services);
public BareboneHostSession (Barebone.AgentConnection? connection, Barebone.Services services) {
Object (connection: connection, services: services);
}
public async void close (Cancellable? cancellable) throws IOError {
@@ -260,13 +275,17 @@ namespace Frida {
var opts = SessionOptions._deserialize (options);
if (opts.realm == EMULATED)
throw new Error.NOT_SUPPORTED ("Emulated realm is not supported on barebone targets");
throw new Error.NOT_SUPPORTED ("Emulated realm is not supported on Barebone targets");
var session_id = AgentSessionId.generate ();
MainContext dbus_context = yield get_dbus_context ();
var session = new BareboneAgentSession (session_id, opts.persist_timeout, dbus_context, services);
BareboneAgentSession session;
if (connection != null)
session = new RemoteBareboneAgentSession (connection, session_id, opts.persist_timeout, dbus_context);
else
session = new LocalBareboneAgentSession (services, session_id, opts.persist_timeout, dbus_context);
agent_sessions[session_id] = session;
session.closed.connect (on_agent_session_closed);
@@ -326,7 +345,158 @@ namespace Frida {
}
}
private sealed class BareboneAgentSession : Object, AgentSession {
private sealed class LocalBareboneAgentSession : BareboneAgentSession {
public Barebone.Services services {
get;
construct;
}
private Gee.Map<AgentScriptId?, BareboneScript> scripts =
new Gee.HashMap<AgentScriptId?, BareboneScript> (AgentScriptId.hash, AgentScriptId.equal);
private uint next_script_id = 1;
public LocalBareboneAgentSession (Barebone.Services services, AgentSessionId id, uint persist_timeout,
MainContext dbus_context) {
Object (
services: services,
id: id,
persist_timeout: persist_timeout,
frida_context: MainContext.ref_thread_default (),
dbus_context: dbus_context
);
}
public override async AgentScriptId create_script (string source, HashTable<string, Variant> options,
Cancellable? cancellable) throws Error, IOError {
check_open ();
var opts = ScriptOptions._deserialize (options);
if (opts.runtime == V8)
throw new Error.INVALID_ARGUMENT ("The V8 runtime is not supported by the Barebone backend");
var id = AgentScriptId (next_script_id++);
var script = BareboneScript.create (id, source, services);
scripts[id] = script;
script.message.connect (on_message_from_script);
return id;
}
public override async void destroy_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
check_open ();
BareboneScript script = get_script (script_id);
yield script.destroy (cancellable);
script.message.disconnect (on_message_from_script);
scripts.unset (script_id);
}
public override async void load_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
check_open ();
var script = get_script (script_id);
yield script.load (cancellable);
}
public override async void post_messages (AgentMessage[] messages, uint batch_id, Cancellable? cancellable)
throws Error, IOError {
transmitter.check_okay_to_receive ();
foreach (var m in messages) {
switch (m.kind) {
case SCRIPT: {
BareboneScript? script = scripts[m.script_id];
if (script != null)
script.post (m.text, m.has_data ? new Bytes (m.data) : null);
break;
}
case DEBUGGER:
break;
}
}
transmitter.notify_rx_batch_id (batch_id);
}
private void on_message_from_script (BareboneScript script, string json, Bytes? data) {
transmitter.post_message_from_script (script.id, json, data);
}
private BareboneScript get_script (AgentScriptId script_id) throws Error {
var script = scripts[script_id];
if (script == null)
throw new Error.INVALID_ARGUMENT ("Invalid script ID");
return script;
}
}
private sealed class RemoteBareboneAgentSession : BareboneAgentSession {
public Barebone.AgentConnection connection {
get;
construct;
}
public RemoteBareboneAgentSession (Barebone.AgentConnection connection, AgentSessionId id, uint persist_timeout,
MainContext dbus_context) {
Object (
connection: connection,
id: id,
persist_timeout: persist_timeout,
frida_context: MainContext.ref_thread_default (),
dbus_context: dbus_context
);
}
construct {
connection.script_message.connect (on_message_from_script);
}
public override async AgentScriptId create_script (string source, HashTable<string, Variant> options,
Cancellable? cancellable) throws Error, IOError {
check_open ();
var opts = ScriptOptions._deserialize (options);
if (opts.runtime == V8)
throw new Error.INVALID_ARGUMENT ("The V8 runtime is not supported by the Barebone backend");
return yield connection.create_script (source, cancellable);
}
public override async void destroy_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
check_open ();
yield connection.destroy_script (script_id, cancellable);
}
public override async void load_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
check_open ();
yield connection.load_script (script_id, cancellable);
}
public override async void post_messages (AgentMessage[] messages, uint batch_id, Cancellable? cancellable)
throws Error, IOError {
transmitter.check_okay_to_receive ();
foreach (var m in messages) {
switch (m.kind) {
case SCRIPT:
yield connection.post_script_message (m.script_id, m.text,
m.has_data ? new Bytes (m.data) : null, cancellable);
break;
case DEBUGGER:
break;
}
}
transmitter.notify_rx_batch_id (batch_id);
}
private void on_message_from_script (AgentScriptId id, string json, Bytes? data) {
transmitter.post_message_from_script (id, json, data);
}
}
private abstract class BareboneAgentSession : Object, AgentSession {
public signal void closed ();
public AgentSessionId id {
@@ -340,8 +510,12 @@ namespace Frida {
}
public AgentMessageSink? message_sink {
get { return transmitter.message_sink; }
set { transmitter.message_sink = value; }
get {
return transmitter.message_sink;
}
set {
transmitter.message_sink = value;
}
}
public MainContext frida_context {
@@ -354,34 +528,9 @@ namespace Frida {
construct;
}
public Barebone.Services services {
get;
construct;
}
public Barebone.Allocator allocator {
get;
construct;
}
private Promise<bool>? close_request;
private Gee.Map<AgentScriptId?, BareboneScript> scripts =
new Gee.HashMap<AgentScriptId?, BareboneScript> (AgentScriptId.hash, AgentScriptId.equal);
private uint next_script_id = 1;
private AgentMessageTransmitter transmitter;
public BareboneAgentSession (AgentSessionId id, uint persist_timeout, MainContext dbus_context,
Barebone.Services services) {
Object (
id: id,
persist_timeout: persist_timeout,
frida_context: MainContext.ref_thread_default (),
dbus_context: dbus_context,
services: services
);
}
protected AgentMessageTransmitter transmitter;
construct {
assert (frida_context != null);
@@ -426,22 +575,8 @@ namespace Frida {
throw_not_supported ();
}
public async AgentScriptId create_script (string source, HashTable<string, Variant> options,
Cancellable? cancellable) throws Error, IOError {
check_open ();
var opts = ScriptOptions._deserialize (options);
if (opts.runtime == V8)
throw new Error.INVALID_ARGUMENT ("The V8 runtime is not supported by the barebone backend");
var id = AgentScriptId (next_script_id++);
var script = BareboneScript.create (id, source, services);
scripts[id] = script;
script.message.connect (on_message_from_script);
return id;
}
public abstract async AgentScriptId create_script (string source, HashTable<string, Variant> options,
Cancellable? cancellable) throws Error, IOError;
public async AgentScriptId create_script_from_bytes (uint8[] bytes, HashTable<string, Variant> options,
Cancellable? cancellable) throws Error, IOError {
@@ -458,33 +593,14 @@ namespace Frida {
throw_not_supported ();
}
public async void destroy_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
check_open ();
public abstract async void destroy_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError;
BareboneScript script = get_script (script_id);
yield script.destroy (cancellable);
script.message.disconnect (on_message_from_script);
scripts.unset (script_id);
}
public async void load_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
check_open ();
var script = get_script (script_id);
yield script.load (cancellable);
}
public abstract async void load_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError;
public async void eternalize_script (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
private BareboneScript get_script (AgentScriptId script_id) throws Error {
var script = scripts[script_id];
if (script == null)
throw new Error.INVALID_ARGUMENT ("Invalid script ID");
return script;
}
public async void enable_debugger (AgentScriptId script_id, Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
@@ -493,25 +609,8 @@ namespace Frida {
throw_not_supported ();
}
public async void post_messages (AgentMessage[] messages, uint batch_id,
Cancellable? cancellable) throws Error, IOError {
transmitter.check_okay_to_receive ();
foreach (var m in messages) {
switch (m.kind) {
case SCRIPT: {
BareboneScript? script = scripts[m.script_id];
if (script != null)
script.post (m.text, m.has_data ? new Bytes (m.data) : null);
break;
}
case DEBUGGER:
break;
}
}
transmitter.notify_rx_batch_id (batch_id);
}
public abstract async void post_messages (AgentMessage[] messages, uint batch_id, Cancellable? cancellable)
throws Error, IOError;
public async PortalMembershipId join_portal (string address, HashTable<string, Variant> options,
Cancellable? cancellable) throws Error, IOError {
@@ -543,15 +642,11 @@ namespace Frida {
transmitter.commit_migration ();
}
private void check_open () throws Error {
protected void check_open () throws Error {
if (close_request != null)
throw new Error.INVALID_OPERATION ("Session is closing");
}
private void on_message_from_script (BareboneScript script, string json, Bytes? data) {
transmitter.post_message_from_script (script.id, json, data);
}
private void on_transmitter_closed () {
transmitter.closed.disconnect (on_transmitter_closed);
transmitter.new_candidates.disconnect (on_transmitter_new_candidates);
+7 -4
View File
@@ -30,7 +30,11 @@ namespace Frida.Barebone {
throw new Error.NOT_SUPPORTED ("%s", e.message);
}
allocation = yield inject_elf (elf, machine, allocator, cancellable);
var raw_elf = new Bytes.static (elf.get_file_data ());
size_t page_size = yield machine.query_page_size (cancellable);
allocation = yield inject_elf (elf, raw_elf, page_size, machine, allocator, cancellable);
uint64 base_va = allocation.virtual_address;
uint64 console_log_trap = 0;
@@ -38,9 +42,8 @@ namespace Frida.Barebone {
if (e.name == "")
return true;
if (e.name[0] == '_') {
if (e.name == "_console_log")
console_log_trap = base_va + e.address;
if (e.name == "_console_log") {
console_log_trap = base_va + e.address;
return true;
}
+274 -64
View File
@@ -11,9 +11,6 @@ namespace Frida.Barebone {
* "connection": {
* "host": "127.0.0.1",
* "port": 3333
* },
* "allocator": {
* "mode": "none"
* }
* }
*
@@ -41,6 +38,21 @@ namespace Frida.Barebone {
* "free_function": "0xfffffff007a3c338"
* }
* }
*
* 4. Injecting a remote agent:
* {
* "agent": {
* "path": "/path/to/target/aarch64-unknown-none/release/frida-barebone-agent",
* "transport": {
* "type": "hostlink",
* "qmp": "unix:/path/to/qmp.sock"
* }
* },
* "image": {
* "file": "/path/to/kernelcache.research.iphone12b",
* "base": "0xfffffff007004000"
* }
* }
*/
public sealed class Config : Object, Json.Serializable {
public ConnectionConfig connection {
@@ -49,41 +61,54 @@ namespace Frida.Barebone {
default = new ConnectionConfig ();
}
public AllocatorConfig allocator {
public AllocatorConfig? allocator {
get;
set;
default = new NoAllocatorConfig ();
}
public AgentConfig? agent {
get;
set;
}
public ImageConfig? image {
get;
set;
}
public void check () throws Error {
if (allocator != null)
allocator.check ();
if (agent != null)
agent.check ();
if (image != null)
image.check ();
}
public bool deserialize_property (string property_name, out Value value, ParamSpec pspec, Json.Node property_node) {
if (property_name == "allocator" && property_node.get_node_type () == Json.NodeType.OBJECT) {
var obj_node = property_node.get_object ();
string? mode = obj_node.get_string_member ("mode");
Type t = 0;
switch (mode) {
case "none":
t = typeof (NoAllocatorConfig);
break;
case "physical":
t = typeof (PhysicalAllocatorConfig);
break;
case "target-functions":
t = typeof (TargetFunctionsAllocatorConfig);
break;
default:
break;
}
if (t != 0) {
var obj = (AllocatorConfig) Json.gobject_deserialize (t, property_node);
if (obj != null && obj.is_valid) {
var v = Value (t);
v.set_object (obj);
value = v;
return true;
if (property_name == "allocator") {
AllocatorConfig? allocator = null;
Type t = typeof (InvalidAllocatorConfig);
if (property_node.get_node_type () == Json.NodeType.OBJECT) {
switch (property_node.get_object ().get_string_member_with_default ("mode", "invalid")) {
case "physical":
t = typeof (PhysicalAllocatorConfig);
break;
case "target-functions":
t = typeof (TargetFunctionsAllocatorConfig);
break;
default:
break;
}
allocator = (AllocatorConfig) Json.gobject_deserialize (t, property_node);
} else {
allocator = new InvalidAllocatorConfig ();
}
var v = Value (t);
v.set_object (allocator);
value = v;
return true;
}
value = Value (pspec.value_type);
@@ -106,34 +131,32 @@ namespace Frida.Barebone {
}
public abstract class AllocatorConfig : Object {
public abstract bool is_valid {
get;
}
public abstract void check () throws Error;
}
public sealed class NoAllocatorConfig : AllocatorConfig {
public override bool is_valid {
get {
return true;
}
public sealed class InvalidAllocatorConfig : AllocatorConfig {
public override void check () throws Error {
throw new Error.NOT_SUPPORTED ("Config for 'allocator' is invalid");
}
}
public sealed class PhysicalAllocatorConfig : AllocatorConfig, Json.Serializable {
public override bool is_valid {
get {
return true;
}
}
public uint64 physical_base {
public MemoryAddress physical_base {
get;
set;
}
public override void check () throws Error {
if (physical_base == null)
throw new Error.NOT_SUPPORTED ("Config for 'allocator.physical_base' is missing");
physical_base.check ();
}
public bool deserialize_property (string property_name, out Value value, ParamSpec pspec, Json.Node property_node) {
if (try_deserialize_address ("physical_base", property_node, out value))
if (property_name == "physical_base") {
value = deserialize_address ("allocator.physical_base", property_node);
return true;
}
value = Value (pspec.value_type);
return false;
@@ -141,45 +164,232 @@ namespace Frida.Barebone {
}
public sealed class TargetFunctionsAllocatorConfig : AllocatorConfig, Json.Serializable {
public override bool is_valid {
get {
return alloc_function != 0 && free_function != 0;
}
}
public uint64 alloc_function {
public MemoryAddress alloc_function {
get;
set;
}
public uint64 free_function {
public MemoryAddress free_function {
get;
set;
}
public override void check () throws Error {
if (alloc_function == null)
throw new Error.NOT_SUPPORTED ("Config for 'allocator.alloc_function' is missing");
alloc_function.check ();
if (free_function == null)
throw new Error.NOT_SUPPORTED ("Config for 'allocator.free_function' is missing");
free_function.check ();
}
public bool deserialize_property (string property_name, out Value value, ParamSpec pspec, Json.Node property_node) {
if (try_deserialize_address ("alloc_function", property_node, out value))
if (property_name == "alloc-function") {
value = deserialize_address ("allocator.alloc_function", property_node);
return true;
}
if (try_deserialize_address ("free_function", property_node, out value))
if (property_name == "free-function") {
value = deserialize_address ("allocator.free_function", property_node);
return true;
}
value = Value (pspec.value_type);
return false;
}
}
private bool try_deserialize_address (string name, Json.Node node, out Value val) {
val = Value (typeof (uint64));
public sealed class AgentConfig : Object, Json.Serializable {
public string path {
get;
set;
}
if (node.get_value_type () != typeof (string))
public TransportConfig transport {
get;
set;
}
public void check () throws Error {
if (path == null)
throw new Error.NOT_SUPPORTED ("Config for 'agent.path' is missing");
if (transport == null)
throw new Error.NOT_SUPPORTED ("Config for 'agent.transport' is missing");
transport.check ();
}
public bool deserialize_property (string property_name, out Value value, ParamSpec pspec, Json.Node property_node) {
if (property_name == "transport") {
TransportConfig transport;
Type t = typeof (InvalidTransportConfig);
if (property_node.get_node_type () == Json.NodeType.OBJECT) {
switch (property_node.get_object ().get_string_member_with_default ("type", "invalid")) {
case "hostlink":
t = typeof (HostlinkTransportConfig);
break;
default:
break;
}
transport = (TransportConfig) Json.gobject_deserialize (t, property_node);
} else {
transport = new InvalidTransportConfig ();
}
var v = Value (t);
v.set_object (transport);
value = v;
return true;
}
value = Value (pspec.value_type);
return false;
}
}
uint64 v;
if (!uint64.try_parse (node.get_string (), out v, null, 16))
public abstract class TransportConfig : Object {
public abstract void check () throws Error;
}
public sealed class InvalidTransportConfig : TransportConfig {
public override void check () throws Error {
throw new Error.NOT_SUPPORTED ("Config for 'agent.transport' is invalid");
}
}
public sealed class HostlinkTransportConfig : TransportConfig {
public string qmp {
get;
set;
}
public override void check () throws Error {
if (qmp == null)
throw new Error.NOT_SUPPORTED ("Config for 'agent.transport.qmp' is missing");
if (!qmp.has_prefix ("unix:"))
throw new Error.NOT_SUPPORTED ("Config for 'agent.transport.qmp' must be a UNIX socket for now");
}
}
public sealed class ImageConfig : Object, Json.Serializable {
public string file {
get;
set;
}
public MemoryAddress base {
get;
set;
}
public Gee.Map<string, uint64?> symbols {
get;
set;
default = new Gee.HashMap<string, uint64?> ();
}
public void check () throws Error {
if (file == null)
throw new Error.NOT_SUPPORTED ("Config for 'image.file' is missing");
if (@base == null)
throw new Error.NOT_SUPPORTED ("Config for 'image.base' is missing");
@base.check ();
if (symbols == null)
throw new Error.NOT_SUPPORTED ("Config for 'image.symbols' is invalid");
}
public bool deserialize_property (string property_name, out Value value, ParamSpec pspec, Json.Node property_node) {
if (property_name == "base") {
value = deserialize_address ("image.base", property_node);
return true;
}
if (property_name == "symbols") {
Gee.Map<string, uint64?> syms = null;
if (property_node.get_node_type () == Json.NodeType.OBJECT) {
syms = new Gee.HashMap<string, uint64?> ();
property_node.get_object ().foreach_member ((obj, name, node) => {
if (syms == null)
return;
uint64 addr;
if (!try_deserialize_address (node, out addr)) {
syms = null;
return;
}
syms[name] = addr;
});
}
value = syms;
return true;
}
value = Value (pspec.value_type);
return false;
}
}
val = v;
return true;
public abstract class MemoryAddress : Object {
public string label {
get;
construct;
}
public uint64 address {
get;
construct;
}
public abstract void check () throws Error;
}
public sealed class InvalidMemoryAddress : MemoryAddress {
public InvalidMemoryAddress (string label) {
Object (label: label);
}
public override void check () throws Error {
throw new Error.NOT_SUPPORTED ("Config for '%s' is invalid", label);
}
}
public sealed class NonNullMemoryAddress : MemoryAddress {
public NonNullMemoryAddress (string label, uint64 address) {
Object (label: label, address: address);
}
public override void check () throws Error {
if (address == 0)
throw new Error.NOT_SUPPORTED ("Config for '%s' cannot be NULL", label);
}
}
private MemoryAddress deserialize_address (string label, Json.Node node) {
uint64 addr;
if (!try_deserialize_address (node, out addr))
return new InvalidMemoryAddress (label);
return new NonNullMemoryAddress (label, addr);
}
private bool try_deserialize_address (Json.Node node, out uint64 address) {
Type t = node.get_value_type ();
if (t == typeof (string))
return uint64.try_parse (node.get_string (), out address, null, 16);
if (t == typeof (int64)) {
address = node.get_int ();
return true;
}
address = 0;
return false;
}
}
+17 -5
View File
@@ -1,22 +1,34 @@
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
namespace Frida.Barebone {
public async Allocation inject_elf (Gum.ElfModule elf, Machine machine, Allocator allocator, Cancellable? cancellable)
throws Error, IOError {
public async Allocation inject_elf (Gum.ElfModule elf, Bytes raw_elf, size_t page_size, Machine machine, Allocator allocator,
Cancellable? cancellable) throws Error, IOError {
size_t vm_size = (size_t) elf.mapped_size;
size_t page_size = yield machine.query_page_size (cancellable);
uint num_pages = (uint) (vm_size / page_size);
if (vm_size % page_size != 0)
num_pages++;
uint64 text_base = 0;
size_t text_size = 0;
elf.enumerate_segments (s => {
if ((s.protection & Gum.PageProtection.EXECUTE) != 0) {
text_base = s.vm_address;
text_size = (size_t) s.vm_size;
return false;
}
return true;
});
if (text_size == 0)
throw new Error.NOT_SUPPORTED ("Unable to detect text segment");
var allocation = yield allocator.allocate (num_pages * page_size, page_size, cancellable);
try {
uint64 base_va = allocation.virtual_address;
Bytes relocated_image = machine.relocate (elf, base_va);
Bytes relocated_image = machine.relocate (elf, raw_elf, base_va);
yield machine.gdb.write_byte_array (base_va, relocated_image, cancellable);
yield machine.protect_pages (base_va, vm_size, READ | EXECUTE, cancellable);
yield machine.protect_pages (base_va + text_base, text_size, READ | EXECUTE, cancellable);
} catch (GLib.Error e) {
yield allocation.deallocate (cancellable);
throw_api_error (e);
+105
View File
@@ -0,0 +1,105 @@
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
namespace Frida.Barebone.Img4 {
public async Payload parse_file (File f, Cancellable? cancellable) throws Error, IOError {
Bytes blob = yield FS.read_all_bytes (f, cancellable);
return parse (blob);
}
public Payload parse (Bytes blob) throws Error {
BufferReader r = new BufferReader (new Buffer (blob, BIG_ENDIAN));
if (r.read_uint8 () != Der.SEQUENCE)
throw new Error.PROTOCOL ("IMG4/IM4P must start with DER SEQUENCE");
size_t seq_len = Der.read_length (r);
size_t seq_end = r.offset + seq_len;
string first_magic = Der.read_string (r);
if (first_magic == "IMG4") {
if (r.read_uint8 () != Der.SEQUENCE)
throw new Error.PROTOCOL ("Expected SEQUENCE for IM4P");
size_t im4p_len = Der.read_length (r);
seq_end = r.offset + im4p_len;
if (Der.read_string (r) != "IM4P")
throw new Error.PROTOCOL ("Missing IM4P magic");
} else if (first_magic != "IM4P") {
throw new Error.PROTOCOL ("Not an IMG4/IM4P container (magic='%s')", first_magic);
}
string kind = Der.read_string (r);
string description = Der.read_string (r);
Bytes payload_raw = Der.read_octet_string (r);
Bytes final_data = maybe_decompress_lzfse (payload_raw);
return new Payload (kind, description, final_data);
}
public class Payload : Object {
public string kind {
get;
construct;
}
public string description {
get;
construct;
}
public Bytes data {
get;
construct;
}
public Payload (string kind, string description, Bytes data) {
Object (
kind: kind,
description: description,
data: data
);
}
}
namespace Der {
private const uint8 OCTET_STRING = 0x04;
private const uint8 UTF8_STRING = 0x0C;
private const uint8 IA5_STRING = 0x16;
private const uint8 SEQUENCE = 0x30;
private size_t read_length (BufferReader r) throws Error {
uint8 b = r.read_uint8 ();
if ((b & 0x80) == 0)
return b;
uint8 nlen = (uint8) (b & 0x7f);
if (nlen == 0)
throw new Error.NOT_SUPPORTED ("Indefinite length not supported in IMG4");
size_t len = 0;
for (uint8 i = 0; i != nlen; i++)
len = (len << 8) | r.read_uint8 ();
return len;
}
private Bytes read_octet_string (BufferReader r) throws Error {
uint8 tag = r.read_uint8 ();
if (tag != Der.OCTET_STRING)
throw new Error.PROTOCOL ("Expected OCTET STRING, got tag 0x%02x".printf (tag));
size_t len = read_length (r);
return r.read_bytes (len);
}
private string read_string (BufferReader r) throws Error {
uint8 tag = r.read_uint8 ();
if (tag != Der.IA5_STRING && tag != Der.UTF8_STRING)
throw new Error.PROTOCOL ("Unexpected string tag 0x%02x", tag);
size_t len = read_length (r);
return r.read_fixed_string (len);
}
}
private Bytes maybe_decompress_lzfse (Bytes src) throws Error {
if (LZFSE.is_lzfse (src))
return LZFSE.decode (src);
return src;
}
}
+188
View File
@@ -0,0 +1,188 @@
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
namespace Frida.Barebone {
public sealed class Layout : Object {
public Gee.List<ModuleInfo> modules {
get;
construct;
}
public Gee.List<SymbolInfo> symbols {
get;
construct;
}
private const uint64 CHAINED_PTR_TARGET_MASK = (1 << 30) - 1;
private Layout (Gee.List<ModuleInfo> modules, Gee.List<SymbolInfo> symbols) {
Object (modules: modules, symbols: symbols);
}
public Layout.empty () {
Object (modules: new Gee.ArrayList<ModuleInfo> (), symbols: new Gee.ArrayList<SymbolInfo> ());
}
public static async Layout load_from_symbol_source (File symbol_source, uint64 kernel_base, ByteOrder byte_order,
uint pointer_size, Cancellable? cancellable) throws Error, IOError {
var payload = yield Img4.parse_file (symbol_source, cancellable);
Bytes kerncache = payload.data;
Gum.DarwinModule mod;
try {
mod = new Gum.DarwinModule.from_blob (kerncache, ARM64, Gum.PtrauthSupport.SUPPORTED);
} catch (Gum.Error e) {
throw new Error.NOT_SUPPORTED ("%s", e.message);
}
var blob = new Blob (kerncache, byte_order, pointer_size);
var modules = compute_module_list (mod, blob, kernel_base);
var symbols = compute_symbol_list (mod);
return new Layout (modules, symbols);
}
private static Gee.List<ModuleInfo> compute_module_list (Gum.DarwinModule mod, Blob blob, uint64 kernel_base) throws Error {
Buffer? kmod_info = null;
Buffer? kmod_start = null;
Error? pending_error = null;
mod.enumerate_sections (s => {
if (s.segment_name == "__PRELINK_INFO") {
try {
if (s.section_name == "__kmod_info")
kmod_info = blob.slice (s.file_offset, (size_t) s.size, "__kmod_info");
else if (s.section_name == "__kmod_start")
kmod_start = blob.slice (s.file_offset, (size_t) s.size, "__kmod_start");
} catch (Error e) {
pending_error = e;
return false;
}
}
return true;
});
if (pending_error != null)
throw pending_error;
if (kmod_info == null)
throw new Error.NOT_SUPPORTED ("Unable to find __kmod_info");
if (kmod_start == null)
throw new Error.NOT_SUPPORTED ("Unable to find __kmod_start");
var kexts = new Gee.ArrayList<ModuleInfo> ();
var info_pointers = new BufferReader (kmod_info);
while (info_pointers.available != 0) {
var kmodinfo_start = chained_pointer_to_vm_offset (info_pointers.read_pointer (), kernel_base);
var kmodinfo = new BufferReader (blob.slice (kmodinfo_start, 196, "__kmod_info entry"));
kmodinfo.skip (blob.pointer_size + 4 + 4);
string name = kmodinfo.read_fixed_string (64);
string version = kmodinfo.read_fixed_string (64);
kmodinfo.skip (4 + (2 * blob.pointer_size) + 8 + 8);
size_t start = chained_pointer_to_vm_offset (kmodinfo.read_pointer (), kernel_base);
size_t stop = chained_pointer_to_vm_offset (kmodinfo.read_pointer (), kernel_base);
kexts.add (new ModuleInfo () {
name = name,
version = version,
start_func_offset = (uint32) start,
stop_func_offset = (uint32) stop,
});
}
var start_offsets = new Gee.ArrayList<uint> ();
var start_pointers = new BufferReader (kmod_start);
while (start_pointers.available != 0)
start_offsets.add ((uint) chained_pointer_to_vm_offset (start_pointers.read_pointer (), kernel_base));
if (start_offsets.size != kexts.size + 1)
throw new Error.PROTOCOL ("Unexpected __kmod_start length");
var n = kexts.size;
for (var i = 0; i != n; i++) {
var kext = kexts[i];
kext.offset = start_offsets[i];
kext.size = start_offsets[i + 1] - kext.offset;
}
var result = new Gee.ArrayList<ModuleInfo> ();
result.add (new ModuleInfo () {
name = "mach_kernel",
version = mod.source_version,
offset = 0,
size = kexts[0].offset,
});
result.add_all (kexts);
return result;
}
private static size_t chained_pointer_to_vm_offset (uint64 val, uint64 kernel_base) {
var target = (size_t) (val & CHAINED_PTR_TARGET_MASK);
bool is_auth = (val >> 63) != 0;
if (is_auth)
return target;
return target - (size_t) (kernel_base & CHAINED_PTR_TARGET_MASK);
}
private static Gee.List<SymbolInfo> compute_symbol_list (Gum.DarwinModule mod) {
var symbols = new Gee.ArrayList<SymbolInfo> ();
mod.enumerate_symbols (s => {
symbols.add (new SymbolInfo () {
name = (s.name[0] == '_') ? s.name[1:] : s.name,
offset = (uint32) s.address,
symbol_type = s.type,
section = s.section,
description = s.description
});
return true;
});
return symbols;
}
}
public class ModuleInfo {
public string name;
public string version;
public uint32 offset;
public uint32 size;
public uint32 start_func_offset;
public uint32 stop_func_offset;
}
public class SymbolInfo {
public string name;
public uint32 offset;
public uint8 symbol_type;
public uint8 section;
public uint16 description;
}
private class Blob {
private Bytes bytes;
private size_t size;
public ByteOrder byte_order;
public uint pointer_size;
public Blob (Bytes b, ByteOrder o, uint ptr_size) {
bytes = b;
size = b.get_size ();
byte_order = o;
pointer_size = ptr_size;
}
public Buffer slice (size_t offset, size_t n, string label) throws Error {
if (offset >= size)
throw new Error.PROTOCOL ("Offset out of bounds while parsing %s", label);
size_t end = offset + n;
if (end > size)
throw new Error.PROTOCOL ("Size out of bounds while parsing %s", label);
return new Buffer (bytes[offset:end], byte_order, pointer_size);
}
}
}
+23
View File
@@ -0,0 +1,23 @@
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
namespace Frida.Barebone.LZFSE {
public bool is_lzfse (Bytes buf) {
if (buf.get_size () < 4)
return false;
unowned uint8[] d = buf.get_data ();
return d[0] == 'b' && d[1] == 'v' && d[2] == 'x';
}
public Bytes decode (Bytes compressed) throws Error {
size_t est_max = compressed.get_size () * 15;
uint8[] dst_buf = new uint8[est_max];
size_t written = decode_buffer (dst_buf, compressed.get_data ());
if (written == 0)
throw new Error.PROTOCOL ("liblzfse returned 0 bytes");
return new Bytes (dst_buf[:written]);
}
[CCode (cheader_filename = "lzfse.h", cname = "lzfse_decode_buffer")]
private extern size_t decode_buffer ([CCode (array_length_type = "size_t")] uint8[] dst, [CCode (array_length_type = "size_t")] uint8[] src, void * scratch = null);
}
+29 -6
View File
@@ -16,10 +16,33 @@ namespace Frida.Barebone {
public abstract async size_t query_page_size (Cancellable? cancellable) throws Error, IOError;
public abstract async uint query_exception_level (Cancellable? cancellable) throws Error, IOError;
public async void enter_exception_level (uint level, uint timeout, Cancellable? cancellable) throws Error, IOError {
var timer = new Timer ();
do {
var el = yield query_exception_level (cancellable);
if (el == level)
return;
yield gdb.continue (cancellable);
var source = new TimeoutSource (10);
source.set_callback (enter_exception_level.callback);
source.attach (MainContext.get_thread_default ());
yield;
yield gdb.stop (cancellable);
} while ((uint) (timer.elapsed () * 1000.0) < timeout);
throw new Error.TIMED_OUT ("Timed out while trying to get target to exception level %u", level);
}
public abstract async void enumerate_ranges (Gum.PageProtection prot, FoundRangeFunc func, Cancellable? cancellable)
throws Error, IOError;
public abstract async Allocation allocate_pages (uint64 physical_address, uint num_pages, Cancellable? cancellable)
public abstract async Allocation allocate_pages (Gee.List<uint64?> physical_addresses, Cancellable? cancellable)
throws Error, IOError;
public abstract async void protect_pages (uint64 virtual_address, size_t size, Gum.PageProtection prot,
@@ -28,10 +51,10 @@ namespace Frida.Barebone {
public abstract async Gee.List<uint64?> scan_ranges (Gee.List<Gum.MemoryRange?> ranges, MatchPattern pattern,
uint max_matches, Cancellable? cancellable) throws Error, IOError;
public Bytes relocate (Gum.ElfModule module, uint64 base_va) throws Error {
public Bytes relocate (Gum.ElfModule elf, Bytes raw_elf, uint64 base_va) throws Error {
uint64 file_start = uint64.MAX;
uint64 file_end = 0;
module.enumerate_segments (s => {
elf.enumerate_segments (s => {
if (s.file_size != 0) {
file_start = uint64.min (s.file_offset, file_start);
file_end = uint64.max (s.file_offset + s.file_size, file_end);
@@ -39,9 +62,9 @@ namespace Frida.Barebone {
return true;
});
var relocated_buf = gdb.make_buffer (new Bytes (module.get_file_data ()[file_start:file_end]));
var relocated_buf = gdb.make_buffer (new Bytes (raw_elf[(size_t) file_start:(size_t) file_end].get_data ()));
Error? pending_error = null;
module.enumerate_relocations (r => {
elf.enumerate_relocations (r => {
unowned string parent_section = (r.parent != null) ? r.parent.name : "";
if (parent_section == ".rela.text" || parent_section.has_prefix (".rela.debug_"))
return true;
@@ -61,7 +84,7 @@ namespace Frida.Barebone {
Bytes relocated_bytes = relocated_buf.bytes;
Bytes relocated_image = gdb.make_buffer_builder ()
.append_bytes (relocated_bytes)
.skip ((size_t) (module.mapped_size - relocated_bytes.get_size ()))
.skip ((size_t) (elf.mapped_size - relocated_bytes.get_size ()))
.build ();
return relocated_image;
}
+5 -1
View File
@@ -1,6 +1,7 @@
backend_sources += files(
'barebone-host-session.vala',
'config.vala',
'agent-connection.vala',
'script.vala',
'machine.vala',
'allocator.vala',
@@ -10,7 +11,11 @@ backend_sources += files(
'c-module.vala',
'rust-module.vala',
'console.vala',
'layout.vala',
'elf.vala',
'img4.vala',
'lzfse.vala',
'qmp-client.vala',
'arch-x86' / 'machine.vala',
'arch-x86_64' / 'machine.vala',
'arch-arm' / 'machine.vala',
@@ -63,4 +68,3 @@ backend_sources += custom_target('frida-data-barebone',
backend_vala_args += gum_vala_args
backend_vala_args_private += '--pkg=quickjs'
backend_deps += quickjs_dep
+461
View File
@@ -0,0 +1,461 @@
[CCode (gir_namespace = "FridaBarebone", gir_version = "1.0")]
namespace Frida.Barebone {
public sealed class QmpClient : Object, AsyncInitable {
public signal void event (string name, Json.Node? data);
public string address {
get;
construct;
}
public uint16 port {
get;
construct;
}
private SocketConnection connection;
private DataInputStream input;
private OutputStream output;
private bool is_connected = false;
private Promise<bool> close_request = new Promise<bool> ();
private Gee.Map<uint, Promise<Json.Node>> pending_requests = new Gee.HashMap<uint, Promise<Json.Node>> ();
private uint next_request_id = 1;
private Cancellable io_cancellable = new Cancellable ();
private const int REQUEST_TIMEOUT_MS = 30000;
public QmpClient (string? address = null, uint16 port = 0) {
Object (
address: address ?? "localhost",
port: port != 0 ? port : 4444
);
}
public static async QmpClient open (string? address = null, uint16 port = 0, Cancellable? cancellable = null)
throws Error, IOError {
var client = new QmpClient (address, port);
try {
yield client.init_async (Priority.DEFAULT, cancellable);
} catch (GLib.Error e) {
throw_api_error (e);
}
return client;
}
private async bool init_async (int io_priority, Cancellable? cancellable) throws Error, IOError {
var connectable = parse_socket_address (address, port, "localhost", 4444);
try {
var client = new SocketClient ();
connection = yield client.connect_async (connectable, cancellable);
} catch (GLib.Error e) {
throw new Error.TRANSPORT ("Unable to connect to QMP server: %s", e.message);
}
var socket = connection.socket;
if (socket.get_family () != UNIX)
Tcp.enable_nodelay (socket);
input = new DataInputStream (connection.get_input_stream ());
input.set_newline_type (DataStreamNewlineType.LF);
output = connection.get_output_stream ();
bool started_message_processing = false;
try {
is_connected = true;
string? greeting_line = yield input.read_line_async (Priority.DEFAULT, cancellable);
if (greeting_line == null)
throw new Error.TRANSPORT ("Connection closed during QMP greeting");
process_incoming_messages.begin ();
started_message_processing = true;
yield execute_command ("qmp_capabilities", null, cancellable);
return true;
} catch (GLib.Error e) {
is_connected = false;
if (!started_message_processing)
close_request.resolve (true);
if (e is Error)
throw_api_error (e);
else
throw new Error.TRANSPORT ("%s", e.message);
}
}
public async void close (Cancellable? cancellable = null) throws IOError {
io_cancellable.cancel ();
try {
yield close_request.future.wait_async (cancellable);
} catch (Error e) {
assert_not_reached ();
}
}
public async Hostlink open_hostlink (Cancellable? cancellable = null) throws Error, IOError {
#if WINDOWS
throw new Error.NOT_SUPPORTED ("Missing open_hostlink() for Windows");
#else
uint64 mmio = (uint64) yield get_qom_property_int ("/machine", "hostlink-mmio", cancellable);
uint irq = (uint) yield get_qom_property_int ("/machine", "hostlink-irq", cancellable);
string bus = yield get_qom_property_string ("/machine", "hostlink-bus", cancellable);
int fds[2];
if (Posix.socketpair (Posix.AF_UNIX, Posix.SOCK_STREAM, 0, fds) != 0)
throw new Error.NOT_SUPPORTED ("Unable to allocate socketpair");
Socket local_sock, remote_sock;
try {
local_sock = new Socket.from_fd (fds[0]);
remote_sock = new Socket.from_fd (fds[1]);
} catch (GLib.Error e) {
throw new Error.TRANSPORT ("%s", e.message);
}
string fd_name = "appfd";
yield getfd (fd_name, fds[1], cancellable);
string chardev = "vserial0";
yield add_chardev_from_fd (chardev, fd_name, cancellable);
yield add_serial_port (chardev, bus, "re.frida.hostlink", "hostlink.port", 1, cancellable);
return new Hostlink () {
connection = SocketConnection.factory_create_connection (local_sock),
mmio = mmio,
irq = irq,
};
#endif
}
public class Hostlink {
public SocketConnection connection;
public uint64 mmio;
public uint irq;
}
#if !WINDOWS
private async int64 get_qom_property_int (string path, string property, Cancellable? cancellable) throws Error, IOError {
var val = yield get_qom_property (path, property, cancellable);
if (val.get_value_type () != typeof (int64))
throw new Error.PROTOCOL ("Expected '%s' property on %s to be an integer", property, path);
return val.get_int ();
}
private async string get_qom_property_string (string path, string property, Cancellable? cancellable)
throws Error, IOError {
var val = yield get_qom_property (path, property, cancellable);
if (val.get_value_type () != typeof (string))
throw new Error.PROTOCOL ("Expected '%s' property on %s to be a string", property, path);
return val.get_string ();
}
private async Json.Node get_qom_property (string path, string property, Cancellable? cancellable) throws Error, IOError {
var args = new Json.Builder ();
args
.begin_object ()
.set_member_name ("path")
.add_string_value (path)
.set_member_name ("property")
.add_string_value (property)
.end_object ();
return yield execute_command ("qom-get", args.get_root (), cancellable);
}
private async void getfd (string name, int fd, Cancellable? cancellable) throws Error, IOError {
var args = new Json.Builder ();
args
.begin_object ()
.set_member_name ("fdname")
.add_string_value (name)
.end_object ();
yield execute_command_with_file_descriptor ("getfd", args.get_root (), fd, cancellable);
}
private async void add_chardev_from_fd (string id, string fd_name, Cancellable? cancellable) throws Error, IOError {
var args = new Json.Builder ();
args
.begin_object ()
.set_member_name ("id")
.add_string_value (id)
.set_member_name ("backend")
.begin_object ()
.set_member_name ("type")
.add_string_value ("socket")
.set_member_name ("data")
.begin_object ()
.set_member_name ("server")
.add_boolean_value (false)
.set_member_name ("addr")
.begin_object ()
.set_member_name ("type")
.add_string_value ("fd")
.set_member_name ("data")
.begin_object ()
.set_member_name ("str")
.add_string_value (fd_name)
.end_object ()
.end_object ()
.end_object ()
.end_object ()
.end_object ();
yield execute_command ("chardev-add", args.get_root (), cancellable);
}
private async void add_serial_port (string chardev, string bus, string name, string id, uint nr, Cancellable? cancellable)
throws Error, IOError {
var args = new Json.Builder ();
args
.begin_object ()
.set_member_name ("driver")
.add_string_value ("virtserialport")
.set_member_name ("chardev")
.add_string_value (chardev)
.set_member_name ("bus")
.add_string_value (bus)
.set_member_name ("name")
.add_string_value (name)
.set_member_name ("id")
.add_string_value (id)
.set_member_name ("nr")
.add_int_value ((int64) nr)
.end_object ();
yield execute_command ("device_add", args.get_root (), cancellable);
}
#endif
public async Json.Node execute_command (string command, Json.Node? arguments = null, Cancellable? cancellable = null)
throws Error, IOError {
check_connected ();
Request request = begin_request (command, arguments);
try {
yield output.write_all_async (request.json.data, Priority.DEFAULT, cancellable, null);
} catch (GLib.Error e) {
cancel_request (request);
throw new Error.TRANSPORT ("%s", e.message);
}
return yield join_request (request, cancellable);
}
public async Json.Node execute_command_with_file_descriptor (string command, Json.Node? arguments = null, int fd,
Cancellable? cancellable = null) throws Error, IOError {
check_connected ();
#if WINDOWS
throw new Error.NOT_SUPPORTED ("Executing command with SocketControlMessage is not supported on Windows");
#else
Request request = begin_request (command, arguments);
var scm = new UnixFDMessage ();
try {
scm.append_fd (fd);
} catch (GLib.Error e) {
throw new Error.NOT_SUPPORTED ("%s", e.message);
}
Socket s = connection.get_socket ();
unowned uint8[] raw_command = request.json.data;
OutputVector vectors[1] = {
OutputVector () {
buffer = raw_command,
size = raw_command.length,
},
};
SocketControlMessage scms[1] = { scm };
ssize_t n;
try {
n = s.send_message (null, vectors, scms, 0, cancellable);
} catch (GLib.Error e) {
cancel_request (request);
throw new Error.TRANSPORT ("%s", e.message);
}
if (n != raw_command.length) {
try {
yield output.write_all_async (raw_command[n:], Priority.DEFAULT, cancellable, null);
} catch (GLib.Error e) {
cancel_request (request);
throw new Error.TRANSPORT ("%s", e.message);
}
}
return yield join_request (request, cancellable);
#endif
}
private Request begin_request (string command, Json.Node? arguments) {
uint id = next_request_id++;
var promise = new Promise<Json.Node> ();
pending_requests[id] = promise;
string json = build_request (command, id, arguments);
return new Request () {
promise = promise,
json = json,
id = id,
};
}
private static string build_request (string command, uint id, Json.Node? arguments) {
var b = new Json.Builder ();
b
.begin_object ()
.set_member_name ("execute")
.add_string_value (command);
if (arguments != null) {
b
.set_member_name ("arguments")
.add_value (arguments);
}
b
.set_member_name ("id")
.add_int_value ((int64) id);
Json.Node message = b.end_object ().get_root ();
return Json.to_string (message, false) + "\n";
}
private async Json.Node join_request (Request request, Cancellable? cancellable) throws Error, IOError {
var timeout_source = new TimeoutSource (REQUEST_TIMEOUT_MS);
timeout_source.set_callback (() => {
Promise<Json.Node>? p;
if (pending_requests.unset (request.id, out p))
p.reject (new Error.TIMED_OUT ("QMP command timed out"));
return Source.REMOVE;
});
timeout_source.attach (MainContext.get_thread_default ());
try {
return yield request.promise.future.wait_async (cancellable);
} finally {
timeout_source.destroy ();
}
}
private void cancel_request (Request r) {
pending_requests.unset (r.id);
}
private class Request {
public Promise<Json.Node> promise;
public string json;
public uint id;
}
private void check_connected () throws Error {
if (!is_connected)
throw new Error.INVALID_OPERATION ("QMP client is not connected");
}
private async void process_incoming_messages () {
try {
while (is_connected) {
string? line = yield input.read_line_async (Priority.DEFAULT, io_cancellable);
if (line == null)
break;
handle_message (Json.from_string (line));
}
} catch (GLib.Error e) {
} finally {
is_connected = false;
foreach (var promise in pending_requests.values)
promise.reject (new Error.TRANSPORT ("QMP connection closed"));
pending_requests.clear ();
}
io_cancellable.cancel ();
var source = new IdleSource ();
source.set_callback (process_incoming_messages.callback);
source.attach (MainContext.get_thread_default ());
yield;
try {
yield connection.close_async ();
} catch (GLib.Error e) {
}
close_request.resolve (true);
}
private void handle_message (Json.Node message) throws Error {
var r = new Json.Reader (message);
bool is_response = r.read_member ("id");
uint id = 0;
if (is_response) {
id = (uint) r.get_int_value ();
GLib.Error? e = r.get_error ();
if (e != null)
throw new Error.PROTOCOL ("Malformed message: %s", e.message);
}
r.end_member ();
if (is_response)
handle_response (id, r);
else
handle_event (r);
}
private void handle_response (uint request_id, Json.Reader r) throws Error {
Promise<Json.Node>? promise;
if (!pending_requests.unset (request_id, out promise))
return;
if (r.read_member ("error")) {
r.read_member ("desc");
string? description = r.get_string_value ();
if (description == null)
throw new Error.PROTOCOL ("Malformed message: %s", r.get_error ().message);
r.end_member ();
promise.reject (new Error.NOT_SUPPORTED ("%s", description));
return;
}
r.end_member ();
r.read_member ("return");
promise.resolve (r.get_current_node ());
}
private void handle_event (Json.Reader r) throws Error {
r.read_member ("event");
string? name = r.get_string_value ();
if (name == null)
throw new Error.PROTOCOL ("Malformed event message: missing 'event' property");
r.end_member ();
Json.Node? data = null;
if (r.read_member ("data")) {
if (!r.is_object ())
throw new Error.PROTOCOL ("Malformed event message: 'data' must be an object");
data = r.get_current_node ();
}
r.end_member ();
event (name, data);
}
}
}
+5 -1
View File
@@ -84,7 +84,11 @@ namespace Frida.Barebone {
throw new Error.NOT_SUPPORTED ("%s", e.message);
}
allocation = yield inject_elf (elf, machine, allocator, cancellable);
var raw_elf = new Bytes.static (elf.get_file_data ());
size_t page_size = yield machine.query_page_size (cancellable);
allocation = yield inject_elf (elf, raw_elf, page_size, machine, allocator, cancellable);
uint64 base_va = allocation.virtual_address;
uint64 console_log_trap = 0;
+13 -12
View File
@@ -845,8 +845,8 @@ namespace Frida {
construct;
}
public LaunchdAgent (DarwinHostSession host_session, Cancellable io_cancellable) {
Object (host_session: host_session, io_cancellable: io_cancellable);
public LaunchdAgent (HostSessionConnection connection, Cancellable io_cancellable) {
Object (connection: connection, io_cancellable: io_cancellable);
}
construct {
@@ -905,7 +905,7 @@ namespace Frida {
try {
if (path == XPC_PROXY_PATH) {
var agent = new XpcProxyAgent ((DarwinHostSession) host_session, identifier, pid);
var agent = new XpcProxyAgent (connection, identifier, pid);
yield agent.run_until_exec (io_cancellable);
}
@@ -921,7 +921,7 @@ namespace Frida {
try {
if (path == XPC_PROXY_PATH) {
var agent = new XpcProxyAgent ((DarwinHostSession) host_session, identifier, pid);
var agent = new XpcProxyAgent (connection, identifier, pid);
yield agent.run_until_exec (io_cancellable);
}
@@ -937,7 +937,8 @@ namespace Frida {
protected override async string? load_source (Cancellable? cancellable) throws Error, IOError {
string * raw_source = Frida.Data.Darwin.get_launchd_js_blob ().data;
return raw_source->replace ("@REPORT_CRASHES@", ((DarwinHostSession) host_session).report_crashes.to_string ());
return raw_source->replace ("@REPORT_CRASHES@",
((DarwinHostSession) connection.host_session).report_crashes.to_string ());
}
}
@@ -952,8 +953,8 @@ namespace Frida {
construct;
}
public XpcProxyAgent (DarwinHostSession host_session, string identifier, uint pid) {
Object (host_session: host_session, identifier: identifier, pid: pid);
public XpcProxyAgent (HostSessionConnection connection, string identifier, uint pid) {
Object (connection: connection, identifier: identifier, pid: pid);
}
construct {
@@ -965,7 +966,7 @@ namespace Frida {
public async void run_until_exec (Cancellable? cancellable) throws Error, IOError {
yield ensure_loaded (cancellable);
var helper = ((DarwinHostSession) host_session).helper;
var helper = ((DarwinHostSession) connection.host_session).helper;
yield helper.resume (pid, cancellable);
yield wait_for_unload (cancellable);
@@ -1002,10 +1003,10 @@ namespace Frida {
construct;
}
public ReportCrashAgent (DarwinHostSession host_session, uint pid, MappedAgentContainer mapped_agent_container,
public ReportCrashAgent (HostSessionConnection connection, uint pid, MappedAgentContainer mapped_agent_container,
Cancellable io_cancellable) {
Object (
host_session: host_session,
connection: connection,
pid: pid,
mapped_agent_container: mapped_agent_container,
io_cancellable: io_cancellable
@@ -1226,9 +1227,9 @@ namespace Frida {
construct;
}
public OSAnalyticsAgent (DarwinHostSession host_session, uint pid, Cancellable io_cancellable) {
public OSAnalyticsAgent (HostSessionConnection connection, uint pid, Cancellable io_cancellable) {
Object (
host_session: host_session,
connection: connection,
pid: pid,
io_cancellable: io_cancellable
);
+52 -93
View File
@@ -30,7 +30,6 @@
#include <sys/mman.h>
#include <sys/sysctl.h>
#include <unistd.h>
#include <util.h>
#ifndef _POSIX_SPAWN_DISABLE_ASLR
# define _POSIX_SPAWN_DISABLE_ASLR 0x0100
@@ -181,6 +180,7 @@ struct _FridaSpawnInstance
mach_vm_address_t dyld_data;
GumAddress modern_entry_address;
GumAddress sim_notify_address;
/* V4+ */
GumAddress notify_objc_init;
@@ -405,9 +405,6 @@ static void frida_spawn_instance_disable_nth_breakpoint (FridaSpawnInstance * se
static guint32 frida_spawn_instance_put_software_breakpoint (FridaSpawnInstance * self, GumAddress where, guint index);
static guint32 frida_spawn_instance_overwrite_arm64_instruction (FridaSpawnInstance * self, GumAddress address, guint32 new_instruction);
static void frida_make_pty (int fds[2]);
static void frida_configure_terminal_attributes (gint fd);
static FridaInjectInstance * frida_inject_instance_new (FridaDarwinHelperBackend * backend, guint id, guint pid);
static FridaInjectInstance * frida_inject_instance_clone (const FridaInjectInstance * instance, guint id);
static void frida_inject_instance_close (FridaInjectInstance * instance);
@@ -804,12 +801,17 @@ _frida_darwin_helper_backend_spawn (FridaDarwinHelperBackend * self, const gchar
posix_spawnattr_t attributes;
sigset_t signal_mask_set;
short flags;
int stdin_pipe[2], stdout_pipe[2], stderr_pipe[2];
FridaFileDescriptor * in_fd = NULL;
FridaFileDescriptor * out_fd = NULL;
FridaFileDescriptor * err_fd = NULL;
GError * stdio_error = NULL;
FridaAslr aslr = FRIDA_ASLR_AUTO;
GVariant * aslr_value;
gchar * old_cwd = NULL;
int result, spawn_errno;
*pipes = NULL;
instance = frida_spawn_instance_new (self);
argv = frida_host_spawn_options_compute_argv (options, path, NULL);
@@ -823,33 +825,13 @@ _frida_darwin_helper_backend_spawn (FridaDarwinHelperBackend * self, const gchar
flags = POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_START_SUSPENDED;
switch (options->stdio)
{
case FRIDA_STDIO_INHERIT:
*pipes = NULL;
*pipes = frida_make_stdio_pipes (options->stdio, TRUE, &in_fd, NULL, &out_fd, NULL, &err_fd, NULL, &stdio_error);
if (stdio_error != NULL)
goto propagate_stdio_error;
posix_spawn_file_actions_adddup2 (&file_actions, 0, 0);
posix_spawn_file_actions_adddup2 (&file_actions, 1, 1);
posix_spawn_file_actions_adddup2 (&file_actions, 2, 2);
break;
case FRIDA_STDIO_PIPE:
frida_make_pty (stdin_pipe);
frida_make_pty (stdout_pipe);
frida_make_pty (stderr_pipe);
*pipes = frida_stdio_pipes_new (stdin_pipe[1], stdout_pipe[0], stderr_pipe[0]);
posix_spawn_file_actions_adddup2 (&file_actions, stdin_pipe[0], 0);
posix_spawn_file_actions_adddup2 (&file_actions, stdout_pipe[1], 1);
posix_spawn_file_actions_adddup2 (&file_actions, stderr_pipe[1], 2);
break;
default:
g_assert_not_reached ();
}
posix_spawn_file_actions_adddup2 (&file_actions, (in_fd != NULL) ? in_fd->handle : 0, 0);
posix_spawn_file_actions_adddup2 (&file_actions, (out_fd != NULL) ? out_fd->handle : 1, 1);
posix_spawn_file_actions_adddup2 (&file_actions, (err_fd != NULL) ? err_fd->handle : 2, 2);
aslr_value = g_hash_table_lookup (options->aux, "aslr");
if (aslr_value != NULL && !frida_parse_aslr_option (aslr_value, &aslr, error))
@@ -872,13 +854,6 @@ _frida_darwin_helper_backend_spawn (FridaDarwinHelperBackend * self, const gchar
if (old_cwd != NULL)
chdir (old_cwd);
if (options->stdio == FRIDA_STDIO_PIPE)
{
close (stdin_pipe[0]);
close (stdout_pipe[1]);
close (stderr_pipe[1]);
}
posix_spawnattr_destroy (&attributes);
posix_spawn_file_actions_destroy (&file_actions);
@@ -892,6 +867,11 @@ _frida_darwin_helper_backend_spawn (FridaDarwinHelperBackend * self, const gchar
goto beach;
propagate_stdio_error:
{
g_propagate_error (error, stdio_error);
goto early_failure;
}
chdir_failed:
{
g_set_error (error,
@@ -936,12 +916,18 @@ any_failure:
kill (instance->pid, SIGKILL);
frida_spawn_instance_close (instance);
g_clear_object (pipes);
pid = 0;
goto beach;
}
beach:
{
g_clear_object (&in_fd);
g_clear_object (&out_fd);
g_clear_object (&err_fd);
g_free (old_cwd);
g_strfreev (envp);
g_strfreev (argv);
@@ -1057,6 +1043,8 @@ frida_darwin_helper_backend_launch_using_fbs (NSString * identifier, NSURL * url
FridaSpringboardApi * api;
NSMutableDictionary * debug_options, * open_options;
FridaStdioPipes * pipes = NULL;
gchar * stdout_name = NULL;
gchar * stderr_name = NULL;
GError * error = NULL;
FridaAslr aslr = FRIDA_ASLR_AUTO;
GVariant * aslr_value;
@@ -1092,22 +1080,12 @@ frida_darwin_helper_backend_launch_using_fbs (NSString * identifier, NSURL * url
if (strlen (spawn_options->cwd) > 0)
goto cwd_not_supported;
pipes = frida_make_stdio_pipes (spawn_options->stdio, FALSE, NULL, NULL, NULL, &stdout_name, NULL, &stderr_name, &error);
if (error != NULL)
goto failure;
if (spawn_options->stdio == FRIDA_STDIO_PIPE)
{
gint stdout_master, stdout_slave, stderr_master, stderr_slave;
gchar stdout_name[PATH_MAX], stderr_name[PATH_MAX];
openpty (&stdout_master, &stdout_slave, stdout_name, NULL, NULL);
openpty (&stderr_master, &stderr_slave, stderr_name, NULL, NULL);
pipes = frida_stdio_pipes_new (-1, stdout_master, stderr_master);
frida_configure_terminal_attributes (stdout_master);
frida_configure_terminal_attributes (stderr_master);
frida_stdio_pipes_retain (pipes, stdout_slave);
frida_stdio_pipes_retain (pipes, stderr_slave);
chmod (stdout_name, 0666);
chmod (stderr_name, 0666);
@@ -1174,7 +1152,7 @@ frida_darwin_helper_backend_launch_using_fbs (NSString * identifier, NSURL * url
}
});
return;
goto beach;
envp_not_supported:
{
@@ -1195,6 +1173,16 @@ cwd_not_supported:
failure:
{
on_complete (NULL, error, on_complete_target);
g_clear_object (&pipes);
goto beach;
}
beach:
{
g_free (stdout_name);
g_free (stderr_name);
return;
}
}
@@ -1881,7 +1869,7 @@ _frida_darwin_helper_backend_prepare_spawn_instance_for_injection (FridaDarwinHe
mach_msg_type_number_t state_count = GUM_DARWIN_THREAD_STATE_COUNT;
thread_state_flavor_t state_flavor = GUM_DARWIN_THREAD_STATE_FLAVOR;
GumAddress dyld_start, dyld_granularity, dyld_chunk, dyld_header;
GumAddress modern_entry_address, legacy_entry_address;
GumAddress modern_entry_address, legacy_entry_address, sim_notify_address;
const gchar * launch_with_closure_names[] = {
"__ZN4dyldL17launchWithClosureEPKN5dyld37closure13LaunchClosureEPK15DyldSharedCachePKNS0_11MachOLoadedEmiPPKcSD_SD_R11DiagnosticsPmSG_PbSH_",
"__ZN4dyldL17launchWithClosureEPKN5dyld312launch_cache13binary_format7ClosureEPK15DyldSharedCachePK11mach_headermiPPKcSE_SE_PmSF_",
@@ -2101,6 +2089,9 @@ _frida_darwin_helper_backend_prepare_spawn_instance_for_injection (FridaDarwinHe
instance->ret_gadget |= 1;
}
sim_notify_address = gum_darwin_module_resolve_symbol_address (dyld, "__ZN5dyld4L29sim_notifyMonitorOfMainCalledEv");
instance->sim_notify_address = sim_notify_address;
instance->ret_state = FRIDA_RET_FROM_HELPER;
kr = frida_get_debug_state (child_thread, &instance->previous_debug_state, instance->cpu_type);
@@ -2112,6 +2103,8 @@ _frida_darwin_helper_backend_prepare_spawn_instance_for_injection (FridaDarwinHe
frida_spawn_instance_set_nth_breakpoint (instance, i++, legacy_entry_address, FRIDA_BREAKPOINT_REPEAT_ALWAYS);
if (modern_entry_address != 0)
frida_spawn_instance_set_nth_breakpoint (instance, i++, modern_entry_address, FRIDA_BREAKPOINT_REPEAT_ALWAYS);
if (sim_notify_address != 0)
frida_spawn_instance_set_nth_breakpoint (instance, i++, sim_notify_address, FRIDA_BREAKPOINT_REPEAT_ALWAYS);
if (instance->dyld_flavor == FRIDA_DYLD_V4_PLUS)
{
GumAddress restart_with_dyld_in_cache = gum_darwin_module_resolve_symbol_address (dyld,
@@ -3104,7 +3097,11 @@ frida_spawn_instance_handle_breakpoint (FridaSpawnInstance * self, FridaBreakpoi
if (self->breakpoint_phase == FRIDA_BREAKPOINT_DETECT_FLAVOR)
{
if (self->dyld_flavor == FRIDA_DYLD_V4_PLUS)
if (pc == self->sim_notify_address)
{
self->breakpoint_phase = FRIDA_BREAKPOINT_LIBSYSTEM_INITIALIZED;
}
else if (self->dyld_flavor == FRIDA_DYLD_V4_PLUS)
{
if (pc == self->modern_entry_address)
{
@@ -4065,44 +4062,6 @@ frida_spawn_instance_overwrite_arm64_instruction (FridaSpawnInstance * self, Gum
return original_instruction;
}
static void
frida_make_pty (int fds[2])
{
gboolean pipe_opened;
int i;
pipe_opened = openpty (&fds[0], &fds[1], NULL, NULL, NULL) != -1;
g_assert (pipe_opened);
for (i = 0; i != 2; i++)
{
const int fd = fds[i];
int res;
res = fcntl (fd, F_SETFD, fcntl (fd, F_GETFD) | FD_CLOEXEC);
g_assert (res == 0);
res = fcntl (fd, F_SETNOSIGPIPE, TRUE);
g_assert (res == 0);
}
frida_configure_terminal_attributes (fds[0]);
}
static void
frida_configure_terminal_attributes (gint fd)
{
struct termios tios;
tcgetattr (fd, &tios);
tios.c_oflag &= ~ONLCR;
tios.c_cflag = (tios.c_cflag & CLOCAL) | CS8 | CREAD | HUPCL;
tios.c_lflag &= ~ECHO;
tcsetattr (fd, 0, &tios);
}
static FridaInjectInstance *
frida_inject_instance_new (FridaDarwinHelperBackend * backend, guint id, guint pid)
{
+10 -62
View File
@@ -136,9 +136,9 @@ namespace Frida {
ChildWatch.add ((Pid) child_pid, on_child_dead);
if (pipes != null) {
stdin_streams[child_pid] = new UnixOutputStream (pipes.input, false);
process_next_output_from.begin (new UnixInputStream (pipes.output, false), child_pid, 1, pipes);
process_next_output_from.begin (new UnixInputStream (pipes.error, false), child_pid, 2, pipes);
stdin_streams[child_pid] = pipes.input;
process_next_output_from.begin (pipes.output, child_pid, 1, pipes);
process_next_output_from.begin (pipes.error, child_pid, 2, pipes);
}
return child_pid;
@@ -180,10 +180,8 @@ namespace Frida {
var pipes = pending.pipes;
if (pipes != null) {
pipes.clear_retained ();
process_next_output_from.begin (new UnixInputStream (pipes.output, false), pid, 1, pipes);
process_next_output_from.begin (new UnixInputStream (pipes.error, false), pid, 2, pipes);
process_next_output_from.begin (pipes.output, pid, 1, pipes);
process_next_output_from.begin (pipes.error, pid, 2, pipes);
}
}
@@ -439,8 +437,11 @@ namespace Frida {
private bool is_booting (uint task) throws Error {
Gum.Darwin.AllImageInfos infos;
if (!Gum.Darwin.query_all_image_infos (task, out infos))
throw new Error.PROCESS_NOT_FOUND ("Target process died unexpectedly");
try {
Gum.Darwin.query_all_image_infos (task, out infos);
} catch (Gum.Error e) {
throw new Error.PROCESS_NOT_FOUND ("%s", e.message);
}
return !infos.libsystem_initialized;
}
@@ -888,57 +889,4 @@ namespace Frida {
return false;
}
}
public sealed class StdioPipes : Object {
public int input {
get;
construct;
}
public int output {
get;
construct;
}
public int error {
get;
construct;
}
private int[] retained = {};
public StdioPipes (int input, int output, int error) {
Object (input: input, output: output, error: error);
}
construct {
try {
if (input != -1)
Unix.set_fd_nonblocking (input, true);
Unix.set_fd_nonblocking (output, true);
Unix.set_fd_nonblocking (error, true);
} catch (GLib.Error e) {
assert_not_reached ();
}
}
~StdioPipes () {
clear_retained ();
if (input != -1)
Posix.close (input);
Posix.close (output);
Posix.close (error);
}
public void clear_retained () {
foreach (var fd in retained)
Posix.close (fd);
retained = {};
}
public void retain (int fd) {
retained += fd;
}
}
}
+2 -1
View File
@@ -130,7 +130,8 @@ namespace Frida {
public async void close (Cancellable? cancellable) throws IOError {
}
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
throws Error, IOError {
if (host_session != null)
throw new Error.INVALID_ARGUMENT ("Already created");
+19 -37
View File
@@ -172,8 +172,6 @@ static gboolean frida_exec_instance_try_perform_transition (FridaExecInstance *
static void frida_exec_instance_suspend (FridaExecInstance * self);
static void frida_exec_instance_resume (FridaExecInstance * self);
static void frida_make_pipe (int fds[2]);
static FridaInjectInstance * frida_inject_instance_new (FridaBinjector * binjector, guint id, guint pid, const FridaRemoteApi * api,
const gchar * temp_path);
static void frida_inject_instance_recreate_fifo (FridaInjectInstance * self);
@@ -211,7 +209,10 @@ _frida_binjector_do_spawn (FridaBinjector * self, const gchar * path, FridaHostS
{
FridaSpawnInstance * instance;
gchar ** argv, ** envp;
int stdin_pipe[2], stdout_pipe[2], stderr_pipe[2];
FridaFileDescriptor * in_fd = NULL;
FridaFileDescriptor * out_fd = NULL;
FridaFileDescriptor * err_fd = NULL;
GError * stdio_error = NULL;
gchar * old_cwd = NULL;
gboolean success;
const gchar * failed_operation;
@@ -221,24 +222,9 @@ _frida_binjector_do_spawn (FridaBinjector * self, const gchar * path, FridaHostS
argv = frida_host_spawn_options_compute_argv (options, path, NULL);
envp = frida_host_spawn_options_compute_envp (options, NULL);
switch (options->stdio)
{
case FRIDA_STDIO_INHERIT:
*pipes = NULL;
break;
case FRIDA_STDIO_PIPE:
frida_make_pipe (stdin_pipe);
frida_make_pipe (stdout_pipe);
frida_make_pipe (stderr_pipe);
*pipes = frida_stdio_pipes_new (stdin_pipe[1], stdout_pipe[0], stderr_pipe[0]);
break;
default:
g_assert_not_reached ();
}
*pipes = frida_make_stdio_pipes (options->stdio, TRUE, &in_fd, NULL, &out_fd, NULL, &err_fd, NULL, &stdio_error);
if (stdio_error != NULL)
goto propagate_stdio_error;
if (strlen (options->cwd) > 0)
{
@@ -254,9 +240,9 @@ _frida_binjector_do_spawn (FridaBinjector * self, const gchar * path, FridaHostS
if (options->stdio == FRIDA_STDIO_PIPE)
{
dup2 (stdin_pipe[0], 0);
dup2 (stdout_pipe[1], 1);
dup2 (stderr_pipe[1], 2);
dup2 (in_fd->handle, 0);
dup2 (out_fd->handle, 1);
dup2 (err_fd->handle, 2);
}
ptrace (PT_TRACE_ME, 0, NULL, 0);
@@ -273,13 +259,6 @@ _frida_binjector_do_spawn (FridaBinjector * self, const gchar * path, FridaHostS
g_warning ("Failed to restore working directory");
}
if (options->stdio == FRIDA_STDIO_PIPE)
{
close (stdin_pipe[0]);
close (stdout_pipe[1]);
close (stderr_pipe[1]);
}
success = frida_wait_for_child_signal (instance->pid, SIGTRAP, NULL);
CHECK_OS_RESULT (success, !=, FALSE, "wait(SIGTRAP)");
@@ -290,6 +269,11 @@ _frida_binjector_do_spawn (FridaBinjector * self, const gchar * path, FridaHostS
goto beach;
propagate_stdio_error:
{
g_propagate_error (error, stdio_error);
goto failure;
}
chdir_failed:
{
g_set_error (error,
@@ -316,6 +300,10 @@ failure:
}
beach:
{
g_clear_object (&in_fd);
g_clear_object (&out_fd);
g_clear_object (&err_fd);
g_free (old_cwd);
g_strfreev (envp);
g_strfreev (argv);
@@ -736,12 +724,6 @@ frida_exec_instance_resume (FridaExecInstance * self)
ptrace (PT_DETACH, self->pid, NULL, 0);
}
static void
frida_make_pipe (int fds[2])
{
g_unix_open_pipe (fds, FD_CLOEXEC, NULL);
}
static FridaInjectInstance *
frida_inject_instance_new (FridaBinjector * binjector, guint id, guint pid, const FridaRemoteApi * api, const gchar * temp_path)
{
+3 -40
View File
@@ -68,9 +68,9 @@ namespace Frida {
monitor_child (child_pid);
if (pipes != null) {
stdin_streams[child_pid] = new UnixOutputStream (pipes.input, false);
process_next_output_from.begin (new UnixInputStream (pipes.output, false), child_pid, 1, pipes);
process_next_output_from.begin (new UnixInputStream (pipes.error, false), child_pid, 2, pipes);
stdin_streams[child_pid] = pipes.input;
process_next_output_from.begin (pipes.output, child_pid, 1, pipes);
process_next_output_from.begin (pipes.error, child_pid, 2, pipes);
}
return child_pid;
@@ -696,41 +696,4 @@ namespace Frida {
protected enum ProgressMessageType {
HELLO = 0xff
}
protected class StdioPipes : Object {
public int input {
get;
construct;
}
public int output {
get;
construct;
}
public int error {
get;
construct;
}
public StdioPipes (int input, int output, int error) {
Object (input: input, output: output, error: error);
}
construct {
try {
Unix.set_fd_nonblocking (input, true);
Unix.set_fd_nonblocking (output, true);
Unix.set_fd_nonblocking (error, true);
} catch (GLib.Error e) {
assert_not_reached ();
}
}
~StdioPipes () {
Posix.close (input);
Posix.close (output);
Posix.close (error);
}
}
}
+8 -2
View File
@@ -24,7 +24,7 @@ namespace Frida {
}
}
public sealed class DeviceManager : Object {
public sealed class DeviceManager : Object, HostSessionHub {
public signal void added (Device device);
public signal void removed (Device device);
public signal void changed ();
@@ -394,6 +394,12 @@ namespace Frida {
changed ();
}
private async HostSessionEntry resolve_host_session (string id, Cancellable? cancellable) throws Error, IOError {
var device = yield get_device_by_id (id, 0, cancellable);
var session = yield device.get_host_session (cancellable);
return new HostSessionEntry (device.provider, session);
}
private void check_open () throws Error {
if (stop_request != null)
throw new Error.INVALID_OPERATION ("Device manager is closed");
@@ -1326,7 +1332,7 @@ namespace Frida {
host_session_request = new Promise<HostSession> ();
try {
var session = yield provider.create (host_session_options, cancellable);
var session = yield provider.create (manager, host_session_options, cancellable);
attach_host_session (session);
current_host_session = session;
+1 -1
View File
@@ -340,7 +340,7 @@ namespace Frida.Fruity {
var stream = yield tunnel.open_tcp_connection (port, cancellable);
return new TcpChannel () { stream = stream, kind = TUNNEL };
} catch (Error e) {
if (e is Error.SERVER_NOT_RUNNING)
if (e is Error.SERVER_NOT_RUNNING || e is Error.TRANSPORT)
pending_error = e;
else
throw e;
+9 -5
View File
@@ -80,7 +80,8 @@ namespace Frida {
yield Fruity.DTXConnection.close_all (device, cancellable);
}
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
throws Error, IOError {
if (host_session != null)
throw new Error.INVALID_OPERATION ("Already created");
@@ -1425,7 +1426,7 @@ namespace Frida {
}
private Promise<Fruity.Injector.GadgetDetails>? gadget_request;
private ulong? exception_handler;
private ulong exception_handler = 0;
private string? main_thread_id;
private bool resumed = false;
@@ -1528,6 +1529,8 @@ namespace Frida {
}
public async void kill (Cancellable? cancellable) throws Error, IOError {
io_cancellable.cancel ();
detach_exception_handler ();
if (exception_handling_status == FULL)
@@ -1595,9 +1598,10 @@ namespace Frida {
}
private void detach_exception_handler () {
if (exception_handler != null) {
lldb.disconnect (exception_handler);
exception_handler = null;
if (exception_handler != 0) {
if (lldb.state != CLOSED)
lldb.disconnect (exception_handler);
exception_handler = 0;
}
}
+6 -2
View File
@@ -88,8 +88,12 @@ namespace Frida.Fruity {
request.set_string ("SystemBUID", system_buid);
var response = yield service.query (request, cancellable);
if (response.has ("Error"))
throw new LockdownError.PROTOCOL ("Unexpected response: %s", response.get_string ("Error"));
if (response.has ("Error")) {
var error = response.get_string ("Error");
if (error == "InvalidHostID")
yield unpair (cancellable);
throw new LockdownError.PROTOCOL ("Unexpected response: %s", error);
}
if (response.get_boolean ("EnableSessionSSL"))
service.stream = yield start_tls (service.stream, cancellable);
+107 -59
View File
@@ -31,9 +31,18 @@ namespace Frida.Fruity {
private uint16 ntb_out_max_datagrams;
private size_t max_in_transfers;
private size_t max_out_transfers;
private size_t max_out_bytes_in_flight;
private uint16 ndp_reserved_slots;
private uint16 ndp_fixed_header_size;
private VirtualNetworkStack? _netstack;
private Gee.Queue<Bytes> pending_input = new Gee.ArrayQueue<Bytes> ();
private bool input_flush_scheduled = false;
private Gee.Queue<Bytes> pending_output = new Gee.ArrayQueue<Bytes> ();
private bool writing = false;
private size_t out_in_flight_count = 0;
private size_t out_in_flight_bytes = 0;
private Gee.Map<Future<uint>, uint16> out_in_flight_sizes = new Gee.HashMap<Future<uint>, uint16> ();
private bool out_topup_scheduled = false;
private uint16 next_outgoing_sequence = 1;
private InetAddress? _remote_ipv6_address;
@@ -115,7 +124,7 @@ namespace Frida.Fruity {
ndp_out_divisor = ntb_params.read_uint16 (20);
ndp_out_payload_remainder = ntb_params.read_uint16 (22);
ndp_out_alignment = ntb_params.read_uint16 (24);
ntb_out_max_datagrams = uint16.min (ntb_params.read_uint16 (26), 16);
ntb_out_max_datagrams = ntb_params.read_uint16 (26);
if (ntb_in_max_size != device_ntb_in_max_size) {
var ntb_size_buf = new BufferBuilder (LITTLE_ENDIAN)
@@ -137,17 +146,24 @@ namespace Frida.Fruity {
max_out_transfers = (5 * MAX_TRANSFER_MEMORY) / ntb_out_max_size;
} else if (speed == LibUSB.Speed.HIGH) {
max_in_transfers = MAX_TRANSFER_MEMORY / ntb_in_max_size;
max_out_transfers = MAX_TRANSFER_MEMORY / ntb_out_max_size;
max_out_transfers = (3 * MAX_TRANSFER_MEMORY) / ntb_out_max_size;
max_out_transfers = size_t.max (max_out_transfers, 6);
max_out_transfers = size_t.min (max_out_transfers, 16);
} else {
max_in_transfers = 4;
max_out_transfers = 4;
}
max_out_bytes_in_flight = 4 * MAX_TRANSFER_MEMORY;
ndp_reserved_slots = (uint16) uint16.min (21, ntb_out_max_datagrams);
ndp_fixed_header_size = (uint16) align (8 + (ndp_reserved_slots * 4) + 4, ndp_out_alignment);
Usb.check (handle.set_interface_alt_setting (config.data_iface, config.data_altsetting),
"Failed to set USB interface alt setting");
_netstack = new VirtualNetworkStack (new Bytes (mac_address), null, 1500);
_netstack.outgoing_datagram.connect (on_netif_outgoing_datagram);
_netstack.outgoing_datagrams.connect (on_netif_outgoing_datagrams);
process_incoming_datagrams.begin ();
@@ -234,63 +250,96 @@ namespace Frida.Fruity {
notify_property ("remote-ipv6-address");
}
_netstack.handle_incoming_datagram (datagram);
on_incoming_datagram (datagram);
}
ndp_index = next_ndp_index;
} while (ndp_index != 0);
}
private void on_netif_outgoing_datagram (Bytes datagram) {
pending_output.offer (datagram);
private void on_incoming_datagram (Bytes datagram) {
pending_input.offer (datagram);
if (!writing) {
writing = true;
if (!input_flush_scheduled) {
var source = new IdleSource ();
source.set_callback (() => {
process_pending_output.begin ();
return false;
input_flush_scheduled = false;
flush_pending_input ();
return Source.REMOVE;
});
source.attach (MainContext.get_thread_default ());
input_flush_scheduled = true;
}
}
private async void process_pending_output () {
private void flush_pending_input () {
var datagrams = pending_input;
pending_input = new Gee.ArrayQueue<Bytes> ();
try {
while (!pending_output.is_empty) {
var pending = new Gee.ArrayList<Promise<uint>> ();
do {
var request = transfer_next_output_batch ();
pending.add (request);
} while (pending.size < max_out_transfers && !pending_output.is_empty);
foreach (var request in pending)
yield request.future.wait_async (io_cancellable);
}
} catch (GLib.Error e) {
} finally {
writing = false;
_netstack.handle_incoming_datagrams (datagrams);
} catch (Error e) {
}
}
private Promise<uint> transfer_next_output_batch () {
size_t num_datagrams = ntb_out_max_datagrams;
TransferLayout layout;
while ((layout = TransferLayout.compute (pending_output, num_datagrams, ndp_out_alignment,
ndp_out_divisor, ndp_out_payload_remainder)).size > ntb_out_max_size) {
num_datagrams--;
private void on_netif_outgoing_datagrams (Gee.Collection<Bytes> datagrams) {
foreach (var d in datagrams)
pending_output.offer (d);
schedule_top_up ();
}
private void schedule_top_up () {
if (out_topup_scheduled)
return;
out_topup_scheduled = true;
var source = new IdleSource ();
source.set_callback (() => {
out_topup_scheduled = false;
top_up_output_window ();
return Source.REMOVE;
});
source.attach (MainContext.get_thread_default ());
}
private void top_up_output_window () {
while (!pending_output.is_empty && out_in_flight_count < max_out_transfers) {
size_t max_datagrams = size_t.min (pending_output.size, (size_t) ndp_reserved_slots);
var layout = TransferLayout.compute (pending_output, max_datagrams, ntb_out_max_size, ndp_fixed_header_size,
ndp_out_alignment, ndp_out_divisor, ndp_out_payload_remainder);
if ((out_in_flight_bytes + layout.size) > max_out_bytes_in_flight)
break;
var batch = new Gee.ArrayList<Bytes> ();
for (var i = 0; i != layout.offsets.length; i++)
batch.add (pending_output.poll ());
var transfer = build_output_transfer (batch, layout, next_outgoing_sequence++);
var request = new Promise<uint> ();
do_transfer_output_batch.begin (transfer, request);
out_in_flight_count++;
out_in_flight_bytes += layout.size;
out_in_flight_sizes.set (request.future, layout.size);
request.future.then (fut => {
on_output_completed (fut);
});
}
}
var batch = new Gee.ArrayList<Bytes> ();
for (var i = 0; i != layout.offsets.size; i++)
batch.add (pending_output.poll ());
private void on_output_completed (Future<uint> fut) {
uint16 size;
out_in_flight_sizes.unset (fut, out size);
var transfer = build_output_transfer (batch, layout, next_outgoing_sequence++);
out_in_flight_count--;
out_in_flight_bytes -= size;
var request = new Promise<uint> ();
do_transfer_output_batch.begin (transfer, request);
return request;
if (fut.error != null)
return;
top_up_output_window ();
}
private async void do_transfer_output_batch (Bytes transfer, Promise<uint> request) {
@@ -306,53 +355,45 @@ namespace Frida.Fruity {
public uint16 size;
public uint16 ndp_header_offset;
public uint16 ndp_header_size;
public Gee.List<uint16> offsets;
public uint16[] offsets;
public static TransferLayout compute (Gee.Collection<Bytes> datagrams, size_t max_datagrams, size_t ndp_alignment,
public static TransferLayout compute (Gee.Collection<Bytes> datagrams, size_t max_datagrams,
uint32 max_transfer_size, uint16 ndp_fixed_header_size, size_t ndp_alignment,
size_t datagram_modulus, size_t datagram_remainder) {
size_t ndp_header_base_size = 4 + 2 + 2;
size_t ndp_entry_size = 2 + 2;
size_t ethernet_header_size = 14;
size_t ndp_header_offset = align (TRANSFER_HEADER_SIZE, ndp_alignment, 0);
size_t num_datagram_slots = size_t.min (datagrams.size, max_datagrams);
size_t ndp_header_size = ndp_header_base_size + ((num_datagram_slots + 1) * ndp_entry_size);
size_t current_transfer_size = ndp_header_offset + ndp_header_size;
var offsets = new Gee.ArrayList<uint16> ();
size_t current_transfer_size = ndp_header_offset + ndp_fixed_header_size;
var offsets = new uint16[max_datagrams];
uint i = 0;
int i = 0;
foreach (var datagram in datagrams) {
var size = (uint16) datagram.get_size ();
size_t start_offset =
align (current_transfer_size + ethernet_header_size, datagram_modulus, datagram_remainder);
size_t end_offset = start_offset + size;
if (end_offset > uint16.MAX)
if (end_offset > uint16.MAX || end_offset > max_transfer_size)
break;
current_transfer_size = end_offset;
offsets.add ((uint16) start_offset);
offsets[i] = (uint16) start_offset;
i++;
if (i == max_datagrams)
break;
}
offsets.resize (i);
return new TransferLayout () {
size = (uint16) current_transfer_size,
ndp_header_offset = (uint16) ndp_header_offset,
ndp_header_size = (uint16) ndp_header_size,
offsets = offsets,
ndp_header_size = ndp_fixed_header_size,
offsets = (owned) offsets,
};
}
private static size_t align (size_t val, size_t modulus, size_t remainder) {
var delta = val % modulus;
if (delta != remainder)
return val + modulus - delta + remainder;
return val;
}
}
private static Bytes build_output_transfer (Gee.List<Bytes> datagrams, TransferLayout layout, uint16 sequence_number) {
@@ -409,6 +450,13 @@ namespace Frida.Fruity {
return new InetAddress.from_bytes (datagram[22:22 + 16].get_data (), IPV6);
}
private static size_t align (size_t val, size_t modulus, size_t remainder = 0) {
var delta = val % modulus;
if (delta != remainder)
return val + modulus - delta + remainder;
return val;
}
}
internal sealed class UsbNcmConfig {
+49 -9
View File
@@ -149,7 +149,7 @@ namespace Frida.Fruity {
}
public sealed class VirtualNetworkStack : Object, NetworkStack {
public signal void outgoing_datagram (Bytes datagram);
public signal void outgoing_datagrams (Gee.Collection<Bytes> datagrams);
public Bytes? ethernet_address {
get;
@@ -182,6 +182,8 @@ namespace Frida.Fruity {
private State state = STARTED;
private Gee.Queue<Bytes>? pending_outgoing;
private Gee.Queue<Request> requests = new Gee.ArrayQueue<Request> ();
private unowned Thread<bool>? lwip_thread;
@@ -281,16 +283,26 @@ namespace Frida.Fruity {
return new Ipv6UdpSocket (this);
}
public void handle_incoming_datagram (Bytes datagram) throws Error {
public void handle_incoming_datagrams (Gee.Collection<Bytes> datagrams) throws Error {
check_started ();
check (perform_on_lwip_thread (() => {
var pbuf = LWIP.PacketBuffer.alloc (RAW, (uint16) datagram.get_size (), POOL);
pbuf.take (datagram.get_data ());
begin_output ();
var err = handle.input (pbuf, ref handle);
if (err == OK)
*((void **) &pbuf) = null;
LWIP.ErrorCode err = OK;
foreach (var d in datagrams) {
var pbuf = LWIP.PacketBuffer.alloc (RAW, (uint16) d.get_size (), POOL);
pbuf.take (d.get_data ());
err = handle.input (pbuf, ref handle);
if (err == OK)
*((void **) &pbuf) = null;
else
break;
}
end_output ();
return err;
}));
@@ -309,14 +321,39 @@ namespace Frida.Fruity {
return OK;
}
internal void begin_output () {
pending_outgoing = new Gee.ArrayQueue<Bytes> ();
}
internal void end_output () {
var datagrams = pending_outgoing;
pending_outgoing = null;
if (datagrams.is_empty)
return;
schedule_on_frida_thread (() => {
if (state == STARTED)
outgoing_datagrams (datagrams);
return Source.REMOVE;
});
}
private void emit_datagram (LWIP.PacketBuffer pbuf) {
var buffer = new uint8[pbuf.tot_len];
unowned uint8[] packet = pbuf.get_contiguous (buffer, pbuf.tot_len);
var datagram = new Bytes (packet[:pbuf.tot_len]);
if (pending_outgoing != null) {
pending_outgoing.offer (datagram);
return;
}
schedule_on_frida_thread (() => {
if (state == STARTED)
outgoing_datagram (datagram);
if (state == STARTED) {
var datagrams = new Gee.ArrayQueue<Bytes> ();
datagrams.offer (datagram);
outgoing_datagrams (datagrams);
}
return Source.REMOVE;
});
}
@@ -713,7 +750,10 @@ namespace Frida.Fruity {
}
}
netstack.begin_output ();
var output_err = pcb.output ();
netstack.end_output ();
if (output_err == RTE)
return output_err;
+131 -41
View File
@@ -126,6 +126,8 @@ namespace Frida.Fruity {
public sealed class PairingService : Object, AsyncInitable {
public const string DNS_SD_NAME = "_remotepairing._tcp.local";
private signal void events_received (ObjectReader events);
public PairingTransport transport {
get;
construct;
@@ -151,8 +153,7 @@ namespace Frida.Fruity {
private set;
}
private Gee.Map<uint64?, Promise<ObjectReader>> requests =
new Gee.HashMap<uint64?, Promise<ObjectReader>> (Numeric.uint64_hash, Numeric.uint64_equal);
private Gee.Queue<Promise<ObjectReader>> requests = new Gee.ArrayQueue<Promise<ObjectReader>> ();
private uint64 next_control_sequence_number = 0;
private uint64 next_encrypted_sequence_number = 0;
@@ -738,11 +739,30 @@ namespace Frida.Fruity {
.end_dictionary ()
.build ();
ObjectReader response = yield request_plain (wrapper, cancellable);
var promise = new Promise<ObjectReader> ();
var pairing_handler = events_received.connect (reader => {
try {
reader
.read_member ("plain")
.read_member ("_0")
.read_member ("event")
.read_member ("_0");
response
.read_member ("event")
.read_member ("_0");
if (reader.has_member ("pairingData") || reader.has_member ("pairingRejectedWithError"))
promise.resolve (reader);
} catch (Error e) {
promise.reject (e);
return;
}
});
ObjectReader response = null;
try {
yield post_plain (wrapper, cancellable);
response = yield promise.future.wait_async (cancellable);
} finally {
disconnect (pairing_handler);
}
if (response.has_member ("pairingRejectedWithError")) {
string description = response
@@ -766,12 +786,12 @@ namespace Frida.Fruity {
private async ObjectReader request_plain (Bytes payload, Cancellable? cancellable) throws Error, IOError {
uint64 seqno = next_control_sequence_number++;
var promise = new Promise<ObjectReader> ();
requests[seqno] = promise;
requests.offer (promise);
try {
yield post_plain_with_sequence_number (seqno, payload, cancellable);
} catch (GLib.Error e) {
if (requests.unset (seqno))
if (requests.remove (promise))
promise.reject (e);
}
@@ -810,7 +830,7 @@ namespace Frida.Fruity {
private async string request_encrypted (string json, Cancellable? cancellable) throws Error, IOError {
uint64 seqno = next_control_sequence_number++;
var promise = new Promise<ObjectReader> ();
requests[seqno] = promise;
requests.offer (promise);
Bytes iv = new BufferBuilder (LITTLE_ENDIAN)
.append_uint64 (next_encrypted_sequence_number++)
@@ -856,7 +876,7 @@ namespace Frida.Fruity {
var e = (error != null)
? error
: new Error.TRANSPORT ("Connection closed while waiting for response");
foreach (Promise<ObjectReader> promise in requests.values)
foreach (Promise<ObjectReader> promise in requests)
promise.reject (e);
requests.clear ();
}
@@ -868,16 +888,26 @@ namespace Frida.Fruity {
return;
reader.end_member ();
uint64 seqno = reader.read_member ("sequenceNumber").get_uint64_value ();
reader.end_member ();
reader.read_member ("message");
Promise<ObjectReader> promise;
if (!requests.unset (seqno, out promise))
return;
bool is_event = false;
if (reader.has_member ("plain")) {
is_event = reader
.read_member ("plain")
.read_member ("_0")
.has_member ("event");
reader
.end_member ()
.end_member ();
}
promise.resolve (reader);
if (is_event) {
events_received (reader);
} else {
var request = requests.poll ();
if (request != null)
request.resolve (reader);
}
} catch (Error e) {
}
}
@@ -2014,7 +2044,9 @@ namespace Frida.Fruity {
private BufferedInputStream input;
private OutputStream output;
private ByteArray pending_output = new ByteArray ();
private Gee.Queue<Bytes> pending_input = new Gee.ArrayQueue<Bytes> ();
private bool input_flush_scheduled = false;
private Gee.Queue<Bytes> pending_output = new Gee.ArrayQueue<Bytes> ();
private bool writing = false;
private Cancellable io_cancellable = new Cancellable ();
@@ -2095,7 +2127,7 @@ namespace Frida.Fruity {
tunnel_params = TunnelParameters.from_json (yield read_message (cancellable));
_tunnel_netstack = new VirtualNetworkStack (null, tunnel_params.address, tunnel_params.mtu);
_tunnel_netstack.outgoing_datagram.connect (post);
_tunnel_netstack.outgoing_datagrams.connect (post_batch);
process_incoming_messages.begin ();
@@ -2116,8 +2148,7 @@ namespace Frida.Fruity {
try {
while (true) {
var datagram = yield read_datagram (io_cancellable);
_tunnel_netstack.handle_incoming_datagram (datagram);
on_incoming_datagram (datagram);
}
} catch (GLib.Error e) {
}
@@ -2142,28 +2173,61 @@ namespace Frida.Fruity {
closed ();
}
private void post (Bytes bytes) {
pending_output.append (bytes.get_data ());
private void on_incoming_datagram (Bytes datagram) {
pending_input.offer (datagram);
if (!input_flush_scheduled) {
var source = new IdleSource ();
source.set_callback (() => {
input_flush_scheduled = false;
flush_pending_input ();
return Source.REMOVE;
});
source.attach (MainContext.get_thread_default ());
input_flush_scheduled = true;
}
}
private void flush_pending_input () {
var datagrams = pending_input;
pending_input = new Gee.ArrayQueue<Bytes> ();
try {
_tunnel_netstack.handle_incoming_datagrams (datagrams);
} catch (Error e) {
}
}
private void post (Bytes datagram) {
var batch = new Gee.ArrayList<Bytes> ();
batch.add (datagram);
post_batch (batch);
}
private void post_batch (Gee.Collection<Bytes> datagrams) {
foreach (var d in datagrams)
pending_output.offer (d);
if (!writing) {
writing = true;
var source = new IdleSource ();
source.set_callback (() => {
process_pending_output.begin ();
return false;
});
source.attach (MainContext.get_thread_default ());
process_pending_output.begin ();
}
}
private async void process_pending_output () {
while (pending_output.len > 0) {
uint8[] batch = pending_output.steal ();
while (!pending_output.is_empty) {
size_t size = 0;
foreach (var d in pending_output)
size += d.get_size ();
var batch = new ByteArray.sized ((uint) size);
foreach (var d in pending_output)
batch.append (d.get_data ());
pending_output = new Gee.ArrayQueue<Bytes> ();
size_t bytes_written;
try {
yield output.write_all_async (batch, Priority.DEFAULT, io_cancellable, out bytes_written);
yield output.write_all_async (batch.data, Priority.DEFAULT, io_cancellable, out bytes_written);
} catch (GLib.Error e) {
break;
}
@@ -2301,7 +2365,9 @@ namespace Frida.Fruity {
private VirtualNetworkStack? _tunnel_netstack;
private Gee.Map<int64?, Stream> streams = new Gee.HashMap<int64?, Stream> (Numeric.int64_hash, Numeric.int64_equal);
private Gee.Queue<Bytes> tx_datagrams = new Gee.ArrayQueue<Bytes> ();
private Gee.Queue<Bytes> pending_input = new Gee.ArrayQueue<Bytes> ();
private bool input_flush_scheduled = false;
private Gee.Queue<Bytes> pending_output = new Gee.ArrayQueue<Bytes> ();
private DatagramBasedSource? rx_source;
private uint8[] rx_buf = new uint8[MAX_UDP_PAYLOAD_SIZE];
@@ -2481,7 +2547,7 @@ namespace Frida.Fruity {
tunnel_params = TunnelParameters.from_json (new JsonObjectReader (json));
_tunnel_netstack = new VirtualNetworkStack (null, tunnel_params.address, tunnel_params.mtu);
_tunnel_netstack.outgoing_datagram.connect (send_datagram);
_tunnel_netstack.outgoing_datagrams.connect (send_datagrams);
establish_request.resolve (true);
}
@@ -2572,7 +2638,14 @@ namespace Frida.Fruity {
}
private void send_datagram (Bytes datagram) {
tx_datagrams.offer (datagram);
var batch = new Gee.ArrayList<Bytes> ();
batch.add (datagram);
send_datagrams (batch);
}
private void send_datagrams (Gee.Collection<Bytes> datagrams) {
foreach (var d in datagrams)
pending_output.offer (d);
process_pending_writes ();
}
@@ -2629,13 +2702,13 @@ namespace Frida.Fruity {
n = connection.write_connection_close (null, &pi, tx_buf, error, ts);
state = CLOSE_WRITTEN;
} else {
Bytes? datagram = tx_datagrams.peek ();
Bytes? datagram = pending_output.peek ();
if (datagram != null) {
int accepted = -1;
n = connection.write_datagram (null, null, tx_buf, &accepted, NGTcp2.WriteDatagramFlags.MORE, 0,
datagram.get_data (), ts);
if (accepted > 0)
tx_datagrams.poll ();
pending_output.poll ();
} else {
Stream? stream = null;
unowned uint8[]? data = null;
@@ -2763,14 +2836,31 @@ namespace Frida.Fruity {
}
private int on_recv_datagram (uint32 flags, uint8[] data) {
try {
_tunnel_netstack.handle_incoming_datagram (new Bytes (data));
} catch (Error e) {
pending_input.offer (new Bytes (data));
if (!input_flush_scheduled) {
var source = new IdleSource ();
source.set_callback (() => {
input_flush_scheduled = false;
flush_pending_input ();
return Source.REMOVE;
});
source.attach (MainContext.get_thread_default ());
input_flush_scheduled = true;
}
return 0;
}
private void flush_pending_input () {
var datagrams = pending_input;
pending_input = new Gee.ArrayQueue<Bytes> ();
try {
_tunnel_netstack.handle_incoming_datagrams (datagrams);
} catch (Error e) {
}
}
private static void on_rand (uint8[] dest, NGTcp2.RNGContext rand_ctx) {
OpenSSL.Rng.generate (dest);
}
+55 -11
View File
@@ -47,6 +47,9 @@ namespace Frida {
}
private void add_nonlocal_backends () {
#if HAVE_SIMMY_BACKEND
add_backend (new SimmyHostSessionBackend ());
#endif
#if !IOS && !ANDROID && !TVOS
#if HAVE_FRUITY_BACKEND
add_backend (new FruityHostSessionBackend ());
@@ -161,7 +164,7 @@ namespace Frida {
get;
}
public abstract async HostSession create (HostSessionOptions? options = null,
public abstract async HostSession create (HostSessionHub hub, HostSessionOptions? options = null,
Cancellable? cancellable = null) throws Error, IOError;
public abstract async void destroy (HostSession session, Cancellable? cancellable = null) throws Error, IOError;
public signal void host_session_detached (HostSession session);
@@ -180,6 +183,42 @@ namespace Frida {
public abstract void unlink_service_session (HostSession host_session, ServiceSessionId id);
}
public interface HostSessionConnection : Object {
public abstract HostSession host_session {
get;
}
public abstract async AgentSession link_agent_session (AgentSessionId id, AgentMessageSink sink,
Cancellable? cancellable = null) throws Error, IOError;
public abstract void unlink_agent_session (AgentSessionId id);
}
public interface HostSessionHub : Object {
public abstract async HostSessionEntry resolve_host_session (string id, Cancellable? cancellable) throws Error, IOError;
}
public class NullHostSessionHub : Object, HostSessionHub {
public async HostSessionEntry resolve_host_session (string id, Cancellable? cancellable) throws Error, IOError {
throw new Error.NOT_SUPPORTED ("Host session lookup not supported");
}
}
public class HostSessionEntry : Object {
public HostSessionProvider provider {
get;
construct;
}
public HostSession session {
get;
construct;
}
public HostSessionEntry (HostSessionProvider provider, HostSession session) {
Object (provider: provider, session: session);
}
}
public enum HostSessionProviderKind {
LOCAL,
REMOTE,
@@ -277,7 +316,8 @@ namespace Frida {
host_session = null;
}
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
throws Error, IOError {
if (host_session != null)
throw new Error.INVALID_OPERATION ("Already created");
@@ -332,7 +372,13 @@ namespace Frida {
}
}
public abstract class LocalHostSession : Object, HostSession, AgentController {
public abstract class LocalHostSession : Object, HostSession, HostSessionConnection, AgentController {
public HostSession host_session {
get {
return this;
}
}
private Gee.HashMap<uint, Cancellable> pending_establish_ops = new Gee.HashMap<uint, Cancellable> ();
private Gee.HashMap<uint, Future<AgentEntry>> agent_entries = new Gee.HashMap<uint, Future<AgentEntry>> ();
@@ -1423,7 +1469,7 @@ namespace Frida {
public abstract class InternalAgent : Object, AgentMessageSink, RpcPeer {
public signal void unloaded ();
public weak LocalHostSession host_session {
public weak HostSessionConnection connection {
get;
construct;
}
@@ -1447,11 +1493,7 @@ namespace Frida {
construct {
rpc_client = new RpcClient (this);
host_session.agent_session_detached.connect (on_agent_session_detached);
}
~InternalAgent () {
host_session.agent_session_detached.disconnect (on_agent_session_detached);
connection.host_session.agent_session_detached.connect (on_agent_session_detached);
}
public async void close (Cancellable? cancellable) throws IOError {
@@ -1463,6 +1505,8 @@ namespace Frida {
}
yield ensure_unloaded (cancellable);
connection.host_session.agent_session_detached.disconnect (on_agent_session_detached);
}
protected abstract async uint get_target_pid (Cancellable? cancellable) throws Error, IOError;
@@ -1516,9 +1560,9 @@ namespace Frida {
target_pid = yield get_target_pid (cancellable);
try {
session_id = yield host_session.attach (target_pid, attach_options, cancellable);
session_id = yield connection.host_session.attach (target_pid, attach_options, cancellable);
session = yield host_session.link_agent_session (session_id, (AgentMessageSink) this, cancellable);
session = yield connection.link_agent_session (session_id, (AgentMessageSink) this, cancellable);
string? source = yield load_source (cancellable);
if (source != null) {
+4 -4
View File
@@ -9,7 +9,7 @@
"version": "1.0.0",
"dependencies": {
"@frida/buffer": "^7.0.4",
"frida-java-bridge": "^7.0.8"
"frida-java-bridge": "^7.0.10"
},
"devDependencies": {
"esbuild": "^0.25.5",
@@ -534,9 +534,9 @@
"license": "MIT"
},
"node_modules/frida-java-bridge": {
"version": "7.0.8",
"resolved": "https://registry.npmjs.org/frida-java-bridge/-/frida-java-bridge-7.0.8.tgz",
"integrity": "sha512-EmqoTGzcDuCd+YFcjBJJPf7iVQCSbbj9fD0/TN8YYYwl5ZJtTqaCLTTsaC07o3bJaDVs/f3ks+Vu1bOw0S1PnQ==",
"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": {
+1 -1
View File
@@ -12,6 +12,6 @@
},
"dependencies": {
"@frida/buffer": "^7.0.4",
"frida-java-bridge": "^7.0.8"
"frida-java-bridge": "^7.0.10"
}
}
+9 -98
View File
@@ -36,21 +36,8 @@ namespace Frida {
string[] argv = options.compute_argv (path);
string[] envp = options.compute_envp ();
StdioPipes? pipes = null;
FileDescriptor? stdin_read = null, stdin_write = null;
FileDescriptor? stdout_read = null, stdout_write = null;
FileDescriptor? stderr_read = null, stderr_write = null;
switch (options.stdio) {
case INHERIT:
break;
case PIPE: {
make_pty (out stdin_read, out stdin_write);
make_pty (out stdout_read, out stdout_write);
make_pty (out stderr_read, out stderr_write);
pipes = new StdioPipes (stdin_write, stdout_read, stderr_read);
break;
}
}
FileDescriptor? in_fd, out_fd, err_fd;
StdioPipes? pipes = make_stdio_pipes (options.stdio, true, out in_fd, null, out out_fd, null, out err_fd, null);
string? old_cwd = null;
if (options.cwd.length > 0) {
@@ -68,10 +55,10 @@ namespace Frida {
if (pid == 0) {
Posix.setsid ();
if (options.stdio == PIPE) {
Posix.dup2 (stdin_read.handle, 0);
Posix.dup2 (stdout_write.handle, 1);
Posix.dup2 (stderr_write.handle, 2);
if (pipes != null) {
Posix.dup2 (in_fd.handle, 0);
Posix.dup2 (out_fd.handle, 1);
Posix.dup2 (err_fd.handle, 2);
}
if (_ptrace (TRACEME) == -1) {
@@ -637,9 +624,9 @@ namespace Frida {
monitor ();
if (pipes != null) {
stdin_stream = new UnixOutputStream (pipes.input.handle, false);
process_next_output_from.begin (new UnixInputStream (pipes.output.handle, false), 1);
process_next_output_from.begin (new UnixInputStream (pipes.error.handle, false), 2);
stdin_stream = pipes.input;
process_next_output_from.begin (pipes.output, 1);
process_next_output_from.begin (pipes.error, 2);
}
}
@@ -703,37 +690,6 @@ namespace Frida {
}
}
private sealed class StdioPipes : Object {
public FileDescriptor input {
get;
construct;
}
public FileDescriptor output {
get;
construct;
}
public FileDescriptor error {
get;
construct;
}
public StdioPipes (FileDescriptor input, FileDescriptor output, FileDescriptor error) {
Object (input: input, output: output, error: error);
}
construct {
try {
Unix.set_fd_nonblocking (input.handle, true);
Unix.set_fd_nonblocking (output.handle, true);
Unix.set_fd_nonblocking (error.handle, true);
} catch (GLib.Error e) {
assert_not_reached ();
}
}
}
private sealed class ExecTransitionSession : SeizeSession {
private ExecTransitionSession (uint pid) {
Object (pid: pid, on_init: SeizeSession.InitBehavior.CONTINUE);
@@ -3167,53 +3123,8 @@ namespace Frida {
return Linux.syscall (SysCall.tgkill, tgid, tid, sig);
}
public extern bool _syscall_satisfies (int syscall_id, LinuxSyscall mask);
private void make_pty (out FileDescriptor read, out FileDescriptor write) throws Error {
#if HAVE_OPENPTY
int rfd = -1, wfd = -1;
char name[Posix.Limits.PATH_MAX];
if (Linux.openpty (out rfd, out wfd, name, null, null) == -1)
throw new Error.NOT_SUPPORTED ("Unable to open PTY: %s", strerror (errno));
enable_close_on_exec (rfd);
enable_close_on_exec (wfd);
configure_terminal_attributes (rfd);
read = new FileDescriptor (rfd);
write = new FileDescriptor (wfd);
#else
try {
int fds[2];
Unix.open_pipe (fds, Posix.FD_CLOEXEC);
read = new FileDescriptor (fds[0]);
write = new FileDescriptor (fds[1]);
} catch (GLib.Error e) {
throw new Error.NOT_SUPPORTED ("Unable to open pipe: %s", e.message);
}
#endif
}
#if HAVE_OPENPTY
private void enable_close_on_exec (int fd) {
Posix.fcntl (fd, Posix.F_SETFD, Posix.fcntl (fd, Posix.F_GETFD) | Posix.FD_CLOEXEC);
}
private void configure_terminal_attributes (int fd) {
var tios = Posix.termios ();
Posix.tcgetattr (fd, out tios);
tios.c_oflag &= ~Posix.ONLCR;
tios.c_cflag = (tios.c_cflag & Posix.CLOCAL) | Posix.CS8 | Posix.CREAD | Posix.HUPCL;
tios.c_lflag &= ~Posix.ECHO;
Posix.tcsetattr (fd, 0, tios);
}
#endif
private sealed class ProcMapsSoEntry {
public uint64 base_address;
public string path;
+7 -7
View File
@@ -758,9 +758,9 @@ namespace Frida {
set;
}
public ZygoteAgent (LinuxHostSession host_session, uint pid, string name) {
public ZygoteAgent (HostSessionConnection connection, uint pid, string name) {
Object (
host_session: host_session,
connection: connection,
pid: pid,
name: name
);
@@ -768,7 +768,7 @@ namespace Frida {
public async void load (Cancellable? cancellable) throws Error, IOError {
#if ARM || ARM64
LinuxHelper helper = ((LinuxHostSession) host_session).helper;
LinuxHelper helper = ((LinuxHostSession) connection.host_session).helper;
yield helper.await_syscall (pid, POLL_LIKE, cancellable);
try {
#endif
@@ -798,9 +798,9 @@ namespace Frida {
private sealed class SystemServerAgent : InternalAgent {
private delegate void CompletionNotify ();
public SystemServerAgent (LinuxHostSession host_session) {
public SystemServerAgent (HostSessionConnection connection) {
Object (
host_session: host_session,
connection: connection,
#if HAVE_V8
script_runtime: ScriptRuntime.V8
#else
@@ -996,7 +996,7 @@ namespace Frida {
}
};
LinuxHelper helper = ((LinuxHostSession) host_session).helper;
LinuxHelper helper = ((LinuxHostSession) connection.host_session).helper;
foreach (var tid in thread_ids) {
bool safe_to_suspend = false;
string thread_name;
@@ -1050,7 +1050,7 @@ namespace Frida {
}
private void resume_threads (Gee.List<uint> thread_ids) {
LinuxHelper helper = ((LinuxHostSession) host_session).helper;
LinuxHelper helper = ((LinuxHostSession) connection.host_session).helper;
foreach (var tid in thread_ids)
helper.resume_syscall.begin (tid, null);
}
+28
View File
@@ -500,6 +500,34 @@ if have_local_backend
endif
endif
if have_simmy_backend
backend_sources += [
'simmy' / 'simmy-host-session.vala',
'simmy' / 'simmy-host-session-glue.m',
'simmy' / 'text-plist.vala',
]
simmy_data = custom_target('frida-data-simmy',
input: [
'simmy' / 'frida-simmy.resources',
'simmy' / 'agent' / 'springboard.js',
],
output: [
'frida-data-simmy.vapi',
'frida-data-simmy.h',
'frida-data-simmy.c',
'frida-data-simmy-blob' + resource_blob_extension,
],
command: [
resource_compiler_cmd_array,
'-c', '@INPUT0@',
'-o', meson.current_build_dir() / 'frida-data-simmy',
'@INPUT1@',
],
)
backend_sources += [simmy_data]
endif
if have_fruity_backend
backend_sources += [
'fruity' / 'fruity-host-session.vala',
+2 -1
View File
@@ -957,7 +957,8 @@ namespace Frida {
_icon = builder.end ();
}
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
throws Error, IOError {
if (channel != null)
throw new Error.INVALID_OPERATION ("Already created");
+131
View File
@@ -0,0 +1,131 @@
const SBS = bind({
module: '/System/Library/PrivateFrameworks/SpringBoardServices.framework/SpringBoardServices',
cprefix: 'SBS',
functions: {
copyFrontmostApplicationDisplayIdentifier: ['pointer', [ ]],
copyLocalizedApplicationNameForDisplayIdentifier: ['pointer', ['pointer' ]],
copyIconImagePNGDataForDisplayIdentifier: ['pointer', ['pointer' ]],
processIDForDisplayIdentifier: ['bool', ['pointer', 'pointer']],
}
});
const CF = bind({
module: '/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation',
cprefix: 'CF',
functions: {
retain: ['pointer', ['pointer' ]],
release: ['void', ['pointer' ]],
stringCreateWithCString: ['pointer', ['pointer', 'pointer', 'uint' ]],
stringGetCStringPtr: ['pointer', ['pointer', 'uint' ]],
stringGetLength: ['long', ['pointer' ]],
stringGetMaximumSizeForEncoding: ['long', ['long', 'uint' ]],
stringGetCString: ['bool', ['pointer', 'pointer', 'long', 'uint']],
dataGetLength: ['long', ['pointer' ]],
dataGetBytePtr: ['pointer', ['pointer' ]],
}
});
const kCFBooleanTrue = Module.getGlobalExportByName('kCFBooleanTrue').readPointer();
const kCFStringEncodingUTF8 = 0x08000100;
rpc.exports = {
getFrontmostApplication(scope) {
const idObj = SBS.copyFrontmostApplicationDisplayIdentifier();
if (idObj.isNull())
return null;
const id = cfStringToUtf8(idObj);
const nameObj = SBS.copyLocalizedApplicationNameForDisplayIdentifier(idObj);
const name = cfStringToUtf8(nameObj);
const pidBuf = Memory.alloc(4);
SBS.processIDForDisplayIdentifier(idObj, pidBuf);
const pid = pidBuf.readU32();
CF.release(nameObj);
CF.release(idObj);
if (pid === 0)
return null;
return [
id,
name,
pid,
(scope === 'full') ? fetchApplicationIcon(id) : null
];
},
fetchApplicationIcons(appIds) {
return appIds.map(fetchApplicationIcon);
},
};
function fetchApplicationIcon(appId) {
const idObj = CF.stringCreateWithCString(NULL, Memory.allocUtf8String(appId), kCFStringEncodingUTF8);
const pngObj = SBS.copyIconImagePNGDataForDisplayIdentifier(idObj);
CF.release(idObj);
const png = CF.dataGetBytePtr(pngObj).readByteArray(CF.dataGetLength(pngObj));
CF.release(pngObj);
return base64FromBytes(png);
}
Interceptor.attach(Module.getGlobalExportByName('SecTaskCopyValueForEntitlement'), {
onEnter(args) {
this.entitlement = cfStringToUtf8(args[1]);
},
onLeave(retval) {
if (this.entitlement === 'com.apple.springboard.iconState' && retval.isNull())
retval.replace(CF.retain(kCFBooleanTrue));
}
});
function bind({ module, cprefix, functions }) {
const mod = Process.getModuleByName(module);
const nfOpts = { exceptions: 'propagate' };
return Object.fromEntries(Object.entries(functions).map(([name, [retType, argTypes]]) => [
name,
new NativeFunction(mod.getExportByName(cprefix + name.charAt(0).toUpperCase() + name.slice(1)), retType, argTypes, nfOpts)
]));
}
function cfStringToUtf8(cfstr) {
const direct = CF.stringGetCStringPtr(cfstr, kCFStringEncodingUTF8);
if (!direct.isNull())
return direct.readUtf8String();
const len16 = CF.stringGetLength(cfstr);
const maxBytes = CF.stringGetMaximumSizeForEncoding(len16, kCFStringEncodingUTF8).valueOf() + 1;
const buf = Memory.alloc(maxBytes);
CF.stringGetCString(cfstr, buf, maxBytes, kCFStringEncodingUTF8);
return buf.readUtf8String();
}
function base64FromBytes(buf) {
const bytes = new Uint8Array(buf);
const enc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
let i = 0;
const len = bytes.length;
let out = '';
while (i < len) {
const c1 = bytes[i++];
const c2 = (i !== len) ? bytes[i++] : 0;
const c3 = (i !== len) ? bytes[i++] : 0;
const o1 = c1 >> 2;
const o2 = ((c1 & 0x03) << 4) | (c2 >> 4);
const o3 = ((c2 & 0x0f) << 2) | (c3 >> 6);
const o4 = c3 & 0x3f;
if (i - 1 > len)
out += enc[o1] + enc[o2] + '==';
else if (i > len)
out += enc[o1] + enc[o2] + enc[o3] + '=';
else
out += enc[o1] + enc[o2] + enc[o3] + enc[o4];
}
return out;
}
+2
View File
@@ -0,0 +1,2 @@
[resource-compiler]
namespace = Frida.Data.Simmy
+431
View File
@@ -0,0 +1,431 @@
#include "frida-core.h"
#include <dlfcn.h>
#import <Foundation/Foundation.h>
typedef struct _FridaSimmyContext FridaSimmyContext;
struct _FridaSimmyContext
{
dispatch_queue_t dispatch_queue;
void * core_simulator;
NSString * SimDeviceLaunchApplicationKeyStandardErrPath;
NSString * SimDeviceLaunchApplicationKeyStandardOutPath;
NSString * SimDeviceLaunchApplicationKeyTerminateRunningProcess;
NSString * SimDeviceLaunchApplicationKeyWaitForDebugger;
NSString * SimDeviceSpawnKeyArguments;
NSString * SimDeviceSpawnKeyStderr;
NSString * SimDeviceSpawnKeyStdin;
NSString * SimDeviceSpawnKeyStdout;
NSString * SimDeviceSpawnKeyWaitForDebugger;
FridaSimmyHostSessionBackendDeviceAddedFunc on_device_added;
gpointer on_device_added_target;
};
static void frida_simmy_context_destroy (FridaSimmyContext * self);
static void frida_simmy_context_emit_devices (FridaSimmyContext * self);
@interface SimRuntime : NSObject
@property (nonatomic, readonly, strong) NSString * identifier;
@property (nonatomic, readonly, strong) NSString * shortName;
@property (nonatomic, readonly, strong) NSString * versionString;
@property (nonatomic, readonly, strong) NSString * root;
@end
@interface SimDevice : NSObject
@property (nonatomic, readonly, strong) NSUUID * UDID;
@property (nonatomic, readonly, strong) NSString * name;
@property (nonatomic, readonly, strong) SimRuntime * runtime;
@property (nonatomic, readonly, strong) NSString * stateString;
- (NSString *)getenv:(NSString *)name
error:(NSError * _Nullable * _Nullable)error;
- (NSDictionary<NSString *, NSDictionary<NSString *, id> *> *)installedAppsWithError:(NSError * _Nullable * _Nullable)error;
- (void)launchApplicationAsyncWithID:(NSString *)identifier
options:(NSDictionary<NSString *, id> * _Nullable)options
completionQueue:(dispatch_queue_t)queue
completionHandler:(void (^) (NSError * error, int pid))handler;
- spawnAsyncWithPath:(NSString *)path
options:(NSDictionary<NSString *, id> * _Nullable)options
terminationQueue:(dispatch_queue_t)tq
terminationHandler:(void (^) (int status))th
completionQueue:(dispatch_queue_t)cq
completionHandler:(void (^) (NSError * error, int pid))ch;
@end
@interface SimDeviceSet : NSObject
@property (nonatomic, readonly, strong) NSArray<SimDevice *> *devices;
@end
@protocol SimServiceContextClass
+ (instancetype)serviceContextForDeveloperDir:(NSString *)dir
error:(NSError * _Nullable * _Nullable)error;
@end
@interface SimServiceContext : NSObject
- (SimDeviceSet *)defaultDeviceSetWithError:(NSError * _Nullable * _Nullable)error;
@end
void *
_frida_simmy_host_session_backend_start (FridaSimmyHostSessionBackendDeviceAddedFunc on_device_added, gpointer on_device_added_target,
FridaSimmyCompleteFunc on_complete, gpointer on_complete_target, GDestroyNotify on_complete_target_destroy_notify)
{
FridaSimmyContext * ctx;
ctx = g_slice_new0 (FridaSimmyContext);
ctx->dispatch_queue = dispatch_queue_create ("re.frida.simmy.queue", DISPATCH_QUEUE_SERIAL);
ctx->on_device_added = on_device_added;
ctx->on_device_added_target = on_device_added_target;
dispatch_async (ctx->dispatch_queue, ^
{
frida_simmy_context_emit_devices (ctx);
on_complete (on_complete_target);
if (on_complete_target_destroy_notify != NULL)
on_complete_target_destroy_notify (on_complete_target);
});
return ctx;
}
void
_frida_simmy_host_session_backend_stop (void * simmy_context, FridaSimmyCompleteFunc on_complete,
gpointer on_complete_target, GDestroyNotify on_complete_target_destroy_notify)
{
FridaSimmyContext * ctx = simmy_context;
dispatch_async (ctx->dispatch_queue, ^
{
frida_simmy_context_destroy (ctx);
g_slice_free (FridaSimmyContext, ctx);
on_complete (on_complete_target);
if (on_complete_target_destroy_notify != NULL)
on_complete_target_destroy_notify (on_complete_target);
});
}
static void
frida_simmy_context_destroy (FridaSimmyContext * self)
{
g_clear_pointer (&self->core_simulator, dlclose);
dispatch_release (self->dispatch_queue);
}
static void
frida_simmy_context_emit_devices (FridaSimmyContext * self)
{
void * xcselect_module;
bool (* xcselect_get_developer_dir_path) (char *, size_t, bool *, bool *, bool *);
char developer_dir[1024] = { 0, };
bool from_override, is_command_line_tools, from_fallback;
void * cs;
NSString ** str;
Class<SimServiceContextClass> SimServiceContextClass;
SimServiceContext * ctx;
SimDeviceSet * set;
xcselect_module = dlopen ("/usr/lib/libxcselect.dylib", RTLD_GLOBAL | RTLD_LAZY);
if (xcselect_module == NULL)
goto beach;
xcselect_get_developer_dir_path = dlsym (xcselect_module, "xcselect_get_developer_dir_path");
if (!xcselect_get_developer_dir_path (developer_dir, sizeof (developer_dir), &from_override, &is_command_line_tools, &from_fallback))
goto beach;
cs = dlopen ("/Library/Developer/PrivateFrameworks/CoreSimulator.framework/CoreSimulator", RTLD_GLOBAL | RTLD_LAZY);
if (cs == NULL)
goto beach;
self->core_simulator = cs;
#define FRIDA_ASSIGN_CS_CONSTANT(N) \
str = dlsym (cs, G_STRINGIFY (N)); \
g_assert (str != NULL); \
self->N = *str
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceLaunchApplicationKeyStandardErrPath);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceLaunchApplicationKeyStandardOutPath);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceLaunchApplicationKeyTerminateRunningProcess);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceLaunchApplicationKeyWaitForDebugger);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceSpawnKeyArguments);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceSpawnKeyStderr);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceSpawnKeyStdin);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceSpawnKeyStdout);
FRIDA_ASSIGN_CS_CONSTANT (SimDeviceSpawnKeyWaitForDebugger);
#undef FRIDA_ASSIGN_CS_CONSTANT
SimServiceContextClass = NSClassFromString (@"SimServiceContext");
ctx = [SimServiceContextClass serviceContextForDeveloperDir:[NSString stringWithUTF8String:developer_dir] error:nil];
set = [ctx defaultDeviceSetWithError:nil];
for (SimDevice * device in set.devices)
{
FridaSimmyRuntime * runtime;
FridaSimmyDevice * d;
if (![device.stateString isEqualToString:@"Booted"])
continue;
runtime = frida_simmy_runtime_new (device.runtime);
d = frida_simmy_device_new ([device retain], device.UDID.UUIDString.UTF8String, device.name.UTF8String,
[device getenv:@"SIMULATOR_MODEL_IDENTIFIER" error:nil].UTF8String, runtime, self);
self->on_device_added (d, self->on_device_added_target);
g_object_unref (d);
g_object_unref (runtime);
}
beach:
if (xcselect_module != NULL)
dlclose (xcselect_module);
}
void
_frida_simmy_device_list_applications (FridaSimmyDevice * self, FridaSimmyDeviceListApplicationsCompleteFunc on_complete,
gpointer on_complete_target, GDestroyNotify on_complete_target_destroy_notify)
{
FridaSimmyContext * ctx = frida_simmy_device_get_simmy_context (self);
dispatch_async (ctx->dispatch_queue, ^
{
GeeArrayList * applications;
SimDevice * device;
applications = gee_array_list_new (FRIDA_SIMMY_TYPE_APPLICATION, g_object_ref, g_object_unref, NULL, NULL, NULL);
device = frida_simmy_device_get_handle (self);
NSDictionary<NSString *, NSDictionary<NSString *, id> *> * bundles = [device installedAppsWithError:nil];
for (NSString * identifier in bundles)
{
NSDictionary<NSString *, id> * bundle;
NSString * display_name;
FridaSimmyApplication * app;
bundle = bundles[identifier];
display_name = bundle[@"CFBundleDisplayName"];
app = frida_simmy_application_new (identifier.UTF8String, display_name.UTF8String);
gee_collection_add (GEE_COLLECTION (applications), app);
g_object_unref (app);
}
on_complete (GEE_LIST (applications), on_complete_target);
if (on_complete_target_destroy_notify != NULL)
on_complete_target_destroy_notify (on_complete_target);
g_object_unref (applications);
});
}
void
_frida_simmy_device_launch_application (FridaSimmyDevice * self, const gchar * identifier, FridaHostSpawnOptions * options,
FridaSimmyDeviceLaunchApplicationCompleteFunc on_complete, gpointer on_complete_target,
GDestroyNotify on_complete_target_destroy_notify)
{
SimDevice * device;
FridaSimmyContext * ctx;
__block FridaStdioPipes * pipes;
__block FridaFileDescriptor * out_fd, * err_fd;
gchar * out_name, * err_name;
GError * error = NULL;
__block GMainContext * main_context;
device = frida_simmy_device_get_handle (self);
ctx = frida_simmy_device_get_simmy_context (self);
pipes = frida_make_stdio_pipes (options->stdio, FALSE, NULL, NULL, &out_fd, &out_name, &err_fd, &err_name, &error);
if (error != NULL)
{
dispatch_async (ctx->dispatch_queue, ^
{
on_complete (error->message, NULL, on_complete_target);
g_error_free (error);
if (on_complete_target_destroy_notify != NULL)
on_complete_target_destroy_notify (on_complete_target);
});
return;
}
main_context = g_main_context_ref_thread_default ();
@autoreleasepool
{
NSMutableDictionary<NSString *, id> * launch_opts;
launch_opts = [@{
ctx->SimDeviceLaunchApplicationKeyWaitForDebugger: @YES,
ctx->SimDeviceLaunchApplicationKeyTerminateRunningProcess: @YES,
} mutableCopy];
if (options->stdio == FRIDA_STDIO_PIPE)
{
launch_opts[ctx->SimDeviceLaunchApplicationKeyStandardOutPath] = @(out_name);
launch_opts[ctx->SimDeviceLaunchApplicationKeyStandardErrPath] = @(err_name);
};
[device launchApplicationAsyncWithID:[NSString stringWithUTF8String:identifier]
options:launch_opts
completionQueue:ctx->dispatch_queue
completionHandler:
^(NSError * error, int pid)
{
FridaSimmySpawnedProcess * process = NULL;
if (error == nil)
process = frida_simmy_spawned_process_new (pid, pipes, main_context);
g_main_context_unref (main_context);
main_context = NULL;
g_clear_object (&pipes);
g_clear_object (&out_fd);
g_clear_object (&err_fd);
on_complete (error.localizedDescription.UTF8String, process, on_complete_target);
g_clear_object (&process);
if (on_complete_target_destroy_notify != NULL)
on_complete_target_destroy_notify (on_complete_target);
}];
}
g_free (out_name);
g_free (err_name);
}
void
_frida_simmy_device_spawn_program (FridaSimmyDevice * self, const gchar * program, FridaHostSpawnOptions * options,
FridaSimmyDeviceSpawnProgramCompleteFunc on_complete, gpointer on_complete_target,
GDestroyNotify on_complete_target_destroy_notify)
{
const FridaStdio stdio = options->stdio;
SimDevice * device;
FridaSimmyContext * ctx;
__block FridaStdioPipes * pipes;
__block FridaFileDescriptor * in_fd, * out_fd, * err_fd;
GError * error = NULL;
__block GMainContext * main_context;
device = frida_simmy_device_get_handle (self);
ctx = frida_simmy_device_get_simmy_context (self);
pipes = frida_make_stdio_pipes (options->stdio, TRUE, &in_fd, NULL, &out_fd, NULL, &err_fd, NULL, &error);
if (error != NULL)
{
dispatch_async (ctx->dispatch_queue, ^
{
on_complete (error->message, NULL, on_complete_target);
g_error_free (error);
if (on_complete_target_destroy_notify != NULL)
on_complete_target_destroy_notify (on_complete_target);
});
return;
}
main_context = g_main_context_ref_thread_default ();
@autoreleasepool
{
NSMutableDictionary<NSString *, id> * spawn_opts;
__block FridaSimmySpawnedProcess * process = NULL;
spawn_opts = [@{
ctx->SimDeviceSpawnKeyWaitForDebugger: @YES,
} mutableCopy];
if (options->has_argv)
{
NSMutableArray<NSString *> * args;
int i;
args = [NSMutableArray arrayWithCapacity:options->argv_length1];
for (i = 0; i != options->argv_length1; i++)
[args addObject:[NSString stringWithUTF8String:options->argv[i]]];
spawn_opts[ctx->SimDeviceSpawnKeyArguments] = args;
}
if (stdio == FRIDA_STDIO_PIPE)
{
spawn_opts[ctx->SimDeviceSpawnKeyStdin] = @(in_fd->handle);
spawn_opts[ctx->SimDeviceSpawnKeyStdout] = @(out_fd->handle);
spawn_opts[ctx->SimDeviceSpawnKeyStderr] = @(err_fd->handle);
};
[device spawnAsyncWithPath:[NSString stringWithUTF8String:program]
options:spawn_opts
terminationQueue:ctx->dispatch_queue
terminationHandler:
^(int status)
{
_frida_simmy_spawned_process_on_termination (process, status);
g_object_unref (process);
process = NULL;
}
completionQueue:ctx->dispatch_queue
completionHandler:
^(NSError * error, int pid)
{
if (error == nil)
process = frida_simmy_spawned_process_new (pid, pipes, main_context);
g_main_context_unref (main_context);
main_context = NULL;
g_clear_object (&pipes);
g_clear_object (&in_fd);
g_clear_object (&out_fd);
g_clear_object (&err_fd);
on_complete (error.localizedDescription.UTF8String, process, on_complete_target);
if (on_complete_target_destroy_notify != NULL)
on_complete_target_destroy_notify (on_complete_target);
}];
}
}
const gchar *
_frida_simmy_runtime_get_identifier (FridaSimmyRuntime * self)
{
return ((SimRuntime *) frida_simmy_runtime_get_handle (self)).identifier.UTF8String;
}
const gchar *
_frida_simmy_runtime_get_short_name (FridaSimmyRuntime * self)
{
return ((SimRuntime *) frida_simmy_runtime_get_handle (self)).shortName.UTF8String;
}
const gchar *
_frida_simmy_runtime_get_version_string (FridaSimmyRuntime * self)
{
return ((SimRuntime *) frida_simmy_runtime_get_handle (self)).versionString.UTF8String;
}
const gchar *
_frida_simmy_runtime_get_root (FridaSimmyRuntime * self)
{
return ((SimRuntime *) frida_simmy_runtime_get_handle (self)).root.UTF8String;
}
+876
View File
@@ -0,0 +1,876 @@
namespace Frida {
public sealed class SimmyHostSessionBackend : Object, HostSessionBackend {
private Gee.Map<string, SimmyHostSessionProvider> providers = new Gee.HashMap<string, SimmyHostSessionProvider> ();
private void * simmy_context;
private MainContext main_context;
private Cancellable io_cancellable = new Cancellable ();
construct {
main_context = MainContext.ref_thread_default ();
}
public async void start (Cancellable? cancellable) throws IOError {
simmy_context = _start (on_device_added, () => {
schedule_on_frida_thread (start.callback);
});
yield;
}
public async void stop (Cancellable? cancellable) throws IOError {
_stop (simmy_context, () => {
schedule_on_frida_thread (stop.callback);
});
yield;
simmy_context = null;
io_cancellable.cancel ();
foreach (var provider in providers.values) {
provider_unavailable (provider);
yield provider.close (cancellable);
}
providers.clear ();
}
private void on_device_added (Simmy.Device device) {
schedule_on_frida_thread (() => {
var prov = new SimmyHostSessionProvider (device);
providers[device.udid] = prov;
provider_available (prov);
return Source.REMOVE;
});
}
private void schedule_on_frida_thread (owned SourceFunc function) {
var source = new IdleSource ();
source.set_callback ((owned) function);
source.attach (main_context);
}
public extern static void * _start (DeviceAddedFunc on_device_added, owned Simmy.CompleteFunc on_complete);
public extern static void _stop (void * simmy_context, owned Simmy.CompleteFunc on_complete);
public delegate void DeviceAddedFunc (Simmy.Device device);
}
public sealed class SimmyHostSessionProvider : Object, HostSessionProvider {
public Simmy.Device device {
get;
construct;
}
public string id {
get { return device.udid; }
}
public string name {
get { return device.name; }
}
public Variant? icon {
get { return null; }
}
public HostSessionProviderKind kind {
get {
return HostSessionProviderKind.REMOTE;
}
}
private SimmyHostSession? host_session;
public SimmyHostSessionProvider (Simmy.Device device) {
Object (device: device);
}
public async void close (Cancellable? cancellable) throws IOError {
if (host_session == null)
return;
host_session.agent_session_detached.disconnect (on_agent_session_detached);
yield host_session.close (cancellable);
host_session = null;
}
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
throws Error, IOError {
if (host_session != null)
throw new Error.INVALID_OPERATION ("Already created");
HostSessionEntry local_system = yield hub.resolve_host_session ("local", cancellable);
host_session = new SimmyHostSession (device, local_system);
host_session.agent_session_detached.connect (on_agent_session_detached);
return host_session;
}
public async void destroy (HostSession session, Cancellable? cancellable) throws Error, IOError {
if (session != host_session)
throw new Error.INVALID_ARGUMENT ("Invalid host session");
host_session.agent_session_detached.disconnect (on_agent_session_detached);
yield host_session.close (cancellable);
host_session = null;
}
public async AgentSession link_agent_session (HostSession host_session, AgentSessionId id, AgentMessageSink sink,
Cancellable? cancellable) throws Error, IOError {
if (host_session != this.host_session)
throw new Error.INVALID_ARGUMENT ("Invalid host session");
return yield this.host_session.link_agent_session (id, sink, cancellable);
}
public void unlink_agent_session (HostSession host_session, AgentSessionId id) {
if (host_session != this.host_session)
return;
this.host_session.unlink_agent_session (id);
}
public async IOStream link_channel (HostSession host_session, ChannelId id, Cancellable? cancellable)
throws Error, IOError {
throw new Error.NOT_SUPPORTED ("Channels are not supported by this backend");
}
public void unlink_channel (HostSession host_session, ChannelId id) {
}
public async ServiceSession link_service_session (HostSession host_session, ServiceSessionId id, Cancellable? cancellable)
throws Error, IOError {
throw new Error.NOT_SUPPORTED ("Services are not supported by this backend");
}
public void unlink_service_session (HostSession host_session, ServiceSessionId id) {
}
private void on_agent_session_detached (AgentSessionId id, SessionDetachReason reason, CrashInfo crash) {
agent_session_detached (id, reason, crash);
}
}
public sealed class SimmyHostSession : Object, HostSession, HostSessionConnection {
public Simmy.Device device {
get;
construct;
}
public HostSessionEntry local_system {
get;
construct;
}
public HostSession host_session {
get {
return this;
}
}
private Gee.Map<uint, Simmy.SpawnedProcess> spawned_processes = new Gee.HashMap<uint, Simmy.SpawnedProcess> ();
private SpringboardAgent springboard_agent;
private Cancellable io_cancellable = new Cancellable ();
public SimmyHostSession (Simmy.Device device, HostSessionEntry local_system) {
Object (device: device, local_system: local_system);
}
construct {
var s = local_system.session;
s.process_crashed.connect (on_process_crashed);
s.agent_session_detached.connect (on_agent_session_detached);
s.uninjected.connect (on_uninjected);
springboard_agent = new SpringboardAgent (this);
springboard_agent.unloaded.connect (on_springboard_agent_unloaded);
}
public async void close (Cancellable? cancellable) throws IOError {
springboard_agent.unloaded.disconnect (on_springboard_agent_unloaded);
yield springboard_agent.close (cancellable);
io_cancellable.cancel ();
}
public async void ping (uint interval_seconds, Cancellable? cancellable) throws Error, IOError {
throw new Error.INVALID_OPERATION ("Only meant to be implemented by services");
}
public async HashTable<string, Variant> query_system_parameters (Cancellable? cancellable) throws Error, IOError {
var parameters = new HashTable<string, Variant> (str_hash, str_equal);
var runtime = device.runtime;
unowned string os_name = runtime.short_name;
var os = new HashTable<string, Variant> (str_hash, str_equal);
os["id"] = os_name.down ();
os["name"] = os_name.replace ("iOS", "iPhone OS");
os["version"] = runtime.version_string;
parameters["os"] = os;
parameters["platform"] = "darwin";
parameters["arch"] = "arm64";
var hardware = new HashTable<string, Variant> (str_hash, str_equal);
hardware["product"] = device.model_identifier;
parameters["hardware"] = hardware;
parameters["access"] = "full";
parameters["name"] = device.name;
parameters["udid"] = device.udid;
return parameters;
}
public async HostApplicationInfo get_frontmost_application (HashTable<string, Variant> options,
Cancellable? cancellable) throws Error, IOError {
var opts = FrontmostQueryOptions._deserialize (options);
return yield springboard_agent.get_frontmost_application (opts, cancellable);
}
public async HostApplicationInfo[] enumerate_applications (HashTable<string, Variant> options,
Cancellable? cancellable) throws Error, IOError {
var opts = ApplicationQueryOptions._deserialize (options);
var scope = opts.scope;
Future<Gee.List<Simmy.Application>> list_apps_request = device.list_applications ();
Future<Gee.List<LaunchdJob>> list_jobs_request = list_launchd_jobs (cancellable);
Gee.Map<string, Bytes>? icons = null;
if (scope == FULL) {
var app_ids = new Gee.ArrayList<string> ();
foreach (Simmy.Application app in yield list_apps_request.wait_async (cancellable))
app_ids.add (app.identifier);
icons = yield springboard_agent.fetch_application_icons (app_ids, cancellable);
}
var pids = new Gee.HashMap<string, uint> ();
foreach (var job in yield list_jobs_request.wait_async (cancellable)) {
if (job.pid == 0)
continue;
unowned string label = job.label;
if (!label.has_prefix ("UIKitApplication:"))
continue;
string identifier = label[17:].split ("[", 2)[0];
pids[identifier] = job.pid;
}
var result = new HostApplicationInfo[0];
foreach (Simmy.Application app in yield list_apps_request.wait_async (cancellable)) {
unowned string identifier = app.identifier;
var info = HostApplicationInfo (identifier, app.display_name, pids[identifier], make_parameters_dict ());
if (scope == FULL)
add_app_icons (info.parameters, icons[identifier]);
result += info;
}
return result;
}
public async HostProcessInfo[] enumerate_processes (HashTable<string, Variant> options, Cancellable? cancellable)
throws Error, IOError {
var opts = ProcessQueryOptions._deserialize (options);
if (!opts.has_selected_pids ()) {
foreach (var job in yield list_launchd_jobs (cancellable).wait_async (cancellable)) {
if (job.pid != 0)
opts.select_pid (job.pid);
}
}
try {
return yield local_system.session.enumerate_processes (opts._serialize (), cancellable);
} catch (GLib.Error e) {
throw_api_error (e);
}
}
public async void enable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
public async void disable_spawn_gating (Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
public async HostSpawnInfo[] enumerate_pending_spawn (Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
public async HostChildInfo[] enumerate_pending_children (Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
public async uint spawn (string program, HostSpawnOptions options, Cancellable? cancellable) throws Error, IOError {
Future<Simmy.SpawnedProcess> f;
if (program[0] == '/')
f = device.spawn_program (program, options);
else
f = device.launch_application (program, options);
var process = yield f.wait_async (cancellable);
uint pid = process.pid;
if (options.stdio == PIPE) {
spawned_processes[pid] = process;
process.terminated.connect (on_spawned_process_terminated);
process.output.connect (on_spawned_process_output);
}
return pid;
}
private void on_spawned_process_terminated (Simmy.SpawnedProcess process, int status) {
spawned_processes.unset (process.pid);
}
private void on_spawned_process_output (Simmy.SpawnedProcess process, int fd, uint8[] data) {
output (process.pid, fd, data);
if (data.length == 0)
spawned_processes.unset (process.pid);
}
public async void input (uint pid, uint8[] data, Cancellable? cancellable) throws Error, IOError {
var process = spawned_processes[pid];
if (process == null)
throw new Error.INVALID_ARGUMENT ("Invalid PID");
yield process.input (data, cancellable);
}
public async void resume (uint pid, Cancellable? cancellable) throws GLib.Error {
yield local_system.session.resume (pid, cancellable);
}
public async void kill (uint pid, Cancellable? cancellable) throws GLib.Error {
yield local_system.session.kill (pid, cancellable);
}
public async AgentSessionId attach (uint pid, HashTable<string, Variant> options,
Cancellable? cancellable) throws GLib.Error {
return yield local_system.session.attach (pid, options, cancellable);
}
public async void reattach (AgentSessionId id, Cancellable? cancellable) throws Error, IOError {
throw new Error.INVALID_OPERATION ("Only meant to be implemented by services");
}
public async AgentSession link_agent_session (AgentSessionId id, AgentMessageSink sink,
Cancellable? cancellable) throws Error, IOError {
return yield local_system.provider.link_agent_session (local_system.session, id, sink, cancellable);
}
public void unlink_agent_session (AgentSessionId id) {
local_system.provider.unlink_agent_session (local_system.session, id);
}
public async InjectorPayloadId inject_library_file (uint pid, string path, string entrypoint, string data,
Cancellable? cancellable) throws GLib.Error {
return yield local_system.session.inject_library_file (pid, path, entrypoint, data, cancellable);
}
public async InjectorPayloadId inject_library_blob (uint pid, uint8[] blob, string entrypoint, string data,
Cancellable? cancellable) throws GLib.Error {
return yield local_system.session.inject_library_blob (pid, blob, entrypoint, data, cancellable);
}
public async ChannelId open_channel (string address, Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
public IOStream link_channel (ChannelId id) throws Error {
throw_not_supported ();
}
public void unlink_channel (ChannelId id) {
assert_not_reached ();
}
public async ServiceSessionId open_service (string address, Cancellable? cancellable) throws Error, IOError {
throw_not_supported ();
}
public ServiceSession link_service_session (ServiceSessionId id) throws Error {
throw_not_supported ();
}
public void unlink_service_session (ServiceSessionId id) {
assert_not_reached ();
}
private void on_process_crashed (CrashInfo crash) {
process_crashed (crash);
}
private void on_agent_session_detached (AgentSessionId id, SessionDetachReason reason, CrashInfo crash) {
agent_session_detached (id, reason, crash);
}
private void on_uninjected (InjectorPayloadId id) {
uninjected (id);
}
internal Future<Gee.List<LaunchdJob>> list_launchd_jobs (Cancellable? cancellable) {
var promise = new Promise<Gee.List<LaunchdJob>> ();
perform_list_launchd_jobs.begin (cancellable, promise);
return promise.future;
}
private async void perform_list_launchd_jobs (Cancellable? cancellable, Promise<Gee.List<LaunchdJob>> request) {
try {
RunResult res = yield run_and_capture_output ({
Path.build_filename (device.runtime.root, "bin", "launchctl"),
"list"
}, cancellable).wait_async (cancellable);
res.check ();
var jobs = new Gee.ArrayList<LaunchdJob> ();
string[] lines = res.standard_output.chomp ().split ("\n");
foreach (string line in lines[1:]) {
string[] tokens = line.split ("\t", 3);
if (tokens.length != 3)
throw new Error.PROTOCOL ("Unexpected launchctl output");
unowned string raw_pid = tokens[0];
unowned string label = tokens[2];
jobs.add (new LaunchdJob () {
pid = uint.parse (raw_pid),
label = label,
});
}
request.resolve (jobs);
} catch (GLib.Error e) {
request.reject (e);
}
}
internal class LaunchdJob {
public uint pid;
public string label;
}
private Future<RunResult> run_and_capture_output (string[] argv, Cancellable? cancellable) {
var promise = new Promise<RunResult> ();
perform_run_and_capture_output.begin (argv, cancellable, promise);
return promise.future;
}
private async void perform_run_and_capture_output (string[] argv, Cancellable? cancellable, Promise<RunResult> request) {
string[] argv_copy = argv;
var opts = HostSpawnOptions ();
opts.has_argv = true;
opts.argv = argv_copy;
opts.stdio = PIPE;
Simmy.SpawnedProcess process;
try {
process = yield device.spawn_program (argv_copy[0], opts).wait_async (cancellable);
} catch (GLib.Error e) {
request.reject (e);
return;
}
int status = -1;
var standard_output = new StringBuilder.sized (1024);
var standard_error = new StringBuilder.sized (1024);
process.terminated.connect ((s) => {
status = s;
perform_run_and_capture_output.callback ();
});
process.output.connect ((fd, data) => {
var buf = new char[data.length + 1];
Memory.copy (buf, data, data.length);
buf[data.length] = '\0';
unowned StringBuilder target = (fd == 1) ? standard_output : standard_error;
target.append ((string) buf);
});
process.resume ();
yield;
request.resolve (new RunResult (argv_copy) {
status = status,
standard_output = standard_output.str,
standard_error = standard_error.str,
});
}
private class RunResult {
public int status;
public string standard_output;
public string standard_error;
private string[] argv;
public RunResult (string[] argv) {
this.argv = argv;
}
public void check () throws Error {
if (status != 0) {
throw new Error.NOT_SUPPORTED ("Failed to run `%s`, it exited with status %d: %s",
string.joinv (" ", argv),
status,
standard_error.chomp ());
}
}
}
private void on_springboard_agent_unloaded (InternalAgent dead_agent) {
dead_agent.unloaded.disconnect (on_springboard_agent_unloaded);
springboard_agent = new SpringboardAgent (this);
springboard_agent.unloaded.connect (on_springboard_agent_unloaded);
}
[NoReturn]
private static void throw_not_supported () throws Error {
throw new Error.NOT_SUPPORTED ("Not yet supported by the Simmy backend");
}
}
private sealed class SpringboardAgent : InternalAgent {
public SpringboardAgent (HostSessionConnection connection) {
Object (connection: connection);
}
public async HostApplicationInfo get_frontmost_application (FrontmostQueryOptions options,
Cancellable? cancellable) throws Error, IOError {
var scope = options.scope;
var scope_node = new Json.Node.alloc ().init_string (scope.to_nick ());
Json.Node response = yield call ("getFrontmostApplication", new Json.Node[] { scope_node }, null, cancellable);
if (response.get_node_type () == NULL)
return HostApplicationInfo.empty ();
var item = response.get_array ();
var identifier = item.get_string_element (0);
var name = item.get_string_element (1);
var pid = (uint) item.get_int_element (2);
var info = HostApplicationInfo (identifier, name, pid, make_parameters_dict ());
if (scope == FULL)
add_app_icons (info.parameters, new Bytes.take (Base64.decode (item.get_string_element (3))));
return info;
}
public async Gee.Map<string, Bytes> fetch_application_icons (Gee.List<string> app_ids, Cancellable? cancellable)
throws Error, IOError {
var app_id_values = new Json.Array ();
foreach (var id in app_ids)
app_id_values.add_string_element (id);
var args = new Json.Node[] { new Json.Node.alloc ().init_array (app_id_values) };
Json.Node response = yield call ("fetchApplicationIcons", args, null, cancellable);
var icons = new Gee.HashMap<string, Bytes> ();
var reader = new Json.Reader (response);
int n = reader.count_elements ();
for (int i = 0; i != n; i++) {
reader.read_element (i);
if (!reader.get_null_value ())
icons[app_ids[i]] = new Bytes (Base64.decode (reader.get_string_value ()));
reader.end_element ();
}
return icons;
}
protected override async uint get_target_pid (Cancellable? cancellable) throws Error, IOError {
var host_session = (SimmyHostSession) connection.host_session;
foreach (var job in yield host_session.list_launchd_jobs (cancellable).wait_async (cancellable)) {
if (job.label == "com.apple.SpringBoard")
return job.pid;
}
throw new Error.NOT_SUPPORTED ("Unable to locate SpringBoard process");
}
protected override async string? load_source (Cancellable? cancellable) throws Error, IOError {
return (string) Frida.Data.Simmy.get_springboard_js_blob ().data;
}
}
private void add_app_icons (HashTable<string, Variant> parameters, Bytes png) {
var icons = new VariantBuilder (new VariantType.array (VariantType.VARDICT));
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 ();
parameters["icons"] = icons.end ();
}
[CCode (gir_namespace = "FridaSimmy", gir_version = "1.0")]
namespace Simmy {
public sealed class Device : Object {
public void * handle {
get;
construct;
}
public string udid {
get;
construct;
}
public string name {
get;
construct;
}
public string model_identifier {
get;
construct;
}
public Runtime runtime {
get;
construct;
}
public void * simmy_context {
get;
construct;
}
public Device (void * handle, string udid, string name, string model_identifier, Runtime runtime,
void * simmy_context) {
Object (
handle: handle,
udid: udid,
name: name,
model_identifier: model_identifier,
runtime: runtime,
simmy_context: simmy_context
);
}
public Future<Gee.List<Application>> list_applications () {
var promise = new Promise<Gee.List<Application>> ();
_list_applications (applications => {
promise.resolve (applications);
});
return promise.future;
}
public Future<SpawnedProcess> launch_application (string identifier, HostSpawnOptions options) {
var promise = new Promise<SpawnedProcess> ();
_launch_application (identifier, options, (error_message, process) => {
if (error_message != null)
promise.reject (new Error.NOT_SUPPORTED ("%s", error_message));
else
promise.resolve (process);
});
return promise.future;
}
public Future<SpawnedProcess> spawn_program (string program, HostSpawnOptions options) {
var promise = new Promise<SpawnedProcess> ();
_spawn_program (program, options, (error_message, process) => {
if (error_message != null)
promise.reject (new Error.NOT_SUPPORTED ("%s", error_message));
else
promise.resolve (process);
});
return promise.future;
}
public extern void _list_applications (owned ListApplicationsCompleteFunc on_complete);
public extern void _launch_application (string identifier, HostSpawnOptions options,
owned LaunchApplicationCompleteFunc on_complete);
public extern void _spawn_program (string program, HostSpawnOptions options,
owned SpawnProgramCompleteFunc on_complete);
public delegate void ListApplicationsCompleteFunc (Gee.List<Application> device);
public delegate void LaunchApplicationCompleteFunc (string? error_message, SpawnedProcess? process);
public delegate void SpawnProgramCompleteFunc (string? error_message, SpawnedProcess? process);
}
public sealed class Runtime : Object {
public void * handle {
get;
construct;
}
public string identifier {
get {
return _get_identifier ();
}
}
public string short_name {
get {
return _get_short_name ();
}
}
public string version_string {
get {
return _get_version_string ();
}
}
public string root {
get {
return _get_root ();
}
}
public Runtime (void * handle) {
Object (handle: handle);
}
public extern unowned string _get_identifier ();
public extern unowned string _get_short_name ();
public extern unowned string _get_version_string ();
public extern unowned string _get_root ();
}
public sealed class Application : Object {
public string identifier {
get;
construct;
}
public string display_name {
get;
construct;
}
public Application (string identifier, string display_name) {
Object (identifier: identifier, display_name: display_name);
}
}
public delegate void CompleteFunc ();
public sealed class SpawnedProcess : Object {
public signal void terminated (int status);
public signal void output (int fd, uint8[] data);
public uint pid {
get;
construct;
}
public StdioPipes? pipes {
get;
construct;
}
public MainContext main_context {
get;
construct;
}
private OutputStream? stdin_stream;
private Cancellable io_cancellable = new Cancellable ();
public SpawnedProcess (uint pid, StdioPipes? pipes, MainContext main_context) {
Object (
pid: pid,
pipes: pipes,
main_context: main_context
);
}
construct {
if (pipes != null) {
var source = new IdleSource ();
source.set_callback (() => {
start_processing_pipes ();
return Source.REMOVE;
});
source.attach (main_context);
}
}
private void start_processing_pipes () {
stdin_stream = pipes.input;
process_next_output_from.begin (pipes.output, 1);
process_next_output_from.begin (pipes.error, 2);
}
public void close () {
io_cancellable.cancel ();
}
public void resume () {
Posix.kill ((Posix.pid_t) pid, Posix.Signal.CONT);
}
public void _on_termination (int status) {
var source = new IdleSource ();
source.set_callback (() => {
if (stdin_stream != null) {
stdin_stream.close_async.begin ();
stdin_stream = null;
}
close ();
terminated (status);
return Source.REMOVE;
});
source.attach (main_context);
}
public async void input (uint8[] data, Cancellable? cancellable) throws Error, IOError {
if (stdin_stream == null)
throw new Error.NOT_SUPPORTED ("Unable to pass input to process spawned without piped stdio");
try {
yield stdin_stream.write_all_async (data, Priority.DEFAULT, cancellable, null);
} catch (GLib.Error e) {
throw new Error.TRANSPORT ("%s", e.message);
}
}
private async void process_next_output_from (InputStream stream, int fd) {
try {
var buf = new uint8[4096];
var n = yield stream.read_async (buf, Priority.DEFAULT, io_cancellable);
var data = buf[0:n];
output (fd, data);
if (n > 0)
process_next_output_from.begin (stream, fd);
} catch (GLib.Error e) {
stream.close_async.begin ();
if (!(e is IOError.CANCELLED))
output (fd, {});
}
}
}
}
}
+296
View File
@@ -0,0 +1,296 @@
[CCode (gir_namespace = "FridaSimmy", gir_version = "1.0")]
namespace Frida.TextPList {
public class Parser {
private Lexer lex;
private Token look;
public Parser (string text) {
lex = new Lexer (text);
}
public Variant parse () throws Error {
look = lex.next_token ();
Variant v = parse_value ();
if (look.type != EOF)
throw new Error.PROTOCOL ("Trailing content after root value");
return v;
}
private Variant parse_value () throws Error {
switch (look.type) {
case LBRACE: return parse_dict ();
case LPAREN: return parse_array ();
case NUMBER: return parse_number ();
case STRING: return parse_string ();
default:
throw new Error.PROTOCOL (@"Unexpected token %s".printf (look.type.to_string ()));
}
}
private Variant parse_dict () throws Error {
eat (LBRACE);
var b = new VariantBuilder (new VariantType ("a{sv}"));
while (look.type != RBRACE) {
if (look.type != STRING)
throw new Error.PROTOCOL ("Dictionary key must be STRING");
string key = look.text;
eat (STRING);
eat (EQUAL);
b.add ("{sv}", key, parse_value ());
if (look.type == SEMI)
eat (SEMI);
if (look.type == RBRACE)
break;
}
eat (RBRACE);
return b.end ();
}
private Variant parse_array () throws Error {
eat (LPAREN);
var b = new VariantBuilder (new VariantType ("av"));
if (look.type != RPAREN) {
b.add_value (parse_value ());
while (look.type == COMMA) {
eat (COMMA);
b.add_value (new Variant.variant (parse_value ()));
}
}
eat (RPAREN);
return b.end ();
}
private Variant parse_number () throws Error {
string raw = look.text;
eat (NUMBER);
if (raw.index_of_char ('.') != -1) {
double d;
if (!double.try_parse (raw, out d))
throw new Error.PROTOCOL (@"Bad double: %s".printf (raw));
return d;
} else {
int64 x;
if (!int64.try_parse (raw, out x))
throw new Error.PROTOCOL (@"Bad int: %s".printf (raw));
return x;
}
}
private Variant parse_string () throws Error {
string s = look.text;
eat (STRING);
return s;
}
private void eat (TokenType t) throws Error {
if (look.type != t)
throw new Error.PROTOCOL (@"Expected %s but found %s".printf (t.to_string (), look.type.to_string ()));
look = lex.next_token ();
}
}
private class Lexer {
private string s;
private int i = 0;
private int n;
public Lexer (string input) {
s = input;
n = s.length;
}
public Token next_token () throws Error {
skip_whitespace_and_comments ();
if (at_end ())
return new Token (EOF);
switch (peek ()) {
case '{': next (); return new Token (LBRACE);
case '}': next (); return new Token (RBRACE);
case '(': next (); return new Token (LPAREN);
case ')': next (); return new Token (RPAREN);
case '=': next (); return new Token (EQUAL);
case ';': next (); return new Token (SEMI);
case ',': next (); return new Token (COMMA);
case '"': return new Token (STRING, read_quoted ());
default:
var word = read_bare ();
if (word.length == 0)
throw new Error.PROTOCOL (@"Unexpected character '%c'".printf (peek ()));
return read_number_or_bareword (word);
}
}
private bool at_end () {
return i >= n;
}
private char peek () {
return s[i];
}
private char next () {
return s[i++];
}
private void skip_whitespace_and_comments () {
while (!at_end ()) {
if (is_whitespace (peek ())) {
next ();
continue;
}
if (peek () == '/' && i + 1 < n) {
if (s[i + 1] == '/') {
i += 2;
while (!at_end () && peek () != '\n')
next ();
continue;
}
if (s[i + 1] == '*') {
i += 2;
while (!at_end ()) {
if (peek () == '*' && i + 1 < n && s[i + 1] == '/') {
i += 2;
break;
}
next ();
}
continue;
}
}
break;
}
}
private static bool is_whitespace (char c) {
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
}
private string read_quoted () throws Error {
next ();
var sb = new StringBuilder ();
while (!at_end ()) {
char c = next ();
if (c == '\\') {
if (at_end ())
break;
char e = next ();
switch (e) {
case 'n':
sb.append_c ('\n');
break;
case 'r':
sb.append_c ('\r');
break;
case 't':
sb.append_c ('\t');
break;
case '"':
sb.append_c ('"');
break;
case '\\':
sb.append_c ('\\');
break;
default:
sb.append_c (e);
break;
}
} else if (c == '"') {
return sb.str;
} else {
sb.append_c (c);
}
}
throw new Error.PROTOCOL ("Unterminated quoted string");
}
private string read_bare () {
int start = i;
while (!at_end () && is_bare_char (peek ()))
next ();
return s.substring (start, i - start);
}
private static bool is_bare_char (char c) {
if (c.isalnum ())
return true;
const string extra = "._-/:$+@";
return extra.index_of_char (c) != -1;
}
private Token read_number_or_bareword (string word) {
bool numeric = true;
bool has_dot = false;
int len = word.length;
for (int k = 0; k != len; k++) {
char c = word[k];
if (k == 0 && (c == '+' || c == '-'))
continue;
if (c == '.') {
if (has_dot) {
numeric = false;
break;
}
has_dot = true;
continue;
}
if (!c.isdigit ()) {
numeric = false;
break;
}
}
if (numeric && word.length > 0 && !(word == "+" || word == "-"))
return new Token (NUMBER, word);
return new Token (STRING, word);
}
}
private class Token {
public TokenType type;
public string text;
public Token (TokenType t, string s = "") {
type = t;
text = s;
}
}
private enum TokenType {
LBRACE,
RBRACE,
LPAREN,
RPAREN,
EQUAL,
SEMI,
COMMA,
STRING,
NUMBER,
EOF,
}
}
+2 -1
View File
@@ -61,7 +61,8 @@ namespace Frida {
io_cancellable.cancel ();
}
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
throws Error, IOError {
string? raw_address = null;
TlsCertificate? certificate = null;
string? origin = null;
-21
View File
@@ -298,25 +298,4 @@ namespace Frida {
public extern void _do_resume ();
}
public sealed class StdioPipes : Object {
public OutputStream input {
get;
construct;
}
public InputStream output {
get;
construct;
}
public InputStream error {
get;
construct;
}
public StdioPipes (OutputStream input, InputStream output, InputStream error) {
Object (input: input, output: output, error: error);
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/frida-gum.git
revision = 17.2.16
revision = 17.4.2
depth = 1
[provide]
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/lwip.git
revision = c5e2835a213436eaf0c40d0a95a35e7cf0dad2d3
revision = 4fe7223c2e80dc328266fb2483e789ee5fad7c79
depth = 1
[provide]
+8
View File
@@ -0,0 +1,8 @@
[wrap-git]
url = https://github.com/frida/lzfse.git
revision = 5cfb7c86919d3c2c636d0d7552b51855a611ba1c
depth = 1
[provide]
dependency_names = lzfse
+1 -1
View File
@@ -1,6 +1,6 @@
[wrap-git]
url = https://github.com/frida/openssl.git
revision = 7b86cb6a0c5cb9d79dca012c98a0a30a58eef5b5
revision = 5029c4562d23547d8e29768e1b0de573c52bf3fc
depth = 1
[provide]
+91 -14
View File
@@ -67,6 +67,11 @@ namespace Frida.HostSessionTest {
h.run ();
});
GLib.Test.add_func ("/HostSession/Fruity/Manual/speedtest", () => {
var h = new Harness ((h) => Fruity.Manual.speedtest.begin (h as Harness));
h.run ();
});
GLib.Test.add_func ("/HostSession/Fruity/Manual/Xpc/list", () => {
var h = new Harness ((h) => Fruity.Manual.Xpc.list.begin (h as Harness));
h.run ();
@@ -465,7 +470,8 @@ namespace Frida.HostSessionTest {
get { return HostSessionProviderKind.LOCAL; }
}
public async HostSession create (HostSessionOptions? options, Cancellable? cancellable) throws Error, IOError {
public async HostSession create (HostSessionHub hub, HostSessionOptions? options, Cancellable? cancellable)
throws Error, IOError {
throw new Error.NOT_SUPPORTED ("Not implemented");
}
@@ -1531,7 +1537,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var session = yield prov.create (null, cancellable);
var session = yield prov.create (new NullHostSessionHub (), null, cancellable);
var applications = yield session.enumerate_applications (make_parameters_dict (), cancellable);
var processes = yield session.enumerate_processes (make_parameters_dict (), cancellable);
@@ -1572,7 +1578,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var host_session = yield prov.create (null, cancellable);
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
uint pid = 0;
bool waiting = false;
@@ -1771,7 +1777,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var session = yield prov.create (null, cancellable);
var session = yield prov.create (new NullHostSessionHub (), null, cancellable);
var applications = yield session.enumerate_applications (make_parameters_dict (), cancellable);
var processes = yield session.enumerate_processes (make_parameters_dict (), cancellable);
@@ -1809,7 +1815,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var host_session = yield prov.create (null, cancellable);
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
uint pid = 0;
bool waiting = false;
@@ -1914,7 +1920,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var host_session = yield prov.create (null, cancellable);
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
uint pid = 0;
bool waiting = false;
@@ -2393,7 +2399,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var host_session = yield prov.create (null, cancellable);
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
var id = yield host_session.attach (pid, make_parameters_dict (), cancellable);
var session = yield prov.link_agent_session (host_session, id, h, cancellable);
@@ -2544,7 +2550,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var session = yield prov.create (null, cancellable);
var session = yield prov.create (new NullHostSessionHub (), null, cancellable);
var processes = yield session.enumerate_processes (make_parameters_dict (), cancellable);
assert_true (processes.length > 0);
@@ -2571,7 +2577,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var host_session = yield prov.create (null, cancellable);
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
uint pid = 0;
bool waiting = false;
@@ -3302,7 +3308,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var session = yield prov.create (null, cancellable);
var session = yield prov.create (new NullHostSessionHub (), null, cancellable);
var processes = yield session.enumerate_processes (make_parameters_dict (), cancellable);
assert_true (processes.length > 0);
@@ -3328,7 +3334,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var host_session = yield prov.create (null, cancellable);
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
uint pid = 0;
bool waiting = false;
@@ -3579,7 +3585,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var session = yield prov.create (null, cancellable);
var session = yield prov.create (new NullHostSessionHub (), null, cancellable);
var processes = yield session.enumerate_processes (make_parameters_dict (), cancellable);
assert_true (processes.length > 0);
@@ -3612,7 +3618,7 @@ namespace Frida.HostSessionTest {
Cancellable? cancellable = null;
stdout.printf ("connecting to frida-server\n");
var host_session = yield prov.create (null, cancellable);
var host_session = yield prov.create (new NullHostSessionHub (), null, cancellable);
stdout.printf ("enumerating processes\n");
var processes = yield host_session.enumerate_processes (make_parameters_dict (), cancellable);
assert_true (processes.length > 0);
@@ -3813,6 +3819,77 @@ namespace Frida.HostSessionTest {
h.done ();
}
private static async void speedtest (Harness h) {
/*
* Configured like this:
*
* ./configure \
* --disable-local-backend \
* --disable-droidy-backend \
* --disable-socket-backend \
* --disable-barebone-backend \
* --disable-compiler-backend \
* --with-compat=disabled \
* --disable-gadget \
* --disable-server \
* --disable-portal \
* --disable-inject \
* -- \
* --force-fallback-for=lwip
*/
if (!GLib.Test.slow ()) {
stdout.printf ("<skipping, run in slow mode with iOS device connected> ");
h.done ();
return;
}
h.disable_timeout ();
var device_manager = new DeviceManager ();
try {
var device = yield device_manager.get_device_by_id (DEVICE_ID);
{
var stream = yield device.open_channel ("tcp:5050");
var jumbo_message = new uint8[64 * 1024 * 1024];
yield stream.get_output_stream ().write_all_async (jumbo_message, Priority.DEFAULT, null,
null);
yield stream.close_async ();
}
{
var stream = yield device.open_channel ("tcp:5051");
var buf = new uint8[4 * 1024 * 1024];
while (true) {
var n = yield stream.get_input_stream ().read_async (buf);
if (n == 0)
break;
}
yield stream.close_async ();
}
var source = new TimeoutSource (500);
source.set_callback (speedtest.callback);
source.attach (MainContext.get_thread_default ());
yield;
} catch (GLib.Error e) {
printerr ("\nFAIL: %s\n\n", e.message);
}
try {
yield device_manager.close ();
} catch (IOError e) {
assert_not_reached ();
}
h.done ();
}
namespace Xpc {
private static async void list (Harness h) {
@@ -4166,7 +4243,7 @@ namespace Frida.HostSessionTest {
try {
Cancellable? cancellable = null;
var session = yield prov.create (null, cancellable);
var session = yield prov.create (new NullHostSessionHub (), null, cancellable);
var processes = yield session.enumerate_processes (make_parameters_dict (), cancellable);
assert_true (processes.length > 0);
+17
View File
@@ -77,6 +77,18 @@ namespace Darwin.XNU {
[CCode (cheader_filename = "sys/sysctl.h")]
public int sysctlbyname (string name, void * oldp, size_t * oldlenp, void * newp = null, size_t newlen = 0);
[CCode (cheader_filename = "util.h")]
public int openpty (out int amaster, out int aslave, [CCode (array_length=false, array_null_terminated=true)] char[] name,
Posix.termios? termp, winsize? winp);
[CCode (cname = "struct winsize", has_type_id = false, cheader_filename = "sys/ttycom.h", destroy_function = "")]
public struct winsize {
public ushort ws_row;
public ushort ws_col;
public ushort ws_xpixel;
public ushort ws_ypixel;
}
[CCode (cname = "struct xinpgen", cheader_filename = "netinet/in_pcb.h")]
public struct InetPcbGeneration {
[CCode (cname = "xig_len")]
@@ -101,4 +113,9 @@ namespace Darwin.XNU {
IPV6,
V4MAPPEDV6,
}
[CCode (cheader_filename = "fcntl.h")]
public const int F_FULLFSYNC;
[CCode (cheader_filename = "fcntl.h")]
public const int F_SETNOSIGPIPE;
}
+14
View File
@@ -0,0 +1,14 @@
[CCode (lower_case_cprefix = "", gir_namespace = "FreeBSD", gir_version = "1.0")]
namespace FreeBSD {
[CCode (cheader_filename = "libutil.h")]
public int openpty (out int amaster, out int aslave, [CCode (array_length=false, array_null_terminated=true)] char[] name,
Posix.termios? termp, winsize? winp);
[CCode (cname = "struct winsize", has_type_id = false, cheader_filename = "sys/ttycom.h", destroy_function = "")]
public struct winsize {
public ushort ws_row;
public ushort ws_col;
public ushort ws_xpixel;
public ushort ws_ypixel;
}
}
+1
View File
@@ -0,0 +1 @@
// Placeholder to satisfy Meson assumptions.