diff --git a/lib/gadget/gadget.vala b/lib/gadget/gadget.vala index 3045a0e57..9b2aa92b2 100644 --- a/lib/gadget/gadget.vala +++ b/lib/gadget/gadget.vala @@ -751,7 +751,7 @@ namespace Frida.Gadget { string config_data; try { - FileUtils.get_contents (config_path, out config_data); + load_asset_text (config_path, out config_data); } catch (FileError e) { if (e is FileError.NOENT) return new Config (); @@ -1226,7 +1226,7 @@ namespace Frida.Gadget { private ScriptConfig load_config (string path) throws Error { string data; try { - FileUtils.get_contents (path, out data); + load_asset_text (path, out data); } catch (FileError e) { if (e is FileError.NOENT) return new ScriptConfig (); @@ -1349,9 +1349,9 @@ namespace Frida.Gadget { try { var path = this.path; - uint8[] contents; + Bytes contents; try { - FileUtils.get_data (path, out contents); + load_asset_bytes (path, out contents); } catch (FileError e) { throw new Error.INVALID_ARGUMENT ("%s", e.message); } @@ -1360,11 +1360,10 @@ namespace Frida.Gadget { options.name = Path.get_basename (path).split (".", 2)[0]; ScriptEngine.ScriptInstance instance; - if (contents.length > 0 && contents[0] == QUICKJS_BYTECODE_MAGIC) { - instance = yield engine.create_script (null, new Bytes (contents), options); - } else { - instance = yield engine.create_script ((string) contents, null, options); - } + if (contents.length > 0 && contents[0] == QUICKJS_BYTECODE_MAGIC) + instance = yield engine.create_script (null, contents, options); + else + instance = yield engine.create_script ((string) contents.get_data (), null, options); if (id.handle != 0) yield engine.destroy_script (id); @@ -2076,6 +2075,60 @@ namespace Frida.Gadget { } #endif + private void load_asset_text (string filename, out string text) throws FileError { + Bytes raw_contents; + load_asset_bytes (filename, out raw_contents); + + unowned string str = (string) raw_contents.get_data (); + if (!str.validate ()) + throw new FileError.FAILED ("%s: invalid UTF-8", filename); + + text = str; + } + + private void load_asset_bytes (string filename, out Bytes bytes) throws FileError { +#if ANDROID + if (maybe_load_asset_bytes_from_apk (filename, out bytes)) + return; +#endif + + uint8[] data; + FileUtils.get_data (filename, out data); + bytes = new Bytes.take ((owned) data); + } + +#if ANDROID + private bool maybe_load_asset_bytes_from_apk (string filename, out Bytes contents) throws FileError { + contents = null; + + var tokens = filename.split ("!", 2); + if (tokens.length != 2 || !tokens[0].has_suffix (".apk")) + return false; + unowned string apk_path = tokens[0]; + unowned string file_path = tokens[1]; + + var reader = Minizip.Reader.create (); + try { + if (reader.open_file (apk_path) != OK) + throw new FileError.FAILED ("Unable to open APK"); + + if (reader.locate_entry (file_path[1:], true) != OK) + throw new FileError.FAILED ("Unable to locate %s inside APK", file_path); + + var size = reader.entry_save_buffer_length (); + var data = new uint8[size + 1]; + if (reader.entry_save_buffer (data[:size]) != OK) + throw new FileError.FAILED ("Unable to extract %s from APK", file_path); + + contents = new Bytes.take ((owned) data); + return true; + } finally { + reader.close (); + Minizip.Reader.destroy (ref reader); + } + } +#endif + private Json.Node make_empty_json_object () { return new Json.Node.alloc ().init_object (new Json.Object ()); } diff --git a/lib/gadget/meson.build b/lib/gadget/meson.build index da282085f..00ea0c8a9 100644 --- a/lib/gadget/meson.build +++ b/lib/gadget/meson.build @@ -29,6 +29,11 @@ if host_os_family == 'darwin' extra_link_args += '-Wl,-framework,Foundation' endif +if host_os == 'android' + extra_vala_args += '--pkg=minizip' + platform_deps += minizip_dep +endif + if host_os_family == 'windows' if host_toolchain != 'microsoft' symfile = 'frida-gadget.symbols' diff --git a/meson.build b/meson.build index c045aecc7..ad63c67c7 100644 --- a/meson.build +++ b/meson.build @@ -347,6 +347,13 @@ else gio_unix_dep = dependency('gio-unix-2.0') endif +if host_os == 'android' + minizip_dep = dependency('minizip', required: false, default_options: [ + 'zlib=enabled', + 'lzma=disabled', + ]) +endif + have_local_backend = get_option('local_backend').allowed() have_fruity_backend = get_option('fruity_backend') \ .disable_auto_if(not host_docks_mobile_devices) \ diff --git a/vapi/minizip.vapi b/vapi/minizip.vapi index 267da344e..9c7c0688c 100644 --- a/vapi/minizip.vapi +++ b/vapi/minizip.vapi @@ -1,3 +1,51 @@ [CCode (cprefix = "", gir_namespace = "Minizip", gir_version = "1.0", lower_case_cprefix = "mz_")] namespace Minizip { + [SimpleType] + [CCode (cheader_filename = "minizip/mz_strm.h,minizip/mz_zip.h,minizip/mz_zip_rw.h", cname = "gpointer", cprefix = "mz_zip_reader_", + has_destroy_function = false)] + public struct Reader { + public static Reader create (out Reader stream = null); + [CCode (cname = "mz_zip_reader_delete")] + public static void destroy (ref Reader stream); + + public Status open_file (string path); + public Status close (); + + public Status locate_entry (string filename, bool ignore_case); + + public Status entry_save_file (string path); + public Status entry_save_buffer (uint8[] buf); + public int32 entry_save_buffer_length (); + } + + [CCode (cheader_filename = "minizip/mz.h", cname = "int32_t", cprefix = "MZ_", has_type_id = false)] + public enum Status { + OK, + STREAM_ERROR, + DATA_ERROR, + MEM_ERROR, + BUF_ERROR, + VERSION_ERROR, + + END_OF_LIST, + END_OF_STREAM, + + PARAM_ERROR, + FORMAT_ERROR, + INTERNAL_ERROR, + CRC_ERROR, + CRYPT_ERROR, + EXIST_ERROR, + PASSWORD_ERROR, + SUPPORT_ERROR, + HASH_ERROR, + OPEN_ERROR, + CLOSE_ERROR, + SEEK_ERROR, + TELL_ERROR, + READ_ERROR, + WRITE_ERROR, + SIGN_ERROR, + SYMLINK_ERROR, + } }