diff --git a/gum/gumapiresolver.c b/gum/gumapiresolver.c index 205bfa9e71..56c59236a3 100644 --- a/gum/gumapiresolver.c +++ b/gum/gumapiresolver.c @@ -1,5 +1,6 @@ /* - * Copyright (C) 2016-2022 Ole André Vadla Ravnås + * Copyright (C) 2016-2023 Ole André Vadla Ravnås + * Copyright (C) 2023 Håvard Sørbø * * Licence: wxWindows Library Licence, Version 3.1 */ @@ -73,6 +74,7 @@ #include "gumapiresolver.h" #include "gummoduleapiresolver.h" +#include "gumswiftapiresolver.h" #ifdef HAVE_DARWIN # include "backend-darwin/gumobjcapiresolver.h" #endif @@ -114,6 +116,9 @@ gum_api_resolver_make (const gchar * type) if (strcmp (type, "module") == 0) return gum_module_api_resolver_new (); + if (strcmp (type, "swift") == 0) + return gum_swift_api_resolver_new (); + #ifdef HAVE_DARWIN if (strcmp (type, "objc") == 0) return gum_objc_api_resolver_new (); diff --git a/gum/gumswiftapiresolver.c b/gum/gumswiftapiresolver.c new file mode 100644 index 0000000000..0b07f232ea --- /dev/null +++ b/gum/gumswiftapiresolver.c @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2023 Ole André Vadla Ravnås + * Copyright (C) 2023 Håvard Sørbø + * + * Licence: wxWindows Library Licence, Version 3.1 + */ + +/** + * GumSwiftApiResolver: + * + * Resolves APIs by searching currently loaded Swift modules. + * + * See [iface@Gum.ApiResolver] for more information. + */ + +#ifndef GUM_DIET + +#include "gumswiftapiresolver.h" + +#include "gummodulemap.h" +#include "gumprocess.h" + +#include + +typedef struct _GumModuleMetadata GumModuleMetadata; +typedef struct _GumFunctionMetadata GumFunctionMetadata; +typedef size_t (* GumSwiftDemangle) (const char * name, char * output, + size_t length); + +struct _GumSwiftApiResolver +{ + GObject parent; + + GRegex * query_pattern; + + GumModuleMap * all_modules; + GHashTable * module_by_name; + + GumSwiftDemangle demangle; +}; + +struct _GumModuleMetadata +{ + gint ref_count; + + const gchar * name; + const gchar * path; + + GArray * functions; + GumSwiftApiResolver * resolver; +}; + +struct _GumFunctionMetadata +{ + gchar * name; + GumAddress address; +}; + +static void gum_swift_api_resolver_iface_init (gpointer g_iface, + gpointer iface_data); +static void gum_swift_api_resolver_finalize (GObject * object); +static void gum_swift_api_resolver_enumerate_matches ( + GumApiResolver * resolver, const gchar * query, GumFoundApiFunc func, + gpointer user_data, GError ** error); + +static void gum_module_metadata_unref (GumModuleMetadata * module); +static GArray * gum_module_metadata_get_functions (GumModuleMetadata * self); +static gboolean gum_module_metadata_collect_export ( + const GumExportDetails * details, gpointer user_data); + +static void gum_function_metadata_free (GumFunctionMetadata * function); + +G_DEFINE_TYPE_EXTENDED (GumSwiftApiResolver, + gum_swift_api_resolver, + G_TYPE_OBJECT, + 0, + G_IMPLEMENT_INTERFACE (GUM_TYPE_API_RESOLVER, + gum_swift_api_resolver_iface_init)) + +static void +gum_swift_api_resolver_class_init (GumSwiftApiResolverClass * klass) +{ + GObjectClass * object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gum_swift_api_resolver_finalize; +} + +static void +gum_swift_api_resolver_iface_init (gpointer g_iface, + gpointer iface_data) +{ + GumApiResolverInterface * iface = g_iface; + + iface->enumerate_matches = gum_swift_api_resolver_enumerate_matches; +} + +static void +gum_swift_api_resolver_init (GumSwiftApiResolver * self) +{ + GArray * entries; + guint i; + + self->query_pattern = g_regex_new ("(.+)!([^\\n\\r\\/]+)(\\/i)?", 0, 0, NULL); + + self->all_modules = gum_module_map_new (); + self->module_by_name = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + (GDestroyNotify) gum_module_metadata_unref); + + entries = gum_module_map_get_values (self->all_modules); + for (i = 0; i != entries->len; i++) + { + GumModuleDetails * d = &g_array_index (entries, GumModuleDetails, i); + GumModuleMetadata * module; + + module = g_slice_new (GumModuleMetadata); + module->ref_count = 2; + module->name = d->name; + module->path = d->path; + module->functions = NULL; + module->resolver = self; + + g_hash_table_insert (self->module_by_name, g_strdup (module->name), module); + g_hash_table_insert (self->module_by_name, g_strdup (module->path), module); + } + + self->demangle = GUM_POINTER_TO_FUNCPTR (GumSwiftDemangle, + gum_module_find_export_by_name (NULL, "swift_demangle_getDemangledName")); +} + +static void +gum_swift_api_resolver_finalize (GObject * object) +{ + GumSwiftApiResolver * self = GUM_SWIFT_API_RESOLVER (object); + + g_hash_table_unref (self->module_by_name); + g_object_unref (self->all_modules); + + g_regex_unref (self->query_pattern); + + G_OBJECT_CLASS (gum_swift_api_resolver_parent_class)->finalize (object); +} + +/** + * gum_swift_api_resolver_new: + * + * Creates a new resolver that searches exports and imports of currently loaded + * modules. + * + * Returns: (transfer full): the newly created resolver instance + */ +GumApiResolver * +gum_swift_api_resolver_new (void) +{ + return g_object_new (GUM_TYPE_SWIFT_API_RESOLVER, NULL); +} + +static void +gum_swift_api_resolver_enumerate_matches (GumApiResolver * resolver, + const gchar * query, + GumFoundApiFunc func, + gpointer user_data, + GError ** error) +{ + GumSwiftApiResolver * self = GUM_SWIFT_API_RESOLVER (resolver); + GMatchInfo * query_info; + gboolean ignore_case; + gchar * module_query, * func_query; + GPatternSpec * module_spec, * func_spec; + GHashTableIter module_iter; + GHashTable * seen_modules; + gboolean carry_on; + GumModuleMetadata * module; + + if (self->demangle == NULL) + goto unsupported_runtime; + + g_regex_match (self->query_pattern, query, 0, &query_info); + if (!g_match_info_matches (query_info)) + goto invalid_query; + + ignore_case = g_match_info_get_match_count (query_info) >= 5; + + module_query = g_match_info_fetch (query_info, 1); + func_query = g_match_info_fetch (query_info, 2); + + g_match_info_free (query_info); + + if (ignore_case) + { + gchar * str; + + str = g_utf8_strdown (module_query, -1); + g_free (module_query); + module_query = str; + + str = g_utf8_strdown (func_query, -1); + g_free (func_query); + func_query = str; + } + + module_spec = g_pattern_spec_new (module_query); + func_spec = g_pattern_spec_new (func_query); + + g_hash_table_iter_init (&module_iter, self->module_by_name); + seen_modules = g_hash_table_new (NULL, NULL); + carry_on = TRUE; + + while (carry_on && + g_hash_table_iter_next (&module_iter, NULL, (gpointer *) &module)) + { + const gchar * module_name = module->name; + const gchar * module_path = module->path; + gchar * module_name_copy = NULL; + gchar * module_path_copy = NULL; + + if (g_hash_table_contains (seen_modules, module)) + continue; + g_hash_table_add (seen_modules, module); + + if (ignore_case) + { + module_name_copy = g_utf8_strdown (module_name, -1); + module_name = module_name_copy; + + module_path_copy = g_utf8_strdown (module_path, -1); + module_path = module_path_copy; + } + + if (g_pattern_match_string (module_spec, module_name) || + g_pattern_match_string (module_spec, module_path)) + { + GArray * functions; + guint i; + + functions = gum_module_metadata_get_functions (module); + + for (i = 0; i != functions->len; i++) + { + const GumFunctionMetadata * f = + &g_array_index (functions, GumFunctionMetadata, i); + + if (g_pattern_match_string (func_spec, f->name)) + { + GumApiDetails details; + + details.name = g_strconcat ( + module->path, + "!", + f->name, + NULL); + details.address = f->address; + details.size = GUM_API_SIZE_NONE; + + carry_on = func (&details, user_data); + + g_free ((gpointer) details.name); + } + } + } + + g_free (module_path_copy); + g_free (module_name_copy); + } + + g_hash_table_unref (seen_modules); + + g_pattern_spec_free (func_spec); + g_pattern_spec_free (module_spec); + + g_free (func_query); + g_free (module_query); + + return; + +unsupported_runtime: + { + g_set_error (error, GUM_ERROR, GUM_ERROR_NOT_SUPPORTED, + "unsupported Swift runtime; please file a bug"); + } +invalid_query: + { + g_set_error (error, GUM_ERROR, GUM_ERROR_INVALID_ARGUMENT, + "invalid query; format is: *someModule*!SomeClassPrefix*.*secret*()"); + } +} + +static void +gum_module_metadata_unref (GumModuleMetadata * module) +{ + module->ref_count--; + if (module->ref_count == 0) + { + if (module->functions != NULL) + g_array_unref (module->functions); + + g_slice_free (GumModuleMetadata, module); + } +} + +static GArray * +gum_module_metadata_get_functions (GumModuleMetadata * self) +{ + if (self->functions == NULL) + { + self->functions = g_array_new (FALSE, FALSE, sizeof (GumFunctionMetadata)); + g_array_set_clear_func (self->functions, + (GDestroyNotify) gum_function_metadata_free); + gum_module_enumerate_exports (self->path, + gum_module_metadata_collect_export, self); + } + + return self->functions; +} + +static gboolean +gum_module_metadata_collect_export (const GumExportDetails * details, + gpointer user_data) +{ + GumModuleMetadata * module = user_data; + gchar name[512]; + size_t len; + GumFunctionMetadata func; + + if (details->type != GUM_EXPORT_FUNCTION) + return TRUE; + + len = module->resolver->demangle (details->name, name, sizeof (name)); + if (len == 0) + return TRUE; + + func.name = g_strdup (name); + func.address = details->address; + g_array_append_val (module->functions, func); + + return TRUE; +} + +static void +gum_function_metadata_free (GumFunctionMetadata * function) +{ + g_free (function->name); +} + +#endif diff --git a/gum/gumswiftapiresolver.h b/gum/gumswiftapiresolver.h new file mode 100644 index 0000000000..a57f908375 --- /dev/null +++ b/gum/gumswiftapiresolver.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2023 Ole André Vadla Ravnås + * Copyright (C) 2023 Håvard Sørbø + * + * Licence: wxWindows Library Licence, Version 3.1 + */ + +#ifndef __GUM_SWIFT_API_RESOLVER_H__ +#define __GUM_SWIFT_API_RESOLVER_H__ + +#include + +G_BEGIN_DECLS + +#define GUM_TYPE_SWIFT_API_RESOLVER (gum_swift_api_resolver_get_type ()) +GUM_DECLARE_FINAL_TYPE (GumSwiftApiResolver, gum_swift_api_resolver, GUM, + SWIFT_API_RESOLVER, GObject) + +GUM_API GumApiResolver * gum_swift_api_resolver_new (void); + +G_END_DECLS + +#endif diff --git a/gum/meson.build b/gum/meson.build index 9b9d89d5af..a8966667c4 100644 --- a/gum/meson.build +++ b/gum/meson.build @@ -33,6 +33,7 @@ gum_headers = [ 'gumreturnaddress.h', 'gumspinlock.h', 'gumstalker.h', + 'gumswiftapiresolver.h', 'gumsymbolutil.h', 'gumsysinternals.h', 'gumtls.h', @@ -67,6 +68,7 @@ gum_sources = [ 'gumprocess.c', 'gumreturnaddress.c', 'gumstalker.c', + 'gumswiftapiresolver.c', 'arch-x86/gumx86writer.c', 'arch-x86/gumx86relocator.c', 'arch-x86/gumx86reader.c', diff --git a/tests/core/apiresolver.c b/tests/core/apiresolver.c index b9ff219365..aaa27abb68 100644 --- a/tests/core/apiresolver.c +++ b/tests/core/apiresolver.c @@ -1,5 +1,6 @@ /* * Copyright (C) 2016-2023 Ole André Vadla Ravnås + * Copyright (C) 2023 Håvard Sørbø * * Licence: wxWindows Library Licence, Version 3.1 */ @@ -16,6 +17,7 @@ TESTLIST_BEGIN (api_resolver) #ifdef HAVE_DARWIN TESTENTRY (objc_method_can_be_resolved_from_class_method_address) TESTENTRY (objc_method_can_be_resolved_from_instance_method_address) + TESTENTRY (swift_method_can_be_resolved) #endif #ifdef HAVE_ANDROID TESTENTRY (linker_exports_can_be_resolved_on_android) @@ -243,6 +245,23 @@ TESTCASE (objc_method_can_be_resolved_from_instance_method_address) g_free (method); } +TESTCASE (swift_method_can_be_resolved) +{ + GumAddress address; + GError * error = NULL; + + fixture->resolver = gum_api_resolver_make ("swift"); + if (fixture->resolver == NULL) + { + g_print (" "); + return; + } + + gum_api_resolver_enumerate_matches (fixture->resolver, + "*!*", resolve_method_impl, &address, &error); + g_assert_no_error (error); +} + static gboolean resolve_method_impl (const GumApiDetails * details, gpointer user_data)