Compare commits

..
4 Commits
Author SHA1 Message Date
Ole André Vadla Ravnås f67d1e81df Dump alive tasks every 30s in server and agent
Route the dump through GLib.info() using the new
g_task_describe_alive_tasks(), so the agent's dump reaches
logcat where its stdio is not visible. Bump the GLib wrap to
the branch holding that function.
2026-07-29 18:12:10 +02:00
Ole André Vadla Ravnås a068c704d1 Trace agent session detach notification path
Log every hop that agent_session_detached has to survive, from
the agent emitting closed, through frida-server's provider and
control channel, the droidy relay, and finally Device's pending
detach request. Each silent id-keyed drop now reports whether
the lookup succeeded, so a hanging detach() points straight at
the hop that swallowed the notification.
2026-07-29 17:47:25 +02:00
Ole André Vadla Ravnås 34ac3a0d50 server: Add output redirection option
Add -o/--output for redirecting stdout and stderr to a file,
so output survives daemonizing. Skip the stdin debug console
when daemonized, as stdin is then /dev/null.
2026-07-29 15:10:37 +02:00
Ole André Vadla Ravnås 1ea81bcd59 server: Add stdin-based task debug console 2026-07-28 14:47:05 +02:00
163 changed files with 2907 additions and 53256 deletions

No files matched your search

+4 -4
View File
@@ -20,7 +20,7 @@ jobs:
strategy:
matrix:
include:
- { id: windows-arm64, runner: '"windows-11-arm"', sys: CLANGARM64, pkg: 'mingw-w64-clang-aarch64-clang', opts: "'-Dfrida-gum:v8=disabled'" }
- { id: windows-arm64, runner: '"windows-11-arm"', sys: CLANGARM64, pkg: 'mingw-w64-clang-aarch64-clang' }
- { id: windows-x86_64, runner: '"windows-latest"', sys: MINGW64, pkg: 'mingw-w64-x86_64-gcc' }
- { id: windows-x86, runner: '"windows-latest"', sys: MINGW32, pkg: 'mingw-w64-i686-gcc' }
- { id: macos-x86_64, runner: '"macos-latest"' }
@@ -71,7 +71,7 @@ jobs:
--host=${{ matrix.id }} `
--enable-compiler-backend `
${{ env.FRIDA_CORE_OPTIONS }} `
-- --force-fallback-for=openssl ${{ matrix.opts }}
-- --force-fallback-for=openssl
.\make
env:
MSYS2_LOCATION: ${{ steps.msys2.outputs.msys2-location }}
@@ -658,7 +658,7 @@ jobs:
matrix:
include:
- { arch: x86_64, runner: windows-latest, msvc-arch: amd64, vcpkg-triplet: x64-windows-release }
- { arch: arm64, runner: windows-11-arm, msvc-arch: arm64, vcpkg-triplet: arm64-windows-release, opts: "'-Dfrida-gum:v8=disabled'" }
- { arch: arm64, runner: windows-11-arm, msvc-arch: arm64, vcpkg-triplet: arm64-windows-release }
fail-fast: false
runs-on: ${{ matrix.runner }}
timeout-minutes: 120
@@ -721,7 +721,7 @@ jobs:
--enable-shared `
--without-prebuilds=sdk `
"--prefix=$env:RUNNER_TEMP\frida-prefix" `
-- --force-fallback-for=openssl,libnghttp2 -Dcompiler_backend=disabled -Dtests=disabled ${{ matrix.opts }}
-- --force-fallback-for=openssl,libnghttp2 -Dcompiler_backend=disabled -Dtests=disabled
- name: Compile
run: python releng/meson/meson.py compile -C build
+1 -9
View File
@@ -2,15 +2,7 @@
/build/
/deps/
/src/android-helper/build/
/src/barebone/agent/linux/*.ko
/src/barebone/agent/linux/*.mod
/src/barebone/agent/linux/*.mod.c
/src/barebone/agent/linux/*.o
/src/barebone/agent/linux/*.o.bin
/src/barebone/agent/linux/.*.cmd
/src/barebone/agent/linux/Module.symvers
/src/barebone/agent/linux/modules.order
/src/barebone/agent/target*/
/src/barebone/agent/target/
/src/barebone/agent/xpacks/
/src/barebone/helpers/memory-scanner/target/
/src/barebone/helpers/target/
+18
View File
@@ -292,6 +292,8 @@ namespace Frida.Agent {
start.begin ((owned) padder);
start_alive_task_dumper ();
main_loop.run ();
main_context.pop_thread_default ();
@@ -362,6 +364,16 @@ namespace Frida.Agent {
padder = null;
}
// TEMPORARY DEBUG HACK: dump alive GTasks every 30 seconds.
private void start_alive_task_dumper () {
var source = new TimeoutSource.seconds (30);
source.set_callback (() => {
GLib.info ("%s", g_task_describe_alive_tasks ());
return Source.CONTINUE;
});
source.attach (main_context);
}
private void keep_running_eternalized () {
agent_gthread = new Thread<bool> ("frida-eternal-agent", () => {
var ignore_scope = new ThreadIgnoreScope (FRIDA_THREAD);
@@ -924,6 +936,8 @@ namespace Frida.Agent {
private void on_session_closed (BaseAgentSession base_session) {
LiveAgentSession session = (LiveAgentSession) base_session;
GLib.info ("detach-trace: Agent emitting provider closed id=%s", session.id.handle);
closed (session.id);
unregister_session (session);
@@ -1724,4 +1738,8 @@ namespace Frida.Agent {
return repeat;
});
}
// TEMPORARY DEBUG HACK: only present when GLib is built with G_ENABLE_DEBUG.
[CCode (cname = "g_task_describe_alive_tasks")]
private extern string g_task_describe_alive_tasks ();
}
+3
View File
@@ -361,6 +361,8 @@ namespace Frida {
private async void teardown_peer_connection_and_emit_closed () {
schedule_on_frida_thread (() => {
GLib.info ("detach-trace: AgentMessageTransmitter %p emitting closed, nice_agent=%p",
this, nice_agent);
if (nice_agent != null)
close_nice_resources_and_emit_closed.begin ();
else
@@ -598,6 +600,7 @@ namespace Frida {
private async void teardown_peer_connection_and_emit_closed () {
schedule_on_frida_thread (() => {
GLib.info ("detach-trace: AgentMessageTransmitter %p emitting closed", this);
closed ();
return Source.REMOVE;
});
+6
View File
@@ -57,6 +57,8 @@ namespace Frida {
}
public async void close (Cancellable? cancellable) throws IOError {
GLib.info ("detach-trace: BaseAgentSession.close() id=%s close_request=%p", id.handle, close_request);
while (close_request != null) {
try {
yield close_request.future.wait_async (cancellable);
@@ -85,6 +87,8 @@ namespace Frida {
yield transmitter.close (cancellable);
GLib.info ("detach-trace: BaseAgentSession.close() done id=%s", id.handle);
close_request.resolve (true);
}
@@ -276,6 +280,8 @@ namespace Frida {
transmitter.new_candidates.disconnect (on_transmitter_new_candidates);
transmitter.candidate_gathering_done.disconnect (on_transmitter_candidate_gathering_done);
GLib.info ("detach-trace: BaseAgentSession emitting closed id=%s", id.handle);
closed ();
}
+2 -15
View File
@@ -12,27 +12,14 @@ frida_pipe_get_sddl_string_for_pipe (void)
#define DACL_ACE_EVERYONE_RW L"(A;;GRGW;;;WD)"
#define SACL_START L"S:"
#define SACL_ACE_LOWINTEGRITY_NORW L"(ML;;NWNR;;;LW)"
#define DACL_ACE_SYSTEM_RW L"(A;;GRGW;;;SY)"
#define DACL_ACE_ADMINS_RW L"(A;;GRGW;;;BA)"
if (frida_pipe_is_windows_8_or_greater ())
{
return DACL_START_NOINHERIT
DACL_ACE_APPCONTAINER_RW
DACL_ACE_EVERYONE_RW
DACL_ACE_SYSTEM_RW
DACL_ACE_ADMINS_RW
SACL_START
SACL_ACE_LOWINTEGRITY_NORW;
return DACL_START_NOINHERIT DACL_ACE_APPCONTAINER_RW DACL_ACE_EVERYONE_RW SACL_START SACL_ACE_LOWINTEGRITY_NORW;
}
else if (frida_pipe_is_windows_vista_or_greater ())
{
return DACL_START_NOINHERIT
DACL_ACE_EVERYONE_RW
DACL_ACE_SYSTEM_RW
DACL_ACE_ADMINS_RW
SACL_START
SACL_ACE_LOWINTEGRITY_NORW;
return DACL_START_NOINHERIT DACL_ACE_EVERYONE_RW SACL_START SACL_ACE_LOWINTEGRITY_NORW;
}
else
{
+3 -6
View File
@@ -527,7 +527,7 @@ have_droidy_backend = get_option('droidy_backend') \
have_socket_backend = get_option('socket_backend').allowed()
have_barebone_backend = get_option('barebone_backend') \
.disable_if(not (quickjs_dep.found() and lzfse_dep.found())) \
.disable_auto_if(not host_docks_mobile_devices and host_os != 'android') \
.disable_auto_if(not host_docks_mobile_devices) \
.allowed()
compiler_backend_opt = get_option('compiler_backend') \
.disable_auto_if(host_os == 'watchos')
@@ -595,9 +595,9 @@ if compiler_backend_opt.allowed()
endif
endif
elif compiler_backend_opt.enabled()
error('Need Go >= 1.26 to build the Compiler backend powered by ESBuild and typescript-go')
error('Need Go >= 1.24 to build the Compiler backend powered by ESBuild and typescript-go')
else
warning('Building without Compiler backend: Go >= 1.26 not found')
warning('Building without Compiler backend: Go >= 1.24 not found')
endif
endif
@@ -879,9 +879,6 @@ if host_os_family == 'darwin'
endif
if host_os == 'macos'
backend_libs_private += ['-Wl,-framework,AppKit']
if have_compiler_backend
backend_libs_private += ['-Wl,-framework,CoreServices']
endif
endif
if host_os in ['ios', 'tvos']
backend_libs_private += ['-Wl,-framework,CoreGraphics', '-Wl,-framework,UIKit']
+1 -1
Submodule releng updated: 963fe3826f...abb8238970.
+87 -4
View File
@@ -12,6 +12,7 @@ namespace Frida.Server {
private static string? directory = null;
#if !WINDOWS && !TVOS
private static bool daemonize = false;
private static string? output_path = null;
#endif
private static string? softener_flavor_str = null;
private static bool enable_preload = true;
@@ -42,6 +43,7 @@ namespace Frida.Server {
{ "directory", 'd', 0, OptionArg.STRING, ref directory, "Store binaries in DIRECTORY", "DIRECTORY" },
#if !WINDOWS && !TVOS
{ "daemonize", 'D', 0, OptionArg.NONE, ref daemonize, "Detach and become a daemon", null },
{ "output", 'o', 0, OptionArg.FILENAME, ref output_path, "Redirect output to FILE", "FILE" },
#endif
{ "policy-softener", 0, 0, OptionArg.STRING, ref softener_flavor_str, "Select policy softener", "system|internal" },
{ "disable-preload", 'P', OptionFlags.REVERSE, OptionArg.NONE, ref enable_preload, "Disable preload optimization", null },
@@ -78,6 +80,13 @@ namespace Frida.Server {
Environment.set_verbose_logging_enabled (verbose);
#if !WINDOWS && !TVOS
if (output_path != null && !daemonize) {
if (!redirect_output_to (output_path))
return 6;
}
#endif
EndpointParameters endpoint_params;
try {
endpoint_params = new EndpointParameters (listen_address, 0, parse_certificate (certpath), origin,
@@ -156,12 +165,15 @@ namespace Frida.Server {
Posix.setsid ();
var null_in = Posix.open ("/dev/null", Posix.O_RDONLY);
var null_out = Posix.open ("/dev/null", Posix.O_WRONLY);
Posix.dup2 (null_in, Posix.STDIN_FILENO);
Posix.dup2 (null_out, Posix.STDOUT_FILENO);
Posix.dup2 (null_out, Posix.STDERR_FILENO);
Posix.close (null_in);
Posix.close (null_out);
if (output_path == null || !redirect_output_to (output_path)) {
var null_out = Posix.open ("/dev/null", Posix.O_WRONLY);
Posix.dup2 (null_out, Posix.STDOUT_FILENO);
Posix.dup2 (null_out, Posix.STDERR_FILENO);
Posix.close (null_out);
}
}
var status = new uint8[1];
@@ -218,6 +230,22 @@ namespace Frida.Server {
return application.run ();
}
#if !WINDOWS && !TVOS
private static bool redirect_output_to (string path) {
int fd = Posix.open (path, Posix.O_WRONLY | Posix.O_CREAT | Posix.O_APPEND, 0644);
if (fd == -1) {
printerr ("Unable to open “%s”: %s\n", path, Posix.strerror (Posix.errno));
return false;
}
Posix.dup2 (fd, Posix.STDOUT_FILENO);
Posix.dup2 (fd, Posix.STDERR_FILENO);
Posix.close (fd);
return true;
}
#endif
namespace Environment {
public extern void init ();
public extern void set_verbose_logging_enabled (bool enabled);
@@ -274,6 +302,15 @@ namespace Frida.Server {
return false;
});
#if !WINDOWS && !TVOS
if (!daemonize)
process_commands.begin ();
#elif !WINDOWS
process_commands.begin ();
#endif
start_alive_task_dumper ();
exit_code = 0;
loop.run ();
@@ -281,6 +318,45 @@ namespace Frida.Server {
return exit_code;
}
#if !WINDOWS
// TEMPORARY DEBUG HACK: type “t” + Enter to dump alive GTasks.
private async void process_commands () {
var input = new DataInputStream (new UnixInputStream (Posix.STDIN_FILENO, false));
while (true) {
string? line;
try {
line = yield input.read_line_async (Priority.DEFAULT, io_cancellable);
} catch (GLib.Error e) {
return;
}
if (line == null)
return;
switch (line.strip ()) {
case "t":
g_task_print_alive_tasks ();
break;
case "":
break;
default:
printerr ("Unknown command; try “t”\n");
break;
}
}
}
#endif
// TEMPORARY DEBUG HACK: dump alive GTasks every 30 seconds.
private void start_alive_task_dumper () {
var source = new TimeoutSource.seconds (30);
source.set_callback (() => {
GLib.info ("%s", g_task_describe_alive_tasks ());
return Source.CONTINUE;
});
source.attach (MainContext.default ());
}
private async void start () {
try {
if (device_id != null && device_id != "local") {
@@ -355,4 +431,11 @@ namespace Frida.Server {
return new TlsCertificate.from_file (path);
}
// TEMPORARY DEBUG HACK: only present when GLib is built with G_ENABLE_DEBUG.
[CCode (cname = "g_task_print_alive_tasks")]
private extern void g_task_print_alive_tasks ();
[CCode (cname = "g_task_describe_alive_tasks")]
private extern string g_task_describe_alive_tasks ();
}
+11 -85
View File
@@ -21,14 +21,7 @@ CORE_TAG_FIELD = f"{{{CORE_NAMESPACE}}}field"
CORE_TAG_CONSTRUCTOR = f"{{{CORE_NAMESPACE}}}constructor"
CORE_TAG_METHOD = f"{{{CORE_NAMESPACE}}}method"
INTERNAL_INTERFACES = {
"Frida.HostSessionHub",
"Frida.AgentMessageSink",
"FridaBase.AgentMessageSink",
"Json.Serializable",
}
OBJECT_TYPE_PATTERN = re.compile(r"\bpublic\s+(sealed |abstract )?(class|interface)\s+(\w+)\b")
OBJECT_TYPE_PATTERN = re.compile(r"\bpublic\s+(sealed )?(class|interface)\s+(\w+)\b")
TOPLEVEL_NAMES = [
"frida.vala",
@@ -138,7 +131,6 @@ def emit_header(api, output_dir):
output_header_file.write("\n\n/* Library lifetime */")
output_header_file.write("\nvoid frida_init (void);")
output_header_file.write("\nvoid frida_init_with_runtime (FridaRuntime runtime);")
output_header_file.write("\nvoid frida_shutdown (void);")
output_header_file.write("\nvoid frida_deinit (void);")
output_header_file.write("\nGMainContext * frida_get_main_context (void);")
@@ -238,10 +230,10 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path, docs:
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") in INTERNAL_INTERFACES) \
if (child.tag == CORE_TAG_IMPLEMENTS and child.get("name") in {"Frida.HostSessionHub", "Frida.AgentMessageSink",
"FridaBase.AgentMessageSink"}) \
or child.tag == CORE_TAG_FIELD \
or child.get("name").startswith("_") \
or (child.tag == CORE_TAG_CONSTRUCTOR and is_async_constructor(child)):
or child.get("name").startswith("_"):
elem.remove(child)
merged_namespace.append(elem)
@@ -249,13 +241,6 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path, docs:
merge_and_transform_elements("interface", object_type_names)
merge_and_transform_elements("enumeration", enum_type_names | error_type_names)
function_names = {func.name for func in api.functions}
for source_root in [core_root, base_root]:
source_namespace = source_root.find("namespace", GIR_NAMESPACES)
for function in source_namespace.findall("function", GIR_NAMESPACES):
if function.get("name") in function_names:
merged_namespace.append(function)
for source_root in [core_root, base_root]:
for record in source_root.findall(".//record", GIR_NAMESPACES):
owner = record.get(f"{{{GLIB_NAMESPACE}}}is-gtype-struct-for")
@@ -292,12 +277,6 @@ def emit_gir(api: ApiSpec, core_gir: str, base_gir: str, output_dir: Path, docs:
def filter_elements(elements: List[ET.Element], spec_set: Set[str]):
return [elem for elem in elements if elem.get("name") in spec_set]
def is_async_constructor(constructor: ET.Element) -> bool:
for type_elem in constructor.iter(f"{{{CORE_NAMESPACE}}}type"):
if type_elem.get("name") == "Gio.Cancellable":
return True
return False
@dataclass
class DocComment:
body: str
@@ -625,14 +604,10 @@ def emit_vapi(api, output_dir):
output_vapi_file.write("[CCode (cheader_filename = \"frida-core.h\", cprefix = \"Frida\", lower_case_cprefix = \"frida_\")]")
output_vapi_file.write("\nnamespace Frida {")
output_vapi_file.write("\n\tpublic static void init ();")
output_vapi_file.write("\n\tpublic static void init_with_runtime (Frida.Runtime runtime);")
output_vapi_file.write("\n\tpublic static void shutdown ();")
output_vapi_file.write("\n\tpublic static void deinit ();")
output_vapi_file.write("\n\tpublic static unowned GLib.MainContext get_main_context ();")
for delegate in referenced_delegates(api):
output_vapi_file.write("\n\t" + delegate)
for object_type in api.object_types:
output_vapi_file.write("\n\n\t%s" % object_type.vapi_declaration)
sections = []
@@ -667,35 +642,6 @@ def emit_vapi(api, output_dir):
output_deps_file.write("glib-2.0\n")
output_deps_file.write("gobject-2.0\n")
output_deps_file.write("gio-2.0\n")
output_deps_file.write("json-glib-1.0\n")
def referenced_delegates(api) -> List[str]:
members = []
for object_type in api.object_types:
members.extend(object_type.vapi_properties)
members.extend(object_type.vapi_methods)
members.extend(object_type.vapi_signals)
if object_type.vapi_constructor is not None:
members.append(object_type.vapi_constructor)
for func in api.functions:
members.append(func.vapi_declaration)
text = "\n".join(members)
return [declaration for name, declaration in api.delegates
if re.search(r"\bFrida\." + name + r"\b", text) is not None]
def strip_internal_interfaces(declaration: str) -> str:
head, sep, rest = declaration.partition(" : ")
if not sep:
return declaration
bases_part, brace_sep, tail = rest.partition("{")
kept = [b.strip() for b in bases_part.split(",") if b.strip() not in INTERNAL_INTERFACES]
if not kept:
return f"{head} {brace_sep}{tail}"
return f"{head} : {', '.join(kept)} {brace_sep}{tail}"
def emit_symbol_maps_from_source(output_dir: Path):
with OutputFile(output_dir / 'frida-core.version') as f:
@@ -854,7 +800,8 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
if stripped_line == "}":
ignoring = False
else:
if stripped_line.startswith("public class Promise") \
if stripped_line.startswith("public abstract") \
or stripped_line.startswith("public class Promise") \
or stripped_line.startswith("public interface Future") \
or stripped_line.startswith("public class CF"):
ignoring = True
@@ -874,7 +821,7 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
ignoring = True
else:
current_object_type = object_type_by_name[name]
current_object_type.vapi_declaration = strip_internal_interfaces(stripped_line)
current_object_type.vapi_declaration = stripped_line
elif stripped_line == "}":
current_enum = None
current_object_type = None
@@ -903,26 +850,17 @@ def parse_api(frida_version, frida_version_components, api_version, toplevel_cod
enum.vapi_declaration = m.group(1)
enum.vapi_members.extend([line.lstrip() for line in m.group(2).strip().split("\n")])
functions = [f for f in parse_vapi_functions(core_vapi + base_vapi) if function_is_public(f.name)]
functions = [f for f in parse_vapi_functions(base_vapi) if function_is_public(f.name)]
for f in functions:
m = re.search(r"^(?:VALA_EXTERN )?[\w\*]+ frida_{}.+?;".format(f.name), all_headers, re.MULTILINE | re.DOTALL)
prototype = beautify_cprototype(m.group(0))
if prototype.startswith("VALA_EXTERN "):
prototype = prototype[12:]
f.c_prototype = prototype
m = re.search(r"^[\w\*]+ frida_{}.+?;".format(f.name), all_headers, re.MULTILINE | re.DOTALL)
f.c_prototype = beautify_cprototype(m.group(0))
delegates = [(m.group(1), m.group(0).strip())
for m in re.finditer(r"^\tpublic delegate .+?\b(\w+) \(.*\);$", core_vapi + base_vapi, re.MULTILINE)]
return ApiSpec(frida_version, frida_version_components, api_version, object_types, functions, enum_types, error_types,
delegates)
return ApiSpec(frida_version, frida_version_components, api_version, object_types, functions, enum_types, error_types)
def function_is_public(name):
return not name.startswith("_") and \
not name.startswith("throw_") and \
name not in MANUALLY_DECLARED_FUNCTIONS and \
name not in {
"on_pending_garbage",
"generate_certificate",
"get_dbus_context",
"invalidate_dbus_context",
@@ -943,17 +881,6 @@ def function_is_public(name):
"make_stdio_pipe",
}
MANUALLY_DECLARED_FUNCTIONS = {
"init",
"init_with_runtime",
"shutdown",
"deinit",
"get_main_context",
"unref",
"version",
"version_string",
}
def parse_vala_object_types(source) -> List[ApiObjectType]:
return [ApiObjectType(m.group(3), m.group(2)) for m in OBJECT_TYPE_PATTERN.finditer(source, re.MULTILINE)]
@@ -969,7 +896,6 @@ class ApiSpec:
functions: List[ApiFunction]
enum_types: List[ApiEnum]
error_types: List[ApiEnum]
delegates: List[tuple]
class ApiEnum:
def __init__(self, name):
+1 -8
View File
@@ -132,20 +132,13 @@ if not is_shared
endif
core_public_dep = declare_dependency(kwargs: core_public_dep_kwargs)
core_public_reqs = ['glib-2.0', 'gobject-2.0', 'gio-2.0', 'json-glib-1.0']
if host_os_family == 'windows'
core_public_reqs += 'gio-windows-2.0'
else
core_public_reqs += 'gio-unix-2.0'
endif
pkg = import('pkgconfig')
pkg_kwargs = {
'filebase': f'frida-core-@api_version@',
'name': 'Frida Core Library',
'version': frida_version,
'description': 'Instrumentation toolkit for developers, reverse-engineers, and security researchers',
'requires': core_public_reqs,
'requires': ['glib-2.0', 'gobject-2.0', 'gio-2.0', 'json-glib-1.0'],
'subdirs': f'frida-@api_version@',
'extra_cflags': ['-I${frida_includedir}'],
'variables': installed_variables,
File diff suppressed because it is too large. Load diff
+4 -4
View File
@@ -13,9 +13,9 @@ dependencies = [
[[package]]
name = "bindgen"
version = "0.72.1"
version = "0.72.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
checksum = "4f72209734318d0b619a5e0f5129918b848c416e122a3c4ce054e03cb87b726f"
dependencies = [
"bitflags",
"cexpr",
@@ -63,9 +63,9 @@ checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "clang-sys"
version = "1.9.1"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "157a8ba7b480713b56f4c09fd13fc3e0a22a5dfab8097ba61cbc5feef950788a"
checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
dependencies = [
"glob",
"libc",
-25
View File
@@ -4,31 +4,6 @@ version = "1.0.0"
edition = "2024"
build = "build.rs"
[lib]
name = "frida_barebone_agent"
path = "src/lib.rs"
crate-type = ["rlib", "staticlib"]
# The XNU flavour is a freestanding executable the host injects and calls into.
# The Linux flavour is the staticlib above, linked into a .ko by linux/Kbuild.
[[bin]]
name = "frida-barebone-agent"
path = "src/main.rs"
required-features = ["blob"]
[features]
# Flavors the host injects and calls into, as opposed to the module Linux loads.
blob = []
# The XNU flavour itself, and the two ways it is packaged: a blob the host injects
# into a running kernel, and a kext that kernel loads.
xnu-core = []
xnu = ["xnu-core", "blob"]
xnu-kext = ["xnu-core"]
win9x = ["blob"]
winnt = ["blob"]
linux = []
linux-injected = ["blob"]
[profile.release]
panic = "abort"
opt-level = "z"
+17 -67
View File
@@ -1,80 +1,30 @@
## Getting the SDK and the devkit
## Getting the C toolchain
The agent is built against the bare soft-float SDK, which carries picolibc and the
compiler runtime, and against a GumJS devkit built for the same flavor. Which flavor
is the target's, listed under Targets below; this walks through the XNU one. No
prebuilt bundles are published for these, so build both:
npm install -g xpm
xpm install
export PATH=$PWD/xpacks/.bin:$PATH
machine=none-arm64-softfloat_nopic
## How to build Gum
releng/deps.py build --bundle=sdk --host=$machine
mkdir -p ~/sdk-$machine
tar -C ~/sdk-$machine -xf deps/sdk-$machine.tar.xz
gum=~/src/frida-gum
mkdir -p $gum/build/$machine
(cd $gum/build/$machine \
&& FRIDA_DEPS=$HOME/src/frida-core/deps CC=clang CXX=clang++ AR=llvm-ar \
RANLIB=llvm-ranlib NM=llvm-nm STRIP=llvm-strip \
../../configure --host=$machine --enable-gumjs \
--with-devkits=gumjs \
&& python3 ../../releng/meson/meson.py configure -Ddevkit_symbol_scope=original \
&& ninja)
./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
sdk=~/sdk-$machine
export GUMJS_DEVKIT_DIR=$gum/build/$machine/bindings/gumjs/devkit
export CC="clang -I$sdk/include -I$sdk/include/glib-2.0 -I$sdk/lib/glib-2.0/include \
-I$sdk/include/capstone -I$sdk/include/json-glib-1.0"
export RUSTFLAGS="-L native=$sdk/lib"
RUSTC_BOOTSTRAP=1 cargo build --release --features xnu \
--target aarch64-unknown-none-softfloat --target-dir target-xnu \
-Z build-std=core,alloc,compiler_builtins
Rust names no bare-metal target for the Windows guests, so the agent spells them
out and they are asked for by path:
RUSTC_BOOTSTRAP=1 cargo build --release --features win9x \
--target ./i686-unknown-none-pic.json -Z json-target-spec \
--target-dir target-win9x -Z build-std=core,alloc,compiler_builtins
## Targets
The agent supports four kernels. One of them has to be picked; none is a default,
since none follows from the host you build on:
- `xnu` — a freestanding ELF executable that the Barebone backend
injects into a running kernel from the outside, over a remote stub.
- `win9x` — the same, for a 32-bit Windows 9x guest.
- `winnt` — the same, for a Windows NT guest, either word size.
- `linux` — a static library that becomes a kernel module, loaded from the
inside. See `linux/README.md`.
Each flavor has an SDK and a devkit of its own, since the addressing differs: an
image the host places goes in position-independent, a Linux module is absolute,
and NT's 64-bit half is called in the Microsoft ABI.
| Flavor | Machine | Rust target |
|---------|-----------------------------------|----------------------------------|
| `xnu` | `none-arm64-softfloat_nopic` | `aarch64-unknown-none-softfloat` |
| `win9x` | `none-x86-softfloat_pic` | `i686-unknown-none-pic.json` |
| `winnt` | `none-x86-softfloat_pic` | `i686-unknown-none-pic.json` |
| `winnt` | `none-x86_64-softfloat_pic_msabi` | `x86_64-unknown-none-pic.json` |
| `linux` | `none-<arch>-softfloat_nopic` | see `linux/README.md` |
Everything above the `kernel` module is shared; each backend supplies the same
set of primitives (logging, allocation, threads, waiting, time, symbols) plus its
half of Gum's platform backend (`gum_xnu.rs` / `gum_windows.rs` / `gum_linux.rs`).
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 --features xnu --target aarch64-unknown-none-softfloat \
&& make -C ~/src/frida-python \
&& killall -9 qemu-system-aarch64 && sleep 2 \
&& frida -D barebone -p 0
cargo build --release && make -C ~/src/frida-python && killall -9 qemu-system-aarch64 && sleep 2 && frida -D barebone -p 0
## Speeding up loop
@@ -1,21 +0,0 @@
{
"abi": "softfloat",
"arch": "aarch64",
"crt-objects-fallback": "false",
"data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32",
"disable-redzone": true,
"features": "+v8a,+strict-align,-neon",
"linker": "rust-lld",
"linker-flavor": "gnu-lld",
"llvm-target": "aarch64-unknown-none",
"max-atomic-width": 128,
"panic-strategy": "abort",
"plt-by-default": false,
"position-independent-executables": true,
"relocation-model": "pic",
"rustc-abi": "softfloat",
"stack-probes": {
"kind": "inline"
},
"target-pointer-width": 64
}
-135
View File
@@ -1,135 +0,0 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rel.dyn : {
_agent_relocs_start = .;
*(.rel.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(4) {
. = ALIGN(4);
__printk = .; . += 4;
_printk = .; . += 4;
_panic = .; . += 4;
_kthread_create_on_node = .; . += 4;
_wake_up_process = .; . += 4;
_fget = .; . += 4;
_kernel_read = .; . += 4;
_kernel_write = .; . += 4;
___kmalloc_noprof = .; . += 4;
_kmalloc = .; . += 4;
_kfree = .; . += 4;
_execmem_alloc = .; . += 4;
_set_memory_rw = .; . += 4;
_set_memory_ro = .; . += 4;
_set_memory_x = .; . += 4;
_set_memory_nx = .; . += 4;
_modules = .; . += 4;
_register_module_notifier = .; . += 4;
_unregister_module_notifier = .; . += 4;
_execmem_free = .; . += 4;
_module_alloc = .; . += 4;
_module_memfree = .; . += 4;
___init_waitqueue_head = .; . += 4;
_prepare_to_wait_event = .; . += 4;
_finish_wait = .; . += 4;
___wake_up = .; . += 4;
_autoremove_wake_function = .; . += 4;
_schedule_hrtimeout_range = .; . += 4;
_pci_get_domain_bus_and_slot = .; . += 4;
_pci_irq_vector = .; . += 4;
_pci_dev_put = .; . += 4;
_schedule = .; . += 4;
_ktime_get_mono_fast_ns = .; . += 4;
_ktime_get_real_ts64 = .; . += 4;
_send_sig = .; . += 4;
_free_irq = .; . += 4;
_request_threaded_irq = .; . += 4;
_vmap = .; . += 4;
___pv_offset = .; . += 4;
_pgprot_kernel = .; . += 4;
_ioremap = .; . += 4;
_of_find_node_opts_by_path = .; . += 4;
_of_irq_get = .; . += 4;
_of_node_put = .; . += 4;
_init_task = .; . += 4;
_tasklist_lock = .; . += 4;
__raw_read_lock = .; . += 4;
__raw_read_unlock = .; . += 4;
__raw_read_lock_irqsave = .; . += 4;
__raw_read_unlock_irqrestore = .; . += 4;
__raw_write_lock = .; . += 4;
__raw_write_unlock = .; . += 4;
__raw_write_lock_irqsave = .; . += 4;
__raw_write_unlock_irqrestore = .; . += 4;
_detach_pid = .; . += 4;
_free_pids = .; . += 4;
_get_task_exe_file = .; . += 4;
_file_path = .; . += 4;
_fput = .; . += 4;
_copy_from_kernel_nofault = .; . += 4;
_get_task_mm = .; . += 4;
_kthread_use_mm = .; . += 4;
_kthread_unuse_mm = .; . += 4;
_mmput = .; . += 4;
_vm_mmap = .; . += 4;
_vm_munmap = .; . += 4;
_tracepoint_probe_register = .; . += 4;
_tracepoint_probe_unregister = .; . += 4;
___tracepoint_sched_process_fork = .; . += 4;
___tracepoint_sched_process_exit = .; . += 4;
___tracepoint_sched_process_exec = .; . += 4;
_vunmap = .; . += 4;
_arm_copy_to_user = .; . += 4;
_down_read = .; . += 4;
_up_read = .; . += 4;
_get_user_pages_unlocked = .; . += 4;
_get_user_pages_remote = .; . += 4;
_user_mode_thread = .; . += 4;
___start_BTF = .; . += 4;
___stop_BTF = .; . += 4;
_call_usermodehelper_setup = .; . += 4;
_call_usermodehelper_exec = .; . += 4;
_arm_copy_from_user = .; . += 4;
_fixup_exception = .; . += 4;
_patch_text = .; . += 4;
}
/* Everything above is only ever read, so a process being instrumented can be shown the very
same pages; everything below it each copy needs its own of. */
. = ALIGN(4096);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
}
. = ALIGN(4);
_heap_start = .;
}
VERSION {
{
global:
_start;
local:
*;
};
}
-134
View File
@@ -1,134 +0,0 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rela.dyn : {
_agent_relocs_start = .;
*(.rela.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(8) {
. = ALIGN(8);
__printk = .; . += 8;
_printk = .; . += 8;
_panic = .; . += 8;
_kthread_create_on_node = .; . += 8;
_wake_up_process = .; . += 8;
_fget = .; . += 8;
_kernel_read = .; . += 8;
_kernel_write = .; . += 8;
___kmalloc_noprof = .; . += 8;
_kmalloc = .; . += 8;
_kfree = .; . += 8;
_execmem_alloc = .; . += 8;
_set_memory_rw = .; . += 8;
_set_memory_ro = .; . += 8;
_set_memory_x = .; . += 8;
_set_memory_nx = .; . += 8;
_modules = .; . += 8;
_register_module_notifier = .; . += 8;
_unregister_module_notifier = .; . += 8;
_execmem_free = .; . += 8;
_module_alloc = .; . += 8;
_module_memfree = .; . += 8;
___init_waitqueue_head = .; . += 8;
_prepare_to_wait_event = .; . += 8;
_finish_wait = .; . += 8;
___wake_up = .; . += 8;
_autoremove_wake_function = .; . += 8;
_schedule_hrtimeout_range = .; . += 8;
_pci_get_domain_bus_and_slot = .; . += 8;
_pci_irq_vector = .; . += 8;
_pci_dev_put = .; . += 8;
_schedule = .; . += 8;
_ktime_get_mono_fast_ns = .; . += 8;
_ktime_get_real_ts64 = .; . += 8;
_send_sig = .; . += 8;
_free_irq = .; . += 8;
_request_threaded_irq = .; . += 8;
_generic_ioremap_prot = .; . += 8;
_vmap = .; . += 8;
_memstart_addr = .; . += 8;
_init_task = .; . += 8;
_tasklist_lock = .; . += 8;
__raw_read_lock = .; . += 8;
__raw_read_unlock = .; . += 8;
__raw_read_lock_irqsave = .; . += 8;
__raw_read_unlock_irqrestore = .; . += 8;
__raw_write_lock = .; . += 8;
__raw_write_unlock = .; . += 8;
__raw_write_lock_irqsave = .; . += 8;
__raw_write_unlock_irqrestore = .; . += 8;
_detach_pid = .; . += 8;
_free_pids = .; . += 8;
_get_task_exe_file = .; . += 8;
_file_path = .; . += 8;
_fput = .; . += 8;
_copy_from_kernel_nofault = .; . += 8;
_get_task_mm = .; . += 8;
_kthread_use_mm = .; . += 8;
_kthread_unuse_mm = .; . += 8;
_mmput = .; . += 8;
_vm_mmap = .; . += 8;
_vm_munmap = .; . += 8;
_tracepoint_probe_register = .; . += 8;
_tracepoint_probe_unregister = .; . += 8;
___tracepoint_sched_process_fork = .; . += 8;
___tracepoint_sched_process_exit = .; . += 8;
___tracepoint_sched_process_exec = .; . += 8;
_vunmap = .; . += 8;
___arch_copy_to_user = .; . += 8;
_down_read = .; . += 8;
_up_read = .; . += 8;
_get_user_pages_unlocked = .; . += 8;
_get_user_pages_remote = .; . += 8;
___arch_copy_from_user = .; . += 8;
_do_futex = .; . += 8;
_user_mode_thread = .; . += 8;
___start_BTF = .; . += 8;
___stop_BTF = .; . += 8;
_call_usermodehelper_setup = .; . += 8;
_call_usermodehelper_exec = .; . += 8;
_access_process_vm = .; . += 8;
_fixup_exception = .; . += 8;
_aarch64_insn_patch_text_nosync = .; . += 8;
_aarch64_insn_patch_text = .; . += 8;
}
/* Everything above is only ever read, so a process being instrumented can be shown the very
same pages; everything below it each copy needs its own of. */
. = ALIGN(4096);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
}
. = ALIGN(8);
_heap_start = .;
}
VERSION {
{
global:
_start;
local:
*;
};
}
-135
View File
@@ -1,135 +0,0 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rel.dyn : {
_agent_relocs_start = .;
*(.rel.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(4) {
. = ALIGN(4);
__printk = .; . += 4;
_printk = .; . += 4;
_panic = .; . += 4;
_kthread_create_on_node = .; . += 4;
_wake_up_process = .; . += 4;
_fget = .; . += 4;
_kernel_read = .; . += 4;
_kernel_write = .; . += 4;
___kmalloc_noprof = .; . += 4;
_kmalloc = .; . += 4;
_kfree = .; . += 4;
_execmem_alloc = .; . += 4;
_set_memory_rw = .; . += 4;
_set_memory_ro = .; . += 4;
_set_memory_x = .; . += 4;
_set_memory_nx = .; . += 4;
_modules = .; . += 4;
_register_module_notifier = .; . += 4;
_unregister_module_notifier = .; . += 4;
_text_poke_kgdb = .; . += 4;
_execmem_free = .; . += 4;
_module_alloc = .; . += 4;
_module_memfree = .; . += 4;
___init_waitqueue_head = .; . += 4;
_prepare_to_wait_event = .; . += 4;
_finish_wait = .; . += 4;
___wake_up = .; . += 4;
_autoremove_wake_function = .; . += 4;
_schedule_hrtimeout_range = .; . += 4;
_pci_get_domain_bus_and_slot = .; . += 4;
_pci_irq_vector = .; . += 4;
_pci_dev_put = .; . += 4;
_schedule = .; . += 4;
_ktime_get_mono_fast_ns = .; . += 4;
_ktime_get_real_ts64 = .; . += 4;
_send_sig = .; . += 4;
_free_irq = .; . += 4;
_request_threaded_irq = .; . += 4;
_vmap = .; . += 4;
_current_task = .; . += 4;
_ioremap = .; . += 4;
___default_kernel_pte_mask = .; . += 4;
_init_task = .; . += 4;
_tasklist_lock = .; . += 4;
__raw_read_lock = .; . += 4;
__raw_read_unlock = .; . += 4;
__raw_read_lock_irqsave = .; . += 4;
__raw_read_unlock_irqrestore = .; . += 4;
__raw_write_lock = .; . += 4;
__raw_write_unlock = .; . += 4;
__raw_write_lock_irqsave = .; . += 4;
__raw_write_unlock_irqrestore = .; . += 4;
_detach_pid = .; . += 4;
_free_pids = .; . += 4;
_get_task_exe_file = .; . += 4;
_file_path = .; . += 4;
_fput = .; . += 4;
_copy_from_kernel_nofault = .; . += 4;
_get_task_mm = .; . += 4;
_kthread_use_mm = .; . += 4;
_kthread_unuse_mm = .; . += 4;
_mmput = .; . += 4;
_vm_mmap = .; . += 4;
_vm_munmap = .; . += 4;
_tracepoint_probe_register = .; . += 4;
_tracepoint_probe_unregister = .; . += 4;
___tracepoint_sched_process_fork = .; . += 4;
___tracepoint_sched_process_exit = .; . += 4;
___tracepoint_sched_process_exec = .; . += 4;
_vunmap = .; . += 4;
__copy_to_user = .; . += 4;
_down_read = .; . += 4;
_up_read = .; . += 4;
_get_user_pages_unlocked = .; . += 4;
_get_user_pages_remote = .; . += 4;
_do_futex = .; . += 4;
_user_mode_thread = .; . += 4;
___start_BTF = .; . += 4;
___stop_BTF = .; . += 4;
_call_usermodehelper_setup = .; . += 4;
_call_usermodehelper_exec = .; . += 4;
__copy_from_user = .; . += 4;
_access_process_vm = .; . += 4;
_register_die_notifier = .; . += 4;
_unregister_die_notifier = .; . += 4;
}
/* Everything above is only ever read, so a process being instrumented can be shown the very
same pages; everything below it each copy needs its own of. */
. = ALIGN(4096);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
}
. = ALIGN(4);
_heap_start = .;
}
VERSION {
{
global:
_start;
local:
*;
};
}
-137
View File
@@ -1,137 +0,0 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rela.dyn : {
_agent_relocs_start = .;
*(.rela.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(8) {
. = ALIGN(8);
__printk = .; . += 8;
_printk = .; . += 8;
_panic = .; . += 8;
_kthread_create_on_node = .; . += 8;
_wake_up_process = .; . += 8;
_fget = .; . += 8;
_kernel_read = .; . += 8;
_kernel_write = .; . += 8;
___kmalloc_noprof = .; . += 8;
_kmalloc = .; . += 8;
_kfree = .; . += 8;
_execmem_alloc = .; . += 8;
_set_memory_rw = .; . += 8;
_set_memory_ro = .; . += 8;
_set_memory_x = .; . += 8;
_set_memory_nx = .; . += 8;
_modules = .; . += 8;
_register_module_notifier = .; . += 8;
_unregister_module_notifier = .; . += 8;
_text_poke_kgdb = .; . += 8;
_execmem_free = .; . += 8;
_module_alloc = .; . += 8;
_module_memfree = .; . += 8;
___init_waitqueue_head = .; . += 8;
_prepare_to_wait_event = .; . += 8;
_finish_wait = .; . += 8;
___wake_up = .; . += 8;
_autoremove_wake_function = .; . += 8;
_schedule_hrtimeout_range = .; . += 8;
_pci_get_domain_bus_and_slot = .; . += 8;
_pci_irq_vector = .; . += 8;
_pci_dev_put = .; . += 8;
_schedule = .; . += 8;
_ktime_get_mono_fast_ns = .; . += 8;
_ktime_get_real_ts64 = .; . += 8;
_send_sig = .; . += 8;
_free_irq = .; . += 8;
_request_threaded_irq = .; . += 8;
_vmap = .; . += 8;
_page_offset_base = .; . += 8;
_current_task = .; . += 8;
_cpu_current_top_of_stack = .; . += 8;
_register_die_notifier = .; . += 8;
_unregister_die_notifier = .; . += 8;
_ioremap = .; . += 8;
___default_kernel_pte_mask = .; . += 8;
_init_task = .; . += 8;
_tasklist_lock = .; . += 8;
__raw_read_lock = .; . += 8;
__raw_read_unlock = .; . += 8;
__raw_read_lock_irqsave = .; . += 8;
__raw_read_unlock_irqrestore = .; . += 8;
__raw_write_lock = .; . += 8;
__raw_write_unlock = .; . += 8;
__raw_write_lock_irqsave = .; . += 8;
__raw_write_unlock_irqrestore = .; . += 8;
_detach_pid = .; . += 8;
_free_pids = .; . += 8;
_get_task_exe_file = .; . += 8;
_file_path = .; . += 8;
_fput = .; . += 8;
_copy_from_kernel_nofault = .; . += 8;
_get_task_mm = .; . += 8;
_kthread_use_mm = .; . += 8;
_kthread_unuse_mm = .; . += 8;
_mmput = .; . += 8;
_vm_mmap = .; . += 8;
_vm_munmap = .; . += 8;
_tracepoint_probe_register = .; . += 8;
_tracepoint_probe_unregister = .; . += 8;
___tracepoint_sched_process_fork = .; . += 8;
___tracepoint_sched_process_exit = .; . += 8;
___tracepoint_sched_process_exec = .; . += 8;
_vunmap = .; . += 8;
__copy_to_user = .; . += 8;
_down_read = .; . += 8;
_up_read = .; . += 8;
_get_user_pages_unlocked = .; . += 8;
_get_user_pages_remote = .; . += 8;
_do_futex = .; . += 8;
_user_mode_thread = .; . += 8;
___start_BTF = .; . += 8;
___stop_BTF = .; . += 8;
_call_usermodehelper_setup = .; . += 8;
_call_usermodehelper_exec = .; . += 8;
__copy_from_user = .; . += 8;
_access_process_vm = .; . += 8;
}
/* Everything above is only ever read, so a process being instrumented can be shown the very
same pages; everything below it each copy needs its own of. */
. = ALIGN(4096);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
}
. = ALIGN(8);
_heap_start = .;
}
VERSION {
{
global:
_start;
local:
*;
};
}
-97
View File
@@ -1,97 +0,0 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rel.dyn : {
_agent_relocs_start = .;
*(.rel.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(4) {
. = ALIGN(4);
_Get_Cur_VM_Handle = .; . += 4;
_Get_Sys_VM_Handle = .; . += 4;
_Schedule_Global_Event = .; . += 4;
_Create_Semaphore = .; . += 4;
_Wait_Semaphore = .; . += 4;
_Signal_Semaphore = .; . += 4;
_Get_Next_VM_Handle = .; . += 4;
_Release_Time_Slice = .; . += 4;
_Set_Global_Time_Out = .; . += 4;
_Cancel_Time_Out = .; . += 4;
_Get_System_Time = .; . += 4;
__HeapAllocate = .; . += 4;
__HeapFree = .; . += 4;
__PageAllocate = .; . += 4;
__PageFree = .; . += 4;
__CopyPageTable = .; . += 4;
__MapPhysToLinear = .; . += 4;
_VMM_Add_DDB = .; . += 4;
_VMM_Remove_DDB = .; . += 4;
__RegOpenKey = .; . += 4;
__RegCloseKey = .; . += 4;
__RegEnumKey = .; . += 4;
__RegQueryValueEx = .; . += 4;
__PageReserve = .; . += 4;
__PageCommit = .; . += 4;
_Hook_VMM_Fault = .; . += 4;
_Unhook_VMM_Fault = .; . += 4;
_Fatal_Error_Handler = .; . += 4;
_Get_Cur_Thread_Handle = .; . += 4;
_Get_Initial_Thread_Handle = .; . += 4;
_Get_Next_Thread_Handle = .; . += 4;
__Debug_Printf_Service = .; . += 4;
__ContextSwitch = .; . += 4;
__PageModifyPermissions = .; . += 4;
__GetCurrentContext = .; . += 4;
_VPICD_Virtualize_IRQ = .; . += 4;
_VPICD_Force_Default_Behavior = .; . += 4;
_VPICD_Phys_EOI = .; . += 4;
_VPICD_Physically_Unmask = .; . += 4;
__VWIN32_QueueUserApc = .; . += 4;
__VWIN32_Get_Thread_Context = .; . += 4;
__VWIN32_Set_Thread_Context = .; . += 4;
__VWIN32_CreateRing0Thread = .; . += 4;
_IFSMgr_Ring0_FileIO = .; . += 4;
_KERNEL32_Base = .; . += 4;
_KERNEL32_ProcessIdObfuscator = .; . += 4;
_KERNEL32_ModuleTable = .; . += 4;
__PageCommitPhys = .; . += 4;
}
/* Everything above is only ever read, so a process being instrumented can be shown the very
same pages; everything below it each copy needs its own of. */
. = ALIGN(4096);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.bss : {
*(.bss*)
}
. = ALIGN(4);
_heap_start = .;
}
VERSION {
{
global:
_start;
frida_win9x_user_main;
local:
*;
};
}
-98
View File
@@ -1,98 +0,0 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rel.dyn : {
_agent_relocs_start = .;
*(.rel.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(4) {
. = ALIGN(4);
_ExAllocatePoolWithTag = .; . += 4;
_ExFreePoolWithTag = .; . += 4;
_PsCreateSystemThread = .; . += 4;
_PsGetCurrentThreadId = .; . += 4;
_KeInitializeEvent = .; . += 4;
_KeWaitForSingleObject = .; . += 4;
_KeSetEvent = .; . += 4;
_KeInitializeDpc = .; . += 4;
_PsSetCreateThreadNotifyRoutine = .; . += 4;
_PsRemoveCreateThreadNotifyRoutine = .; . += 4;
_KeInsertQueueDpc = .; . += 4;
_ZwYieldExecution = .; . += 4;
_KeBugCheckEx = .; . += 4;
_MmMapIoSpace = .; . += 4;
_MmGetPhysicalAddress = .; . += 4;
_HalGetInterruptVector = .; . += 4;
_IoConnectInterrupt = .; . += 4;
_IoDisconnectInterrupt = .; . += 4;
_PsActiveProcessHead = .; . += 4;
_PsInitialSystemProcess = .; . += 4;
_PsGetProcessId = .; . += 4;
_PsGetProcessImageFileName = .; . += 4;
_PsGetProcessPeb = .; . += 4;
_PsGetCurrentThread = .; . += 4;
_PsGetThreadProcess = .; . += 4;
_PsGetThreadId = .; . += 4;
_PsGetContextThread = .; . += 4;
_PsSetContextThread = .; . += 4;
_KeStackAttachProcess = .; . += 4;
_KeUnstackDetachProcess = .; . += 4;
_ZwCreateFile = .; . += 4;
_ZwOpenKey = .; . += 4;
_ZwEnumerateKey = .; . += 4;
_ZwQueryValueKey = .; . += 4;
_ZwReadFile = .; . += 4;
_ZwClose = .; . += 4;
_ZwCreateEvent = .; . += 4;
_ZwAllocateVirtualMemory = .; . += 4;
_ZwFreeVirtualMemory = .; . += 4;
_ZwCreateThread = .; . += 4;
_PsProcessType = .; . += 4;
_ObReferenceObjectByHandle = .; . += 4;
_ObOpenObjectByPointer = .; . += 4;
_ExEventObjectType = .; . += 4;
_IoAllocateMdl = .; . += 4;
_IoFreeMdl = .; . += 4;
_MmBuildMdlForNonPagedPool = .; . += 4;
_MmMapLockedPagesSpecifyCache = .; . += 4;
_MmUnmapLockedPages = .; . += 4;
}
/* Everything above is only ever read, so a process being instrumented can be shown the very
same pages; everything below it each copy needs its own of. */
. = ALIGN(4096);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.bss : {
*(.bss*)
}
. = ALIGN(4);
_heap_start = .;
}
VERSION {
{
global:
_start;
frida_winnt_user_main;
frida_winnt_user_bootstrap;
local:
*;
};
}
-108
View File
@@ -1,108 +0,0 @@
SECTIONS {
/* The large code model gives every section an l-prefixed name of its own, so that
the compiler can reach it with a 64-bit displacement rather than assume 2GB. */
.text : {
*(.text.entrypoint)
*(.ltext.entrypoint)
*(.text*)
*(.ltext*)
}
.rela.dyn : {
_agent_relocs_start = .;
*(.rela.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
*(.lrodata*)
}
.kernel_addrs : ALIGN(8) {
. = ALIGN(8);
_ExAllocatePoolWithTag = .; . += 8;
_ExFreePoolWithTag = .; . += 8;
_PsCreateSystemThread = .; . += 8;
_PsGetCurrentThreadId = .; . += 8;
_KeInitializeEvent = .; . += 8;
_KeWaitForSingleObject = .; . += 8;
_KeSetEvent = .; . += 8;
_KeInitializeDpc = .; . += 8;
_PsSetCreateThreadNotifyRoutine = .; . += 8;
_PsRemoveCreateThreadNotifyRoutine = .; . += 8;
_KeInsertQueueDpc = .; . += 8;
_ZwYieldExecution = .; . += 8;
_KeBugCheckEx = .; . += 8;
_MmMapIoSpace = .; . += 8;
_MmGetPhysicalAddress = .; . += 8;
_HalGetInterruptVector = .; . += 8;
_IoConnectInterrupt = .; . += 8;
_IoDisconnectInterrupt = .; . += 8;
_PsActiveProcessHead = .; . += 8;
_PsInitialSystemProcess = .; . += 8;
_PsGetProcessId = .; . += 8;
_PsGetProcessImageFileName = .; . += 8;
_PsGetProcessPeb = .; . += 8;
_PsGetCurrentThread = .; . += 8;
_PsGetThreadProcess = .; . += 8;
_PsGetThreadId = .; . += 8;
_PsGetContextThread = .; . += 8;
_PsSetContextThread = .; . += 8;
_KeStackAttachProcess = .; . += 8;
_KeUnstackDetachProcess = .; . += 8;
_ZwCreateFile = .; . += 8;
_ZwOpenKey = .; . += 8;
_ZwEnumerateKey = .; . += 8;
_ZwQueryValueKey = .; . += 8;
_ZwReadFile = .; . += 8;
_ZwClose = .; . += 8;
_ZwCreateEvent = .; . += 8;
_ZwAllocateVirtualMemory = .; . += 8;
_ZwFreeVirtualMemory = .; . += 8;
_ZwCreateThread = .; . += 8;
_PsProcessType = .; . += 8;
_ObReferenceObjectByHandle = .; . += 8;
_ObOpenObjectByPointer = .; . += 8;
_ExEventObjectType = .; . += 8;
_IoAllocateMdl = .; . += 8;
_IoFreeMdl = .; . += 8;
_MmBuildMdlForNonPagedPool = .; . += 8;
_MmMapLockedPagesSpecifyCache = .; . += 8;
_MmUnmapLockedPages = .; . += 8;
}
/* Everything above is only ever read, so a process being instrumented can be shown the very
same pages; everything below it each copy needs its own of. */
. = ALIGN(4096);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.data : {
*(.data*)
*(.ldata*)
}
.bss : {
*(.bss*)
*(.lbss*)
}
. = ALIGN(8);
_heap_start = .;
}
VERSION {
{
global:
_start;
frida_winnt_user_main;
frida_winnt_user_bootstrap;
local:
*;
};
}
-112
View File
@@ -1,112 +0,0 @@
SECTIONS {
.text : {
*(.text.entrypoint)
*(.text*)
}
.rela.dyn : {
_agent_relocs_start = .;
*(.rela.dyn)
_agent_relocs_end = .;
}
.rodata : {
*(.rodata*)
}
.kernel_addrs : ALIGN(8) {
. = ALIGN(8);
_panic = .; . += 8;
_IOLog = .; . += 8;
_kalloc = .; . += 8;
_proc_iterate = .; . += 8;
_proc_find = .; . += 8;
_proc_rele = .; . += 8;
_proc_pid = .; . += 8;
_proc_best_name = .; . += 8;
_proc_task = .; . += 8;
_current_task = .; . += 8;
_current_thread = .; . += 8;
_get_task_map = .; . += 8;
_mach_vm_allocate = .; . += 8;
_mach_vm_deallocate = .; . += 8;
_mach_vm_protect = .; . += 8;
_mach_vm_remap = .; . += 8;
_vm_map_copyin = .; . += 8;
_vm_map_copyout = .; . += 8;
_thread_create = .; . += 8;
_thread_set_state = .; . += 8;
_thread_resume = .; . += 8;
_thread_terminate = .; . += 8;
_task_suspend = .; . += 8;
_task_resume = .; . += 8;
_task_clear_return_wait = .; . += 8;
_get_bsdtask_info = .; . += 8;
_thread_suspend = .; . += 8;
_kfree = .; . += 8;
_kernel_thread_start = .; . += 8;
_assert_wait = .; . += 8;
_assert_wait_timeout = .; . += 8;
_thread_block = .; . += 8;
_thread_wakeup = .; . += 8;
_thread_wakeup_prim = .; . += 8;
_mach_absolute_time = .; . += 8;
_absolutetime_to_nanoseconds = .; . += 8;
_clock_get_calendar_microtime = .; . += 8;
_ml_io_map = .; . += 8;
_ml_vtophys = .; . += 8;
_ml_static_vtop = .; . += 8;
__ZN9IOService11getPlatformEv = .; . += 8;
__ZN9IOServiceC2Ev = .; . += 8;
__ZN8OSSymbol17withCStringNoCopyEPKc = .; . += 8;
__ZN6OSData9withBytesEPKvj = .; . += 8;
_kalloc_data = .; . += 8;
_kfree_data = .; . += 8;
_wakeup = .; . += 8;
_sock_socket = .; . += 8;
_sock_connect = .; . += 8;
_sock_send = .; . += 8;
_sock_receive = .; . += 8;
_sock_close = .; . += 8;
_sysent = .; . += 8;
_task_restartable_ranges_register = .; . += 8;
_task_threads = .; . += 8;
_mach_vm_region = .; . += 8;
_mach_vm_region_recurse = .; . += 8;
_copyin = .; . += 8;
_copyout = .; . += 8;
_ulock_wake = .; . += 8;
_task_info = .; . += 8;
_convert_port_to_thread = .; . += 8;
_convert_port_to_thread_read = .; . += 8;
_convert_port_to_thread_inspect = .; . += 8;
}
. = ALIGN(16384);
_agent_private_start = .;
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.init_array : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
.data : {
*(.data*)
}
.bss : {
*(.bss*)
}
. = ALIGN(8);
_heap_start = .;
}
VERSION {
{
global:
_start;
local:
*;
};
}
+59
View File
@@ -0,0 +1,59 @@
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;
_kalloc_data = .; . += 8;
_kfree_data = .; . += 8;
_wakeup = .; . += 8;
_sock_socket = .; . += 8;
_sock_connect = .; . += 8;
_sock_send = .; . += 8;
_sock_receive = .; . += 8;
_sock_close = .; . += 8;
}
.data.rel.ro : {
*(.data.rel.ro*)
}
.got : {
*(.got*)
}
.bss : {
*(.bss*)
}
. = ALIGN(8);
_heap_start = .;
}
VERSION {
{
global:
_start;
local:
*;
};
}
@@ -1,17 +0,0 @@
{
"abi": "eabi",
"arch": "arm",
"crt-objects-fallback": "false",
"data-layout": "e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64",
"features": "+v7,+thumb2,+thumb-mode,+soft-float,+strict-align,-neon",
"linker": "rust-lld",
"linker-flavor": "gnu-lld",
"llvm-floatabi": "soft",
"llvm-target": "armv7a-none-eabi",
"max-atomic-width": 64,
"panic-strategy": "abort",
"plt-by-default": false,
"position-independent-executables": true,
"relocation-model": "pic",
"target-pointer-width": 32
}
+22 -134
View File
@@ -8,17 +8,12 @@ fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let devkit_dir = PathBuf::from(env::var("GUMJS_DEVKIT_DIR").unwrap());
let target = env::var("TARGET").unwrap();
let cc_str = env::var(format!("CC_{}", target.replace('-', "_")))
let cc_str = env::var("CC_aarch64_unknown_none")
.or_else(|_| env::var("CC"))
.unwrap_or_else(|_| "cc".to_string());
// Same convention as cc-rs: the variable may carry flags, and a bare-metal
// compiler needs them to find its libc at all.
let mut cc_argv = cc_str.split_whitespace();
let cc = Path::new(cc_argv.next().unwrap());
let cc_args: Vec<&str> = cc_argv.collect();
let cc_include_paths = detect_gcc_include_paths(cc, &cc_args);
let cc_library_paths = detect_gcc_library_paths(cc, &cc_args);
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()
@@ -30,10 +25,9 @@ fn main() {
.header(devkit_dir.join("frida-gumjs.h").to_str().unwrap())
.clang_arg("-nostdinc")
.clang_arg("-target")
.clang_arg(format!("{}-none-elf", target.split('-').next().unwrap()))
.clang_arg("aarch64-none-elf")
.clang_args(cc_clang_args)
.clang_arg(format!("-I{}", devkit_dir.to_str().unwrap()))
.blocklist_type("max_align_t")
.merge_extern_blocks(true)
.generate()
.expect("Unable to generate bindings");
@@ -42,131 +36,25 @@ fn main() {
.write_to_file(out_dir.join("bindings.rs"))
.expect("Couldn't write bindings");
// The Linux flavour is a staticlib: kbuild owns the final link, so it — not
// us — decides where the devkit and libc archives come from.
if env::var("CARGO_FEATURE_BLOB").is_ok() {
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());
}
for arg in &cc_args {
if let Some(path) = arg.strip_prefix("-L") {
println!("cargo:rustc-link-search=native={path}");
}
}
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");
let flavor = if env::var("CARGO_FEATURE_WIN9X").is_ok() {
"win9x"
} else if env::var("CARGO_FEATURE_WINNT").is_ok() {
"winnt"
} else if env::var("CARGO_FEATURE_LINUX_INJECTED").is_ok() {
"linux"
} else {
"xnu"
};
let arch = match target.split('-').next().unwrap() {
"i686" => "x86",
"armv7a" => "arm",
"aarch64" => "arm64",
other => other,
};
let script = format!("agent-{flavor}-{arch}.lds");
println!("cargo:rustc-link-arg=--script={script}");
println!("cargo:rustc-link-arg=--gc-sections");
// All of the agent lives in the library crate, so nothing in the binary
// references the entrypoint the host calls; keep the linker from
// dropping it along with the rest of the unreferenced archive members.
println!("cargo:rustc-link-arg=--undefined=_start");
let note = compile_version_note(&out_dir, cc, &cc_args, arch);
println!("cargo:rustc-link-arg={}", note.to_string_lossy());
}
if target.starts_with("i686") && env::var("CARGO_FEATURE_LINUX_INJECTED").is_ok() {
let shim = out_dir.join("regparm.o");
let status = Command::new(cc)
.args(&cc_args)
.args(["-m32", "-c", "-O2", "-fPIC", "-ffreestanding", "-o"])
.arg(&shim)
.arg("src/linux/regparm.c")
.status()
.expect("Couldn't run the compiler");
assert!(status.success(), "Couldn't compile the kernel-call shim");
println!("cargo:rustc-link-arg={}", shim.to_string_lossy());
println!("cargo:rerun-if-changed=src/linux/regparm.c");
}
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-env-changed=GUMJS_DEVKIT_DIR");
println!("cargo:rerun-if-env-changed=FRIDA_VERSION");
println!("cargo:rerun-if-changed={}", devkit_dir.join("frida-gumjs.h").display());
}
fn compile_version_note(out_dir: &Path, cc: &Path, cc_args: &[&str], arch: &str) -> PathBuf {
let version = env::var("FRIDA_VERSION")
.ok()
.filter(|v| !v.is_empty())
.unwrap_or_else(|| "0.0.0".to_string());
let payload = format!(
"{{\"type\":\"frida\",\"name\":\"frida-barebone-agent\",\
\"version\":\"{version}\",\"architecture\":\"{arch}\"}}"
println!(
"cargo:rustc-link-search=native={}",
devkit_dir.to_str().unwrap()
);
let source = out_dir.join("version-note.S");
fs::write(
&source,
format!(
".section .note.package,\"\",%note\n\
.balign 4\n\
.long 2f - 1f\n\
.long 4f - 3f\n\
.long 0xcafe1a7e\n\
1: .asciz \"FDO\"\n\
2: .balign 4\n\
3: .asciz {payload:?}\n\
4: .balign 4\n"
),
)
.expect("Couldn't write the version note");
let object = out_dir.join("version-note.o");
let status = Command::new(cc)
.args(cc_args)
.args(["-target", &llvm_target(), "-c", "-o"])
.arg(&object)
.arg(&source)
.status()
.expect("Couldn't run the compiler");
assert!(status.success(), "Couldn't assemble the version note");
object
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");
}
fn llvm_target() -> String {
let target = env::var("TARGET").unwrap();
let spec = Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join(format!("{target}.json"));
let text = fs::read_to_string(&spec).expect("Couldn't read the target specification");
let key = "\"llvm-target\"";
let at = text.find(key).expect("Target specification names no LLVM target");
let rest = &text[at + key.len()..];
let open = rest.find('"').unwrap() + 1;
let close = open + rest[open..].find('"').unwrap();
rest[open..close].to_string()
}
pub fn detect_gcc_include_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
pub fn detect_gcc_include_paths(gcc: &Path) -> Vec<PathBuf> {
let out = Command::new(gcc)
.args(args)
.args(["-xc", "-E", "-v", "-"])
.stdin(Stdio::null())
.output()
@@ -206,8 +94,8 @@ pub fn detect_gcc_include_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
paths
}
pub fn detect_gcc_library_paths(gcc: &Path, args: &[&str]) -> Vec<PathBuf> {
let out = Command::new(gcc).args(args).arg("-print-search-dirs").output()
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);
+5 -5
View File
@@ -1,8 +1,8 @@
export FRIDA_BAREBONE_CONFIG=$PWD/etc/xnu.json
export PYTHONPATH=$HOME/src/frida-python
sdk=$HOME/src/frida-core/deps/sdk-none-arm64-softfloat_pic
export CC="clang --target=aarch64-none-elf -I$sdk/include -I$sdk/include/glib-2.0 -I$sdk/lib/glib-2.0/include \
-I$sdk/include/capstone -I$sdk/include/json-glib-1.0"
export RUSTFLAGS="-L native=$sdk/lib"
export GUMJS_DEVKIT_DIR=$HOME/src/frida-gum/build/none-arm64-softfloat_pic/bindings/gumjs/devkit
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
@@ -1,8 +0,0 @@
{
"agent": {
"transport": {
"type": "socket",
"path": "/tmp/frida-guest.sock"
}
}
}
-8
View File
@@ -1,8 +0,0 @@
{
"agent": {
"transport": {
"type": "device",
"path": "/dev/frida"
}
}
}
-15
View File
@@ -1,15 +0,0 @@
{
"connection": {
"host": "127.0.0.1",
"port": 1234
},
"kernel": "win9x",
"agent": {
"path": "/home/oleavr/src/frida-core/src/barebone/agent/target-win9x/i686-unknown-none-pic/release/frida-barebone-agent",
"transport": {
"type": "hostlink",
"qmp": "unix:/tmp/claude-1000/-home-oleavr-src-frida-core/0f3e358c-8f66-4246-b793-deb2cd3727f3/scratchpad/qmp.sock",
"bus": "frida-vserial.0"
}
}
}
-15
View File
@@ -1,15 +0,0 @@
{
"connection": {
"host": "127.0.0.1",
"port": 1234
},
"kernel": "winnt",
"agent": {
"path": "/home/oleavr/src/frida-core/src/barebone/agent/target-winnt/i686-unknown-none-pic/release/frida-barebone-agent",
"transport": {
"type": "hostlink",
"qmp": "unix:/tmp/qmp.sock",
"bus": "frida-vserial.0"
}
}
}
@@ -1,20 +0,0 @@
{
"arch": "x86",
"cpu": "pentium4",
"crt-objects-fallback": "false",
"data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
"features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float",
"linker": "rust-lld",
"linker-flavor": "gnu-lld",
"llvm-target": "i686-unknown-none-elf",
"max-atomic-width": 64,
"panic-strategy": "abort",
"plt-by-default": false,
"position-independent-executables": true,
"relocation-model": "pic",
"rustc-abi": "softfloat",
"stack-probes": {
"kind": "inline"
},
"target-pointer-width": 32
}
-20
View File
@@ -1,20 +0,0 @@
{
"arch": "x86",
"cpu": "pentium4",
"crt-objects-fallback": "false",
"data-layout": "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
"features": "-mmx,-sse,-sse2,-sse3,-ssse3,-sse4.1,-sse4.2,-avx,-avx2,+soft-float",
"linker": "rust-lld",
"linker-flavor": "gnu-lld",
"llvm-target": "i686-unknown-none-elf",
"max-atomic-width": 64,
"panic-strategy": "abort",
"plt-by-default": true,
"position-independent-executables": false,
"relocation-model": "static",
"rustc-abi": "softfloat",
"stack-probes": {
"kind": "inline"
},
"target-pointer-width": 32
}
-28
View File
@@ -1,28 +0,0 @@
MODULE_NAME ?= frida-agent
obj-m += $(MODULE_NAME).o
# frida-agent-prelinked.o is not built from source here: it is the Rust agent, the
# GumJS devkit and the C library collapsed into a single relocatable object by the prelink
# step in Makefile. Copy it in through if_changed rather than kbuild's _shipped rule,
# so the .cmd file modpost expects gets written.
$(MODULE_NAME)-y := frida-kmod.o frida-agent-prelinked.o
targets += frida-agent-prelinked.o
quiet_cmd_frida_agent_prelinked = COPY $@
cmd_frida_agent_prelinked = cp $< $@
$(obj)/frida-agent-prelinked.o: $(src)/frida-agent-prelinked.o.bin FORCE
$(call if_changed,frida_agent_prelinked)
# On x86 objtool validates every object going into a module and generates its ORC
# unwind tables. It was not built to read a JavaScript runtime linked against a
# freestanding libc: it gives up on the first Rust switch jump table it cannot
# follow, and the tables would only ever describe frames no kernel stack trace walks
# into. Both the blob and the module object it is linked into have to be excused,
# since objtool runs over each.
OBJECT_FILES_NON_STANDARD_frida-agent-prelinked.o := y
OBJECT_FILES_NON_STANDARD_$(MODULE_NAME).o := y
ccflags-y += $(FRIDA_KMOD_DEFS)
-206
View File
@@ -1,206 +0,0 @@
# Builds frida-agent.ko in two steps, because kbuild insists on owning the final
# link:
#
# 1. Collapse the Rust staticlib plus everything it needs — the GumJS devkit and
# the C library — into one relocatable object, and localize every symbol except
# the entry points frida-kmod.c calls. Localizing matters: Gum and the C library
# define names like memcpy and malloc that must not leak into the kernel's
# namespace.
# 2. Hand that object to kbuild as a shipped object, so vermagic, modinfo and
# CRCs all come from the target kernel's own build system.
#
# See README.md for usage.
# KDIR is the kernel to build against. When it is a source tree that was configured
# elsewhere — which is what Android's modules_prepare tarball gives — point KOUT at
# the prepared tree.
KDIR ?= /lib/modules/$(shell uname -r)/build
PROFILE ?= release
# The kernel's own name for the architecture, and the one Rust knows it by. Both
# follow from the machine this is built for, which defaults to the one building.
ARCH ?= $(shell uname -m \
| sed -e 's/^i[3-6]86$$/x86/' -e 's/^x86_64$$/x86/' -e 's/^aarch64$$/arm64/')
BITS ?= $(shell uname -m \
| sed -e 's/^i[3-6]86$$/32/' -e 's/^x86_64$$/64/' -e 's/^aarch64$$/64/')
ifeq ($(ARCH)-$(BITS),x86-32)
RUST_TARGET ?= i686-unknown-none
AGENT_TARGET_FLAGS ?= --target $(AGENT_DIR)/$(RUST_TARGET).json -Z json-target-spec
SDK_ARCH ?= x86
TOOLPREFIX ?=
AGENT_LD_EMULATION ?= elf_i386
else ifeq ($(ARCH)-$(BITS),x86-64)
RUST_TARGET ?= x86_64-unknown-none
SDK_ARCH ?= x86_64
TOOLPREFIX ?=
AGENT_LD_EMULATION ?= elf_x86_64
else ifeq ($(ARCH)-$(BITS),arm64-64)
RUST_TARGET ?= aarch64-unknown-none
SDK_ARCH ?= arm64
TOOLPREFIX ?= aarch64-none-elf-
else
$(error Unsupported ARCH "$(ARCH)" and BITS "$(BITS)"; this flavour covers \
x86/32, x86/64 and arm64/64)
endif
AGENT_TARGET_FLAGS ?= --target $(RUST_TARGET)
AGENT_DIR := $(CURDIR)/..
AGENT_TARGET_DIR := $(AGENT_DIR)/target-linux
AGENT_LIB := $(AGENT_TARGET_DIR)/$(RUST_TARGET)/$(PROFILE)/libfrida_barebone_agent.a
GUMJS_DEVKIT_DIR ?= $(error Set GUMJS_DEVKIT_DIR to the directory holding libfrida-gumjs.a)
# Any binutils that speak the target's ELF will do; nothing here is compiled with
# them. Building for the host's own architecture, that is the host's binutils.
AGENT_LD ?= $(TOOLPREFIX)ld
AGENT_AR ?= $(TOOLPREFIX)ar
AGENT_NM ?= $(TOOLPREFIX)nm
AGENT_OBJCOPY ?= $(TOOLPREFIX)objcopy
# The ABI the kernel imposes on anything linked into it. The flags only reach crates
# Cargo compiles, and the toolchain ships core, alloc and compiler_builtins prebuilt
# without them, so those three have to be rebuilt from source as well. That needs the
# rust-src component and an unstable flag, hence RUSTC_BOOTSTRAP.
#
# On x86_64 that is endbr64 on every address-taken function, since a kernel with
# CONFIG_X86_KERNEL_IBT faults an indirect call landing anywhere else, and switches
# compiled as comparisons rather than jump tables, which objtool cannot follow into.
# Soft-float, the kernel code model and the absent red zone come with the target
# itself. On arm64
# it is x18, which the kernel reserves for the shadow call stack, so nothing linked in
# may use it as a general-purpose register.
ifeq ($(ARCH)-$(BITS),x86-32)
AGENT_RUSTFLAGS ?=
AGENT_CFLAGS ?= --target=i386-none-elf \
-Xclang -target-feature -Xclang +soft-float -mno-mmx -mno-sse \
-mlong-double-64 -fno-pic --sysroot=$(FRIDA_SDK)
else ifeq ($(ARCH)-$(BITS),x86-64)
AGENT_RUSTFLAGS ?= -Zcf-protection=branch \
-Cllvm-args=--min-jump-table-entries=65536 \
-Crelocation-model=static -Zplt=yes
AGENT_CFLAGS ?= --target=x86_64-none-elf \
-Xclang -target-feature -Xclang +soft-float -mno-mmx -mno-sse -mno-red-zone \
-mlong-double-64 -mcmodel=kernel -fcf-protection=branch -fno-jump-tables \
-fno-pic --sysroot=$(FRIDA_SDK)
else
AGENT_RUSTFLAGS ?= -Zfixed-x18
AGENT_CFLAGS ?= --target=aarch64-none-elf -mabi=aapcs-soft -mgeneral-regs-only \
-ffixed-x18 -fno-pic --sysroot=$(FRIDA_SDK)
endif
AGENT_BUILD_STD ?= -Z build-std=core,alloc,compiler_builtins
# The devkit header reaches for GLib's and capstone's, which a devkit built against
# an SDK leaves in that SDK rather than beside itself. The sysroot's own include
# directory is spelled out because only clang's bare-metal driver, which it selects
# for arm64 but not for x86_64, searches there on its own.
AGENT_CFLAGS += -isystem $(FRIDA_SDK)/include \
-I$(FRIDA_SDK)/include/glib-2.0 \
-I$(FRIDA_SDK)/lib/glib-2.0/include \
-I$(FRIDA_SDK)/include/capstone \
-I$(FRIDA_SDK)/include/json-glib-1.0
# The soft-float SDK, whose libc is picolibc and whose compiler runtime is
# compiler-rt. GCC's own newlib and libgcc are hard-float and cannot be used here.
FRIDA_SDK ?= $(error Set FRIDA_SDK to a none-$(SDK_ARCH)-softfloat_nopic SDK prefix)
LIBM = $(FRIDA_SDK)/lib/libm.a
LIBBUILTINS = $(FRIDA_SDK)/lib/libclang_rt.builtins.a
# bindgen reads the devkit header through whatever this compiler reports as its
# include path, so it has to be one that knows where the SDK keeps its libc. It has
# to be clang either way: -mabi=aapcs-soft landed there in 19 and GCC has it at no
# version, and on x86_64 the soft-float ABI is reachable only as an -Xclang feature.
AGENT_CC ?= clang
PRELINKED := frida-agent-prelinked.o.bin
AGENT_LIB_SOFTFLOAT := $(AGENT_TARGET_DIR)/libfrida-barebone-agent-softfloat.a
# On arm64 the toolchain's own compiler_builtins is compiled for the ordinary AAPCS,
# where a double travels in d0: its __muldf3 opens with "fmov x14, d0". Everything
# else there is -mabi=aapcs-soft and passes that same double in x0. Whole-archiving
# the staticlib puts those definitions in front of the SDK's compiler-rt, which has
# the soft-float ones, and every double the C library computes then comes back
# garbage — quietly, since the arithmetic still runs. Drop them and let compiler-rt
# answer instead. x86_64 needs none of this: soft-float is part of the Rust target
# there, so the copy build-std compiles already passes doubles the way the C half
# expects.
ifeq ($(ARCH),arm64)
DROP_HARDFLOAT_BUILTINS = \
$(AGENT_AR) d $@ $$($(AGENT_NM) --print-armap $@ | sed -n 's/^__muldf3 in //p') && \
$(AGENT_AR) s $@
else
DROP_HARDFLOAT_BUILTINS = :
endif
# The agent puts malloc, the C library's locks and its process stubs on the kernel's
# own facilities, so picolibc's versions are duplicate definitions rather than
# fallbacks. Work out which members they live in instead of naming them: the set
# follows from what the agent defines, and picolibc moves things between releases.
LIBC := $(AGENT_TARGET_DIR)/libc-trimmed.a
ENTRY_POINTS := frida_agent_start frida_agent_stop \
gum_interceptor_obtain gum_interceptor_begin_transaction \
gum_interceptor_end_transaction gum_interceptor_replace \
gum_interceptor_revert
KEEP_GLOBAL := $(foreach s,$(ENTRY_POINTS),--keep-global-symbol=$(s))
# Two structures the reparenting path reaches into lost a member upstream, and
# neither change announces itself as anything frida-kmod.c could test for. The
# headers it is compiled against are the authority on both, so ask them.
FRIDA_KMOD_DEFS := \
$(if $(shell grep -l 'list_head[[:space:]]*thread_group;' \
$(KDIR)/include/linux/sched.h),-DFRIDA_HAVE_TASK_THREAD_GROUP) \
$(if $(shell grep -l 'spinlock_t[[:space:]]*lock;' \
$(KDIR)/include/linux/fs_struct.h),-DFRIDA_HAVE_FS_STRUCT_LOCK)
all: modules
# A module that dies in its init path is left wedged under its own name until the
# next boot, so bring-up wants a fresh one per attempt.
MODULE_NAME ?= frida-agent
modules: $(PRELINKED)
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) \
FRIDA_KMOD_DEFS="$(FRIDA_KMOD_DEFS)" MODULE_NAME=$(MODULE_NAME) modules
$(PRELINKED): $(AGENT_LIB_SOFTFLOAT) $(LIBC) prelink.lds
$(AGENT_LD) -r $(if $(AGENT_LD_EMULATION),-m $(AGENT_LD_EMULATION)) \
-T prelink.lds -o $@.tmp \
--whole-archive $(AGENT_LIB_SOFTFLOAT) --no-whole-archive \
--start-group \
$(GUMJS_DEVKIT_DIR)/libfrida-gumjs.a \
$(LIBC) \
$(LIBM) \
$(LIBBUILTINS) \
--end-group
$(AGENT_OBJCOPY) $(KEEP_GLOBAL) --strip-debug \
--remove-section=.llvmbc --remove-section=.llvmcmd $@.tmp $@
rm -f $@.tmp
$(AGENT_LIB_SOFTFLOAT): agent
cp $(AGENT_LIB) $@
$(DROP_HARDFLOAT_BUILTINS)
$(LIBC): export LC_ALL = C
$(LIBC): $(AGENT_LIB_SOFTFLOAT)
cp $(FRIDA_SDK)/lib/libc.a $@
$(AGENT_NM) --defined-only $(AGENT_LIB_SOFTFLOAT) \
| awk 'NF == 3 && $$2 ~ /^[TDBR]$$/ { print $$3 }' | sort -u > $@.agent-syms
$(AGENT_NM) --print-armap $@ | sed -n 's/^\([^ ]*\) in \(.*\)$$/\1 \2/p' | sort -u > $@.members
$(AGENT_AR) d $@ $$(join $@.agent-syms $@.members | awk '{ print $$2 }' | sort -u)
$(AGENT_AR) s $@
rm -f $@.agent-syms $@.members
agent:
cd $(AGENT_DIR) && RUSTC_BOOTSTRAP=1 RUSTFLAGS="$(AGENT_RUSTFLAGS)" \
CC_$(subst -,_,$(RUST_TARGET))="$(AGENT_CC) $(AGENT_CFLAGS)" \
cargo build --$(PROFILE) \
$(AGENT_TARGET_FLAGS) --features linux \
--target-dir $(AGENT_TARGET_DIR) $(AGENT_BUILD_STD)
clean:
$(MAKE) -C $(KDIR) $(if $(KOUT),O=$(KOUT)) M=$(CURDIR) ARCH=$(ARCH) clean
rm -f $(PRELINKED) $(PRELINKED).tmp $(AGENT_LIB_SOFTFLOAT) $(LIBC)
.PHONY: all modules agent clean
-291
View File
@@ -1,291 +0,0 @@
# Linux kernel module
The same agent that the Barebone backend injects into XNU from the outside, but
loaded from the inside as an ordinary kernel module.
## How it differs from the XNU flavor
| | XNU | Linux |
|-------------------|--------------------------------------------------|-------------------------------------------|
| Delivery | remote stub injects a freestanding ELF | `insmod frida-agent.ko` |
| Kernel primitives | addresses patched into `.kernel_addrs` by the host | direct calls into `frida-kmod.c` |
| Bootstrap config | GVariant blob written into guest memory | module parameters |
| Transport | virtio hostlink or vsock | `/dev/frida` character device |
| Page permissions | host rewrites descriptors over the hostlink | `set_memory_*()` |
| Writable alias | shadow copy, committed via physical-memory bridge | `vmap()` of the same pages |
| Symbols | table supplied by the host | kallsyms |
Because the Linux agent performs its own memory work, it never issues the
`RemapWritablePages`, `MemoryProtect` or `PatchCode` host RPCs. The wire protocol
is otherwise identical, so the host side sees the same `(yqv)` frames.
## Building
Four inputs: this repository, a GumJS devkit built for
`none-<arch>-softfloat_nopic`, the SDK it was built against, and the kernel the module is for. `Makefile`
covers x86_64 and arm64, and defaults to the architecture of the machine building.
Building for the running kernel is a good deal shorter than cross-building for a
device, so that walkthrough comes first; the arm64 one below it is the same steps
with the kernel obtained from elsewhere.
### Toolchain
rustup target add x86_64-unknown-none # or aarch64-unknown-none
rustup component add rust-src
sudo dnf install clang binutils kernel-devel-$(uname -r)
clang must be 19 or newer, since that is where `-mabi=aapcs-soft` landed; GCC does
not implement it at any version, nor does it reach x86_64's soft-float ABI. The
binutils are for the prelink, which reads the archive map that only GNU `nm` prints —
the LLVM one does not; cross-building wants the target's, natively the host's own
will do. The host compiler is for Cargo's own build scripts, which run on the machine
doing the building.
The commands below are run from the top of this repository, with `releng` checked
out — `git submodule update --init releng` if the clone did not recurse.
### For the running x86_64 kernel
No prebuilt bundles are published for `none-x86_64-softfloat_nopic`, so both inputs are
built locally, against the same SDK:
releng/deps.py build --bundle=sdk --host=none-x86_64-softfloat_nopic
mkdir -p ~/sdk-none-x86_64-softfloat_nopic
tar -C ~/sdk-none-x86_64-softfloat_nopic -xf deps/sdk-none-x86_64-softfloat_nopic.tar.xz
gum=subprojects/frida-gum
mkdir -p $gum/build/none-x86_64-softfloat_nopic
(cd $gum/build/none-x86_64-softfloat_nopic \
&& FRIDA_DEPS=$PWD/../../../../deps ../../configure \
--host=none-x86_64-softfloat_nopic \
--enable-gumjs \
--with-devkits=gumjs \
--with-devkit-symbol-scope=original \
&& make)
make -C src/barebone/agent/linux \
FRIDA_SDK=$HOME/sdk-none-x86_64-softfloat_nopic \
GUMJS_DEVKIT_DIR=$PWD/$gum/build/none-x86_64-softfloat_nopic/bindings/gumjs/devkit
Everything else — `KDIR`, the Rust target, the binutils, the kernel's own name for the
architecture — follows from the running kernel.
### For an arm64 device
Here there are published bundles to start from, and the kernel has to be assembled.
#### The devkit and the SDK
version=17.17.0
base=https://github.com/frida/frida/releases/download/$version
curl -LO $base/frida-gumjs-devkit-$version-none-arm64-softfloat_nopic.tar.xz
mkdir -p ~/gumjs-devkit
tar -C ~/gumjs-devkit -xf frida-gumjs-devkit-$version-none-arm64-softfloat_nopic.tar.xz
releng/deps.py sync sdk none-arm64-softfloat_nopic ~/sdk-none-arm64-softfloat_nopic
The SDK carries picolibc and the compiler-rt builtins the devkit expects to be linked
against, and `releng/deps.toml` pins which one. Mixing a devkit with an SDK built for
a different libc leaves undefined symbols at prelink time.
#### The kernel
Nothing here needs the device's own kernel to be built, but the config alone is not
enough either. `uname -r` ends in the GKI commit and the build number:
adb shell uname -r
6.1.145-android14-11-gc1de4747ac59-ab14219743
which is commit `c1de4747ac59` and build `14219743`. Three pieces are keyed to them:
commit=c1de4747ac59
build=14219743
ci=https://ci.android.com/builds/submitted/$build/kernel_aarch64/latest/raw
mkdir -p ~/kernel-prepared ~/kernel-source
curl -sSL $ci/modules_prepare_outdir.tar.gz | tar -xz -C ~/kernel-prepared
curl -sSL $ci/kernel_aarch64_Module.symvers -o ~/kernel-prepared/Module.symvers
curl -sSL https://android.googlesource.com/kernel/common/+archive/$commit.tar.gz \
| tar -xz -C ~/kernel-source
`modules_prepare_outdir.tar.gz` is the configured build tree with `scripts/` already
compiled as x86-64 binaries, so the kbuild half has to run on an x86-64 Linux host —
an arm64 one gets as far as `CC [M]` and then fails to run `fixdep`. `Module.symvers`
carries the CRCs `CONFIG_MODVERSIONS` checks. The top-level `Makefile` in that prepared tree is a stub
that includes the source's, hence both `KDIR` and `KOUT` below.
A different GKI build of the same branch does not substitute for these: vermagic is
compared verbatim, and it names the exact build.
#### Build
make -C src/barebone/agent/linux \
FRIDA_SDK=$HOME/sdk-none-arm64-softfloat_nopic \
GUMJS_DEVKIT_DIR=$HOME/gumjs-devkit \
AGENT_LD=aarch64-linux-gnu-ld \
AGENT_AR=aarch64-linux-gnu-ar \
AGENT_NM=aarch64-linux-gnu-nm \
AGENT_OBJCOPY=aarch64-linux-gnu-objcopy \
KDIR=$HOME/kernel-source \
KOUT=$HOME/kernel-prepared \
LLVM=1
`make` builds the Rust staticlib itself, prelinks it against the devkit and the SDK,
then hands the result to kbuild. The `AGENT_*` overrides are only needed because the
defaults name an `aarch64-none-elf-` GNU toolchain; drop them if that is what is
installed.
The prelink half runs anywhere; the kbuild half needs the x86-64 Linux host above.
Cross-building from macOS means running that step in a container holding the kernel
tree.
adb push src/barebone/agent/linux/frida-agent.ko /data/local/tmp/
## Loading
insmod frida-agent.ko
The module's init path returns as soon as the worker thread is running — watch
`dmesg` for `frida: listening on /dev/frida`. `rmmod frida-agent` unwinds in the
reverse order and does not return until the worker is off the module's text.
## Talking to it
The agent exposes `/dev/frida` and waits: one client at a time, `read`/`write` of the
same length-prefixed frames the other transports carry, with `poll` for readiness.
It never dials out, so there is no network namespace to get right and no ordering
requirement between loading the module and attaching a client.
On Android the node is created by devtmpfs without a policy-specific label, so an
SELinux-enforcing system needs the client's domain granted access to it before it can
open the device. A socket-based transport was the other option considered and is a
worse fit: `AF_VSOCK` only reaches a peer when a hypervisor is on the other side, and
having the kernel connect out to loopback needs something already listening before
the module loads.
### Reaching it from a host
Serve the Barebone device from a frida-server running on the target, so the `open()`
happens next to the device node and the host talks to an ordinary frida-server:
FRIDA_BAREBONE_CONFIG=/data/local/tmp/linux-kmod.json \
frida-server --device barebone -l 127.0.0.1:27042
`etc/linux-kmod.json` is that config. From the host, with `adb forward tcp:27042
tcp:27042` in place:
frida -H 127.0.0.1:27042 -p 0
## Injecting into userspace
Loaded on a live system the module doubles as a ptrace-free injector for ordinary
processes — how the Linux local backend reaches its targets when the module is
present, falling back to `ptrace` when it is not. There is no second device node;
the channel is `prctl()` with a magic option and a token in the fifth argument,
prctl(0x46524944, op, arg1, arg2, 0x1d5f9e6b2c7a4038)
and any call whose option or token does not match falls through to the real
prctl, so probing for the module looks like a stock kernel. `op` 32 pings and
returns the magic.
Allocate, free, read, write and spawn — the primitives the injector drives
against a target pid — require `CAP_SYS_ADMIN`. A spawned thread is a real
`CLONE_THREAD` sibling that adopts the target's mm, files, credentials,
namespaces, fs and SysV semaphore undo list, so it is a native member of the
group rather than a kernel thread wearing its address space.
Cloaking is Gum's own registry mirrored into the kernel so it holds against
`/proc` too: the module intercepts the `maps`, `smaps`, task and status readers
and drops cloaked threads, ranges and fds while keeping the counts consistent. A
process cloaks its own resources with the token alone — no capability — so an
injected agent self-cloaks by forwarding Gum's updates over the same channel,
and the loader adds the footprint the agent itself never sees. A cloaked thread
still sees through the cloak when it reads `/proc`, so the agent is not blind to
its own process.
## What the target kernel has to provide
- **x86_64 or arm64.** The register-level pieces — the page size, cache maintenance,
where the syscall ABI leaves prctl()'s arguments, and how a spawned thread's
registers are set up for its return to userspace — have a half for each. The XNU
flavor remains arm64-only.
- **`CONFIG_KPROBES` and `CONFIG_KALLSYMS_ALL`.** A good deal of what the shim
needs is compiled in but not exported — `kallsyms_lookup_name`,
`kallsyms_on_each_symbol` and `set_memory_*` all are, and GKI trims its export
table to the KMI symbol list on top of that. The shim resolves them by name
through a kprobe, which is the standard way back in. Without kallsyms the module
still loads but cannot look up symbols or change page permissions.
- **Unsigned modules.** `CONFIG_MODULE_SIG_FORCE` must be off. Android's
`CONFIG_MODULE_SIG_PROTECT` is fine: it only demands signatures for modules on
the GKI list.
## Kernel ABI constraints
A hardened kernel imposes an ABI on anything linked into it, and the FP constraint
below is why this flavor is built against a soft-float SDK
(`none-arm64-softfloat_nopic`, `none-x86_64-softfloat_nopic`) rather than an ordinary
bare-metal one. The remaining constraints are per-architecture.
- **FP/SIMD.** JavaScript numbers are doubles, so a hardfloat build would use FP
for as long as the runtime lives, and neither architecture lets a kernel thread
hold it for free — x86 wants every stretch bracketed by `kernel_fpu_begin()`, and
on arm64 kernels before 6.9 there is no correct way to hold it at all:
`kernel_neon_begin()` runs with preemption disabled so we cannot sleep under it,
and the scheduler does not save a kernel thread's FP registers — live values are
lost on preemption and the FP state of whichever user task was interrupted is
corrupted. Building everything soft-float removes the question rather than
answering it: `-mabi=aapcs-soft -mgeneral-regs-only` on arm64, and on x86_64
LLVM's `+soft-float`, which is also what Rust's own `x86_64-unknown-none` turns
on, so the two halves agree on where a double travels. `-mno-sse` alone does not
reach the ABI; and `long double` has to be narrowed to 64 bits with
`-mlong-double-64`, since soft-float has no lowering for x87's 80-bit format and
LLVM crashes rather than calling out to a builtin.
- **Indirect branch tracking (x86_64).** With `CONFIG_X86_KERNEL_IBT` an indirect
call landing on anything but an `endbr64` faults, so everything linked in is built
`-fcf-protection=branch` and the Rust half `-Zcf-protection=branch`.
- **Kernel code model and red zone (x86_64).** Modules are loaded into the top 2GB
of the address space and their relocations are resolved as if by `-mcmodel=kernel`,
and there is no red zone below the stack pointer for an interrupt to step on. Both
come with the Rust target; the C half is told explicitly.
- **objtool (x86_64).** It validates every object entering a module and builds its
ORC unwind tables, and was not written to read a JavaScript runtime linked against
a freestanding libc. `Kbuild` marks the prelinked blob `OBJECT_FILES_NON_STANDARD`.
- **Shadow call stack (arm64).** With `CONFIG_SHADOW_CALL_STACK` the kernel reserves x18, so
the SDK and Gum are built `-ffixed-x18` and the Rust half with `-Zfixed-x18`.
Anything linked in that uses x18 as a scratch register overwrites the kernel's SCS
pointer, and the thread then returns through whatever that address happens to hold.
The Rust flag only reaches crates Cargo compiles, so `core`, `alloc` and
`compiler_builtins` — which the toolchain ships prebuilt, and which between them
account for every x18 write that used to reach the module — are rebuilt from source
via `-Z build-std`. That needs `rustup component add rust-src`.
## The soft-float libc
The libc is picolibc and the compiler runtime is compiler-rt, both from the
soft-float SDK that `FRIDA_SDK` points at. `Makefile` trims a copy of its
`libc.a` before the prelink: the agent implements `malloc`, the C library's locks and
its process stubs over the kernel's own facilities, so picolibc's are duplicate
definitions rather than fallbacks. The set is derived from what the agent defines
rather than listed, since picolibc moves things between releases.
On arm64 those compiler-rt builtins are load-bearing in a way that is easy to miss.
Rust's toolchain carries its own `compiler_builtins`, compiled for the ordinary AAPCS
where a double travels in `d0`, and the prelink whole-archives the Rust staticlib — so
without the filtering step in `Makefile` those definitions answer the C library's calls
to `__muldf3` and friends, which pass the same double in `x0`. Nothing crashes. The
arithmetic simply returns garbage, and the first thing to notice is GLib deciding a
hash table need not grow. x86_64 needs none of that filtering: soft-float is part of
the Rust target there, so the copy `-Z build-std` compiles already agrees with the C
half.
## What a script may look like
The runtime compiles scripts on a kernel thread's stack, and QuickJS parses
expressions by recursion, so the ceiling is nesting rather than length: roughly
fourteen levels of parentheses or fifteen of object literals on arm64 Linux, whose
kernel stack is 16 KiB — as is x86_64's. A script that exceeds it fails to compile rather than taking
anything down, though the message is not always `stack overflow` — an over-deep
`if`/`else` chain reports `expecting ';'`. Long scripts are fine; deep ones are not.
File diff suppressed because it is too large. Load diff
-8
View File
@@ -1,8 +0,0 @@
SECTIONS
{
.init_array 0 : {
_agent_init_start = .;
*(.init_array*)
_agent_init_end = .;
}
}
-4
View File
@@ -1,4 +0,0 @@
/frida-agent
/frida-agent.kext/
/frida-kext.o
/frida-signing.o
-37
View File
@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>frida-agent</string>
<key>CFBundleIdentifier</key>
<string>re.frida.agent</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>frida-agent</string>
<key>CFBundlePackageType</key>
<string>KEXT</string>
<key>CFBundleShortVersionString</key>
<string>@FRIDA_VERSION@</string>
<key>CFBundleVersion</key>
<string>@BUNDLE_VERSION@</string>
<key>OSBundleRequired</key>
<string>Root</string>
<key>OSBundleLibraries</key>
<dict>
<key>com.apple.kpi.bsd</key>
<string>12.0</string>
<key>com.apple.kpi.libkern</key>
<string>12.0</string>
<key>com.apple.kpi.iokit</key>
<string>12.0</string>
<key>com.apple.kpi.mach</key>
<string>12.0</string>
<key>com.apple.kpi.unsupported</key>
<string>12.0</string>
</dict>
</dict>
</plist>
-88
View File
@@ -1,88 +0,0 @@
PROFILE ?= release
ARCH ?= arm64e
ifeq ($(ARCH),arm64e)
RUST_TARGET ?= arm64e-apple-darwin
CLANG_TARGET ?= arm64e-apple-macos11
else ifeq ($(ARCH),arm64)
RUST_TARGET ?= aarch64-apple-darwin
CLANG_TARGET ?= arm64-apple-macos11
else
RUST_TARGET ?= x86_64-apple-darwin
CLANG_TARGET ?= x86_64-apple-macos11
endif
AGENT_DIR := $(CURDIR)/..
AGENT_TARGET_DIR := $(AGENT_DIR)/target-xnu-kext
AGENT_LIB := $(AGENT_TARGET_DIR)/$(RUST_TARGET)/$(PROFILE)/libfrida_barebone_agent.a
SDK ?= $(shell xcrun --sdk macosx --show-sdk-path)
KERNEL_HEADERS := $(SDK)/System/Library/Frameworks/Kernel.framework/Headers
FRIDA_VERSION := $(or $(FRIDA_VERSION),0.0.0)
BUNDLE_VERSION := $(firstword $(subst -, ,$(FRIDA_VERSION)))
FRIDA_SDK ?= $(error Set FRIDA_SDK to a macos-$(ARCH)-kernel SDK prefix)
GUMJS_DEVKIT_DIR ?= $(error Set GUMJS_DEVKIT_DIR to the directory holding libfrida-gumjs.a)
AGENT_RUSTFLAGS ?= -Cembed-bitcode=no
AGENT_BUILD_STD ?= -Z build-std=core,alloc,compiler_builtins
GLUE_CFLAGS ?= -target $(CLANG_TARGET) -isysroot $(SDK) -I$(KERNEL_HEADERS) \
-mkernel -fno-builtin -fno-common -nostdinc -std=gnu11 \
-DKERNEL -DKERNEL_PRIVATE -DAPPLE -D__MACHO__=1
AGENT_CC ?= clang -target $(CLANG_TARGET) -mkernel -nostdlibinc \
-isystem $(FRIDA_SDK)/include \
-I$(FRIDA_SDK)/include/glib-2.0 \
-I$(FRIDA_SDK)/lib/glib-2.0/include \
-I$(FRIDA_SDK)/include/capstone \
-I$(FRIDA_SDK)/include/json-glib-1.0
stamp_version = sed -e 's/@FRIDA_VERSION@/$(FRIDA_VERSION)/' \
-e 's/@BUNDLE_VERSION@/$(BUNDLE_VERSION)/' Info.plist
BUNDLE := frida-agent.kext
BINARY := $(BUNDLE)/Contents/MacOS/frida-agent
all: $(BUNDLE)
skeleton: frida-kext.o Info.plist
mkdir -p $(BUNDLE)/Contents/MacOS
$(stamp_version) > $(BUNDLE)/Contents/Info.plist
$(shell xcrun --find ld) -kext -arch $(ARCH) -o $(BINARY) frida-kext.o
$(BUNDLE): frida-agent Info.plist
mkdir -p $(BUNDLE)/Contents/MacOS
$(stamp_version) > $(BUNDLE)/Contents/Info.plist
cp frida-agent $(BINARY)
frida-agent: frida-kext.o frida-signing.o agent
$(shell xcrun --find ld) -kext -arch $(ARCH) -o $@ \
frida-kext.o \
frida-signing.o \
$(AGENT_LIB) \
$(GUMJS_DEVKIT_DIR)/libfrida-gumjs.a \
$(FRIDA_SDK)/lib/libc.a \
$(FRIDA_SDK)/lib/libm.a \
$(FRIDA_SDK)/lib/libclang_rt.builtins.a
frida-kext.o: frida-kext.c
$(shell xcrun --find clang) $(GLUE_CFLAGS) -c $< -o $@
frida-signing.o: frida-signing.c
$(AGENT_CC) -I$(GUMJS_DEVKIT_DIR) -c $< -o $@
agent:
cd $(AGENT_DIR) && RUSTC_BOOTSTRAP=1 RUSTFLAGS="$(AGENT_RUSTFLAGS)" \
CC_$(subst -,_,$(RUST_TARGET))="$(AGENT_CC)" \
GUMJS_DEVKIT_DIR="$(GUMJS_DEVKIT_DIR)" \
cargo build --$(PROFILE) \
--target $(RUST_TARGET) --features xnu-kext \
--target-dir $(AGENT_TARGET_DIR) $(AGENT_BUILD_STD)
clean:
rm -rf $(BUNDLE) frida-agent frida-kext.o frida-signing.o
.PHONY: all agent clean skeleton
-122
View File
@@ -1,122 +0,0 @@
# The agent as a kext
The XNU flavour of the barebone agent is ordinarily a blob: a host with a debugger writes it into
a running kernel, tells it where that kernel keeps the things it calls, and starts it. The Linux
flavour can also be built as a module the kernel loads (`../linux`), and this is the same thing for
XNU -- a kext macOS loads.
What the two packagings share is everything the agent does. What differs is how it is put there,
how it is told about the kernel around it, and how the host reaches it.
## Building
Three things, in order. First an SDK for this kernel -- picolibc, GLib, QuickJS, capstone and the
rest, built for `macos-arm64-kernel`:
cd frida-core
python3 -m releng.deps build --host macos-arm64-kernel
That fetches the same sources every other flavour is built from, six of them pinned to a commit
that says what a Darwin kernel is not; see below. Then a GumJS devkit against it:
cd frida-gum
./configure --host=macos-arm64-kernel --enable-gumjs --disable-v8 --with-devkits=gumjs
make
Then the kext itself:
make FRIDA_SDK=.../deps/sdk-macos-arm64-kernel \
GUMJS_DEVKIT_DIR=.../frida-gum/build/bindings/gumjs/devkit
which builds the agent as a static library for the kernel's ABI, `frida-kext.c` against the Kernel
framework's headers, and links the two with the devkit into `frida-agent.kext`.
`make skeleton` builds the glue alone, which is enough to check the pipeline without the rest.
## How it is told about the kernel
A kext may call what the kernel exports to kexts, and the agent calls a great deal more than that:
`proc_find` and `copyin` are exported, `task_threads` and `mach_vm_allocate` are not. So the
addresses arrive the way they do for the blob -- worked out by whoever knows this kernel -- and are
written in through `/dev/frida` before the agent is started:
ioctl(fd, FRIDA_IOC_SET_ADDR, &(FridaAddrRequest){ .name = "task_threads", .address = ... });
...
ioctl(fd, FRIDA_IOC_START);
A name nothing writes stays zero, and the agent already answers for that: every one of them is an
`Option` on the Rust side, and what needs it says so rather than calling through a null.
Once started, the same device carries the frames: read what the agent says, write what the host
says, framed with a four-byte little-endian length exactly as on every other transport.
## What the sources needed
An SDK for a Darwin kernel is a target nothing here had been built for, and six of the sources say
so. Each is a commit of its own on the fork, a few lines apiece:
- **picolibc** -- Mach-O has no `__attribute__((alias))`, so the aliasing macros fall through to
the assembler ones it already carries for Mach-O, and the names are spent before being spelled.
Its aarch64 assembly is written for ELF and its C stubs answer for it instead. The interrupt
vectors and the `exec` family are not built: one owns a machine this does not, the other crashes
the compiler.
- **compiler-rt** -- `apple_versioning.c` is stubs for macOS releases long past, written against a
header only userspace has.
- **libffi** -- the trampoline table is built out of what userspace has of Mach; whether that is
there is now asked of the header rather than of the system's name.
- **QuickJS**, **tinycc** and **libiconv** -- all three ask what to do on Apple by asking whether
they are on Apple. Where that means userspace -- a userspace malloc's header, a cache-control
header, a locale one -- they now ask whether they are freestanding as well.
## Which architecture, and where pointers are signed
Apple silicon runs an arm64e kernel and loads nothing else, so that is what `ARCH` defaults to,
and the SDK and the devkit are built for `macos-arm64e-kernel` to match.
Pointer authentication is the thing to get right there, and what makes it awkward is that Rust's
`arm64e-apple-darwin` is a tag rather than an ABI: it neither signs a function pointer nor
authenticates one.
_pick: adrp x0, 0 ; add x0, x0, #0x0 ; ret <- no signing
_call: mov x2, x0 ; mov x0, x1 ; br x2 <- no authenticating
So the line is drawn where the kernel is, and not inside:
- **Ourselves.** GLib, GumJS and the agent are built `-fno-ptrauth-calls`, so the C half calls a
Rust callback the way Rust hands it over -- plainly. Nothing there signs, so nothing there is
disappointed. `otool` says as much: not one `blraa` outside `__auth_stubs`.
- **The kernel calling us.** The glue keeps the kernel's own ABI, so the device table it registers
is written with `AUTH` relocations and the loader signs it as the kernel expects.
- **Us calling the kernel.** Through `__auth_stubs`, which the loader fills the same way.
- **Us handing the kernel a function to call.** A thread's entry is the one that matters, and the
agent signs it itself, as it already does for the kernel it is injected into. What it signs with
is no longer written down twice: the glue derives the discriminator from the kernel's own
headers with `ptrauth_type_discriminator`, and asserts that it is the number the blob flavour
carries -- 0xd507 for a `thread_continue_t`, which is what it was.
What this does not buy is pointer authentication *within* the kext, which is protection given up
rather than correctness lost. Getting it back wants rustc to grow the ABI, or every callback
wrapped in a C thunk.
None of this touches x86_64, which has no pointer authentication at all: an Intel kext wants only
its own SDK built the same way.
## Floating point
Every other kernel flavour is built soft-float, since a kernel does not save the floating-point
registers across a trap. This one cannot be: Darwin has no soft-float C ABI on either
architecture. arm64 refuses the type outright without one -- `ABI 'darwinpcs' does not support
it` -- and `-mabi=aapcs-soft` alongside it crashes both Apple's clang and upstream LLVM on the
first varargs function taking a double; x86_64 wants SSE to return one. A script engine is nothing
but doubles, so the floating-point registers stay in play, and what that costs at run time is a
thing to answer on the machine.
## Putting copies into processes
Not there yet. The blob's linker script marks out its own writable half and what a copy of it has
to relocate; a kext is laid out by the kernel's loader instead, so the glue says there is nothing
to copy from. Everything the agent does in the kernel itself is unaffected.
Carrying the blob's ELF and placing it the way the host does is not a way round that, either: on a
machine with SPTM, memory a kext allocates cannot be made executable, while a kext's own text is
mapped executable by the kernel's loader because it was signed.
-447
View File
@@ -1,447 +0,0 @@
#include <libkern/libkern.h>
#include <mach/kmod.h>
#include <mach/mach_types.h>
#include <miscfs/devfs/devfs.h>
#include <sys/conf.h>
#include <sys/errno.h>
#include <sys/ioccom.h>
#include <sys/malloc.h>
#include <sys/proc.h>
#include <sys/random.h>
#include <sys/uio.h>
#include <kern/locks.h>
#include <kern/thread.h>
#include <ptrauth.h>
extern int frida_agent_start (uint64_t own_base, uint64_t own_size);
extern void frida_agent_stop (void);
extern void frida_agent_wake (void);
#define FRIDA_KERNEL_ADDRS(X) \
X (panic) \
X (kalloc) X (kfree) X (kalloc_data) X (kfree_data) \
X (proc_iterate) X (proc_find) X (proc_rele) X (proc_pid) X (proc_best_name) X (proc_task) \
X (current_task) X (current_thread) X (get_task_map) X (get_bsdtask_info) \
X (mach_vm_allocate) X (mach_vm_deallocate) X (mach_vm_protect) X (mach_vm_remap) \
X (kernel_map) X (kernel_pmap) X (pmap_find_phys) X (ml_static_ptovirt) \
X (mach_vm_region) X (mach_vm_region_recurse) \
X (vm_map_copyin) X (vm_map_copyout) X (vm_map_wire_external) \
X (thread_create) X (thread_set_state) X (thread_resume) X (thread_suspend) \
X (thread_terminate) X (kernel_thread_start) \
X (task_suspend) X (task_resume) X (task_clear_return_wait) X (task_threads) X (task_info) \
X (task_restartable_ranges_register) \
X (assert_wait) X (assert_wait_timeout) X (thread_block) X (thread_wakeup) \
X (thread_wakeup_prim) X (wakeup) X (ulock_wake) \
X (mach_absolute_time) X (absolutetime_to_nanoseconds) X (clock_get_calendar_microtime) \
X (ml_io_map) X (ml_vtophys) X (ml_static_vtop) \
X (copyin) X (copyout) X (sysent) X (IOLog) \
X (convert_port_to_thread) X (convert_port_to_thread_read) \
X (convert_port_to_thread_inspect) \
X (sock_socket) X (sock_connect) X (sock_send) X (sock_receive) X (sock_close)
#define FRIDA_DEFINE_SLOT(name) void * _##name;
FRIDA_KERNEL_ADDRS (FRIDA_DEFINE_SLOT)
#undef FRIDA_DEFINE_SLOT
typedef struct _FridaKernelAddr FridaKernelAddr;
struct _FridaKernelAddr
{
const char * name;
void ** slot;
};
static const FridaKernelAddr frida_kernel_addrs[] = {
#define FRIDA_LIST_SLOT(name) { #name, &_##name },
FRIDA_KERNEL_ADDRS (FRIDA_LIST_SLOT)
#undef FRIDA_LIST_SLOT
};
extern void * frida_kext_init_start __asm ("section$start$__DATA$__mod_init_func");
extern void * frida_kext_init_end __asm ("section$end$__DATA$__mod_init_func");
const uintptr_t frida_agent_init_start = (uintptr_t) &frida_kext_init_start;
const uintptr_t frida_agent_init_end = (uintptr_t) &frida_kext_init_end;
const uintptr_t frida_agent_private_start = 0;
const uintptr_t frida_agent_heap_start = 0;
const uintptr_t frida_agent_relocs_start = 0;
const uintptr_t frida_agent_relocs_end = 0;
typedef int (* FridaProcCallout) (proc_t process, void * argument);
const unsigned frida_agent_disc_thread_continue =
ptrauth_type_discriminator (thread_continue_t);
const unsigned frida_agent_disc_proc_callout =
ptrauth_type_discriminator (FridaProcCallout);
_Static_assert (ptrauth_type_discriminator (thread_continue_t) == 0xd507,
"the agent signs a thread's entry with 0xd507");
int
getentropy (void * buffer, size_t size)
{
read_random (buffer, (u_int) size);
return 0;
}
void
sys_icache_invalidate (void * start, size_t size)
{
flush_dcache ((vm_offset_t) start, (unsigned) size, FALSE);
invalidate_icache ((vm_offset_t) start, (unsigned) size, FALSE);
}
#define FRIDA_IOC_SET_ADDR _IOW ('F', 1, FridaAddrRequest)
#define FRIDA_IOC_START _IO ('F', 2)
#define FRIDA_IOC_GET_IMAGE _IOR ('F', 3, FridaImageInfo)
typedef struct _FridaAddrRequest FridaAddrRequest;
typedef struct _FridaImageInfo FridaImageInfo;
struct _FridaAddrRequest
{
char name[64];
uint64_t address;
};
struct _FridaImageInfo
{
uint64_t base;
uint64_t size;
};
#define FRIDA_LINK_ROOM (1024 * 1024)
typedef struct _FridaLink FridaLink;
struct _FridaLink
{
lck_mtx_t * lock;
bool open;
uint8_t * to_host;
size_t to_host_len;
uint8_t * from_host;
size_t from_host_len;
};
static FridaLink frida_link;
static lck_grp_t * frida_lock_group;
static int frida_dev_open (dev_t dev, int flags, int devtype, struct proc * p);
static int frida_dev_close (dev_t dev, int flags, int devtype, struct proc * p);
static int frida_dev_read (dev_t dev, struct uio * uio, int ioflag);
static int frida_dev_write (dev_t dev, struct uio * uio, int ioflag);
static int frida_dev_ioctl (dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc * p);
static struct cdevsw frida_cdevsw = {
.d_open = frida_dev_open,
.d_close = frida_dev_close,
.d_read = frida_dev_read,
.d_write = frida_dev_write,
.d_ioctl = frida_dev_ioctl,
.d_stop = eno_stop,
.d_reset = eno_reset,
.d_ttys = NULL,
.d_select = eno_select,
.d_mmap = eno_mmap,
.d_strategy = eno_strat,
.d_reserved_1 = eno_getc,
.d_reserved_2 = eno_putc,
.d_type = 0
};
static int frida_major = -1;
static void * frida_node = NULL;
static bool frida_running = false;
static uint64_t frida_own_base = 0;
static uint64_t frida_own_size = 0;
kern_return_t
frida_kext_start (kmod_info_t * ki, void * d)
{
frida_own_base = ki->address;
frida_own_size = ki->size;
frida_lock_group = lck_grp_alloc_init ("frida", LCK_GRP_ATTR_NULL);
if (frida_lock_group == NULL)
return KERN_FAILURE;
frida_link.lock = lck_mtx_alloc_init (frida_lock_group, LCK_ATTR_NULL);
frida_major = cdevsw_add (-1, &frida_cdevsw);
if (frida_major == -1)
return KERN_FAILURE;
frida_node = devfs_make_node (makedev (frida_major, 0), DEVFS_CHAR, UID_ROOT, GID_WHEEL, 0600,
"frida");
if (frida_node == NULL)
{
cdevsw_remove (frida_major, &frida_cdevsw);
frida_major = -1;
return KERN_FAILURE;
}
return KERN_SUCCESS;
}
kern_return_t
frida_kext_stop (kmod_info_t * ki, void * d)
{
if (frida_running)
{
frida_agent_stop ();
frida_running = false;
}
if (frida_node != NULL)
{
devfs_remove (frida_node);
frida_node = NULL;
}
if (frida_major != -1)
{
cdevsw_remove (frida_major, &frida_cdevsw);
frida_major = -1;
}
return KERN_SUCCESS;
}
KMOD_EXPLICIT_DECL (re.frida.agent, "1.0", frida_kext_start, frida_kext_stop)
static int
frida_dev_open (dev_t dev, int flags, int devtype, struct proc * p)
{
int result = 0;
lck_mtx_lock (frida_link.lock);
if (frida_link.open)
{
result = EBUSY;
}
else
{
frida_link.to_host = _MALLOC (FRIDA_LINK_ROOM, M_TEMP, M_WAITOK);
frida_link.from_host = _MALLOC (FRIDA_LINK_ROOM, M_TEMP, M_WAITOK);
if (frida_link.to_host != NULL && frida_link.from_host != NULL)
{
frida_link.to_host_len = 0;
frida_link.from_host_len = 0;
frida_link.open = true;
}
else
{
result = ENOMEM;
}
}
lck_mtx_unlock (frida_link.lock);
return result;
}
static int
frida_dev_close (dev_t dev, int flags, int devtype, struct proc * p)
{
lck_mtx_lock (frida_link.lock);
frida_link.open = false;
if (frida_link.to_host != NULL)
{
_FREE (frida_link.to_host, M_TEMP);
frida_link.to_host = NULL;
}
if (frida_link.from_host != NULL)
{
_FREE (frida_link.from_host, M_TEMP);
frida_link.from_host = NULL;
}
wakeup (&frida_link);
lck_mtx_unlock (frida_link.lock);
return 0;
}
static int
frida_dev_read (dev_t dev, struct uio * uio, int ioflag)
{
int result = 0;
lck_mtx_lock (frida_link.lock);
while (frida_link.open && frida_link.to_host_len == 0)
{
if (msleep (&frida_link, frida_link.lock, PCATCH, "frida", NULL) != 0)
{
lck_mtx_unlock (frida_link.lock);
return EINTR;
}
}
if (frida_link.to_host_len != 0)
{
size_t n = MIN ((size_t) uio_resid (uio), frida_link.to_host_len);
result = uiomove ((const char *) frida_link.to_host, (int) n, uio);
if (result == 0)
{
memmove (frida_link.to_host, frida_link.to_host + n, frida_link.to_host_len - n);
frida_link.to_host_len -= n;
}
}
lck_mtx_unlock (frida_link.lock);
return result;
}
static int
frida_dev_write (dev_t dev, struct uio * uio, int ioflag)
{
size_t n;
int result;
lck_mtx_lock (frida_link.lock);
n = MIN ((size_t) uio_resid (uio), FRIDA_LINK_ROOM - frida_link.from_host_len);
if (n == 0)
{
lck_mtx_unlock (frida_link.lock);
return ENOBUFS;
}
result = uiomove ((char *) frida_link.from_host + frida_link.from_host_len, (int) n, uio);
if (result == 0)
frida_link.from_host_len += n;
lck_mtx_unlock (frida_link.lock);
if (result == 0 && frida_running)
frida_agent_wake ();
return result;
}
int
frida_kmod_link_open (void)
{
return frida_link.open ? 0 : -1;
}
void
frida_kmod_link_close (void)
{
}
int
frida_kmod_link_send (const void * data, size_t size)
{
lck_mtx_lock (frida_link.lock);
if (!frida_link.open || frida_link.to_host_len + size > FRIDA_LINK_ROOM)
{
lck_mtx_unlock (frida_link.lock);
return -1;
}
memcpy (frida_link.to_host + frida_link.to_host_len, data, size);
frida_link.to_host_len += size;
wakeup (&frida_link);
lck_mtx_unlock (frida_link.lock);
return 0;
}
bool
frida_kmod_link_pending (void)
{
bool pending;
lck_mtx_lock (frida_link.lock);
pending = frida_link.from_host_len != 0;
lck_mtx_unlock (frida_link.lock);
return pending;
}
ssize_t
frida_kmod_link_recv (void * data, size_t size)
{
size_t n;
lck_mtx_lock (frida_link.lock);
n = MIN (size, frida_link.from_host_len);
if (n != 0)
{
memcpy (data, frida_link.from_host, n);
memmove (frida_link.from_host, frida_link.from_host + n, frida_link.from_host_len - n);
frida_link.from_host_len -= n;
}
lck_mtx_unlock (frida_link.lock);
return (ssize_t) n;
}
static int
frida_dev_ioctl (dev_t dev, u_long cmd, caddr_t data, int fflag, struct proc * p)
{
switch (cmd)
{
case FRIDA_IOC_SET_ADDR:
{
FridaAddrRequest * r = (FridaAddrRequest *) data;
size_t i;
r->name[sizeof (r->name) - 1] = '\0';
for (i = 0; i != sizeof (frida_kernel_addrs) / sizeof (frida_kernel_addrs[0]); i++)
{
if (strncmp (frida_kernel_addrs[i].name, r->name, sizeof (r->name)) == 0)
{
*frida_kernel_addrs[i].slot = (void *) (uintptr_t) r->address;
return 0;
}
}
return ENOENT;
}
case FRIDA_IOC_GET_IMAGE:
{
FridaImageInfo * info = (FridaImageInfo *) data;
info->base = frida_own_base;
info->size = frida_own_size;
return 0;
}
case FRIDA_IOC_START:
{
if (frida_running)
return EBUSY;
if (frida_agent_start (frida_own_base, frida_own_size) != 0)
return ENOMEM;
frida_running = true;
return 0;
}
default:
return ENOTTY;
}
}
-5
View File
@@ -1,5 +0,0 @@
#include <frida-gumjs.h>
#include <ptrauth.h>
_Static_assert (ptrauth_type_discriminator (__typeof__ (((GSourceFuncs *) 0)->prepare))
== 0xe4fe, "the kernel's own ABI still discriminates by type");
-154
View File
@@ -1,154 +0,0 @@
#!/usr/bin/env python3
"""Make a kernel the agent can write to and run code in."""
import struct
import sys
NOP = 0xD503201F
FORBIDS_BELOW = 0x3800
ALLOWS_BELOW = 0x2000
def main(source, destination):
image = bytearray(open(source, "rb").read())
secs = sections(image)
print("pages the pmap has locked down:")
locked = stop_refusing(image, secs, strings_saying(bytes(image), secs, b"locked down"),
"may be mapped")
print("what a table forbids underneath it:")
below = stop_forbidding_execution_below(image, secs)
print("executable mappings in the kernel's own pmap:")
executable = stop_refusing(image, secs,
strings_saying(bytes(image), secs, b"executable mapping"),
"may be made")
if not (locked and below and executable):
sys.exit("kernel does not look like one this tool knows")
open(destination, "wb").write(bytes(image))
print(f"\n{locked + below + executable} changes -> {destination}")
def stop_refusing(image, secs, saying, what):
undone = 0
for segment, name, addr, size, foff in secs:
if name != "__text":
continue
code = bytes(image[foff:foff + size])
blocks = {addr + b
for b in (start_of_block(code, s) for s in complaints_at(code, addr, saying))
if b is not None}
if not blocks:
continue
for where, at, word in branches_into(code, addr, blocks):
struct.pack_into("<I", image, foff + at, NOP)
print(f" {segment} {where:#x} {word:#010x} -> nop ({what})")
undone += 1
return undone
def stop_forbidding_execution_below(image, secs):
undone = 0
for segment, name, addr, size, foff in secs:
if name != "__text":
continue
code = bytes(image[foff:foff + size])
for i in range(0, len(code) - 4, 4):
word = struct.unpack_from("<I", code, i)[0]
builds_high = (word & 0xFF800000) == 0xF2800000 and ((word >> 21) & 3) == 3
if not builds_high or ((word >> 5) & 0xFFFF) != FORBIDS_BELOW:
continue
into = word & 0x1F
struct.pack_into("<I", image, foff + i, 0xF2E00000 | (ALLOWS_BELOW << 5) | into)
print(f" {segment} {addr + i:#x} {word:#010x} -> "
f"movk x{into}, #{ALLOWS_BELOW:#06x}, lsl 48"
f" (a table no longer forbids what is under it)")
undone += 1
return undone
def strings_saying(image, secs, wanted):
where = set()
at = image.find(wanted)
while at != -1:
start = image.rfind(b"\0", 0, at) + 1
for _, name, addr, size, foff in secs:
if name == "__cstring" and foff <= start < foff + size:
where.add(addr + (start - foff))
at = image.find(wanted, at + 1)
return where
def complaints_at(code, base, about):
said = []
for i in range(0, len(code) - 4, 4):
word = struct.unpack_from("<I", code, i)[0]
if (word & 0x9F000000) != 0x90000000:
continue
into = word & 0x1F
page = (((word >> 5) & 0x7FFFF) << 2) | ((word >> 29) & 3)
if page & (1 << 20):
page -= 1 << 21
page = ((base + i) & ~0xFFF) + (page << 12)
for j in range(i + 4, min(i + 48, len(code) - 4), 4):
after = struct.unpack_from("<I", code, j)[0]
if (after & 0xFF800000) == 0x91000000 and ((after >> 5) & 0x1F) == into:
if page + ((after >> 10) & 0xFFF) in about:
said.append(i)
break
return said
def start_of_block(code, at):
i = at
while i >= 4:
word = struct.unpack_from("<I", code, i - 4)[0]
if (word & 0xFC000000) in (0x94000000, 0x14000000):
return i
if (word & 0xFFFFFC1F) == 0xD65F0000 or word == 0xD65F0FFF:
return i
i -= 4
return None
def branches_into(code, base, blocks):
found = []
for i in range(0, len(code) - 4, 4):
word = struct.unpack_from("<I", code, i)[0]
if (word & 0xFF000010) == 0x54000000 or (word & 0x7E000000) == 0x34000000:
far, sign = (word >> 5) & 0x7FFFF, 1 << 18
elif (word & 0x7E000000) == 0x36000000:
far, sign = (word >> 5) & 0x3FFF, 1 << 13
else:
continue
if far & sign:
far -= sign << 1
if base + i + far * 4 in blocks:
found.append((base + i, i, word))
return found
def sections(image):
count = struct.unpack_from("<I", image, 16)[0]
at, found = 32, []
for _ in range(count):
kind, size = struct.unpack_from("<II", image, at)
if kind == 0x19:
how_many = struct.unpack_from("<I", image, at + 64)[0]
so = at + 72
for _ in range(how_many):
found.append((
image[so + 16:so + 32].rstrip(b"\0").decode(),
image[so:so + 16].rstrip(b"\0").decode(),
*struct.unpack_from("<QQ", image, so + 32),
struct.unpack_from("<I", image, so + 48)[0],
))
so += 80
at += size
return found
if len(sys.argv) != 3:
sys.exit(f"usage: {sys.argv[0]} <kernel> <patched-kernel>")
main(sys.argv[1], sys.argv[2])
+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": {}
}
}
+9 -7
View File
@@ -1,7 +1,7 @@
use core::ffi::c_void;
use crate::bindings::{gpointer, gsize, gum_try_mprotect, GumPageProtection};
use crate::{kernel, libc};
use crate::bindings::{gpointer, gsize, GumPageProtection};
use crate::{gum, libc, xnu};
const GUM_PAGE_READ: GumPageProtection = 1;
const GUM_PAGE_EXECUTE: GumPageProtection = 4;
@@ -10,7 +10,7 @@ const GUM_PAGE_EXECUTE: GumPageProtection = 4;
// static trampoline `text` made executable, followed by a writable parameter
// table `map_size` bytes later (the trampolines reach their parameters via that
// fixed offset). The two halves are whole pages, so flipping the first to RX
// leaves the parameter table writable.
// over the hostlink leaves the parameter table writable.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn ffi_tramp_embedder_map(
text: *const c_void,
@@ -19,7 +19,7 @@ pub unsafe extern "C" fn ffi_tramp_embedder_map(
parm_table: *mut *mut c_void,
) -> i32 {
unsafe {
let base = kernel::alloc_code(map_size * 2);
let base = xnu::kalloc(map_size * 2);
if base.is_null() {
return 0;
}
@@ -28,9 +28,9 @@ pub unsafe extern "C" fn ffi_tramp_embedder_map(
libc::__clear_cache(base, base.add(map_size));
let made_executable =
gum_try_mprotect(base as gpointer, map_size as gsize, GUM_PAGE_READ | GUM_PAGE_EXECUTE);
gum::gum_try_mprotect(base as gpointer, map_size as gsize, GUM_PAGE_READ | GUM_PAGE_EXECUTE);
if made_executable == 0 {
kernel::free_code(base, map_size * 2);
xnu::free(base, map_size * 2);
return 0;
}
@@ -46,5 +46,7 @@ pub unsafe extern "C" fn ffi_tramp_embedder_unmap(
_parm_table: *mut c_void,
map_size: usize,
) {
kernel::free_code(code_table as *mut u8, map_size * 2);
unsafe {
xnu::free(code_table as *mut u8, map_size * 2);
}
}
+25 -47
View File
@@ -1,68 +1,46 @@
use core::ptr;
use core::sync::atomic::{AtomicUsize, Ordering};
use crate::bindings::{g_wait_is_set, gint64, gpointer};
use crate::kernel;
use crate::xnu;
const G_WAIT_INFINITE: gint64 = -1;
pub static mut WAKEUP_TOKEN: u64 = 0;
pub fn wakeup_token() -> *const u8 {
ptr::addr_of!(WAKEUP_TOKEN) as *const u8
}
#[unsafe(no_mangle)]
pub extern "C" fn g_get_monotonic_time() -> gint64 {
kernel::monotonic_micros()
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 timeout = if timeout_us == G_WAIT_INFINITE {
None
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 {
Some(timeout_us as u64)
xnu::assert_wait_timeout(
wait_event,
xnu::THREAD_INTERRUPTIBLE,
(timeout_us * 1000) as u32,
1, // TODO
)
};
let ours = is_loop_thread();
let event = if ours {
LOOP_TOKEN.store(token as usize, Ordering::Release);
ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8
} else {
token as *const u8
};
kernel::wait(event, timeout, &mut || unsafe { g_wait_is_set(token) != 0 });
if ours {
LOOP_TOKEN.store(0, Ordering::Release);
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) {
kernel::wake(ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8);
kernel::wake(token as *const u8);
}
pub fn is_loop_thread() -> bool {
kernel::current_thread_id() == LOOP_THREAD.load(Ordering::Acquire) as u64
}
pub fn own_the_loop() {
LOOP_THREAD.store(kernel::current_thread_id() as usize, Ordering::Release);
}
static LOOP_THREAD: AtomicUsize = AtomicUsize::new(0);
static LOOP_TOKEN: AtomicUsize = AtomicUsize::new(0);
#[unsafe(no_mangle)]
pub extern "C" fn g_io_channel_unix_new(_fd: i32) -> gpointer {
ptr::null_mut()
}
#[unsafe(no_mangle)]
pub extern "C" fn g_spawn_async_with_pipes() -> i32 {
0
pub extern "C" fn g_wait_wake(_token: gpointer) {
xnu::thread_wakeup(ptr::addr_of_mut!(WAKEUP_TOKEN) as *const u8);
}
+76 -153
View File
@@ -8,39 +8,34 @@ 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, AtomicUsize, Ordering};
use core::sync::atomic::{AtomicU32, AtomicU64, Ordering};
// Our implementation structures that overlay the opaque GLib structures
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
#[repr(C, align(8))]
struct MutexImpl {
lock: AtomicU32,
_padding: [u8; core::mem::size_of::<GMutex>() - core::mem::size_of::<AtomicU32>()],
}
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
#[repr(C, align(8))]
struct RecMutexImpl {
owner: AtomicUsize, // Thread ID of the owner (uses the 'p' field)
owner: AtomicU64, // Thread ID of the owner (uses the 'p' field)
count: AtomicU32, // Recursion count (uses i[0])
_unused: u32, // Unused (uses i[1])
}
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
#[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>()],
}
// A generation counter rather than a flag: a flag stays set once signalled, so every
// later wait returns at once and the caller's loop spins instead of blocking.
#[cfg_attr(target_pointer_width = "32", repr(C, align(4)))]
#[cfg_attr(target_pointer_width = "64", repr(C, align(8)))]
#[repr(C, align(8))]
struct CondImpl {
generation: AtomicU32,
signal: AtomicU32, // Signal state
_padding: [u8; core::mem::size_of::<GCond>() - core::mem::size_of::<AtomicU32>()],
}
@@ -149,9 +144,10 @@ pub extern "C" fn g_rec_mutex_clear(_rec_mutex: *mut GRecMutex) {
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_) {
if rec_mutex_try_acquire(impl_, current_thread) {
break;
}
rec_mutex_wait(impl_);
@@ -163,8 +159,9 @@ pub extern "C" fn g_rec_mutex_lock(rec_mutex: *mut GRecMutex) {
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_) {
if rec_mutex_try_acquire(impl_, current_thread) {
1
} else {
0
@@ -186,7 +183,7 @@ pub extern "C" fn g_rec_mutex_unlock(rec_mutex: *mut GRecMutex) {
.is_ok()
{
impl_.owner.store(0, Ordering::Relaxed);
wake_waiters(&impl_.count);
lock_notify_all();
break;
}
} else {
@@ -201,10 +198,7 @@ pub extern "C" fn g_rec_mutex_unlock(rec_mutex: *mut GRecMutex) {
}
}
// Asking the kernel who we are costs more than the uncontended acquisition itself, and
// every allocation takes this path, so the identity is only fetched once the lock turns
// out to be held.
unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl) -> bool {
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 {
@@ -212,12 +206,12 @@ unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl) -> bool {
.compare_exchange_weak(0, 1, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
impl_.owner.store(crate::kernel::current_thread_id() as usize, Ordering::Relaxed);
impl_.owner.store(current_thread, Ordering::Relaxed);
return true;
}
} else {
let current_owner = impl_.owner.load(Ordering::Relaxed);
if current_owner == crate::kernel::current_thread_id() as usize {
if current_owner == current_thread {
if impl_.count
.compare_exchange_weak(
current_count,
@@ -236,7 +230,7 @@ unsafe fn rec_mutex_try_acquire(impl_: &mut RecMutexImpl) -> bool {
unsafe fn rec_mutex_wait(impl_: &mut RecMutexImpl) {
while impl_.count.load(Ordering::Relaxed) != 0 {
wait_on(&impl_.count, &mut || impl_.count.load(Ordering::Relaxed) == 0);
unsafe { lock_wait_primitive() };
}
}
@@ -288,7 +282,7 @@ 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);
wake_waiters(&impl_.state);
lock_notify_all();
}
}
@@ -335,7 +329,7 @@ pub extern "C" fn g_rw_lock_reader_unlock(rw_lock: *mut GRWLock) {
.is_ok()
{
if current - 1 == 0 {
wake_waiters(&impl_.state);
lock_notify_all();
}
break;
}
@@ -361,15 +355,13 @@ unsafe fn rw_lock_reader_try_acquire(impl_: &mut RWLockImpl) -> bool {
unsafe fn rw_lock_wait_for_writers(impl_: &mut RWLockImpl) {
while impl_.state.load(Ordering::Relaxed) & RW_WRITER_LOCK_BIT != 0 {
wait_on(&impl_.state, &mut || {
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 {
wait_on(&impl_.state, &mut || impl_.state.load(Ordering::Relaxed) == 0);
unsafe { lock_wait_primitive() };
}
}
@@ -377,7 +369,7 @@ unsafe fn rw_lock_wait_for_all(impl_: &mut RWLockImpl) {
pub extern "C" fn g_cond_init(cond: *mut GCond) {
unsafe {
let impl_ = cond_impl(cond);
impl_.generation.store(0, Ordering::Relaxed);
impl_.signal.store(0, Ordering::Relaxed);
}
}
@@ -387,17 +379,11 @@ 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 {
// Read the generation under the mutex, so a signal that lands after the
// unlock below is still observed as a change.
let seen = cond_impl(cond).generation.load(Ordering::Acquire);
g_mutex_unlock(mutex);
let impl_ = cond_impl(cond);
while impl_.generation.load(Ordering::Acquire) == seen {
wait_on(&impl_.generation, &mut || {
impl_.generation.load(Ordering::Acquire) != seen
});
while impl_.signal.load(Ordering::Acquire) == 0 {
lock_wait_primitive();
}
g_mutex_lock(mutex);
@@ -408,8 +394,8 @@ pub extern "C" fn g_cond_wait(cond: *mut GCond, mutex: *mut GMutex) {
pub extern "C" fn g_cond_signal(cond: *mut GCond) {
unsafe {
let impl_ = cond_impl(cond);
impl_.generation.fetch_add(1, Ordering::Release);
wake_waiters(&impl_.generation);
impl_.signal.store(1, Ordering::Release);
lock_notify_all();
}
}
@@ -417,81 +403,22 @@ pub extern "C" fn g_cond_signal(cond: *mut GCond) {
pub extern "C" fn g_cond_broadcast(cond: *mut GCond) {
unsafe {
let impl_ = cond_impl(cond);
impl_.generation.fetch_add(1, Ordering::Release);
wake_waiters(&impl_.generation);
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 {
unsafe {
let seen = cond_impl(cond).generation.load(Ordering::Acquire);
g_mutex_unlock(mutex);
let impl_ = cond_impl(cond);
let mut signalled = impl_.generation.load(Ordering::Acquire) != seen;
while !signalled && crate::kernel::monotonic_micros() < end_time {
wait_on(&impl_.generation, &mut || {
impl_.generation.load(Ordering::Acquire) != seen
});
signalled = impl_.generation.load(Ordering::Acquire) != seen;
}
g_mutex_lock(mutex);
if signalled { 1 } else { 0 }
}
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
}
// A half that runs on a system with slots of its own says so, and every GPrivate takes one. The
// map below is what the kernel halves use, and it costs a lock and a lookup per call, which is
// what code the Stalker writes does on every block it leaves.
pub struct ThreadSlots {
pub take: fn() -> u32,
pub get: fn(u32) -> *mut c_void,
pub set: fn(u32, *mut c_void),
}
pub fn use_thread_slots(slots: &'static ThreadSlots) {
unsafe { SLOTS = Some(slots) };
}
fn slot_of(key: *mut GPrivate, slots: &ThreadSlots) -> u32 {
let taken = unsafe { (*key).p } as usize;
if taken != 0 {
return (taken - 1) as u32;
}
unsafe {
lock_acquire(&TLS_LOCK);
let mut taken = (*key).p as usize;
if taken == 0 {
taken = ((slots.take)() + 1) as usize;
(*key).p = taken as *mut c_void;
}
lock_release(&TLS_LOCK);
(taken - 1) as u32
}
}
fn slots() -> Option<&'static ThreadSlots> {
unsafe { SLOTS }
}
static mut SLOTS: Option<&'static ThreadSlots> = None;
#[unsafe(no_mangle)]
pub extern "C" fn g_private_get(key: *mut GPrivate) -> *mut c_void {
if let Some(slots) = slots() {
return (slots.get)(slot_of(key, slots));
}
unsafe {
let thread_id = crate::kernel::current_thread_id();
let thread_id = get_current_thread_id();
lock_acquire(&TLS_LOCK);
ensure_tls_storage();
@@ -510,13 +437,8 @@ pub extern "C" fn g_private_get(key: *mut GPrivate) -> *mut c_void {
#[unsafe(no_mangle)]
pub extern "C" fn g_private_set(key: *mut GPrivate, value: *mut c_void) {
if let Some(slots) = slots() {
(slots.set)(slot_of(key, slots), value);
return;
}
unsafe {
let thread_id = crate::kernel::current_thread_id();
let thread_id = get_current_thread_id();
lock_acquire(&TLS_LOCK);
ensure_tls_storage();
@@ -546,9 +468,7 @@ pub extern "C" fn _g_system_thread_create(
data: gpointer,
) -> *mut GSystemThread {
unsafe {
crate::kernel::log("frida: glib is creating a thread\n\0");
let system_thread = crate::kernel::alloc(core::mem::size_of::<SystemThreadImpl>()) as *mut SystemThreadImpl;
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);
@@ -558,12 +478,12 @@ pub extern "C" fn _g_system_thread_create(
(*system_thread).finished.store(0, Ordering::Relaxed);
(*system_thread).detached.store(0, Ordering::Relaxed);
let wrapper_data = crate::kernel::alloc(core::mem::size_of::<ThreadWrapperData>()) as *mut ThreadWrapperData;
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::kernel::spawn_thread(thread_wrapper, wrapper_data as *mut c_void);
let _xnu_result = crate::xnu::kernel_thread_start(thread_wrapper, wrapper_data as *mut c_void);
system_thread as *mut GSystemThread
}
@@ -576,9 +496,8 @@ extern "C" fn thread_wrapper(parameter: *mut c_void, _wait_result: i32) {
let data = (*wrapper_data).data;
let system_thread = (*wrapper_data).system_thread;
let current_thread_id = crate::kernel::current_thread_id();
(*system_thread).thread_id.store(current_thread_id, Ordering::Release);
wake_waiters(&(*system_thread).thread_id);
let current_thread_id = get_current_thread_id();
(*system_thread).thread_id.store(current_thread_id, Ordering::Relaxed);
func.unwrap()(data);
@@ -618,10 +537,12 @@ pub extern "C" fn _g_system_thread_wait(thread: *mut GSystemThread) {
unsafe {
let system_thread = thread as *mut SystemThreadImpl;
while (*system_thread).thread_id.load(Ordering::Acquire) == 0 {
wait_on(&(*system_thread).thread_id, &mut || {
(*system_thread).thread_id.load(Ordering::Acquire) != 0
});
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);
@@ -635,16 +556,19 @@ pub extern "C" fn _g_system_thread_wait(thread: *mut GSystemThread) {
}
unsafe fn cleanup_system_thread(system_thread: *mut SystemThreadImpl) {
crate::kernel::free(system_thread as *mut u8, core::mem::size_of::<SystemThreadImpl>());
crate::xnu::free(system_thread as *mut u8, core::mem::size_of::<SystemThreadImpl>());
}
unsafe fn cleanup_wrapper_data(wrapper_data: *mut ThreadWrapperData) {
crate::kernel::free(wrapper_data as *mut u8, core::mem::size_of::<ThreadWrapperData>());
crate::xnu::free(wrapper_data as *mut u8, core::mem::size_of::<ThreadWrapperData>());
}
unsafe fn lock_acquire(lock: &AtomicU32) {
while !unsafe { lock_try_acquire(lock) } {
wait_on(lock, &mut || lock.load(Ordering::Relaxed) == 0);
loop {
if unsafe { lock_try_acquire(lock) } {
break;
}
unsafe { lock_wait(lock) };
}
}
@@ -655,36 +579,35 @@ unsafe fn lock_try_acquire(lock: &AtomicU32) -> bool {
unsafe fn lock_release(lock: &AtomicU32) {
lock.store(0, Ordering::Release);
wake_waiters(lock);
unsafe { lock_notify_all() };
}
// Sleeps until `ready` holds or the slice expires, whichever comes first.
//
// These used to spin on wfe/sev. A kernel thread that spins never reaches the
// scheduler, so a lost wakeup — or a genuine deadlock, which is how we found this —
// starves the CPU it is on until the hardware watchdog takes the machine down. The
// bounded sleep turns both into a stall that can be observed instead.
//
// The address of the word being waited on names the wait channel: XNU pairs
// assert_wait()/thread_wakeup() on it directly, while the Linux shim has a single
// channel and treats it as a hint. Every caller re-checks its own condition in a
// loop, so a spurious wakeup costs nothing either way.
fn wait_on<T>(word: &T, ready: &mut dyn FnMut() -> bool) {
crate::kernel::wait(token_of(word), Some(WAIT_SLICE_US), ready);
// The wait above returns without sleeping whenever `ready` already holds, so on
// its own it is not enough to keep a contended loop off the CPU: every caller
// here loops, and a loop in a kernel thread that never reaches the scheduler
// takes the machine down rather than merely running hot. Yield unconditionally.
crate::kernel::yield_now();
unsafe fn lock_wait(lock: &AtomicU32) {
while lock.load(Ordering::Relaxed) != 0 {
unsafe { lock_wait_primitive() };
}
}
fn wake_waiters<T>(word: &T) {
crate::kernel::wake(token_of(word));
unsafe fn lock_wait_primitive() {
unsafe {
asm!("wfe", options(nomem, nostack));
}
}
fn token_of<T>(word: &T) -> *const u8 {
word as *const T as *const u8
unsafe fn lock_notify_all() {
unsafe {
asm!("sev", options(nomem, nostack));
}
}
const WAIT_SLICE_US: u64 = 1000;
// The thread pointer is exposed to JavaScript as a GumThreadId, so it must fit
// in a double without losing precision; the low bits keep it unique per thread.
const JS_SAFE_THREAD_ID_MASK: u64 = (1 << 48) - 1;
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 & JS_SAFE_THREAD_ID_MASK
}
+438 -401
View File
@@ -1,76 +1,296 @@
// Gum's platform backend, without the parts that depend on the kernel. Memory protection,
// symbols and the module registry are in gum_injected.rs, gum_linux.rs and gum_windows.rs.
use crate::{
bindings::{
_GInterfaceInfo, _GTypeInfo, GObject, GObjectClass, GPrivate, GType, GumAddress,
GumExportDetails,
GumExportType_GUM_EXPORT_FUNCTION, GumFoundExportFunc, GumMemoryRange, GumModule,
GumFoundSymbolFunc, GumModuleInterface, GumSymbolDetails, GumSymbolType_GUM_SYMBOL_FUNCTION,
GumSymbolType_GUM_SYMBOL_OBJECT, GumSymbolType_GUM_SYMBOL_UNKNOWN, GumThreadId, GumTlsKey,
gssize, g_free, g_object_get_type, g_object_new,
g_once_init_enter, g_once_init_leave, g_strdup, g_type_add_interface_static,
g_type_class_peek_parent, g_type_register_static, gchar, gboolean, gpointer, gsize, guint,
_GInterfaceInfo, _GTypeInfo, _GumPageProtection_GUM_PAGE_EXECUTE,
_GumPageProtection_GUM_PAGE_READ, _GumPageProtection_GUM_PAGE_WRITE,
_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, g_variant_get_boolean, g_variant_get_uint64, g_variant_new,
g_variant_new_fixed_array, g_variant_type_free, g_variant_type_new, gboolean, gchar,
gconstpointer, gpointer, gsize, guint,
gum_barebone_register_module,
gum_barebone_try_remap_writable_pages as _gum_barebone_try_remap_writable_pages,
g_variant_unref, gum_module_get_type, gum_mprotect, gum_query_page_size,
},
gthread, kernel, libc,
gthread, host_rpc, libc, FridaCommand,
};
use alloc::boxed::Box;
use alloc::ffi::CString;
use core::ffi::CStr;
use alloc::format;
use alloc::vec::Vec;
use core::ffi::CStr;
use core::mem::size_of;
use core::ptr;
use core::sync::atomic::{AtomicU32, Ordering};
#[cfg(feature = "linux")]
use crate::gum_linux::enumerate_exports_in_range;
#[cfg(any(feature = "linux", feature = "linux-injected"))]
use crate::gum_modules::enumerate_symbols_in_range;
#[cfg(any(feature = "win9x", feature = "winnt"))]
use crate::gum_windows::enumerate_exports_in_range;
#[cfg(any(feature = "xnu-core", feature = "linux-injected"))]
use crate::gum_injected::enumerate_exports_in_range;
// Code slabs the agent allocates are normal writable RAM, so gum can keep a real writable alias to
// them across the slab's lifetime. Pre-existing kernel/kext text is CTRR-locked and can only be
// modified through the host's physical-memory bridge, so we shadow it and commit on dispose. We
// distinguish the two by tracking the executable ranges we hand out.
static SLAB_LOCK: AtomicU32 = AtomicU32::new(0);
static mut SLABS: Vec<(u64, u64)> = Vec::new();
const SHADOW_MAGIC: u64 = 0x4644_4f48_5341_4853;
const SHADOW_HEADER: usize = 24;
const SHADOW_MIN_ADDRESS: u64 = 0xffff_f000_0000_0000;
fn slab_lock() {
while SLAB_LOCK
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
core::hint::spin_loop();
}
}
fn slab_unlock() {
SLAB_LOCK.store(0, Ordering::Release);
}
fn register_slab(start: u64, size: usize) {
slab_lock();
unsafe {
(*core::ptr::addr_of_mut!(SLABS)).push((start, start + size as u64));
}
slab_unlock();
}
fn unregister_slab(start: u64) {
slab_lock();
unsafe {
(*core::ptr::addr_of_mut!(SLABS)).retain(|&(begin, _)| begin != start);
}
slab_unlock();
}
fn is_agent_slab(address: u64) -> bool {
slab_lock();
let found = unsafe {
(*core::ptr::addr_of!(SLABS))
.iter()
.any(|&(begin, end)| address >= begin && address < end)
};
slab_unlock();
found
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_process_get_current_thread_id() -> GumThreadId {
kernel::current_thread_id() as GumThreadId
}
#[cfg(any(feature = "win9x", feature = "winnt", feature = "xnu-core"))]
#[unsafe(no_mangle)]
pub extern "C" fn gum_process_get_id() -> guint {
kernel::current_process_id() as guint
}
#[cfg(feature = "linux-injected")]
#[unsafe(no_mangle)]
pub extern "C" fn gum_process_get_id() -> guint {
crate::source_process_id() as guint
unsafe { gthread::get_current_thread_id() }
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_page_size() -> guint {
kernel::page_size() as guint
}
// Only the kernel is allowed to ask the translation control register what it was set up with,
// so a half that runs in a process is told instead.
#[cfg(target_arch = "aarch64")]
pub(crate) fn page_size_the_kernel_runs_with() -> usize {
let translation_control: u64;
unsafe {
core::arch::asm!("mrs {}, tcr_el1", out(reg) translation_control, options(nomem, nostack));
}
let tcr_el1: u64;
core::arch::asm!("mrs {}, tcr_el1", out(reg) tcr_el1, options(nomem, nostack));
match (translation_control >> 14) & 0x3 {
0b01 => 65536,
0b10 => 16384,
_ => 4096,
let tg0 = (tcr_el1 >> 14) & 0x3;
match tg0 {
0b00 => 4096,
0b01 => 65536,
0b10 => 16384,
_ => 4096,
}
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "arm"))]
pub(crate) fn page_size_the_kernel_runs_with() -> usize {
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 {
if is_agent_slab(first_page as u64) {
return remap_agent_pages(first_page, n_pages);
}
shadow_kernel_pages(first_page, n_pages)
}
fn remap_agent_pages(first_page: gpointer, n_pages: guint) -> gpointer {
unsafe {
let page_size = gum_query_page_size() as usize;
let mut virtual_addrs = Vec::with_capacity(n_pages as usize);
let mut current_page = first_page as u64;
for _ in 0..n_pages {
virtual_addrs.push(current_page as gpointer);
current_page += page_size as u64;
}
_gum_barebone_try_remap_writable_pages(
virtual_addrs.as_ptr() as *mut *const core::ffi::c_void,
virtual_addrs.len() as guint,
)
}
}
fn shadow_kernel_pages(first_page: gpointer, n_pages: guint) -> gpointer {
unsafe {
let total = n_pages as usize * gum_query_page_size() as usize;
let buffer = crate::xnu::kalloc(SHADOW_HEADER + total);
*(buffer as *mut u64) = SHADOW_MAGIC;
*(buffer.add(8) as *mut u64) = first_page as u64;
*(buffer.add(16) as *mut u32) = n_pages;
let body = buffer.add(SHADOW_HEADER);
core::ptr::copy_nonoverlapping(first_page as *const u8, body, total);
body as gpointer
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages: guint) {
if (writable as u64) < SHADOW_MIN_ADDRESS {
return;
}
unsafe {
let buffer = (writable as *mut u8).sub(SHADOW_HEADER);
if *(buffer as *const u64) != SHADOW_MAGIC {
return;
}
let first_page = *(buffer.add(8) as *const u64);
let n_pages = *(buffer.add(16) as *const u32);
let total = n_pages as usize * gum_query_page_size() as usize;
commit_kernel_patch(first_page, writable as *const u8, total);
libc::__clear_cache(first_page as *const u8, (first_page + total as u64) as *const u8);
crate::xnu::free(buffer, SHADOW_HEADER + total);
}
}
unsafe fn commit_kernel_patch(address: u64, data: *const u8, len: usize) {
unsafe {
let element_type = g_variant_type_new(c"y".as_ptr());
let bytes = g_variant_new_fixed_array(element_type, data as gconstpointer, len as gsize, 1);
g_variant_type_free(element_type);
let payload = g_variant_new(c"(t@ay)".as_ptr(), address, bytes);
let reply = host_rpc(FridaCommand::PatchCode, payload);
g_variant_unref(reply);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_try_remap_writable_pages(
addrs: *const gpointer,
n_addrs: guint,
) -> gpointer {
if !crate::transport_is_up() {
return ptr::null_mut();
}
unsafe {
let element_type = g_variant_type_new(c"t".as_ptr());
let payload = g_variant_new_fixed_array(
element_type,
addrs as gconstpointer,
n_addrs as gsize,
size_of::<u64>() as gsize,
);
g_variant_type_free(element_type);
let reply = host_rpc(FridaCommand::RemapWritablePages, payload);
let virtual_address = g_variant_get_uint64(reply);
g_variant_unref(reply);
virtual_address as gpointer
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_try_mprotect(
address: gpointer,
size: gsize,
prot: GumPageProtection,
) -> gboolean {
if !crate::transport_is_up() {
return 1;
}
unsafe {
let payload = g_variant_new(c"(ttu)".as_ptr(), address as u64, size as u64, prot as u32);
let reply = host_rpc(FridaCommand::MemoryProtect, payload);
let success = g_variant_get_boolean(reply);
g_variant_unref(reply);
if success != 0 {
flush_tlb_range(address as u64, size as u64);
}
success
}
}
// The host rewrites our page-table descriptors through the physical-memory
// bridge, which leaves this CPU's TLB holding the stale translation. Stalker
// flips a slab page RW then RX in place, so without this the freeze never takes
// effect and executing the page faults with a permission abort.
unsafe fn flush_tlb_range(address: u64, size: u64) {
unsafe {
let page_size = gum_query_page_size() as u64;
let start = address & !(page_size - 1);
let end = (address + size + page_size - 1) & !(page_size - 1);
core::arch::asm!("dsb ish", options(nostack, preserves_flags));
let mut va = start;
while va < end {
core::arch::asm!("tlbi vaae1is, {operand}", operand = in(reg) va >> 12,
options(nostack, preserves_flags));
va += page_size;
}
core::arch::asm!("dsb ish", "isb", options(nostack, preserves_flags));
}
}
#[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 {
register_slab(ptr as u64, size as usize);
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 {
// Executable slabs were flipped to RX in the page tables; restore RW before returning them to
// the allocator, otherwise the reclaimed pages stay non-writable and the next consumer faults.
if is_agent_slab(address as u64) {
unsafe {
gum_mprotect(
address,
size,
(_GumPageProtection_GUM_PAGE_READ | _GumPageProtection_GUM_PAGE_WRITE)
as GumPageProtection,
);
}
unregister_slab(address as u64);
}
crate::xnu::free(address as *mut u8, size as usize);
1
}
#[unsafe(no_mangle)]
@@ -104,6 +324,40 @@ 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,
@@ -134,7 +388,7 @@ fn gum_native_module_get_type() -> GType {
class_size: core::mem::size_of::<GObjectClass>() as u16,
base_init: None,
base_finalize: None,
class_init: Some(crate::signed_to_be_called_back(gum_native_module_class_init, 0)),
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,
@@ -146,12 +400,12 @@ fn gum_native_module_get_type() -> GType {
let new_type = g_type_register_static(g_object_get_type(), type_name, &type_info, 0);
let interface_info = _GInterfaceInfo {
interface_init: Some(crate::signed_to_be_called_back(gum_native_module_iface_init, 0)),
interface_init: Some(gum_native_module_iface_init),
interface_finalize: None,
interface_data: core::ptr::null_mut(),
};
g_type_add_interface_static(new_type, crate::bindings::gum_module_get_type(), &interface_info);
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,
@@ -169,24 +423,18 @@ unsafe extern "C" fn gum_native_module_class_init(klass: gpointer, _class_data:
GUM_NATIVE_MODULE_PARENT_CLASS = g_type_class_peek_parent(klass) as *mut GObjectClass;
(*object_class).finalize = Some(crate::signed_to_be_called_back(gum_native_module_finalize, 0));
(*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(crate::signed_to_be_called_back(gum_native_module_get_name, 0));
(*iface).get_version = Some(crate::signed_to_be_called_back(gum_native_module_get_version, 0));
(*iface).get_path = Some(crate::signed_to_be_called_back(gum_native_module_get_path, 0));
(*iface).get_range = Some(crate::signed_to_be_called_back(gum_native_module_get_range, 0));
(*iface).enumerate_exports = Some(crate::signed_to_be_called_back(gum_native_module_enumerate_exports, 0));
(*iface).find_export_by_name = Some(crate::signed_to_be_called_back(gum_native_module_find_export_by_name, 0));
#[cfg(any(feature = "linux", feature = "linux-injected"))]
{
(*iface).enumerate_symbols =
Some(crate::signed_to_be_called_back(gum_native_module_enumerate_symbols, 0));
}
(*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);
}
}
@@ -200,36 +448,22 @@ unsafe extern "C" fn gum_native_module_finalize(object: *mut GObject) {
}
}
pub(crate) fn gum_native_module_new(
path: &str,
version: &str,
range: &GumMemoryRange,
) -> *mut GumModule {
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());
let name_offset = path.rfind(['/', '\\']).map_or(0, |at| at + 1);
(*module).name = (*module).path.add(name_offset);
(*module).version = version_copied(version);
(*module).name = (*module).path.add(path.rfind('/').unwrap() + 1);
(*module).version = g_strdup(version_cstr.as_ptr());
(*module).range = *range;
module as *mut GumModule
}
}
fn version_copied(version: &str) -> *mut gchar {
if version.is_empty() {
return ptr::null_mut();
}
let copy = CString::new(version).unwrap();
unsafe { g_strdup(copy.as_ptr()) }
}
extern "C" fn gum_native_module_get_name(module: *mut GumModule) -> *const gchar {
unsafe {
let native_module = module as *mut GumNativeModule;
@@ -258,34 +492,6 @@ extern "C" fn gum_native_module_get_range(module: *mut GumModule) -> *const GumM
}
}
unsafe extern "C" fn gum_native_module_find_export_by_name(
self_: *mut GumModule,
symbol_name: *const gchar,
) -> GumAddress {
unsafe {
let module = self_ as *mut GumNativeModule;
let range = (*module).range;
let wanted = CStr::from_ptr(symbol_name);
let mut found = 0;
let mut on_export = |name: *const gchar, address: u64| {
if CStr::from_ptr(name) == wanted {
found = address;
}
found == 0
};
enumerate_exports_in_range(
range.base_address,
range.base_address + range.size as u64,
&mut on_export,
);
found
}
}
unsafe extern "C" fn gum_native_module_enumerate_exports(
self_: *mut GumModule,
func: GumFoundExportFunc,
@@ -293,303 +499,134 @@ unsafe extern "C" fn gum_native_module_enumerate_exports(
) {
unsafe {
let module = self_ as *mut GumNativeModule;
let range = (*module).range;
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 mut on_export = |name: *const gchar, address: u64| {
let export_details = GumExportDetails {
type_: GumExportType_GUM_EXPORT_FUNCTION,
name,
address,
type_: export_type,
name: g_strdup(symbol_ref.name_ptr()),
address: symbol_ref.address(),
size: -1,
};
func.unwrap()(&export_details as *const GumExportDetails, user_data) != 0
};
enumerate_exports_in_range(
range.base_address,
range.base_address + range.size as u64,
&mut on_export,
);
}
}
#[cfg(any(feature = "linux", feature = "linux-injected"))]
unsafe extern "C" fn gum_native_module_enumerate_symbols(
self_: *mut GumModule,
func: GumFoundSymbolFunc,
user_data: gpointer,
) {
unsafe {
let module = self_ as *mut GumNativeModule;
let range = (*module).range;
let mut on_symbol = |name: *const gchar, address: u64, size: u64, kind: u8, global: bool| {
let details = GumSymbolDetails {
is_global: global as gboolean,
type_: match kind {
SYMBOL_IS_FUNCTION => GumSymbolType_GUM_SYMBOL_FUNCTION,
SYMBOL_IS_OBJECT => GumSymbolType_GUM_SYMBOL_OBJECT,
_ => GumSymbolType_GUM_SYMBOL_UNKNOWN,
},
section: ptr::null(),
name,
address,
size: size as gssize,
};
func.unwrap()(&details as *const GumSymbolDetails, user_data) != 0
};
enumerate_symbols_in_range(
range.base_address,
range.base_address + range.size as u64,
&mut on_symbol,
);
}
}
/// What the backends implement for `gum_native_module_enumerate_symbols`: call
/// `callback` for every symbol in `[start, end)`, stopping early when it returns
/// false.
#[cfg(any(feature = "linux", feature = "linux-injected"))]
pub(crate) type FoundSymbolCallback<'a> = dyn FnMut(*const gchar, u64, u64, u8, bool) -> bool + 'a;
#[cfg(any(feature = "linux", feature = "linux-injected"))]
const SYMBOL_IS_OBJECT: u8 = 1;
#[cfg(any(feature = "linux", feature = "linux-injected"))]
const SYMBOL_IS_FUNCTION: u8 = 2;
/// What the backends implement for `gum_native_module_enumerate_exports`: call
/// `callback` for every exported function in `[start, end)`, stopping early when
/// it returns false.
pub(crate) type FoundExportCallback<'a> = dyn FnMut(*const gchar, u64) -> bool + 'a;
// Code slabs the agent allocates are normal writable RAM, so Gum can keep a real writable alias to
// them across the slab's lifetime. Pre-existing kernel text is write-protected — CTRR-locked on
// Apple silicon, STRICT_KERNEL_RWX on Linux — and can only be modified through a separate path, so
// we treat it differently. We distinguish the two by tracking the executable ranges we hand out.
static SLAB_LOCK: AtomicU32 = AtomicU32::new(0);
static mut SLABS: Vec<(u64, u64)> = Vec::new();
fn slab_lock() {
while SLAB_LOCK
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
kernel::yield_now();
}
}
fn slab_unlock() {
SLAB_LOCK.store(0, Ordering::Release);
}
pub(crate) fn register_slab(start: u64, size: usize) {
slab_lock();
unsafe {
(*core::ptr::addr_of_mut!(SLABS)).push((start, start + size as u64));
}
slab_unlock();
}
pub(crate) fn unregister_slab(start: u64) {
slab_lock();
unsafe {
(*core::ptr::addr_of_mut!(SLABS)).retain(|&(begin, _)| begin != start);
}
slab_unlock();
}
// For callers that must not block, primarily a fault handler, which can interrupt the code
// that holds the lock. Report a busy registry and let the caller decide.
pub(crate) fn is_agent_slab_if_idle(address: u64) -> Option<bool> {
if SLAB_LOCK
.compare_exchange(0, 1, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
return None;
}
let found = unsafe {
(*core::ptr::addr_of!(SLABS))
.iter()
.any(|&(begin, end)| address >= begin && address < end)
};
slab_unlock();
Some(found)
}
pub(crate) fn is_agent_slab(address: u64) -> bool {
slab_lock();
let found = unsafe {
(*core::ptr::addr_of!(SLABS))
.iter()
.any(|&(begin, end)| address >= begin && address < end)
};
slab_unlock();
found
}
// The host gives the symbol table, thus this makes a name from an address.
#[cfg(feature = "blob")]
mod symbolication {
use super::*;
use crate::bindings::{
GArray, GumAddress, GumDebugSymbolDetails, g_array_append_vals, g_array_new, gconstpointer,
};
use core::ffi::CStr;
#[unsafe(no_mangle)]
pub extern "C" fn gum_symbol_name_from_address(address: gpointer) -> *mut gchar {
match closest_to(address) {
Some(symbol) => unsafe { g_strdup(symbol.name_ptr()) },
None => ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_symbol_details_from_address(
address: gpointer,
details: *mut GumDebugSymbolDetails,
) -> gboolean {
let Some(symbol) = closest_to(address) else {
return 0;
};
let details = unsafe { &mut *details };
*details = unsafe { core::mem::zeroed() };
details.address = address as u64;
put_text(symbol.name(), &mut details.symbol_name);
if let Some(module) = module_containing(address as u64) {
put_text(&module.name, &mut details.module_name);
}
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_module_find_global_export_by_name(name: *const gchar) -> GumAddress {
let Some(name) = text_of(name) else {
return 0;
};
#[cfg(any(feature = "win9x", feature = "winnt"))]
if crate::running_in_a_process() {
return export_of_a_process_module(name);
}
#[cfg(feature = "xnu-core")]
if crate::xnu::in_copy() {
return crate::xnu_mapped::export_named(name);
}
let table = unsafe { &*ptr::addr_of!(crate::SYMBOL_TABLE) };
match table.find_symbol_by_name(name) {
Some(symbol) => symbol.address(),
None => 0,
}
}
// A copy runs in a process, thus a name without a module means one of the modules of that
// process.
#[cfg(any(feature = "win9x", feature = "winnt"))]
fn export_of_a_process_module(wanted: &str) -> GumAddress {
let mut found = 0;
for module in crate::kernel::enumerate_modules() {
crate::kernel::enumerate_exports(module.base as _, &mut |name, address| {
if text_of(name as *const gchar) == Some(wanted) {
found = address;
}
found == 0
});
if found != 0 {
let should_continue =
func.unwrap()(&export_details as *const GumExportDetails, user_data);
if should_continue == 0 {
break;
}
}
found
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_function(name: *const gchar) -> gpointer {
let Some(name) = text_of(name) else {
return ptr::null_mut();
};
let table = unsafe { &*ptr::addr_of!(crate::SYMBOL_TABLE) };
match table.find_symbol_by_name(name) {
Some(symbol) => symbol.address() as gpointer,
None => ptr::null_mut(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_functions_named(name: *const gchar) -> *mut GArray {
let table = unsafe { &*ptr::addr_of!(crate::SYMBOL_TABLE) };
let found = text_of(name)
.map(|name| table.find_symbols_by_name(name).map(|s| s.address()).collect())
.unwrap_or_else(Vec::new);
pointer_array_of(&found)
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_functions_matching(pattern: *const gchar) -> *mut GArray {
let table = unsafe { &*ptr::addr_of!(crate::SYMBOL_TABLE) };
let found = text_of(pattern)
.map(|pattern| {
table
.find_symbols_matching_glob(pattern)
.map(|s| s.address())
.collect()
})
.unwrap_or_else(Vec::new);
pointer_array_of(&found)
}
fn closest_to(address: gpointer) -> Option<crate::symbols::SymbolRef<'static>> {
let table = unsafe { &*ptr::addr_of!(crate::SYMBOL_TABLE) };
table.find_closest_symbol_by_address(address as u64)
}
fn module_containing(address: u64) -> Option<&'static crate::ModuleInfo> {
let modules = unsafe { &*ptr::addr_of!(crate::MODULE_INFO) };
modules.iter().find(|m| {
address >= m.offset as u64 && address < m.offset as u64 + m.size as u64
})
}
fn pointer_array_of(addresses: &[u64]) -> *mut GArray {
let pointers: Vec<gpointer> = addresses.iter().map(|a| *a as gpointer).collect();
unsafe {
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
g_array_append_vals(
array,
pointers.as_ptr() as gconstpointer,
pointers.len() as guint,
);
array
}
}
fn text_of(value: *const gchar) -> Option<&'static str> {
if value.is_null() {
return None;
}
unsafe { CStr::from_ptr(value) }.to_str().ok()
}
fn put_text(text: &str, out: &mut [gchar]) {
let limit = out.len() - 1;
for (i, byte) in text.bytes().take(limit).enumerate() {
out[i] = byte as gchar;
}
out[text.len().min(limit)] = 0;
}
}
#[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
}
-393
View File
@@ -1,393 +0,0 @@
use alloc::ffi::CString;
use alloc::string::String;
use core::ffi::{CStr, c_char};
use crate::bindings::{
GError, GumScriptApiValue, _GumScriptApiType_GUM_SCRIPT_API_BOOLEAN,
_GumScriptApiType_GUM_SCRIPT_API_STRING, _GumScriptApiType_GUM_SCRIPT_API_UINT, gboolean,
gpointer, gum_script_api_add_function, gum_script_api_new, gum_script_api_registry_add,
gum_script_api_registry_obtain, gum_script_api_set_prelude, gum_script_api_unref,
};
use crate::linux::layout::{
enumerate_constants_in, enumerate_fields_in, enumerate_parameters_in, field_offset_in,
find_constant, name_of_type, resolve_struct, size_of_struct, size_of_type, types_are_described,
};
pub fn publish() {
unsafe {
let api = gum_script_api_new(c"Btf".as_ptr());
gum_script_api_add_function(
api,
c"_findStruct".as_ptr(),
c"s".as_ptr(),
_GumScriptApiType_GUM_SCRIPT_API_UINT,
Some(crate::signed_to_be_called_back(on_find_struct, 0)),
core::ptr::null_mut(),
);
gum_script_api_add_function(
api,
c"_sizeOf".as_ptr(),
c"u".as_ptr(),
_GumScriptApiType_GUM_SCRIPT_API_UINT,
Some(crate::signed_to_be_called_back(on_size_of, 0)),
core::ptr::null_mut(),
);
gum_script_api_add_function(
api,
c"_findOffsetOf".as_ptr(),
c"us".as_ptr(),
_GumScriptApiType_GUM_SCRIPT_API_UINT,
Some(crate::signed_to_be_called_back(on_find_offset, 0)),
core::ptr::null_mut(),
);
gum_script_api_add_function(
api,
c"_fieldsOf".as_ptr(),
c"u".as_ptr(),
_GumScriptApiType_GUM_SCRIPT_API_STRING,
Some(crate::signed_to_be_called_back(on_fields_of, 0)),
core::ptr::null_mut(),
);
gum_script_api_add_function(
api,
c"_findConstant".as_ptr(),
c"s".as_ptr(),
_GumScriptApiType_GUM_SCRIPT_API_STRING,
Some(crate::signed_to_be_called_back(on_find_constant, 0)),
core::ptr::null_mut(),
);
gum_script_api_add_function(
api,
c"_constantsOf".as_ptr(),
c"s".as_ptr(),
_GumScriptApiType_GUM_SCRIPT_API_STRING,
Some(crate::signed_to_be_called_back(on_constants_of, 0)),
core::ptr::null_mut(),
);
gum_script_api_add_function(
api,
c"_signatureOf".as_ptr(),
c"s".as_ptr(),
_GumScriptApiType_GUM_SCRIPT_API_STRING,
Some(crate::signed_to_be_called_back(on_signature_of, 0)),
core::ptr::null_mut(),
);
gum_script_api_add_function(
api,
c"_isAvailable".as_ptr(),
c"".as_ptr(),
_GumScriptApiType_GUM_SCRIPT_API_BOOLEAN,
Some(crate::signed_to_be_called_back(on_is_available, 0)),
core::ptr::null_mut(),
);
gum_script_api_set_prelude(api, PRELUDE.as_ptr());
gum_script_api_registry_add(gum_script_api_registry_obtain(), api);
gum_script_api_unref(api);
}
}
unsafe extern "C" fn on_find_struct(
args: *const GumScriptApiValue,
retval: *mut GumScriptApiValue,
_user_data: gpointer,
_error: *mut *mut GError,
) -> gboolean {
let container = unsafe { text_of(args, 0) };
let handle = resolve_struct(container).map_or(NOTHING, |handle| (handle + 1) as u32);
unsafe { (*retval).__bindgen_anon_1.u = handle };
1
}
unsafe extern "C" fn on_size_of(
args: *const GumScriptApiValue,
retval: *mut GumScriptApiValue,
_user_data: gpointer,
_error: *mut *mut GError,
) -> gboolean {
let handle = unsafe { handle_of(args, 0) };
unsafe { (*retval).__bindgen_anon_1.u = size_of_struct(handle) as u32 };
1
}
unsafe extern "C" fn on_find_offset(
args: *const GumScriptApiValue,
retval: *mut GumScriptApiValue,
_user_data: gpointer,
_error: *mut *mut GError,
) -> gboolean {
let handle = unsafe { handle_of(args, 0) };
let field = unsafe { text_of(args, 1) };
let offset = field_offset_in(handle, field).map_or(NOWHERE, |offset| offset as u32);
unsafe { (*retval).__bindgen_anon_1.u = offset };
1
}
unsafe extern "C" fn on_is_available(
_args: *const GumScriptApiValue,
retval: *mut GumScriptApiValue,
_user_data: gpointer,
_error: *mut *mut GError,
) -> gboolean {
unsafe { (*retval).__bindgen_anon_1.b = types_are_described() as gboolean };
1
}
unsafe extern "C" fn on_fields_of(
args: *const GumScriptApiValue,
retval: *mut GumScriptApiValue,
_user_data: gpointer,
_error: *mut *mut GError,
) -> gboolean {
let handle = unsafe { handle_of(args, 0) };
let mut described = String::from("{");
let mut separator = "";
enumerate_fields_in(handle, &mut |name, field| {
described.push_str(&alloc::format!(
"{separator}\"{name}\":{{\"offset\":{},\"size\":{},\"type\":\"{}\"",
field.offset,
size_of_type(field.id),
name_of_type(field.id)
));
if field.bit_size != 0 {
described.push_str(&alloc::format!(
",\"bitOffset\":{},\"bitSize\":{}",
field.bit_offset, field.bit_size
));
}
described.push('}');
separator = ",";
true
});
described.push('}');
unsafe { answer_with(retval, described) };
1
}
unsafe extern "C" fn on_find_constant(
args: *const GumScriptApiValue,
retval: *mut GumScriptApiValue,
_user_data: gpointer,
_error: *mut *mut GError,
) -> gboolean {
let name = unsafe { text_of(args, 0) };
let described = find_constant(name).map_or_else(String::new, |value| alloc::format!("{value}"));
unsafe { answer_with(retval, described) };
1
}
unsafe extern "C" fn on_constants_of(
args: *const GumScriptApiValue,
retval: *mut GumScriptApiValue,
_user_data: gpointer,
_error: *mut *mut GError,
) -> gboolean {
let container = unsafe { text_of(args, 0) };
let mut described = String::from("{");
let mut separator = "";
let found = enumerate_constants_in(container, &mut |name, value| {
described.push_str(&alloc::format!("{separator}\"{name}\":{value}"));
separator = ",";
});
described.push('}');
unsafe { answer_with(retval, if found { described } else { String::new() }) };
1
}
unsafe extern "C" fn on_signature_of(
args: *const GumScriptApiValue,
retval: *mut GumScriptApiValue,
_user_data: gpointer,
_error: *mut *mut GError,
) -> gboolean {
let name = unsafe { text_of(args, 0) };
let mut parameters = String::from("[");
let mut separator = "";
let returned = enumerate_parameters_in(name, &mut |name, kind| {
parameters.push_str(&alloc::format!(
"{separator}{{\"name\":\"{name}\",\"type\":\"{kind}\"}}"
));
separator = ",";
});
parameters.push(']');
let described = match returned {
Some(returned) => {
alloc::format!("{{\"returns\":\"{returned}\",\"parameters\":{parameters}}}")
}
None => String::new(),
};
unsafe { answer_with(retval, described) };
1
}
unsafe fn answer_with(retval: *mut GumScriptApiValue, described: String) {
unsafe {
let holder = (&raw mut LAST_ANSWER).as_mut().unwrap();
*holder = Some(CString::new(described).unwrap());
(*retval).__bindgen_anon_1.s = holder.as_ref().unwrap().as_ptr();
}
}
static mut LAST_ANSWER: Option<CString> = None;
const NOTHING: u32 = 0;
const NOWHERE: u32 = u32::MAX;
unsafe fn handle_of(args: *const GumScriptApiValue, index: usize) -> usize {
unsafe { (*args.add(index)).__bindgen_anon_1.u as usize - 1 }
}
unsafe fn text_of(args: *const GumScriptApiValue, index: usize) -> &'static str {
unsafe {
CStr::from_ptr((*args.add(index)).__bindgen_anon_1.s as *const c_char)
.to_str()
.unwrap_or("")
}
}
const PRELUDE: &CStr = c"
(() => {
const { _findStruct, _sizeOf, _findOffsetOf, _fieldsOf, _findConstant, _constantsOf,
_signatureOf, _isAvailable } = Btf;
const NOWHERE = 0xffffffff;
class BtfStruct {
constructor(name, handle) {
this.name = name;
this.handle = handle;
this.size = _sizeOf(handle);
this.known = new Map();
}
getOffsetOf(field) {
const offset = this.findOffsetOf(field);
if (offset === null)
throw new Error(`${this.name}: unable to find field '${field}'`);
return offset;
}
findOffsetOf(field) {
let offset = this.known.get(field);
if (offset === undefined) {
offset = _findOffsetOf(this.handle, field);
if (offset === NOWHERE)
return null;
this.known.set(field, offset);
}
return offset;
}
get fields() {
const fields = JSON.parse(_fieldsOf(this.handle));
Object.defineProperty(this, 'fields', { value: fields });
return fields;
}
toString() {
return `BtfStruct(${this.name}, size: ${this.size})`;
}
}
const resolved = new Map();
const functions = new Map();
Btf.available = _isAvailable();
Btf.getStruct = name => {
const described = Btf.findStruct(name);
if (described === null)
throw new Error(`unable to find struct '${name}'`);
return described;
};
Btf.findStruct = name => {
let described = resolved.get(name);
if (described === undefined) {
const handle = _findStruct(name);
if (handle === 0)
return null;
described = new BtfStruct(name, handle);
resolved.set(name, described);
}
return described;
};
Btf.getConstant = name => {
const value = Btf.findConstant(name);
if (value === null)
throw new Error(`unable to find constant '${name}'`);
return value;
};
Btf.findConstant = name => {
const value = _findConstant(name);
if (value === '')
return null;
return numberFrom(value);
};
Btf.getEnum = name => {
const constants = Btf.findEnum(name);
if (constants === null)
throw new Error(`unable to find enum '${name}'`);
return constants;
};
Btf.findEnum = name => {
const constants = _constantsOf(name);
if (constants === '')
return null;
return JSON.parse(constants);
};
Btf.getFunction = name => {
const signature = Btf.findFunction(name);
if (signature === null)
throw new Error(`unable to find function '${name}'`);
return signature;
};
Btf.findFunction = name => {
let signature = functions.get(name);
if (signature === undefined) {
const described = _signatureOf(name);
if (described === '')
return null;
signature = JSON.parse(described);
functions.set(name, signature);
}
return signature;
};
function numberFrom(text) {
const value = parseInt(text);
return Number.isSafeInteger(value) ? value : int64(text);
}
})();
";
-846
View File
@@ -1,846 +0,0 @@
// Gum's platform backend for an agent the host injected into a kernel that
// keeps its page tables and its executable regions to itself. Memory
// permissions and kernel-text writes go out over the hostlink, because the
// guest cannot perform them itself: the host owns the page tables and the
// physical-memory bridge.
use crate::{
FridaCommand,
bindings::{
_GumPageProtection_GUM_PAGE_EXECUTE, _GumPageProtection_GUM_PAGE_READ,
_GumPageProtection_GUM_PAGE_WRITE, _GumRwxSupport_GUM_RWX_FULL,
_GumRwxSupport_GUM_RWX_NONE, GArray,
GumDebugSymbolDetails, GumFoundRangeFunc, GumFoundThreadFunc, GumMemoryRange,
GumModuleRegistry, GumPageProtection, GumRangeDetails, GumRwxSupport, GumThreadDetails,
GumThreadId, GumThreadRegistry, gum_barebone_register_thread,
gum_barebone_unregister_thread,
g_array_append_vals, g_array_new, g_object_unref, g_strdup, g_variant_get_boolean,
g_variant_get_uint64, g_variant_new, g_variant_new_fixed_array, g_variant_type_free,
g_variant_type_new, g_variant_unref, gboolean, gchar, gconstpointer, gpointer, gsize, guint,
gum_barebone_register_module,
gum_barebone_try_remap_writable_pages as _gum_barebone_try_remap_writable_pages,
gum_mprotect, gum_query_page_size,
},
gum::{self, FoundExportCallback},
host_rpc, kernel, libc,
};
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::format;
use alloc::vec::Vec;
use core::ffi::CStr;
use core::mem::size_of;
use core::ptr;
// Where the guest's kernel keeps itself and its modules, which is what Gum
// reports as the path of each one.
#[cfg(feature = "xnu-core")]
const KERNEL_PATH: &str = "/System/Library/Kernels/kernel";
#[cfg(feature = "xnu-core")]
const MODULE_DIRECTORY: &str = "/System/Library/Extensions/";
#[cfg(feature = "xnu-core")]
const MODULE_SUFFIX: &str = ".kext";
#[cfg(feature = "linux-injected")]
const KERNEL_PATH: &str = "/boot/vmlinux";
#[cfg(feature = "linux-injected")]
const MODULE_DIRECTORY: &str = "/lib/modules/";
#[cfg(feature = "linux-injected")]
const MODULE_SUFFIX: &str = ".ko";
const SHADOW_HEADER: usize = 24;
#[cfg(feature = "xnu-core")]
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_platform() -> *const crate::bindings::gchar {
c"darwin".as_ptr() as *const crate::bindings::gchar
}
#[cfg(feature = "linux-injected")]
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_platform() -> *const crate::bindings::gchar {
c"linux".as_ptr() as *const crate::bindings::gchar
}
#[cfg(feature = "linux-injected")]
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_stack_size() -> crate::bindings::gsize {
if kernel::in_copy() {
return if crate::on_js_thread() { crate::linux::STACK_SIZE as crate::bindings::gsize } else { 0 };
}
crate::linux::stack_headroom() as crate::bindings::gsize
}
#[cfg(feature = "xnu-core")]
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_stack_size() -> crate::bindings::gsize {
if crate::on_js_thread() {
crate::xnu_injection::STACK as crate::bindings::gsize
} else {
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_query_rwx_support() -> GumRwxSupport {
#[cfg(feature = "linux-injected")]
return _GumRwxSupport_GUM_RWX_FULL;
#[cfg(not(feature = "linux-injected"))]
_GumRwxSupport_GUM_RWX_NONE
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_can_remap_writable() -> gboolean {
#[cfg(feature = "linux-injected")]
if kernel::in_copy() {
return 0;
}
#[cfg(feature = "xnu-core")]
if crate::xnu::in_copy() {
return 0;
}
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_try_remap_writable_pages(
first_page: gpointer,
n_pages: guint,
) -> gpointer {
#[cfg(feature = "xnu-kext")]
if crate::xnu::the_writable_view_of(first_page as u64) != 0 {
return remap_agent_pages(first_page, n_pages);
}
#[cfg(all(feature = "linux-injected", not(target_arch = "arm")))]
if writable_in_place(first_page, n_pages) {
return first_page;
}
#[cfg(not(feature = "xnu-kext"))]
if gum::is_agent_slab(first_page as u64) {
return remap_agent_pages(first_page, n_pages);
}
shadow_kernel_pages(first_page, n_pages)
}
#[cfg(feature = "xnu-kext")]
fn remap_agent_pages(first_page: gpointer, _n_pages: guint) -> gpointer {
crate::xnu::the_writable_view_of(first_page as u64) as gpointer
}
#[cfg(all(feature = "linux-injected", not(target_arch = "arm")))]
fn writable_in_place(first_page: gpointer, n_pages: guint) -> bool {
let size = n_pages as usize * unsafe { gum_query_page_size() } as usize;
kernel::set_protection(first_page as u64, size, _GumPageProtection_GUM_PAGE_RWX)
}
#[cfg(not(feature = "xnu-kext"))]
fn remap_agent_pages(first_page: gpointer, n_pages: guint) -> gpointer {
let alias = remap_agent_pages_through_host(first_page, n_pages);
if !alias.is_null() {
aliases().insert(alias as u64, first_page as u64);
}
alias
}
fn aliases() -> &'static mut BTreeMap<u64, u64> {
unsafe { core::ptr::addr_of_mut!(ALIASES).as_mut().unwrap() }
}
static mut ALIASES: BTreeMap<u64, u64> = BTreeMap::new();
#[cfg(not(feature = "xnu-kext"))]
fn remap_agent_pages_through_host(first_page: gpointer, n_pages: guint) -> gpointer {
unsafe {
let page_size = gum_query_page_size() as usize;
let mut virtual_addrs = Vec::with_capacity(n_pages as usize);
let mut current_page = first_page as u64;
for _ in 0..n_pages {
virtual_addrs.push(current_page as gpointer);
current_page += page_size as u64;
}
_gum_barebone_try_remap_writable_pages(
virtual_addrs.as_ptr() as *mut *const core::ffi::c_void,
virtual_addrs.len() as guint,
)
}
}
#[cfg(feature = "linux-injected")]
const _GumPageProtection_GUM_PAGE_RWX: u32 = 7;
unsafe fn what_changed(first_page: u64, shadow: *const u8, total: usize) -> Option<(usize, usize)> {
let live = unsafe { core::slice::from_raw_parts(first_page as *const u8, total) };
let ours = unsafe { core::slice::from_raw_parts(shadow, total) };
let first = live.iter().zip(ours).position(|(a, b)| a != b)?;
let last = total - live.iter().zip(ours).rev().position(|(a, b)| a != b).unwrap();
let start = first & !(WORD_SIZE - 1);
let end = (last + WORD_SIZE - 1) & !(WORD_SIZE - 1);
Some((start, end - start))
}
const WORD_SIZE: usize = 4;
fn shadow_kernel_pages(first_page: gpointer, n_pages: guint) -> gpointer {
unsafe {
let total = n_pages as usize * gum_query_page_size() as usize;
let buffer = kernel::alloc(SHADOW_HEADER + total);
*(buffer.add(8) as *mut u64) = first_page as u64;
*(buffer.add(16) as *mut u32) = n_pages;
let body = buffer.add(SHADOW_HEADER);
core::ptr::copy_nonoverlapping(first_page as *const u8, body, total);
shadows().insert(body as u64);
body as gpointer
}
}
fn shadows() -> &'static mut BTreeSet<u64> {
unsafe { core::ptr::addr_of_mut!(SHADOWS).as_mut().unwrap() }
}
static mut SHADOWS: BTreeSet<u64> = BTreeSet::new();
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages: guint) {
#[cfg(feature = "xnu-kext")]
if let Some(base) = crate::xnu::the_page_behind(writable as u64) {
let size = _n_pages as usize * unsafe { gum_query_page_size() } as usize;
unsafe {
libc::__clear_cache(base as *const u8, (base + size as u64) as *const u8);
}
return;
}
if let Some(executable) = aliases().remove(&(writable as u64)) {
let size = _n_pages as usize * unsafe { gum_query_page_size() } as usize;
unsafe {
libc::__clear_cache(executable as *const u8, (executable + size as u64) as *const u8);
}
return;
}
if !shadows().remove(&(writable as u64)) {
#[cfg(all(feature = "linux-injected", not(target_arch = "arm")))]
{
let size = _n_pages as usize * unsafe { gum_query_page_size() } as usize;
unsafe {
libc::__clear_cache(writable as *const u8,
(writable as u64 + size as u64) as *const u8);
}
}
return;
}
unsafe {
let buffer = (writable as *mut u8).sub(SHADOW_HEADER);
let first_page = *(buffer.add(8) as *const u64);
let n_pages = *(buffer.add(16) as *const u32);
let total = n_pages as usize * gum_query_page_size() as usize;
if let Some((offset, len)) = what_changed(first_page, writable as *const u8, total) {
commit_kernel_patch(first_page + offset as u64, (writable as *const u8).add(offset),
len);
libc::__clear_cache((first_page + offset as u64) as *const u8,
(first_page + (offset + len) as u64) as *const u8);
}
kernel::free(buffer, SHADOW_HEADER + total);
}
}
#[cfg(feature = "xnu-kext")]
unsafe fn commit_kernel_patch(address: u64, data: *const u8, len: usize) {
crate::xnu::write_through_a_writable_alias(address, data, len);
}
#[cfg(all(feature = "linux-injected", not(target_arch = "arm")))]
unsafe fn commit_kernel_patch(address: u64, data: *const u8, len: usize) {
if kernel::patch_text(address, data, len) {
return;
}
unsafe { ask_the_host_to_patch(address, data, len) };
}
#[cfg(all(not(feature = "xnu-kext"), any(not(feature = "linux-injected"), target_arch = "arm")))]
unsafe fn commit_kernel_patch(address: u64, data: *const u8, len: usize) {
unsafe { ask_the_host_to_patch(address, data, len) };
}
#[cfg(not(feature = "xnu-kext"))]
unsafe fn ask_the_host_to_patch(address: u64, data: *const u8, len: usize) {
unsafe {
let element_type = g_variant_type_new(c"y".as_ptr());
let bytes = g_variant_new_fixed_array(element_type, data as gconstpointer, len as gsize, 1);
g_variant_type_free(element_type);
let payload = g_variant_new(c"(t@ay)".as_ptr(), address, bytes);
let reply = host_rpc(FridaCommand::PatchCode, payload);
g_variant_unref(reply);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_try_remap_writable_pages(
addrs: *const gpointer,
n_addrs: guint,
) -> gpointer {
if !crate::transport_is_up() {
return ptr::null_mut();
}
unsafe {
let mut wide = Vec::with_capacity(n_addrs as usize);
for i in 0..n_addrs as usize {
wide.push(*addrs.add(i) as u64);
}
let element_type = g_variant_type_new(c"t".as_ptr());
let payload = g_variant_new_fixed_array(
element_type,
wide.as_ptr() as gconstpointer,
n_addrs as gsize,
size_of::<u64>() as gsize,
);
g_variant_type_free(element_type);
let reply = host_rpc(FridaCommand::RemapWritablePages, payload);
let virtual_address = g_variant_get_uint64(reply);
g_variant_unref(reply);
virtual_address as gpointer
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_try_mprotect(
address: gpointer,
size: gsize,
prot: GumPageProtection,
) -> gboolean {
protect_here(address as u64, size as usize, prot as u32) as gboolean
}
// Where the agent has a half that runs in a process of the target, the protection is that
// half's own business: it has an address space it may change itself.
#[cfg(feature = "linux-injected")]
fn protect_here(address: u64, size: usize, prot: u32) -> bool {
kernel::protect(address, size, prot)
}
#[cfg(feature = "xnu-kext")]
fn protect_here(address: u64, size: usize, prot: u32) -> bool {
if crate::xnu::in_copy() {
return kernel::protect(address, size, prot);
}
true
}
#[cfg(all(feature = "xnu-core", not(feature = "xnu-kext")))]
fn protect_here(address: u64, size: usize, prot: u32) -> bool {
if crate::xnu::in_copy() {
return kernel::protect(address, size, prot);
}
ask_the_host_to_protect(address, size, prot)
}
#[cfg(not(any(feature = "linux-injected", feature = "xnu-core")))]
fn protect_here(address: u64, size: usize, prot: u32) -> bool {
ask_the_host_to_protect(address, size, prot)
}
pub fn ask_the_host_to_protect(address: u64, size: usize, prot: u32) -> bool {
if !crate::transport_is_up() {
return true;
}
let granted = the_host_grants_it(address, size, prot);
#[cfg(feature = "xnu-core")]
if granted {
crate::kernel::make_the_machine_agree();
}
granted
}
fn the_host_grants_it(address: u64, size: usize, prot: u32) -> bool {
unsafe {
let payload = g_variant_new(c"(ttu)".as_ptr(), address, size as u64, prot);
let reply = host_rpc(FridaCommand::MemoryProtect, payload);
let success = g_variant_get_boolean(reply) != 0;
g_variant_unref(reply);
if success {
flush_tlb_range(address, size as u64);
}
success
}
}
// The host rewrites our page-table descriptors through the physical-memory
// bridge, which leaves this CPU's TLB holding the stale translation. Stalker
// flips a slab page RW then RX in place, so without this the freeze never takes
// effect and executing the page faults with a permission abort.
unsafe fn flush_tlb_range(address: u64, size: u64) {
let page_size = unsafe { gum_query_page_size() } as u64;
let start = address & !(page_size - 1);
let end = (address + size + page_size - 1) & !(page_size - 1);
unsafe { flush_pages(start, end, page_size) };
}
#[cfg(target_arch = "arm")]
unsafe fn flush_pages(start: u64, end: u64, page_size: u64) {
unsafe {
core::arch::asm!("dsb ish", options(nostack, preserves_flags));
let mut va = start;
while va < end {
core::arch::asm!("mcr p15, 0, {operand}, c8, c3, 3", operand = in(reg) va as u32,
options(nostack, preserves_flags));
va += page_size;
}
core::arch::asm!("dsb ish", "isb", options(nostack, preserves_flags));
}
}
#[cfg(target_arch = "aarch64")]
unsafe fn flush_pages(start: u64, end: u64, page_size: u64) {
unsafe {
core::arch::asm!("dsb ish", options(nostack, preserves_flags));
let mut va = start;
while va < end {
core::arch::asm!("tlbi vaae1is, {operand}", operand = in(reg) va >> 12,
options(nostack, preserves_flags));
va += page_size;
}
core::arch::asm!("dsb ish", "isb", options(nostack, preserves_flags));
}
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe fn flush_pages(start: u64, end: u64, page_size: u64) {
unsafe {
let mut va = start;
while va < end {
core::arch::asm!("invlpg [{operand}]", operand = in(reg) va as usize,
options(nostack, preserves_flags));
va += page_size;
}
}
}
#[cfg(feature = "xnu-kext")]
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_allocate_near(
spec: gconstpointer,
size: gsize,
alignment: gsize,
prot: GumPageProtection,
) -> gpointer {
if spec.is_null() {
return gum_memory_allocate(ptr::null_mut(), size, alignment, prot);
}
let (wanted, reach) = unsafe {
(
(*(spec as *const GumAddressSpec)).near_address as u64,
(*(spec as *const GumAddressSpec)).max_distance as u64,
)
};
let within_reach = |at: gpointer| {
!at.is_null()
&& (at as u64).abs_diff(wanted).max((at as u64 + size - 1).abs_diff(wanted)) <= reach
};
let close = crate::xnu::kernel_alloc_code_above(wanted.saturating_sub(reach), size as usize)
as gpointer;
if within_reach(close) {
remember_slab_of(close, size);
return close;
}
if !close.is_null() {
release_pages_now(close, size);
}
let got = gum_memory_allocate(ptr::null_mut(), size, alignment, prot);
if within_reach(got) {
return got;
}
if !got.is_null() {
release_pages_now(got, size);
}
ptr::null_mut()
}
#[cfg(feature = "xnu-kext")]
fn release_pages_now(at: gpointer, size: gsize) {
gum::unregister_slab(at as u64);
crate::xnu::forget_what_we_took(at as u64);
kernel::free_code(at as *mut u8, size as usize);
}
#[cfg(feature = "xnu-kext")]
fn remember_slab_of(at: gpointer, size: gsize) {
unsafe { gum::register_slab(at as u64, size as usize) };
crate::xnu::remember_what_we_took(at as u64, size as usize, true);
}
#[cfg(feature = "xnu-kext")]
#[repr(C)]
struct GumAddressSpec {
near_address: gpointer,
max_distance: gsize,
}
#[cfg(feature = "xnu-kext")]
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_allocate_bookkeeping(size: gsize, _alignment: gsize) -> gpointer {
let ptr = crate::xnu::kernel_alloc_pages(size as usize);
if !ptr.is_null() {
unsafe { core::ptr::write_bytes(ptr, 0, size as usize) };
}
ptr as gpointer
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_allocate(
address: gpointer,
size: gsize,
_alignment: gsize,
prot: GumPageProtection,
) -> gpointer {
let may_run = (prot & _GumPageProtection_GUM_PAGE_EXECUTE) != 0;
#[cfg(feature = "xnu-kext")]
let ptr = kernel::alloc_code(size as usize);
#[cfg(feature = "xnu-kext")]
let _ = address;
#[cfg(all(feature = "xnu-core", not(feature = "xnu-kext")))]
let ptr = if crate::xnu::in_copy() {
crate::xnu_user_calls::code_memory_near(address as u64, size as usize)
} else {
kernel::alloc_code(size as usize)
};
#[cfg(not(feature = "xnu-core"))]
let ptr = kernel::alloc_code(size as usize);
#[cfg(not(feature = "xnu-core"))]
let _ = address;
unsafe {
#[cfg(not(feature = "xnu-kext"))]
core::ptr::write_bytes(ptr, 0, size as usize);
if may_run {
gum::register_slab(ptr as u64, size as usize);
}
}
#[cfg(not(feature = "xnu-kext"))]
if may_run {
unsafe { gum_mprotect(ptr as gpointer, size, prot) };
}
#[cfg(feature = "xnu-kext")]
crate::xnu::remember_what_we_took(ptr as u64, size as usize, may_run);
ptr as gpointer
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_free(address: gpointer, size: gsize) -> gboolean {
#[cfg(feature = "xnu-kext")]
if gum::is_agent_slab(address as u64) {
return 1;
}
// Executable slabs were flipped to RX in the page tables; restore RW before returning them to
// the allocator, otherwise the reclaimed pages stay non-writable and the next consumer faults.
if gum::is_agent_slab(address as u64) {
unsafe {
gum_mprotect(
address,
size,
(_GumPageProtection_GUM_PAGE_READ | _GumPageProtection_GUM_PAGE_WRITE)
as GumPageProtection,
);
}
gum::unregister_slab(address as u64);
}
#[cfg(feature = "xnu-kext")]
crate::xnu::forget_what_we_took(address as u64);
kernel::free_code(address as *mut u8, size as usize);
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
#[cfg(feature = "linux-injected")]
if kernel::in_copy() {
kernel::register_what_the_copy_lives_among(registry);
kernel::watch_the_loader();
return;
}
#[cfg(feature = "xnu-core")]
if crate::xnu::in_copy() {
crate::xnu_mapped::register_what_the_copy_lives_among(registry);
return;
}
#[cfg(feature = "linux-injected")]
{
crate::gum_modules::publish(registry);
return;
}
#[cfg(not(feature = "linux-injected"))]
{
let kernel_base = kernel::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 {
KERNEL_PATH
} else {
&format!("{}{}{}", MODULE_DIRECTORY, module_info.name, MODULE_SUFFIX)
};
let module_range = GumMemoryRange {
base_address: module_base,
size: module_info.size as gsize,
};
let module =
gum::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;
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_on_registry_deactivating(_registry: *mut GumModuleRegistry) {
#[cfg(feature = "linux-injected")]
crate::gum_modules::unpublish();
}
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_on_thread_registry_activating(registry: *mut GumThreadRegistry) {
unsafe { THREAD_REGISTRY = registry };
kernel::enumerate_threads(&mut |thread| announce_thread(thread.id));
}
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_on_thread_registry_deactivating(_registry: *mut GumThreadRegistry) {
unsafe { THREAD_REGISTRY = ptr::null_mut() };
}
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
pub(crate) fn thread_appeared(id: u32) {
announce_thread(id);
}
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
pub(crate) fn thread_vanished(id: u32) {
let registry = unsafe { THREAD_REGISTRY };
if registry.is_null() {
return;
}
unsafe { gum_barebone_unregister_thread(registry, id as GumThreadId) };
}
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
fn announce_thread(id: u32) {
let registry = unsafe { THREAD_REGISTRY };
if registry.is_null() {
return;
}
let mut details: GumThreadDetails = unsafe { core::mem::zeroed() };
details.id = id as GumThreadId;
unsafe { gum_barebone_register_thread(registry, &details) };
}
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
static mut THREAD_REGISTRY: *mut GumThreadRegistry = ptr::null_mut();
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_enumerate_threads(func: GumFoundThreadFunc, user_data: gpointer) {
let Some(emit) = func else {
return;
};
kernel::enumerate_threads(&mut |thread| {
let mut details: GumThreadDetails = unsafe { core::mem::zeroed() };
details.id = thread.id as GumThreadId;
unsafe { emit(&details, user_data) };
});
}
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
#[unsafe(no_mangle)]
pub extern "C" fn _gum_process_enumerate_ranges(
prot: GumPageProtection,
func: GumFoundRangeFunc,
user_data: gpointer,
) {
let Some(emit) = func else {
return;
};
let mut report = |base: u64, size: u64, protection: u32| {
if (protection & prot as u32) != prot as u32 {
return;
}
let range = GumMemoryRange {
base_address: base,
size: size as gsize,
};
let details = GumRangeDetails {
range: &range,
protection: protection as GumPageProtection,
file: ptr::null(),
};
unsafe { emit(&details, user_data) };
};
#[cfg(feature = "linux-injected")]
if !kernel::in_copy() {
each_kernel_module_range(|base, size| report(base, size, KERNEL_RANGE_PROTECTION));
return;
}
kernel::enumerate_ranges(&mut |base, size, protection| report(base, size as u64, protection));
}
#[cfg(feature = "linux-injected")]
const KERNEL_RANGE_PROTECTION: u32 = 1 | 2 | 4;
#[cfg(feature = "linux-injected")]
fn each_kernel_module_range(mut visit: impl FnMut(u64, u64)) {
let kernel_base = kernel::get_kernel_base();
let module_infos = unsafe { &*core::ptr::addr_of!(crate::MODULE_INFO) };
for module_info in module_infos.iter() {
visit(kernel_base + module_info.offset, module_info.size);
}
}
#[cfg(any(feature = "linux-injected", feature = "xnu-core"))]
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_query_protection(
address: gpointer,
prot: *mut GumPageProtection,
) -> gboolean {
#[cfg(feature = "linux-injected")]
if !kernel::in_copy() {
let mut protection = 0u32;
each_kernel_module_range(|base, size| {
if address as u64 >= base && (address as u64) < base + size {
protection = KERNEL_RANGE_PROTECTION;
}
});
if protection == 0 {
return 0;
}
unsafe { *prot = protection as GumPageProtection };
return 1;
}
let protection = kernel::protection_at(address as u64);
if protection == 0 {
return 0;
}
unsafe { *prot = protection as GumPageProtection };
1
}
pub(crate) unsafe fn enumerate_exports_in_range(
start_address: u64,
end_address: u64,
callback: &mut FoundExportCallback<'_>,
) {
#[cfg(feature = "linux-injected")]
if kernel::in_copy() {
kernel::enumerate_exports_in_range(start_address, end_address, callback);
return;
}
#[cfg(feature = "linux-injected")]
if crate::gum_modules::enumerate_exports_in_module(start_address, callback) {
return;
}
#[cfg(feature = "xnu-core")]
if crate::xnu::in_copy() {
crate::xnu_mapped::enumerate_exports_in_range(start_address, end_address, callback);
return;
}
unsafe {
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
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;
}
if !callback(symbol_ref.name_ptr(), symbol_ref.address()) {
break;
}
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
0
}
-217
View File
@@ -1,217 +0,0 @@
// Linux half of Gum's platform backend. Everything is done locally: we are the
// kernel, so page permissions go through set_memory_*(), writable aliases of
// write-protected text come from vmap(), and symbols come from kallsyms.
use crate::{
bindings::{
_GumPageProtection_GUM_PAGE_EXECUTE, _GumPageProtection_GUM_PAGE_READ,
_GumPageProtection_GUM_PAGE_WRITE, _GumRwxSupport_GUM_RWX_NONE, GArray,
GumDebugSymbolDetails, GumMemoryRange, GumModuleRegistry, GumPageProtection, GumRwxSupport,
g_array_append_vals, g_array_new, g_object_unref, g_pattern_spec_free, g_pattern_spec_match_string,
g_pattern_spec_new, g_strdup, gboolean, gchar, gconstpointer, gpointer, gsize, guint,
gum_barebone_register_module, gum_mprotect,
},
gum::{self, FoundExportCallback},
kernel,
};
use alloc::format;
use core::ffi::CStr;
use core::ptr;
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_platform() -> *const crate::bindings::gchar {
c"linux".as_ptr() as *const crate::bindings::gchar
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_stack_size() -> gsize {
crate::linux::stack_headroom() as gsize
}
#[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 {
kernel::remap_writable(first_page as u64, n_pages) as gpointer
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_dispose_writable_pages(writable: gpointer, _n_pages: guint) {
kernel::unmap_writable(writable as *mut core::ffi::c_void);
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_try_mprotect(
address: gpointer,
size: gsize,
prot: GumPageProtection,
) -> gboolean {
if kernel::protect(address as u64, size as usize, prot as u32) {
1
} else {
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_allocate(
_address: gpointer,
size: gsize,
_alignment: gsize,
prot: GumPageProtection,
) -> gpointer {
let ptr = kernel::alloc_code(size as usize);
unsafe {
core::ptr::write_bytes(ptr, 0, size as usize);
if (prot & _GumPageProtection_GUM_PAGE_EXECUTE) != 0 {
gum::register_slab(ptr as u64, size as usize);
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 {
// vfree() wants the range writable again: the pages go back to the vmalloc
// allocator, and leaving them RX would fault the next consumer.
if gum::is_agent_slab(address as u64) {
unsafe {
gum_mprotect(
address,
size,
(_GumPageProtection_GUM_PAGE_READ | _GumPageProtection_GUM_PAGE_WRITE)
as GumPageProtection,
);
}
gum::unregister_slab(address as u64);
}
kernel::free_code(address as *mut u8, size as usize);
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
crate::gum_modules::publish(registry);
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_on_registry_deactivating(_registry: *mut GumModuleRegistry) {
crate::gum_modules::unpublish();
}
pub(crate) unsafe fn enumerate_exports_in_range(
start_address: u64,
end_address: u64,
callback: &mut FoundExportCallback<'_>,
) {
if crate::gum_modules::enumerate_exports_in_module(start_address, callback) {
return;
}
kernel::enumerate_symbols(&mut |name, address| {
if address < start_address || address >= end_address {
return true;
}
callback(name.as_ptr() as *const gchar, address)
});
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_symbol_details_from_address(
address: gpointer,
details: *mut GumDebugSymbolDetails,
) -> gboolean {
let Some((name, symbol_address)) = kernel::symbol_from_address(address as u64) else {
return 0;
};
unsafe {
let name_bytes = name.as_bytes();
let copy_len = core::cmp::min(name_bytes.len(), (*details).symbol_name.len() - 1);
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;
}
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_symbol_name_from_address(address: gpointer) -> *mut gchar {
let Some((name, _)) = kernel::symbol_from_address(address as u64) else {
return ptr::null_mut();
};
let mut name = name;
name.push('\0');
unsafe { g_strdup(name.as_ptr() as *const gchar) }
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_function(name: *const gchar) -> gpointer {
let address = kernel::find_function(unsafe { CStr::from_ptr(name) });
address as gpointer
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_functions_named(name: *const gchar) -> *mut GArray {
let wanted = unsafe { CStr::from_ptr(name) };
collect_addresses(&mut |candidate, _address| candidate == wanted)
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_find_functions_matching(pattern: *const gchar) -> *mut GArray {
unsafe {
let spec = g_pattern_spec_new(pattern);
let array = collect_addresses(&mut |candidate, _address| {
g_pattern_spec_match_string(spec, candidate.as_ptr() as *const gchar) != 0
});
g_pattern_spec_free(spec);
array
}
}
fn collect_addresses(predicate: &mut dyn FnMut(&CStr, u64) -> bool) -> *mut GArray {
unsafe {
let array = g_array_new(0, 0, core::mem::size_of::<gpointer>() as guint);
kernel::enumerate_symbols(&mut |name, address| {
if predicate(name, address) {
let addr = address as gpointer;
g_array_append_vals(array, &addr as *const gpointer as gconstpointer, 1);
}
true
});
array
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_load_symbols(_path: *const gchar) -> gboolean {
0
}
-98
View File
@@ -1,98 +0,0 @@
use alloc::format;
use crate::bindings::{
GumMemoryRange, GumModuleRegistry, g_object_unref, gpointer, gsize,
gum_barebone_register_module, gum_barebone_unregister_module,
};
use crate::gum::{self, FoundExportCallback, FoundSymbolCallback};
use crate::kernel;
use crate::linux::{LoadedModule, ModuleEvent};
pub fn publish(registry: *mut GumModuleRegistry) {
unsafe { REGISTRY = registry };
let base = kernel::get_kernel_base();
if base != 0 {
register(registry, KERNEL_PATH, "", base, kernel::get_kernel_size());
}
for m in kernel::enumerate_modules() {
register(registry, &path_of(&m.name), &m.version, m.base, m.size);
}
kernel::watch_modules(on_module_event);
}
pub fn enumerate_symbols_in_range(
start_address: u64,
end_address: u64,
callback: &mut FoundSymbolCallback<'_>,
) {
#[cfg(feature = "linux-injected")]
if kernel::in_copy() {
kernel::enumerate_symbols_in_range(start_address, end_address, callback);
return;
}
kernel::enumerate_module_symbols(start_address, &mut |name, address, size, kind, global| {
if address < start_address || address >= end_address {
return true;
}
callback(name as *const _, address, size, kind, global)
});
}
pub fn enumerate_exports_in_module(
start_address: u64,
callback: &mut FoundExportCallback<'_>,
) -> bool {
kernel::enumerate_module_exports(start_address, &mut |name, address| {
callback(name as *const _, address)
})
}
pub fn unpublish() {
kernel::unwatch_modules();
unsafe { REGISTRY = core::ptr::null_mut() };
}
fn on_module_event(kind: ModuleEvent, m: LoadedModule) {
let registry = unsafe { REGISTRY };
if registry.is_null() {
return;
}
match kind {
ModuleEvent::Loaded => register(registry, &path_of(&m.name), &m.version, m.base, m.size),
ModuleEvent::Unloaded => unsafe { gum_barebone_unregister_module(registry, m.base) },
}
}
fn register(
registry: *mut GumModuleRegistry,
path: &str,
version: &str,
base: u64,
size: u64,
) {
unsafe {
let range = GumMemoryRange {
base_address: base,
size: size as gsize,
};
let module = gum::gum_native_module_new(path, version, &range);
gum_barebone_register_module(registry, module);
g_object_unref(module as gpointer);
}
}
fn path_of(name: &str) -> alloc::string::String {
format!("{}{}{}", MODULE_DIRECTORY, name, MODULE_SUFFIX)
}
static mut REGISTRY: *mut GumModuleRegistry = core::ptr::null_mut();
const KERNEL_PATH: &str = "/boot/vmlinux";
const MODULE_DIRECTORY: &str = "/lib/modules/";
const MODULE_SUFFIX: &str = ".ko";
-641
View File
@@ -1,641 +0,0 @@
// The Windows part of Gum's platform backend, for Win9x and NT. Kernel code is writable on
// both, thus this backend needs no alias. Only the position of the modules is different.
use crate::{
bindings::{
_GumPageProtection_GUM_PAGE_EXECUTE, _GumRwxSupport_GUM_RWX_FULL, GumMemoryRange,
GumModuleRegistry, GumPageProtection, GumRwxSupport, g_object_unref, gboolean, gpointer,
gsize, guint, gum_barebone_register_module, gum_mprotect, GumFoundRangeFunc,
GumFoundThreadFunc, GumModifyThreadFlags, GumModifyThreadFunc, GumRangeDetails,
GumThreadDetails, GumThreadFlags, GumThreadId, GumThreadRegistry,
gum_thread_details_copy,
},
gum::{self, FoundExportCallback},
kernel,
};
use crate::bindings::{GumCpuContext, GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT};
use alloc::format;
use core::ptr;
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_platform() -> *const crate::bindings::gchar {
c"windows".as_ptr() as *const crate::bindings::gchar
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_query_stack_size() -> crate::bindings::gsize {
if crate::on_js_thread() {
kernel::THREAD_STACK_SIZE as crate::bindings::gsize
} else {
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_query_rwx_support() -> GumRwxSupport {
_GumRwxSupport_GUM_RWX_FULL
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_can_remap_writable() -> gboolean {
#[cfg(feature = "win9x")]
{
crate::win9x::in_copy() as gboolean
}
#[cfg(not(feature = "win9x"))]
{
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_try_remap_writable_pages(
first_page: gpointer,
_n_pages: guint,
) -> gpointer {
#[cfg(feature = "win9x")]
if crate::win9x::in_copy() {
return crate::win9x_user::take_writes_on(first_page as *mut u8, _n_pages) as gpointer;
}
first_page
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_dispose_writable_pages(_writable: gpointer, _n_pages: guint) {
#[cfg(feature = "win9x")]
if crate::win9x::in_copy() {
crate::win9x_user::make_the_writes(_writable as *mut u8, _n_pages);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_query_protection(
address: gpointer,
prot: *mut GumPageProtection,
) -> gboolean {
let protection = kernel::protection_at(address as usize);
if protection == 0 {
return 0;
}
unsafe { *prot = protection as GumPageProtection };
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_try_mprotect(
address: gpointer,
size: gsize,
prot: GumPageProtection,
) -> gboolean {
if kernel::protect(address as u64, size as usize, prot as u32) {
1
} else {
0
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_memory_allocate(
_address: gpointer,
size: gsize,
_alignment: gsize,
prot: GumPageProtection,
) -> gpointer {
let ptr = kernel::alloc_code(size as usize);
if ptr.is_null() {
return ptr::null_mut();
}
unsafe {
core::ptr::write_bytes(ptr, 0, size as usize);
if (prot & _GumPageProtection_GUM_PAGE_EXECUTE) != 0 {
gum::register_slab(ptr as u64, size as usize);
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 {
if gum::is_agent_slab(address as u64) {
gum::unregister_slab(address as u64);
}
kernel::free_code(address as *mut u8, size as usize);
1
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_on_registry_activating(registry: *mut GumModuleRegistry) {
#[cfg(any(feature = "win9x", feature = "winnt"))]
if crate::running_in_a_process() {
register_the_process_modules(registry);
return;
}
unsafe {
let modules = &*core::ptr::addr_of!(crate::MODULE_INFO);
for module_info in modules.iter() {
let path = format!("{}{}", kernel::MODULE_DIRECTORY, module_info.name);
let range = GumMemoryRange {
base_address: module_info.offset as u64,
size: module_info.size as gsize,
};
let module = gum::gum_native_module_new(&path, &module_info.version, &range);
gum_barebone_register_module(registry, module);
g_object_unref(module as gpointer);
}
}
}
// A script can hook a library the moment it arrives, thus stand in front of the loader and say
// what came and what went.
#[cfg(any(feature = "win9x", feature = "winnt"))]
pub(crate) fn watch_the_loader() {
let Some(entries) = kernel::loader_entry_points() else {
return;
};
unsafe {
let interceptor = crate::bindings::gum_interceptor_obtain();
crate::bindings::gum_interceptor_begin_transaction(interceptor);
crate::bindings::gum_interceptor_replace(interceptor, entries.load as gpointer,
kernel::on_module_load as gpointer, &raw mut LOADER_LOAD, ptr::null());
if entries.load_with_flags != 0 {
crate::bindings::gum_interceptor_replace(interceptor,
entries.load_with_flags as gpointer, kernel::on_module_load_with_flags as gpointer,
&raw mut LOADER_LOAD_WITH_FLAGS, ptr::null());
}
crate::bindings::gum_interceptor_replace(interceptor, entries.unload as gpointer,
kernel::on_module_unload as gpointer, &raw mut LOADER_UNLOAD, ptr::null());
crate::bindings::gum_interceptor_end_transaction(interceptor);
}
}
#[cfg(any(feature = "win9x", feature = "winnt"))]
pub(crate) fn forget_the_loader() {
let Some(entries) = kernel::loader_entry_points() else {
return;
};
unsafe {
let interceptor = crate::bindings::gum_interceptor_obtain();
crate::bindings::gum_interceptor_begin_transaction(interceptor);
crate::bindings::gum_interceptor_revert(interceptor, entries.load as gpointer);
if entries.load_with_flags != 0 {
crate::bindings::gum_interceptor_revert(interceptor,
entries.load_with_flags as gpointer);
}
crate::bindings::gum_interceptor_revert(interceptor, entries.unload as gpointer);
crate::bindings::gum_interceptor_end_transaction(interceptor);
}
}
#[cfg(any(feature = "win9x", feature = "winnt"))]
pub(crate) fn loader_load() -> gpointer {
unsafe { LOADER_LOAD }
}
#[cfg(any(feature = "win9x", feature = "winnt"))]
pub(crate) fn loader_load_with_flags() -> gpointer {
unsafe { LOADER_LOAD_WITH_FLAGS }
}
#[cfg(any(feature = "win9x", feature = "winnt"))]
pub(crate) fn loader_unload() -> gpointer {
unsafe { LOADER_UNLOAD }
}
#[cfg(feature = "win9x")]
pub(crate) fn module_arrived(base: u64, path: &str) {
if !known_mut().insert(base) {
return;
}
let range = GumMemoryRange {
base_address: base,
size: kernel::image_size(base as u32) as gsize,
};
unsafe {
let registry = crate::bindings::gum_module_registry_obtain();
crate::bindings::gum_module_registry_lock(registry);
let native = gum::gum_native_module_new(path, "", &range);
gum_barebone_register_module(registry, native);
g_object_unref(native as gpointer);
crate::bindings::gum_module_registry_unlock(registry);
}
}
// A module arrived, thus tell the registry where it is.
#[cfg(feature = "winnt")]
pub(crate) fn module_arrived(base: u64) {
if !known_mut().insert(base) {
return;
}
let Some(module) = kernel::describe_module(base) else {
known_mut().remove(&base);
return;
};
let range = GumMemoryRange {
base_address: module.base,
size: module.size as gsize,
};
unsafe {
let registry = crate::bindings::gum_module_registry_obtain();
crate::bindings::gum_module_registry_lock(registry);
let native = gum::gum_native_module_new(&module.path, "", &range);
gum_barebone_register_module(registry, native);
g_object_unref(native as gpointer);
crate::bindings::gum_module_registry_unlock(registry);
}
}
// A module went away, thus take it out of the registry while its name is still known.
#[cfg(any(feature = "win9x", feature = "winnt"))]
pub(crate) fn module_left(base: u64) {
if !known_mut().remove(&base) {
return;
}
unsafe {
let registry = crate::bindings::gum_module_registry_obtain();
crate::bindings::gum_module_registry_lock(registry);
crate::bindings::gum_barebone_unregister_module(registry, base);
crate::bindings::gum_module_registry_unlock(registry);
}
}
#[cfg(any(feature = "win9x", feature = "winnt"))]
fn known_mut() -> &'static mut alloc::collections::BTreeSet<u64> {
unsafe { (&raw mut KNOWN).as_mut().unwrap() }
}
#[cfg(any(feature = "win9x", feature = "winnt"))]
static mut KNOWN: alloc::collections::BTreeSet<u64> = alloc::collections::BTreeSet::new();
#[cfg(any(feature = "win9x", feature = "winnt"))]
static mut LOADER_LOAD: gpointer = ptr::null_mut();
#[cfg(any(feature = "win9x", feature = "winnt"))]
static mut LOADER_LOAD_WITH_FLAGS: gpointer = ptr::null_mut();
#[cfg(any(feature = "win9x", feature = "winnt"))]
static mut LOADER_UNLOAD: gpointer = ptr::null_mut();
fn register_the_process_modules(registry: *mut GumModuleRegistry) {
for module in kernel::enumerate_modules() {
#[cfg(any(feature = "win9x", feature = "winnt"))]
known_mut().insert(module.base);
let range = GumMemoryRange {
base_address: module.base,
size: module.size as gsize,
};
unsafe {
let native = gum::gum_native_module_new(&module.path, "", &range);
gum_barebone_register_module(registry, native);
g_object_unref(native as gpointer);
}
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _gum_process_enumerate_ranges(
prot: GumPageProtection,
func: GumFoundRangeFunc,
user_data: gpointer,
) {
let Some(emit) = func else {
return;
};
kernel::enumerate_ranges(&mut |base, size, protection| {
if (protection & prot as u32) != prot as u32 {
return;
}
let range = GumMemoryRange {
base_address: base,
size: size as gsize,
};
let details = GumRangeDetails {
range: &range,
protection: protection as GumPageProtection,
file: ptr::null(),
};
unsafe { emit(&details, user_data) };
});
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_on_thread_registry_activating(registry: *mut GumThreadRegistry) {
unsafe { THREAD_REGISTRY = registry };
kernel::enumerate_threads(&mut |thread| announce_thread(thread.id));
if kernel::watches_threads() {
kernel::watch_threads(announce_thread, announce_thread_is_gone);
} else {
watch_the_threads();
}
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_on_thread_registry_deactivating(_registry: *mut GumThreadRegistry) {
if kernel::watches_threads() {
kernel::forget_threads();
} else {
forget_the_threads();
}
unsafe { THREAD_REGISTRY = ptr::null_mut() };
}
fn watch_the_threads() {
let Some(entries) = kernel::thread_entry_points() else {
return;
};
unsafe {
let interceptor = crate::bindings::gum_interceptor_obtain();
crate::bindings::gum_interceptor_begin_transaction(interceptor);
crate::bindings::gum_interceptor_replace(interceptor, entries.start as gpointer,
kernel::on_thread_start as gpointer, kernel::thread_start_slot(), ptr::null());
crate::bindings::gum_interceptor_replace(interceptor, entries.exit as gpointer,
kernel::on_thread_exit as gpointer, kernel::thread_exit_slot(), ptr::null());
crate::bindings::gum_interceptor_end_transaction(interceptor);
}
}
fn forget_the_threads() {
let Some(entries) = kernel::thread_entry_points() else {
return;
};
unsafe {
let interceptor = crate::bindings::gum_interceptor_obtain();
crate::bindings::gum_interceptor_begin_transaction(interceptor);
crate::bindings::gum_interceptor_revert(interceptor, entries.start as gpointer);
crate::bindings::gum_interceptor_revert(interceptor, entries.exit as gpointer);
crate::bindings::gum_interceptor_end_transaction(interceptor);
}
}
pub(crate) fn thread_appeared(id: u32) {
announce_thread(id);
}
pub(crate) fn thread_appeared_at(id: u32, routine: usize, parameter: usize) {
let registry = unsafe { THREAD_REGISTRY };
if registry.is_null() {
return;
}
let mut details: GumThreadDetails = unsafe { core::mem::zeroed() };
details.id = id as GumThreadId;
details.entrypoint.routine = routine as u64;
details.entrypoint.parameter = parameter as u64;
details.flags = crate::bindings::GumThreadFlags_GUM_THREAD_FLAGS_ENTRYPOINT_ROUTINE
| crate::bindings::GumThreadFlags_GUM_THREAD_FLAGS_ENTRYPOINT_PARAMETER;
unsafe { crate::bindings::gum_barebone_register_thread(registry, &details) };
}
pub(crate) fn thread_vanished(id: u32) {
announce_thread_is_gone(id);
}
fn announce_thread(id: u32) {
let registry = unsafe { THREAD_REGISTRY };
if registry.is_null() {
return;
}
let mut details: GumThreadDetails = unsafe { core::mem::zeroed() };
details.id = id as GumThreadId;
unsafe { crate::bindings::gum_barebone_register_thread(registry, &details) };
}
fn announce_thread_is_gone(id: u32) {
let registry = unsafe { THREAD_REGISTRY };
if registry.is_null() {
return;
}
unsafe { crate::bindings::gum_barebone_unregister_thread(registry, id as GumThreadId) };
}
static mut THREAD_REGISTRY: *mut GumThreadRegistry = ptr::null_mut();
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_enumerate_threads(func: GumFoundThreadFunc, user_data: gpointer) {
let Some(emit) = func else {
return;
};
kernel::enumerate_threads(&mut |thread| {
let mut details: GumThreadDetails = unsafe { core::mem::zeroed() };
details.flags = 0;
details.id = thread.id as GumThreadId;
if let Some(state) = thread.cpu_state {
details.flags = GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT;
details.cpu_context = cpu_context_from(&state);
}
unsafe { emit(&details, user_data) };
});
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_find_thread_by_id(thread_id: GumThreadId, _flags: GumThreadFlags)
-> *mut GumThreadDetails {
let Some(thread) = kernel::find_thread(thread_id as u32) else {
return ptr::null_mut();
};
let mut details: GumThreadDetails = unsafe { core::mem::zeroed() };
details.id = thread.id as GumThreadId;
if let Some(state) = thread.cpu_state {
details.flags = GumThreadFlags_GUM_THREAD_FLAGS_CPU_CONTEXT;
details.cpu_context = cpu_context_from(&state);
}
unsafe { gum_thread_details_copy(&details) }
}
#[unsafe(no_mangle)]
pub extern "C" fn gum_barebone_modify_thread(thread_id: GumThreadId, func: GumModifyThreadFunc,
user_data: gpointer, _flags: GumModifyThreadFlags) -> gboolean {
let Some(modify) = func else {
return 0;
};
let changed = kernel::modify_thread(thread_id as u32, &mut |state| {
let mut context = cpu_context_from(state);
unsafe { modify(thread_id, &mut context, user_data) };
*state = cpu_state_from(&context);
});
changed as gboolean
}
#[cfg(target_arch = "x86")]
pub(crate) fn cpu_context_from(state: &kernel::CpuState) -> GumCpuContext {
GumCpuContext {
eip: state.eip,
edi: state.edi,
esi: state.esi,
ebp: state.ebp,
esp: state.esp,
ebx: state.ebx,
edx: state.edx,
ecx: state.ecx,
eax: state.eax,
xmm: ptr::null_mut(),
}
}
#[cfg(target_arch = "x86")]
pub(crate) fn cpu_state_from(context: &GumCpuContext) -> kernel::CpuState {
kernel::CpuState {
eip: context.eip,
edi: context.edi,
esi: context.esi,
ebp: context.ebp,
esp: context.esp,
ebx: context.ebx,
edx: context.edx,
ecx: context.ecx,
eax: context.eax,
}
}
#[cfg(target_arch = "x86_64")]
pub(crate) fn cpu_context_from(state: &kernel::CpuState) -> GumCpuContext {
GumCpuContext {
rip: state.rip,
r15: state.r15,
r14: state.r14,
r13: state.r13,
r12: state.r12,
r11: state.r11,
r10: state.r10,
r9: state.r9,
r8: state.r8,
rdi: state.rdi,
rsi: state.rsi,
rbp: state.rbp,
rsp: state.rsp,
rbx: state.rbx,
rdx: state.rdx,
rcx: state.rcx,
rax: state.rax,
xmm: ptr::null_mut(),
}
}
#[cfg(target_arch = "x86_64")]
pub(crate) fn cpu_state_from(context: &GumCpuContext) -> kernel::CpuState {
kernel::CpuState {
rip: context.rip,
r15: context.r15,
r14: context.r14,
r13: context.r13,
r12: context.r12,
r11: context.r11,
r10: context.r10,
r9: context.r9,
r8: context.r8,
rdi: context.rdi,
rsi: context.rsi,
rbp: context.rbp,
rsp: context.rsp,
rbx: context.rbx,
rdx: context.rdx,
rcx: context.rcx,
rax: context.rax,
}
}
pub(crate) fn fault_type_of(code: u32) -> crate::bindings::GumExceptionType {
use crate::bindings::*;
match code {
ACCESS_VIOLATION | MISALIGNED_DATA | ARRAY_BOUNDS_EXCEEDED =>
_GumExceptionType_GUM_EXCEPTION_ACCESS_VIOLATION,
GUARD_PAGE => _GumExceptionType_GUM_EXCEPTION_GUARD_PAGE,
ILLEGAL_INSTRUCTION | PRIVILEGED_INSTRUCTION =>
_GumExceptionType_GUM_EXCEPTION_ILLEGAL_INSTRUCTION,
STACK_OVERFLOW => _GumExceptionType_GUM_EXCEPTION_STACK_OVERFLOW,
FLOAT_DENORMAL_OPERAND | FLOAT_DIVIDE_BY_ZERO | FLOAT_INEXACT_RESULT
| FLOAT_INVALID_OPERATION | FLOAT_OVERFLOW | FLOAT_STACK_CHECK | FLOAT_UNDERFLOW
| INTEGER_DIVIDE_BY_ZERO | INTEGER_OVERFLOW =>
_GumExceptionType_GUM_EXCEPTION_ARITHMETIC,
BREAKPOINT => _GumExceptionType_GUM_EXCEPTION_BREAKPOINT,
SINGLE_STEP => _GumExceptionType_GUM_EXCEPTION_SINGLE_STEP,
_ => _GumExceptionType_GUM_EXCEPTION_SYSTEM,
}
}
const ACCESS_VIOLATION: u32 = 0xc000_0005;
const MISALIGNED_DATA: u32 = 0x8000_0002;
const ARRAY_BOUNDS_EXCEEDED: u32 = 0xc000_008c;
const GUARD_PAGE: u32 = 0x8000_0001;
const ILLEGAL_INSTRUCTION: u32 = 0xc000_001d;
const PRIVILEGED_INSTRUCTION: u32 = 0xc000_0096;
const STACK_OVERFLOW: u32 = 0xc000_00fd;
const FLOAT_DENORMAL_OPERAND: u32 = 0xc000_008d;
const FLOAT_DIVIDE_BY_ZERO: u32 = 0xc000_008e;
const FLOAT_INEXACT_RESULT: u32 = 0xc000_008f;
const FLOAT_INVALID_OPERATION: u32 = 0xc000_0090;
const FLOAT_OVERFLOW: u32 = 0xc000_0091;
const FLOAT_STACK_CHECK: u32 = 0xc000_0092;
const FLOAT_UNDERFLOW: u32 = 0xc000_0093;
const INTEGER_DIVIDE_BY_ZERO: u32 = 0xc000_0094;
const INTEGER_OVERFLOW: u32 = 0xc000_0095;
const BREAKPOINT: u32 = 0x8000_0003;
const SINGLE_STEP: u32 = 0x8000_0004;
pub(crate) unsafe fn enumerate_exports_in_range(
start_address: u64,
end_address: u64,
callback: &mut FoundExportCallback,
) {
#[cfg(any(feature = "win9x", feature = "winnt"))]
if crate::running_in_a_process() {
kernel::enumerate_exports(start_address as _, &mut |name, address| {
callback(name as *const crate::bindings::gchar, address)
});
return;
}
unsafe {
let symbol_table = &*core::ptr::addr_of!(crate::SYMBOL_TABLE);
for symbol_ref in symbol_table.iter_symbols_in_range(start_address, end_address) {
if !callback(symbol_ref.name_ptr(), symbol_ref.address()) {
break;
}
}
}
}
-116
View File
@@ -1,116 +0,0 @@
use core::sync::atomic::{AtomicBool, Ordering};
// The copy's own memory, which is not the C library's: that one asks for memory of its own
// while it is taking its lock, and a lock that allocates through the allocator it guards never
// finishes the first time it is asked.
pub fn take(size: usize) -> *mut u8 {
let wanted = wanted_for(size);
let class = class_of(wanted);
held(|| {
if let Some(piece) = pop(class) {
return piece;
}
carve(class)
})
}
pub fn give_back(piece: *mut u8) {
if piece.is_null() {
return;
}
let class = unsafe { *(piece.sub(HEADER) as *const usize) };
held(|| push(class, piece));
}
fn pages_for(size: usize) -> *mut u8 {
#[cfg(feature = "linux-injected")]
{
crate::linux::user::map_writable(size)
}
#[cfg(feature = "xnu-core")]
{
crate::xnu_user_calls::map_writable(size)
}
}
fn pop(class: usize) -> Option<*mut u8> {
let free = unsafe { (&raw mut FREE).as_mut().unwrap() };
let piece = free[class];
if piece.is_null() {
return None;
}
free[class] = unsafe { *(piece as *const *mut u8) };
Some(piece)
}
fn push(class: usize, piece: *mut u8) {
let free = unsafe { (&raw mut FREE).as_mut().unwrap() };
unsafe { *(piece as *mut *mut u8) = free[class] };
free[class] = piece;
}
fn carve(class: usize) -> *mut u8 {
let wanted = HEADER + (SMALLEST << class);
let mut edge = unsafe { EDGE };
let mut left = unsafe { LEFT };
if left < wanted {
let asked = if wanted > CHUNK { wanted } else { CHUNK };
edge = pages_for(asked) as usize;
if edge == 0 {
return core::ptr::null_mut();
}
left = asked;
}
unsafe {
EDGE = edge + wanted;
LEFT = left - wanted;
*(edge as *mut usize) = class;
}
(edge + HEADER) as *mut u8
}
fn held<T>(work: impl FnOnce() -> T) -> T {
while TAKEN.swap(true, Ordering::Acquire) {
core::hint::spin_loop();
}
let answer = work();
TAKEN.store(false, Ordering::Release);
answer
}
fn wanted_for(size: usize) -> usize {
if size < SMALLEST { SMALLEST } else { size }
}
fn class_of(size: usize) -> usize {
let mut class = 0;
while (SMALLEST << class) < size {
class += 1;
}
class
}
static mut FREE: [*mut u8; CLASSES] = [core::ptr::null_mut(); CLASSES];
static mut EDGE: usize = 0;
static mut LEFT: usize = 0;
static TAKEN: AtomicBool = AtomicBool::new(false);
const HEADER: usize = 16;
const SMALLEST: usize = 16;
const CLASSES: usize = 32;
const CHUNK: usize = 4 * 1024 * 1024;
-119
View File
@@ -1,119 +0,0 @@
// Linux transport: /dev/frida, framed the same way as the XNU hostlinks (4-byte
// little-endian length prefix) so the host-side (yqv) protocol is identical across
// every transport.
//
// The character device itself lives in linux/frida-kmod.c and hands us a byte
// stream; whoever opens the device decides how it reaches the host.
use core::cell::UnsafeCell;
use core::ffi::{c_int, c_void};
use alloc::vec::Vec;
struct Inner {
rx_lenbuf: [u8; 4],
rx_lenhave: usize,
rx_buf: Vec<u8>,
rx_have: usize,
rx_need: usize,
}
pub struct Hostlink {
state: UnsafeCell<Inner>,
on_rx: Option<fn(&[u8])>,
}
unsafe impl Send for Hostlink {}
impl Hostlink {
pub fn init(on_rx: Option<fn(&[u8])>) -> Result<Self, ()> {
if unsafe { frida_kmod_link_open() } != 0 {
return Err(());
}
Ok(Hostlink {
state: UnsafeCell::new(Inner {
rx_lenbuf: [0; 4],
rx_lenhave: 0,
rx_buf: Vec::new(),
rx_have: 0,
rx_need: 0,
}),
on_rx,
})
}
pub fn send(&self, payload: &[u8]) {
let header = (payload.len() as u32).to_le_bytes();
unsafe {
frida_kmod_link_send(header.as_ptr() as *const c_void, header.len());
frida_kmod_link_send(payload.as_ptr() as *const c_void, payload.len());
}
}
pub fn process(&self) {
let s = unsafe { &mut *self.state.get() };
loop {
if s.rx_lenhave < 4 {
let lo = s.rx_lenhave;
let n = recv_nonblocking(&mut s.rx_lenbuf[lo..4]);
if n == 0 {
return;
}
s.rx_lenhave += n;
if s.rx_lenhave < 4 {
continue;
}
s.rx_need = u32::from_le_bytes(s.rx_lenbuf) as usize;
s.rx_have = 0;
s.rx_buf.resize(s.rx_need, 0);
}
while s.rx_have < s.rx_need {
let lo = s.rx_have;
let hi = s.rx_need;
let n = recv_nonblocking(&mut s.rx_buf[lo..hi]);
if n == 0 {
return;
}
s.rx_have += n;
}
// Detach the frame and reset receive state before dispatching: the callback may
// re-enter process() (a synchronous host RPC issued while handling a command), and
// it must start from a clean state instead of re-dispatching this same frame.
let frame = core::mem::take(&mut s.rx_buf);
let need = s.rx_need;
s.rx_lenhave = 0;
s.rx_have = 0;
s.rx_need = 0;
if let Some(cb) = self.on_rx {
cb(&frame[..need]);
}
}
}
}
impl Drop for Hostlink {
fn drop(&mut self) {
unsafe { frida_kmod_link_close() };
}
}
pub fn a_turn_is_wanted() -> bool {
unsafe { frida_kmod_link_pending() }
}
fn recv_nonblocking(dst: &mut [u8]) -> usize {
let n = unsafe { frida_kmod_link_recv(dst.as_mut_ptr() as *mut c_void, dst.len()) };
if n <= 0 { 0 } else { n as usize }
}
unsafe extern "C" {
fn frida_kmod_link_open() -> c_int;
fn frida_kmod_link_close();
fn frida_kmod_link_send(data: *const c_void, size: usize) -> c_int;
fn frida_kmod_link_recv(data: *mut c_void, size: usize) -> isize;
fn frida_kmod_link_pending() -> bool;
}
File diff suppressed because it is too large. Load diff
+32 -37
View File
@@ -8,7 +8,7 @@ use core::sync::atomic::{AtomicU32, Ordering};
use alloc::vec::Vec;
use crate::kernel;
use crate::xnu;
const AF_VSOCK: c_int = 40;
const SOCK_STREAM: c_int = 1;
@@ -79,10 +79,6 @@ unsafe extern "C" {
static UPCALL_PENDING: AtomicU32 = AtomicU32::new(0);
pub fn a_turn_is_wanted() -> bool {
UPCALL_PENDING.load(Ordering::Acquire) != 0
}
struct Inner {
so: SocketT,
rx_lenbuf: [u8; 4],
@@ -157,17 +153,39 @@ impl Hostlink {
pub fn send(&self, payload: &[u8]) {
let s = unsafe { &*self.state.get() };
let length = (payload.len() as u32).to_le_bytes();
unsafe {
send_all(s.so, &length);
send_all(s.so, payload);
let len = payload.len() as u32;
let mut hdr = [
(len & 0xff) as u8,
((len >> 8) & 0xff) as u8,
((len >> 16) & 0xff) as u8,
((len >> 24) & 0xff) as u8,
];
let mut iov = [
Iovec {
iov_base: hdr.as_mut_ptr() as *mut c_void,
iov_len: 4,
},
Iovec {
iov_base: payload.as_ptr() as *mut c_void,
iov_len: payload.len(),
},
];
let msg = Msghdr {
msg_name: ptr::null_mut(),
msg_namelen: 0,
msg_iov: iov.as_mut_ptr(),
msg_iovlen: 2,
msg_control: ptr::null_mut(),
msg_controllen: 0,
msg_flags: 0,
};
let mut sent: usize = 0;
let _ = _sock_send(s.so, &msg, 0, &mut sent);
}
}
pub fn process(&self) {
UPCALL_PENDING.store(0, Ordering::Release);
let s = unsafe { &mut *self.state.get() };
loop {
if s.rx_lenhave < 4 {
@@ -241,32 +259,9 @@ unsafe fn recv_nonblocking(so: SocketT, dst: &mut [u8]) -> usize {
}
}
unsafe fn send_all(so: SocketT, bytes: &[u8]) {
let mut gone = 0;
while gone < bytes.len() {
let mut iov = [Iovec {
iov_base: unsafe { bytes.as_ptr().add(gone) } as *mut c_void,
iov_len: bytes.len() - gone,
}];
let msg = Msghdr {
msg_name: ptr::null_mut(),
msg_namelen: 0,
msg_iov: iov.as_mut_ptr(),
msg_iovlen: 1,
msg_control: ptr::null_mut(),
msg_controllen: 0,
msg_flags: 0,
};
let mut sent: usize = 0;
if unsafe { _sock_send(so, &msg, 0, &mut sent) } != 0 || sent == 0 {
return;
}
gone += sent;
}
}
unsafe extern "C" fn upcall(_so: SocketT, cookie: *mut c_void, _waitf: c_int) {
UPCALL_PENDING.fetch_add(1, Ordering::Release);
crate::nudge_the_loop(cookie as *const u8);
if !cookie.is_null() {
xnu::thread_wakeup(cookie as *const u8);
}
}
-377
View File
@@ -1,377 +0,0 @@
pub trait Image {
fn read_at(&self, position: u32, buffer: &mut [u8]) -> u32;
}
pub fn enumerate(image: &dyn Image, found: &mut dyn FnMut(&[u8])) {
let Some(resources) = resource_directory(image) else {
return;
};
let Some(group) = resources.first_leaf(RT_GROUP_ICON) else {
return;
};
// A group contains each size one time for each color depth. Keep only the best of each size.
let count = read_u16(image, group.offset + GROUP_COUNT_OFFSET) as u32;
for index in 0..count {
if !is_best_of_its_size(image, group.offset, count, index) {
continue;
}
let entry = group.offset + GROUP_ENTRIES_OFFSET + index * GROUP_ENTRY_SIZE;
let id = read_u16(image, entry + GROUP_ENTRY_ID_OFFSET);
if let Some(icon) = resources.leaf(RT_ICON, id) {
if let Some(bytes) = read_blob(image, icon.offset, icon.size) {
found(bytes);
}
}
}
}
pub fn describe(image: &dyn Image, into: &mut [u8]) -> usize {
if read_u16(image, read_u32(image, DOS_HEADERS_OFFSET)) == NE_SIGNATURE {
return describe_ne(image, into);
}
string_in_version(image, FILE_DESCRIPTION, into)
}
pub fn identify(image: &dyn Image, into: &mut [u8]) -> usize {
if read_u16(image, read_u32(image, DOS_HEADERS_OFFSET)) == NE_SIGNATURE {
let mut description = [0u8; 128];
let taken = describe_ne(image, &mut description);
let described = &description[..taken];
let Some(space) = described.iter().position(|b| *b == b' ') else {
return 0;
};
let program = &described[space + 1..];
if program.iter().filter(|b| b.is_ascii_alphanumeric()).count() > MAX_PROGRAM_WORD {
return 0;
}
return write_identity(&described[..space], program, into);
}
let mut company = [0u8; 64];
let mut taken_company = string_in_version(image, COMPANY_NAME, &mut company);
if taken_company == 0 {
taken_company = string_in_version(image, PRODUCT_NAME, &mut company);
}
let mut internal = [0u8; 64];
let mut taken_internal = string_in_version(image, INTERNAL_NAME, &mut internal);
if taken_internal == 0 {
taken_internal = string_in_version(image, ORIGINAL_FILENAME, &mut internal);
}
if taken_company == 0 || taken_internal == 0 {
return 0;
}
write_identity(&company[..taken_company], without_extension(&internal[..taken_internal]), into)
}
fn without_extension(name: &[u8]) -> &[u8] {
match name.iter().rposition(|b| *b == b'.') {
Some(dot) => &name[..dot],
None => name,
}
}
fn write_identity(company: &[u8], program: &[u8], into: &mut [u8]) -> usize {
let maker = match company.iter().position(|b| *b == b' ') {
Some(space) => &company[..space],
None => company,
};
let mut written = 0;
let mut put = |byte: u8, into: &mut [u8], written: &mut usize| {
if *written != into.len() {
into[*written] = byte;
*written += 1;
}
};
let mut put_word = |word: &[u8], into: &mut [u8], written: &mut usize| {
for byte in word {
if byte.is_ascii_alphanumeric() {
put(byte.to_ascii_lowercase(), into, written);
}
}
};
put_word(b"com", into, &mut written);
put(b'.', into, &mut written);
put_word(maker, into, &mut written);
put(b'.', into, &mut written);
put_word(program, into, &mut written);
written
}
fn string_in_version(image: &dyn Image, key: &[u8], into: &mut [u8]) -> usize {
let Some(resources) = resource_directory(image) else {
return 0;
};
let Some(version) = resources.first_leaf(RT_VERSION) else {
return 0;
};
let Some(blob) = read_blob(image, version.offset, version.size) else {
return 0;
};
let Some(at) = find(blob, key) else {
return 0;
};
// Each string in the block says how long its value is, in the six bytes before its key. A
// value of nothing leaves the next key where the value would be.
if at < 6 {
return 0;
}
let value_length = u16::from_le_bytes([blob[at - 4], blob[at - 3]]) as usize;
if value_length == 0 {
return 0;
}
let mut at = at + key.len();
while at % 4 != 0 {
at += 1;
}
let mut written = 0;
while at + 1 < blob.len() && written < into.len() && written < value_length {
let unit = blob[at] as u16 | ((blob[at + 1] as u16) << 8);
if unit == 0 {
break;
}
into[written] = if unit < 0x80 { unit as u8 } else { b'?' };
written += 1;
at += 2;
}
written
}
fn describe_ne(image: &dyn Image, into: &mut [u8]) -> usize {
let headers = read_u32(image, DOS_HEADERS_OFFSET);
let table = read_u32(image, headers + NE_NON_RESIDENT_NAMES_OFFSET);
if table == 0 {
return 0;
}
let mut length = [0u8; 1];
if image.read_at(table, &mut length) != 1 {
return 0;
}
let wanted = core::cmp::min(length[0] as usize, into.len());
image.read_at(table + 1, &mut into[..wanted]) as usize
}
fn find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
haystack.windows(needle.len()).position(|window| window == needle)
}
fn is_best_of_its_size(image: &dyn Image, group: u32, count: u32, index: u32) -> bool {
let mine = entry_shape(image, group, index);
(0..count).filter(|other| *other != index).all(|other| {
let theirs = entry_shape(image, group, other);
theirs.0 != mine.0 || theirs.1 != mine.1 || theirs.2 < mine.2
|| (theirs.2 == mine.2 && other > index)
})
}
fn entry_shape(image: &dyn Image, group: u32, index: u32) -> (u8, u8, u16) {
let entry = group + GROUP_ENTRIES_OFFSET + index * GROUP_ENTRY_SIZE;
(
read_u8(image, entry + GROUP_ENTRY_WIDTH_OFFSET),
read_u8(image, entry + GROUP_ENTRY_HEIGHT_OFFSET),
read_u16(image, entry + GROUP_ENTRY_DEPTH_OFFSET),
)
}
// A resource has the address that it gets after the loader maps it. The section gives the
// distance from that address to the position in the file.
fn resource_directory(image: &dyn Image) -> Option<ResourceDirectory<'_>> {
let headers = read_u32(image, DOS_HEADERS_OFFSET);
if read_u32(image, headers) != PE_SIGNATURE {
return None;
}
let optional = headers + OPTIONAL_HEADER_OFFSET;
let directory_rva = read_u32(image, optional + resource_directory_offset(image, optional));
if directory_rva == 0 {
return None;
}
let sections = optional + read_u16(image, headers + OPTIONAL_HEADER_SIZE_OFFSET) as u32;
let section_count = read_u16(image, headers + SECTION_COUNT_OFFSET) as u32;
for index in 0..section_count {
let section = sections + index * SECTION_SIZE;
let virtual_address = read_u32(image, section + SECTION_RVA_OFFSET);
let virtual_size = read_u32(image, section + SECTION_VIRTUAL_SIZE_OFFSET);
if directory_rva >= virtual_address && directory_rva < virtual_address + virtual_size {
let raw_data = read_u32(image, section + SECTION_RAW_DATA_OFFSET);
return Some(ResourceDirectory {
image,
rva: directory_rva,
offset: directory_rva - virtual_address + raw_data,
});
}
}
None
}
// The magic value gives the form of the optional header, and the two forms have different
// widths before the data directories.
fn resource_directory_offset(image: &dyn Image, optional: u32) -> u32 {
if read_u16(image, optional) == PE32_MAGIC {
return PE32_RESOURCE_DIRECTORY_OFFSET;
}
PE32PLUS_RESOURCE_DIRECTORY_OFFSET
}
struct ResourceDirectory<'i> {
image: &'i dyn Image,
rva: u32,
offset: u32,
}
struct Resource {
offset: u32,
size: u32,
}
impl ResourceDirectory<'_> {
fn first_leaf(&self, kind: u16) -> Option<Resource> {
let by_name = self.subdirectory(self.offset, kind)?;
let by_language = self.first_subdirectory(by_name)?;
self.leaf_at(self.first_subdirectory(by_language)?)
}
fn leaf(&self, kind: u16, name: u16) -> Option<Resource> {
let by_name = self.subdirectory(self.offset, kind)?;
let by_language = self.subdirectory(by_name, name)?;
self.leaf_at(self.first_subdirectory(by_language)?)
}
fn subdirectory(&self, directory: u32, id: u16) -> Option<u32> {
for entry in self.entries(directory) {
if read_u32(self.image, entry) == id as u32 {
return Some(self.child(entry));
}
}
None
}
fn first_subdirectory(&self, directory: u32) -> Option<u32> {
self.entries(directory).next().map(|e| self.child(e))
}
fn entries(&self, directory: u32) -> impl Iterator<Item = u32> + '_ {
let named = read_u16(self.image, directory + NAMED_ENTRY_COUNT_OFFSET) as u32;
let identified = read_u16(self.image, directory + ID_ENTRY_COUNT_OFFSET) as u32;
let first = directory + DIRECTORY_HEADER_SIZE + named * DIRECTORY_ENTRY_SIZE;
(0..identified).map(move |index| first + index * DIRECTORY_ENTRY_SIZE)
}
fn child(&self, entry: u32) -> u32 {
let value = read_u32(self.image, entry + DIRECTORY_ENTRY_CHILD_OFFSET);
self.offset + (value & !SUBDIRECTORY_FLAG)
}
fn leaf_at(&self, data_entry: u32) -> Option<Resource> {
let data_rva = read_u32(self.image, data_entry);
Some(Resource {
offset: self.offset + data_rva - self.rva,
size: read_u32(self.image, data_entry + DATA_ENTRY_SIZE_OFFSET),
})
}
}
fn read_u32(image: &dyn Image, position: u32) -> u32 {
let mut bytes = [0u8; 4];
image.read_at(position, &mut bytes);
u32::from_le_bytes(bytes)
}
fn read_u8(image: &dyn Image, position: u32) -> u8 {
let mut bytes = [0u8; 1];
image.read_at(position, &mut bytes);
bytes[0]
}
fn read_u16(image: &dyn Image, position: u32) -> u16 {
let mut bytes = [0u8; 2];
image.read_at(position, &mut bytes);
u16::from_le_bytes(bytes)
}
fn read_blob(image: &dyn Image, position: u32, size: u32) -> Option<&'static [u8]> {
let blob = unsafe { &mut *core::ptr::addr_of_mut!(ICON_BUFFER) };
if size as usize > blob.len() {
return None;
}
let read = image.read_at(position, &mut blob[..size as usize]);
if read != size {
return None;
}
Some(&blob[..size as usize])
}
static mut ICON_BUFFER: [u8; MAX_ICON_SIZE] = [0; MAX_ICON_SIZE];
const MAX_ICON_SIZE: usize = 16 * 1024;
const DOS_HEADERS_OFFSET: u32 = 0x3c;
const PE_SIGNATURE: u32 = 0x00004550;
const SECTION_COUNT_OFFSET: u32 = 0x06;
const OPTIONAL_HEADER_SIZE_OFFSET: u32 = 0x14;
const OPTIONAL_HEADER_OFFSET: u32 = 0x18;
const PE32_MAGIC: u16 = 0x010b;
const PE32_RESOURCE_DIRECTORY_OFFSET: u32 = 0x70;
const PE32PLUS_RESOURCE_DIRECTORY_OFFSET: u32 = 0x80;
const SECTION_SIZE: u32 = 0x28;
const SECTION_VIRTUAL_SIZE_OFFSET: u32 = 0x08;
const SECTION_RVA_OFFSET: u32 = 0x0c;
const SECTION_RAW_DATA_OFFSET: u32 = 0x14;
const RT_ICON: u16 = 3;
const RT_GROUP_ICON: u16 = 14;
const RT_VERSION: u16 = 16;
const NE_SIGNATURE: u16 = 0x454e;
const MAX_PROGRAM_WORD: usize = 24;
const NE_NON_RESIDENT_NAMES_OFFSET: u32 = 0x2c;
const COMPANY_NAME: &[u8] = b"C\0o\0m\0p\0a\0n\0y\0N\0a\0m\0e\0\0\0";
const INTERNAL_NAME: &[u8] = b"I\0n\0t\0e\0r\0n\0a\0l\0N\0a\0m\0e\0\0\0";
const PRODUCT_NAME: &[u8] = b"P\0r\0o\0d\0u\0c\0t\0N\0a\0m\0e\0\0\0";
const ORIGINAL_FILENAME: &[u8] =
b"O\0r\0i\0g\0i\0n\0a\0l\0F\0i\0l\0e\0n\0a\0m\0e\0\0\0";
const FILE_DESCRIPTION: &[u8] = b"F\0i\0l\0e\0D\0e\0s\0c\0r\0i\0p\0t\0i\0o\0n\0\0\0";
const NAMED_ENTRY_COUNT_OFFSET: u32 = 0x0c;
const ID_ENTRY_COUNT_OFFSET: u32 = 0x0e;
const DIRECTORY_HEADER_SIZE: u32 = 0x10;
const DIRECTORY_ENTRY_SIZE: u32 = 0x08;
const DIRECTORY_ENTRY_CHILD_OFFSET: u32 = 0x04;
const SUBDIRECTORY_FLAG: u32 = 0x8000_0000;
const DATA_ENTRY_SIZE_OFFSET: u32 = 0x04;
const GROUP_COUNT_OFFSET: u32 = 0x04;
const GROUP_ENTRIES_OFFSET: u32 = 0x06;
const GROUP_ENTRY_SIZE: u32 = 0x0e;
const GROUP_ENTRY_WIDTH_OFFSET: u32 = 0x00;
const GROUP_ENTRY_HEIGHT_OFFSET: u32 = 0x01;
const GROUP_ENTRY_DEPTH_OFFSET: u32 = 0x06;
const GROUP_ENTRY_ID_OFFSET: u32 = 0x0c;
-132
View File
@@ -1,132 +0,0 @@
// Facade over the host kernel the agent is running inside of. Both backends
// expose the same primitives, so the rest of the agent never names a specific
// kernel.
//
// A kernel is reached from the outside -- a remote stub injects the agent and
// patches the addresses it needs into .kernel_addrs -- or from the inside,
// where the agent is a kernel module whose glue is an ordinary C translation
// unit compiled against the target kernel's headers. Linux is reached either
// way; the others only from the outside.
#[cfg(any(feature = "linux", feature = "linux-injected"))]
pub use crate::linux::*;
#[cfg(feature = "win9x")]
pub use crate::win9x::*;
#[cfg(feature = "winnt")]
pub use crate::winnt::*;
#[cfg(feature = "xnu-core")]
pub use crate::xnu::*;
#[cfg(feature = "xnu-core")]
pub use crate::xnu_applications::*;
#[cfg(feature = "xnu-core")]
pub use crate::xnu_bell::*;
#[cfg(feature = "xnu-core")]
pub use crate::xnu_hiding::*;
#[cfg(feature = "xnu-core")]
pub use crate::xnu_injection::*;
#[cfg(feature = "xnu-core")]
pub use crate::xnu_processes::*;
#[cfg(feature = "xnu-core")]
pub use crate::xnu_relay::*;
#[cfg(feature = "xnu-core")]
pub use crate::xnu_spawn::*;
use core::ffi::c_void;
pub type ThreadEntry = unsafe extern "C" fn(parameter: *mut c_void, wait_result: i32);
pub struct ThreadInfo {
pub id: u32,
pub cpu_state: Option<CpuState>,
}
#[cfg(target_arch = "x86_64")]
pub struct CpuState {
pub rip: u64,
pub r15: u64,
pub r14: u64,
pub r13: u64,
pub r12: u64,
pub r11: u64,
pub r10: u64,
pub r9: u64,
pub r8: u64,
pub rdi: u64,
pub rsi: u64,
pub rbp: u64,
pub rsp: u64,
pub rbx: u64,
pub rdx: u64,
pub rcx: u64,
pub rax: u64,
}
#[cfg(target_arch = "arm")]
pub struct CpuState {
pub pc: u32,
pub sp: u32,
pub cpsr: u32,
pub r: [u32; 8],
pub r8: u32,
pub r9: u32,
pub r10: u32,
pub r11: u32,
pub r12: u32,
pub lr: u32,
}
#[cfg(target_arch = "aarch64")]
pub struct CpuState {
pub pc: u64,
pub sp: u64,
pub nzcv: u64,
pub x: [u64; 29],
pub fp: u64,
pub lr: u64,
}
#[cfg(target_arch = "x86")]
pub struct CpuState {
pub eip: u32,
pub edi: u32,
pub esi: u32,
pub ebp: u32,
pub esp: u32,
pub ebx: u32,
pub edx: u32,
pub ecx: u32,
pub eax: u32,
}
// What the host looked up before the agent started, for the kernels that say nothing about
// their own layout and leave nowhere for the agent to read it.
pub fn take_note_of(what: &str, number: u64) {
unsafe { noted_numbers() }.push((alloc::string::String::from(what), number));
}
pub fn noted(what: &str) -> Option<u64> {
unsafe { noted_numbers() }
.iter()
.find(|(name, _)| name == what)
.map(|(_, number)| *number)
}
pub fn take_note_of_mapping(address: u64, size: usize, protection: u32) {
unsafe { mappings() }.push((address, size, protection));
}
pub fn noted_mappings() -> &'static [(u64, usize, u32)] {
unsafe { mappings() }.as_slice()
}
unsafe fn mappings() -> &'static mut alloc::vec::Vec<(u64, usize, u32)> {
unsafe { (&raw mut MAPPINGS).as_mut().unwrap() }
}
static mut MAPPINGS: alloc::vec::Vec<(u64, usize, u32)> = alloc::vec::Vec::new();
unsafe fn noted_numbers() -> &'static mut alloc::vec::Vec<(alloc::string::String, u64)> {
unsafe { (&raw mut NOTED).as_mut().unwrap() }
}
static mut NOTED: alloc::vec::Vec<(alloc::string::String, u64)> = alloc::vec::Vec::new();
File diff suppressed because it is too large. Load diff
+81 -528
View File
@@ -1,193 +1,35 @@
// The C library's lower edge. Nothing here does real work: the agent has no
// filesystem and never exits, so these exist to satisfy references and to give
// malloc some memory to hand out.
//
// The two flavours link against different C libraries, and they disagree on what
// this layer is called. newlib asks for the underscore-prefixed syscall names;
// picolibc, which the soft-float flavour needs because newlib has no soft-float
// build for arm64, calls them without the prefix. The bodies are shared and the
// exported names live in the two `exports` modules at the end.
use core::ptr;
use core::sync::atomic::Ordering;
use crate::bindings::{GMutex, GRecMutex};
use crate::gthread;
// Serialises the C library. Which hooks the library reaches for differs: newlib asks
// for __malloc_lock/__malloc_unlock, picolibc for the retargetable lock API below,
// whose default implementation is an empty function — so on picolibc malloc runs
// unlocked unless we supply these, and any second thread corrupts the heap.
static mut LIBC_LOCK: GRecMutex = unsafe { core::mem::zeroed() };
static mut MALLOC_LOCK: GRecMutex = unsafe { core::mem::zeroed() };
fn libc_lock() {
gthread::g_rec_mutex_lock(ptr::addr_of_mut!(LIBC_LOCK));
}
fn libc_unlock() {
gthread::g_rec_mutex_unlock(ptr::addr_of_mut!(LIBC_LOCK));
offer_preemption_point();
}
fn offer_preemption_point() {
let total = ALLOCATIONS_TOTAL.fetch_add(1, Ordering::Relaxed) + 1;
if total % ALLOCATIONS_PER_YIELD == 0 {
crate::kernel::yield_now();
}
}
static ALLOCATIONS_TOTAL: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
const ALLOCATIONS_PER_YIELD: u32 = 1024;
// GLib's g_get_num_processors() probes this; report a single CPU so the
// parallel scanners fall back to their inline path.
#[unsafe(no_mangle)]
pub extern "C" fn sysconf(_name: i32) -> isize {
1
pub extern "C" fn __malloc_lock(_reent: *mut core::ffi::c_void) {
gthread::g_rec_mutex_lock(ptr::addr_of_mut!(MALLOC_LOCK));
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[unsafe(no_mangle)]
pub extern "C" fn __clear_cache(_start: *const u8, _end: *const u8) {}
#[cfg(all(target_arch = "arm", not(any(feature = "linux", feature = "linux-injected"))))]
#[unsafe(no_mangle)]
pub extern "C" fn __clear_cache(start: *const u8, end: *const u8) {
unsafe {
core::arch::asm!(
"push {{r7}}",
"mov r7, {number}",
"svc #0",
"pop {{r7}}",
number = in(reg) 0xf0002usize,
in("r0") start,
in("r1") end,
in("r2") 0,
options(nostack),
);
}
pub extern "C" fn __malloc_unlock(_reent: *mut core::ffi::c_void) {
gthread::g_rec_mutex_unlock(ptr::addr_of_mut!(MALLOC_LOCK));
}
#[cfg(all(target_arch = "arm", any(feature = "linux", feature = "linux-injected")))]
#[unsafe(no_mangle)]
pub extern "C" fn __clear_cache(start: *const u8, end: *const u8) {
unsafe {
let told: u32;
core::arch::asm!("mrc p15, 0, {}, c0, c0, 1", out(reg) told, options(nomem, nostack));
let data_line = 4usize << ((told >> 16) & 0xf);
let code_line = 4usize << (told & 0xf);
let mut at = (start as usize) & !(data_line - 1);
while at < end as usize {
core::arch::asm!("mcr p15, 0, {}, c7, c11, 1", in(reg) at, options(nostack));
at += data_line;
}
core::arch::asm!("dsb ish", options(nostack));
let mut at = (start as usize) & !(code_line - 1);
while at < end as usize {
core::arch::asm!("mcr p15, 0, {}, c7, c5, 1", in(reg) at, options(nostack));
at += code_line;
}
core::arch::asm!("dsb ish", "isb", options(nostack));
}
pub extern "C" fn _fini() {
}
#[cfg(target_arch = "arm")]
#[unsafe(no_mangle)]
pub extern "C" fn __aeabi_uread4(address: *const u8) -> u32 {
let mut bytes = [0u8; 4];
for i in 0..4 {
bytes[i] = unsafe { address.add(i).read_volatile() };
}
u32::from_ne_bytes(bytes)
pub extern "C" fn _exit(_status: i32) -> ! {
loop {}
}
#[cfg(target_arch = "arm")]
#[unsafe(no_mangle)]
pub extern "C" fn __aeabi_uread8(address: *const u8) -> u64 {
let mut bytes = [0u8; 8];
for i in 0..8 {
bytes[i] = unsafe { address.add(i).read_volatile() };
}
u64::from_ne_bytes(bytes)
pub extern "C" fn _kill(_pid: i32, _sig: i32) -> i32 {
-1
}
#[cfg(target_arch = "arm")]
#[unsafe(no_mangle)]
pub extern "C" fn __aeabi_uwrite4(value: u32, address: *mut u8) -> u32 {
let bytes = value.to_ne_bytes();
for i in 0..4 {
unsafe { address.add(i).write_volatile(bytes[i]) };
}
value
}
#[cfg(target_arch = "arm")]
#[unsafe(no_mangle)]
pub extern "C" fn __aeabi_uwrite8(value: u64, address: *mut u8) -> u64 {
let bytes = value.to_ne_bytes();
for i in 0..8 {
unsafe { address.add(i).write_volatile(bytes[i]) };
}
value
}
#[cfg(all(target_arch = "aarch64", not(any(feature = "linux-injected", feature = "xnu-core"))))]
#[unsafe(no_mangle)]
pub extern "C" fn __clear_cache(_start: *const u8, _end: *const u8) {
unsafe {
core::arch::asm!(
"ic iallu", // Invalidate all instruction caches
"dsb sy", // Data synchronization barrier
"isb", // Instruction synchronization barrier
options(nomem, nostack),
);
}
}
// Invalidating the whole instruction cache is the kernel's to do, and the copy runs where that
// instruction is not allowed. Line by line is what both halves may ask for.
#[cfg(all(target_arch = "aarch64", any(feature = "linux-injected", feature = "xnu-core")))]
#[unsafe(no_mangle)]
pub extern "C" fn __clear_cache(start: *const u8, end: *const u8) {
#[cfg(feature = "xnu-core")]
let told = crate::kernel::cache_shape();
#[cfg(not(feature = "xnu-core"))]
let told: u64 = {
let read: u64;
unsafe { core::arch::asm!("mrs {}, ctr_el0", out(reg) read, options(nomem, nostack)) };
read
};
let data_line = 4usize << ((told >> 16) & 0xf);
let code_line = 4usize << (told & 0xf);
let data_is_coherent = (told >> 28) & 1 != 0;
let code_is_coherent = (told >> 29) & 1 != 0;
if !data_is_coherent {
let mut at = start as usize & !(data_line - 1);
while at < end as usize {
unsafe { core::arch::asm!("dc cvau, {}", in(reg) at, options(nostack)) };
at += data_line;
}
}
unsafe { core::arch::asm!("dsb ish", options(nomem, nostack)) };
if !code_is_coherent {
let mut at = start as usize & !(code_line - 1);
while at < end as usize {
unsafe { core::arch::asm!("ic ivau, {}", in(reg) at, options(nostack)) };
at += code_line;
}
unsafe { core::arch::asm!("dsb ish", options(nomem, nostack)) };
}
unsafe { core::arch::asm!("isb", options(nomem, nostack)) };
}
fn sbrk_impl(incr: isize) -> *mut u8 {
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();
@@ -198,7 +40,7 @@ fn sbrk_impl(incr: isize) -> *mut u8 {
gthread::g_mutex_lock(ptr::addr_of_mut!(HEAP_MUTEX));
if HEAP_START.is_null() {
HEAP_START = crate::kernel::alloc_heap(HEAP_SIZE);
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");
@@ -207,8 +49,7 @@ fn sbrk_impl(incr: isize) -> *mut u8 {
}
let prev_heap_current = HEAP_CURRENT;
// Signed: the allocator hands memory back by asking for a negative increment.
let new_heap_current = HEAP_CURRENT.offset(incr);
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));
@@ -224,14 +65,20 @@ fn sbrk_impl(incr: isize) -> *mut u8 {
}
}
#[unsafe(no_mangle)]
pub extern "C" fn _getpid() -> i32 {
0
}
#[repr(C)]
struct Timeval {
tv_sec: i64,
tv_usec: i64,
}
fn gettimeofday_impl(tp: *mut core::ffi::c_void) -> i32 {
let (secs, microsecs) = crate::kernel::wall_clock_micros();
#[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,
@@ -246,362 +93,68 @@ fn gettimeofday_impl(tp: *mut core::ffi::c_void) -> i32 {
0
}
fn write_impl(count: usize) -> isize {
#[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
}
mod exports {
use super::*;
// The C library's own allocator sits on a fixed sbrk heap and carries its own
// locking; inside a kernel module the kernel's allocator is already there, already
// safe to share between threads, and not capped at a size we picked up front.
// Every C allocation goes here instead.
//
// The sixteen bytes before the returned pointer hold the block the kernel gave us
// and its total size, so a pointer that was moved forward to satisfy an alignment
// request frees exactly like one that was not.
use core::ffi::c_void;
const HEAP_HEADER_SIZE: usize = 16;
const MALLOC_ALIGNMENT: usize = 2 * core::mem::size_of::<*const u8>();
#[unsafe(no_mangle)]
pub extern "C" fn malloc(size: usize) -> *mut c_void {
let total = size + HEAP_HEADER_SIZE + MALLOC_ALIGNMENT;
let block = crate::kernel::alloc(total);
if block.is_null() {
return ptr::null_mut();
}
offer_preemption_point();
unsafe {
let lowest = block.add(HEAP_HEADER_SIZE) as usize;
let aligned = (lowest + MALLOC_ALIGNMENT - 1) & !(MALLOC_ALIGNMENT - 1);
attach_header(block, total, aligned as *mut u8)
}
}
#[unsafe(no_mangle)]
pub extern "C" fn posix_memalign(
result: *mut *mut c_void,
alignment: usize,
size: usize,
) -> i32 {
let total = size + alignment + HEAP_HEADER_SIZE;
let block = crate::kernel::alloc(total);
if block.is_null() {
return 12; // ENOMEM
}
unsafe {
let lowest = block.add(HEAP_HEADER_SIZE) as usize;
let aligned = (lowest + alignment - 1) & !(alignment - 1);
*result = attach_header(block, total, aligned as *mut u8);
}
0
}
#[unsafe(no_mangle)]
pub extern "C" fn free(p: *mut c_void) {
if p.is_null() {
return;
}
unsafe {
let (block, total) = header_of(p);
crate::kernel::free(block, total);
}
}
#[unsafe(no_mangle)]
pub extern "C" fn malloc_usable_size(p: *mut c_void) -> usize {
unsafe { usable_size(p) }
}
#[unsafe(no_mangle)]
pub extern "C" fn calloc(count: usize, size: usize) -> *mut c_void {
let total = count * size;
let p = malloc(total);
if !p.is_null() {
unsafe { ptr::write_bytes(p as *mut u8, 0, total) };
}
p
}
#[unsafe(no_mangle)]
pub extern "C" fn realloc(p: *mut c_void, size: usize) -> *mut c_void {
if p.is_null() {
return malloc(size);
}
let previous_size = unsafe { usable_size(p) };
let grown = malloc(size);
if !grown.is_null() {
unsafe {
ptr::copy_nonoverlapping(p as *const u8, grown as *mut u8, previous_size.min(size))
};
free(p);
}
grown
}
unsafe fn attach_header(block: *mut u8, total: usize, p: *mut u8) -> *mut c_void {
unsafe {
let header = p.sub(HEAP_HEADER_SIZE) as *mut usize;
*header = block as usize;
*header.add(1) = total;
}
p as *mut c_void
}
unsafe fn header_of(p: *mut c_void) -> (*mut u8, usize) {
unsafe {
let header = (p as *mut u8).sub(HEAP_HEADER_SIZE) as *const usize;
(*header as *mut u8, *header.add(1))
}
}
unsafe fn usable_size(p: *mut c_void) -> usize {
unsafe {
let (block, total) = header_of(p);
total - (p as usize - block as usize)
}
}
// One recursive mutex covers every lock the C library asks for. They are coarse
// and few, and over-serialising them is safe where getting it wrong is not.
type Lock = *mut core::ffi::c_void;
// The lock object itself, which we define so the C library's own no-op lock
// implementation is never pulled out of its archive. Its contents are never
// read: the address is just a token, and one mutex covers them all.
// Nothing here references it, so without #[used] it is dropped before the linker
// ever sees it, and the archive member arrives after all to supply it.
#[allow(non_upper_case_globals)]
#[unsafe(no_mangle)]
#[used]
pub static mut __lock___libc_recursive_mutex: u8 = 0;
#[unsafe(no_mangle)]
pub extern "C" fn __retarget_lock_init(_lock: *mut Lock) {}
#[unsafe(no_mangle)]
pub extern "C" fn __retarget_lock_init_recursive(_lock: *mut Lock) {}
#[unsafe(no_mangle)]
pub extern "C" fn __retarget_lock_close(_lock: Lock) {}
#[unsafe(no_mangle)]
pub extern "C" fn __retarget_lock_close_recursive(_lock: Lock) {}
#[unsafe(no_mangle)]
pub extern "C" fn __retarget_lock_acquire(_lock: Lock) {
libc_lock();
}
#[unsafe(no_mangle)]
pub extern "C" fn __retarget_lock_acquire_recursive(_lock: Lock) {
libc_lock();
}
#[unsafe(no_mangle)]
pub extern "C" fn __retarget_lock_release(_lock: Lock) {
libc_unlock();
}
#[unsafe(no_mangle)]
pub extern "C" fn __retarget_lock_release_recursive(_lock: Lock) {
libc_unlock();
}
#[unsafe(no_mangle)]
pub extern "C" fn exit(_status: i32) -> ! {
loop {}
}
#[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 {
sbrk_impl(incr)
}
#[unsafe(no_mangle)]
pub extern "C" fn getpid() -> i32 {
crate::kernel::current_process_id() as i32
}
#[unsafe(no_mangle)]
pub extern "C" fn gettimeofday(
tp: *mut core::ffi::c_void,
_tzp: *mut core::ffi::c_void,
) -> i32 {
gettimeofday_impl(tp)
}
#[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 {
write_impl(count)
}
#[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 stat(_path: *const u8, _st: *mut core::ffi::c_void) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn unlink(_path: *const u8) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn rename(_old: *const u8, _new: *const u8) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn access(_path: *const u8, _mode: i32) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn chmod(_path: *const u8, _mode: i32) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn chdir(_path: *const u8) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn rmdir(_path: *const u8) -> i32 {
-1
#[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
}
// GLib's g_get_num_processors() probes this; report a single CPU so the
// parallel scanners fall back to their inline path.
#[unsafe(no_mangle)]
pub extern "C" fn sysconf(_name: i32) -> isize {
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),
);
}
#[unsafe(no_mangle)]
pub extern "C" fn gethostname(_name: *mut u8, _len: usize) -> i32 {
-1
}
#[repr(C)]
pub struct Timespec {
tv_sec: i64,
tv_nsec: i64,
}
const CLOCK_MONOTONIC: i32 = 1;
#[unsafe(no_mangle)]
pub extern "C" fn clock_gettime(clock_id: i32, tp: *mut Timespec) -> i32 {
let (secs, nsecs) = if clock_id == CLOCK_MONOTONIC {
let micros = crate::kernel::monotonic_micros();
(micros / 1_000_000, (micros % 1_000_000) * 1000)
} else {
let (secs, micros) = crate::kernel::wall_clock_micros();
(secs as i64, (micros as i64) * 1000)
};
unsafe {
(*tp).tv_sec = secs;
(*tp).tv_nsec = nsecs;
}
0
}
// picolibc consults this for errno values it does not know itself.
#[unsafe(no_mangle)]
pub extern "C" fn _user_strerror(_errnum: i32) -> *const core::ffi::c_char {
ptr::null()
}
// No dynamic loader here. Gum calls dladdr() to describe an address and already
// falls back to printing it raw when the lookup fails.
#[unsafe(no_mangle)]
pub extern "C" fn dladdr(_address: *const core::ffi::c_void, _info: *mut core::ffi::c_void) -> i32 {
0
}
#[unsafe(no_mangle)]
pub extern "C" fn dlopen(_file: *const u8, _mode: i32) -> *mut core::ffi::c_void {
ptr::null_mut()
}
#[unsafe(no_mangle)]
pub extern "C" fn dlsym(
_handle: *mut core::ffi::c_void,
_name: *const u8,
) -> *mut core::ffi::c_void {
ptr::null_mut()
}
#[unsafe(no_mangle)]
pub extern "C" fn dlclose(_handle: *mut core::ffi::c_void) -> i32 {
-1
}
#[unsafe(no_mangle)]
pub extern "C" fn dlerror() -> *mut core::ffi::c_char {
ptr::null_mut()
}
// picolibc declares the standard streams and leaves defining them to the
// application. The agent has no I/O — its logging goes to the kernel's own log —
// so nothing should ever reach for one of these.
#[repr(transparent)]
pub struct Stream(*mut core::ffi::c_void);
unsafe impl Sync for Stream {}
#[unsafe(no_mangle)]
pub static stdin: Stream = Stream(ptr::null_mut());
#[unsafe(no_mangle)]
pub static stdout: Stream = Stream(ptr::null_mut());
#[unsafe(no_mangle)]
pub static stderr: Stream = Stream(ptr::null_mut());
}
-154
View File
@@ -1,154 +0,0 @@
use core::sync::atomic::{AtomicU32, Ordering};
// What the kernel half and the copy leave for each other. Both reach it by its own address, so
// each side is told only where it begins.
#[derive(Clone, Copy)]
pub struct Arena {
begins: usize,
}
impl Arena {
pub fn at(begins: usize) -> Self {
Arena { begins }
}
pub fn at_offset(&self, offset: usize) -> usize {
self.begins + offset
}
// Until the frames flow, what the copy has to say is left here for the kernel half to read.
pub fn say(&self, msg: &str) {
let said = msg.as_bytes();
let length = said.iter().position(|byte| *byte == 0).unwrap_or(said.len());
let kept = length.min(SAID_SIZE - 1);
unsafe {
core::ptr::copy_nonoverlapping(said.as_ptr(), (self.begins + SAID) as *mut u8, kept);
((self.begins + SAID + kept) as *mut u8).write(0);
}
}
pub fn said(&self) -> &str {
let bytes =
unsafe { core::slice::from_raw_parts((self.begins + SAID) as *const u8, SAID_SIZE) };
let length = bytes.iter().position(|byte| *byte == 0).unwrap_or(SAID_SIZE);
core::str::from_utf8(&bytes[..length]).unwrap_or("")
}
pub fn page_size(&self) -> u32 {
self.word(PAGE_SIZE).load(Ordering::Acquire)
}
pub fn leave(&self, offset: usize, value: u64) {
unsafe { ((self.begins + offset) as *mut u64).write_volatile(value) };
}
pub fn progress(&self) -> u32 {
self.word(PROGRESS).load(Ordering::Acquire)
}
pub fn note(&self, step: u32) {
self.word(PROGRESS).store(step, Ordering::Release);
}
pub fn home(&self) -> u32 {
self.word(HOME).load(Ordering::Acquire)
}
pub fn report_home(&self) {
self.word(REPORTED).store(self.word(HOME).load(Ordering::Acquire), Ordering::Release);
}
pub fn reachable_at(&self, says: u32, hears: u32) {
self.word(SAYS).store(says, Ordering::Release);
self.word(HEARS).store(hears, Ordering::Release);
}
pub fn note_a_thread(&self, thread: u32, is_gone: bool) {
let head = self.word(THREADS_HEAD).load(Ordering::Relaxed);
let tail = self.word(THREADS_TAIL).load(Ordering::Acquire);
if head.wrapping_sub(tail) == THREAD_SLOTS {
return;
}
let slot = (head % THREAD_SLOTS) as usize;
self.word(THREADS + (slot * 4)).store((thread << 1) | is_gone as u32, Ordering::Relaxed);
self.word(THREADS_HEAD).store(head.wrapping_add(1), Ordering::Release);
}
pub fn take_a_thread(&self) -> Option<(u32, bool)> {
let head = self.word(THREADS_HEAD).load(Ordering::Acquire);
let tail = self.word(THREADS_TAIL).load(Ordering::Relaxed);
if head == tail {
return None;
}
let slot = (tail % THREAD_SLOTS) as usize;
let noted = self.word(THREADS + (slot * 4)).load(Ordering::Relaxed);
self.word(THREADS_TAIL).store(tail.wrapping_add(1), Ordering::Release);
Some((noted >> 1, noted & 1 != 0))
}
pub fn holds_a_thread(&self) -> bool {
self.word(THREADS_HEAD).load(Ordering::Acquire)
!= self.word(THREADS_TAIL).load(Ordering::Relaxed)
}
pub fn tell_it_to_go(&self) {
self.word(GO).store(1, Ordering::Release);
}
pub fn was_told_to_go(&self) -> bool {
self.word(GO).load(Ordering::Acquire) != 0
}
pub fn gone(&self) {
self.word(GONE).store(1, Ordering::Release);
}
pub fn has_gone(&self) -> bool {
self.word(GONE).load(Ordering::Acquire) != 0
}
pub fn says_through(&self) -> u32 {
self.word(SAYS).load(Ordering::Acquire)
}
pub fn hears_through(&self) -> u32 {
self.word(HEARS).load(Ordering::Acquire)
}
pub fn tell_the_kernel_half(&self) {
self.word(TO_KERNEL).fetch_add(1, Ordering::AcqRel);
super::user::say_something();
}
fn word(&self, offset: usize) -> &AtomicU32 {
unsafe { &*((self.begins + offset) as *const AtomicU32) }
}
}
pub const PROGRESS: usize = 20;
pub const PAGE_SIZE: usize = 48;
pub const FAULT_KIND: usize = 24;
pub const FAULT_ADDRESS: usize = 32;
pub const FAULT_PC: usize = 40;
pub const FAULT_LR: usize = 56;
pub const SAID: usize = 64;
pub const GO: usize = 288;
pub const GONE: usize = 292;
const THREADS_HEAD: usize = 296;
const THREADS_TAIL: usize = 300;
const THREADS: usize = 304;
const THREAD_SLOTS: u32 = 32;
pub const SAID_SIZE: usize = 192;
pub const REPORTED: usize = 0;
pub const HOME: usize = 4;
pub const SAYS: usize = 8;
pub const HEARS: usize = 52;
pub const TO_KERNEL: usize = 12;
pub const WOKEN: usize = 16;
-137
View File
@@ -1,137 +0,0 @@
use core::ffi::c_void;
use crate::kernel::ThreadEntry;
pub fn log(msg: &str) {
(primitives().log)(msg)
}
pub fn panic(msg: &str) -> ! {
(primitives().panic)(msg)
}
pub fn alloc(size: usize) -> *mut u8 {
(primitives().alloc)(size)
}
pub fn free(ptr: *mut u8, size: usize) {
(primitives().free)(ptr, size)
}
pub fn alloc_heap(size: usize) -> *mut u8 {
(primitives().alloc_heap)(size)
}
pub fn alloc_code(size: usize) -> *mut u8 {
(primitives().alloc_code)(size)
}
pub fn free_code(ptr: *mut u8, size: usize) {
(primitives().free_code)(ptr, size)
}
pub fn page_size() -> usize {
(primitives().page_size)()
}
pub fn protect(address: u64, size: usize, protection: u32) -> bool {
(primitives().protect)(address, size, protection)
}
pub fn spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> isize {
(primitives().spawn_thread)(entry, parameter)
}
pub fn wait(token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut() -> bool) {
(primitives().wait)(token, timeout_us, check)
}
pub fn wake(token: *const u8) {
(primitives().wake)(token)
}
pub fn yield_now() {
(primitives().yield_now)()
}
pub fn monotonic_micros() -> i64 {
(primitives().monotonic_micros)()
}
pub fn wall_clock_micros() -> (u32, u32) {
(primitives().wall_clock_micros)()
}
pub fn home_process_id() -> u32 {
(primitives().home_process_id)()
}
pub fn current_process_id() -> u32 {
(primitives().current_process_id)()
}
pub fn current_thread_id() -> u64 {
(primitives().current_thread_id)()
}
pub fn install_fault_reporter() {
(primitives().install_fault_reporter)()
}
pub fn select_user() {
unsafe { ACTIVE = &super::user::USER };
}
pub fn in_copy() -> bool {
core::ptr::eq(primitives(), &super::user::USER)
}
fn primitives() -> &'static Primitives {
unsafe { ACTIVE }
}
static mut ACTIVE: &Primitives = &KERNEL;
static KERNEL: Primitives = Primitives {
log: super::native::log,
panic: super::native::panic,
alloc: super::native::alloc,
free: super::native::free,
alloc_heap: super::native::alloc,
alloc_code: super::native::alloc_code,
free_code: super::native::free_code,
page_size: super::native::page_size,
protect: super::native::protect,
spawn_thread: super::native::spawn_thread,
wait: super::native::wait,
wake: super::native::wake,
yield_now: super::native::yield_now,
monotonic_micros: super::native::monotonic_micros,
wall_clock_micros: super::native::wall_clock_micros,
home_process_id: super::native::current_process_id,
current_process_id: super::native::current_process_id,
current_thread_id: super::native::current_thread_id,
install_fault_reporter: super::native::install_fault_reporter,
};
pub struct Primitives {
pub log: fn(&str),
pub panic: fn(&str) -> !,
pub alloc: fn(usize) -> *mut u8,
pub free: fn(*mut u8, usize),
pub alloc_heap: fn(usize) -> *mut u8,
pub alloc_code: fn(usize) -> *mut u8,
pub free_code: fn(*mut u8, usize),
pub page_size: fn() -> usize,
pub protect: fn(u64, usize, u32) -> bool,
pub spawn_thread: fn(ThreadEntry, *mut c_void) -> isize,
pub wait: fn(*const u8, Option<u64>, &mut dyn FnMut() -> bool),
pub wake: fn(*const u8),
pub yield_now: fn(),
pub monotonic_micros: fn() -> i64,
pub wall_clock_micros: fn() -> (u32, u32),
pub home_process_id: fn() -> u32,
pub current_process_id: fn() -> u32,
pub current_thread_id: fn() -> u64,
pub install_fault_reporter: fn(),
}
-874
View File
@@ -1,874 +0,0 @@
use core::ffi::{c_int, c_void};
use core::ptr;
use alloc::collections::BTreeMap;
use alloc::string::String;
use super::layout::{field_offset, struct_size};
use super::STACK_SPAN;
use super::native;
use super::processes::task_with_id;
use super::arena::{Arena, HOME, REPORTED, WOKEN};
use super::user::entry_offset;
#[cfg(target_arch = "arm")]
use _arm_copy_to_user as copy_to_user;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use __copy_to_user as copy_to_user;
#[cfg(not(any(target_arch = "arm", target_arch = "x86", target_arch = "x86_64")))]
use ___arch_copy_to_user as copy_to_user;
pub fn inject_into_process(id: u32) -> u32 {
if let Some(placed) = unsafe { placements() }.get_mut(&id) {
let home = pid_reported_by(placed);
if home != 0 && placed.says.is_null() {
take_what_the_copy_opened(placed);
}
return home;
}
let Some(task) = task_with_id(id) else {
return 0;
};
let Some(home) = give_the_copy_a_home(task, id) else {
return 0;
};
let placed = Placement {
task,
base: home.base,
arena: home.arena,
seen_by_the_copy: home.seen_by_the_copy,
stack: home.stack,
says: ptr::null_mut(),
hears: ptr::null_mut(),
};
unsafe { placements() }.insert(id, placed);
watch_the_threads();
0
}
fn take_what_the_copy_opened(placed: &mut Placement) {
let arena = Arena::at(placed.arena);
placed.says = native::take_the_file(arena.says_through());
placed.hears = native::take_the_file(arena.hears_through());
listen_to_the_copy(placed.says);
}
fn listen_to_the_copy(says: *mut c_void) {
native::spawn_thread(carry_what_the_copy_says, says);
}
unsafe extern "C" fn carry_what_the_copy_says(argument: *mut c_void, _reason: i32) {
while native::wait_for_a_word(argument) {
native::wake(argument as *const u8);
}
}
struct Placement {
task: usize,
base: usize,
arena: usize,
seen_by_the_copy: usize,
stack: usize,
says: *mut c_void,
hears: *mut c_void,
}
fn pid_reported_by(placed: &Placement) -> u32 {
unsafe { ((placed.arena + REPORTED) as *const u32).read_volatile() }
}
// The copy is given a task of its own that shares the address space it was mapped into, so
// nothing of the target is borrowed and a target that never runs is no obstacle.
struct Home {
base: usize,
arena: usize,
seen_by_the_copy: usize,
stack: usize,
}
fn give_the_copy_a_home(task: usize, id: u32) -> Option<Home> {
let memory = unsafe { _get_task_mm(task as *mut c_void) };
if memory.is_null() {
return None;
}
unsafe { _kthread_use_mm(memory) };
let placed = map_and_start(id);
unsafe { _kthread_unuse_mm(memory) };
unsafe { _mmput(memory) };
placed
}
fn map_and_start(id: u32) -> Option<Home> {
let base = map_a_copy()?;
let stack = map_a_stack()?;
let where_the_copy_sees_it = base + crate::own_range().1;
give(
where_the_copy_sees_it + HOME,
&id as *const u32 as usize,
size_of::<u32>(),
)?;
let page = native::page_size() as u32;
give(
where_the_copy_sees_it + super::arena::PAGE_SIZE,
&page as *const u32 as usize,
size_of::<u32>(),
)?;
let arena = view_of(where_the_copy_sees_it)?;
start(base, where_the_copy_sees_it, stack);
Some(Home {
base,
arena,
seen_by_the_copy: where_the_copy_sees_it,
stack,
})
}
// The kernel half reaches the same pages by an address of its own, so what the two leave for
// each other needs neither the address space borrowed nor a copy made.
fn borrowed_memory() -> Option<usize> {
let at = field_offset("task_struct", "mm")?;
Some(unsafe { ((native::current_task() as usize + at) as *const usize).read() })
}
fn view_of(seen_by_the_copy: usize) -> Option<usize> {
let count = ARENA_SIZE / PAGE_SIZE;
let pages = native::alloc(count * size_of::<usize>()) as *mut c_void;
let held = unsafe { _get_user_pages_unlocked(seen_by_the_copy, count, pages, FOLL_WRITE) };
if held != count as isize {
native::free(pages as *mut u8, count * size_of::<usize>());
return None;
}
let view = native::map_pages(pages, count);
native::free(pages as *mut u8, count * size_of::<usize>());
if view.is_null() {
return None;
}
Some(view as usize)
}
fn map_a_stack() -> Option<usize> {
let base = unsafe {
_vm_mmap(
ptr::null_mut(),
0,
STACK_SIZE,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
0,
)
};
if base >= FIRST_ERROR_ADDRESS {
return None;
}
Some(base + STACK_SIZE - STACK_HEADROOM)
}
fn map_a_copy() -> Option<usize> {
let (own, size) = crate::own_range();
let shared = crate::writable_half_start() - own;
let private = crate::writable_half_size();
let base = unsafe {
_vm_mmap(
ptr::null_mut(),
0,
size + ARENA_SIZE,
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_PRIVATE | MAP_ANONYMOUS,
0,
)
};
if base >= FIRST_ERROR_ADDRESS {
return None;
}
give(base, own, shared)?;
let rebased = native::alloc(private);
unsafe { crate::install_writable_half(base, rebased as usize) };
let handed_over = give(base + shared, rebased as usize, private);
native::free(rebased, private);
handed_over?;
Some(base)
}
fn give(destination: usize, source: usize, size: usize) -> Option<()> {
let opened = unsafe { open_user_access() };
let left =
unsafe { copy_to_user(destination as *mut c_void, source as *const c_void, size) };
unsafe { close_user_access(opened) };
if left != 0 {
if left == size {
} else if left < size {
} else {
}
return None;
}
Some(())
}
#[cfg(target_arch = "arm")]
unsafe fn open_user_access() -> usize {
let domains: usize;
unsafe {
core::arch::asm!("mrc p15, 0, {}, c3, c0, 0", out(reg) domains, options(nostack));
let opened = (domains & !USER_DOMAIN_MASK) | USER_DOMAIN_CLIENT;
core::arch::asm!("mcr p15, 0, {}, c3, c0, 0", "isb", in(reg) opened, options(nostack));
}
domains
}
#[cfg(target_arch = "arm")]
unsafe fn close_user_access(domains: usize) {
unsafe {
core::arch::asm!("mcr p15, 0, {}, c3, c0, 0", "isb", in(reg) domains, options(nostack));
}
}
#[cfg(target_arch = "arm")]
const USER_DOMAIN_MASK: usize = 0b11 << 2;
#[cfg(target_arch = "arm")]
const USER_DOMAIN_CLIENT: usize = 0b01 << 2;
#[cfg(not(target_arch = "arm"))]
unsafe fn open_user_access() -> usize {
0
}
#[cfg(not(target_arch = "arm"))]
unsafe fn close_user_access(_domains: usize) {}
fn start(base: usize, arena: usize, stack: usize) {
let entry = native::alloc(size_of::<Entry>()) as *mut Entry;
unsafe {
entry.write(Entry {
arena,
bootstrap: base + entry_offset(),
stack,
});
_user_mode_thread(USER_ENTRY, entry as *mut c_void, CLONE_VM | CLONE_FS | CLONE_FILES);
}
}
#[repr(C)]
struct Entry {
arena: usize,
bootstrap: usize,
stack: usize,
}
// Runs on the new task before the kernel lets it out, which is where what it starts with in
// userland is written down.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn frida_cb_user(argument: *mut c_void) -> c_int {
let entry = argument as *mut Entry;
let (arena, bootstrap, stack) = unsafe { ((*entry).arena, (*entry).bootstrap, (*entry).stack) };
native::free(entry as *mut u8, size_of::<Entry>());
let Some(places) = describe_registers() else {
return 0;
};
let registers = registers_of_this_task(&places);
let stack = hand_over_argument(stack, arena);
unsafe {
((registers + places.pc) as *mut usize).write(bootstrap & !THUMB_BIT);
((registers + places.stack) as *mut usize).write(stack);
((registers + places.flags) as *mut usize).write(
USER_EXECUTION | (bootstrap & THUMB_BIT) * THUMB_STATE);
((registers + places.first) as *mut usize).write(arena);
write_selectors(registers, &places);
}
0
}
// What a task starts with in userland is at the top of the kernel stack it was given.
#[cfg(target_arch = "aarch64")]
fn registers_of_this_task(places: &Places) -> usize {
let stack = unsafe {
((native::current_task() as usize + places.stack_of_task) as *const usize).read()
};
stack + STACK_SPAN - places.size
}
#[cfg(target_arch = "x86_64")]
fn registers_of_this_task(places: &Places) -> usize {
native::top_of_stack() - places.size
}
#[cfg(not(any(target_arch = "aarch64", target_arch = "x86_64")))]
fn registers_of_this_task(places: &Places) -> usize {
let here = &places as *const _ as usize;
let top = (here & !(STACK_SPAN - 1)) + STACK_SPAN;
top - TOP_OF_STACK_PADDING - places.size
}
#[cfg(any(target_arch = "x86", target_arch = "arm"))]
const TOP_OF_STACK_PADDING: usize = 8;
#[cfg(not(any(target_arch = "x86", target_arch = "arm")))]
const TOP_OF_STACK_PADDING: usize = 0;
#[cfg(target_arch = "x86_64")]
unsafe fn write_selectors(registers: usize, _places: &Places) {
unsafe {
((registers + 136) as *mut usize).write(0x33);
((registers + 160) as *mut usize).write(0x2b);
}
}
#[cfg(target_arch = "x86")]
unsafe fn write_selectors(registers: usize, _places: &Places) {
unsafe {
((registers + 52) as *mut usize).write(0x73);
((registers + 64) as *mut usize).write(0x7b);
((registers + 28) as *mut usize).write(0x7b);
((registers + 32) as *mut usize).write(0x7b);
}
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
unsafe fn write_selectors(_registers: usize, _places: &Places) {}
#[cfg(target_arch = "x86")]
fn hand_over_argument(stack: usize, arena: usize) -> usize {
let frame = [0usize, arena];
let sp = stack - size_of::<[usize; 2]>();
match give(sp, frame.as_ptr() as usize, size_of::<[usize; 2]>()) {
Some(()) => sp,
None => stack,
}
}
#[cfg(not(target_arch = "x86"))]
fn hand_over_argument(stack: usize, _arena: usize) -> usize {
stack
}
struct Places {
size: usize,
first: usize,
stack: usize,
pc: usize,
flags: usize,
stack_of_task: usize,
}
#[cfg(target_arch = "aarch64")]
fn describe_registers() -> Option<Places> {
Some(Places {
size: struct_size("pt_regs")?,
first: field_offset("user_pt_regs", "regs")?,
stack: field_offset("user_pt_regs", "sp")?,
pc: field_offset("user_pt_regs", "pc")?,
flags: field_offset("user_pt_regs", "pstate")?,
stack_of_task: field_offset("task_struct", "stack")?,
})
}
#[cfg(target_arch = "arm")]
fn describe_registers() -> Option<Places> {
Some(Places {
size: 72,
first: 0,
stack: 13 * 4,
pc: 15 * 4,
flags: 16 * 4,
stack_of_task: 0,
})
}
#[cfg(target_arch = "x86")]
fn describe_registers() -> Option<Places> {
Some(Places {
size: 68,
first: 24,
stack: 60,
pc: 48,
flags: 56,
stack_of_task: 0,
})
}
#[cfg(target_arch = "x86_64")]
fn describe_registers() -> Option<Places> {
Some(Places {
size: 168,
first: 112,
stack: 152,
pc: 128,
flags: 144,
stack_of_task: 0,
})
}
pub fn arena_for_pid(id: u32) -> Option<u64> {
unsafe { placements() }.get(&id).map(|placed| placed.arena as u64)
}
pub fn arenas() -> alloc::vec::Vec<u64> {
unsafe { placements() }
.values()
.map(|placed| placed.arena as u64)
.collect()
}
pub fn tell_the_copy_at(arena: u64) {
let Some(id) = (unsafe { placements() })
.iter()
.find(|(_, placed)| placed.arena as u64 == arena)
.map(|(id, _)| *id)
else {
return;
};
tell_the_copy(id);
}
pub fn a_copy_has_something_to_say() -> bool {
unsafe { placements() }.values().any(|placed| what_it_says(placed).is_some())
}
pub fn report_what_the_copies_hit() {
for placed in unsafe { placements() }.values() {
let Some(hit) = what_it_says(placed) else {
continue;
};
bump_to(placed.arena + super::arena::PROGRESS, NOTHING_MORE);
let arena = Arena::at(placed.arena);
let said = arena.said();
if hit == super::user::SPOKE {
native::log(&alloc::format!("copy in {}: {}\n", pid_reported_by(placed), said));
} else if hit == super::user::PANICKED {
native::log(&alloc::format!("copy in {} gave up: {}\n", pid_reported_by(placed), said));
} else {
let kind = read_address(placed.arena + super::arena::FAULT_KIND);
native::log(&alloc::format!(
"copy in {} died on signal {} ({}) at {:#x}, pc {}, lr {}, image at {:#x}, {}\n",
pid_reported_by(placed),
kind as u32,
(kind >> 32) as u32,
read_address(placed.arena + super::arena::FAULT_ADDRESS),
relative_to_image(read_address(placed.arena + super::arena::FAULT_PC), placed),
relative_to_image(read_address(placed.arena + super::arena::FAULT_LR), placed),
placed.base,
said
));
}
}
}
// A copy notes where it is on its way up as well, and those steps are not for reporting.
fn what_it_says(placed: &Placement) -> Option<u32> {
let said = read_word(placed.arena + super::arena::PROGRESS);
if said != super::user::FAULTED && said != super::user::PANICKED && said != super::user::SPOKE {
return None;
}
Some(said)
}
fn bump_to(address: usize, value: u32) {
unsafe { (address as *mut u32).write_volatile(value) };
}
fn relative_to_image(address: u64, placed: &Placement) -> String {
let base = placed.base as u64;
let size = crate::own_range().1 as u64;
if address >= base && address - base < size {
alloc::format!("image+{:#x}", address - base)
} else {
alloc::format!("{:#x}", address)
}
}
const NOTHING_MORE: u32 = 0;
fn watch_the_threads() {
if WATCHING.swap(true, core::sync::atomic::Ordering::AcqRel) {
return;
}
unsafe {
_tracepoint_probe_register(___tracepoint_sched_process_fork,
THREAD_APPEARED_PROBE as *mut c_void, ptr::null_mut());
_tracepoint_probe_register(___tracepoint_sched_process_exit,
THREAD_LEFT_PROBE as *mut c_void, ptr::null_mut());
}
}
pub fn hold_what_is_spawned() {
if HOLDING.swap(true, core::sync::atomic::Ordering::AcqRel) {
return;
}
unsafe {
_tracepoint_probe_register(___tracepoint_sched_process_exec,
EXEC_PROBE as *mut c_void, ptr::null_mut());
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn frida_cb_exec(_data: *mut c_void, task: *mut c_void, _was: c_int,
program: *mut c_void) {
if !super::spawn::holds_this_one(task as usize) {
return;
}
native::send_signal(STOP, task as usize);
let Some(id) = id_of(task as usize) else {
return;
};
let Some(at) = field_offset("linux_binprm", "filename") else {
return;
};
super::spawn::note_a_held_spawn(id, unsafe {
((program as usize + at) as *const *const u8).read()
});
native::wake(task as *const u8);
}
static HOLDING: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
const STOP: c_int = 19;
#[unsafe(no_mangle)]
pub unsafe extern "C" fn frida_cb_thread_appeared(_data: *mut c_void, _parent: *mut c_void,
child: *mut c_void) {
note_a_thread(child as usize, false);
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn frida_cb_thread_left(_data: *mut c_void, task: *mut c_void, _group_dead: bool) {
note_a_thread(task as usize, true);
}
fn note_a_thread(task: usize, is_gone: bool) {
let Some(home) = group_of(task) else {
return;
};
let Some(thread) = id_of(task) else {
return;
};
let Some(placed) = (unsafe { placements() }).get(&home) else {
return;
};
Arena::at(placed.arena).note_a_thread(thread, is_gone);
if !placed.hears.is_null() {
native::leave_a_word(placed.hears);
}
}
fn group_of(task: usize) -> Option<u32> {
let at = field_offset("task_struct", "tgid")?;
Some(unsafe { ((task + at) as *const u32).read() })
}
pub fn id_of(task: usize) -> Option<u32> {
let at = field_offset("task_struct", "pid")?;
Some(unsafe { ((task + at) as *const u32).read() })
}
static WATCHING: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
pub fn detach_from_process(id: u32) -> bool {
let Some(placed) = (unsafe { placements() }.remove(&id)) else {
return false;
};
let memory = unsafe { _get_task_mm(placed.task as *mut c_void) };
ask_it_to_leave(&placed);
take_back_what_it_was_given(&placed, memory);
super::relay::forget(placed.arena as u64);
true
}
pub fn stop_copies() {
let homes: alloc::vec::Vec<u32> = unsafe { placements() }.keys().copied().collect();
for home in homes {
detach_from_process(home);
}
if !WATCHING.swap(false, core::sync::atomic::Ordering::AcqRel) {
return;
}
unsafe {
_tracepoint_probe_unregister(___tracepoint_sched_process_fork,
THREAD_APPEARED_PROBE as *mut c_void, ptr::null_mut());
_tracepoint_probe_unregister(___tracepoint_sched_process_exit,
THREAD_LEFT_PROBE as *mut c_void, ptr::null_mut());
}
}
fn ask_it_to_leave(placed: &Placement) {
let arena = Arena::at(placed.arena);
arena.tell_it_to_go();
if !placed.hears.is_null() {
native::leave_a_word(placed.hears);
}
let waited_on = &placed.arena as *const usize as *const u8;
while !arena.has_gone() {
native::wait(waited_on, Some(LEAVING_GRACE_US), &mut || arena.has_gone());
}
}
fn take_back_what_it_was_given(placed: &Placement, memory: *mut c_void) {
if !placed.says.is_null() {
native::let_the_file_go(placed.says);
native::let_the_file_go(placed.hears);
}
unsafe { _vunmap(placed.arena as *mut c_void) };
if memory.is_null() {
return;
}
let (_, image) = crate::own_range();
unsafe {
_kthread_use_mm(memory);
_vm_munmap(placed.base, image + ARENA_SIZE);
_vm_munmap(placed.stack + STACK_HEADROOM - STACK_SIZE, STACK_SIZE);
_kthread_unuse_mm(memory);
_mmput(memory);
}
}
const LEAVING_GRACE_US: u64 = 100_000;
pub fn tell_the_copy(id: u32) {
let Some(placed) = (unsafe { placements() }).get_mut(&id) else {
return;
};
bump(placed.arena + WOKEN);
if placed.hears.is_null() {
return;
}
native::leave_a_word(placed.hears);
}
fn read_address(address: usize) -> u64 {
unsafe { (address as *const u64).read_volatile() }
}
fn read_word(address: usize) -> u32 {
unsafe { (address as *const u32).read_volatile() }
}
fn bump(address: usize) {
unsafe { (address as *mut u32).write_volatile(read_word(address).wrapping_add(1)) };
}
unsafe fn placements() -> &'static mut BTreeMap<u32, Placement> {
unsafe { (&raw mut PLACEMENTS).as_mut().unwrap() }
}
static mut PLACEMENTS: BTreeMap<u32, Placement> = BTreeMap::new();
pub(crate) const ARENA_SIZE: usize = 2 * 1024 * 1024;
#[cfg(target_arch = "arm")]
const THUMB_BIT: usize = 1;
#[cfg(not(target_arch = "arm"))]
const THUMB_BIT: usize = 0;
#[cfg(target_arch = "arm")]
const THUMB_STATE: usize = 0x20;
#[cfg(not(target_arch = "arm"))]
const THUMB_STATE: usize = 0;
#[cfg(target_arch = "aarch64")]
const USER_EXECUTION: usize = 0;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const USER_EXECUTION: usize = 0x202;
#[cfg(target_arch = "arm")]
const USER_EXECUTION: usize = 0x10;
const PROT_READ: usize = 1;
const PROT_WRITE: usize = 2;
const PROT_EXEC: usize = 4;
const MAP_PRIVATE: usize = 2;
const MAP_ANONYMOUS: usize = 0x20;
const FIRST_ERROR_ADDRESS: usize = usize::MAX - 4095;
const PAGE_SIZE: usize = 4096;
const FOLL_WRITE: c_int = 1;
const FUTEX_WAIT: c_int = 128;
const FUTEX_WAKE: c_int = 129;
const WAKE_EVERY_WAITER: u32 = i32::MAX as u32;
const CLONE_VM: usize = 0x100;
const CLONE_FS: usize = 0x200;
const CLONE_FILES: usize = 0x400;
pub const STACK_SIZE: usize = 1024 * 1024;
const STACK_HEADROOM: usize = 16;
unsafe extern "C" {
#[cfg(not(target_arch = "x86"))]
static _get_task_mm: unsafe extern "C" fn(*mut c_void) -> *mut c_void;
#[cfg(not(target_arch = "x86"))]
static _kthread_use_mm: unsafe extern "C" fn(*mut c_void);
#[cfg(not(target_arch = "x86"))]
static _kthread_unuse_mm: unsafe extern "C" fn(*mut c_void);
#[cfg(not(target_arch = "x86"))]
static _mmput: unsafe extern "C" fn(*mut c_void);
#[cfg(not(target_arch = "x86"))]
static _vm_mmap: unsafe extern "C" fn(*mut c_void, usize, usize, usize, usize, usize) -> usize;
#[cfg(not(target_arch = "x86"))]
static _vm_munmap: unsafe extern "C" fn(usize, usize) -> c_int;
#[cfg(not(target_arch = "x86"))]
static _tracepoint_probe_register:
unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void) -> c_int;
#[cfg(not(target_arch = "x86"))]
static _tracepoint_probe_unregister:
unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void) -> c_int;
static ___tracepoint_sched_process_fork: *mut c_void;
static ___tracepoint_sched_process_exit: *mut c_void;
static ___tracepoint_sched_process_exec: *mut c_void;
#[cfg(not(target_arch = "x86"))]
static _vunmap: unsafe extern "C" fn(*mut c_void);
#[cfg(not(any(target_arch = "arm", target_arch = "x86", target_arch = "x86_64")))]
static ___arch_copy_to_user: unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> usize;
#[cfg(target_arch = "x86_64")]
static __copy_to_user: unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> usize;
#[cfg(target_arch = "arm")]
static _arm_copy_to_user: unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> usize;
#[cfg(not(target_arch = "x86"))]
static _down_read: unsafe extern "C" fn(*mut c_void);
#[cfg(not(target_arch = "x86"))]
static _up_read: unsafe extern "C" fn(*mut c_void);
#[cfg(not(target_arch = "x86"))]
static _get_user_pages_unlocked:
unsafe extern "C" fn(usize, usize, *mut c_void, c_int) -> isize;
#[cfg(not(target_arch = "x86"))]
static _get_user_pages_remote: unsafe extern "C" fn(
*mut c_void,
usize,
usize,
c_int,
*mut c_void,
*mut c_void,
) -> isize;
static ___arch_copy_from_user:
unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> usize;
#[cfg(not(target_arch = "x86"))]
static _do_futex: unsafe extern "C" fn(usize, c_int, u32, usize, usize, u32, u32) -> isize;
#[cfg(not(target_arch = "x86"))]
static _user_mode_thread:
unsafe extern "C" fn(unsafe extern "C" fn(*mut c_void) -> c_int, *mut c_void, usize) -> c_int;
}
#[cfg(target_arch = "x86")]
unsafe extern "C" {
#[link_name = "frida_k_get_task_mm"]
fn _get_task_mm(a0: *mut c_void) -> *mut c_void;
#[link_name = "frida_k_kthread_use_mm"]
fn _kthread_use_mm(a0: *mut c_void);
#[link_name = "frida_k_kthread_unuse_mm"]
fn _kthread_unuse_mm(a0: *mut c_void);
#[link_name = "frida_k_mmput"]
fn _mmput(a0: *mut c_void);
#[link_name = "frida_k_vm_mmap"]
fn _vm_mmap(a0: *mut c_void, a1: usize, a2: usize, a3: usize, a4: usize, a5: usize) -> usize;
#[link_name = "frida_k_vm_munmap"]
fn _vm_munmap(a0: usize, a1: usize) -> c_int;
#[link_name = "frida_k_vunmap"]
fn _vunmap(a0: *mut c_void);
#[link_name = "frida_k__copy_to_user"]
fn __copy_to_user(a0: *mut c_void, a1: *const c_void, a2: usize) -> usize;
#[link_name = "frida_k_down_read"]
fn _down_read(a0: *mut c_void);
#[link_name = "frida_k_up_read"]
fn _up_read(a0: *mut c_void);
#[link_name = "frida_k_get_user_pages_remote"]
fn _get_user_pages_remote(a0: *mut c_void, a1: usize, a2: usize, a3: c_int, a4: *mut c_void, a5: *mut c_void) -> isize;
#[link_name = "frida_k_do_futex"]
fn _do_futex(a0: usize, a1: c_int, a2: u32, a3: usize, a4: usize, a5: u32, a6: u32) -> isize;
}
#[cfg(target_arch = "x86")]
unsafe extern "C" {
#[link_name = "frida_k_get_user_pages_unlocked"]
fn _get_user_pages_unlocked(a0: usize, a1: usize, a2: *mut c_void, a3: c_int) -> isize;
#[link_name = "frida_k_tracepoint_probe_register"]
fn _tracepoint_probe_register(a0: *mut c_void, a1: *mut c_void, a2: *mut c_void) -> c_int;
#[link_name = "frida_k_tracepoint_probe_unregister"]
fn _tracepoint_probe_unregister(a0: *mut c_void, a1: *mut c_void, a2: *mut c_void) -> c_int;
#[link_name = "frida_k_user_mode_thread"]
fn _user_mode_thread(a0: unsafe extern "C" fn(*mut c_void) -> c_int, a1: *mut c_void,
a2: usize) -> c_int;
fn frida_kcb_user(argument: *mut c_void) -> c_int;
fn frida_kcb_exec(data: *mut c_void, task: *mut c_void, was: c_int, program: *mut c_void);
fn frida_kcb_thread_appeared(data: *mut c_void, parent: *mut c_void, child: *mut c_void);
fn frida_kcb_thread_left(data: *mut c_void, task: *mut c_void, group_dead: bool);
}
#[cfg(target_arch = "x86")]
const USER_ENTRY: unsafe extern "C" fn(*mut c_void) -> c_int = frida_kcb_user;
#[cfg(not(target_arch = "x86"))]
const USER_ENTRY: unsafe extern "C" fn(*mut c_void) -> c_int = frida_cb_user;
#[cfg(target_arch = "x86")]
const EXEC_PROBE: unsafe extern "C" fn(*mut c_void, *mut c_void, c_int, *mut c_void) =
frida_kcb_exec;
#[cfg(not(target_arch = "x86"))]
const EXEC_PROBE: unsafe extern "C" fn(*mut c_void, *mut c_void, c_int, *mut c_void) =
frida_cb_exec;
#[cfg(target_arch = "x86")]
const THREAD_APPEARED_PROBE: unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void) =
frida_kcb_thread_appeared;
#[cfg(not(target_arch = "x86"))]
const THREAD_APPEARED_PROBE: unsafe extern "C" fn(*mut c_void, *mut c_void, *mut c_void) =
frida_cb_thread_appeared;
#[cfg(target_arch = "x86")]
const THREAD_LEFT_PROBE: unsafe extern "C" fn(*mut c_void, *mut c_void, bool) =
frida_kcb_thread_left;
#[cfg(not(target_arch = "x86"))]
const THREAD_LEFT_PROBE: unsafe extern "C" fn(*mut c_void, *mut c_void, bool) =
frida_cb_thread_left;
-575
View File
@@ -1,575 +0,0 @@
use core::ffi::{c_int, c_long, c_void};
use core::ptr;
use crate::bindings::{
gum_barebone_handle_exception, GumCpuContext,
_GumExceptionType_GUM_EXCEPTION_ACCESS_VIOLATION as ACCESS_VIOLATION,
};
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[repr(C)]
struct NotifierBlock {
notifier_call: Option<unsafe extern "C" fn(*mut NotifierBlock, c_long, *mut c_void) -> c_int>,
next: *mut NotifierBlock,
priority: c_int,
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe impl Sync for NotifierBlock {}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
static mut REPORTER: NotifierBlock =
NotifierBlock { notifier_call: None, next: ptr::null_mut(), priority: 0 };
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[repr(C)]
struct DieArgs {
regs: *mut c_void,
message: *const u8,
err: c_long,
trapnr: c_int,
signr: c_int,
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const DIE_OOPS: c_long = 1;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const NOTIFY_DONE: c_int = 0;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
const NOTIFY_STOP: c_int = 0x8001;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
unsafe fn dispatch(action: c_long, data: *mut c_void) -> c_int {
if action != DIE_OOPS {
return NOTIFY_DONE;
}
let regs = unsafe { (*(data as *const DieArgs)).regs };
if unsafe { recovered(regs) } {
return NOTIFY_STOP;
}
NOTIFY_DONE
}
#[cfg(target_arch = "x86")]
static mut RESUME_CONTEXT: GumCpuContext = unsafe { core::mem::zeroed() };
#[cfg(target_arch = "x86")]
static mut RESUME_STUB: usize = 0;
#[cfg(target_arch = "x86")]
const RESUME_SCRATCH_SIZE: usize = 512;
#[cfg(target_arch = "x86")]
static mut RESUME_SCRATCH: [u8; RESUME_SCRATCH_SIZE] = [0; RESUME_SCRATCH_SIZE];
#[cfg(target_arch = "x86")]
pub fn install() {
unsafe { build_resume_stub() };
unsafe {
(&raw mut REPORTER).write(NotifierBlock {
notifier_call: Some(frida_kcb_die),
next: ptr::null_mut(),
priority: 0,
});
frida_k_register_die_notifier(&raw mut REPORTER as *mut c_void);
}
}
#[cfg(target_arch = "x86")]
pub fn uninstall() {
unsafe { frida_k_unregister_die_notifier(&raw mut REPORTER as *mut c_void) };
}
#[cfg(target_arch = "x86")]
unsafe fn build_resume_stub() {
let stub = crate::kernel::alloc_code(16);
if stub.is_null() {
return;
}
unsafe {
stub.write(0xbf);
(stub.add(1) as *mut u32).write((&raw mut RESUME_CONTEXT) as u32);
stub.add(5).write(0xe9);
let rel = (frida_i386_resume as usize).wrapping_sub(stub as usize + 10) as u32;
(stub.add(6) as *mut u32).write(rel);
RESUME_STUB = stub as usize;
}
}
#[cfg(target_arch = "x86")]
unsafe fn recovered(regs: *mut c_void) -> bool {
let faulted_at = unsafe { (regs as *const u32).add(12).read() as usize };
let (base, size) = crate::own_range();
if faulted_at < base || faulted_at >= base + size {
return false;
}
let mut context = unsafe { context_of(regs) };
let handled = unsafe {
gum_barebone_handle_exception(
ACCESS_VIOLATION,
faulted_at as *mut c_void,
faulting_address() as *mut c_void,
&mut context,
)
};
if handled == 0 || unsafe { RESUME_STUB } == 0 {
return false;
}
unsafe {
(&raw mut RESUME_CONTEXT).write(context);
(regs as *mut u32).add(12).write(RESUME_STUB as u32);
}
true
}
#[cfg(target_arch = "x86")]
fn faulting_address() -> usize {
let value: usize;
unsafe { core::arch::asm!("mov {}, cr2", out(reg) value, options(nomem, nostack)) };
value
}
#[cfg(target_arch = "x86")]
unsafe fn context_of(regs: *mut c_void) -> GumCpuContext {
let r = regs as *const u32;
let mut c: GumCpuContext = unsafe { core::mem::zeroed() };
unsafe {
c.ebx = r.add(0).read();
c.ecx = r.add(1).read();
c.edx = r.add(2).read();
c.esi = r.add(3).read();
c.edi = r.add(4).read();
c.ebp = r.add(5).read();
c.eax = r.add(6).read();
c.eip = r.add(12).read();
c.esp = ((&raw mut RESUME_SCRATCH) as usize + RESUME_SCRATCH_SIZE - 16) as u32;
}
c
}
#[cfg(target_arch = "x86")]
#[unsafe(naked)]
unsafe extern "C" fn frida_i386_resume() {
core::arch::naked_asm!(
"mov esi, [edi + 8]",
"mov ebp, [edi + 12]",
"mov ebx, [edi + 20]",
"mov edx, [edi + 24]",
"mov ecx, [edi + 28]",
"mov eax, [edi + 32]",
"mov esp, [edi + 16]",
"push dword ptr [edi]",
"mov edi, [edi + 4]",
"ret",
);
}
#[cfg(target_arch = "x86")]
#[unsafe(no_mangle)]
unsafe extern "C" fn frida_cb_die(_nb: *mut NotifierBlock, action: c_long, data: *mut c_void) -> c_int {
unsafe { dispatch(action, data) }
}
#[cfg(target_arch = "x86")]
unsafe extern "C" {
fn frida_kcb_die(nb: *mut NotifierBlock, action: c_long, data: *mut c_void) -> c_int;
fn frida_k_register_die_notifier(nb: *mut c_void) -> c_int;
fn frida_k_unregister_die_notifier(nb: *mut c_void) -> c_int;
}
#[cfg(target_arch = "x86_64")]
pub fn install() {
unsafe {
(&raw mut REPORTER).write(NotifierBlock {
notifier_call: Some(on_die),
next: ptr::null_mut(),
priority: 0,
});
_register_die_notifier(&raw mut REPORTER as *mut c_void);
}
}
#[cfg(target_arch = "x86_64")]
pub fn uninstall() {
unsafe { _unregister_die_notifier(&raw mut REPORTER as *mut c_void) };
}
#[cfg(target_arch = "x86_64")]
unsafe extern "C" fn on_die(_nb: *mut NotifierBlock, action: c_long, data: *mut c_void) -> c_int {
unsafe { dispatch(action, data) }
}
#[cfg(target_arch = "x86_64")]
unsafe fn recovered(regs: *mut c_void) -> bool {
let faulted_at = unsafe { program_counter(regs) };
let (base, size) = crate::own_range();
if faulted_at < base || faulted_at >= base + size {
return false;
}
let mut context = unsafe { context_of(regs) };
let handled = unsafe {
gum_barebone_handle_exception(
ACCESS_VIOLATION,
faulted_at as *mut c_void,
faulting_address() as *mut c_void,
&mut context,
)
};
if handled == 0 {
return false;
}
unsafe { restore_context(regs, &context) };
true
}
#[cfg(target_arch = "x86_64")]
fn faulting_address() -> usize {
let value: usize;
unsafe { core::arch::asm!("mov {}, cr2", out(reg) value, options(nomem, nostack)) };
value
}
#[cfg(target_arch = "x86_64")]
unsafe fn program_counter(regs: *mut c_void) -> usize {
unsafe { (regs as *const u64).add(16).read() as usize }
}
#[cfg(target_arch = "x86_64")]
unsafe fn context_of(regs: *mut c_void) -> GumCpuContext {
let r = regs as *const u64;
let mut c: GumCpuContext = unsafe { core::mem::zeroed() };
unsafe {
c.r15 = r.add(0).read();
c.r14 = r.add(1).read();
c.r13 = r.add(2).read();
c.r12 = r.add(3).read();
c.rbp = r.add(4).read();
c.rbx = r.add(5).read();
c.r11 = r.add(6).read();
c.r10 = r.add(7).read();
c.r9 = r.add(8).read();
c.r8 = r.add(9).read();
c.rax = r.add(10).read();
c.rcx = r.add(11).read();
c.rdx = r.add(12).read();
c.rsi = r.add(13).read();
c.rdi = r.add(14).read();
c.rip = r.add(16).read();
c.rsp = r.add(19).read();
}
c
}
#[cfg(target_arch = "x86_64")]
unsafe fn restore_context(regs: *mut c_void, c: &GumCpuContext) {
let r = regs as *mut u64;
unsafe {
r.add(0).write(c.r15);
r.add(1).write(c.r14);
r.add(2).write(c.r13);
r.add(3).write(c.r12);
r.add(4).write(c.rbp);
r.add(5).write(c.rbx);
r.add(6).write(c.r11);
r.add(7).write(c.r10);
r.add(8).write(c.r9);
r.add(9).write(c.r8);
r.add(10).write(c.rax);
r.add(11).write(c.rcx);
r.add(12).write(c.rdx);
r.add(13).write(c.rsi);
r.add(14).write(c.rdi);
r.add(16).write(c.rip);
r.add(19).write(c.rsp);
}
}
#[cfg(target_arch = "x86_64")]
unsafe extern "C" {
static _register_die_notifier: unsafe extern "C" fn(*mut c_void) -> c_int;
static _unregister_die_notifier: unsafe extern "C" fn(*mut c_void) -> c_int;
}
#[cfg(target_arch = "arm")]
const PROLOGUE: [u32; 2] = [0xe92d4010, 0xe52de004];
#[cfg(target_arch = "arm")]
static mut ORIGINAL_STUB: usize = 0;
#[cfg(target_arch = "arm")]
pub fn install() {
let target = unsafe { _fixup_exception } as *const u32;
for slot in 0..2 {
if unsafe { target.add(slot).read_volatile() } != PROLOGUE[slot] {
return;
}
}
unsafe { build_original_stub(target) };
if unsafe { ORIGINAL_STUB } == 0 {
return;
}
unsafe {
let addr = target as *mut u32;
_patch_text(addr.add(1) as *mut c_void, frida_arm_fixup as usize as u32);
_patch_text(addr as *mut c_void, 0xe51ff004);
}
}
#[cfg(target_arch = "arm")]
pub fn uninstall() {
let target = unsafe { _fixup_exception } as *mut u32;
unsafe {
_patch_text(target.add(1) as *mut c_void, PROLOGUE[1]);
_patch_text(target as *mut c_void, PROLOGUE[0]);
}
}
#[cfg(target_arch = "arm")]
unsafe fn build_original_stub(target: *const u32) {
let stub = crate::kernel::alloc_code(16) as *mut u32;
if stub.is_null() {
return;
}
unsafe {
stub.add(0).write(PROLOGUE[0]);
stub.add(1).write(PROLOGUE[1]);
stub.add(2).write(0xe51ff004);
stub.add(3).write(target.add(2) as u32);
ORIGINAL_STUB = stub as usize;
}
}
#[cfg(target_arch = "arm")]
unsafe extern "C" fn frida_arm_fixup(regs: *mut c_void) -> c_int {
if unsafe { recovered(regs) } {
return 1;
}
let original: unsafe extern "C" fn(*mut c_void) -> c_int =
unsafe { core::mem::transmute(ORIGINAL_STUB) };
unsafe { original(regs) }
}
#[cfg(target_arch = "arm")]
unsafe fn recovered(regs: *mut c_void) -> bool {
let faulted_at = unsafe { (regs as *const u32).add(15).read() as usize };
let (base, size) = crate::own_range();
if faulted_at < base || faulted_at >= base + size {
return false;
}
let accessed: usize;
unsafe { core::arch::asm!("mrc p15, 0, {}, c6, c0, 0", out(reg) accessed, options(nomem, nostack)) };
let mut context = unsafe { context_of(regs) };
let handled = unsafe {
gum_barebone_handle_exception(
ACCESS_VIOLATION,
faulted_at as *mut c_void,
accessed as *mut c_void,
&mut context,
)
};
if handled == 0 {
return false;
}
unsafe { restore_context(regs, &context) };
true
}
#[cfg(target_arch = "arm")]
unsafe fn context_of(regs: *mut c_void) -> GumCpuContext {
let g = regs as *const u32;
let mut r = [0u32; 8];
for slot in 0..8 {
r[slot] = unsafe { g.add(slot).read() };
}
let mut c: GumCpuContext = unsafe { core::mem::zeroed() };
c.r = r;
unsafe {
c.r8 = g.add(8).read();
c.r9 = g.add(9).read();
c.r10 = g.add(10).read();
c.r11 = g.add(11).read();
c.r12 = g.add(12).read();
c.sp = g.add(13).read();
c.lr = g.add(14).read();
c.pc = g.add(15).read();
c.cpsr = g.add(16).read();
}
c
}
#[cfg(target_arch = "arm")]
unsafe fn restore_context(regs: *mut c_void, c: &GumCpuContext) {
let g = regs as *mut u32;
unsafe {
for slot in 0..8 {
g.add(slot).write(c.r[slot]);
}
g.add(8).write(c.r8);
g.add(9).write(c.r9);
g.add(10).write(c.r10);
g.add(11).write(c.r11);
g.add(12).write(c.r12);
g.add(13).write(c.sp);
g.add(14).write(c.lr);
g.add(15).write(c.pc);
g.add(16).write(c.cpsr);
}
}
#[cfg(target_arch = "arm")]
unsafe extern "C" {
static _fixup_exception: unsafe extern "C" fn();
static _patch_text: unsafe extern "C" fn(*mut c_void, u32);
}
#[cfg(target_arch = "aarch64")]
const PROLOGUE: [u32; 4] = [0xd503233f, 0xa9be7bfd, 0x910003fd, 0xa90153f3];
#[cfg(target_arch = "aarch64")]
#[unsafe(no_mangle)]
static mut FRIDA_ARM64_ORIGINAL_CONT: u64 = 0;
#[cfg(target_arch = "aarch64")]
pub fn install() {
let target = unsafe { _fixup_exception } as *const u32;
for slot in 0..4 {
if unsafe { target.add(slot).read_volatile() } != PROLOGUE[slot] {
return;
}
}
unsafe { FRIDA_ARM64_ORIGINAL_CONT = target.add(4) as u64 };
let jump = detour_to(frida_arm64_fixup as usize);
unsafe {
let addr = target as *mut u32;
_aarch64_insn_patch_text_nosync(addr.add(2) as *mut c_void, jump[2]);
_aarch64_insn_patch_text_nosync(addr.add(3) as *mut c_void, jump[3]);
_aarch64_insn_patch_text_nosync(addr.add(1) as *mut c_void, jump[1]);
_aarch64_insn_patch_text_nosync(addr as *mut c_void, jump[0]);
}
}
#[cfg(target_arch = "aarch64")]
pub fn uninstall() {
let target = unsafe { _fixup_exception } as *mut u32;
unsafe {
for slot in 0..4 {
_aarch64_insn_patch_text_nosync(target.add(slot) as *mut c_void, PROLOGUE[slot]);
}
}
}
#[cfg(target_arch = "aarch64")]
fn detour_to(destination: usize) -> [u32; 4] {
[0x58000050, 0xd61f0200, destination as u32, (destination >> 32) as u32]
}
#[cfg(target_arch = "aarch64")]
core::arch::global_asm!(
".globl frida_arm64_call_original",
"frida_arm64_call_original:",
"paciasp",
"stp x29, x30, [sp, #-32]!",
"mov x29, sp",
"stp x19, x20, [sp, #16]",
"adrp x16, {cont}",
"add x16, x16, :lo12:{cont}",
"ldr x16, [x16]",
"br x16",
cont = sym FRIDA_ARM64_ORIGINAL_CONT,
);
#[cfg(target_arch = "aarch64")]
unsafe extern "C" {
fn frida_arm64_call_original(regs: *mut c_void) -> c_int;
}
#[cfg(target_arch = "aarch64")]
unsafe extern "C" fn frida_arm64_fixup(regs: *mut c_void) -> c_int {
if unsafe { recovered(regs) } {
return 1;
}
unsafe { frida_arm64_call_original(regs) }
}
#[cfg(target_arch = "aarch64")]
unsafe fn recovered(regs: *mut c_void) -> bool {
let faulted_at = unsafe { (regs as *const u64).add(32).read() as usize };
let (base, size) = crate::own_range();
if faulted_at < base || faulted_at >= base + size {
return false;
}
let accessed: usize;
unsafe { core::arch::asm!("mrs {}, far_el1", out(reg) accessed, options(nomem, nostack)) };
let mut context = unsafe { context_of(regs) };
let handled = unsafe {
gum_barebone_handle_exception(
ACCESS_VIOLATION,
faulted_at as *mut c_void,
accessed as *mut c_void,
&mut context,
)
};
if handled == 0 {
return false;
}
unsafe { restore_context(regs, &context) };
true
}
#[cfg(target_arch = "aarch64")]
unsafe fn context_of(regs: *mut c_void) -> GumCpuContext {
let r = regs as *const u64;
let mut x = [0u64; 29];
for slot in 0..29 {
x[slot] = unsafe { r.add(slot).read() };
}
GumCpuContext {
pc: unsafe { r.add(32).read() },
sp: unsafe { r.add(31).read() },
nzcv: unsafe { r.add(33).read() },
x,
fp: unsafe { r.add(29).read() },
lr: unsafe { r.add(30).read() },
}
}
#[cfg(target_arch = "aarch64")]
unsafe fn restore_context(regs: *mut c_void, c: &GumCpuContext) {
let r = regs as *mut u64;
unsafe {
for slot in 0..29 {
r.add(slot).write(c.x[slot]);
}
r.add(29).write(c.fp);
r.add(30).write(c.lr);
r.add(31).write(c.sp);
r.add(32).write(c.pc);
r.add(33).write(c.nzcv);
}
}
#[cfg(target_arch = "aarch64")]
unsafe extern "C" {
static _fixup_exception: unsafe extern "C" fn();
static _aarch64_insn_patch_text_nosync: unsafe extern "C" fn(*mut c_void, u32) -> c_int;
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "arm", target_arch = "aarch64")))]
pub fn install() {}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "arm", target_arch = "aarch64")))]
pub fn uninstall() {}
-355
View File
@@ -1,355 +0,0 @@
// The agent as a kernel module: every primitive is an ordinary call into
// linux/frida-kmod.c, compiled by kbuild against the target kernel's headers,
// which is what keeps us out of the business of guessing struct layouts,
// config-dependent macros and per-version symbol names.
use core::ffi::{CStr, c_char, c_int, c_void};
use alloc::string::String;
use alloc::vec::Vec;
use super::{LoadedModule, ModuleEvent};
use crate::kernel::ThreadEntry;
pub fn log(msg: &str) {
unsafe { frida_kmod_log(msg.as_ptr() as *const c_char) };
}
pub fn panic(msg: &str) -> ! {
unsafe { frida_kmod_panic(msg.as_ptr() as *const c_char) }
}
pub fn run_when_ready(action: fn()) {
action();
}
pub fn spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> isize {
unsafe { frida_kmod_spawn_thread(entry, parameter) as isize }
}
pub fn install_hooks() {
unsafe { frida_kmod_install_hooks() };
}
pub fn alloc(size: usize) -> *mut u8 {
unsafe { frida_kmod_alloc(size) }
}
pub fn free(ptr: *mut u8, size: usize) {
unsafe { frida_kmod_free(ptr, size) };
}
pub fn alloc_code(size: usize) -> *mut u8 {
unsafe { frida_kmod_alloc_code(size) }
}
pub fn free_code(ptr: *mut u8, size: usize) {
unsafe { frida_kmod_free_code(ptr, size) };
}
pub fn page_size() -> usize {
crate::gum::page_size_the_kernel_runs_with()
}
pub fn alloc_heap(size: usize) -> *mut u8 {
alloc(size)
}
pub fn alloc_dma(size: usize) -> *mut u8 {
alloc_code(size)
}
pub fn free_dma(ptr: *mut u8, size: usize) {
free_code(ptr, size);
}
pub fn own_range() -> (u64, u64) {
let mut base: u64 = 0;
let mut size: u64 = 0;
unsafe { frida_kmod_own_range(&mut base, &mut size) };
(base, size)
}
// The sequence number is taken before `check` runs, so a wakeup racing with the
// condition we are about to observe still makes the commit return immediately.
pub fn wait(_token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut() -> bool) {
let seq = unsafe { frida_kmod_wait_prepare() };
if check() {
return;
}
unsafe { frida_kmod_wait_commit(seq, timeout_us.map_or(-1, |us| us as i64)) };
}
pub fn wake(_token: *const u8) {
unsafe { frida_kmod_wake() };
}
/// Gives the scheduler a chance to run something else. A kernel thread that loops
/// without doing this is a hard lockup waiting to happen.
pub fn yield_now() {
unsafe { frida_kmod_yield() };
}
pub fn monotonic_micros() -> i64 {
unsafe { frida_kmod_monotonic_micros() }
}
pub fn wall_clock_micros() -> (u32, u32) {
let mut secs: u32 = 0;
let mut micros: u32 = 0;
unsafe { frida_kmod_wall_clock_micros(&mut secs, &mut micros) };
(secs, micros)
}
pub fn current_process_id() -> u32 {
KERNEL_PROCESS
}
pub fn current_thread_id() -> u64 {
unsafe { frida_kmod_current_thread_id() }
}
pub fn protect(address: u64, size: usize, gum_prot: u32) -> bool {
unsafe { frida_kmod_protect(address, size, gum_prot) != 0 }
}
pub fn remap_writable(first_page: u64, n_pages: u32) -> *mut c_void {
unsafe { frida_kmod_remap_writable(first_page, n_pages) }
}
/// Tears down an alias handed out by [`remap_writable`] and flushes the icache
/// over the range it aliased; the shim remembers which range that was.
pub fn unmap_writable(mapping: *mut c_void) {
unsafe { frida_kmod_unmap_writable(mapping) };
}
pub fn get_kernel_base() -> u64 {
unsafe { frida_kmod_kernel_base() }
}
pub fn get_kernel_size() -> u64 {
unsafe { frida_kmod_kernel_size() }
}
pub fn enumerate_modules() -> Vec<LoadedModule> {
unsafe extern "C" fn on_module(
name: *const c_char,
version: *const c_char,
base: u64,
size: u64,
user_data: *mut c_void,
) -> c_int {
unsafe {
let modules = &mut *(user_data as *mut Vec<LoadedModule>);
modules.push(LoadedModule {
name: String::from(CStr::from_ptr(name).to_str().unwrap_or("?")),
version: String::from(CStr::from_ptr(version).to_str().unwrap_or("")),
base,
size,
});
1
}
}
let mut modules: Vec<LoadedModule> = Vec::new();
unsafe {
frida_kmod_enumerate_modules(on_module, &mut modules as *mut _ as *mut c_void);
}
modules
}
pub fn watch_modules(on_event: fn(ModuleEvent, LoadedModule)) {
unsafe extern "C" fn on_module_event(
loaded: c_int,
name: *const c_char,
version: *const c_char,
base: u64,
size: u64,
user_data: *mut c_void,
) {
unsafe {
let on_event: fn(ModuleEvent, LoadedModule) = core::mem::transmute(user_data);
on_event(
if loaded != 0 {
ModuleEvent::Loaded
} else {
ModuleEvent::Unloaded
},
LoadedModule {
name: String::from(CStr::from_ptr(name).to_str().unwrap_or("?")),
version: String::from(CStr::from_ptr(version).to_str().unwrap_or("")),
base,
size,
},
);
}
}
unsafe {
frida_kmod_watch_modules(on_module_event, on_event as *mut c_void);
}
}
pub fn unwatch_modules() {
unsafe { frida_kmod_unwatch_modules() };
}
pub fn enumerate_module_symbols(
base: u64,
on_symbol: &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool,
) -> bool {
unsafe extern "C" fn on_module_symbol(
name: *const c_char,
address: u64,
size: u64,
kind: u8,
is_global: c_int,
user_data: *mut c_void,
) -> c_int {
unsafe {
let callback =
&mut *(user_data as *mut &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool);
if callback(name, address, size, kind, is_global != 0) { 1 } else { 0 }
}
}
let mut boxed: &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool = on_symbol;
unsafe {
frida_kmod_enumerate_module_symbols(base, on_module_symbol, &mut boxed as *mut _ as *mut c_void) != 0
}
}
pub fn enumerate_module_exports(
base: u64,
on_export: &mut dyn FnMut(*const c_char, u64) -> bool,
) -> bool {
unsafe extern "C" fn on_module_export(
name: *const c_char,
address: u64,
user_data: *mut c_void,
) -> c_int {
unsafe {
let callback = &mut *(user_data as *mut &mut dyn FnMut(*const c_char, u64) -> bool);
if callback(name, address) { 1 } else { 0 }
}
}
let mut boxed: &mut dyn FnMut(*const c_char, u64) -> bool = on_export;
unsafe {
frida_kmod_enumerate_module_exports(base, on_module_export, &mut boxed as *mut _ as *mut c_void) != 0
}
}
pub fn find_symbol(name: &CStr) -> u64 {
unsafe { frida_kmod_find_symbol(name.as_ptr()) }
}
pub fn find_function(name: &CStr) -> u64 {
unsafe { frida_kmod_find_function(name.as_ptr()) }
}
/// Resolves `address` to `(name, symbol_address)`, or `None` when it falls
/// outside every known symbol.
pub fn symbol_from_address(address: u64) -> Option<(String, u64)> {
const NAME_CAPACITY: usize = 256;
let mut buffer = [0u8; NAME_CAPACITY];
let mut symbol_address: u64 = 0;
let found = unsafe {
frida_kmod_symbol_name_from_address(
address,
buffer.as_mut_ptr() as *mut c_char,
NAME_CAPACITY,
&mut symbol_address,
)
};
if found == 0 {
return None;
}
let name = CStr::from_bytes_until_nul(&buffer).unwrap();
Some((String::from(name.to_str().unwrap()), symbol_address))
}
/// Visits every symbol the running kernel knows about. Returns false when
/// kallsyms is unavailable, which is the case on kernels built without
/// CONFIG_KALLSYMS or when the shim could not resolve its entry points.
pub fn enumerate_symbols(callback: &mut dyn FnMut(&CStr, u64) -> bool) -> bool {
unsafe extern "C" fn on_symbol(
name: *const c_char,
address: u64,
user_data: *mut c_void,
) -> c_int {
unsafe {
let callback = &mut *(user_data as *mut &mut dyn FnMut(&CStr, u64) -> bool);
if callback(CStr::from_ptr(name), address) { 1 } else { 0 }
}
}
let mut boxed: &mut dyn FnMut(&CStr, u64) -> bool = callback;
unsafe { frida_kmod_enumerate_symbols(on_symbol, &mut boxed as *mut _ as *mut c_void) != 0 }
}
const KERNEL_PROCESS: u32 = 0;
type FoundSymbolFunc =
unsafe extern "C" fn(name: *const c_char, address: u64, user_data: *mut c_void) -> c_int;
type ModuleEventFunc = unsafe extern "C" fn(c_int, *const c_char, *const c_char, u64, u64, *mut c_void);
type FoundModuleSymbolFunc =
unsafe extern "C" fn(*const c_char, u64, u64, u8, c_int, *mut c_void) -> c_int;
type FoundModuleExportFunc = unsafe extern "C" fn(*const c_char, u64, *mut c_void) -> c_int;
type FoundModuleFunc = unsafe extern "C" fn(
name: *const c_char,
version: *const c_char,
base: u64,
size: u64,
user_data: *mut c_void,
) -> c_int;
unsafe extern "C" {
fn frida_kmod_log(message: *const c_char);
fn frida_kmod_panic(message: *const c_char) -> !;
fn frida_kmod_spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> c_int;
fn frida_kmod_install_hooks();
fn frida_kmod_alloc(size: usize) -> *mut u8;
fn frida_kmod_free(ptr: *mut u8, size: usize);
fn frida_kmod_alloc_code(size: usize) -> *mut u8;
fn frida_kmod_free_code(ptr: *mut u8, size: usize);
fn frida_kmod_own_range(base: *mut u64, size: *mut u64);
fn frida_kmod_wait_prepare() -> u32;
fn frida_kmod_wait_commit(seq: u32, timeout_us: i64);
fn frida_kmod_wake();
fn frida_kmod_yield();
fn frida_kmod_monotonic_micros() -> i64;
fn frida_kmod_wall_clock_micros(secs: *mut u32, micros: *mut u32);
fn frida_kmod_current_thread_id() -> u64;
fn frida_kmod_protect(address: u64, size: usize, gum_prot: u32) -> c_int;
fn frida_kmod_remap_writable(first_page: u64, n_pages: u32) -> *mut c_void;
fn frida_kmod_unmap_writable(mapping: *mut c_void);
fn frida_kmod_kernel_base() -> u64;
fn frida_kmod_kernel_size() -> u64;
fn frida_kmod_enumerate_modules(func: FoundModuleFunc, user_data: *mut c_void);
fn frida_kmod_watch_modules(func: ModuleEventFunc, user_data: *mut c_void);
fn frida_kmod_unwatch_modules();
fn frida_kmod_enumerate_module_symbols(
base: u64,
func: FoundModuleSymbolFunc,
user_data: *mut c_void,
) -> c_int;
fn frida_kmod_enumerate_module_exports(
base: u64,
func: FoundModuleExportFunc,
user_data: *mut c_void,
) -> c_int;
fn frida_kmod_find_symbol(name: *const c_char) -> u64;
fn frida_kmod_find_function(name: *const c_char) -> u64;
fn frida_kmod_symbol_name_from_address(
address: u64,
buffer: *mut c_char,
size: usize,
symbol_address: *mut u64,
) -> c_int;
fn frida_kmod_enumerate_symbols(func: FoundSymbolFunc, user_data: *mut c_void) -> c_int;
}
-622
View File
@@ -1,622 +0,0 @@
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use core::ffi::c_void;
pub fn field_offset(container: &str, field: &str) -> Option<usize> {
let types = described_types()?;
let described = types.named(container)?;
types.offset_of(&described, field).map(|bits| bits / 8)
}
pub fn resolve_struct(container: &str) -> Option<usize> {
let described = described_types()?.named(container)?;
let resolved = unsafe { (&raw mut RESOLVED).as_mut().unwrap() };
resolved.push(described);
Some(resolved.len() - 1)
}
pub fn size_of_struct(handle: usize) -> usize {
resolved_struct(handle).size as usize
}
pub fn field_offset_in(handle: usize, field: &str) -> Option<usize> {
let types = described_types()?;
types
.offset_of(&resolved_struct(handle), field)
.map(|bits| bits / 8)
}
pub fn enumerate_fields_in(handle: usize, on_field: &mut dyn FnMut(&str, &Field) -> bool) {
let Some(types) = described_types() else {
return;
};
types.visit_fields(&resolved_struct(handle), 0, on_field);
}
pub fn find_constant(name: &str) -> Option<i64> {
described_types()?.constant_named(name)
}
pub fn enumerate_constants_in(container: &str, on_constant: &mut dyn FnMut(&str, i64)) -> bool {
let Some(types) = described_types() else {
return false;
};
let Some(described) = types.enum_named(container) else {
return false;
};
types.visit_constants(&described, &mut |name, value| {
on_constant(name, value);
true
});
true
}
pub fn enumerate_parameters_in(
name: &str,
on_parameter: &mut dyn FnMut(&str, &str),
) -> Option<String> {
let types = described_types()?;
let described = types.function_named(name)?;
let signature = types.with_id(described.size)?;
types.visit_parameters(&signature, on_parameter);
Some(types.render(signature.size, 0))
}
pub fn name_of_type(id: u32) -> String {
described_types().map_or_else(String::new, |types| types.render(id, 0))
}
pub fn size_of_type(id: u32) -> usize {
described_types().map_or(0, |types| types.span_of(id, 0))
}
pub struct Field {
pub offset: usize,
pub bit_offset: usize,
pub bit_size: usize,
pub id: u32,
}
fn resolved_struct(handle: usize) -> Described {
unsafe { (&raw const RESOLVED).as_ref().unwrap()[handle].clone() }
}
pub fn types_are_described() -> bool {
described_types().is_some()
}
pub fn struct_size(container: &str) -> Option<usize> {
let types = described_types()?;
Some(types.named(container)?.size as usize)
}
struct DescribedTypes {
types: &'static [u8],
names: &'static [u8],
}
impl DescribedTypes {
fn named(&self, container: &str) -> Option<Described> {
let index = self.index();
if let Some(offset) = index.containers.get(container) {
return self.at(*offset as usize);
}
while let Some(described) = self.take_in_next_type(index) {
if self.names_container(&described, container) {
return Some(described);
}
}
None
}
fn with_id(&self, wanted: u32) -> Option<Described> {
if wanted == 0 {
return None;
}
let index = self.index();
let checkpoint = (wanted as usize - FIRST_ID) / CHECKPOINT_SPAN;
while index.checkpoints.len() <= checkpoint {
self.take_in_next_type(index)?;
}
let mut described = self.at(index.checkpoints[checkpoint] as usize)?;
let mut id = (checkpoint * CHECKPOINT_SPAN + FIRST_ID) as u32;
while id != wanted {
described = self.at(described.body + described.body_size)?;
id += 1;
}
Some(described)
}
fn take_in_next_type(&self, index: &mut Index) -> Option<Described> {
let described = self.at(index.offset)?;
if (index.id as usize - FIRST_ID) % CHECKPOINT_SPAN == 0 {
index.checkpoints.push(index.offset as u32);
}
if self.holds_fields(&described) && described.name != 0 {
index
.containers
.insert(self.name_of(described.name), index.offset as u32);
}
index.offset = described.body + described.body_size;
index.id += 1;
Some(described)
}
fn names_container(&self, described: &Described, container: &str) -> bool {
self.holds_fields(described)
&& described.name != 0
&& self.name_of(described.name) == container
}
fn holds_fields(&self, described: &Described) -> bool {
described.kind == KIND_STRUCT || described.kind == KIND_UNION
}
fn index(&self) -> &'static mut Index {
let known = unsafe { (&raw mut INDEX).as_mut().unwrap() };
if known.is_none() {
*known = Some(Index {
containers: BTreeMap::new(),
checkpoints: Vec::new(),
offset: 0,
id: FIRST_ID as u32,
});
}
known.as_mut().unwrap()
}
fn visit_fields(
&self,
described: &Described,
base: usize,
on_field: &mut dyn FnMut(&str, &Field) -> bool,
) -> bool {
for index in 0..described.members {
let member = described.body + (index * MEMBER_SIZE);
let name = self.name_of(word_at(self.types, member));
let placement = base + self.placement_of(described, member);
if name.is_empty() {
let Some(within) = self.with_id(word_at(self.types, member + 4)) else {
continue;
};
if within.kind == KIND_STRUCT || within.kind == KIND_UNION {
if !self.visit_fields(&within, placement, on_field) {
return false;
}
}
} else {
let field = Field {
offset: placement / 8,
bit_offset: placement,
bit_size: self.bits_of(described, member),
id: word_at(self.types, member + 4),
};
if !on_field(name, &field) {
return false;
}
}
}
true
}
fn function_named(&self, wanted: &str) -> Option<Described> {
let known = self.functions();
let at = known
.binary_search_by(|entry| self.name_of(entry.name).cmp(wanted))
.ok()?;
self.at(known[at].offset as usize)
}
fn functions(&self) -> &'static [Named] {
let known = unsafe { (&raw mut FUNCTIONS).as_mut().unwrap() };
if known.is_none() {
let mut found = Vec::new();
self.visit_every_type_at(&mut |offset, described| {
if described.kind == KIND_FUNCTION && described.name != 0 {
found.push(Named {
name: described.name,
offset: offset as u32,
});
}
true
});
found.sort_unstable_by(|a, b| self.name_of(a.name).cmp(self.name_of(b.name)));
*known = Some(found);
}
known.as_ref().unwrap()
}
fn visit_parameters(&self, signature: &Described, on_parameter: &mut dyn FnMut(&str, &str)) {
for index in 0..signature.members {
let parameter = signature.body + (index * PARAMETER_SIZE);
let name = self.name_of(word_at(self.types, parameter));
let id = word_at(self.types, parameter + 4);
if id == 0 {
on_parameter("", "...");
continue;
}
on_parameter(name, &self.render(id, 0));
}
}
fn render(&self, id: u32, depth: usize) -> String {
if depth == MAX_DEPTH {
return String::from("...");
}
let Some(described) = self.with_id(id) else {
return String::from("void");
};
let named = self.name_of(described.name);
match described.kind {
KIND_POINTER => alloc::format!("{} *", self.render(described.size, depth + 1)),
KIND_ARRAY => {
let element = word_at(self.types, described.body);
let count = word_at(self.types, described.body + 8);
alloc::format!("{}[{}]", self.render(element, depth + 1), count)
}
KIND_STRUCT => alloc::format!("struct {named}"),
KIND_UNION => alloc::format!("union {named}"),
KIND_ENUM | KIND_WIDE_ENUM => alloc::format!("enum {named}"),
KIND_CONSTANT => alloc::format!("const {}", self.render(described.size, depth + 1)),
KIND_VOLATILE => alloc::format!("volatile {}", self.render(described.size, depth + 1)),
KIND_RESTRICT => alloc::format!("{} restrict", self.render(described.size, depth + 1)),
KIND_SIGNATURE => self.render_signature(&described, depth),
KIND_LOOKING_AHEAD => alloc::format!("struct {named}"),
_ => String::from(named),
}
}
fn render_signature(&self, described: &Described, depth: usize) -> String {
let mut rendered = alloc::format!("{} (", self.render(described.size, depth + 1));
for index in 0..described.members {
let parameter = described.body + (index * PARAMETER_SIZE);
if index != 0 {
rendered.push_str(", ");
}
rendered.push_str(&self.render(word_at(self.types, parameter + 4), depth + 1));
}
rendered.push(')');
rendered
}
fn span_of(&self, id: u32, depth: usize) -> usize {
if depth == MAX_DEPTH {
return 0;
}
let Some(described) = self.with_id(id) else {
return 0;
};
match described.kind {
KIND_POINTER => core::mem::size_of::<usize>(),
KIND_ARRAY => {
let element = word_at(self.types, described.body);
let count = word_at(self.types, described.body + 8) as usize;
self.span_of(element, depth + 1) * count
}
KIND_INT | KIND_STRUCT | KIND_UNION | KIND_ENUM | KIND_WIDE_ENUM | KIND_FLOAT => {
described.size as usize
}
KIND_TYPEDEF | KIND_CONSTANT | KIND_VOLATILE | KIND_RESTRICT | KIND_TAGGED_TYPE => {
self.span_of(described.size, depth + 1)
}
_ => 0,
}
}
fn bits_of(&self, described: &Described, member: usize) -> usize {
if !described.members_carry_bitfields {
return 0;
}
(word_at(self.types, member + 8) >> 24) as usize
}
fn constant_named(&self, wanted: &str) -> Option<i64> {
let known = unsafe { (&raw mut CONSTANTS).as_mut().unwrap() };
if let Some(value) = known.get(wanted) {
return Some(*value);
}
let mut found = None;
self.visit_every_type(&mut |described| {
if !self.holds_constants(described) {
return true;
}
self.visit_constants(described, &mut |name, value| {
if name == wanted {
found = Some(value);
}
found.is_none()
});
found.is_none()
});
if let Some(value) = found {
known.insert(String::from(wanted), value);
}
found
}
fn enum_named(&self, wanted: &str) -> Option<Described> {
let mut found = None;
self.visit_every_type(&mut |described| {
if self.holds_constants(described)
&& described.name != 0
&& self.name_of(described.name) == wanted
{
found = Some(described.clone());
}
found.is_none()
});
found
}
fn visit_constants(&self, described: &Described, on_constant: &mut dyn FnMut(&str, i64) -> bool) {
let wide = described.kind == KIND_WIDE_ENUM;
let stride = if wide { 12 } else { 8 };
for index in 0..described.members {
let constant = described.body + (index * stride);
let name = self.name_of(word_at(self.types, constant));
let value = if wide {
((word_at(self.types, constant + 8) as u64) << 32
| word_at(self.types, constant + 4) as u64) as i64
} else {
word_at(self.types, constant + 4) as i32 as i64
};
if !on_constant(name, value) {
return;
}
}
}
fn visit_every_type(&self, on_type: &mut dyn FnMut(&Described) -> bool) {
self.visit_every_type_at(&mut |_offset, described| on_type(described));
}
fn visit_every_type_at(&self, on_type: &mut dyn FnMut(usize, &Described) -> bool) {
let mut offset = 0;
while let Some(described) = self.at(offset) {
if !on_type(offset, &described) {
return;
}
offset = described.body + described.body_size;
}
}
fn holds_constants(&self, described: &Described) -> bool {
described.kind == KIND_ENUM || described.kind == KIND_WIDE_ENUM
}
// A structure the kernel lays out itself keeps its fields in a member with no name of its
// own, so a field is looked for through those as well, and answers where it sits in the
// structure the question was asked about.
fn offset_of(&self, described: &Described, field: &str) -> Option<usize> {
for index in 0..described.members {
let member = described.body + (index * MEMBER_SIZE);
let name = self.name_of(word_at(self.types, member));
let placement = self.placement_of(described, member);
if name == field {
return Some(placement);
}
if name.is_empty() {
let within = self.with_id(word_at(self.types, member + 4))?;
if within.kind == KIND_STRUCT || within.kind == KIND_UNION {
if let Some(deeper) = self.offset_of(&within, field) {
return Some(placement + deeper);
}
}
}
}
None
}
fn placement_of(&self, described: &Described, member: usize) -> usize {
let placement = word_at(self.types, member + 8);
let bits = if described.members_carry_bitfields {
placement & 0x00ff_ffff
} else {
placement
};
bits as usize
}
fn at(&self, offset: usize) -> Option<Described> {
if offset + DESCRIPTION_SIZE > self.types.len() {
return None;
}
let name = word_at(self.types, offset);
let info = word_at(self.types, offset + 4);
let size = word_at(self.types, offset + 8);
let kind = (info >> 24) & 0x1f;
let members = (info & 0xffff) as usize;
Some(Described {
name,
kind,
size,
members,
members_carry_bitfields: (info >> 31) != 0,
body: offset + DESCRIPTION_SIZE,
body_size: body_size_of(kind, members),
})
}
fn name_of(&self, offset: u32) -> &'static str {
let start = offset as usize;
let end = self.names[start..]
.iter()
.position(|letter| *letter == 0)
.map(|length| start + length)
.unwrap_or(self.names.len());
core::str::from_utf8(&self.names[start..end]).unwrap_or("")
}
}
static mut CONSTANTS: BTreeMap<String, i64> = BTreeMap::new();
static mut FUNCTIONS: Option<Vec<Named>> = None;
static mut INDEX: Option<Index> = None;
static mut RESOLVED: Vec<Described> = Vec::new();
struct Named {
name: u32,
offset: u32,
}
struct Index {
containers: BTreeMap<&'static str, u32>,
checkpoints: Vec<u32>,
offset: usize,
id: u32,
}
#[derive(Clone)]
struct Described {
name: u32,
kind: u32,
size: u32,
members: usize,
members_carry_bitfields: bool,
body: usize,
body_size: usize,
}
fn described_types() -> Option<DescribedTypes> {
let start = unsafe { ___start_BTF } as usize;
let stop = unsafe { ___stop_BTF } as usize;
if start == 0 || stop <= start {
return None;
}
let described = unsafe { core::slice::from_raw_parts(start as *const u8, stop - start) };
if u16::from_ne_bytes([described[0], described[1]]) != DESCRIPTION_MAGIC {
return None;
}
let header_size = word_at(described, 4) as usize;
let types = header_size + word_at(described, 8) as usize;
let types_size = word_at(described, 12) as usize;
let names = header_size + word_at(described, 16) as usize;
let names_size = word_at(described, 20) as usize;
if names + names_size > described.len() {
return None;
}
Some(DescribedTypes {
types: &described[types..types + types_size],
names: &described[names..names + names_size],
})
}
fn body_size_of(kind: u32, members: usize) -> usize {
match kind {
KIND_INT | KIND_VARIABLE | KIND_TAG => 4,
KIND_ARRAY => 12,
KIND_STRUCT | KIND_UNION => members * MEMBER_SIZE,
KIND_ENUM | KIND_SIGNATURE => members * 8,
KIND_SECTION | KIND_WIDE_ENUM => members * 12,
_ => 0,
}
}
fn word_at(bytes: &[u8], offset: usize) -> u32 {
u32::from_ne_bytes([
bytes[offset],
bytes[offset + 1],
bytes[offset + 2],
bytes[offset + 3],
])
}
const DESCRIPTION_MAGIC: u16 = 0xeb9f;
const DESCRIPTION_SIZE: usize = 12;
const FIRST_ID: usize = 1;
const CHECKPOINT_SPAN: usize = 64;
const MEMBER_SIZE: usize = 12;
const KIND_INT: u32 = 1;
const KIND_POINTER: u32 = 2;
const KIND_ARRAY: u32 = 3;
const KIND_STRUCT: u32 = 4;
const KIND_UNION: u32 = 5;
const KIND_ENUM: u32 = 6;
const KIND_FUNCTION: u32 = 12;
const KIND_LOOKING_AHEAD: u32 = 7;
const KIND_TYPEDEF: u32 = 8;
const KIND_VOLATILE: u32 = 9;
const KIND_CONSTANT: u32 = 10;
const KIND_RESTRICT: u32 = 11;
const KIND_SIGNATURE: u32 = 13;
const KIND_VARIABLE: u32 = 14;
const KIND_SECTION: u32 = 15;
const KIND_TAG: u32 = 17;
const KIND_FLOAT: u32 = 16;
const KIND_TAGGED_TYPE: u32 = 18;
const KIND_WIDE_ENUM: u32 = 19;
const PARAMETER_SIZE: usize = 8;
const MAX_DEPTH: usize = 8;
unsafe extern "C" {
static ___start_BTF: *const c_void;
static ___stop_BTF: *const c_void;
}
-365
View File
@@ -1,365 +0,0 @@
use alloc::string::String;
use alloc::vec::Vec;
use core::ffi::c_void;
use core::ptr;
use crate::bindings::{
GError, GumElfModule, GumExportDetails, GumMemoryRange, GumModuleRegistry, gboolean,
g_bytes_new, g_bytes_unref, g_clear_error, g_object_unref, gconstpointer, gpointer, gsize,
gum_barebone_register_module, gum_barebone_unregister_module,
gum_elf_module_enumerate_exports, gum_elf_module_enumerate_symbols,
gum_elf_module_get_preferred_address, gum_elf_module_new_from_blob, gum_interceptor_begin_transaction,
gum_interceptor_end_transaction, gum_interceptor_obtain, gum_interceptor_replace,
};
use crate::bindings::{GumElfSymbolBind_GUM_ELF_BIND_LOCAL, GumElfSymbolDetails};
use crate::gum::{self, FoundExportCallback, FoundSymbolCallback};
use alloc::ffi::CString;
use super::user::{EXECUTABLE, READABLE, WRITABLE, contents_of};
pub fn register_what_the_copy_lives_among(registry: *mut GumModuleRegistry) {
unsafe { REGISTRY = registry };
for image in mapped_images() {
announce(&image);
unsafe { known() }.push(image);
}
}
// The loader calls the same empty function every time it has changed what is mapped -- that is
// what a debugger watches -- so the copy asks again from there.
pub fn watch_the_loader() {
let Some(rendezvous) = where_the_loader_says_so() else {
return;
};
unsafe {
let interceptor = gum_interceptor_obtain();
gum_interceptor_begin_transaction(interceptor);
gum_interceptor_replace(interceptor, rendezvous as gpointer,
the_loader_changed_something as gpointer, &raw mut LOADER_SAYS_SO,
ptr::null_mut());
gum_interceptor_end_transaction(interceptor);
}
}
unsafe extern "C" fn the_loader_changed_something() {
let said_so: unsafe extern "C" fn() = unsafe { core::mem::transmute(LOADER_SAYS_SO) };
unsafe { said_so() };
look_again();
}
fn look_again() {
let registry = unsafe { REGISTRY };
if registry.is_null() {
return;
}
let mapped = mapped_images();
let known = unsafe { known() };
for image in &mapped {
if !known.iter().any(|seen| seen.base == image.base) {
announce(image);
}
}
for seen in known.iter() {
if !mapped.iter().any(|image| image.base == seen.base) {
unsafe { gum_barebone_unregister_module(registry, seen.base) };
}
}
*known = mapped;
}
fn announce(image: &Image) {
let range = GumMemoryRange {
base_address: image.base,
size: image.size as gsize,
};
let module = gum::gum_native_module_new(&image.path, "", &range);
unsafe {
gum_barebone_register_module(unsafe { REGISTRY }, module);
g_object_unref(module as gpointer);
}
}
pub fn enumerate_exports_in_range(from: u64, to: u64, found: &mut FoundExportCallback<'_>) {
let Some(image) = mapped_images().into_iter().find(|image| image.base == from) else {
return;
};
let Some(module) = read_the_image(&image) else {
return;
};
let mut asking = Asking {
found,
slide: slide_of(&image, module),
from,
to,
};
unsafe {
gum_elf_module_enumerate_exports(
module,
Some(crate::signed_to_be_called_back(note_an_export, 0)),
&mut asking as *mut Asking<'_, '_> as gpointer,
);
g_object_unref(module as gpointer);
}
}
pub fn enumerate_symbols_in_range(from: u64, to: u64, found: &mut FoundSymbolCallback<'_>) {
let Some(image) = mapped_images().into_iter().find(|image| image.base == from) else {
return;
};
let Some(module) = read_the_image(&image) else {
return;
};
let mut asking = AskingForSymbols {
found,
slide: slide_of(&image, module),
from,
to,
};
unsafe {
gum_elf_module_enumerate_symbols(
module,
Some(crate::signed_to_be_called_back(note_a_symbol, 0)),
&mut asking as *mut AskingForSymbols<'_, '_> as gpointer,
);
g_object_unref(module as gpointer);
}
}
struct AskingForSymbols<'a, 'b> {
found: &'a mut FoundSymbolCallback<'b>,
slide: u64,
from: u64,
to: u64,
}
unsafe extern "C" fn note_a_symbol(
details: *const GumElfSymbolDetails,
asking: gpointer,
) -> gboolean {
let asking = unsafe { &mut *(asking as *mut AskingForSymbols<'_, '_>) };
let details = unsafe { &*details };
if details.address == 0 {
return 1;
}
let address = details.address.wrapping_add(asking.slide);
if address < asking.from || address >= asking.to {
return 1;
}
(asking.found)(
details.name,
address,
details.size as u64,
details.type_ as u8,
details.bind != GumElfSymbolBind_GUM_ELF_BIND_LOCAL,
) as gboolean
}
fn slide_of(image: &Image, module: *mut GumElfModule) -> u64 {
image
.base
.wrapping_sub(unsafe { gum_elf_module_get_preferred_address(module) })
}
fn read_the_image(image: &Image) -> Option<*mut GumElfModule> {
let path = CString::new(image.path.as_str()).ok()?;
let data = contents_of(&path);
if data.is_empty() {
return None;
}
unsafe {
let blob = g_bytes_new(data.as_ptr() as gconstpointer, data.len() as gsize);
let mut error: *mut GError = ptr::null_mut();
let module = gum_elf_module_new_from_blob(blob, &mut error);
g_bytes_unref(blob);
if module.is_null() {
g_clear_error(&mut error);
return None;
}
Some(module)
}
}
struct Asking<'a, 'b> {
found: &'a mut FoundExportCallback<'b>,
slide: u64,
from: u64,
to: u64,
}
unsafe extern "C" fn note_an_export(details: *const GumExportDetails, asking: gpointer) -> gboolean {
let asking = unsafe { &mut *(asking as *mut Asking<'_, '_>) };
let details = unsafe { &*details };
let address = details.address.wrapping_add(asking.slide);
if address < asking.from || address >= asking.to {
return 1;
}
(asking.found)(details.name, address) as gboolean
}
fn where_the_loader_says_so() -> Option<u64> {
let mut found = 0u64;
for image in mapped_images() {
let mut on_export = |name: *const crate::bindings::gchar, address: u64| {
if unsafe { core::ffi::CStr::from_ptr(name) } == c"_dl_debug_state" {
found = address;
}
found == 0
};
enumerate_exports_in_range(image.base, image.base + image.size, &mut on_export);
if found != 0 {
return Some(found);
}
}
None
}
unsafe fn known() -> &'static mut Vec<Image> {
unsafe { (&raw mut KNOWN).as_mut().unwrap() }
}
static mut KNOWN: Vec<Image> = Vec::new();
static mut REGISTRY: *mut GumModuleRegistry = ptr::null_mut();
static mut LOADER_SAYS_SO: *mut c_void = ptr::null_mut();
pub fn enumerate_ranges(found: &mut dyn FnMut(u64, u64, u32)) {
let listed = contents_of(c"/proc/self/maps");
for line in listed.split(|byte| *byte == b'\n') {
let Some(mapping) = mapping_in(line) else {
continue;
};
if super::user::range_is_ours(mapping.start) {
continue;
}
found(mapping.start, mapping.end - mapping.start, mapping.protection);
}
}
pub fn protection_at(address: u64) -> u32 {
if super::user::range_is_ours(address) {
return 0;
}
let listed = contents_of(c"/proc/self/maps");
for line in listed.split(|byte| *byte == b'\n') {
let Some(mapping) = mapping_in(line) else {
continue;
};
if address >= mapping.start && address < mapping.end {
return mapping.protection;
}
}
0
}
fn mapped_images() -> Vec<Image> {
let listed = contents_of(c"/proc/self/maps");
let mut images: Vec<Image> = Vec::new();
for line in listed.split(|byte| *byte == b'\n') {
let Some(mapping) = mapping_in(line) else {
continue;
};
if mapping.path.is_empty() || super::user::range_is_ours(mapping.start) {
continue;
}
let holds_code = mapping.protection & EXECUTABLE as u32 != 0;
match images.last_mut() {
Some(image) if image.path.as_bytes() == mapping.path => {
image.size = mapping.end - image.base;
image.holds_code |= holds_code;
}
_ => images.push(Image {
path: String::from_utf8_lossy(mapping.path).into_owned(),
base: mapping.start,
size: mapping.end - mapping.start,
holds_code,
}),
}
}
images.retain(|image| image.holds_code);
images
}
fn mapping_in(line: &[u8]) -> Option<Mapping<'_>> {
let boundary = line.iter().position(|byte| *byte == b'-')?;
let start = number_in(&line[..boundary])?;
let rest = &line[boundary + 1..];
let mut fields = rest.split(|byte| *byte == b' ');
let end = number_in(fields.next()?)?;
let how = fields.next()?;
Some(Mapping {
path: line[..].iter().position(|byte| *byte == b'/').map_or(&[], |at| &line[at..]),
start,
end,
protection: protection_of(how),
})
}
fn protection_of(how: &[u8]) -> u32 {
let mut protection = 0;
if how[0] == b'r' {
protection |= READABLE as u32;
}
if how[1] == b'w' {
protection |= WRITABLE as u32;
}
if how[2] == b'x' {
protection |= EXECUTABLE as u32;
}
protection
}
fn number_in(digits: &[u8]) -> Option<u64> {
let mut value = 0u64;
for digit in digits {
value = (value << 4) | (digit.to_ascii_lowercase() as char).to_digit(16)? as u64;
}
Some(value)
}
struct Image {
path: String,
base: u64,
size: u64,
holds_code: bool,
}
struct Mapping<'a> {
path: &'a [u8],
start: u64,
end: u64,
protection: u32,
}
-91
View File
@@ -1,91 +0,0 @@
// Linux backend. The agent reaches the kernel in one of two ways, and the rest
// of the agent cannot tell which: loaded from the inside as a kernel module,
// where every primitive is a call into a shim kbuild compiled against the
// target's headers; or injected from the outside like XNU and Windows, where
// the host patches the addresses it needs into .kernel_addrs and the memory it
// cannot touch is reached over the hostlink.
use alloc::string::String;
pub fn stack_headroom() -> usize {
let marker = 0usize;
let here = &marker as *const usize as usize;
here - (here & !(STACK_SPAN - 1))
}
#[cfg(any(target_arch = "aarch64", target_arch = "x86_64"))]
pub(crate) const STACK_SPAN: usize = 16 * 1024;
#[cfg(any(target_arch = "arm", target_arch = "x86"))]
pub(crate) const STACK_SPAN: usize = 8 * 1024;
#[cfg(feature = "linux")]
mod kmod;
#[cfg(feature = "linux-injected")]
mod arena;
#[cfg(feature = "linux-injected")]
mod facade;
#[cfg(feature = "linux-injected")]
#[cfg(feature = "linux-injected")]
mod injection;
#[cfg(feature = "linux-injected")]
pub(crate) mod layout;
#[cfg(feature = "linux-injected")]
mod mapped;
#[cfg(feature = "linux-injected")]
mod modules;
#[cfg(feature = "linux-injected")]
mod kfault;
#[cfg(feature = "linux-injected")]
mod native;
#[cfg(feature = "linux-injected")]
mod relay;
#[cfg(feature = "linux-injected")]
mod spawn;
#[cfg(feature = "linux-injected")]
mod threads;
#[cfg(feature = "linux-injected")]
pub(crate) mod user;
#[cfg(feature = "linux-injected")]
mod processes;
#[cfg(feature = "linux")]
pub use self::kmod::*;
#[cfg(feature = "linux-injected")]
pub use self::injection::*;
#[cfg(feature = "linux-injected")]
pub use self::relay::*;
#[cfg(feature = "linux-injected")]
pub use self::facade::*;
#[cfg(feature = "linux-injected")]
pub use self::native::{
alloc_dma, free_dma, get_kernel_base, get_kernel_size, install_interrupt_handler, map_io,
map_pages,
mmio_interrupt, pci_interrupt, release_fault_reporter, release_interrupt, run_when_ready,
patch_text, set_kernel_base, set_protection, virt_to_phys,
};
#[cfg(feature = "linux-injected")]
pub use self::mapped::*;
#[cfg(feature = "linux-injected")]
pub use self::modules::*;
#[cfg(feature = "linux-injected")]
pub use self::processes::*;
#[cfg(feature = "linux-injected")]
pub use self::spawn::*;
#[cfg(feature = "linux-injected")]
pub use self::threads::*;
#[derive(Debug, Clone)]
pub enum ModuleEvent {
Loaded,
Unloaded,
}
#[derive(Debug, Clone)]
pub struct LoadedModule {
pub name: String,
pub version: String,
pub base: u64,
pub size: u64,
}
-793
View File
@@ -1,793 +0,0 @@
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::ffi::{c_char, c_int, c_long, c_void};
use core::ptr;
use super::layout::{field_offset, struct_size};
use crate::bindings::{g_free, gsize, gum_memory_read};
use super::{LoadedModule, ModuleEvent};
pub fn enumerate_modules() -> Vec<LoadedModule> {
let Some(layout) = module_layout() else {
return Vec::new();
};
let head = unsafe { _modules } as usize;
if head == 0 {
return Vec::new();
}
let mut modules = Vec::new();
let mut node = word_at(head);
let mut left = MAX_MODULES;
while node != head && node != 0 && left != 0 {
let module = node - layout.list;
if is_live(module, &layout) {
modules.push(describe(module, &layout));
}
node = word_at(node);
left -= 1;
}
modules
}
pub fn enumerate_module_symbols(
base: u64,
on_symbol: &mut dyn FnMut(*const c_char, u64, u64, u8, bool) -> bool,
) -> bool {
let Some(layout) = module_layout() else {
return false;
};
let Some(kallsyms) = layout.kallsyms.as_ref() else {
return false;
};
let Some(module) = module_at(base, layout) else {
return false;
};
let table = word_at(module + kallsyms.at);
if table == 0 {
return false;
}
let symbols = word_at(table + kallsyms.symtab);
let names = word_at(table + kallsyms.strtab);
let count = u32_at(table + kallsyms.num_symtab) as usize;
if symbols == 0 || names == 0 {
return false;
}
for index in 0..count {
let symbol = symbols + index * SYMBOL_SIZE;
let value = symbol_value(symbol);
if value == 0 {
continue;
}
let info = symbol_info(symbol);
let name = (names + u32_at(symbol) as usize) as *const c_char;
if !on_symbol(
name,
value as u64,
symbol_size(symbol),
info & 0xf,
info >> 4 != LOCAL,
) {
break;
}
}
true
}
pub fn enumerate_module_exports(
base: u64,
on_export: &mut dyn FnMut(*const c_char, u64) -> bool,
) -> bool {
let Some(layout) = module_layout() else {
return false;
};
let Some(exports) = layout.exports.as_ref() else {
return false;
};
let Some(module) = module_at(base, layout) else {
return false;
};
let table = word_at(module + exports.at);
let count = u32_at(module + exports.count) as usize;
if table == 0 {
return true;
}
for index in 0..count {
let entry = table + index * exports.stride;
let (name, address) = if exports.relative {
(
relative_target(entry + OFFSET_SIZE),
relative_target(entry) as u64,
)
} else {
(word_at(entry + WORD), word_at(entry) as u64)
};
if name == 0 || address == 0 {
continue;
}
if !on_export(name as *const c_char, address) {
break;
}
}
true
}
fn module_at(base: u64, layout: &Layout) -> Option<usize> {
let head = unsafe { _modules } as usize;
if head == 0 {
return None;
}
let mut node = word_at(head);
let mut left = MAX_MODULES;
while node != head && node != 0 && left != 0 {
let module = node - layout.list;
if describe(module, layout).base == base {
return Some(module);
}
node = word_at(node);
left -= 1;
}
None
}
fn relative_target(at: usize) -> usize {
let offset = unsafe { (at as *const i32).read_volatile() };
at.wrapping_add_signed(offset as isize)
}
pub fn watch_modules(on_event: fn(ModuleEvent, LoadedModule)) {
if module_layout().is_none() {
return;
}
unsafe {
if _register_module_notifier.is_none() {
return;
}
WATCHER = Some(on_event);
NOTIFIER = Box::into_raw(Box::new(NotifierBlock {
notifier_call: Some(NOTIFIER_CALL),
next: ptr::null_mut(),
priority: 0,
}));
register_notifier(NOTIFIER as *mut c_void);
}
}
pub fn unwatch_modules() {
unsafe {
if (&raw const WATCHER).read().is_none() {
return;
}
if _unregister_module_notifier.is_some() {
unregister_notifier(NOTIFIER as *mut c_void);
}
drop(Box::from_raw(NOTIFIER));
NOTIFIER = ptr::null_mut();
WATCHER = None;
}
}
#[cfg(not(target_arch = "x86"))]
unsafe fn register_notifier(block: *mut c_void) {
unsafe {
if let Some(register) = _register_module_notifier {
register(block);
}
}
}
#[cfg(not(target_arch = "x86"))]
unsafe fn unregister_notifier(block: *mut c_void) {
unsafe {
if let Some(unregister) = _unregister_module_notifier {
unregister(block);
}
}
}
#[cfg(target_arch = "x86")]
unsafe fn register_notifier(block: *mut c_void) {
unsafe { frida_k_register_module_notifier(block) };
}
#[cfg(target_arch = "x86")]
unsafe fn unregister_notifier(block: *mut c_void) {
unsafe { frida_k_unregister_module_notifier(block) };
}
#[cfg(not(target_arch = "x86"))]
const NOTIFIER_CALL: unsafe extern "C" fn(*mut NotifierBlock, c_long, *mut c_void) -> c_int =
on_module_state;
#[cfg(target_arch = "x86")]
const NOTIFIER_CALL: unsafe extern "C" fn(*mut NotifierBlock, c_long, *mut c_void) -> c_int =
frida_kcb_module_state;
#[cfg(target_arch = "x86")]
#[unsafe(no_mangle)]
unsafe extern "C" fn frida_cb_module_state(
block: *mut NotifierBlock,
action: c_long,
module: *mut c_void,
) -> c_int {
unsafe { on_module_state(block, action, module) }
}
unsafe extern "C" fn on_module_state(
_block: *mut NotifierBlock,
action: c_long,
module: *mut c_void,
) -> c_int {
let kind = match action {
MODULE_STATE_LIVE => ModuleEvent::Loaded,
MODULE_STATE_GOING => ModuleEvent::Unloaded,
_ => return NOTIFY_DONE,
};
let Some(on_event) = (unsafe { WATCHER }) else {
return NOTIFY_DONE;
};
let Some(layout) = module_layout() else {
return NOTIFY_DONE;
};
on_event(kind, describe(module as usize, &layout));
NOTIFY_DONE
}
fn describe(module: usize, layout: &Layout) -> LoadedModule {
let (base, size) = match layout.memory {
Memory::Described { at, base, size } => (word_at(module + at + base), word_at(module + at + size)),
Memory::Split { base, size } => (word_at(module + base), word_at(module + size)),
};
LoadedModule {
name: text_at(module + layout.name),
version: layout
.version
.map(|at| text_at(word_at(module + at)))
.unwrap_or_default(),
base: base as u64,
size: (size & 0xffff_ffff) as u64,
}
}
fn is_live(module: usize, layout: &Layout) -> bool {
let Some(at) = layout.state else {
return true;
};
let state = unsafe { ((module + at) as *const u32).read_volatile() };
state == MODULE_STATE_LIVE as u32
}
fn module_layout() -> Option<&'static Layout> {
unsafe {
let known = (&raw mut LAYOUT).as_mut().unwrap();
if known.is_none() {
*known = discover_layout();
}
known.as_ref()
}
}
static mut LAYOUT: Option<Layout> = None;
struct Layout {
list: usize,
name: usize,
version: Option<usize>,
state: Option<usize>,
memory: Memory,
kallsyms: Option<Kallsyms>,
exports: Option<Exports>,
}
struct Kallsyms {
at: usize,
symtab: usize,
num_symtab: usize,
strtab: usize,
}
struct Exports {
at: usize,
count: usize,
stride: usize,
relative: bool,
}
enum Memory {
Described { at: usize, base: usize, size: usize },
Split { base: usize, size: usize },
}
fn discover_layout() -> Option<Layout> {
layout_from_types().or_else(layout_from_probing)
}
fn layout_from_types() -> Option<Layout> {
let memory = match field_offset("module", "mem") {
Some(at) => Memory::Described {
at,
base: field_offset("module_memory", "base")?,
size: field_offset("module_memory", "size")?,
},
None => match field_offset("module", "core_layout") {
Some(at) => Memory::Described {
at,
base: field_offset("module_layout", "base")?,
size: field_offset("module_layout", "size")?,
},
None => Memory::Split {
base: field_offset("module", "module_core")?,
size: field_offset("module", "core_size")?,
},
},
};
let list = field_offset("module", "list")?;
Some(Layout {
list,
name: field_offset("module", "name")?,
version: field_offset("module", "version"),
state: field_offset("module", "state"),
memory,
kallsyms: kallsyms_from_types(),
exports: exports_from_types(list),
})
}
fn layout_from_probing() -> Option<Layout> {
let list = WORD;
let name = list + 2 * WORD;
let start = (name + MODULE_NAME_LEN + WORD - 1) & !(WORD - 1);
let (at, size) = probe_memory(list, start)?;
let memory = Memory::Described { at, base: 0, size };
Some(Layout {
list,
name,
version: None,
state: Some(0),
kallsyms: probe_kallsyms(list, start, &memory),
exports: probe_exports(list, start, &memory),
memory,
})
}
fn probe_memory(list: usize, start: usize) -> Option<(usize, usize)> {
let head = unsafe { _modules } as usize;
if head == 0 {
return None;
}
for at in (start..PROBE_SPAN).step_by(WORD) {
for size in (WORD..=MAX_SIZE_DISTANCE).step_by(WORD) {
if each_module(head, list, |module| describes_memory(module, at, size)) {
return Some((at, size));
}
}
}
None
}
fn describes_memory(module: usize, at: usize, size: usize) -> bool {
let base = word_at(module + at);
let size = unsafe { ((module + at + size) as *const u32).read_volatile() } as usize;
base != 0
&& base & (PAGE_SIZE - 1) == 0
&& size != 0
&& size & (PAGE_SIZE - 1) == 0
&& size <= MAX_MODULE_SIZE
&& base.abs_diff(module) <= MODULE_REACH
}
fn each_module(head: usize, list: usize, is_sane: impl Fn(usize) -> bool) -> bool {
let mut node = word_at(head);
let mut seen = 0;
let mut left = MAX_MODULES;
while node != head && node != 0 && left != 0 {
if !is_sane(node - list) {
return false;
}
seen += 1;
node = word_at(node);
left -= 1;
}
seen != 0
}
fn kallsyms_from_types() -> Option<Kallsyms> {
Some(Kallsyms {
at: field_offset("module", "kallsyms")?,
symtab: field_offset("mod_kallsyms", "symtab")?,
num_symtab: field_offset("mod_kallsyms", "num_symtab")?,
strtab: field_offset("mod_kallsyms", "strtab")?,
})
}
fn exports_from_types(_list: usize) -> Option<Exports> {
let at = field_offset("module", "syms")?;
let count = field_offset("module", "num_syms")?;
let relative = field_offset("kernel_symbol", "value_offset").is_some();
Some(Exports {
at,
count,
stride: struct_size("kernel_symbol").unwrap_or(stride_of_exports(relative)),
relative,
})
}
fn probe_kallsyms(list: usize, start: usize, memory: &Memory) -> Option<Kallsyms> {
let head = unsafe { _modules } as usize;
for at in (start..PROBE_SPAN).step_by(WORD) {
if each_module(head, list, |module| describes_kallsyms(module, at, memory)) {
return Some(Kallsyms {
at,
symtab: 0,
num_symtab: WORD,
strtab: 2 * WORD,
});
}
}
None
}
fn describes_kallsyms(module: usize, at: usize, memory: &Memory) -> bool {
let owned = regions_of(module, memory);
let table = word_at(module + at);
if !owned.holds(table, 3 * WORD) {
return false;
}
let Some(symbols) = try_read_word(table) else {
return false;
};
let Some(count) = try_read_u32(table + WORD) else {
return false;
};
let Some(names) = try_read_word(table + 2 * WORD) else {
return false;
};
let count = count as usize;
if count == 0 || count > MAX_SYMBOLS {
return false;
}
if !owned.holds(symbols, count * SYMBOL_SIZE) || !owned.holds(names, 1) {
return false;
}
try_read_byte(names) == Some(0)
}
fn probe_exports(list: usize, start: usize, memory: &Memory) -> Option<Exports> {
let head = unsafe { _modules } as usize;
for at in (start..PROBE_SPAN).step_by(WORD) {
for count in counts_near(at, start) {
for relative in [true, false] {
let stride = stride_of_exports(relative);
if exports_shaped(head, list, at, count, stride, relative, memory) {
return Some(Exports {
at,
count,
stride,
relative,
});
}
}
}
}
None
}
fn exports_shaped(
head: usize,
list: usize,
at: usize,
count: usize,
stride: usize,
relative: bool,
memory: &Memory,
) -> bool {
let mut evidence = false;
let mut node = word_at(head);
let mut left = MAX_MODULES;
while node != head && node != 0 && left != 0 {
let module = node - list;
let table = word_at(module + at);
let exported = u32_at(module + count) as usize;
if table == 0 || exported == 0 {
if table != 0 || exported != 0 {
return false;
}
} else {
if exported > MAX_EXPORTS {
return false;
}
let owned = regions_of(module, memory);
if !owned.holds(table, exported * stride) {
return false;
}
for index in 0..exported.min(EXPORTS_SAMPLED) {
if !names_an_export(table + index * stride, relative, &owned) {
return false;
}
}
evidence = true;
}
node = word_at(node);
left -= 1;
}
evidence
}
fn names_an_export(entry: usize, relative: bool, owned: &Regions) -> bool {
let name = if relative {
let Some(offset) = try_read_u32(entry + OFFSET_SIZE) else {
return false;
};
(entry + OFFSET_SIZE).wrapping_add_signed(offset as i32 as isize)
} else {
match try_read_word(entry + WORD) {
Some(name) => name,
None => return false,
}
};
if !owned.holds(name, 1) {
return false;
}
match try_read_byte(name) {
Some(first) => first.is_ascii_alphabetic() || first == b'_',
None => false,
}
}
fn counts_near(at: usize, start: usize) -> impl Iterator<Item = usize> {
let after = (WORD..=MAX_COUNT_DISTANCE).step_by(WORD).map(move |d| at + d);
let before = (WORD..=MAX_COUNT_DISTANCE)
.step_by(WORD)
.filter_map(move |d| at.checked_sub(d).filter(|&count| count >= start));
after.chain(before)
}
fn stride_of_exports(relative: bool) -> usize {
if relative { 3 * OFFSET_SIZE } else { 3 * WORD }
}
fn regions_of(module: usize, memory: &Memory) -> Regions {
let Memory::Described { at, base, size } = *memory else {
return Regions::default();
};
let mut owned = Regions::default();
for offset in (0..REGION_WINDOW).step_by(WORD) {
let entry = module + at + offset;
let start = word_at(entry + base);
let span = u32_at(entry + size) as usize;
if start != 0
&& start & (PAGE_SIZE - 1) == 0
&& span != 0
&& span & (PAGE_SIZE - 1) == 0
&& span <= MAX_MODULE_SIZE
&& start.abs_diff(module) <= MODULE_REACH
{
owned.add(start, span);
}
}
owned
}
#[derive(Default)]
struct Regions {
entries: [(usize, usize); MAX_REGIONS],
count: usize,
}
impl Regions {
fn add(&mut self, base: usize, size: usize) {
if self.count == MAX_REGIONS {
return;
}
self.entries[self.count] = (base, size);
self.count += 1;
}
fn holds(&self, address: usize, span: usize) -> bool {
let Some(end) = address.checked_add(span) else {
return false;
};
self.entries[..self.count]
.iter()
.any(|&(base, size)| address >= base && end <= base + size)
}
}
fn reads_as_pointer(address: usize) -> bool {
address >= LOWEST_MAPPING && address & (OFFSET_SIZE - 1) == 0
}
fn try_read_word(address: usize) -> Option<usize> {
Some(usize::from_ne_bytes(try_read(address)?))
}
fn try_read_u32(address: usize) -> Option<u32> {
let bytes = try_read::<4>(address)?;
Some(u32::from_ne_bytes(bytes))
}
fn try_read_byte(address: usize) -> Option<u8> {
Some(try_read::<1>(address)?[0])
}
fn try_read<const N: usize>(address: usize) -> Option<[u8; N]> {
unsafe {
let mut read: gsize = 0;
let data = gum_memory_read(address as *const c_void, N as gsize, &mut read);
if data.is_null() {
return None;
}
let mut bytes = [0u8; N];
core::ptr::copy_nonoverlapping(data as *const u8, bytes.as_mut_ptr(), N);
g_free(data as *mut c_void);
Some(bytes)
}
}
fn word_at(address: usize) -> usize {
unsafe { (address as *const usize).read_volatile() }
}
fn u32_at(address: usize) -> u32 {
unsafe { (address as *const u32).read_volatile() }
}
#[cfg(target_pointer_width = "64")]
fn symbol_value(symbol: usize) -> usize {
word_at(symbol + 8)
}
#[cfg(target_pointer_width = "64")]
fn symbol_size(symbol: usize) -> u64 {
word_at(symbol + 16) as u64
}
#[cfg(target_pointer_width = "64")]
fn symbol_info(symbol: usize) -> u8 {
unsafe { ((symbol + 4) as *const u8).read_volatile() }
}
#[cfg(target_pointer_width = "32")]
fn symbol_value(symbol: usize) -> usize {
word_at(symbol + 4)
}
#[cfg(target_pointer_width = "32")]
fn symbol_size(symbol: usize) -> u64 {
word_at(symbol + 8) as u64
}
#[cfg(target_pointer_width = "32")]
fn symbol_info(symbol: usize) -> u8 {
unsafe { ((symbol + 12) as *const u8).read_volatile() }
}
fn text_at(address: usize) -> String {
if address == 0 {
return String::new();
}
let mut text = String::new();
for i in 0..MAX_NAME {
let byte = unsafe { ((address + i) as *const u8).read_volatile() };
if byte == 0 {
break;
}
text.push(byte as char);
}
text
}
static mut WATCHER: Option<fn(ModuleEvent, LoadedModule)> = None;
static mut NOTIFIER: *mut NotifierBlock = ptr::null_mut();
#[repr(C)]
struct NotifierBlock {
notifier_call:
Option<unsafe extern "C" fn(*mut NotifierBlock, c_long, *mut c_void) -> c_int>,
next: *mut NotifierBlock,
priority: c_int,
}
const MODULE_STATE_LIVE: c_long = 0;
const MODULE_STATE_GOING: c_long = 2;
const NOTIFY_DONE: c_int = 0;
const MAX_MODULES: usize = 4096;
const WORD: usize = core::mem::size_of::<usize>();
const MODULE_NAME_LEN: usize = 64 - WORD;
const PAGE_SIZE: usize = 4096;
const PROBE_SPAN: usize = 1024;
const MAX_MODULE_SIZE: usize = 256 * 1024 * 1024;
const MODULE_REACH: usize = 512 * 1024 * 1024;
const MAX_SIZE_DISTANCE: usize = 3 * WORD;
const MAX_COUNT_DISTANCE: usize = 3 * WORD;
const REGION_WINDOW: usize = 512;
const MAX_REGIONS: usize = 16;
const OFFSET_SIZE: usize = 4;
const SYMBOL_SIZE: usize = if WORD == 8 { 24 } else { 16 };
const LOWEST_MAPPING: usize = 1 << 20;
const MAX_SYMBOLS: usize = 1 << 20;
const MAX_EXPORTS: usize = 1 << 16;
const EXPORTS_SAMPLED: usize = 64;
const LOCAL: u8 = 0;
const MAX_NAME: usize = 64;
#[cfg(target_arch = "x86")]
unsafe extern "C" {
fn frida_kcb_module_state(
block: *mut NotifierBlock,
action: c_long,
module: *mut c_void,
) -> c_int;
fn frida_k_register_module_notifier(block: *mut c_void) -> c_int;
fn frida_k_unregister_module_notifier(block: *mut c_void) -> c_int;
}
unsafe extern "C" {
static _modules: *const c_void;
static _register_module_notifier: Option<unsafe extern "C" fn(*mut c_void) -> c_int>;
#[allow(dead_code)]
static _unregister_module_notifier: Option<unsafe extern "C" fn(*mut c_void) -> c_int>;
}
-846
View File
@@ -1,846 +0,0 @@
// The agent injected from the outside: the host resolves the kernel functions
// below and patches their addresses into .kernel_addrs, the same way it does
// for XNU and Windows. Everything the kernel would otherwise have to lend us --
// its symbol table, its module list, the right to write to its pages -- comes
// from the host instead, over the hostlink.
use core::ffi::{c_char, c_int, c_uint, c_void};
use core::ptr;
use core::ptr::read_volatile;
use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use crate::kernel::ThreadEntry;
#[cfg(target_arch = "x86")]
pub fn log(msg: &str) {
unsafe { frida_k_log(msg.as_ptr() as *const c_char) };
}
#[cfg(not(target_arch = "x86"))]
pub fn log(msg: &str) {
unsafe {
if let Some(f) = __printk {
f(msg.as_ptr() as *const c_char)
} else {
_printk.unwrap()(msg.as_ptr() as *const c_char)
}
};
}
pub fn panic(msg: &str) -> ! {
unsafe { _panic(msg.as_ptr() as *const c_char) }
}
pub fn run_when_ready(action: fn()) {
waiters();
action();
}
pub fn install_fault_reporter() {
super::kfault::install();
}
pub fn release_fault_reporter() {
super::kfault::uninstall();
}
pub fn send_signal(signal: c_int, task: usize) {
unsafe { _send_sig(signal, task as *mut c_void, 1) };
}
pub fn release_interrupt() {
let Some(interrupt) = (unsafe { (&raw mut INTERRUPT).as_mut().unwrap().take() }) else {
return;
};
unsafe { _free_irq(interrupt.irq, interrupt.target) };
}
pub fn take_the_file(descriptor: u32) -> *mut c_void {
unsafe { _fget(descriptor) }
}
pub fn let_the_file_go(file: *mut c_void) {
unsafe { super::processes::let_go_of(file) };
}
pub fn wait_for_a_word(file: *mut c_void) -> bool {
let mut byte = 0u8;
let mut at = 0i64;
unsafe { _kernel_read(file, &mut byte, 1, &mut at) == 1 }
}
pub fn leave_a_word(file: *mut c_void) {
let byte = 0u8;
let mut at = 0i64;
unsafe { _kernel_write(file, &byte, 1, &mut at) };
}
pub fn spawn_thread(entry: ThreadEntry, parameter: *mut c_void) -> isize {
let trampoline = alloc(size_of::<Trampoline>()) as *mut Trampoline;
unsafe {
trampoline.write(Trampoline { entry, parameter });
let task = _kthread_create_on_node(
THREAD_ENTRY,
trampoline as *mut c_void,
NUMA_NO_NODE,
c"frida".as_ptr(),
);
_wake_up_process(task);
super::processes::cloak_task(task);
}
0
}
#[cfg(target_arch = "x86")]
pub fn alloc(size: usize) -> *mut u8 {
unsafe { frida_k_alloc(size, GFP_KERNEL as usize) }
}
#[cfg(not(target_arch = "x86"))]
pub fn alloc(size: usize) -> *mut u8 {
unsafe {
if let Some(f) = ___kmalloc_noprof {
f(size, GFP_KERNEL)
} else {
_kmalloc.unwrap()(size, GFP_KERNEL)
}
}
}
pub fn free(ptr: *mut u8, _size: usize) {
unsafe { _kfree(ptr) };
}
#[cfg(target_arch = "x86")]
pub fn alloc_code(size: usize) -> *mut u8 {
unsafe {
frida_k_alloc_code(EXECMEM_MODULE_TEXT as usize, size, size.div_ceil(PAGE_SIZE))
}
}
#[cfg(not(target_arch = "x86"))]
pub fn alloc_code(size: usize) -> *mut u8 {
unsafe {
let code = if let Some(f) = _execmem_alloc {
f(EXECMEM_MODULE_TEXT, size)
} else {
_module_alloc.unwrap()(size)
};
if let Some(f) = _set_memory_rw {
f(code as usize, size.div_ceil(PAGE_SIZE) as c_int);
}
code
}
}
#[cfg(target_arch = "x86")]
pub fn free_code(ptr: *mut u8, _size: usize) {
unsafe { frida_k_free_code(ptr) };
}
#[cfg(not(target_arch = "x86"))]
pub fn free_code(ptr: *mut u8, _size: usize) {
unsafe {
if let Some(f) = _execmem_free {
f(ptr);
} else {
_module_memfree.unwrap()(ptr);
}
}
}
pub fn page_size() -> usize {
crate::gum::page_size_the_kernel_runs_with()
}
pub fn protect(address: u64, size: usize, protection: u32) -> bool {
#[cfg(not(target_arch = "arm"))]
if set_protection(address, size, protection) {
return true;
}
crate::gum_injected::ask_the_host_to_protect(address, size, protection)
}
#[cfg(target_arch = "x86")]
pub fn set_protection(_address: u64, _size: usize, _protection: u32) -> bool {
false
}
#[cfg(target_arch = "aarch64")]
pub fn patch_text(address: u64, data: *const u8, len: usize) -> bool {
if address % 4 != 0 || len % 4 != 0 {
return false;
}
let count = len / 4;
let mut addrs = alloc::vec::Vec::with_capacity(count);
let mut insns = alloc::vec::Vec::with_capacity(count);
for i in 0..count {
addrs.push((address as usize + (i * 4)) as *mut c_void);
insns.push(unsafe { (data as *const u32).add(i).read_unaligned() });
}
unsafe { _aarch64_insn_patch_text(addrs.as_mut_ptr(), insns.as_ptr(), count as c_int) == 0 }
}
#[cfg(target_arch = "arm")]
pub fn patch_text(address: u64, data: *const u8, len: usize) -> bool {
if address % 2 != 0 || len % 2 != 0 {
return false;
}
unsafe {
for i in 0..len / 4 {
let insn = (data as *const u32).add(i).read_unaligned();
_patch_text((address as usize + (i * 4)) as *mut c_void, insn);
}
}
true
}
#[cfg(target_arch = "x86_64")]
pub fn patch_text(address: u64, data: *const u8, len: usize) -> bool {
unsafe {
let Some(poke) = _text_poke_kgdb else {
return false;
};
!poke(address as *mut c_void, data as *const c_void, len).is_null()
}
}
#[cfg(target_arch = "x86")]
pub fn patch_text(address: u64, data: *const u8, len: usize) -> bool {
unsafe { !frida_k_text_poke(address as *mut c_void, data as *const c_void, len).is_null() }
}
#[cfg(not(target_arch = "x86"))]
pub fn set_protection(address: u64, size: usize, protection: u32) -> bool {
let pages = size.div_ceil(PAGE_SIZE) as c_int;
unsafe {
let write = if protection & GUM_PAGE_WRITE != 0 { _set_memory_rw } else { _set_memory_ro };
let Some(write) = write else {
return false;
};
if write(address as usize, pages) != 0 {
return false;
}
if protection & GUM_PAGE_EXECUTE != 0 {
if let Some(execute) = _set_memory_x {
execute(address as usize, pages);
}
}
}
true
}
const GUM_PAGE_WRITE: u32 = 2;
const GUM_PAGE_EXECUTE: u32 = 4;
pub fn current_process_id() -> u32 {
KERNEL_PROCESS
}
pub fn alloc_dma(size: usize) -> *mut u8 {
alloc(size)
}
pub fn free_dma(ptr: *mut u8, size: usize) {
free(ptr, size);
}
pub fn wait(_token: *const u8, timeout_us: Option<u64>, check: &mut dyn FnMut() -> bool) {
let waiters = waiters();
let mut waiter = [0usize; WAIT_ENTRY_WORDS];
let waiter = queue_this_thread(&mut waiter);
unsafe { _prepare_to_wait_event(waiters, waiter, TASK_INTERRUPTIBLE) };
if !check() {
match timeout_us {
Some(us) => {
let mut until = (us as i64) * 1000;
unsafe { _schedule_hrtimeout_range(&mut until, WAKE_SLACK_NS, HRTIMER_MODE_REL) };
}
None => unsafe { _schedule() },
}
}
unsafe { _finish_wait(waiters, waiter) };
}
pub fn wake(_token: *const u8) {
unsafe { ___wake_up(waiters(), TASK_NORMAL, WAKE_EVERY_WAITER, ptr::null_mut()) };
}
fn queue_this_thread(entry: &mut [usize; WAIT_ENTRY_WORDS]) -> *mut c_void {
let entry = entry.as_mut_ptr();
unsafe {
entry.add(WAIT_ENTRY_THREAD).write(current_task() as usize);
entry.add(WAIT_ENTRY_WAKE).write(_autoremove_wake_function as usize);
let queued = entry.add(WAIT_ENTRY_QUEUED);
queued.write(queued as usize);
queued.add(1).write(queued as usize);
}
entry as *mut c_void
}
fn waiters() -> *mut c_void {
let queue = (&raw mut WAITERS) as *mut c_void;
if !WAITERS_CLAIMED.swap(true, Ordering::AcqRel) {
unsafe {
___init_waitqueue_head(queue, c"frida".as_ptr(), (&raw mut WAITERS_KEY) as *mut c_void)
};
WAITERS_READY.store(true, Ordering::Release);
}
while !WAITERS_READY.load(Ordering::Acquire) {
core::hint::spin_loop();
}
queue
}
static mut WAITERS: [usize; WAIT_QUEUE_WORDS] = [0; WAIT_QUEUE_WORDS];
static mut WAITERS_KEY: [usize; LOCK_KEY_WORDS] = [0; LOCK_KEY_WORDS];
static WAITERS_CLAIMED: AtomicBool = AtomicBool::new(false);
static WAITERS_READY: AtomicBool = AtomicBool::new(false);
pub fn pci_interrupt(bus: u8, devfn: u8) -> Option<u32> {
let device = unsafe { _pci_get_domain_bus_and_slot(0, bus as c_uint, devfn as c_uint) };
if device.is_null() {
return None;
}
let line = unsafe { _pci_irq_vector(device, 0) };
unsafe { _pci_dev_put(device) };
if line < 0 {
return None;
}
Some(line as u32)
}
#[cfg(target_arch = "arm")]
pub fn mmio_interrupt(mmio_base: u64) -> Option<u32> {
let path = alloc::format!("/virtio_mmio@{:x}\0", mmio_base);
let node = unsafe { _of_find_node_opts_by_path(path.as_ptr() as *const c_char, ptr::null_mut()) };
if node.is_null() {
return None;
}
let line = unsafe { _of_irq_get(node, 0) };
unsafe { _of_node_put(node) };
if line <= 0 {
return None;
}
Some(line as u32)
}
#[cfg(not(target_arch = "arm"))]
pub fn mmio_interrupt(_mmio_base: u64) -> Option<u32> {
None
}
pub fn yield_now() {
unsafe { _schedule() };
}
pub fn monotonic_micros() -> i64 {
(unsafe { _ktime_get_mono_fast_ns() } / 1000) as i64
}
pub fn wall_clock_micros() -> (u32, u32) {
let mut now = Timespec64 { tv_sec: 0, tv_nsec: 0 };
unsafe { _ktime_get_real_ts64(&mut now) };
(now.tv_sec as u32, (now.tv_nsec / 1000) as u32)
}
// Linux keeps the running task where the architecture puts it: in a register
// of its own on arm64, and behind the per-CPU segment elsewhere, at an offset
// the host resolves along with everything else.
pub fn current_thread_id() -> u64 {
current_task() & JS_SAFE_THREAD_ID_MASK
}
#[cfg(target_arch = "arm")]
pub fn current_task() -> u64 {
let task: u32;
unsafe {
core::arch::asm!("mrc p15, 0, {}, c13, c0, 3", out(reg) task, options(nomem, nostack));
}
task as u64
}
#[cfg(target_arch = "aarch64")]
pub fn current_task() -> u64 {
let task: u64;
unsafe {
core::arch::asm!("mrs {}, sp_el0", out(reg) task, options(nomem, nostack));
}
task
}
#[cfg(target_arch = "x86_64")]
pub fn current_task() -> u64 {
let task: u64;
unsafe {
core::arch::asm!("mov {}, gs:[{}]", out(reg) task, in(reg) _current_task, options(nostack));
}
task
}
#[cfg(target_arch = "x86_64")]
pub fn top_of_stack() -> usize {
let top: usize;
unsafe {
core::arch::asm!("mov {}, gs:[{}]", out(reg) top, in(reg) _cpu_current_top_of_stack, options(nostack));
}
top
}
#[cfg(target_arch = "x86")]
pub fn current_task() -> u64 {
let task: u32;
unsafe {
core::arch::asm!("mov {}, fs:[{}]", out(reg) task, in(reg) _current_task as u32, options(nostack));
}
task as u64
}
pub fn get_kernel_base() -> u64 {
KERNEL_BASE.load(Ordering::Relaxed)
}
pub fn get_kernel_size() -> u64 {
let described = unsafe { &*core::ptr::addr_of!(crate::MODULE_INFO) };
described.first().map(|kernel| kernel.size as u64).unwrap_or(0)
}
pub fn set_kernel_base(base: u64) {
KERNEL_BASE.store(base, Ordering::Relaxed);
}
pub type IOInterruptHandler =
extern "C" fn(target: *mut c_void, refcon: *mut c_void, nub: *mut c_void, source: i32);
// The transport's interrupt arrives on a thread of the kernel's choosing, so
// what it carries -- the token to wake, and the handler that wakes it -- is put
// somewhere the trampoline can find it again.
pub fn install_interrupt_handler(
irq: u32,
target: *mut c_void,
handler: IOInterruptHandler,
refcon: *mut c_void,
) -> i32 {
unsafe {
INTERRUPT = Some(Interrupt { irq, target, handler, refcon });
_request_threaded_irq(
irq,
INTERRUPT_ENTRY,
None,
IRQF_SHARED,
c"frida".as_ptr(),
target,
)
}
}
#[cfg(target_arch = "arm")]
pub fn map_io(phys_addr: u64, size: u64) -> *mut c_void {
unsafe { _ioremap(phys_addr as u32, size as usize) }
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub fn map_io(phys_addr: u64, size: u64) -> *mut c_void {
unsafe { _ioremap(phys_addr as usize, size as usize) }
}
#[cfg(not(any(target_arch = "arm", target_arch = "x86", target_arch = "x86_64")))]
pub fn map_io(phys_addr: u64, size: u64) -> *mut c_void {
unsafe { _generic_ioremap_prot(phys_addr, size as usize, page_of(DEVICE_MEMORY) as usize) }
}
pub fn map_pages(pages: *mut c_void, count: usize) -> *mut c_void {
unsafe { _vmap(pages, count as u32, VM_MAP as usize, page_of(ORDINARY_MEMORY) as usize) }
}
// Which attribute a page carries is an index into a register the kernel filled in, and what it
// put where differs between versions, so the register itself is asked.
#[cfg(target_arch = "arm")]
fn page_of(_memory: u64) -> u64 {
unsafe { read_volatile(_pgprot_kernel) as u64 }
}
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn page_of(_memory: u64) -> u64 {
PAGE_KERNEL & unsafe { read_volatile(___default_kernel_pte_mask) } as u64
}
#[cfg(target_arch = "x86_64")]
const PAGE_KERNEL: u64 = 0x8000_0000_0000_0163;
#[cfg(target_arch = "x86")]
const PAGE_KERNEL: u64 = 0x163;
#[cfg(target_arch = "aarch64")]
fn page_of(memory: u64) -> u64 {
PTE_TYPE_PAGE | PTE_AF | PTE_SHARED | PTE_WRITE | PTE_PXN | PTE_UXN | (attribute_of(memory) << 2)
}
#[cfg(target_arch = "aarch64")]
fn attribute_of(memory: u64) -> u64 {
let attributes: u64;
unsafe {
core::arch::asm!("mrs {}, mair_el1", out(reg) attributes, options(nomem, nostack));
}
let mut index = 0;
while index != ATTRIBUTES_KEPT {
if (attributes >> (index * 8)) & 0xff == memory {
break;
}
index += 1;
}
index
}
#[cfg(target_arch = "aarch64")]
pub fn virt_to_phys(vaddr: u64) -> u64 {
let memstart = unsafe { read_volatile(_memstart_addr) };
vaddr - linear_map_base() + memstart
}
#[cfg(target_arch = "aarch64")]
fn linear_map_base() -> u64 {
let tcr_el1: u64;
unsafe {
core::arch::asm!("mrs {}, tcr_el1", out(reg) tcr_el1, options(nomem, nostack));
}
let kernel_address_bits = 64 - ((tcr_el1 >> TCR_T1SZ_SHIFT) & TCR_T1SZ_MASK);
u64::MAX << kernel_address_bits
}
#[cfg(target_arch = "arm")]
pub fn virt_to_phys(vaddr: u64) -> u64 {
vaddr.wrapping_add(unsafe { read_volatile(___pv_offset) })
}
#[cfg(target_arch = "x86_64")]
pub fn virt_to_phys(vaddr: u64) -> u64 {
vaddr.wrapping_sub(unsafe { read_volatile(_page_offset_base) })
}
#[cfg(target_arch = "x86")]
pub fn virt_to_phys(vaddr: u64) -> u64 {
vaddr.wrapping_sub(page_offset())
}
#[cfg(target_arch = "x86")]
fn page_offset() -> u64 {
get_kernel_base() & !(SPLIT_GRANULARITY - 1)
}
#[cfg(target_arch = "x86")]
const SPLIT_GRANULARITY: u64 = 1 << 30;
struct Trampoline {
entry: ThreadEntry,
parameter: *mut c_void,
}
struct Interrupt {
irq: u32,
target: *mut c_void,
handler: IOInterruptHandler,
refcon: *mut c_void,
}
static mut INTERRUPT: Option<Interrupt> = None;
// Linux hands its handler the cookie it was registered with and expects to hear
// whether the interrupt was ours; the agent's handler takes the four arguments
// XNU's interrupt controller passes.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn frida_cb_interrupt(_irq: c_int, _cookie: *mut c_void) -> c_int {
let Some(interrupt) = (unsafe { &*core::ptr::addr_of!(INTERRUPT) }) else {
return IRQ_NONE;
};
(interrupt.handler)(interrupt.target, interrupt.refcon, core::ptr::null_mut(), 0);
IRQ_HANDLED
}
// kthread hands its worker one argument and expects an exit code back, whereas
// the agent's threads take the pair that XNU's continuations do.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn frida_cb_thread(data: *mut c_void) -> c_int {
let trampoline = data as *mut Trampoline;
unsafe {
let Trampoline { entry, parameter } = trampoline.read();
free(trampoline as *mut u8, size_of::<Trampoline>());
entry(parameter, 0);
}
0
}
// The thread pointer is exposed to JavaScript as a GumThreadId, so it must fit
// in a double without losing precision; the low bits keep it unique per thread.
const KERNEL_PROCESS: u32 = 0;
const JS_SAFE_THREAD_ID_MASK: u64 = (1 << 48) - 1;
const GFP_KERNEL: u32 = 0xcc0;
const NUMA_NO_NODE: c_int = -1;
const EXECMEM_MODULE_TEXT: u32 = 1;
const PAGE_SIZE: usize = 4096;
#[cfg(target_arch = "aarch64")]
const TCR_T1SZ_SHIFT: u64 = 16;
#[cfg(target_arch = "aarch64")]
const TCR_T1SZ_MASK: u64 = 0x3f;
const TASK_INTERRUPTIBLE: c_uint = 1;
const TASK_NORMAL: c_uint = 3;
const WAKE_EVERY_WAITER: c_int = 0;
const WAKE_SLACK_NS: u64 = 50_000;
const HRTIMER_MODE_REL: c_int = 1;
const WAIT_ENTRY_WORDS: usize = 8;
const WAIT_ENTRY_THREAD: usize = 1;
const WAIT_ENTRY_WAKE: usize = 2;
const WAIT_ENTRY_QUEUED: usize = 3;
const WAIT_QUEUE_WORDS: usize = 16;
const LOCK_KEY_WORDS: usize = 8;
const DEVICE_MEMORY: u64 = 0x04;
const ORDINARY_MEMORY: u64 = 0xff;
const ATTRIBUTES_KEPT: u64 = 8;
const VM_MAP: u64 = 0x4;
const PTE_TYPE_PAGE: u64 = 0x3;
const PTE_SHARED: u64 = 3 << 8;
const PTE_AF: u64 = 1 << 10;
const PTE_WRITE: u64 = 1 << 51;
const PTE_PXN: u64 = 1 << 53;
const PTE_UXN: u64 = 1 << 54;
const IRQF_SHARED: usize = 0x80;
const IRQ_NONE: c_int = 0;
const IRQ_HANDLED: c_int = 1;
static KERNEL_BASE: AtomicU64 = AtomicU64::new(0);
#[repr(C)]
struct Timespec64 {
tv_sec: i64,
tv_nsec: i64,
}
type KthreadFn = unsafe extern "C" fn(data: *mut c_void) -> c_int;
type IrqHandlerFn = unsafe extern "C" fn(irq: c_int, cookie: *mut c_void) -> c_int;
unsafe extern "C" {
// Kernels built before the printk rework export the name without the
// underscore; the host fills in whichever this one has.
static __printk: Option<unsafe extern "C" fn(*const c_char)>;
static _printk: Option<unsafe extern "C" fn(*const c_char)>;
#[cfg(not(target_arch = "x86"))]
static _panic: unsafe extern "C" fn(*const c_char) -> !;
static _kthread_create_on_node:
unsafe extern "C" fn(KthreadFn, *mut c_void, c_int, *const c_char) -> *mut c_void;
#[cfg(not(target_arch = "x86"))]
static _wake_up_process: unsafe extern "C" fn(*mut c_void) -> c_int;
#[cfg(not(target_arch = "x86"))]
static _fget: unsafe extern "C" fn(c_uint) -> *mut c_void;
#[cfg(not(target_arch = "x86"))]
static _kernel_read: unsafe extern "C" fn(*mut c_void, *mut u8, usize, *mut i64) -> isize;
#[cfg(not(target_arch = "x86"))]
static _kernel_write: unsafe extern "C" fn(*mut c_void, *const u8, usize, *mut i64) -> isize;
// Allocation profiling renamed the entry points in 6.10; before that the
// size-plus-flags pair went to __kmalloc.
static ___kmalloc_noprof: Option<unsafe extern "C" fn(usize, u32) -> *mut u8>;
static _kmalloc: Option<unsafe extern "C" fn(usize, u32) -> *mut u8>;
#[cfg(not(target_arch = "x86"))]
static _kfree: unsafe extern "C" fn(*mut u8);
// Executable memory moved out of the module loader in 6.12.
static _execmem_alloc: Option<unsafe extern "C" fn(u32, usize) -> *mut u8>;
static _set_memory_rw: Option<unsafe extern "C" fn(usize, c_int) -> c_int>;
static _set_memory_ro: Option<unsafe extern "C" fn(usize, c_int) -> c_int>;
static _set_memory_x: Option<unsafe extern "C" fn(usize, c_int) -> c_int>;
static _set_memory_nx: Option<unsafe extern "C" fn(usize, c_int) -> c_int>;
#[cfg(target_arch = "aarch64")]
static _aarch64_insn_patch_text:
unsafe extern "C" fn(*mut *mut c_void, *const u32, c_int) -> c_int;
#[cfg(target_arch = "arm")]
static _patch_text: unsafe extern "C" fn(*mut c_void, u32);
#[cfg(target_arch = "x86_64")]
static _text_poke_kgdb:
Option<unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> *mut c_void>;
static _execmem_free: Option<unsafe extern "C" fn(*mut u8)>;
static _module_alloc: Option<unsafe extern "C" fn(usize) -> *mut u8>;
static _module_memfree: Option<unsafe extern "C" fn(*mut u8)>;
#[cfg(not(target_arch = "x86"))]
static ___init_waitqueue_head: unsafe extern "C" fn(*mut c_void, *const c_char, *mut c_void);
#[cfg(not(target_arch = "x86"))]
static _prepare_to_wait_event: unsafe extern "C" fn(*mut c_void, *mut c_void, c_uint) -> c_int;
#[cfg(not(target_arch = "x86"))]
static _finish_wait: unsafe extern "C" fn(*mut c_void, *mut c_void);
#[cfg(not(target_arch = "x86"))]
static ___wake_up: unsafe extern "C" fn(*mut c_void, c_uint, c_int, *mut c_void);
static _autoremove_wake_function: *const c_void;
#[cfg(not(target_arch = "x86"))]
static _schedule_hrtimeout_range: unsafe extern "C" fn(*mut i64, u64, c_int) -> c_int;
#[cfg(not(target_arch = "x86"))]
static _pci_get_domain_bus_and_slot: unsafe extern "C" fn(c_int, c_uint, c_uint) -> *mut c_void;
#[cfg(not(target_arch = "x86"))]
static _pci_irq_vector: unsafe extern "C" fn(*mut c_void, c_uint) -> c_int;
#[cfg(not(target_arch = "x86"))]
static _pci_dev_put: unsafe extern "C" fn(*mut c_void);
#[cfg(not(target_arch = "x86"))]
static _schedule: unsafe extern "C" fn();
#[cfg(not(target_arch = "x86"))]
static _ktime_get_mono_fast_ns: unsafe extern "C" fn() -> u64;
#[cfg(not(target_arch = "x86"))]
static _ktime_get_real_ts64: unsafe extern "C" fn(*mut Timespec64);
#[cfg(not(target_arch = "x86"))]
static _send_sig: unsafe extern "C" fn(c_int, *mut c_void, c_int) -> c_int;
#[cfg(not(target_arch = "x86"))]
static _free_irq: unsafe extern "C" fn(u32, *mut c_void) -> *mut c_void;
#[cfg(not(target_arch = "x86"))]
static _request_threaded_irq: unsafe extern "C" fn(
u32,
IrqHandlerFn,
Option<IrqHandlerFn>,
usize,
*const c_char,
*mut c_void,
) -> c_int; static _generic_ioremap_prot: unsafe extern "C" fn(u64, usize, usize) -> *mut c_void;
#[cfg(not(target_arch = "x86"))]
static _vmap: unsafe extern "C" fn(*mut c_void, u32, usize, usize) -> *mut c_void;
#[cfg(target_arch = "aarch64")]
static _memstart_addr: *const u64;
#[cfg(target_arch = "x86_64")]
static _page_offset_base: *const u64;
#[cfg(not(any(target_arch = "aarch64", target_arch = "arm")))]
static _current_task: usize;
#[cfg(target_arch = "x86_64")]
static _cpu_current_top_of_stack: usize;
#[cfg(target_arch = "arm")]
static ___pv_offset: *const u64;
#[cfg(target_arch = "arm")]
static _pgprot_kernel: *const u32;
#[cfg(target_arch = "arm")]
static _ioremap: unsafe extern "C" fn(u32, usize) -> *mut c_void;
#[cfg(target_arch = "arm")]
static _of_find_node_opts_by_path:
unsafe extern "C" fn(*const c_char, *mut *const c_char) -> *mut c_void;
#[cfg(target_arch = "arm")]
static _of_irq_get: unsafe extern "C" fn(*mut c_void, c_int) -> c_int;
#[cfg(target_arch = "arm")]
static _of_node_put: unsafe extern "C" fn(*mut c_void);
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
#[cfg(not(target_arch = "x86"))]
static _ioremap: unsafe extern "C" fn(usize, usize) -> *mut c_void;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
static ___default_kernel_pte_mask: *const usize;
}
#[cfg(target_arch = "x86")]
unsafe extern "C" {
fn frida_k_log(message: *const c_char);
fn frida_k_alloc(size: usize, flags: usize) -> *mut u8;
fn frida_k_text_poke(addr: *mut c_void, opcode: *const c_void, len: usize) -> *mut c_void;
fn frida_k_alloc_code(kind: usize, size: usize, pages: usize) -> *mut u8;
fn frida_k_free_code(code: *mut u8);
}
#[cfg(target_arch = "x86")]
unsafe extern "C" {
#[link_name = "frida_k_panic"]
fn _panic(a0: *const c_char) -> !;
#[link_name = "frida_k_wake_up_process"]
fn _wake_up_process(a0: *mut c_void) -> c_int;
#[link_name = "frida_k_fget"]
fn _fget(a0: c_uint) -> *mut c_void;
#[link_name = "frida_k_kernel_read"]
fn _kernel_read(a0: *mut c_void, a1: *mut u8, a2: usize, a3: *mut i64) -> isize;
#[link_name = "frida_k_kernel_write"]
fn _kernel_write(a0: *mut c_void, a1: *const u8, a2: usize, a3: *mut i64) -> isize;
#[link_name = "frida_k_kfree"]
fn _kfree(a0: *mut u8);
#[link_name = "frida_k___init_waitqueue_head"]
fn ___init_waitqueue_head(a0: *mut c_void, a1: *const c_char, a2: *mut c_void);
#[link_name = "frida_k_prepare_to_wait_event"]
fn _prepare_to_wait_event(a0: *mut c_void, a1: *mut c_void, a2: c_uint) -> c_int;
#[link_name = "frida_k_finish_wait"]
fn _finish_wait(a0: *mut c_void, a1: *mut c_void);
#[link_name = "frida_k___wake_up"]
fn ___wake_up(a0: *mut c_void, a1: c_uint, a2: c_int, a3: *mut c_void);
#[link_name = "frida_k_schedule_hrtimeout_range"]
fn _schedule_hrtimeout_range(a0: *mut i64, a1: u64, a2: c_int) -> c_int;
#[link_name = "frida_k_pci_get_domain_bus_and_slot"]
fn _pci_get_domain_bus_and_slot(a0: c_int, a1: c_uint, a2: c_uint) -> *mut c_void;
#[link_name = "frida_k_pci_irq_vector"]
fn _pci_irq_vector(a0: *mut c_void, a1: c_uint) -> c_int;
#[link_name = "frida_k_pci_dev_put"]
fn _pci_dev_put(a0: *mut c_void);
#[link_name = "frida_k_schedule"]
fn _schedule();
#[link_name = "frida_k_ktime_get_mono_fast_ns"]
fn _ktime_get_mono_fast_ns() -> u64;
#[link_name = "frida_k_ktime_get_real_ts64"]
fn _ktime_get_real_ts64(a0: *mut Timespec64);
#[link_name = "frida_k_send_sig"]
fn _send_sig(a0: c_int, a1: *mut c_void, a2: c_int) -> c_int;
#[link_name = "frida_k_free_irq"]
fn _free_irq(a0: u32, a1: *mut c_void) -> *mut c_void;
#[link_name = "frida_k_vmap"]
fn _vmap(a0: *mut c_void, a1: u32, a2: usize, a3: usize) -> *mut c_void;
#[link_name = "frida_k_ioremap"]
fn _ioremap(a0: usize, a1: usize) -> *mut c_void;
}
#[cfg(target_arch = "x86")]
unsafe extern "C" {
#[link_name = "frida_k_request_threaded_irq"]
fn _request_threaded_irq(
a0: u32,
a1: IrqHandlerFn,
a2: Option<IrqHandlerFn>,
a3: usize,
a4: *const c_char,
a5: *mut c_void,
) -> c_int;
fn frida_kcb_thread(data: *mut c_void) -> c_int;
fn frida_kcb_interrupt(irq: c_int, cookie: *mut c_void) -> c_int;
}
#[cfg(target_arch = "x86")]
const THREAD_ENTRY: unsafe extern "C" fn(*mut c_void) -> c_int = frida_kcb_thread;
#[cfg(not(target_arch = "x86"))]
const THREAD_ENTRY: unsafe extern "C" fn(*mut c_void) -> c_int = frida_cb_thread;
#[cfg(target_arch = "x86")]
const INTERRUPT_ENTRY: unsafe extern "C" fn(c_int, *mut c_void) -> c_int = frida_kcb_interrupt;
#[cfg(not(target_arch = "x86"))]
const INTERRUPT_ENTRY: unsafe extern "C" fn(c_int, *mut c_void) -> c_int = frida_cb_interrupt;
-564
View File
@@ -1,564 +0,0 @@
use alloc::vec::Vec;
use core::ffi::{c_char, c_int, c_long, c_void};
use core::ptr;
pub struct ProcessInfo {
pub id: u32,
pub name: *const u8,
pub path: *const u8,
pub command_line: *const u8,
}
pub fn enumerate_processes(found: &mut dyn FnMut(ProcessInfo)) {
let Some(layout) = task_layout() else {
return;
};
let mut path = Vec::new();
path.resize(PATH_MAX, 0u8);
for task in take_task_snapshot(layout) {
found(ProcessInfo {
id: task.id,
name: task.name.as_ptr(),
path: path_of(task.executable, &mut path),
command_line: ptr::null(),
});
if !task.executable.is_null() {
unsafe { _fput(task.executable) };
}
}
}
pub fn running_task_ids() -> Vec<u32> {
let Some(layout) = task_layout() else {
return Vec::new();
};
let flags = lock_tasklist();
let ids = tasks_of(layout.init, layout.list)
.map(|task| read_id(task, layout))
.collect();
unlock_tasklist(flags);
ids
}
pub unsafe fn let_go_of(file: *mut c_void) {
unsafe { _fput(file) };
}
pub fn task_with_id(id: u32) -> Option<usize> {
let layout = task_layout()?;
let flags = lock_tasklist();
let found = tasks_of(layout.init, layout.list).find(|task| read_id(*task, layout) == id);
unlock_tasklist(flags);
found
}
pub fn describe_process(process: &ProcessInfo) -> *const u8 {
process.name
}
pub fn enumerate_icons(_path: *const u8, _found: &mut dyn FnMut(&[u8])) {}
struct Task {
id: u32,
name: [u8; NAME_SIZE + 1],
executable: *mut c_void,
}
fn take_task_snapshot(layout: &Layout) -> Vec<Task> {
let mut snapshot = Vec::with_capacity(tasks_of(layout.init, layout.list).count() + SNAPSHOT_HEADROOM);
let flags = lock_tasklist();
for task in tasks_of(layout.init, layout.list) {
if snapshot.len() == snapshot.capacity() {
break;
}
snapshot.push(read_task(task, layout));
}
unlock_tasklist(flags);
snapshot
}
fn read_task(task: usize, layout: &Layout) -> Task {
let mut name = [0u8; NAME_SIZE + 1];
read_kernel(task + layout.name, &mut name[..NAME_SIZE]);
Task {
id: read_id(task, layout),
name,
executable: unsafe { _get_task_exe_file(task as *mut c_void) },
}
}
fn read_id(task: usize, layout: &Layout) -> u32 {
let mut id = [0u8; 4];
read_kernel(task + layout.id, &mut id);
u32::from_ne_bytes(id)
}
fn path_of(executable: *mut c_void, buffer: &mut [u8]) -> *const u8 {
if executable.is_null() {
return ptr::null();
}
let path = unsafe {
_file_path(
executable,
buffer.as_mut_ptr() as *mut c_char,
buffer.len() as c_int,
)
};
if (path as usize) >= ERROR_POINTER_START {
return ptr::null();
}
path as *const u8
}
fn tasks_of(init: usize, list: usize) -> TaskList {
TaskList {
head: init + list,
node: init + list,
list,
left: MAX_TASKS,
}
}
struct TaskList {
head: usize,
node: usize,
list: usize,
left: usize,
}
impl Iterator for TaskList {
type Item = usize;
fn next(&mut self) -> Option<usize> {
if self.left == 0 {
return None;
}
self.left -= 1;
let node = read_kernel_word(self.node)?;
if node == self.head {
return None;
}
self.node = node;
Some(node - self.list)
}
}
fn task_layout() -> Option<&'static Layout> {
unsafe {
let known = (&raw mut LAYOUT).as_mut().unwrap();
if known.is_none() {
*known = discover_layout();
}
known.as_ref()
}
}
static mut LAYOUT: Option<Layout> = None;
struct Layout {
init: usize,
list: usize,
name: usize,
id: usize,
}
fn discover_layout() -> Option<Layout> {
let init = unsafe { _init_task } as usize;
if let Some(layout) = described_layout(init) {
return Some(layout);
}
let image = read_task_image(init);
let name = find(&image, IDLE_TASK_NAME)?;
let (list, id) = find_task_list(init, &image, name)?;
Some(Layout { init, list, name, id })
}
fn described_layout(init: usize) -> Option<Layout> {
Some(Layout {
init,
list: super::layout::field_offset("task_struct", "tasks")?,
name: super::layout::field_offset("task_struct", "comm")?,
id: super::layout::field_offset("task_struct", "pid")?,
})
}
fn read_task_image(task: usize) -> Vec<u8> {
let mut image = Vec::new();
image.resize(MAX_TASK_SIZE, 0u8);
let mut readable = 0;
while readable != image.len() {
if !read_kernel(task + readable, &mut image[readable..readable + READ_CHUNK]) {
break;
}
readable += READ_CHUNK;
}
image.truncate(readable);
image
}
fn find(image: &[u8], text: &[u8]) -> Option<usize> {
image.windows(text.len()).position(|window| window == text)
}
fn find_task_list(init: usize, image: &[u8], name: usize) -> Option<(usize, usize)> {
let mut longest: Option<(usize, usize, usize)> = None;
for list in (0..image.len() - LIST_SIZE).step_by(WORD_SIZE) {
if !heads_a_circular_list(init, image, list) {
continue;
}
let Some(tasks) = sample_tasks(init, list, name) else {
continue;
};
let Some(id) = find_identifier(&tasks) else {
continue;
};
let longer = match longest {
Some((sampled, _, _)) => tasks.len() > sampled,
None => true,
};
if longer {
longest = Some((tasks.len(), list, id));
}
}
longest.map(|(_, list, id)| (list, id))
}
fn heads_a_circular_list(init: usize, image: &[u8], list: usize) -> bool {
let head = init + list;
let next = word_in(image, list);
let previous = word_in(image, list + WORD_SIZE);
let kernel_space = kernel_space_holding(init);
if !is_kernel_address(next, kernel_space) || !is_kernel_address(previous, kernel_space) || next == head {
return false;
}
read_kernel_word(next + WORD_SIZE) == Some(head) && read_kernel_word(previous) == Some(head)
}
fn sample_tasks(init: usize, list: usize, name: usize) -> Option<Vec<usize>> {
let mut tasks = Vec::new();
for task in tasks_of(init, list) {
if !names_a_task(task + name) {
return None;
}
tasks.push(task);
if tasks.len() == MAX_SAMPLED_TASKS {
break;
}
}
if tasks.len() < MIN_SAMPLED_TASKS {
return None;
}
Some(tasks)
}
fn names_a_task(name: usize) -> bool {
let mut text = [0u8; NAME_SIZE];
if !read_kernel(name, &mut text) {
return false;
}
let Some(end) = text.iter().position(|letter| *letter == 0) else {
return false;
};
end != 0 && text[..end].iter().all(|letter| *letter >= 0x20 && *letter < 0x7f)
}
fn find_identifier(tasks: &[usize]) -> Option<usize> {
let mut identifiers = Vec::with_capacity(tasks.len());
for id in (0..MAX_TASK_SIZE - 8).step_by(4) {
identifiers.clear();
let leads_every_task = tasks.iter().all(|task| {
let Some((own, group)) = read_kernel_pair(*task + id) else {
return false;
};
if own != group || own >= MAX_IDENTIFIER || identifiers.contains(&own) {
return false;
}
identifiers.push(own);
true
});
if leads_every_task {
return Some(id);
}
}
None
}
fn read_kernel_pair(address: usize) -> Option<(u32, u32)> {
let mut bytes = [0u8; 8];
if !read_kernel(address, &mut bytes) {
return None;
}
Some((
u32::from_ne_bytes(bytes[..4].try_into().unwrap()),
u32::from_ne_bytes(bytes[4..].try_into().unwrap()),
))
}
fn read_kernel_word(address: usize) -> Option<usize> {
let mut bytes = [0u8; WORD_SIZE];
if !read_kernel(address, &mut bytes) {
return None;
}
Some(usize::from_ne_bytes(bytes))
}
fn read_kernel(address: usize, destination: &mut [u8]) -> bool {
let read = unsafe {
_copy_from_kernel_nofault(
destination.as_mut_ptr() as *mut c_void,
address as *const c_void,
destination.len(),
)
};
read == 0
}
fn word_in(image: &[u8], offset: usize) -> usize {
usize::from_ne_bytes(image[offset..offset + WORD_SIZE].try_into().unwrap())
}
fn is_kernel_address(address: usize, kernel_space: usize) -> bool {
address >= kernel_space && address % WORD_SIZE == 0
}
fn kernel_space_holding(address: usize) -> usize {
address & !(SPLIT_GRANULARITY - 1)
}
const SPLIT_GRANULARITY: usize = 1 << 30;
#[cfg(target_arch = "x86")]
fn lock_tasklist() -> usize {
unsafe { frida_k_lock_tasklist() }
}
#[cfg(not(target_arch = "x86"))]
fn lock_tasklist() -> usize {
unsafe {
match (__raw_read_lock_irqsave, __raw_read_unlock_irqrestore) {
(Some(lock), Some(_)) => lock(_tasklist_lock),
_ => {
__raw_read_lock.unwrap()(_tasklist_lock);
0
}
}
}
}
#[cfg(target_arch = "x86")]
fn unlock_tasklist(flags: usize) {
unsafe { frida_k_unlock_tasklist(flags) };
}
#[cfg(not(target_arch = "x86"))]
fn unlock_tasklist(flags: usize) {
unsafe {
match (__raw_read_lock_irqsave, __raw_read_unlock_irqrestore) {
(Some(_), Some(unlock)) => unlock(_tasklist_lock, flags),
_ => __raw_read_unlock.unwrap()(_tasklist_lock),
}
}
}
const PIDTYPE_TGID: c_int = 1;
const PIDTYPE_MAX: usize = 4;
pub fn cloak_task(task: *mut c_void) {
let (Some(tasks), Some(sibling), Some(thread_node)) = (
super::layout::field_offset("task_struct", "tasks"),
super::layout::field_offset("task_struct", "sibling"),
super::layout::field_offset("task_struct", "thread_node"),
) else {
return;
};
let mut freed: [*mut c_void; PIDTYPE_MAX] = [ptr::null_mut(); PIDTYPE_MAX];
let flags = write_lock_tasklist();
unsafe {
detach_pid(freed.as_mut_ptr(), task, PIDTYPE_TGID);
unlink(task as usize + tasks);
unlink(task as usize + sibling);
unlink(task as usize + thread_node);
}
write_unlock_tasklist(flags);
unsafe { free_pids(freed.as_mut_ptr()) };
}
#[cfg(target_arch = "x86")]
unsafe fn detach_pid(pids: *mut *mut c_void, task: *mut c_void, kind: c_int) {
unsafe { frida_k_detach_pid(pids, task, kind) };
}
#[cfg(not(target_arch = "x86"))]
unsafe fn detach_pid(pids: *mut *mut c_void, task: *mut c_void, kind: c_int) {
unsafe { _detach_pid(pids, task, kind) };
}
#[cfg(target_arch = "x86")]
unsafe fn free_pids(pids: *mut *mut c_void) {
unsafe { frida_k_free_pids(pids) };
}
#[cfg(not(target_arch = "x86"))]
unsafe fn free_pids(pids: *mut *mut c_void) {
unsafe { _free_pids(pids) };
}
unsafe fn unlink(node: usize) {
let next = unsafe { (node as *const usize).read() };
let prev = unsafe { ((node + WORD_SIZE) as *const usize).read() };
unsafe {
(prev as *mut usize).write(next);
((next + WORD_SIZE) as *mut usize).write(prev);
(node as *mut usize).write(node);
((node + WORD_SIZE) as *mut usize).write(node);
}
}
#[cfg(target_arch = "x86")]
fn write_lock_tasklist() -> usize {
unsafe { frida_k_write_lock_tasklist() }
}
#[cfg(not(target_arch = "x86"))]
fn write_lock_tasklist() -> usize {
unsafe {
match (__raw_write_lock_irqsave, __raw_write_unlock_irqrestore) {
(Some(lock), Some(_)) => lock(_tasklist_lock),
_ => {
__raw_write_lock.unwrap()(_tasklist_lock);
0
}
}
}
}
#[cfg(target_arch = "x86")]
fn write_unlock_tasklist(flags: usize) {
unsafe { frida_k_write_unlock_tasklist(flags) };
}
#[cfg(not(target_arch = "x86"))]
fn write_unlock_tasklist(flags: usize) {
unsafe {
match (__raw_write_lock_irqsave, __raw_write_unlock_irqrestore) {
(Some(_), Some(unlock)) => unlock(_tasklist_lock, flags),
_ => __raw_write_unlock.unwrap()(_tasklist_lock),
}
}
}
const IDLE_TASK_NAME: &[u8] = b"swapper";
const NAME_SIZE: usize = 16;
const PATH_MAX: usize = 4096;
const MAX_TASK_SIZE: usize = 16 * 1024;
const READ_CHUNK: usize = 64;
const WORD_SIZE: usize = size_of::<usize>();
const LIST_SIZE: usize = 2 * WORD_SIZE;
const MAX_TASKS: usize = 8192;
const SNAPSHOT_HEADROOM: usize = 64;
const MIN_SAMPLED_TASKS: usize = 4;
const MAX_SAMPLED_TASKS: usize = 32;
const MAX_IDENTIFIER: u32 = 4 * 1024 * 1024;
const ERROR_POINTER_START: usize = usize::MAX - 4095;
unsafe extern "C" {
static _init_task: *const c_void;
static _tasklist_lock: *mut c_void;
static __raw_read_lock: Option<unsafe extern "C" fn(*mut c_void)>;
static __raw_read_unlock: Option<unsafe extern "C" fn(*mut c_void)>;
static __raw_read_lock_irqsave: Option<unsafe extern "C" fn(*mut c_void) -> usize>;
static __raw_read_unlock_irqrestore: Option<unsafe extern "C" fn(*mut c_void, usize)>;
#[cfg(not(target_arch = "x86"))]
static _detach_pid: unsafe extern "C" fn(*mut *mut c_void, *mut c_void, c_int);
#[cfg(not(target_arch = "x86"))]
static _free_pids: unsafe extern "C" fn(*mut *mut c_void);
#[cfg(not(target_arch = "x86"))]
static __raw_write_lock: Option<unsafe extern "C" fn(*mut c_void)>;
#[cfg(not(target_arch = "x86"))]
static __raw_write_unlock: Option<unsafe extern "C" fn(*mut c_void)>;
#[cfg(not(target_arch = "x86"))]
static __raw_write_lock_irqsave: Option<unsafe extern "C" fn(*mut c_void) -> usize>;
#[cfg(not(target_arch = "x86"))]
static __raw_write_unlock_irqrestore: Option<unsafe extern "C" fn(*mut c_void, usize)>;
#[cfg(not(target_arch = "x86"))]
static _get_task_exe_file: unsafe extern "C" fn(*mut c_void) -> *mut c_void;
#[cfg(not(target_arch = "x86"))]
static _file_path: unsafe extern "C" fn(*mut c_void, *mut c_char, c_int) -> *const c_char;
#[cfg(not(target_arch = "x86"))]
static _fput: unsafe extern "C" fn(*mut c_void);
#[cfg(not(target_arch = "x86"))]
static _copy_from_kernel_nofault:
unsafe extern "C" fn(*mut c_void, *const c_void, usize) -> c_long;
}
#[cfg(target_arch = "x86")]
unsafe extern "C" {
#[link_name = "frida_k_copy_from_kernel_nofault"]
fn _copy_from_kernel_nofault(a0: *mut c_void, a1: *const c_void, a2: usize) -> c_long;
fn frida_k_lock_tasklist() -> usize;
fn frida_k_unlock_tasklist(flags: usize);
fn frida_k_write_lock_tasklist() -> usize;
fn frida_k_write_unlock_tasklist(flags: usize);
fn frida_k_detach_pid(pids: *mut *mut c_void, task: *mut c_void, kind: c_int);
fn frida_k_free_pids(pids: *mut *mut c_void);
}
#[cfg(target_arch = "x86")]
unsafe extern "C" {
#[link_name = "frida_k_get_task_exe_file"]
fn _get_task_exe_file(a0: *mut c_void) -> *mut c_void;
#[link_name = "frida_k_file_path"]
fn _file_path(a0: *mut c_void, a1: *mut c_char, a2: c_int) -> *const c_char;
#[link_name = "frida_k_fput"]
fn _fput(a0: *mut c_void);
}
-642
View File
@@ -1,642 +0,0 @@
extern void *_panic;
void frida_k_panic (void * a0)
{
typedef void (* fn_t) (const char *, ...);
((fn_t) _panic) (a0);
}
extern void *_wake_up_process;
unsigned long frida_k_wake_up_process (void * a0)
{
typedef unsigned long (__attribute__((regparm(3))) * fn_t) (void *);
return ((fn_t) _wake_up_process) (a0);
}
extern void *_fget;
void * frida_k_fget (unsigned long a0)
{
typedef void * (__attribute__((regparm(3))) * fn_t) (unsigned long);
return ((fn_t) _fget) (a0);
}
extern void *_kernel_read;
long frida_k_kernel_read (void * a0, void * a1, unsigned long a2, void * a3)
{
typedef long (__attribute__((regparm(3))) * fn_t) (void *, void *, unsigned long, void *);
return ((fn_t) _kernel_read) (a0, a1, a2, a3);
}
extern void *_kernel_write;
long frida_k_kernel_write (void * a0, void * a1, unsigned long a2, void * a3)
{
typedef long (__attribute__((regparm(3))) * fn_t) (void *, void *, unsigned long, void *);
return ((fn_t) _kernel_write) (a0, a1, a2, a3);
}
extern void *_kfree;
void frida_k_kfree (void * a0)
{
typedef void (__attribute__((regparm(3))) * fn_t) (void *);
((fn_t) _kfree) (a0);
}
extern void *___init_waitqueue_head;
void frida_k___init_waitqueue_head (void * a0, void * a1, void * a2)
{
typedef void (__attribute__((regparm(3))) * fn_t) (void *, void *, void *);
((fn_t) ___init_waitqueue_head) (a0, a1, a2);
}
extern void *_prepare_to_wait_event;
unsigned long frida_k_prepare_to_wait_event (void * a0, void * a1, unsigned long a2)
{
typedef unsigned long (__attribute__((regparm(3))) * fn_t) (void *, void *, unsigned long);
return ((fn_t) _prepare_to_wait_event) (a0, a1, a2);
}
extern void *_finish_wait;
void frida_k_finish_wait (void * a0, void * a1)
{
typedef void (__attribute__((regparm(3))) * fn_t) (void *, void *);
((fn_t) _finish_wait) (a0, a1);
}
extern void *___wake_up;
void frida_k___wake_up (void * a0, unsigned long a1, unsigned long a2, void * a3)
{
typedef void (__attribute__((regparm(3))) * fn_t) (void *, unsigned long, unsigned long, void *);
((fn_t) ___wake_up) (a0, a1, a2, a3);
}
extern void *_schedule_hrtimeout_range;
unsigned long frida_k_schedule_hrtimeout_range (void * a0, unsigned long long a1, unsigned long a2)
{
typedef unsigned long (__attribute__((regparm(3))) * fn_t) (void *, unsigned long long, unsigned long);
return ((fn_t) _schedule_hrtimeout_range) (a0, a1, a2);
}
extern void *_pci_get_domain_bus_and_slot;
void * frida_k_pci_get_domain_bus_and_slot (unsigned long a0, unsigned long a1, unsigned long a2)
{
typedef void * (__attribute__((regparm(3))) * fn_t) (unsigned long, unsigned long, unsigned long);
return ((fn_t) _pci_get_domain_bus_and_slot) (a0, a1, a2);
}
extern void *_pci_irq_vector;
unsigned long frida_k_pci_irq_vector (void * a0, unsigned long a1)
{
typedef unsigned long (__attribute__((regparm(3))) * fn_t) (void *, unsigned long);
return ((fn_t) _pci_irq_vector) (a0, a1);
}
extern void *_pci_dev_put;
void frida_k_pci_dev_put (void * a0)
{
typedef void (__attribute__((regparm(3))) * fn_t) (void *);
((fn_t) _pci_dev_put) (a0);
}
extern void *_schedule;
void frida_k_schedule (void)
{
typedef void (__attribute__((regparm(3))) * fn_t) (void);
((fn_t) _schedule) ();
}
extern void *_ktime_get_mono_fast_ns;
unsigned long long frida_k_ktime_get_mono_fast_ns (void)
{
typedef unsigned long long (__attribute__((regparm(3))) * fn_t) (void);
return ((fn_t) _ktime_get_mono_fast_ns) ();
}
extern void *_ktime_get_real_ts64;
void frida_k_ktime_get_real_ts64 (void * a0)
{
typedef void (__attribute__((regparm(3))) * fn_t) (void *);
((fn_t) _ktime_get_real_ts64) (a0);
}
extern void *_send_sig;
unsigned long frida_k_send_sig (unsigned long a0, void * a1, unsigned long a2)
{
typedef unsigned long (__attribute__((regparm(3))) * fn_t) (unsigned long, void *, unsigned long);
return ((fn_t) _send_sig) (a0, a1, a2);
}
extern void *_free_irq;
void * frida_k_free_irq (unsigned long a0, void * a1)
{
typedef void * (__attribute__((regparm(3))) * fn_t) (unsigned long, void *);
return ((fn_t) _free_irq) (a0, a1);
}
extern void *_vmap;
void * frida_k_vmap (void * a0, unsigned long a1, unsigned long a2, unsigned long a3)
{
typedef void * (__attribute__((regparm(3))) * fn_t) (void *, unsigned long, unsigned long, unsigned long);
return ((fn_t) _vmap) (a0, a1, a2, a3);
}
extern void *_ioremap;
void * frida_k_ioremap (unsigned long a0, unsigned long a1)
{
typedef void * (__attribute__((regparm(3))) * fn_t) (unsigned long, unsigned long);
return ((fn_t) _ioremap) (a0, a1);
}
extern void *_get_task_exe_file;
void * frida_k_get_task_exe_file (void * a0)
{
typedef void * (__attribute__((regparm(3))) * fn_t) (void *);
return ((fn_t) _get_task_exe_file) (a0);
}
extern void *_file_path;
void * frida_k_file_path (void * a0, void * a1, unsigned long a2)
{
typedef void * (__attribute__((regparm(3))) * fn_t) (void *, void *, unsigned long);
return ((fn_t) _file_path) (a0, a1, a2);
}
extern void *_fput;
void frida_k_fput (void * a0)
{
typedef void (__attribute__((regparm(3))) * fn_t) (void *);
((fn_t) _fput) (a0);
}
extern void *_get_task_mm;
void * frida_k_get_task_mm (void * a0)
{
typedef void * (__attribute__((regparm(3))) * fn_t) (void *);
return ((fn_t) _get_task_mm) (a0);
}
extern void *_kthread_use_mm;
void frida_k_kthread_use_mm (void * a0)
{
typedef void (__attribute__((regparm(3))) * fn_t) (void *);
((fn_t) _kthread_use_mm) (a0);
}
extern void *_kthread_unuse_mm;
void frida_k_kthread_unuse_mm (void * a0)
{
typedef void (__attribute__((regparm(3))) * fn_t) (void *);
((fn_t) _kthread_unuse_mm) (a0);
}
extern void *_mmput;
void frida_k_mmput (void * a0)
{
typedef void (__attribute__((regparm(3))) * fn_t) (void *);
((fn_t) _mmput) (a0);
}
extern void *_vm_mmap;
unsigned long frida_k_vm_mmap (void * a0, unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5)
{
typedef unsigned long (__attribute__((regparm(3))) * fn_t) (void *, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long);
return ((fn_t) _vm_mmap) (a0, a1, a2, a3, a4, a5);
}
extern void *_vm_munmap;
unsigned long frida_k_vm_munmap (unsigned long a0, unsigned long a1)
{
typedef unsigned long (__attribute__((regparm(3))) * fn_t) (unsigned long, unsigned long);
return ((fn_t) _vm_munmap) (a0, a1);
}
extern void *_vunmap;
void frida_k_vunmap (void * a0)
{
typedef void (__attribute__((regparm(3))) * fn_t) (void *);
((fn_t) _vunmap) (a0);
}
extern void *__copy_to_user;
unsigned long frida_k__copy_to_user (void * a0, void * a1, unsigned long a2)
{
typedef unsigned long (__attribute__((regparm(3))) * fn_t) (void *, void *, unsigned long);
return ((fn_t) __copy_to_user) (a0, a1, a2);
}
extern void *_down_read;
void frida_k_down_read (void * a0)
{
typedef void (__attribute__((regparm(3))) * fn_t) (void *);
((fn_t) _down_read) (a0);
}
extern void *_up_read;
void frida_k_up_read (void * a0)
{
typedef void (__attribute__((regparm(3))) * fn_t) (void *);
((fn_t) _up_read) (a0);
}
extern void *_get_user_pages_remote;
long frida_k_get_user_pages_remote (void * a0, unsigned long a1, unsigned long a2, unsigned long a3, void * a4, void * a5)
{
typedef long (__attribute__((regparm(3))) * fn_t) (void *, unsigned long, unsigned long, unsigned long, void *, void *);
return ((fn_t) _get_user_pages_remote) (a0, a1, a2, a3, a4, a5);
}
extern void *_do_futex;
long frida_k_do_futex (unsigned long a0, unsigned long a1, unsigned long a2, unsigned long a3, unsigned long a4, unsigned long a5, unsigned long a6)
{
typedef long (__attribute__((regparm(3))) * fn_t) (unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long, unsigned long);
return ((fn_t) _do_futex) (a0, a1, a2, a3, a4, a5, a6);
}
extern void *_call_usermodehelper_exec;
unsigned long frida_k_call_usermodehelper_exec (void * a0, unsigned long a1)
{
typedef unsigned long (__attribute__((regparm(3))) * fn_t) (void *, unsigned long);
return ((fn_t) _call_usermodehelper_exec) (a0, a1);
}
extern void *__printk;
extern void *_printk;
extern void *___kmalloc_noprof;
extern void *_kmalloc;
extern void *_execmem_alloc;
extern void *_module_alloc;
extern void *_set_memory_rw;
extern void *_execmem_free;
extern void *_module_memfree;
extern void *__raw_read_lock;
extern void *__raw_read_unlock;
extern void *__raw_read_lock_irqsave;
extern void *__raw_read_unlock_irqrestore;
extern void *__raw_write_lock;
extern void *__raw_write_unlock;
extern void *__raw_write_lock_irqsave;
extern void *__raw_write_unlock_irqrestore;
extern void *_detach_pid;
extern void *_free_pids;
extern void *_tasklist_lock;
typedef void (* frida_say_t) (const char *, ...);
typedef void * (__attribute__((regparm(3))) * frida_take_t) (unsigned long, unsigned long);
typedef void * (__attribute__((regparm(3))) * frida_take_one_t) (unsigned long);
typedef void (__attribute__((regparm(3))) * frida_give_t) (void *);
typedef unsigned long (__attribute__((regparm(3))) * frida_widen_t) (unsigned long, unsigned long);
typedef void (__attribute__((regparm(3))) * frida_hold_t) (void *);
typedef unsigned long (__attribute__((regparm(3))) * frida_hold_flags_t) (void *);
typedef void (__attribute__((regparm(3))) * frida_release_flags_t) (void *, unsigned long);
void frida_k_log (const char * message)
{
void *say = __printk ? __printk : _printk;
((frida_say_t) say) (message);
}
void * frida_k_alloc (unsigned long size, unsigned long flags)
{
void *take = ___kmalloc_noprof ? ___kmalloc_noprof : _kmalloc;
return ((frida_take_t) take) (size, flags);
}
void * frida_k_alloc_code (unsigned long kind, unsigned long size, unsigned long pages)
{
void *code;
if (_execmem_alloc)
code = ((frida_take_t) _execmem_alloc) (kind, size);
else
code = ((frida_take_one_t) _module_alloc) (size);
if (_set_memory_rw)
((frida_widen_t) _set_memory_rw) ((unsigned long) code, pages);
return code;
}
void frida_k_free_code (void * code)
{
void *give = _execmem_free ? _execmem_free : _module_memfree;
((frida_give_t) give) (code);
}
unsigned long frida_k_lock_tasklist (void)
{
if (__raw_read_lock_irqsave && __raw_read_unlock_irqrestore)
return ((frida_hold_flags_t) __raw_read_lock_irqsave) (_tasklist_lock);
((frida_hold_t) __raw_read_lock) (_tasklist_lock);
return 0;
}
void frida_k_unlock_tasklist (unsigned long flags)
{
if (__raw_read_lock_irqsave && __raw_read_unlock_irqrestore)
((frida_release_flags_t) __raw_read_unlock_irqrestore) (_tasklist_lock, flags);
else
((frida_hold_t) __raw_read_unlock) (_tasklist_lock);
}
unsigned long frida_k_write_lock_tasklist (void)
{
if (__raw_write_lock_irqsave && __raw_write_unlock_irqrestore)
return ((frida_hold_flags_t) __raw_write_lock_irqsave) (_tasklist_lock);
((frida_hold_t) __raw_write_lock) (_tasklist_lock);
return 0;
}
void frida_k_write_unlock_tasklist (unsigned long flags)
{
if (__raw_write_lock_irqsave && __raw_write_unlock_irqrestore)
((frida_release_flags_t) __raw_write_unlock_irqrestore) (_tasklist_lock, flags);
else
((frida_hold_t) __raw_write_unlock) (_tasklist_lock);
}
void frida_k_detach_pid (void * pids, void * task, int type)
{
typedef void (__attribute__ ((regparm (3))) * fn_t) (void *, void *, int);
((fn_t) _detach_pid) (pids, task, type);
}
void frida_k_free_pids (void * pids)
{
typedef void (__attribute__ ((regparm (3))) * fn_t) (void *);
((fn_t) _free_pids) (pids);
}
extern int frida_cb_thread (void * data);
extern int frida_cb_interrupt (int irq, void * cookie);
extern int frida_cb_user (void * argument);
extern void frida_cb_exec (void * data, void * task, int was, void * program);
extern void frida_cb_thread_appeared (void * data, void * parent, void * child);
extern void frida_cb_thread_left (void * data, void * task, _Bool group_dead);
__attribute__ ((regparm (3))) int
frida_kcb_thread (void * data)
{
return frida_cb_thread (data);
}
__attribute__ ((regparm (3))) int
frida_kcb_interrupt (int irq, void * cookie)
{
return frida_cb_interrupt (irq, cookie);
}
__attribute__ ((regparm (3))) int
frida_kcb_user (void * argument)
{
return frida_cb_user (argument);
}
__attribute__ ((regparm (3))) void
frida_kcb_exec (void * data, void * task, int was, void * program)
{
frida_cb_exec (data, task, was, program);
}
__attribute__ ((regparm (3))) void
frida_kcb_thread_appeared (void * data, void * parent, void * child)
{
frida_cb_thread_appeared (data, parent, child);
}
__attribute__ ((regparm (3))) void
frida_kcb_thread_left (void * data, void * task, _Bool group_dead)
{
frida_cb_thread_left (data, task, group_dead);
}
extern void *_request_threaded_irq;
int
frida_k_request_threaded_irq (unsigned int a0, void * a1, void * a2, unsigned long a3, const char * a4,
void * a5)
{
typedef int (__attribute__ ((regparm (3))) * fn_t) (unsigned int, void *, void *, unsigned long,
const char *, void *);
return ((fn_t) _request_threaded_irq) (a0, a1, a2, a3, a4, a5);
}
extern void *_copy_from_kernel_nofault;
long
frida_k_copy_from_kernel_nofault (void * a0, const void * a1, unsigned int a2)
{
typedef long (__attribute__ ((regparm (3))) * fn_t) (void *, const void *, unsigned int);
return ((fn_t) _copy_from_kernel_nofault) (a0, a1, a2);
}
extern void *_tracepoint_probe_register;
extern void *_tracepoint_probe_unregister;
extern void *_user_mode_thread;
extern void *_call_usermodehelper_setup;
int
frida_k_tracepoint_probe_register (void * a0, void * a1, void * a2)
{
typedef int (__attribute__ ((regparm (3))) * fn_t) (void *, void *, void *);
return ((fn_t) _tracepoint_probe_register) (a0, a1, a2);
}
int
frida_k_tracepoint_probe_unregister (void * a0, void * a1, void * a2)
{
typedef int (__attribute__ ((regparm (3))) * fn_t) (void *, void *, void *);
return ((fn_t) _tracepoint_probe_unregister) (a0, a1, a2);
}
void *
frida_k_user_mode_thread (void * a0, void * a1, unsigned long a2)
{
typedef void * (__attribute__ ((regparm (3))) * fn_t) (void *, void *, unsigned long);
return ((fn_t) _user_mode_thread) (a0, a1, a2);
}
void *
frida_k_call_usermodehelper_setup (const void * a0, const void * a1, const void * a2,
unsigned int a3, void * a4, void * a5, void * a6)
{
typedef void * (__attribute__ ((regparm (3))) * fn_t) (const void *, const void *, const void *,
unsigned int, void *, void *, void *);
return ((fn_t) _call_usermodehelper_setup) (a0, a1, a2, a3, a4, a5, a6);
}
extern int frida_cb_hold_spawn (void * info, void * credentials);
extern void frida_cb_release_words (void * info);
__attribute__ ((regparm (3))) int
frida_kcb_hold_spawn (void * info, void * credentials)
{
return frida_cb_hold_spawn (info, credentials);
}
__attribute__ ((regparm (3))) void
frida_kcb_release_words (void * info)
{
frida_cb_release_words (info);
}
extern void *_get_user_pages_unlocked;
long
frida_k_get_user_pages_unlocked (unsigned long a0, unsigned long a1, void * a2, int a3)
{
typedef long (__attribute__ ((regparm (3))) * fn_t) (unsigned long, unsigned long, void *,
unsigned int);
return ((fn_t) _get_user_pages_unlocked) (a0, a1, a2, a3);
}
extern void *_text_poke_kgdb;
void * frida_k_text_poke (void * addr, const void * opcode, unsigned long len)
{
typedef void * (__attribute__((regparm(3))) * fn_t) (void *, const void *, unsigned long);
if (!_text_poke_kgdb)
return 0;
return ((fn_t) _text_poke_kgdb) (addr, opcode, len);
}
extern void *_register_module_notifier;
int frida_k_register_module_notifier (void * a0)
{
typedef int (__attribute__ ((regparm (3))) * fn_t) (void *);
return ((fn_t) _register_module_notifier) (a0);
}
extern void *_unregister_module_notifier;
int frida_k_unregister_module_notifier (void * a0)
{
typedef int (__attribute__ ((regparm (3))) * fn_t) (void *);
return ((fn_t) _unregister_module_notifier) (a0);
}
extern int frida_cb_module_state (void * nb, unsigned long action, void * data);
__attribute__ ((regparm (3))) int
frida_kcb_module_state (void * nb, unsigned long action, void * data)
{
return frida_cb_module_state (nb, action, data);
}
extern void *_register_die_notifier;
int frida_k_register_die_notifier (void * a0)
{
typedef int (__attribute__ ((regparm (3))) * fn_t) (void *);
return ((fn_t) _register_die_notifier) (a0);
}
extern void *_unregister_die_notifier;
int frida_k_unregister_die_notifier (void * a0)
{
typedef int (__attribute__ ((regparm (3))) * fn_t) (void *);
return ((fn_t) _unregister_die_notifier) (a0);
}
extern int frida_cb_die (void * nb, unsigned long action, void * data);
__attribute__ ((regparm (3))) int
frida_kcb_die (void * nb, unsigned long action, void * data)
{
return frida_cb_die (nb, action, data);
}
-167
View File
@@ -1,167 +0,0 @@
use alloc::collections::{BTreeMap, VecDeque};
use alloc::vec::Vec;
use crate::ring::{Ring, Taken};
use super::arena::Arena;
use super::facade::{wait, yield_now};
// The kernel half's side: a frame for a process the host attached to goes to the copy living
// there, and what that copy answers goes on to the host.
pub fn forward_frame(arena: u64, frame: &[u8]) -> bool {
unsafe { waiting() }.entry(arena).or_default().push_back(frame.to_vec());
send_what_waits(arena);
true
}
pub fn serve_waiting_frames() {
let arenas: Vec<u64> = unsafe { waiting() }.keys().copied().collect();
for arena in arenas {
send_what_waits(arena);
}
}
pub fn take_frame_from_target(arena: u64) -> Option<Vec<u8>> {
read_frame(&TO_KERNEL, arena, wake_the_copy)
}
pub fn injected_arenas() -> Vec<u64> {
super::injection::arenas()
}
pub fn holds_a_frame_from_target(arena: u64) -> bool {
TO_KERNEL.holds_anything(arena)
}
pub fn a_frame_waits_for_room() -> bool {
unsafe { waiting() }
.iter()
.any(|(arena, frames)| !frames.is_empty() && TO_COPY.has_room(*arena))
}
// The copy's side, where the same two rings are reached at an address of its own.
pub fn publish_frame_to_host(arena: u64, frame: &[u8]) -> bool {
write_frame(&TO_KERNEL, arena, frame, wake_the_kernel_half)
}
pub fn take_frame_from_host(arena: u64) -> Option<Vec<u8>> {
read_frame(&TO_COPY, arena, wake_the_kernel_half)
}
pub fn holds_a_frame_from_host(arena: u64) -> bool {
TO_COPY.holds_anything(arena)
}
fn send_what_waits(arena: u64) {
let frames = unsafe { waiting() }.get_mut(&arena).unwrap();
while let Some(frame) = frames.front_mut() {
TO_COPY.take_lock(arena, yield_now);
let piece = TO_COPY.write(arena, arena + TO_COPY.buffer, frame, 0);
if piece.is_none() {
TO_COPY.ask_for_room(arena);
}
TO_COPY.let_lock_go(arena);
let Some(written) = piece else {
return;
};
wake_the_copy(arena);
if written == frame.len() {
frames.pop_front();
} else {
frame.drain(..written);
}
}
}
fn write_frame(ring: &Ring, arena: u64, frame: &[u8], tell: fn(u64)) -> bool {
ring.take_lock(arena, yield_now);
let mut written = 0;
while written != frame.len() {
match ring.write(arena, arena + ring.buffer, frame, written) {
Some(now) => {
written = now;
tell(arena);
}
None => {
ring.ask_for_room(arena);
wait(crate::glib::wakeup_token(), None, &mut || ring.has_room(arena));
}
}
}
ring.let_lock_go(arena);
true
}
fn read_frame(ring: &Ring, arena: u64, tell: fn(u64)) -> Option<Vec<u8>> {
let hold = unsafe { holds() }.entry((arena, ring.head)).or_default();
loop {
match ring.read(arena, arena + ring.buffer, hold) {
Taken::Nothing => return None,
Taken::Piece => {
if ring.room_is_wanted(arena) {
tell(arena);
}
}
Taken::Frame => {
let frame = core::mem::take(hold);
if ring.room_is_wanted(arena) {
tell(arena);
}
return Some(frame);
}
}
}
}
pub fn forget(arena: u64) {
unsafe { holds() }.retain(|(of, _), _| *of != arena);
unsafe { waiting() }.remove(&arena);
}
fn wake_the_copy(arena: u64) {
super::injection::tell_the_copy_at(arena);
}
fn wake_the_kernel_half(arena: u64) {
Arena::at(arena as usize).tell_the_kernel_half();
}
unsafe fn holds() -> &'static mut BTreeMap<(u64, u64), Vec<u8>> {
unsafe { (&raw mut HOLDS).as_mut().unwrap() }
}
static mut HOLDS: BTreeMap<(u64, u64), Vec<u8>> = BTreeMap::new();
unsafe fn waiting() -> &'static mut BTreeMap<u64, VecDeque<Vec<u8>>> {
unsafe { (&raw mut WAITING).as_mut().unwrap() }
}
static mut WAITING: BTreeMap<u64, VecDeque<Vec<u8>>> = BTreeMap::new();
const TO_COPY: Ring = Ring {
buffer: 0x1000,
head: 256,
tail: 260,
lock: 264,
room: 268,
size: FRAME_BUFFER_SIZE,
};
const TO_KERNEL: Ring = Ring {
buffer: 0x1000 + FRAME_BUFFER_SIZE as u64,
head: 272,
tail: 276,
lock: 280,
room: 284,
size: FRAME_BUFFER_SIZE,
};
const FRAME_BUFFER_SIZE: usize = 0x10000;
-219
View File
@@ -1,219 +0,0 @@
use alloc::ffi::CString;
use alloc::vec::Vec;
use core::ffi::{c_int, c_void};
use core::mem::size_of;
use core::ptr;
use super::layout::field_offset;
use super::native;
use super::processes::task_with_id;
pub fn spawn_process(words: &[&str]) -> u32 {
let Some(words) = said_as(words) else {
return 0;
};
super::injection::hold_what_is_spawned();
let held = native::alloc(size_of::<Held>()) as *mut Held;
unsafe {
held.write(Held {
id: 0,
words,
environment: environment(),
})
};
let info = unsafe {
_call_usermodehelper_setup(
(*held).words.first_word(),
(*held).words.as_argv(),
(*held).environment.as_argv(),
GFP_KERNEL,
HOLD_SPAWN,
RELEASE_WORDS,
held as *mut c_void,
)
};
if info.is_null() {
return 0;
}
if unsafe { _call_usermodehelper_exec(info, UMH_WAIT_EXEC) } != 0 {
return 0;
}
unsafe { (*held).id }
}
pub fn resume_process(id: u32) -> bool {
let Some(task) = task_with_id(id) else {
return false;
};
native::send_signal(CONTINUE, task);
true
}
pub fn gate_spawns(on: bool) {
GATING.store(on, core::sync::atomic::Ordering::Release);
if on {
super::injection::hold_what_is_spawned();
}
}
pub fn holds_this_one(task: usize) -> bool {
if GATING.load(core::sync::atomic::Ordering::Acquire) {
return true;
}
let Some(id) = super::injection::id_of(task) else {
return false;
};
let held = unsafe { (&raw const HELD).read() };
held != 0 && held == id
}
// A spawn is held in the kernel's own exec path, where telling the host is not this thread's
// to do; the loop says it once it is back where sending a frame belongs.
pub fn note_a_held_spawn(id: u32, program: *const u8) {
let said = unsafe { core::ffi::CStr::from_ptr(program.cast()) };
unsafe { held_spawns() }.push((id, said.to_bytes().to_vec()));
}
pub fn a_spawn_is_held() -> bool {
!unsafe { held_spawns() }.is_empty()
}
pub fn tell_of_held_spawns(say: &mut dyn FnMut(u32, &[u8])) {
for (id, program) in core::mem::take(unsafe { held_spawns() }) {
say(id, &program);
}
}
unsafe fn held_spawns() -> &'static mut Vec<(u32, Vec<u8>)> {
unsafe { (&raw mut HELD_SPAWNS).as_mut().unwrap() }
}
static mut HELD_SPAWNS: Vec<(u32, Vec<u8>)> = Vec::new();
static GATING: core::sync::atomic::AtomicBool = core::sync::atomic::AtomicBool::new(false);
#[unsafe(no_mangle)]
pub unsafe extern "C" fn frida_cb_hold_spawn(info: *mut c_void, _credentials: *mut c_void) -> c_int {
let Some(at) = field_offset("subprocess_info", "data") else {
return 0;
};
let held = unsafe { ((info as usize + at) as *const usize).read() } as *mut Held;
let id = super::injection::id_of(native::current_task() as usize).unwrap_or(0);
unsafe {
(*held).id = id;
(&raw mut HELD).write(id);
}
0
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn frida_cb_release_words(info: *mut c_void) {
let Some(at) = field_offset("subprocess_info", "data") else {
return;
};
let held = unsafe { ((info as usize + at) as *const usize).read() } as *mut Held;
unsafe { ptr::drop_in_place(held) };
native::free(held as *mut u8, size_of::<Held>());
}
fn said_as(words: &[&str]) -> Option<Words> {
let said: Vec<CString> = words.iter().map(|word| CString::new(*word).unwrap()).collect();
if said.is_empty() {
return None;
}
let mut pointers: Vec<*const u8> = said.iter().map(|word| word.as_ptr() as *const u8).collect();
pointers.push(ptr::null());
Some(Words { said, pointers })
}
fn environment() -> Words {
said_as(&["HOME=/", "PATH=/sbin:/usr/sbin:/bin:/usr/bin", "TERM=linux"]).unwrap()
}
struct Held {
id: u32,
words: Words,
environment: Words,
}
struct Words {
said: Vec<CString>,
pointers: Vec<*const u8>,
}
impl Words {
fn first_word(&self) -> *const u8 {
self.said[0].as_ptr() as *const u8
}
fn as_argv(&self) -> *const *const u8 {
self.pointers.as_ptr()
}
}
static mut HELD: u32 = 0;
const GFP_KERNEL: u32 = 0xcc0;
const UMH_WAIT_EXEC: c_int = 1;
const CONTINUE: c_int = 18;
unsafe extern "C" {
#[cfg(not(target_arch = "x86"))]
static _call_usermodehelper_setup: unsafe extern "C" fn(
*const u8,
*const *const u8,
*const *const u8,
u32,
unsafe extern "C" fn(*mut c_void, *mut c_void) -> c_int,
unsafe extern "C" fn(*mut c_void),
*mut c_void,
) -> *mut c_void;
#[cfg(not(target_arch = "x86"))]
static _call_usermodehelper_exec: unsafe extern "C" fn(*mut c_void, c_int) -> c_int;
}
#[cfg(target_arch = "x86")]
unsafe extern "C" {
#[link_name = "frida_k_call_usermodehelper_exec"]
fn _call_usermodehelper_exec(a0: *mut c_void, a1: c_int) -> c_int;
}
#[cfg(target_arch = "x86")]
unsafe extern "C" {
#[link_name = "frida_k_call_usermodehelper_setup"]
fn _call_usermodehelper_setup(
a0: *const u8,
a1: *const *const u8,
a2: *const *const u8,
a3: u32,
a4: unsafe extern "C" fn(*mut c_void, *mut c_void) -> c_int,
a5: unsafe extern "C" fn(*mut c_void),
a6: *mut c_void,
) -> *mut c_void;
fn frida_kcb_hold_spawn(info: *mut c_void, credentials: *mut c_void) -> c_int;
fn frida_kcb_release_words(info: *mut c_void);
}
#[cfg(target_arch = "x86")]
const HOLD_SPAWN: unsafe extern "C" fn(*mut c_void, *mut c_void) -> c_int = frida_kcb_hold_spawn;
#[cfg(not(target_arch = "x86"))]
const HOLD_SPAWN: unsafe extern "C" fn(*mut c_void, *mut c_void) -> c_int = frida_cb_hold_spawn;
#[cfg(target_arch = "x86")]
const RELEASE_WORDS: unsafe extern "C" fn(*mut c_void) = frida_kcb_release_words;
#[cfg(not(target_arch = "x86"))]
const RELEASE_WORDS: unsafe extern "C" fn(*mut c_void) = frida_cb_release_words;
-29
View File
@@ -1,29 +0,0 @@
use alloc::ffi::CString;
use alloc::format;
use alloc::vec::Vec;
use crate::kernel::ThreadInfo;
use super::facade::{home_process_id, in_copy};
use super::processes::running_task_ids;
use super::user::names_in;
pub fn enumerate_threads(found: &mut dyn FnMut(ThreadInfo)) {
let copy = in_copy();
let ids = if copy { running_threads() } else { running_task_ids() };
for id in ids {
if copy && super::user::thread_is_ours(id) {
continue;
}
found(ThreadInfo { id, cpu_state: None });
}
}
fn running_threads() -> Vec<u32> {
let where_they_are = CString::new(format!("/proc/{}/task", home_process_id())).unwrap();
names_in(&where_they_are)
.iter()
.filter_map(|name| name.parse().ok())
.collect()
}
File diff suppressed because it is too large. Load diff
+691 -10
View File
@@ -1,12 +1,693 @@
// The XNU flavour ships as a freestanding ELF executable: the host injects the
// image and calls its `_start`. All of the agent lives in the library crate;
// this target exists only to produce that link. build.rs passes `--undefined
// _start` so the entrypoint survives --gc-sections.
//
// The Linux flavour is built as a static library instead and linked into a
// kernel module by kbuild — see linux/Kbuild.
#![no_std]
#![no_main]
#![no_std]
use frida_barebone_agent as _;
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_byte, 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_get_variant, 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_get_stalker, gum_script_load_sync, gum_script_post, gum_script_set_message_handler,
gum_script_unload_sync, gum_stalker_exclude, GumMemoryRange,
};
use symbols::SymbolTable;
mod ffi;
mod glib;
mod gthread;
mod gum;
mod hostlink_virtio;
mod hostlink_vsock;
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,
RemapWritablePages = 5,
MemoryProtect = 6,
PatchCode = 7,
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::RemapWritablePages => write!(f, "RemapWritablePages"),
FridaCommand::MemoryProtect => write!(f, "MemoryProtect"),
FridaCommand::PatchCode => write!(f, "PatchCode"),
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();
pub enum Transport {
Virtio(hostlink_virtio::Hostlink),
Vsock(hostlink_vsock::Hostlink),
}
impl Transport {
pub fn send(&self, payload: &[u8]) {
match self {
Transport::Virtio(h) => h.send(payload),
Transport::Vsock(h) => h.send(payload),
}
}
pub fn process(&self) {
match self {
Transport::Virtio(h) => h.process(),
Transport::Vsock(h) => h.process(),
}
}
}
pub enum TransportConfig {
Virtio { mmio: u64, irq: u32 },
Vsock { host_port: u32 },
}
static mut TRANSPORT_DRIVER: *mut Transport = core::ptr::null_mut();
#[inline(always)]
fn transport_set(driver: Transport) {
unsafe {
let boxed = Box::into_raw(Box::new(driver));
TRANSPORT_DRIVER = boxed;
}
}
#[inline(always)]
fn transport_get_unchecked() -> &'static Transport {
unsafe {
debug_assert!(!TRANSPORT_DRIVER.is_null());
&*TRANSPORT_DRIVER
}
}
#[inline(always)]
fn transport_is_up() -> bool {
unsafe { !TRANSPORT_DRIVER.is_null() }
}
static mut SCRIPTS: BTreeMap<u32, *mut GumScript> = BTreeMap::new();
static NEXT_SCRIPT_ID: AtomicU32 = AtomicU32::new(1);
static mut OWN_RANGE: GumMemoryRange = GumMemoryRange {
base_address: 0,
size: 0,
};
static NEXT_REQUEST_ID: AtomicU32 = AtomicU32::new(1);
static mut PENDING_REPLIES: BTreeMap<u16, *mut GVariant> = BTreeMap::new();
#[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 {
xnu::io_log("frida: _start entry\n\0");
CONFIG_DATA = core::slice::from_raw_parts(config_data, config_size);
xnu::io_log("frida: _start config stored\n\0");
let _r = xnu::kernel_thread_start(frida_agent_worker, 12345usize as *mut core::ffi::c_void);
}
}
// KNOWN ISSUE (vphone research kernel): the worker panics during gum_init_embedded with
// "debug exceptions enabled in kernel mode". The worker itself is healthy — it runs with
// PSTATE.D=1 (debug masked) throughout. The panic is the research kernel's synchronous-
// exception handler reacting to ANY exception the agent takes in kernel mode while the VZ
// debug stub is active. Two confirmed sources: (1) the host's breakpoint-based Callbacks
// patch BRK at gum_try_mprotect/remap, and the BRK is a debug exception; (2) a stray jump
// into the agent's data region during GObject init (instruction abort) — looks like a
// misapplied relocation for a function pointer stored in .data/.rodata.
unsafe extern "C" fn frida_agent_worker(_parameter: *mut core::ffi::c_void, _wait_result: i32) {
unsafe {
xnu::io_log("frida: worker entry\n\0");
bindings::g_set_panic_handler(Some(frida_panic_handler), ptr::null_mut());
bindings::gum_init_embedded();
xnu::io_log("frida: gum_init_embedded done\n\0");
bindings::g_log_set_default_handler(Some(frida_log_handler), ptr::null_mut());
let (transport_config, kernel_base, module_info, symbol_table, own_range) =
parse_config(core::ptr::addr_of!(CONFIG_DATA).read());
xnu::set_kernel_base(kernel_base);
MODULE_INFO = module_info;
SYMBOL_TABLE = symbol_table;
OWN_RANGE = own_range;
xnu::io_log("frida: config parsed, init transport\n\0");
let wake_token = ptr::addr_of_mut!(glib::WAKEUP_TOKEN) as *const u8;
let transport = match transport_config {
TransportConfig::Virtio { mmio, irq } => Transport::Virtio(
hostlink_virtio::Hostlink::init(mmio, irq, Some(on_frame_from_host), wake_token)
.unwrap(),
),
TransportConfig::Vsock { host_port } => Transport::Vsock(
hostlink_vsock::Hostlink::init(host_port, Some(on_frame_from_host), wake_token)
.unwrap(),
),
};
transport_set(transport);
xnu::io_log("frida: transport up, entering main loop\n\0");
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],
) -> (TransportConfig, u64, Vec<ModuleInfo>, SymbolTable, GumMemoryRange) {
unsafe {
let type_string = c"((tt)yvta(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 transport_kind_variant = g_variant_get_child_value(root_variant, 1);
let transport_kind = g_variant_get_byte(transport_kind_variant);
let transport_cfg_outer = g_variant_get_child_value(root_variant, 2);
let transport_cfg_inner = g_variant_get_variant(transport_cfg_outer);
let transport_config = match transport_kind {
0 => {
let mmio_variant = g_variant_get_child_value(transport_cfg_inner, 0);
let mmio = g_variant_get_uint64(mmio_variant);
let irq_variant = g_variant_get_child_value(transport_cfg_inner, 1);
let irq = g_variant_get_uint32(irq_variant);
g_variant_unref(irq_variant);
g_variant_unref(mmio_variant);
TransportConfig::Virtio { mmio, irq }
}
1 => {
let host_port = g_variant_get_uint32(transport_cfg_inner);
TransportConfig::Vsock { host_port }
}
_ => panic!("Unsupported transport kind: {}", transport_kind),
};
g_variant_unref(transport_cfg_inner);
g_variant_unref(transport_cfg_outer);
g_variant_unref(transport_kind_variant);
let kernel_base_variant = g_variant_get_child_value(root_variant, 3);
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, 4);
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, 5);
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,
));
let own_range_variant = g_variant_get_child_value(root_variant, 0);
let mut own_base: u64 = 0;
let mut own_size: u64 = 0;
g_variant_get(
own_range_variant,
c"(tt)".as_ptr(),
&mut own_base,
&mut own_size,
);
let own_range = GumMemoryRange {
base_address: own_base,
size: own_size as gsize,
};
g_variant_unref(own_range_variant);
g_variant_unref(symbol_array_variant);
g_variant_unref(module_info_variant);
g_variant_unref(kernel_base_variant);
g_variant_unref(root_variant);
g_variant_type_free(variant_type);
(transport_config, kernel_base, module_info, symbol_table, own_range)
}
}
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)
};
if cmd == FridaCommand::Reply {
unsafe {
core::ptr::addr_of_mut!(PENDING_REPLIES)
.as_mut()
.unwrap()
.insert(request_id, payload_variant);
}
return;
}
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);
}
}
pub fn host_rpc(command: FridaCommand, payload: *mut GVariant) -> *mut GVariant {
unsafe {
let request_id = NEXT_REQUEST_ID.fetch_add(1, Ordering::Relaxed) as u16;
let message = g_variant_new(
c"(yqv)".as_ptr(),
command as u8 as u32,
request_id as u32,
payload,
);
let transport = transport_get_unchecked();
transport.send(&serialize_message(message).unwrap());
g_variant_unref(message);
let wait_event = ptr::addr_of_mut!(glib::WAKEUP_TOKEN) as *const u8;
loop {
xnu::assert_wait(wait_event, xnu::THREAD_INTERRUPTIBLE);
transport.process();
if let Some(reply) = take_pending_reply(request_id) {
xnu::thread_wakeup(wait_event);
return reply;
}
xnu::thread_block(None);
}
}
}
fn take_pending_reply(request_id: u16) -> Option<*mut GVariant> {
unsafe {
core::ptr::addr_of_mut!(PENDING_REPLIES)
.as_mut()
.unwrap()
.remove(&request_id)
}
}
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);
}
exclude_own_range_from_stalker(script);
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))
}
}
// Stalker must not instrument our own runtime: when it follows the current
// thread into the hostlink transport it would deadlock on the locks that the
// RPC it depends on is holding. The host injected the image, so it tells us our
// range as part of the config.
unsafe fn exclude_own_range_from_stalker(script: *mut GumScript) {
unsafe {
gum_stalker_exclude(gum_script_get_stalker(script), core::ptr::addr_of!(OWN_RANGE));
}
}
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)
}};
}
-13
View File
@@ -14,19 +14,6 @@ pub unsafe fn ptrauth_sign(ptr: *const u8, discriminator: usize) -> *const u8 {
signed as *const u8
}
pub unsafe fn ptrauth_strip_pointer(ptr: *const u8) -> *const u8 {
let stripped: usize;
unsafe {
asm!(
".inst 0xdac147e0", // xpacd x0
in("x0") ptr as usize,
lateout("x0") stripped,
options(nomem, nostack),
);
}
stripped as *const u8
}
pub unsafe fn ptrauth_strip_data(ptr: *const u8) -> *const u8 {
let stripped: usize;
unsafe {
-124
View File
@@ -1,124 +0,0 @@
use core::sync::atomic::{AtomicU32, Ordering};
pub struct Ring {
pub buffer: u64,
pub head: u64,
pub tail: u64,
pub lock: u64,
pub room: u64,
pub size: usize,
}
pub enum Taken {
Nothing,
Piece,
Frame,
}
impl Ring {
pub fn write(&self, arena: u64, buffer: u64, frame: &[u8], from: usize) -> Option<usize> {
let head = self.index(arena, self.head).load(Ordering::Relaxed);
let tail = self.index(arena, self.tail).load(Ordering::Acquire);
let free = self.size - head.wrapping_sub(tail) as usize;
if free <= HEADER {
return None;
}
let piece = core::cmp::min(frame.len() - from, free - HEADER);
let more = if from + piece == frame.len() { 0 } else { MORE };
self.put(buffer, head, &(piece as u32 | more).to_le_bytes());
self.put(buffer, head.wrapping_add(HEADER as u32), &frame[from..from + piece]);
self.index(arena, self.head)
.store(head.wrapping_add((HEADER + piece) as u32), Ordering::Release);
Some(from + piece)
}
pub fn read(&self, arena: u64, buffer: u64, into: &mut alloc::vec::Vec<u8>) -> Taken {
let head = self.index(arena, self.head).load(Ordering::Acquire);
let tail = self.index(arena, self.tail).load(Ordering::Relaxed);
if head == tail {
return Taken::Nothing;
}
let mut header = [0u8; HEADER];
self.get(buffer, tail, &mut header);
let header = u32::from_le_bytes(header);
let piece = (header & !MORE) as usize;
let already = into.len();
into.resize(already + piece, 0);
self.get(buffer, tail.wrapping_add(HEADER as u32), &mut into[already..]);
self.index(arena, self.tail)
.store(tail.wrapping_add((HEADER + piece) as u32), Ordering::Release);
if header & MORE == 0 {
Taken::Frame
} else {
Taken::Piece
}
}
pub fn take_lock(&self, arena: u64, rest: fn()) {
while self.index(arena, self.lock)
.compare_exchange(0, 1, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
rest();
}
}
pub fn let_lock_go(&self, arena: u64) {
self.index(arena, self.lock).store(0, Ordering::Release);
}
pub fn ask_for_room(&self, arena: u64) {
self.index(arena, self.room).store(1, Ordering::Release);
}
pub fn room_is_wanted(&self, arena: u64) -> bool {
self.index(arena, self.room).swap(0, Ordering::AcqRel) != 0
}
pub fn has_room(&self, arena: u64) -> bool {
let head = self.index(arena, self.head).load(Ordering::Relaxed);
let tail = self.index(arena, self.tail).load(Ordering::Acquire);
self.size - head.wrapping_sub(tail) as usize > HEADER
}
pub fn holds_anything(&self, arena: u64) -> bool {
self.index(arena, self.head).load(Ordering::Acquire)
!= self.index(arena, self.tail).load(Ordering::Relaxed)
}
fn put(&self, buffer: u64, at: u32, bytes: &[u8]) {
let at = at as usize & (self.size - 1);
let first = core::cmp::min(bytes.len(), self.size - at);
unsafe {
core::ptr::copy_nonoverlapping(bytes.as_ptr(), (buffer + at as u64) as *mut u8, first);
core::ptr::copy_nonoverlapping(bytes.as_ptr().add(first), buffer as *mut u8,
bytes.len() - first);
}
}
fn get(&self, buffer: u64, at: u32, bytes: &mut [u8]) {
let at = at as usize & (self.size - 1);
let first = core::cmp::min(bytes.len(), self.size - at);
unsafe {
core::ptr::copy_nonoverlapping((buffer + at as u64) as *const u8, bytes.as_mut_ptr(),
first);
core::ptr::copy_nonoverlapping(buffer as *const u8, bytes.as_mut_ptr().add(first),
bytes.len() - first);
}
}
fn index(&self, arena: u64, at: u64) -> &AtomicU32 {
unsafe { AtomicU32::from_ptr((arena + at) as *mut u32) }
}
}
const HEADER: usize = 4;
const MORE: u32 = 0x8000_0000;
-257
View File
@@ -1,257 +0,0 @@
use alloc::vec::Vec;
#[cfg(target_arch = "x86")]
macro_rules! windows_fn {
($($argument:ty),* $(,)? => $result:ty) => {
unsafe extern "stdcall" fn($($argument),*) -> $result
};
}
#[cfg(target_arch = "x86_64")]
macro_rules! windows_fn {
($($argument:ty),* $(,)? => $result:ty) => {
unsafe extern "win64" fn($($argument),*) -> $result
};
}
pub struct Api {
pub find_first: usize,
pub find_next: usize,
pub find_close: usize,
pub create_file: usize,
pub read_file: usize,
pub set_file_pointer: usize,
pub close_handle: usize,
}
pub fn enumerate(api: &Api, menu: &[u8], found: &mut dyn FnMut(&str, &str, &str, &str)) {
walk_for_shortcuts(api, menu, 0, found);
}
fn identity_of(api: &Api, target: &[u8]) -> (Vec<u8>, Vec<u8>) {
let mut asciiz = Vec::new();
asciiz.extend_from_slice(target);
asciiz.push(0);
let Some(image) = Image::open(api, &asciiz) else {
return (Vec::new(), Vec::new());
};
let mut identity = [0u8; 128];
let mut description = [0u8; 128];
let named = crate::icons::identify(&image, &mut identity);
let described = crate::icons::describe(&image, &mut description);
(identity[..named].to_vec(), description[..described].to_vec())
}
fn walk_for_shortcuts(api: &Api, directory: &[u8], depth: u32,
found: &mut dyn FnMut(&str, &str, &str, &str)) {
if depth == MAX_MENU_DEPTH {
return;
}
let find_first: windows_fn!(*const u8, *mut u8 => u32) =
unsafe { core::mem::transmute(api.find_first) };
let find_next: windows_fn!(u32, *mut u8 => u32) =
unsafe { core::mem::transmute(api.find_next) };
let find_close: windows_fn!(u32 => u32) = unsafe { core::mem::transmute(api.find_close) };
let mut pattern = Vec::new();
pattern.extend_from_slice(directory);
pattern.extend_from_slice(b"\\*.*\0");
let mut entry = [0u8; FIND_DATA_SIZE];
let search = unsafe { find_first(pattern.as_ptr(), entry.as_mut_ptr()) };
if search == INVALID_HANDLE {
return;
}
loop {
let attributes = u32::from_le_bytes([entry[0], entry[1], entry[2], entry[3]]);
let name = text_in(&entry[FIND_DATA_NAME..]);
if name != b"." && name != b".." {
let mut path = Vec::new();
path.extend_from_slice(directory);
path.push(b'\\');
path.extend_from_slice(name);
if attributes & FILE_ATTRIBUTE_DIRECTORY != 0 {
walk_for_shortcuts(api, &path, depth + 1, found);
} else if ends_with_ignoring_case(name, b".lnk") {
if let Some(target) = target_of_shortcut(api, &path)
.filter(|target| ends_with_ignoring_case(target, b".exe")) {
let shown = &name[..name.len() - 4];
let (identity, description) = identity_of(api, &target);
found(text_as_str(&identity), text_as_str(&target), text_as_str(shown),
text_as_str(&description));
}
}
}
if unsafe { find_next(search, entry.as_mut_ptr()) } == 0 {
break;
}
}
unsafe { find_close(search) };
}
fn target_of_shortcut(api: &Api, path: &[u8]) -> Option<Vec<u8>> {
let mut asciiz = Vec::new();
asciiz.extend_from_slice(path);
asciiz.push(0);
let blob = read_whole_file(api, &asciiz)?;
if blob.len() < SHELL_LINK_HEADER || blob[0] != SHELL_LINK_HEADER as u8 {
return None;
}
let flags = u32::from_le_bytes([blob[0x14], blob[0x15], blob[0x16], blob[0x17]]);
let mut at = SHELL_LINK_HEADER;
if flags & HAS_TARGET_ID_LIST != 0 {
if at + 2 > blob.len() {
return None;
}
at += 2 + u16::from_le_bytes([blob[at], blob[at + 1]]) as usize;
}
if flags & HAS_LINK_INFO == 0 || at + 0x1c > blob.len() {
return None;
}
let word = |offset: usize| {
u32::from_le_bytes([blob[offset], blob[offset + 1], blob[offset + 2], blob[offset + 3]])
};
if word(at + 8) & VOLUME_ID_AND_LOCAL_BASE_PATH == 0 {
return None;
}
let base = at + word(at + 0x10) as usize;
let suffix = at + word(at + 0x18) as usize;
if base >= blob.len() || suffix >= blob.len() {
return None;
}
let mut target = Vec::new();
target.extend_from_slice(text_in(&blob[base..]));
target.extend_from_slice(text_in(&blob[suffix..]));
Some(target)
}
fn read_whole_file(api: &Api, path: &[u8]) -> Option<Vec<u8>> {
let create_file: windows_fn!(*const u8, u32, u32, u32, u32, u32, u32 => u32) =
unsafe { core::mem::transmute(api.create_file) };
let read_file: windows_fn!(u32, *mut u8, u32, *mut u32, u32 => u32) =
unsafe { core::mem::transmute(api.read_file) };
let close_handle: windows_fn!(u32 => u32) = unsafe { core::mem::transmute(api.close_handle) };
let file = unsafe {
create_file(path.as_ptr(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0)
};
if file == INVALID_HANDLE {
return None;
}
let mut blob = alloc::vec![0u8; MAX_SHORTCUT];
let mut taken = 0u32;
let ok = unsafe {
read_file(file, blob.as_mut_ptr(), blob.len() as u32, &raw mut taken, 0)
};
unsafe { close_handle(file) };
if ok == 0 {
return None;
}
blob.truncate(taken as usize);
Some(blob)
}
fn text_in(bytes: &[u8]) -> &[u8] {
let end = bytes.iter().position(|b| *b == 0).unwrap_or(bytes.len());
&bytes[..end]
}
fn text_as_str(bytes: &[u8]) -> &str {
core::str::from_utf8(bytes).unwrap_or("")
}
fn ends_with_ignoring_case(name: &[u8], suffix: &[u8]) -> bool {
if name.len() < suffix.len() {
return false;
}
name[name.len() - suffix.len()..]
.iter()
.zip(suffix)
.all(|(a, b)| a.to_ascii_lowercase() == *b)
}
const MAX_MENU_DEPTH: u32 = 4;
const MAX_SHORTCUT: usize = 4096;
const FIND_DATA_SIZE: usize = 0x140;
const FIND_DATA_NAME: usize = 0x2c;
const FILE_ATTRIBUTE_DIRECTORY: u32 = 0x10;
const INVALID_HANDLE: u32 = 0xffff_ffff;
const GENERIC_READ: u32 = 0x8000_0000;
const FILE_SHARE_READ: u32 = 0x1;
const OPEN_EXISTING: u32 = 3;
const SHELL_LINK_HEADER: usize = 0x4c;
const HAS_TARGET_ID_LIST: u32 = 0x1;
const HAS_LINK_INFO: u32 = 0x2;
const VOLUME_ID_AND_LOCAL_BASE_PATH: u32 = 0x1;
struct Image<'a> {
api: &'a Api,
handle: u32,
}
impl crate::icons::Image for Image<'_> {
fn read_at(&self, position: u32, buffer: &mut [u8]) -> u32 {
let set_pointer: windows_fn!(u32, u32, u32, u32 => u32) =
unsafe { core::mem::transmute(self.api.set_file_pointer) };
let read_file: windows_fn!(u32, *mut u8, u32, *mut u32, u32 => u32) =
unsafe { core::mem::transmute(self.api.read_file) };
unsafe { set_pointer(self.handle, position, 0, 0) };
let mut taken = 0u32;
if unsafe { read_file(self.handle, buffer.as_mut_ptr(), buffer.len() as u32,
&raw mut taken, 0) } == 0 {
return 0;
}
taken
}
}
impl<'a> Image<'a> {
fn open(api: &'a Api, path: &[u8]) -> Option<Image<'a>> {
let create_file: windows_fn!(*const u8, u32, u32, u32, u32, u32, u32 => u32) =
unsafe { core::mem::transmute(api.create_file) };
let handle = unsafe {
create_file(path.as_ptr(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0)
};
if handle == INVALID_HANDLE {
return None;
}
Some(Image { api, handle })
}
}
impl Drop for Image<'_> {
fn drop(&mut self) {
let close_handle: windows_fn!(u32 => u32) =
unsafe { core::mem::transmute(self.api.close_handle) };
unsafe { close_handle(self.handle) };
}
}
+17 -18
View File
@@ -1,7 +1,7 @@
use crate::bindings::{
GPatternSpec, g_pattern_spec_free, g_pattern_spec_match_string, g_pattern_spec_new,
};
use crate::kernel::get_kernel_base;
use crate::xnu::get_kernel_base;
use alloc::ffi::CString;
use core::cmp::Ordering;
use core::ffi::{CStr, c_char};
@@ -101,7 +101,7 @@ impl SymbolTable {
}
pub fn find_symbol_by_address(&self, address: u64) -> Option<SymbolRef> {
let target_offset = address - get_kernel_base();
let target_offset = (address - get_kernel_base()) as u32;
let (_, entry) = self.binary_search_by_address(target_offset)?;
Some(SymbolRef {
symbol_table: self,
@@ -111,7 +111,7 @@ impl SymbolTable {
}
pub fn find_symbol_name_ptr_by_address(&self, address: u64) -> *const c_char {
let target_offset = address - get_kernel_base();
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 {
@@ -121,7 +121,7 @@ impl SymbolTable {
pub fn find_closest_symbol_by_address(&self, address: u64) -> Option<SymbolRef> {
let kernel_base = get_kernel_base();
let target_offset = address - 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,
@@ -142,8 +142,8 @@ impl SymbolTable {
}
let kernel_base = get_kernel_base();
let start_offset = start_address - kernel_base;
let end_offset = end_address - 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;
@@ -152,7 +152,7 @@ impl SymbolTable {
let mid = left + (right - left) / 2;
let entry = self.get_symbol_entry_by_address_index(mid);
if { entry.address_offset } < start_offset {
if entry.address_offset < start_offset {
left = mid + 1;
} else {
right = mid;
@@ -161,7 +161,7 @@ impl SymbolTable {
while left > 0 {
let prev_entry = self.get_symbol_entry_by_address_index(left - 1);
if { prev_entry.address_offset } >= start_offset {
if prev_entry.address_offset >= start_offset {
left -= 1;
} else {
break;
@@ -209,21 +209,21 @@ impl SymbolTable {
)
}
fn binary_search_by_address(&self, target_offset: u64) -> Option<(usize, &SymbolEntry)> {
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),
|entry| entry.address_offset.cmp(&target_offset),
false,
)
}
fn binary_search_closest_by_address(
&self,
target_offset: u64,
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),
|entry| entry.address_offset.cmp(&target_offset),
true,
)
}
@@ -266,10 +266,9 @@ impl SymbolTable {
}
}
// The host writes this layout, thus the compiler must add no padding.
#[repr(C, packed)]
#[repr(C)]
struct SymbolEntry {
address_offset: u64,
address_offset: u32,
symbol_type: u8,
section: u8,
description: u16,
@@ -310,7 +309,7 @@ impl<'a> SymbolRef<'a> {
}
pub fn address(&self) -> u64 {
self.kernel_base + { self.entry.address_offset }
self.kernel_base + self.entry.address_offset as u64
}
pub fn symbol_type(&self) -> u8 {
@@ -416,7 +415,7 @@ pub struct SymbolsInRangeIterator<'a> {
symbol_table: &'a SymbolTable,
current_index: usize,
end_index: usize,
end_offset: u64,
end_offset: u32,
kernel_base: u64,
}
@@ -428,7 +427,7 @@ impl<'a> Iterator for SymbolsInRangeIterator<'a> {
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 {
if entry.address_offset >= self.end_offset {
break;
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
-308
View File
@@ -1,308 +0,0 @@
// Windows maps the page tables into themselves. Thus the entry for a page is at an address
// that you calculate from the page, and no kernel export is necessary. The two word sizes
// differ only in the address of the self-map and the number of levels.
pub fn protect(address: u64, size: usize, gum_prot: u32) -> bool {
let first_page = (address / PAGE_SIZE as u64) as usize;
let pages = (size + (address as usize & (PAGE_SIZE - 1))).div_ceil(PAGE_SIZE);
for page in first_page..first_page + pages {
let address = page * PAGE_SIZE;
if !maps_small_page(address) {
continue;
}
reprotect_page(address, gum_prot);
invalidate_page(address);
}
true
}
// Read the page tables and join adjacent pages that have the same permissions. Report only
// the kernel half of the address space, because the other half belongs to the process that
// is current.
pub fn enumerate_ranges(found: &mut dyn FnMut(u64, u64, u32)) {
let mut ranges = Ranges {
found,
base: 0,
size: 0,
protection: 0,
};
walk_kernel_space(&mut ranges);
ranges.flush();
}
struct Ranges<'f> {
found: &'f mut dyn FnMut(u64, u64, u32),
base: usize,
size: usize,
protection: u32,
}
impl Ranges<'_> {
fn add(&mut self, address: usize, size: usize, protection: u32) {
if protection != self.protection || self.base.wrapping_add(self.size) != address {
self.flush();
self.base = address;
self.protection = protection;
}
self.size += size;
}
fn flush(&mut self) {
if self.protection != 0 {
(self.found)(self.base as u64, self.size as u64, self.protection);
}
self.size = 0;
self.protection = 0;
}
}
#[cfg(target_arch = "x86")]
mod arch {
use super::*;
// A 32-bit kernel can use PAE. The two forms differ in the width of an entry, in the address
// of the directory, and in the availability of the non-executable bit.
pub fn protection_at(address: usize) -> u32 {
unsafe {
if pae_enabled() {
let pde = ((PAE_PDE_BASE + (address >> 21) * 8) as *const u64).read_volatile();
if (pde & PAGE_PRESENT as u64) == 0 {
return 0;
}
if (pde & PAGE_LARGE as u64) != 0 {
return protection_of(pde, PAGE_NO_EXECUTE);
}
let pte = ((PTE_BASE + (address >> 12) * 8) as *const u64).read_volatile();
if (pte & PAGE_PRESENT as u64) == 0 {
return 0;
}
protection_of(pte, PAGE_NO_EXECUTE)
} else {
let pde = ((PDE_BASE + (address >> 22) * 4) as *const u32).read_volatile();
if (pde & PAGE_PRESENT) == 0 {
return 0;
}
if (pde & PAGE_LARGE) != 0 {
return protection_of(pde as u64, 0);
}
let pte = ((PTE_BASE + (address >> 12) * 4) as *const u32).read_volatile();
if (pte & PAGE_PRESENT) == 0 {
return 0;
}
protection_of(pte as u64, 0)
}
}
}
// Two gigabytes contain sufficiently few pages to examine each one.
pub fn walk_kernel_space(ranges: &mut Ranges) {
let mut address = KERNEL_SPACE_START;
while address != 0 {
let protection = protection_at(address);
if protection != 0 {
ranges.add(address, PAGE_SIZE, protection);
}
address = address.wrapping_add(PAGE_SIZE);
}
}
pub fn reprotect_page(address: usize, gum_prot: u32) {
unsafe {
if pae_enabled() {
let entry = (PTE_BASE + (address >> 12) * 8) as *mut u64;
let value = apply_protection(entry.read_volatile(), gum_prot as u64,
PAGE_WRITEABLE as u64, PAGE_NO_EXECUTE);
entry.write_volatile(value);
} else {
let entry = (PTE_BASE + (address >> 12) * 4) as *mut u32;
let value = apply_protection(entry.read_volatile() as u64, gum_prot as u64,
PAGE_WRITEABLE as u64, 0);
entry.write_volatile(value as u32);
}
}
}
// The kernel maps its pool with large pages, which have no page table. Thus the self-map has
// no entry to change. Such a mapping is already writable and executable.
pub fn maps_small_page(address: usize) -> bool {
unsafe {
if pae_enabled() {
let pde = ((PAE_PDE_BASE + (address >> 21) * 8) as *const u64).read_volatile();
(pde & PAGE_PRESENT as u64) != 0 && (pde & PAGE_LARGE as u64) == 0
} else {
let pde = ((PDE_BASE + (address >> 22) * 4) as *const u32).read_volatile();
(pde & PAGE_PRESENT) != 0 && (pde & PAGE_LARGE) == 0
}
}
}
fn pae_enabled() -> bool {
let cr4: u32;
unsafe {
core::arch::asm!("mov {0:e}, cr4", out(reg) cr4,
options(nomem, nostack, preserves_flags));
}
(cr4 & CR4_PAE) != 0
}
const KERNEL_SPACE_START: usize = 0x8000_0000;
const PTE_BASE: usize = 0xc000_0000;
const PDE_BASE: usize = 0xc030_0000;
const PAE_PDE_BASE: usize = 0xc060_0000;
const CR4_PAE: u32 = 1 << 5;
}
#[cfg(target_arch = "x86_64")]
mod arch {
use super::*;
pub fn protection_at(address: usize) -> u32 {
for level in TOP_LEVEL..TABLE_LEVEL {
let entry = entry_at(level, address);
if (entry & PAGE_PRESENT as u64) == 0 {
return 0;
}
if (entry & PAGE_LARGE as u64) != 0 {
return protection_of(entry, PAGE_NO_EXECUTE);
}
}
let entry = entry_at(TABLE_LEVEL, address);
if (entry & PAGE_PRESENT as u64) == 0 {
return 0;
}
protection_of(entry, PAGE_NO_EXECUTE)
}
// Half of a 48-bit space contains too many pages to examine each one. Thus the walk goes down
// the levels, and an absent table costs nothing.
pub fn walk_kernel_space(ranges: &mut Ranges) {
for index in ENTRIES_PER_TABLE / 2..ENTRIES_PER_TABLE {
descend(sign_extend(index << LEVEL_SHIFTS[TOP_LEVEL]), TOP_LEVEL, ranges);
}
}
fn descend(address: usize, level: usize, ranges: &mut Ranges) {
let entry = entry_at(level, address);
if (entry & PAGE_PRESENT as u64) == 0 {
return;
}
let span = 1usize << LEVEL_SHIFTS[level];
if level == TABLE_LEVEL || (entry & PAGE_LARGE as u64) != 0 {
ranges.add(address, span, protection_of(entry, PAGE_NO_EXECUTE));
return;
}
let child_span = span / ENTRIES_PER_TABLE;
for index in 0..ENTRIES_PER_TABLE {
descend(address.wrapping_add(index * child_span), level + 1, ranges);
}
}
pub fn reprotect_page(address: usize, gum_prot: u32) {
let entry = entry_pointer(TABLE_LEVEL, address);
unsafe {
let value = apply_protection(entry.read_volatile(), gum_prot as u64,
PAGE_WRITEABLE as u64, PAGE_NO_EXECUTE);
entry.write_volatile(value);
}
}
// The kernel maps its pool with large pages, which have no page table. Thus the self-map has
// no entry to change. Such a mapping is already writable and executable.
pub fn maps_small_page(address: usize) -> bool {
for level in TOP_LEVEL..TABLE_LEVEL {
let entry = entry_at(level, address);
if (entry & PAGE_PRESENT as u64) == 0 || (entry & PAGE_LARGE as u64) != 0 {
return false;
}
}
true
}
fn entry_at(level: usize, address: usize) -> u64 {
unsafe { entry_pointer(level, address).read_volatile() }
}
fn entry_pointer(level: usize, address: usize) -> *mut u64 {
let index = (address >> LEVEL_SHIFTS[level]) & LEVEL_INDEX_MASKS[level];
(LEVEL_BASES[level] + index * 8) as *mut u64
}
fn sign_extend(address: usize) -> usize {
(((address << CANONICAL_SPARE_BITS) as isize) >> CANONICAL_SPARE_BITS) as usize
}
const TOP_LEVEL: usize = 0;
const TABLE_LEVEL: usize = 3;
const ENTRIES_PER_TABLE: usize = 512;
const CANONICAL_SPARE_BITS: usize = 16;
const LEVEL_SHIFTS: [usize; 4] = [39, 30, 21, 12];
const LEVEL_INDEX_MASKS: [usize; 4] = [0x1ff, 0x3_ffff, 0x7ff_ffff, 0xf_ffff_ffff];
const LEVEL_BASES: [usize; 4] = [
0xffff_f6fb_7dbe_d000,
0xffff_f6fb_7da0_0000,
0xffff_f6fb_4000_0000,
0xffff_f680_0000_0000,
];
}
pub use arch::protection_at;
use arch::{maps_small_page, reprotect_page, walk_kernel_space};
fn protection_of(entry: u64, no_execute: u64) -> u32 {
let mut prot = GUM_PAGE_READ;
if (entry & PAGE_WRITEABLE as u64) != 0 {
prot |= GUM_PAGE_WRITE;
}
if no_execute == 0 || (entry & no_execute) == 0 {
prot |= GUM_PAGE_EXECUTE;
}
prot
}
fn apply_protection(entry: u64, gum_prot: u64, writeable: u64, no_execute: u64) -> u64 {
let mut value = entry;
if (gum_prot & GUM_PAGE_WRITE as u64) != 0 {
value |= writeable;
} else {
value &= !writeable;
}
if no_execute != 0 {
if (gum_prot & GUM_PAGE_EXECUTE as u64) != 0 {
value &= !no_execute;
} else {
value |= no_execute;
}
}
value
}
fn invalidate_page(address: usize) {
unsafe {
core::arch::asm!("invlpg [{0}]", in(reg) address, options(nostack, preserves_flags));
}
}
const PAGE_SIZE: usize = 4096;
const PAGE_PRESENT: u32 = 0x1;
const PAGE_WRITEABLE: u32 = 0x2;
const PAGE_LARGE: u32 = 0x80;
const PAGE_NO_EXECUTE: u64 = 1 << 63;
pub(crate) const GUM_PAGE_READ: u32 = 0x1;
pub(crate) const GUM_PAGE_WRITE: u32 = 0x2;
pub(crate) const GUM_PAGE_EXECUTE: u32 = 0x4;
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
-309
View File
@@ -1,309 +0,0 @@
use core::ffi::c_void;
pub fn list_applications_when_the_loop_can(request_id: u16) {
unsafe {
ASKED_BY = request_id;
ASKED_FOR = true;
}
}
pub fn applications_are_asked_for() -> bool {
unsafe { ASKED_FOR }
}
pub fn asked_for_applications() -> Option<u16> {
if !unsafe { ASKED_FOR } {
return None;
}
unsafe { ASKED_FOR = false };
Some(unsafe { ASKED_BY })
}
pub fn each_application(found: &mut dyn FnMut(&[u8], &[u8], &[u8])) {
let Some(arena) = crate::xnu_injection::a_copy_that_can_ask_the_system() else {
return;
};
let put = |at: u64, value: u64| unsafe { ((arena + at) as *mut u64).write_volatile(value) };
put(crate::xnu_relay::APPS_ANSWER, crate::xnu_relay::NOTHING_WANTED);
put(crate::xnu_relay::APPS_WANTED, 1);
crate::xnu_injection::wake_the_copy_at(arena);
let answered = &mut || unsafe {
((arena + crate::xnu_relay::APPS_ANSWER) as *const u64).read_volatile()
} != crate::xnu_relay::NOTHING_WANTED;
let began = crate::xnu::absolutetime_to_nanoseconds(crate::xnu::mach_absolute_time());
while !answered() {
let now = crate::xnu::absolutetime_to_nanoseconds(crate::xnu::mach_absolute_time());
if now - began > LONG_ENOUGH_TO_LOOK {
break;
}
crate::kernel::wait(crate::glib::wakeup_token(), None, answered);
}
let said = unsafe { ((arena + crate::xnu_relay::APPS_ANSWER) as *const u64).read_volatile() };
put(crate::xnu_relay::APPS_WANTED, crate::xnu_relay::NOTHING_WANTED);
if said != crate::xnu_relay::DONE {
return;
}
let mut at = arena + crate::xnu_relay::SAYING;
let past = at + crate::xnu_relay::SAYING_ROOM;
while at < past && unsafe { (at as *const u8).read_volatile() } != 0 {
let identifier = word_at(&mut at, past);
let program = word_at(&mut at, past);
let shown = word_at(&mut at, past);
found(identifier, program, shown);
}
}
fn word_at(at: &mut u64, past: u64) -> &'static [u8] {
let from = *at;
while *at < past && unsafe { (*at as *const u8).read_volatile() } != 0 {
*at += 1;
}
let length = (*at - from) as usize;
*at += 1;
unsafe { core::slice::from_raw_parts(from as *const u8, length) }
}
static mut ASKED_FOR: bool = false;
static mut ASKED_BY: u16 = 0;
const LONG_ENOUGH_TO_LOOK: u64 = 20_000_000_000;
pub fn start_the_one_called(identifier: &[u8], runs: &mut [u8; MOST_ONE_SAYS]) -> bool {
let Some(asking) = AskingTheSystem::found() else {
return false;
};
if !asking.what_it_runs(identifier, runs) {
return false;
}
asking.start_it(identifier)
}
pub fn say_what_is_installed(arena: u64) {
let mut at = arena + crate::xnu_relay::SAYING;
let past = at + crate::xnu_relay::SAYING_ROOM - (3 * MOST_ONE_SAYS) as u64;
if let Some(asking) = AskingTheSystem::found() {
asking.each_one(&mut |identifier, program, shown| {
if at >= past {
return;
}
for piece in [identifier, program, shown] {
for byte in piece {
unsafe { (at as *mut u8).write_volatile(*byte) };
at += 1;
}
unsafe { (at as *mut u8).write_volatile(0) };
at += 1;
}
});
}
unsafe {
(at as *mut u8).write_volatile(0);
((arena + crate::xnu_relay::APPS_ANSWER) as *mut u64)
.write_volatile(crate::xnu_relay::DONE);
}
}
struct AskingTheSystem {
class_named: unsafe extern "C" fn(*const u8) -> *const c_void,
what_to_do: unsafe extern "C" fn(*const u8) -> *const c_void,
ask: unsafe extern "C" fn(*const c_void, *const c_void) -> *const c_void,
how_many: unsafe extern "C" fn(*const c_void) -> isize,
the_one_at: unsafe extern "C" fn(*const c_void, isize) -> *const c_void,
text_into: unsafe extern "C" fn(*const c_void, *mut u8, isize, i32) -> i32,
text_of: unsafe extern "C" fn(*const c_void, *const u8, i32) -> *const c_void,
let_go: unsafe extern "C" fn(*const c_void),
}
impl AskingTheSystem {
fn found() -> Option<&'static AskingTheSystem> {
let held = unsafe { (&raw const ASKING).as_ref().unwrap() };
if held.is_some() {
return held.as_ref();
}
for framework in [
c"/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation",
c"/System/Library/Frameworks/CoreServices.framework/CoreServices",
c"/System/Library/PrivateFrameworks/MobileCoreServices.framework/MobileCoreServices",
] {
open_the_framework(framework.as_ptr() as *const u8);
}
let runtime = |wanted: &[u8]| crate::xnu_libsystem::function_named(b"/libobjc.A.dylib",
wanted);
let foundation = |wanted: &[u8]| crate::xnu_libsystem::function_named(b"/CoreFoundation",
wanted);
let found = AskingTheSystem {
class_named: unsafe { core::mem::transmute(runtime(b"_objc_getClass")?) },
what_to_do: unsafe { core::mem::transmute(runtime(b"_sel_registerName")?) },
ask: unsafe { core::mem::transmute(runtime(b"_objc_msgSend")?) },
how_many: unsafe { core::mem::transmute(foundation(b"_CFArrayGetCount")?) },
the_one_at: unsafe { core::mem::transmute(foundation(b"_CFArrayGetValueAtIndex")?) },
text_into: unsafe { core::mem::transmute(foundation(b"_CFStringGetCString")?) },
text_of: unsafe { core::mem::transmute(foundation(b"_CFStringCreateWithCString")?) },
let_go: unsafe { core::mem::transmute(foundation(b"_CFRelease")?) },
};
unsafe { ASKING = Some(found) };
unsafe { (&raw const ASKING).as_ref().unwrap().as_ref() }
}
fn each_one(&self, found: &mut dyn FnMut(&[u8], &[u8], &[u8])) {
let workspace = self.tell(self.class(b"LSApplicationWorkspace\0"), b"defaultWorkspace\0");
if workspace.is_null() {
return;
}
let all = self.tell(workspace, b"allApplications\0");
if all.is_null() {
return;
}
for step in 0..unsafe { (self.how_many)(all) } {
let one = unsafe { (self.the_one_at)(all, step) };
let mut identifier = [0u8; MOST_ONE_SAYS];
let mut program = [0u8; MOST_ONE_SAYS];
let mut shown = [0u8; MOST_ONE_SAYS];
if !self.text(self.tell(one, b"applicationIdentifier\0"), &mut identifier) {
continue;
}
self.text(self.tell(one, b"canonicalExecutablePath\0"), &mut program);
self.text(self.tell(one, b"localizedName\0"), &mut shown);
found(&identifier[..length_of(&identifier)], &program[..length_of(&program)],
&shown[..length_of(&shown)]);
}
}
fn what_it_runs(&self, identifier: &[u8], runs: &mut [u8; MOST_ONE_SAYS]) -> bool {
let mut found = false;
self.each_one(&mut |named, program, _shown| {
if found || named != identifier {
return;
}
let from = program.iter().rposition(|byte| *byte == b'/').map_or(0, |at| at + 1);
let name = &program[from..];
runs[..name.len().min(MOST_ONE_SAYS)]
.copy_from_slice(&name[..name.len().min(MOST_ONE_SAYS)]);
found = true;
});
found
}
fn start_it(&self, identifier: &[u8]) -> bool {
let Some(start) = what_starts_applications() else {
return false;
};
type Start = unsafe extern "C" fn(*const c_void, *const c_void, i32) -> u32;
let start: Start = unsafe { core::mem::transmute(start) };
let named = unsafe {
(self.text_of)(core::ptr::null(), identifier.as_ptr(), WHAT_TEXT_IS_KEPT_AS)
};
if named.is_null() {
return false;
}
let asked = self.asking_it_to_unlock_first();
let went = unsafe { start(named, asked, NOT_HELD_BY_THE_SYSTEM) };
unsafe { (self.let_go)(named) };
if !asked.is_null() {
unsafe { (self.let_go)(asked) };
}
went == WENT_WELL
}
fn asking_it_to_unlock_first(&self) -> *const c_void {
let (Some(unlock), Some(yes), Some(dictionary_of), Some(keys), Some(values)) = (
crate::xnu_libsystem::function_named(b"/SpringBoardServices",
b"_SBSApplicationLaunchOptionUnlockDeviceKey"),
crate::xnu_libsystem::function_named(b"/CoreFoundation", b"_kCFBooleanTrue"),
crate::xnu_libsystem::function_named(b"/CoreFoundation", b"_CFDictionaryCreate"),
crate::xnu_libsystem::function_named(b"/CoreFoundation",
b"_kCFTypeDictionaryKeyCallBacks"),
crate::xnu_libsystem::function_named(b"/CoreFoundation",
b"_kCFTypeDictionaryValueCallBacks"),
) else {
return core::ptr::null();
};
type DictionaryOf = unsafe extern "C" fn(*const c_void, *const *const c_void,
*const *const c_void, isize, *const c_void, *const c_void) -> *const c_void;
let dictionary_of: DictionaryOf = unsafe { core::mem::transmute(dictionary_of) };
let key = [unsafe { (unlock as *const *const c_void).read() }];
let value = [unsafe { (yes as *const *const c_void).read() }];
unsafe {
dictionary_of(core::ptr::null(), key.as_ptr(), value.as_ptr(), 1,
keys as *const c_void, values as *const c_void)
}
}
fn class(&self, named: &[u8]) -> *const c_void {
unsafe { (self.class_named)(named.as_ptr()) }
}
fn tell(&self, what: *const c_void, to_do: &[u8]) -> *const c_void {
if what.is_null() {
return core::ptr::null();
}
unsafe { (self.ask)(what, (self.what_to_do)(to_do.as_ptr())) }
}
fn text(&self, said: *const c_void, into: &mut [u8; MOST_ONE_SAYS]) -> bool {
if said.is_null() {
return false;
}
unsafe {
(self.text_into)(said, into.as_mut_ptr(), into.len() as isize, WHAT_TEXT_IS_KEPT_AS) != 0
}
}
}
fn what_starts_applications() -> Option<u64> {
open_the_framework(
c"/System/Library/PrivateFrameworks/SpringBoardServices.framework/SpringBoardServices"
.as_ptr() as *const u8);
crate::xnu_libsystem::function_named(b"/SpringBoardServices",
b"_SBSLaunchApplicationWithIdentifierAndLaunchOptions")
}
fn open_the_framework(path: *const u8) {
let Some(open) = crate::xnu_libsystem::function_named(b"/libdyld.dylib", b"_dlopen") else {
return;
};
type Open = unsafe extern "C" fn(*const u8, i32) -> *mut c_void;
let open: Open = unsafe { core::mem::transmute(open) };
unsafe { open(path, WHEN_IT_IS_ASKED_FOR) };
}
fn length_of(said: &[u8]) -> usize {
said.iter().position(|byte| *byte == 0).unwrap_or(said.len())
}
static mut ASKING: Option<AskingTheSystem> = None;
const WHEN_IT_IS_ASKED_FOR: i32 = 2;
const WHAT_TEXT_IS_KEPT_AS: i32 = 0x0800_0100;
const MOST_ONE_SAYS: usize = 256;
const NOT_HELD_BY_THE_SYSTEM: i32 = 0;
const WENT_WELL: u32 = 0;
-127
View File
@@ -1,127 +0,0 @@
use core::ffi::{c_int, c_void};
pub fn hang_the_bell() {
if unsafe { HUNG } {
return;
}
unsafe { HUNG = true };
let calls = unsafe { _sysent };
if calls == 0 {
return;
}
let signed = unsafe {
((calls + ENDING_A_WAIT * AN_ENTRY) as *const u64).read_volatile()
};
let ending_a_wait = unsafe { crate::pac::ptrauth_strip_data(signed as *const u8) };
unsafe { WHERE_IT_HANGS = ending_a_wait as *mut c_void };
unsafe {
let interceptor = crate::bindings::gum_interceptor_obtain();
crate::bindings::gum_interceptor_begin_transaction(interceptor);
crate::bindings::gum_interceptor_replace(interceptor, ending_a_wait as *mut c_void,
answer_the_bell as *mut c_void, (&raw mut ENDS_A_WAIT) as *mut *mut c_void,
core::ptr::null_mut());
crate::bindings::gum_interceptor_end_transaction(interceptor);
}
}
unsafe extern "C" fn answer_the_bell(process: *mut c_void, asked: *mut c_void,
answer: *mut i32) -> c_int
{
if unsafe { (*(asked as *const Asked)).value } == WHAT_ONLY_A_COPY_SAYS {
RUNG.fetch_add(1, core::sync::atomic::Ordering::Relaxed);
if let Some(this_thread) = unsafe { _current_thread } {
crate::xnu_hiding::a_thread_of_ours(unsafe { this_thread() });
}
crate::kernel::wake(crate::glib::wakeup_token());
return 0;
}
match unsafe { ENDS_A_WAIT } {
Some(end_it) => unsafe { end_it(process, asked, answer) },
None => 0,
}
}
pub fn take_the_bell_down() {
if !unsafe { HUNG } {
return;
}
unsafe { HUNG = false };
let hangs_on = unsafe { WHERE_IT_HANGS };
if hangs_on.is_null() {
return;
}
unsafe {
let interceptor = crate::bindings::gum_interceptor_obtain();
crate::bindings::gum_interceptor_begin_transaction(interceptor);
crate::bindings::gum_interceptor_revert(interceptor, hangs_on);
crate::bindings::gum_interceptor_end_transaction(interceptor);
ENDS_A_WAIT = None;
WHERE_IT_HANGS = core::ptr::null_mut();
}
}
pub fn ring_the_bell() {
let arena = crate::xnu_user::arena();
if arena == 0 {
return;
}
unsafe {
crate::xnu_user_calls::ask(ENDING_A_WAIT as i64, [(WAITING_ON_AN_ADDRESS | EVERY_WAITER)
as u64, arena + crate::xnu_relay::BELL_WORD, WHAT_ONLY_A_COPY_SAYS, 0])
};
}
pub fn wake_the_copy(process: *mut c_void, ours: u64, theirs: u64) {
let Some(end_the_wait) = (unsafe { _ulock_wake }) else {
return;
};
unsafe {
let word = (ours + crate::xnu_relay::WAKE_WORD) as *mut u32;
word.write_volatile(word.read_volatile().wrapping_add(1));
}
let mut asked = Asked { how: WAITING_ON_AN_ADDRESS | EVERY_WAITER, _padding: 0,
address: theirs + crate::xnu_relay::WAKE_WORD, value: 0 };
let mut answer = 0;
unsafe { end_the_wait(process, &mut asked as *mut Asked as *mut c_void, &mut answer) };
}
#[repr(C)]
struct Asked {
how: u32,
_padding: u32,
address: u64,
value: u64,
}
type EndsAWait = unsafe extern "C" fn(*mut c_void, *mut c_void, *mut i32) -> c_int;
static mut HUNG: bool = false;
static RUNG: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0);
static mut ENDS_A_WAIT: Option<EndsAWait> = None;
static mut WHERE_IT_HANGS: *mut c_void = core::ptr::null_mut();
const ENDING_A_WAIT: usize = 516;
const AN_ENTRY: usize = 24;
const WHAT_ONLY_A_COPY_SAYS: u64 = 0x6672_6964_6100_0002;
const WAITING_ON_AN_ADDRESS: u32 = 1;
const EVERY_WAITER: u32 = 0x100;
unsafe extern "C" {
static _current_thread: Option<unsafe extern "C" fn() -> *mut c_void>;
pub static _sysent: usize;
static _ulock_wake: Option<unsafe extern "C" fn(*mut c_void, *mut c_void, *mut i32) -> c_int>;
}
-720
View File
@@ -1,720 +0,0 @@
use core::ffi::c_int;
use core::ffi::c_void;
pub fn hide_our_threads() {
hide_us_from_the_thread_list();
say_no_port_names_one_of_ours();
say_less_of_what_a_task_holds();
hide_what_we_have_in_a_process();
if unsafe { HIDDEN } {
return;
}
unsafe { HIDDEN = true };
let calls = unsafe { crate::xnu_bell::_sysent };
if calls == 0 {
return;
}
let signed = unsafe {
((calls + ASKING_ABOUT_A_PROCESS * AN_ENTRY) as *const u64).read_volatile()
};
let asking = unsafe { crate::pac::ptrauth_strip_data(signed as *const u8) };
unsafe {
WHERE_IT_IS_ASKED = asking as *mut c_void;
let interceptor = crate::bindings::gum_interceptor_obtain();
crate::bindings::gum_interceptor_begin_transaction(interceptor);
crate::bindings::gum_interceptor_replace(interceptor, asking as *mut c_void,
say_what_it_has as *mut c_void, (&raw mut THE_ANSWER) as *mut *mut c_void,
core::ptr::null_mut());
crate::bindings::gum_interceptor_end_transaction(interceptor);
}
}
pub fn show_our_threads_again() {
if !unsafe { HIDDEN } {
return;
}
unsafe { HIDDEN = false };
unsafe {
let interceptor = crate::bindings::gum_interceptor_obtain();
crate::bindings::gum_interceptor_begin_transaction(interceptor);
if !WHERE_IT_IS_ASKED.is_null() {
crate::bindings::gum_interceptor_revert(interceptor, WHERE_IT_IS_ASKED);
}
if !WHERE_THE_LIST_IS_ASKED.is_null() {
crate::bindings::gum_interceptor_revert(interceptor, WHERE_THE_LIST_IS_ASKED);
}
if !WHERE_A_RANGE_IS_ASKED.is_null() {
crate::bindings::gum_interceptor_revert(interceptor, WHERE_A_RANGE_IS_ASKED);
}
if !WHERE_A_DEEPER_RANGE_IS_ASKED.is_null() {
crate::bindings::gum_interceptor_revert(interceptor, WHERE_A_DEEPER_RANGE_IS_ASKED);
}
crate::bindings::gum_interceptor_end_transaction(interceptor);
THE_ANSWER = None;
THE_LIST = None;
THE_RANGE = None;
THE_DEEPER_RANGE = None;
WHERE_A_RANGE_IS_ASKED = core::ptr::null_mut();
WHERE_A_DEEPER_RANGE_IS_ASKED = core::ptr::null_mut();
HIDDEN_FROM_THE_MAP = false;
WHERE_IT_IS_ASKED = core::ptr::null_mut();
WHERE_THE_LIST_IS_ASKED = core::ptr::null_mut();
HIDDEN_FROM_THE_LIST = false;
}
}
fn hide_us_from_the_thread_list() {
if unsafe { HIDDEN_FROM_THE_LIST } {
return;
}
unsafe { HIDDEN_FROM_THE_LIST = true };
let Some(asking) = (unsafe { _task_threads }) else {
return;
};
unsafe {
WHERE_THE_LIST_IS_ASKED = asking as *mut c_void;
let interceptor = crate::bindings::gum_interceptor_obtain();
crate::bindings::gum_interceptor_begin_transaction(interceptor);
crate::bindings::gum_interceptor_replace(interceptor, asking as *mut c_void,
say_which_threads as *mut c_void, (&raw mut THE_LIST) as *mut *mut c_void,
core::ptr::null_mut());
crate::bindings::gum_interceptor_end_transaction(interceptor);
}
}
fn say_less_of_what_a_task_holds() {
if unsafe { LESS_IS_SAID } {
return;
}
unsafe { LESS_IS_SAID = true };
let Some(asking) = (unsafe { _task_info }) else {
return;
};
unsafe {
let interceptor = crate::bindings::gum_interceptor_obtain();
crate::bindings::gum_interceptor_begin_transaction(interceptor);
crate::bindings::gum_interceptor_replace(interceptor, asking as *mut c_void,
say_what_the_task_holds as *mut c_void, (&raw mut WHAT_IT_HOLDS) as *mut *mut c_void,
core::ptr::null_mut());
crate::bindings::gum_interceptor_end_transaction(interceptor);
}
}
unsafe extern "C" fn say_what_the_task_holds(port: *mut c_void, what: u32, said: *mut u8,
how_much: *mut u32) -> c_int
{
let went = match unsafe { WHAT_IT_HOLDS } {
Some(ask_it) => unsafe { ask_it(port, what, said, how_much) },
None => 0,
};
if went != 0 || said.is_null() || asked_by_one_of_ours() {
return went;
}
let (Some(id), room) = (crate::xnu_injection::whose_task_does_this_port_name(port),
unsafe { *how_much } as usize * 4)
else {
return went;
};
let (mut ours_of_it, mut ranges) = (0u64, 0u32);
crate::xnu_injection::what_we_have_in_process(id, &mut |_, size| {
ours_of_it += size;
ranges += 1;
});
if ours_of_it == 0 {
return went;
}
let take_from = |at: usize, less: u64| {
if at + 8 > room {
return;
}
let word = unsafe { (said.add(at) as *const u64).read_unaligned() };
unsafe { (said.add(at) as *mut u64).write_unaligned(word.saturating_sub(less)) };
};
let take_from_a_count = |at: usize, less: u32| {
if at + 4 > room {
return;
}
let count = unsafe { (said.add(at) as *const u32).read_unaligned() };
unsafe { (said.add(at) as *mut u32).write_unaligned(count.saturating_sub(less)) };
};
match what {
HOW_THE_TASK_IS => {
take_from(WHAT_A_TASK_HAS_MAPPED, ours_of_it);
take_from(WHAT_OF_IT_IS_THERE, ours_of_it);
}
WHAT_THE_TASK_HAS_MAPPED | WHAT_THE_TASK_HAS_MAPPED_AND_MAY_LOSE => {
take_from(WHAT_A_MAP_HAS_IN_IT, ours_of_it);
take_from_a_count(HOW_MANY_RANGES_IT_TOOK, ranges);
take_from(WHAT_OF_A_MAP_IS_THERE, ours_of_it);
}
_ => {}
}
went
}
static mut LESS_IS_SAID: bool = false;
static mut WHAT_IT_HOLDS:
Option<unsafe extern "C" fn(*mut c_void, u32, *mut u8, *mut u32) -> c_int> = None;
const HOW_THE_TASK_IS: u32 = 5;
const WHAT_A_TASK_HAS_MAPPED: usize = 8;
const WHAT_OF_IT_IS_THERE: usize = 16;
const WHAT_THE_TASK_HAS_MAPPED: u32 = 22;
const WHAT_THE_TASK_HAS_MAPPED_AND_MAY_LOSE: u32 = 23;
const WHAT_A_MAP_HAS_IN_IT: usize = 0;
const HOW_MANY_RANGES_IT_TOOK: usize = 8;
const WHAT_OF_A_MAP_IS_THERE: usize = 16;
fn say_no_port_names_one_of_ours() {
if unsafe { NOTHING_IS_NAMED } {
return;
}
unsafe { NOTHING_IS_NAMED = true };
let turning = [
(unsafe { _convert_port_to_thread }, (&raw mut A_THREAD) as *mut *mut c_void,
say_it_names_nothing as *mut c_void),
(unsafe { _convert_port_to_thread_read }, (&raw mut A_THREAD_TO_READ) as *mut *mut c_void,
say_it_names_nothing_to_read as *mut c_void),
(unsafe { _convert_port_to_thread_inspect },
(&raw mut A_THREAD_TO_LOOK_AT) as *mut *mut c_void,
say_it_names_nothing_to_look_at as *mut c_void),
];
let interceptor = unsafe { crate::bindings::gum_interceptor_obtain() };
unsafe { crate::bindings::gum_interceptor_begin_transaction(interceptor) };
for (asking, held, ours) in turning {
let Some(asking) = asking else {
continue;
};
unsafe {
crate::bindings::gum_interceptor_replace(interceptor, asking as *mut c_void, ours,
held, core::ptr::null_mut());
}
}
unsafe { crate::bindings::gum_interceptor_end_transaction(interceptor) };
}
unsafe extern "C" fn say_it_names_nothing(port: *mut c_void) -> *mut c_void {
turn_it_into_a_thread(port, unsafe { A_THREAD })
}
unsafe extern "C" fn say_it_names_nothing_to_read(port: *mut c_void) -> *mut c_void {
turn_it_into_a_thread(port, unsafe { A_THREAD_TO_READ })
}
unsafe extern "C" fn say_it_names_nothing_to_look_at(port: *mut c_void) -> *mut c_void {
turn_it_into_a_thread(port, unsafe { A_THREAD_TO_LOOK_AT })
}
fn turn_it_into_a_thread(port: *mut c_void, turning: Option<TurnAPortIntoAThread>)
-> *mut c_void
{
if !asked_by_one_of_ours() && names_one_of_our_threads(port as u64) {
return core::ptr::null_mut();
}
match turning {
Some(turn_it) => unsafe { turn_it(port) },
None => core::ptr::null_mut(),
}
}
fn names_one_of_our_threads(port: u64) -> bool {
if !looks_like_the_kernel(port) {
return false;
}
let at = unsafe { WHERE_A_PORT_HOLDS_ITS_THREAD };
if at != 0 {
return ours_is_held_at(port, at);
}
for word in 1..HOW_FAR_INTO_A_PORT {
if ours_is_held_at(port, word * 8) {
unsafe { WHERE_A_PORT_HOLDS_ITS_THREAD = word * 8 };
return true;
}
}
false
}
static mut WHERE_A_PORT_HOLDS_ITS_THREAD: usize = 0;
type TurnAPortIntoAThread = unsafe extern "C" fn(*mut c_void) -> *mut c_void;
static mut NOTHING_IS_NAMED: bool = false;
static mut A_THREAD: Option<TurnAPortIntoAThread> = None;
static mut A_THREAD_TO_READ: Option<TurnAPortIntoAThread> = None;
static mut A_THREAD_TO_LOOK_AT: Option<TurnAPortIntoAThread> = None;
unsafe extern "C" fn say_which_threads(task: *mut c_void, threads: *mut *mut u64,
how_many: *mut u32) -> c_int
{
let went = match unsafe { THE_LIST } {
Some(ask_it) => unsafe { ask_it(task, threads, how_many) },
None => 0,
};
if went != 0 {
return went;
}
let only_counting = asked_by_one_of_ours();
let said = unsafe { *threads };
let count = unsafe { *how_many } as usize;
if said.is_null() || count == 0 {
return went;
}
let mut kept = 0;
for step in 0..count {
let signed = unsafe { said.add(step).read() };
if is_one_of_our_threads(signed) {
continue;
}
if !only_counting {
unsafe { said.add(kept).write(signed) };
}
kept += 1;
}
if only_counting {
return went;
}
for step in kept..count {
unsafe { said.add(step).write(0) };
}
unsafe { *how_many = kept as u32 };
went
}
fn is_one_of_our_threads(signed: u64) -> bool {
let held = unsafe { crate::pac::ptrauth_strip_pointer(signed as *const u8) } as u64;
if !looks_like_the_kernel(held) {
return false;
}
if our_threads().contains(&held) {
return true;
}
let at = unsafe { WHERE_A_PORT_SAYS_ITS_THREAD };
if at != 0 {
return ours_is_held_at(held, at);
}
for word in 1..HOW_FAR_INTO_A_PORT {
if ours_is_held_at(held, word * 8) {
unsafe { WHERE_A_PORT_SAYS_ITS_THREAD = word * 8 };
return true;
}
}
false
}
fn ours_is_held_at(port: u64, at: usize) -> bool {
let Some(held) = a_word_of(port, at) else {
return false;
};
let held = unsafe { crate::pac::ptrauth_strip_pointer(held as *const u8) } as u64;
looks_like_the_kernel(held) && our_threads().contains(&held)
}
pub fn a_word_of(at: u64, step: usize) -> Option<u64> {
if step + 8 > room_in_the_page(at) {
return None;
}
Some(unsafe { ((at as usize + step) as *const u64).read_volatile() })
}
pub fn room_in_the_page(at: u64) -> usize {
A_PAGE - (at as usize & (A_PAGE - 1))
}
const A_PAGE: usize = 0x1000;
fn looks_like_the_kernel(at: u64) -> bool {
at >= WHERE_THE_KERNEL_BEGINS && (at & 7) == 0
}
pub fn a_thread_of_ours(thread: *mut c_void) {
let thread = thread as u64;
if thread == 0 || our_threads().contains(&thread) {
return;
}
if let Some(free) = our_threads().iter_mut().find(|kept| **kept == 0) {
*free = thread;
}
}
pub fn no_longer_a_thread_of_ours(thread: *mut c_void) {
for kept in our_threads().iter_mut().filter(|kept| **kept == thread as u64) {
*kept = 0;
}
}
fn our_threads() -> &'static mut [u64; MOST_OF_OURS] {
unsafe { (&raw mut OUR_THREADS).as_mut().unwrap() }
}
fn asked_by_one_of_ours() -> bool {
let Some(this_thread) = (unsafe { _current_thread }) else {
return false;
};
our_threads().contains(&(unsafe { this_thread() } as u64))
}
fn hide_what_we_have_in_a_process() {
if unsafe { HIDDEN_FROM_THE_MAP } {
return;
}
unsafe { HIDDEN_FROM_THE_MAP = true };
let (Some(one_range), Some(one_range_deeper)) = (unsafe { _mach_vm_region },
unsafe { _mach_vm_region_recurse })
else {
return;
};
unsafe {
WHERE_A_RANGE_IS_ASKED = one_range as *mut c_void;
WHERE_A_DEEPER_RANGE_IS_ASKED = one_range_deeper as *mut c_void;
let interceptor = crate::bindings::gum_interceptor_obtain();
crate::bindings::gum_interceptor_begin_transaction(interceptor);
crate::bindings::gum_interceptor_replace(interceptor, one_range as *mut c_void,
say_what_is_there as *mut c_void, (&raw mut THE_RANGE) as *mut *mut c_void,
core::ptr::null_mut());
crate::bindings::gum_interceptor_replace(interceptor, one_range_deeper as *mut c_void,
say_what_is_there_deeper as *mut c_void, (&raw mut THE_DEEPER_RANGE) as *mut *mut c_void,
core::ptr::null_mut());
crate::bindings::gum_interceptor_end_transaction(interceptor);
}
}
unsafe extern "C" fn say_what_is_there(map: *mut c_void, address: *mut u64, size: *mut u64,
flavour: c_int, into: *mut c_void, count: *mut u32, named: *mut u32) -> c_int
{
let Some(ask_it) = (unsafe { THE_RANGE }) else {
return 0;
};
let mut went = unsafe { ask_it(map, address, size, flavour, into, count, named) };
if asked_by_one_of_ours() {
return went;
}
for _ in 0..HOW_MANY_OF_OURS_IN_A_ROW {
if went != 0 {
break;
}
let Some(past) = ours_is_there(map, unsafe { *address }, unsafe { *size }) else {
break;
};
unsafe { *address = past };
went = unsafe { ask_it(map, address, size, flavour, into, count, named) };
}
went
}
unsafe extern "C" fn say_what_is_there_deeper(map: *mut c_void, address: *mut u64, size: *mut u64,
depth: *mut u32, into: *mut c_void, count: *mut u32) -> c_int
{
let Some(ask_it) = (unsafe { THE_DEEPER_RANGE }) else {
return 0;
};
let mut went = unsafe { ask_it(map, address, size, depth, into, count) };
if asked_by_one_of_ours() {
return went;
}
for _ in 0..HOW_MANY_OF_OURS_IN_A_ROW {
if went != 0 {
break;
}
let Some(past) = ours_is_there(map, unsafe { *address }, unsafe { *size }) else {
break;
};
unsafe { *address = past };
went = unsafe { ask_it(map, address, size, depth, into, count) };
}
went
}
fn ours_is_there(map: *mut c_void, address: u64, size: u64) -> Option<u64> {
let mut past = None;
crate::xnu_injection::what_we_have_in(map, &mut |at, how_much| {
if past.is_none() && address < at + how_much && at < address + size {
past = Some(at + how_much);
}
});
past
}
pub fn one_of_ours(number: u64) {
if number == 0 || ours().contains(&number) {
return;
}
if let Some(free) = ours().iter_mut().find(|kept| **kept == 0) {
*free = number;
}
}
pub fn forget_the_threads_of(arena: u64) {
let said = unsafe { ((arena + crate::xnu_relay::OUR_THREADS) as *const u64) };
for step in 0..MOST_OF_OURS_IN_ONE {
let number = unsafe { said.add(step).read_volatile() };
for kept in ours().iter_mut().filter(|kept| **kept == number && number != 0) {
*kept = 0;
}
}
}
pub fn take_note_of_what_a_copy_says(arena: u64) {
let said = unsafe { ((arena + crate::xnu_relay::OUR_THREADS) as *const u64) };
for step in 0..MOST_OF_OURS_IN_ONE {
one_of_ours(unsafe { said.add(step).read_volatile() });
}
}
fn take_ours_out_of_the_count(asked: &Asked, went: c_int) -> c_int {
let ours_there = crate::xnu_injection::how_many_of_ours_are_in(asked.id as u32);
let mut ours_of_it = 0u64;
crate::xnu_injection::what_we_have_in_process(asked.id as u32, &mut |_, room| {
ours_of_it += room;
});
if ours_there == 0 || asked.into == 0 {
return went;
}
let (Some(fetch), Some(put_back)) = (unsafe { _copyin }, unsafe { _copyout }) else {
return went;
};
let mut said = [0u8; HOW_THE_TASK_IS_SAID_TO_BE_DOING];
let room = said.len() as u64;
if unsafe { fetch(asked.into, said.as_mut_ptr() as *mut c_void, room) } != 0 {
return went;
}
let counted = u32::from_le_bytes(said[HOW_MANY_THREADS..HOW_MANY_THREADS + 4]
.try_into().unwrap());
said[HOW_MANY_THREADS..HOW_MANY_THREADS + 4]
.copy_from_slice(&counted.saturating_sub(ours_there).to_le_bytes());
for at in [HOW_MUCH_IS_MAPPED, HOW_MUCH_OF_IT_IS_THERE] {
let said_to_be = u64::from_le_bytes(said[at..at + 8].try_into().unwrap());
said[at..at + 8].copy_from_slice(&said_to_be.saturating_sub(ours_of_it).to_le_bytes());
}
unsafe { put_back(said.as_ptr() as *const c_void, asked.into, room) };
went
}
fn say_what_the_process_has_there(process: *mut c_void, asked: &mut Asked, answer: *mut i32,
went: c_int) -> c_int
{
let Some(ask_it) = (unsafe { THE_ANSWER }) else {
return went;
};
let Some(fetch) = (unsafe { _copyin }) else {
return went;
};
if asked.into == 0 {
return went;
}
let mut answered = went;
for _ in 0..MOST_RANGES_OF_OURS_IN_A_ROW {
if answered != 0 {
return answered;
}
let mut said = [0u8; WHAT_A_REGION_IS_SAID_TO_BE];
if unsafe { fetch(asked.into, said.as_mut_ptr() as *mut c_void, said.len() as u64) } != 0 {
return answered;
}
let word = |at: usize| u64::from_le_bytes(said[at..at + 8].try_into().unwrap());
let (there, size) = (word(WHERE_A_REGION_IS), word(HOW_BIG_A_REGION_IS));
let mut is_ours = false;
crate::xnu_injection::what_we_have_in_process(asked.id as u32, &mut |at, room| {
is_ours |= there >= at && there < at + room;
});
if !is_ours {
return answered;
}
asked.about = there + size;
answered = unsafe { ask_it(process, asked as *mut Asked as *mut c_void, answer) };
}
answered
}
unsafe extern "C" fn say_what_it_has(process: *mut c_void, asked: *mut c_void, answer: *mut i32)
-> c_int
{
let went = match unsafe { THE_ANSWER } {
Some(ask_it) => unsafe { ask_it(process, asked, answer) },
None => 0,
};
if asked.is_null() || answer.is_null() {
return went;
}
let asked = unsafe { &mut *(asked as *mut Asked) };
if went != 0 || asked_by_one_of_ours() {
return went;
}
if asked.what == HOW_THE_TASK_IS_DOING {
return take_ours_out_of_the_count(asked, went);
}
if asked.what == A_REGION || asked.what == A_REGION_AND_WHERE_IT_CAME_FROM {
return say_what_the_process_has_there(process, asked, answer, went);
}
if asked.what != LISTING_ITS_THREADS {
return went;
}
let said = unsafe { *answer } as usize;
let how_many = said / core::mem::size_of::<u64>();
if how_many == 0 || how_many > MOST_THREADS_IN_ONE || asked.into == 0 {
return went;
}
let (Some(fetch), Some(put_back)) = (unsafe { _copyin }, unsafe { _copyout }) else {
return went;
};
let mut numbers = [0u64; MOST_THREADS_IN_ONE];
let room = (how_many * core::mem::size_of::<u64>()) as u64;
if unsafe { fetch(asked.into, numbers.as_mut_ptr() as *mut c_void, room) } != 0 {
return went;
}
let mut kept = 0;
for step in 0..how_many {
if ours().contains(&numbers[step]) {
continue;
}
numbers[kept] = numbers[step];
kept += 1;
}
if kept == how_many {
return went;
}
let left = (kept * core::mem::size_of::<u64>()) as u64;
if unsafe { put_back(numbers.as_ptr() as *const c_void, asked.into, left) } != 0 {
return went;
}
unsafe { *answer = left as i32 };
went
}
#[repr(C)]
struct Asked {
which: u64,
id: u64,
what: u64,
about: u64,
into: u64,
room: u64,
}
fn ours() -> &'static mut [u64; MOST_OF_OURS] {
unsafe { (&raw mut OURS).as_mut().unwrap() }
}
static mut HIDDEN: bool = false;
static mut HIDDEN_FROM_THE_LIST: bool = false;
static mut HIDDEN_FROM_THE_MAP: bool = false;
static mut THE_RANGE: Option<unsafe extern "C" fn(*mut c_void, *mut u64, *mut u64, c_int,
*mut c_void, *mut u32, *mut u32) -> c_int> = None;
static mut THE_DEEPER_RANGE: Option<unsafe extern "C" fn(*mut c_void, *mut u64, *mut u64,
*mut u32, *mut c_void, *mut u32) -> c_int> = None;
static mut WHERE_A_RANGE_IS_ASKED: *mut c_void = core::ptr::null_mut();
static mut WHERE_A_DEEPER_RANGE_IS_ASKED: *mut c_void = core::ptr::null_mut();
static mut THE_LIST: Option<unsafe extern "C" fn(*mut c_void, *mut *mut u64, *mut u32) -> c_int> =
None;
static mut WHERE_THE_LIST_IS_ASKED: *mut c_void = core::ptr::null_mut();
static mut WHERE_A_PORT_SAYS_ITS_THREAD: usize = 0;
static mut OUR_THREADS: [u64; MOST_OF_OURS] = [0; MOST_OF_OURS];
static mut THE_ANSWER: Option<unsafe extern "C" fn(*mut c_void, *mut c_void, *mut i32) -> c_int> =
None;
static mut WHERE_IT_IS_ASKED: *mut c_void = core::ptr::null_mut();
static mut OURS: [u64; MOST_OF_OURS] = [0; MOST_OF_OURS];
pub const MOST_OF_OURS_IN_ONE: usize = 8;
const MOST_OF_OURS: usize = 128;
const HOW_FAR_INTO_A_PORT: usize = 32;
const WHERE_THE_KERNEL_BEGINS: u64 = 0xffff_fe00_0000_0000;
const MOST_THREADS_IN_ONE: usize = 64;
const HOW_MANY_OF_OURS_IN_A_ROW: usize = 8;
const ASKING_ABOUT_A_PROCESS: usize = 336;
const AN_ENTRY: usize = 24;
const LISTING_ITS_THREADS: u64 = 6;
const HOW_THE_TASK_IS_DOING: u64 = 4;
const A_REGION: u64 = 7;
const A_REGION_AND_WHERE_IT_CAME_FROM: u64 = 8;
const HOW_THE_TASK_IS_SAID_TO_BE_DOING: usize = 96;
const HOW_MANY_THREADS: usize = 84;
const HOW_MUCH_IS_MAPPED: usize = 0;
const HOW_MUCH_OF_IT_IS_THERE: usize = 8;
const WHAT_A_REGION_IS_SAID_TO_BE: usize = 96;
const WHERE_A_REGION_IS: usize = 80;
const HOW_BIG_A_REGION_IS: usize = 88;
const MOST_RANGES_OF_OURS_IN_A_ROW: usize = 32;
unsafe extern "C" {
static _mach_vm_region: Option<unsafe extern "C" fn(*mut c_void, *mut u64, *mut u64, c_int,
*mut c_void, *mut u32, *mut u32) -> c_int>;
static _mach_vm_region_recurse: Option<unsafe extern "C" fn(*mut c_void, *mut u64, *mut u64,
*mut u32, *mut c_void, *mut u32) -> c_int>;
static _copyin: Option<unsafe extern "C" fn(u64, *mut c_void, u64) -> c_int>;
static _copyout: Option<unsafe extern "C" fn(*const c_void, u64, u64) -> c_int>;
static _current_thread: Option<unsafe extern "C" fn() -> *mut c_void>;
static _task_info: Option<unsafe extern "C" fn(*mut c_void, u32, *mut u8, *mut u32) -> c_int>;
static _convert_port_to_thread: Option<TurnAPortIntoAThread>;
static _convert_port_to_thread_read: Option<TurnAPortIntoAThread>;
static _convert_port_to_thread_inspect: Option<TurnAPortIntoAThread>;
static _task_threads:
Option<unsafe extern "C" fn(*mut c_void, *mut *mut u64, *mut u32) -> c_int>;
}
-850
View File
@@ -1,850 +0,0 @@
use core::ffi::{c_int, c_void};
pub fn inject_into_process(id: u32) -> u32 {
crate::xnu_bell::hang_the_bell();
crate::xnu_hiding::hide_our_threads();
let Some(process) = process_with_id(id) else {
return 0;
};
let placed = give_the_copy_a_home(id, process.map);
unsafe { release_process(process.handle) };
let Some(home) = placed else {
return 0;
};
let arena_here = home.arena_here;
let seen_from_here = home.image_here;
let before = look_at_the_task(process.task);
let Some(bare_thread) = start(process.task, &home) else {
return 0;
};
learn_where_the_threads_are_counted(&before, &look_at_the_task(process.task));
crate::xnu_hiding::a_thread_of_ours(bare_thread);
if !woke_up(id, arena_here) {
return 0;
}
unsafe { arenas() }.insert(id, Placed {
map: process.map,
task: process.task,
arena: arena_here,
image_here: seen_from_here,
seen_size: home.seen_size,
code: home.image,
in_the_process: home.arena,
stack: home.stack,
bare_thread,
});
id
}
pub fn a_copy_that_can_start_a_program() -> Option<u64> {
a_copy_in(THE_ONE_THAT_STARTS_THINGS)
}
pub fn a_copy_that_can_ask_the_system() -> Option<u64> {
let mut asked = unsafe { ASKED };
if asked == 0 || !process_is_alive(asked) {
asked = the_one_that_shows_applications();
unsafe { ASKED = asked };
}
a_copy_in(asked)
}
fn the_one_that_shows_applications() -> u32 {
let mut found = 0;
for wanted in [b"backboardd".as_slice(), b"SpringBoard"] {
crate::xnu_processes::enumerate_processes(&mut |process| {
if found == 0 && called(process.name, wanted) {
found = process.id;
}
});
if found != 0 {
return found;
}
}
found
}
fn called(name: *const u8, wanted: &[u8]) -> bool {
for (step, byte) in wanted.iter().enumerate() {
if unsafe { name.add(step).read() } != *byte {
return false;
}
}
unsafe { name.add(wanted.len()).read() == 0 }
}
fn a_copy_in(id: u32) -> Option<u64> {
if let Some(arena) = arena_for_pid(id) {
return Some(arena);
}
inject_into_process(id);
arena_for_pid(id)
}
static mut ASKED: u32 = 0;
const THE_ONE_THAT_STARTS_THINGS: u32 = 1;
pub fn stop_copies() {
let everywhere: alloc::vec::Vec<u32> = unsafe { arenas() }.keys().copied().collect();
for id in everywhere {
detach_from_process(id);
}
}
pub fn detach_from_process(id: u32) -> bool {
let Some(placed) = unsafe { arenas() }.remove(&id) else {
return false;
};
tell_it_to_go(id, &placed);
take_back_what_it_was_given(id, &placed);
crate::xnu_relay::forget(placed.arena);
true
}
fn tell_it_to_go(id: u32, placed: &Placed) {
if !process_is_alive(id) {
return;
}
unsafe {
((placed.arena + crate::xnu_relay::STOP_REQUEST) as *mut u32).write_volatile(1)
};
wake_the_copy_in(id, placed.arena, placed.in_the_process);
let has_stopped = &mut || unsafe {
((placed.arena + crate::xnu_relay::WORKER_STOPPED) as *const u32).read_volatile()
} != 0;
let began = crate::kernel::monotonic_micros();
while !has_stopped() {
let waited = (crate::kernel::monotonic_micros() - began) as u64;
if waited >= LONG_ENOUGH_TO_LEAVE || !process_is_alive(id) {
return;
}
crate::kernel::wait(crate::glib::wakeup_token(), Some(LONG_ENOUGH_TO_LEAVE - waited),
has_stopped);
}
}
const LONG_ENOUGH_TO_LEAVE: u64 = 2_000_000;
struct Kept {
code: u64,
map: *mut c_void,
}
unsafe fn kept_images() -> &'static mut alloc::collections::BTreeMap<u32, Kept> {
unsafe { (&raw mut KEPT).as_mut().unwrap() }
}
static mut KEPT: alloc::collections::BTreeMap<u32, Kept> = alloc::collections::BTreeMap::new();
fn where_a_copy_has_been(id: u32, map: *mut c_void) -> Option<u64> {
let kept = unsafe { kept_images() }.get(&id)?;
(kept.map == map).then_some(kept.code)
}
fn what_an_agent_before_this_one_left(map: *mut c_void, shared: usize, size: usize)
-> Option<u64>
{
let ask = unsafe { _mach_vm_region }?;
let mut at = 0u64;
for _ in 0..MOST_RANGES_TO_LOOK_THROUGH {
let mut here = at;
let mut room = 0u64;
let mut about = [0u32; WHAT_A_RANGE_IS_SAID_TO_BE as usize];
let mut words = WHAT_A_RANGE_IS_SAID_TO_BE;
let mut named = 0u32;
if unsafe {
ask(map, &mut here, &mut room, A_RANGE_AS_IT_IS, about.as_mut_ptr() as *mut c_void,
&mut words, &mut named)
} != KERN_SUCCESS
{
return None;
}
if room == shared as u64 && about[WHAT_MAY_BE_DONE_WITH_IT] == (READ | EXECUTE) as u32
&& says_a_copy_was_here(map, here + shared as u64, here)
{
return Some(here);
}
at = here + room;
if room == 0 {
return None;
}
}
None
}
fn says_a_copy_was_here(map: *mut c_void, statics: u64, code: u64) -> bool {
let Some(seen) = share(map, statics, A_PAGE) else {
return false;
};
let said = unsafe { (seen as *const u64).read_volatile() };
let began = unsafe { ((seen + 8) as *const u64).read_volatile() };
unsafe { give_memory_back(ourselves(), seen, A_PAGE) };
said == A_COPY_WAS_HERE && began == code
}
const A_COPY_WAS_HERE: u64 = 0x4672_6964_6100_0003;
const A_PAGE: u64 = 0x4000;
const MOST_RANGES_TO_LOOK_THROUGH: usize = 4096;
const A_RANGE_AS_IT_IS: c_int = 9;
const WHAT_A_RANGE_IS_SAID_TO_BE: u32 = 9;
const WHAT_MAY_BE_DONE_WITH_IT: usize = 0;
fn take_back_what_it_was_given(id: u32, placed: &Placed) {
crate::xnu_hiding::forget_the_threads_of(placed.arena);
crate::xnu_hiding::no_longer_a_thread_of_ours(placed.bare_thread);
if let Some(terminate) = unsafe { _thread_terminate } {
unsafe { terminate(placed.bare_thread) };
}
let process = process_with_id(id);
let map = process.as_ref().map(|process| process.map);
what_the_copy_took(placed, &mut |at, size| unsafe { give_memory_back(map, at, size) });
unsafe { kept_images() }.insert(id, Kept { code: placed.code, map: placed.map });
say_a_copy_was_here(placed);
unsafe { give_memory_back(ourselves(), placed.image_here, placed.seen_size) };
unsafe { give_memory_back(ourselves(), placed.arena, crate::xnu_relay::ARENA_SIZE) };
let Some(process) = process else {
return;
};
unsafe {
give_memory_back(map, placed.in_the_process, crate::xnu_relay::ARENA_SIZE);
give_memory_back(map, placed.stack, STACK);
release_process(process.handle);
}
}
fn say_a_copy_was_here(placed: &Placed) {
let shared = (crate::writable_half_start() - crate::own_range().0) as u64;
let statics = match placed.seen_size == crate::own_range().1 as u64 {
true => placed.image_here + shared,
false => placed.image_here,
};
unsafe {
(statics as *mut u64).write_volatile(A_COPY_WAS_HERE);
((statics + 8) as *mut u64).write_volatile(placed.code);
}
}
fn what_the_copy_took(placed: &Placed, found: &mut dyn FnMut(u64, u64)) {
let taken = (placed.arena + crate::xnu_relay::WHAT_WE_TOOK) as *const u64;
let how_many = (unsafe { taken.read_volatile() } as usize)
.min(crate::xnu_relay::MOST_WE_TAKE);
for step in 0..how_many {
let at = unsafe { taken.add(1 + step * 2).read_volatile() };
if at != 0 {
found(at, unsafe { taken.add(2 + step * 2).read_volatile() });
}
}
}
unsafe fn give_memory_back(map: Option<*mut c_void>, address: u64, size: u64) {
let (Some(map), Some(deallocate)) = (map, unsafe { _mach_vm_deallocate }) else {
return;
};
unsafe { deallocate(map, address, size) };
}
pub fn echo_through_copy(id: u32, frame: &[u8], into: &mut [u8]) -> usize {
let Some(arena) = arena_for_pid(id) else {
return 0;
};
crate::xnu_relay::forward_frame(arena, frame);
let answered = &mut || crate::xnu_relay::holds_a_frame_from_target(arena);
for _ in 0..LONG_ENOUGH {
if let Some(said) = crate::xnu_relay::take_frame_from_target(arena) {
let length = said.len().min(into.len());
into[..length].copy_from_slice(&said[..length]);
return length;
}
crate::kernel::wait(crate::glib::wakeup_token(), None, answered);
}
0
}
pub fn process_id_according_to_copy(id: u32) -> u32 {
let mut said = [0u8; 4];
if echo_through_copy(id, b"pid", &mut said) != said.len() {
return 0;
}
u32::from_le_bytes(said)
}
pub fn copy_can_take_memory(id: u32) -> bool {
let mut said = [0u8; 8];
echo_through_copy(id, b"mem", &mut said) == said.len() && said[0] == 1
}
pub fn threads_according_to_copy(id: u32) -> u32 {
let mut said = [0u8; 4];
if echo_through_copy(id, b"thr", &mut said) != said.len() {
return 0;
}
u32::from_le_bytes(said)
}
pub fn process_is_alive(id: u32) -> bool {
match process_with_id(id) {
Some(process) => {
unsafe { release_process(process.handle) };
true
}
None => false,
}
}
pub fn wake_the_copy_at(arena: u64) {
let Some((id, theirs)) = unsafe { arenas() }.iter()
.find(|(_, placed)| placed.arena == arena)
.map(|(id, placed)| (*id, placed.in_the_process))
else {
return;
};
wake_the_copy_in(id, arena, theirs);
}
fn wake_the_copy_in(id: u32, ours: u64, theirs: u64) {
let Some(process) = process_with_id(id) else {
return;
};
if let Some(process_of) = unsafe { crate::xnu_spawn::_get_bsdtask_info } {
crate::xnu_bell::wake_the_copy(unsafe { process_of(process.task) }, ours, theirs);
}
unsafe { release_process(process.handle) };
}
pub fn whose_task_does_this_port_name(port: *mut c_void) -> Option<u32> {
if port.is_null() {
return None;
}
let holds = |at: usize| {
let held = crate::xnu_hiding::a_word_of(port as u64, at)?;
let held = unsafe { crate::pac::ptrauth_strip_pointer(held as *const u8) } as *mut c_void;
unsafe { arenas() }.iter().find(|(_, placed)| placed.task == held).map(|(id, _)| *id)
};
let at = unsafe { WHERE_A_PORT_HOLDS_ITS_TASK };
if at != 0 {
return holds(at);
}
for word in 1..HOW_FAR_IN_TO_LOOK_FOR_THE_TASK {
if let Some(id) = holds(word * 8) {
unsafe { WHERE_A_PORT_HOLDS_ITS_TASK = word * 8 };
return Some(id);
}
}
None
}
static mut WHERE_A_PORT_HOLDS_ITS_TASK: usize = 0;
const HOW_FAR_IN_TO_LOOK_FOR_THE_TASK: usize = 32;
pub fn what_we_have_in_process(id: u32, found: &mut dyn FnMut(u64, u64)) {
let Some(placed) = (unsafe { arenas() }).get(&id) else {
return;
};
what_we_have_in(placed.map, found);
}
pub fn how_many_of_ours_are_in(id: u32) -> u32 {
let Some(placed) = (unsafe { arenas() }).get(&id) else {
return 0;
};
let said = (placed.arena + crate::xnu_relay::OUR_THREADS) as *const u64;
(0..crate::xnu_hiding::MOST_OF_OURS_IN_ONE)
.filter(|step| unsafe { said.add(*step).read_volatile() } != 0)
.count() as u32
}
pub fn what_we_have_in(map: *mut c_void, found: &mut dyn FnMut(u64, u64)) {
for placed in unsafe { arenas() }.values().filter(|placed| placed.map == map) {
found(placed.in_the_process, crate::xnu_relay::ARENA_SIZE);
found(placed.code, crate::own_range().1 as u64);
found(placed.stack, STACK);
what_the_copy_took(placed, found);
}
}
pub fn arena_for_pid(id: u32) -> Option<u64> {
unsafe { arenas() }.get(&id).map(|placed| placed.arena)
}
pub fn a_copy_is_asking_for_something() -> bool {
unsafe { arenas() }.values().any(|placed| unsafe {
((placed.arena + crate::xnu_relay::PROTECT_WANTED) as *const u64).read_volatile()
} != crate::xnu_relay::NOTHING_WANTED)
}
pub fn injected_arenas() -> alloc::vec::Vec<u64> {
unsafe { arenas() }.values().map(|placed| placed.arena).collect()
}
unsafe fn arenas() -> &'static mut alloc::collections::BTreeMap<u32, Placed> {
unsafe { (&raw mut ARENAS).as_mut().unwrap() }
}
static mut ARENAS: alloc::collections::BTreeMap<u32, Placed> = alloc::collections::BTreeMap::new();
fn woke_up(id: u32, arena: u64) -> bool {
let is_up = &mut || unsafe { (arena as *const u64).read_volatile() } == crate::xnu_user::AWAKE;
let began = crate::kernel::monotonic_micros();
while !is_up() {
let waited = (crate::kernel::monotonic_micros() - began) as u64;
if waited >= LONG_ENOUGH_TO_COME_UP || !process_is_alive(id) {
return false;
}
crate::kernel::wait(crate::glib::wakeup_token(),
Some(LONG_ENOUGH_TO_COME_UP - waited), is_up);
}
true
}
const LONG_ENOUGH_TO_COME_UP: u64 = 10_000_000;
pub struct Process {
pub handle: *mut c_void,
pub task: *mut c_void,
pub map: *mut c_void,
}
#[derive(Clone)]
struct Placed {
map: *mut c_void,
task: *mut c_void,
arena: u64,
image_here: u64,
seen_size: u64,
code: u64,
in_the_process: u64,
stack: u64,
bare_thread: *mut c_void,
}
struct Home {
image: u64,
image_here: u64,
seen_size: u64,
code: u64,
arena: u64,
arena_here: u64,
stack: u64,
}
pub fn process_with_id(id: u32) -> Option<Process> {
let find = unsafe { _proc_find }?;
let task_of = unsafe { _proc_task }?;
let map_of = unsafe { _get_task_map }?;
let handle = unsafe { find(id as c_int) };
if handle.is_null() {
return None;
}
let task = unsafe { task_of(handle) };
Some(Process { handle, task, map: unsafe { map_of(task) } })
}
fn give_the_copy_a_home(id: u32, map: *mut c_void) -> Option<Home> {
let (base, size) = crate::own_range();
let shared = crate::writable_half_start() - base;
let (code, seen_from_here, seen_size) = match where_a_copy_has_been(id, map)
.or_else(|| what_an_agent_before_this_one_left(map, shared, size))
{
Some(code) => {
let private = (size - shared) as u64;
let seen = share(map, code + shared as u64, private)?;
unsafe {
crate::install_writable_half(code as usize, seen as usize);
core::ptr::write_bytes((seen as usize + crate::writable_half_size()) as *mut u8, 0,
size - shared - crate::writable_half_size());
}
(code, seen, private)
}
None => {
let code = take_memory(map, size as u64)?;
let seen = share(map, code, size as u64)?;
lay_out_the_image(base, size, code, seen);
(code, seen, size as u64)
}
};
let arena = take_memory(map, crate::xnu_relay::ARENA_SIZE)?;
let arena_here = share(map, arena, crate::xnu_relay::ARENA_SIZE)?;
unsafe {
core::ptr::write_bytes(arena_here as *mut u8, 0, crate::xnu_relay::ARENA_SIZE as usize);
((arena_here + crate::xnu_relay::IMAGE_BASE) as *mut u64).write(code);
((arena_here + crate::xnu_relay::IMAGE_SIZE) as *mut u64).write(size as u64);
((arena_here + crate::xnu_relay::PAGE_SIZE) as *mut u64)
.write(crate::gum::page_size_the_kernel_runs_with() as u64);
((arena_here + crate::xnu_relay::CACHE_SHAPE) as *mut u64)
.write(crate::xnu::kernel_cache_shape());
((arena_here + crate::xnu_relay::HAS_RUN) as *mut u32).write(1);
}
let stack = take_memory(map, STACK)?;
if seen_size == size as u64 {
let protect = unsafe { _mach_vm_protect }?;
let shared = shared as u64;
if unsafe { protect(map, code, shared, 0, READ | EXECUTE) } != KERN_SUCCESS {
return None;
}
if unsafe { protect(map, code + shared, size as u64 - shared, 0, READ | WRITE) }
!= KERN_SUCCESS
{
return None;
}
}
Some(Home {
image: code,
image_here: seen_from_here,
seen_size,
code: code + crate::xnu_user::entry_offset() as u64,
arena,
arena_here,
stack,
})
}
fn lay_out_the_image(base: usize, size: usize, code: u64, seen_from_here: u64) {
let shared = crate::writable_half_start() - base;
let private = crate::writable_half_size();
unsafe {
core::ptr::copy_nonoverlapping(base as *const u8, seen_from_here as *mut u8, shared);
crate::install_writable_half(code as usize, (seen_from_here as usize) + shared);
core::ptr::write_bytes(
(seen_from_here as usize + shared + private) as *mut u8,
0,
size - shared - private,
);
}
}
fn share(map: *mut c_void, address: u64, size: u64) -> Option<u64> {
let remap = unsafe { _mach_vm_remap }?;
let ours = ourselves()?;
let mut here: u64 = 0;
let mut may: u32 = 0;
let mut most: u32 = 0;
let told = unsafe {
remap(ours, &mut here as *mut u64, size, 0, ANYWHERE, map, address, SHARED,
&mut may as *mut u32, &mut most as *mut u32, INHERIT_NONE)
};
if told != KERN_SUCCESS {
return None;
}
Some(here)
}
fn take_memory(map: *mut c_void, size: u64) -> Option<u64> {
let allocate = unsafe { _mach_vm_allocate }?;
let mut address: u64 = 0;
if unsafe { allocate(map, &mut address as *mut u64, size, ANYWHERE) } != KERN_SUCCESS {
return None;
}
Some(address)
}
#[allow(dead_code)]
fn put(map: *mut c_void, bytes: &[u8]) -> Option<u64> {
let copy_in = unsafe { _vm_map_copyin }?;
let copy_out = unsafe { _vm_map_copyout }?;
let ours = ourselves()?;
let held = crate::kernel::alloc(PAGE as usize);
unsafe {
core::ptr::write_bytes(held, 0, PAGE as usize);
core::ptr::copy_nonoverlapping(bytes.as_ptr(), held, bytes.len());
}
let mut copy: *mut c_void = core::ptr::null_mut();
let taken = unsafe { copy_in(ours, held as u64, PAGE, 0, &mut copy as *mut *mut c_void) };
crate::kernel::free(held, PAGE as usize);
if taken != KERN_SUCCESS {
return None;
}
let mut landed: u64 = 0;
if unsafe { copy_out(map, &mut landed as *mut u64, copy) } != KERN_SUCCESS {
return None;
}
Some(landed)
}
fn ourselves() -> Option<*mut c_void> {
let map_of = unsafe { _get_task_map }?;
let task_of = unsafe { _current_task }?;
Some(unsafe { map_of(task_of()) })
}
fn look_at_the_task(task: *mut c_void) -> alloc::vec::Vec<u32> {
let how_far = HOW_FAR_INTO_A_TASK
.min(crate::xnu_hiding::room_in_the_page(task as u64) / 4);
(0..how_far)
.map(|step| unsafe { ((task as *const u32).add(step)).read_volatile() })
.collect()
}
fn learn_where_the_threads_are_counted(before: &[u32], after: &[u32]) {
if unsafe { WHERE_THEY_ARE_COUNTED } != 0 {
return;
}
for step in 0..before.len() - 1 {
let went_up = |at: usize| after[at] == before[at].wrapping_add(1);
if before[step] == before[step + 1] && went_up(step) && went_up(step + 1) {
unsafe { WHERE_THEY_ARE_COUNTED = step * 4 };
return;
}
}
}
pub fn count_only_what_the_process_made(task: *mut c_void) -> Option<(u32, u32)> {
let at = unsafe { WHERE_THEY_ARE_COUNTED };
if at == 0 || !any_copy_is_in(task) {
return None;
}
let counted = ((task as usize) + at) as *mut u32;
let kept = unsafe { (counted.read_volatile(), counted.add(1).read_volatile()) };
unsafe {
counted.write_volatile(1);
counted.add(1).write_volatile(1);
}
Some(kept)
}
pub fn count_them_all_again(task: *mut c_void, kept: (u32, u32)) {
let at = unsafe { WHERE_THEY_ARE_COUNTED };
if at == 0 {
return;
}
let counted = ((task as usize) + at) as *mut u32;
unsafe {
counted.write_volatile(kept.0);
counted.add(1).write_volatile(kept.1);
}
}
fn any_copy_is_in(task: *mut c_void) -> bool {
unsafe { arenas() }.keys().any(|id| {
match process_with_id(*id) {
Some(process) => {
let ours = process.task == task;
unsafe { release_process(process.handle) };
ours
}
None => false,
}
})
}
static mut WHERE_THEY_ARE_COUNTED: usize = 0;
const HOW_FAR_INTO_A_TASK: usize = 0x400;
fn start(task: *mut c_void, home: &Home) -> Option<*mut c_void> {
start_a_thread(task, home.code, home.stack + STACK - 0x100, home.arena)
}
pub fn serve_what_the_copies_ask() {
for placed in unsafe { arenas() }.values() {
crate::xnu_hiding::take_note_of_what_a_copy_says(placed.arena);
}
let asked: alloc::vec::Vec<(u32, u64)> = unsafe { arenas() }
.iter()
.map(|(id, placed)| (*id, placed.arena))
.collect();
for (id, arena) in asked {
let asked = |at: u64| unsafe { ((arena + at) as *const u64).read_volatile() };
let wants_protection = asked(crate::xnu_relay::PROTECT_WANTED);
if wants_protection == crate::xnu_relay::NOTHING_WANTED {
continue;
}
let Some(process) = process_with_id(id) else {
continue;
};
let done = set_protection(process.map, wants_protection,
asked(crate::xnu_relay::PROTECT_SIZE), asked(crate::xnu_relay::PROTECT_TO) as c_int);
answer(arena, crate::xnu_relay::PROTECT_WANTED, crate::xnu_relay::PROTECT_ANSWER, done);
wake_the_copy_at(arena);
unsafe { release_process(process.handle) };
}
}
fn set_protection(map: *mut c_void, address: u64, size: u64, may: c_int) -> bool {
let Some(protect) = (unsafe { _mach_vm_protect }) else {
return false;
};
let asking = if (may & WRITE) != 0 { may | A_COPY_OF_ITS_OWN } else { may };
if unsafe { protect(map, address, size, 0, asking) } == KERN_SUCCESS {
return true;
}
unsafe { protect(map, address, size, AS_HIGH_AS_IT_GOES, READ | WRITE | EXECUTE) };
unsafe { protect(map, address, size, 0, asking) == KERN_SUCCESS }
}
fn answer(arena: u64, asked_at: u64, answer_at: u64, went_well: bool) {
unsafe {
((arena + asked_at) as *mut u64).write_volatile(crate::xnu_relay::NOTHING_WANTED);
((arena + answer_at) as *mut u64).write_volatile(
if went_well { crate::xnu_relay::DONE } else { crate::xnu_relay::REFUSED });
}
}
fn start_a_thread(task: *mut c_void, code: u64, stack: u64, argument: u64) -> Option<*mut c_void> {
let create = unsafe { _thread_create };
let set_state = unsafe { _thread_set_state };
let resume = unsafe { _thread_resume };
let (Some(create), Some(set_state), Some(resume)) = (create, set_state, resume) else {
return None;
};
let mut thread: *mut c_void = core::ptr::null_mut();
if unsafe { create(task, &mut thread as *mut *mut c_void) } != KERN_SUCCESS {
return None;
}
let mut state = [0u32; STATE_WORDS];
let places = state.as_mut_ptr() as *mut u8;
unsafe {
(places.add(STACK_POINTER) as *mut u64).write(stack);
(places.add(PROGRAM_COUNTER) as *mut u64).write(code);
(places as *mut u64).write(argument);
(places.add(FLAGS) as *mut u32).write(PLAIN_ADDRESSES);
}
if unsafe { set_state(thread, ARM_THREAD_STATE64, state.as_ptr(), STATE_WORDS as c_int) }
!= KERN_SUCCESS
{
return None;
}
(unsafe { resume(thread) } == KERN_SUCCESS).then_some(thread)
}
const PAGE: u64 = 0x4000;
pub(crate) const STACK: u64 = 512 * 1024;
const KERN_SUCCESS: c_int = 0;
const ANYWHERE: c_int = 1;
const AS_HIGH_AS_IT_GOES: c_int = 1;
const A_COPY_OF_ITS_OWN: c_int = 0x10;
const READ: c_int = 1;
const WRITE: c_int = 2;
const EXECUTE: c_int = 4;
const ARM_THREAD_STATE64: c_int = 6;
const SHARED: c_int = 0;
const LONG_ENOUGH: u32 = 100_000;
const INHERIT_NONE: c_int = 2;
const STATE_WORDS: usize = 68;
const STACK_POINTER: usize = 29 * 8 + 16;
const PROGRAM_COUNTER: usize = 29 * 8 + 24;
const FLAGS: usize = 29 * 8 + 36;
const PLAIN_ADDRESSES: u32 = 1;
pub unsafe fn release_process(handle: *mut c_void) {
if let Some(release) = unsafe { _proc_rele } {
unsafe { release(handle) };
}
}
unsafe extern "C" {
static _proc_find: Option<unsafe extern "C" fn(c_int) -> *mut c_void>;
static _proc_rele: Option<unsafe extern "C" fn(*mut c_void)>;
static _proc_task: Option<unsafe extern "C" fn(*mut c_void) -> *mut c_void>;
static _current_task: Option<unsafe extern "C" fn() -> *mut c_void>;
static _get_task_map: Option<unsafe extern "C" fn(*mut c_void) -> *mut c_void>;
static _mach_vm_allocate: Option<unsafe extern "C" fn(*mut c_void, *mut u64, u64, c_int) -> c_int>;
static _mach_vm_deallocate: Option<unsafe extern "C" fn(*mut c_void, u64, u64) -> c_int>;
static _mach_vm_protect: Option<unsafe extern "C" fn(*mut c_void, u64, u64, c_int, c_int) -> c_int>;
static _mach_vm_region: Option<unsafe extern "C" fn(*mut c_void, *mut u64, *mut u64, c_int,
*mut c_void, *mut u32, *mut u32) -> c_int>;
static _mach_vm_remap: Option<
unsafe extern "C" fn(*mut c_void, *mut u64, u64, u64, c_int, *mut c_void, u64, c_int,
*mut u32, *mut u32, c_int) -> c_int,
>;
static _vm_map_copyin: Option<unsafe extern "C" fn(*mut c_void, u64, u64, c_int, *mut *mut c_void) -> c_int>;
static _vm_map_copyout: Option<unsafe extern "C" fn(*mut c_void, *mut u64, *mut c_void) -> c_int>;
static _thread_create: Option<unsafe extern "C" fn(*mut c_void, *mut *mut c_void) -> c_int>;
static _thread_set_state: Option<unsafe extern "C" fn(*mut c_void, c_int, *const u32, c_int) -> c_int>;
static _thread_resume: Option<unsafe extern "C" fn(*mut c_void) -> c_int>;
static _thread_terminate: Option<unsafe extern "C" fn(*mut c_void) -> c_int>;
}
-237
View File
@@ -1,237 +0,0 @@
pub fn function_named(library: &[u8], wanted: &[u8]) -> Option<u64> {
let image = image_named(library)?;
function_in(image, wanted)
}
pub fn making_threads() -> Option<&'static MakingThreads> {
let held = unsafe { (&raw const MAKING).as_ref().unwrap() };
if held.is_some() {
return held.as_ref();
}
let pthread = image_named(b"/libsystem_pthread.dylib")?;
let found = MakingThreads {
out_of_a_bare_one: function_in(pthread, b"_pthread_create_from_mach_thread")
.map(|at| unsafe { core::mem::transmute(at) }),
the_usual_way: unsafe { core::mem::transmute(function_in(pthread, b"_pthread_create")?) },
};
unsafe { MAKING = Some(found) };
unsafe { (&raw const MAKING).as_ref().unwrap().as_ref() }
}
pub type StartAThread = unsafe extern "C" fn(*mut core::ffi::c_void) -> *mut core::ffi::c_void;
pub type MakeAThread = unsafe extern "C" fn(*mut u64, *const u8, StartAThread,
*mut core::ffi::c_void) -> i32;
pub struct MakingThreads {
pub out_of_a_bare_one: Option<MakeAThread>,
pub the_usual_way: MakeAThread,
}
static mut MAKING: Option<MakingThreads> = None;
pub fn signed_to_begin_at(start: StartAThread) -> StartAThread {
unsafe { core::mem::transmute(crate::pac::ptrauth_sign(start as *const u8, NOTHING_TO_TIE_IT_TO)) }
}
const NOTHING_TO_TIE_IT_TO: usize = 0;
pub fn asking_about_threads() -> Option<&'static AskingAboutThreads> {
let held = unsafe { (&raw const ASKING).as_ref().unwrap() };
if held.is_some() {
return held.as_ref();
}
let kernel = image_named(b"/libsystem_kernel.dylib")?;
let found = AskingAboutThreads {
this_task: function_in(kernel, b"_mach_task_self_")?,
which_ones: unsafe { core::mem::transmute(function_in(kernel, b"_task_threads")?) },
what_it_is_doing: unsafe { core::mem::transmute(function_in(kernel, b"_thread_get_state")?) },
set_what_it_does: unsafe { core::mem::transmute(function_in(kernel, b"_thread_set_state")?) },
hold_it_still: unsafe { core::mem::transmute(function_in(kernel, b"_thread_suspend")?) },
let_it_go: unsafe { core::mem::transmute(function_in(kernel, b"_thread_resume")?) },
give_back: unsafe { core::mem::transmute(function_in(kernel, b"_vm_deallocate")?) },
which_one_is_this: unsafe { core::mem::transmute(function_in(kernel, b"_mach_thread_self")?) },
};
unsafe { ASKING = Some(found) };
unsafe { (&raw const ASKING).as_ref().unwrap().as_ref() }
}
pub struct AskingAboutThreads {
pub this_task: u64,
pub which_ones: unsafe extern "C" fn(u32, *mut *mut u32, *mut u32) -> i32,
pub what_it_is_doing: unsafe extern "C" fn(u32, i32, *mut u32, *mut u32) -> i32,
pub set_what_it_does: unsafe extern "C" fn(u32, i32, *const u32, u32) -> i32,
pub hold_it_still: unsafe extern "C" fn(u32) -> i32,
pub let_it_go: unsafe extern "C" fn(u32) -> i32,
pub give_back: unsafe extern "C" fn(u32, u64, u64) -> i32,
pub which_one_is_this: unsafe extern "C" fn() -> u32,
}
static mut ASKING: Option<AskingAboutThreads> = None;
pub fn image_named(wanted: &[u8]) -> Option<u64> {
let cache = crate::xnu_user_calls::where_the_shared_code_is()?;
let read_word = |at: u64| unsafe { ((cache + at) as *const u32).read_volatile() };
let read_long = |at: u64| unsafe { ((cache + at) as *const u64).read_volatile() };
let moved_by = cache.wrapping_sub(read_long(read_word(SPANS_ARE_AT) as u64));
let images_at = read_word(IMAGES_ARE_AT) as u64;
let images = read_word(HOW_MANY_IMAGES);
for image in 0..images as u64 {
let at = images_at + (image * AN_IMAGE);
let name_at = read_word(at + WHAT_IT_IS_CALLED) as u64;
if !name_here(cache + name_at, wanted) {
continue;
}
return Some(read_long(at).wrapping_add(moved_by));
}
None
}
pub fn function_in(image: u64, wanted: &[u8]) -> Option<u64> {
let (trie, size) = what_the_image_offers(image)?;
let mut at = trie;
let mut spent = 0;
for _ in 0..wanted.len() + 1 {
if at >= trie + size {
return None;
}
let (said, after) = a_number_at(at);
if said != 0 && spent == wanted.len() {
let (_, past_flags) = a_number_at(after);
let (offset, _) = a_number_at(past_flags);
return Some(image + offset);
}
let children = after + said;
let (count, mut step) = (byte_at(children), children + 1);
let mut went = None;
for _ in 0..count {
let began = step;
let mut length = 0;
while length < LONGEST_NAME as u64 && byte_at(began + length) != 0 {
length += 1;
}
let matches = spent + length as usize <= wanted.len()
&& (0..length).all(|index| byte_at(began + index) == wanted[spent + index as usize]);
let (next, after_next) = a_number_at(began + length + 1);
if matches && went.is_none() {
went = Some((trie + next, spent + length as usize));
}
step = after_next;
}
let (there, now_spent) = went?;
at = there;
spent = now_spent;
}
None
}
fn what_the_image_offers(image: u64) -> Option<(u64, u64)> {
let commands = read_word(image + HOW_MANY_COMMANDS);
let mut at = image + PAST_THE_HEADER;
let mut linkedit = None;
let mut offers = None;
for _ in 0..commands {
let kind = read_word(at);
let length = read_word(at + 4) as u64;
if kind == A_SEGMENT && name_here(at + 8, b"__LINKEDIT") {
linkedit = Some((read_long(at + 24), read_long(at + 40)));
}
if kind == WHAT_IT_OFFERS {
offers = Some((read_word(at + 8) as u64, read_word(at + 12) as u64));
}
at += length;
}
let ((where_it_wants_to_be, where_it_is_in_the_file), (offered_at, size)) =
(linkedit?, offers?);
Some((where_it_wants_to_be + slide_of() + (offered_at - where_it_is_in_the_file), size))
}
fn slide_of() -> u64 {
let cache = crate::xnu_user_calls::where_the_shared_code_is().unwrap_or(0);
cache.wrapping_sub(read_long(cache + read_word(cache + SPANS_ARE_AT) as u64))
}
fn read_word(at: u64) -> u32 {
unsafe { (at as *const u32).read_volatile() }
}
fn read_long(at: u64) -> u64 {
unsafe { (at as *const u64).read_volatile() }
}
fn byte_at(at: u64) -> u8 {
unsafe { (at as *const u8).read_volatile() }
}
fn a_number_at(at: u64) -> (u64, u64) {
let mut said = 0u64;
let mut step = at;
for shift in (0..MOST_BYTES_TO_A_NUMBER * 7).step_by(7) {
let byte = byte_at(step);
step += 1;
said |= ((byte & 0x7f) as u64) << shift;
if (byte & 0x80) == 0 {
break;
}
}
(said, step)
}
const MOST_BYTES_TO_A_NUMBER: usize = 10;
const HOW_MANY_COMMANDS: u64 = 16;
const PAST_THE_HEADER: u64 = 32;
const A_SEGMENT: u32 = 0x19;
const WHAT_IT_OFFERS: u32 = 0x8000_0033;
fn name_here(at: u64, wanted: &[u8]) -> bool {
let mut length = 0;
while length < LONGEST_NAME {
if unsafe { ((at as *const u8).add(length)).read_volatile() } == 0 {
break;
}
length += 1;
}
if length < wanted.len() {
return false;
}
let from = length - wanted.len();
for (step, byte) in wanted.iter().enumerate() {
if unsafe { ((at as *const u8).add(from + step)).read_volatile() } != *byte {
return false;
}
}
true
}
const SPANS_ARE_AT: u64 = 0x10;
const IMAGES_ARE_AT: u64 = 0x1c0;
const HOW_MANY_IMAGES: u64 = 0x1c4;
const AN_IMAGE: u64 = 32;
const WHAT_IT_IS_CALLED: u64 = 24;
const LONGEST_NAME: usize = 256;
-224
View File
@@ -1,224 +0,0 @@
use alloc::string::{String, ToString};
use alloc::vec::Vec;
use crate::bindings::{
GumDarwinExportDetails, GumDarwinModule, GumMemoryRange, GumModuleRegistry, GumAddress,
gboolean, g_object_unref, gpointer, gsize,
};
use crate::gum;
pub fn register_what_the_copy_lives_among(registry: *mut GumModuleRegistry) {
for image in what_is_loaded() {
let range = GumMemoryRange { base_address: image.base, size: image.size as gsize };
let module = gum::gum_native_module_new(&image.path, "", &range);
unsafe {
crate::bindings::gum_barebone_register_module(registry, module);
g_object_unref(module as gpointer);
}
}
}
pub fn enumerate_exports_in_range(from: u64, to: u64,
found: &mut crate::gum::FoundExportCallback<'_>)
{
let _ = to;
let Some(image) = what_is_loaded().into_iter().find(|image| image.base == from) else {
return;
};
let Some(module) = read_the_image(image.base, &image.path) else {
return;
};
let mut asking = Asking { found, base: image.base };
unsafe {
crate::bindings::gum_darwin_module_enumerate_exports(module,
Some(crate::signed_to_be_called_back(note_an_export, 0)),
&mut asking as *mut Asking<'_, '_> as gpointer);
g_object_unref(module as gpointer);
}
}
struct Asking<'a, 'b> {
found: &'a mut crate::gum::FoundExportCallback<'b>,
base: u64,
}
unsafe extern "C" fn note_an_export(details: *const GumDarwinExportDetails, asking: gpointer)
-> gboolean
{
let asking = unsafe { &mut *(asking as *mut Asking<'_, '_>) };
let details = unsafe { &*details };
let offset = unsafe { details.__bindgen_anon_1.__bindgen_anon_1.offset };
let named = unsafe { details.name.add(1) };
(asking.found)(named, asking.base + offset);
1
}
pub fn export_named(wanted: &str) -> u64 {
let Some(ask_the_loader) = crate::xnu_libsystem::function_named(b"/libdyld.dylib", b"_dlsym")
else {
return 0;
};
type AskTheLoader = unsafe extern "C" fn(*const core::ffi::c_void, *const u8) -> u64;
let ask_the_loader: AskTheLoader = unsafe { core::mem::transmute(ask_the_loader) };
let Ok(asked) = alloc::ffi::CString::new(wanted) else {
return 0;
};
let signed = unsafe { ask_the_loader(WHEREVER_IT_IS, asked.as_ptr() as *const u8) };
unsafe { crate::pac::ptrauth_strip_data(signed as *const u8) as u64 }
}
const WHEREVER_IT_IS: *const core::ffi::c_void = -2isize as *const core::ffi::c_void;
struct Image {
base: u64,
size: u64,
path: String,
}
fn what_is_loaded() -> Vec<Image> {
let mut found = Vec::new();
if !crate::xnu_user::on_a_thread_the_system_knows() {
return found;
}
let Some(list) = where_the_loader_keeps_its_list() else {
return found;
};
let images = read_word(list + HOW_MANY_IMAGES) as usize;
let at = read_long(list + WHERE_THE_IMAGES_ARE);
for step in 0..images.min(MOST_IMAGES) {
let each = at + (step * AN_IMAGE) as u64;
let base = read_long(each);
let path = read_long(each + WHAT_IT_CAME_FROM);
if base == 0 || path == 0 {
continue;
}
let path = text_at(path);
let size = how_much_of_it_there_is(base, &path);
if size == 0 {
continue;
}
found.push(Image { base, size, path });
}
let loader = read_long(list + WHERE_THE_LOADER_IS);
if loader != 0 {
let size = how_much_of_it_there_is(loader, WHAT_THE_LOADER_IS_CALLED);
if size != 0 {
found.push(Image { base: loader, size,
path: WHAT_THE_LOADER_IS_CALLED.to_string() });
}
}
found
}
fn where_the_loader_keeps_its_list() -> Option<u64> {
let asking = crate::xnu_libsystem::asking_about_threads()?;
let about: unsafe extern "C" fn(u32, u32, *mut u32, *mut u32) -> i32 =
unsafe { core::mem::transmute(crate::xnu_libsystem::function_named(
b"/libsystem_kernel.dylib", b"_task_info")?) };
let task = unsafe { (asking.this_task as *const u32).read_volatile() };
let mut said = [0u32; ABOUT_THE_LOADER_WORDS];
let mut room = said.len() as u32;
let told = unsafe { about(task, ABOUT_THE_LOADER, said.as_mut_ptr(), &mut room) };
if told != 0 {
return None;
}
let list = (said[0] as u64) | ((said[1] as u64) << 32);
(list != 0).then_some(list)
}
fn read_the_image(base: u64, name: &str) -> Option<*mut GumDarwinModule> {
let named = alloc::ffi::CString::new(name).ok()?;
let module = unsafe {
crate::bindings::gum_darwin_module_new_from_memory(named.as_ptr(), NO_TASK_TO_ASK,
base as GumAddress, 0, core::ptr::null_mut())
};
(!module.is_null()).then_some(module)
}
fn how_much_of_it_there_is(base: u64, name: &str) -> u64 {
let Some(module) = read_the_image(base, name) else {
return 0;
};
let mut size = 0;
for step in 0.. {
let segment = unsafe { crate::bindings::gum_darwin_module_get_nth_segment(module, step) };
if segment.is_null() {
break;
}
if name_is(unsafe { &raw const (*segment).name } as u64, b"__TEXT") {
size = unsafe { (*segment).vm_size };
break;
}
}
unsafe { g_object_unref(module as gpointer) };
size
}
fn name_is(at: u64, wanted: &[u8]) -> bool {
for (step, byte) in wanted.iter().enumerate() {
if unsafe { ((at as *const u8).add(step)).read_volatile() } != *byte {
return false;
}
}
let past = unsafe { ((at as *const u8).add(wanted.len())).read_volatile() };
past == 0
}
fn text_at(at: u64) -> String {
let mut said = Vec::new();
for step in 0..LONGEST_NAME {
let byte = unsafe { ((at as *const u8).add(step)).read_volatile() };
if byte == 0 {
break;
}
said.push(byte);
}
core::str::from_utf8(&said).unwrap_or("?").to_string()
}
fn read_word(at: u64) -> u32 {
unsafe { (at as *const u32).read_volatile() }
}
fn read_long(at: u64) -> u64 {
unsafe { (at as *const u64).read_volatile() }
}
const NO_TASK_TO_ASK: u32 = 0;
const ABOUT_THE_LOADER: u32 = 17;
const ABOUT_THE_LOADER_WORDS: usize = 8;
const HOW_MANY_IMAGES: u64 = 4;
const WHERE_THE_LOADER_IS: u64 = 32;
const WHAT_THE_LOADER_IS_CALLED: &str = "/usr/lib/dyld";
const WHERE_THE_IMAGES_ARE: u64 = 8;
const AN_IMAGE: usize = 24;
const WHAT_IT_CAME_FROM: u64 = 8;
const MOST_IMAGES: usize = 4096;
const LONGEST_NAME: usize = 1024;
-142
View File
@@ -1,142 +0,0 @@
use alloc::vec::Vec;
use core::ffi::{c_char, c_int, c_void};
pub struct ProcessInfo {
pub id: u32,
pub name: *const u8,
pub path: *const u8,
pub command_line: *const u8,
}
// The kernel walks its own list, and where a process keeps its number and its name is what the
// host looked up before any of this started.
pub fn enumerate_processes(found: &mut dyn FnMut(ProcessInfo)) {
let mut listed: Vec<(u32, [u8; NAME_SIZE])> = Vec::new();
if let Some(iterate) = unsafe { _proc_iterate } {
unsafe {
iterate(
ALL_PROCESSES,
signed_to_be_called_back(note_a_process),
&mut listed as *mut Vec<(u32, [u8; NAME_SIZE])> as *mut c_void,
core::ptr::null_mut(),
core::ptr::null_mut(),
)
};
} else {
walk_every_number(&mut listed);
}
for (id, name) in &listed {
found(ProcessInfo {
id: *id,
name: name.as_ptr(),
path: name.as_ptr(),
command_line: core::ptr::null(),
});
}
}
pub fn describe_process(process: &ProcessInfo) -> *const u8 {
process.name
}
pub fn enumerate_icons(_path: *const u8, _found: &mut dyn FnMut(&[u8])) {}
fn walk_every_number(listed: &mut Vec<(u32, [u8; NAME_SIZE])>) {
let Some(find) = (unsafe { _proc_find }) else {
return;
};
let release = unsafe { _proc_rele }.unwrap();
let mut last_seen = 0;
for number in 0..=HIGHEST_NUMBER {
if number - last_seen > LONGEST_GAP {
break;
}
let process = unsafe { find(number as c_int) };
if process.is_null() {
continue;
}
last_seen = number;
unsafe {
note_a_process(process, listed as *mut Vec<(u32, [u8; NAME_SIZE])> as *mut c_void);
release(process);
}
}
}
fn signed_to_be_called_back(callout: ProcCallout) -> ProcCallout {
unsafe {
core::mem::transmute(crate::pac::ptrauth_sign(callout as *const u8,
crate::xnu::what_a_proc_callout_is_signed_with()))
}
}
unsafe extern "C" fn note_a_process(process: *mut c_void, listed: *mut c_void) -> c_int {
let listed = listed as *mut Vec<(u32, [u8; NAME_SIZE])>;
unsafe { (*listed).push((number_of(process), name_of(process))) };
KEEP_GOING
}
unsafe fn number_of(process: *mut c_void) -> u32 {
unsafe {
if let Some(ask) = _proc_pid {
return ask(process) as u32;
}
let where_it_is = crate::kernel::noted("process.number").unwrap() as usize;
((process as *const u8).add(where_it_is) as *const u32).read()
}
}
unsafe fn name_of(process: *mut c_void) -> [u8; NAME_SIZE] {
unsafe {
let said = match _proc_best_name {
Some(ask) => ask(process) as *const u8,
None => {
let where_it_is = crate::kernel::noted("process.name").unwrap() as usize;
(process as *const u8).add(where_it_is)
}
};
let mut name = [0u8; NAME_SIZE];
for (at, letter) in name.iter_mut().enumerate().take(NAME_SIZE - 1) {
let byte = said.add(at).read();
if byte == 0 {
break;
}
*letter = byte;
}
name
}
}
type ProcCallout = unsafe extern "C" fn(*mut c_void, *mut c_void) -> c_int;
const NAME_SIZE: usize = 33;
const ALL_PROCESSES: c_int = 1;
const KEEP_GOING: c_int = 0;
const HIGHEST_NUMBER: u32 = 99999;
const LONGEST_GAP: u32 = 4096;
unsafe extern "C" {
static _proc_iterate: Option<
unsafe extern "C" fn(
c_int,
ProcCallout,
*mut c_void,
*mut c_void,
*mut c_void,
),
>;
static _proc_find: Option<unsafe extern "C" fn(c_int) -> *mut c_void>;
static _proc_rele: Option<unsafe extern "C" fn(*mut c_void)>;
static _proc_pid: Option<unsafe extern "C" fn(*mut c_void) -> c_int>;
static _proc_best_name: Option<unsafe extern "C" fn(*mut c_void) -> *const c_char>;
}
-16
View File
@@ -1,16 +0,0 @@
use crate::kernel;
pub fn enumerate_ranges(found: &mut dyn FnMut(u64, usize, u32)) {
for (address, size, protection) in kernel::noted_mappings() {
found(*address, *size, *protection);
}
}
pub fn protection_at(address: u64) -> u32 {
kernel::noted_mappings()
.iter()
.find(|(base, size, _)| address >= *base && address < *base + *size as u64)
.map(|(_, _, protection)| *protection)
.unwrap_or(0)
}
-222
View File
@@ -1,222 +0,0 @@
use alloc::collections::{BTreeMap, VecDeque};
use alloc::vec::Vec;
use crate::ring::{Ring, Taken};
pub fn forward_frame(arena: u64, frame: &[u8]) -> bool {
unsafe { waiting() }.entry(arena).or_default().push_back(Going { frame: frame.to_vec(),
written: 0 });
send_what_waits(arena);
true
}
struct Going {
frame: Vec<u8>,
written: usize,
}
pub fn serve_waiting_frames() {
let arenas: Vec<u64> = unsafe { waiting() }.keys().copied().collect();
for arena in arenas {
send_what_waits(arena);
}
}
pub fn take_frame_from_target(arena: u64) -> Option<Vec<u8>> {
let frame = read_frame(&TO_KERNEL, arena);
if frame.is_some() {
crate::xnu_injection::wake_the_copy_at(arena);
}
frame
}
pub fn holds_a_frame_from_target(arena: u64) -> bool {
TO_KERNEL.holds_anything(arena)
}
pub fn a_frame_waits_for_room() -> bool {
unsafe { waiting() }
.iter()
.any(|(arena, frames)| !frames.is_empty() && TO_COPY.has_room(*arena))
}
pub fn publish_frame_to_host(arena: u64, frame: &[u8]) -> bool {
write_frame(&TO_KERNEL, arena, frame, tell_the_kernel_half)
}
pub fn take_frame_from_host(arena: u64) -> Option<Vec<u8>> {
let frame = read_frame(&TO_COPY, arena);
if frame.is_some() {
tell_the_kernel_half(arena);
}
frame
}
pub fn holds_a_frame_from_host(arena: u64) -> bool {
TO_COPY.holds_anything(arena)
}
pub fn forget(arena: u64) {
unsafe { holds() }.retain(|(of, _), _| *of != arena);
unsafe { waiting() }.remove(&arena);
}
fn send_what_waits(arena: u64) {
let frames = unsafe { waiting() }.get_mut(&arena).unwrap();
while let Some(going) = frames.front_mut() {
if !write_what_fits(&TO_COPY, arena, going) {
crate::xnu_injection::wake_the_copy_at(arena);
TO_COPY.ask_for_room(arena);
return;
}
frames.pop_front();
crate::xnu_injection::wake_the_copy_at(arena);
}
}
fn write_what_fits(ring: &Ring, arena: u64, going: &mut Going) -> bool {
ring.take_lock(arena, rest);
while going.written < going.frame.len() {
match ring.write(arena, arena + ring.buffer, &going.frame, going.written) {
Some(now) => going.written = now,
None => break,
}
}
ring.let_lock_go(arena);
going.written == going.frame.len()
}
fn write_frame(ring: &Ring, arena: u64, frame: &[u8], tell: fn(u64)) -> bool {
ring.take_lock(arena, rest);
let mut written = 0;
while written < frame.len() {
match ring.write(arena, arena + ring.buffer, frame, written) {
Some(now) => {
written = now;
tell(arena);
}
None => {
ring.ask_for_room(arena);
tell(arena);
crate::kernel::wait(crate::glib::wakeup_token(), None,
&mut || ring.has_room(arena));
}
}
}
ring.let_lock_go(arena);
true
}
fn tell_the_kernel_half(_arena: u64) {
crate::xnu_bell::ring_the_bell();
}
fn read_frame(ring: &Ring, arena: u64) -> Option<Vec<u8>> {
let mut frame = unsafe { holds() }.remove(&(arena, ring.buffer)).unwrap_or_default();
loop {
match ring.read(arena, arena + ring.buffer, &mut frame) {
Taken::Nothing => {
if !frame.is_empty() {
unsafe { holds() }.insert((arena, ring.buffer), frame);
}
return None;
}
Taken::Piece => {}
Taken::Frame => return Some(frame),
}
}
}
fn rest() {
core::hint::spin_loop();
}
unsafe fn holds() -> &'static mut BTreeMap<(u64, u64), Vec<u8>> {
unsafe { (&raw mut HOLDS).as_mut().unwrap() }
}
static mut HOLDS: BTreeMap<(u64, u64), Vec<u8>> = BTreeMap::new();
unsafe fn waiting() -> &'static mut BTreeMap<u64, VecDeque<Going>> {
unsafe { (&raw mut WAITING).as_mut().unwrap() }
}
static mut WAITING: BTreeMap<u64, VecDeque<Going>> = BTreeMap::new();
pub const ARENA_SIZE: u64 = 0x1000 + (2 * FRAME_BUFFER_SIZE as u64) + SAYING_ROOM + TAKEN_ROOM;
pub const WHAT_WE_TOOK: u64 = 0x1000 + (2 * FRAME_BUFFER_SIZE as u64) + SAYING_ROOM;
pub const TAKEN_ROOM: u64 = 0x1000;
pub const MOST_WE_TAKE: usize = 255;
pub const SAYING: u64 = 0x1000 + (2 * FRAME_BUFFER_SIZE as u64);
pub const SAYING_ROOM: u64 = 0x10000;
pub const AWAKE_AT: u64 = 0;
pub const IMAGE_BASE: u64 = 8;
pub const IMAGE_SIZE: u64 = 16;
pub const STOP_REQUEST: u64 = 24;
pub const WORKER_STOPPED: u64 = 28;
pub const HAS_RUN: u64 = 36;
pub const PAGE_SIZE: u64 = 40;
pub const CACHE_SHAPE: u64 = 48;
pub const PROTECT_WANTED: u64 = 544;
pub const PROTECT_SIZE: u64 = 552;
pub const PROTECT_TO: u64 = 560;
pub const PROTECT_ANSWER: u64 = 568;
pub const SPAWN_WANTED: u64 = 576;
pub const SPAWN_ANSWER: u64 = 584;
pub const SPAWN_ID: u64 = 592;
pub const WAKE_WORD: u64 = 616;
pub const BELL_WORD: u64 = 624;
pub const OUR_THREADS: u64 = 656;
pub const APPS_WANTED: u64 = 600;
pub const APPS_ANSWER: u64 = 608;
pub const SPAWN_WORDS: u64 = 0x800;
pub const SPAWN_WORDS_ROOM: u64 = 0x800;
pub const NOTHING_WANTED: u64 = 0;
pub const DONE: u64 = 1;
pub const REFUSED: u64 = 2;
pub const THE_SYSTEM_IS_STARTING_IT: u64 = 3;
pub const TO_COPY: Ring = Ring {
buffer: 0x1000,
head: 256,
tail: 260,
lock: 264,
room: 268,
size: FRAME_BUFFER_SIZE,
};
pub const TO_KERNEL: Ring = Ring {
buffer: 0x1000 + FRAME_BUFFER_SIZE as u64,
head: 272,
tail: 276,
lock: 280,
room: 284,
size: FRAME_BUFFER_SIZE,
};
const FRAME_BUFFER_SIZE: usize = 0x10000;
Loaded 100 of 163 files, more files were not shown because too many files have changed in this diff. Show more