From ee136d4fd19d70b972cbc45d03ba6d85052a6d01 Mon Sep 17 00:00:00 2001 From: lico-n <85761230+lico-n@users.noreply.github.com> Date: Sun, 6 Aug 2023 07:55:55 +0200 Subject: [PATCH] allow loading arbitrary so lib files (#8) * feat: add support to inject arbitrary libraries * adjust readme for arbitrary libraries * bump module version * remove unnecessary submodules from ci * adjust readme --- .github/workflows/ci.yml | 2 -- README.md | 41 +++++++++++++++++++++++++++++-------- cpplint.cfg | 2 +- module.gradle | 4 ++-- module/src/jni/config.cpp | 42 +++++++++++++++++++++++++++++++------- module/src/jni/config.h | 5 ++++- module/src/jni/inject.cpp | 43 +++++++++++++++++++++++---------------- 7 files changed, 100 insertions(+), 39 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 75440bc..ec37a9c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,8 +13,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - with: - submodules: 'true' - uses: actions/setup-java@5ffc13f4174014e2d4d4572b3d74c3fa61aeb2c2 # v3.11.0 with: distribution: temurin diff --git a/README.md b/README.md index 423ad7f..3291457 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ more stealthy way. - The gadget is not embedded into the APK itself. So APK Integrity/Signature checks will still pass. - The process is not being ptraced like it is with frida-server. Avoiding ptrace based detection. - Control about the injection time of the gadget. +- Allows you to load multiple arbitrary libraries into the process. This repo also provides a [Riru](https://github.com/RikkaApps/Riru) flavor in case you are still using riru with an older magisk version rather than zygisk. @@ -40,21 +41,47 @@ might run checks at start up and delaying the injection can help avoid these. `/data/local/tmp/re.zyg.fri/target_packages` accepts a start up delay in milliseconds. You can provide it separated by a comma from the package_name. -f.e. `adb shell 'su -c "echo com.example.package,20000 > /data/local/tmp/re.zyg.fri/target_packages"'` +f.e. +``` +adb shell 'su -c "echo com.example.package,20000 > /data/local/tmp/re.zyg.fri/target_packages"' +``` would inject the gadget after a delay of 20 seconds. -You get a 10 seconds countdown to injection time in the ZygiskFrida logs `adb logcat -S ZygiskFrida`. -This can help time if you want to time the injection with app interactions. +You get a 10 seconds countdown to injection in the ZygiskFrida logs `adb logcat -S ZygiskFrida`. +This can help if you want to time the injection with app interactions. **Gadget version and config** -The gadget started is located at `/data/local/tmp/re.zyg.fri/libgadget.so`.\ -You can follow the [Gadget Docs](https://frida.re/docs/gadget/) and add an additional +The bundled gadget is located at `/data/local/tmp/re.zyg.fri/libgadget.so`.\ +You can follow the [Gadget Docs](https://frida.re/docs/gadget/) and add additional gadget config and scripts in that location. In case you want to use a different gadget version than the one bundled, you can simply replace the `libgadget.so` with your own frida gadget. +**Loading arbitrary libraries** + +This module also allows you to load arbitrary .so libraries into the process.\ +This can allow you to load additional helper libraries for the gadget or +enable any other use case that might need libraries loaded into the app process. + +For this you can add the file `/data/local/tmp/re.zyg.fri/injected_libraries`.\ +The file should consist of file paths to libraries. +The libraries are loaded in the order they are specified in the file. + +Example file content that would first load libhelperexample.so and then the bundled frida-gadget: +``` +/data/local/tmp/re.zyg.fri/libhelperexample.so +/data/local/tmp/re.zyg.fri/libgadget.so +``` + +Make sure the libraries are located somewhere accessible by the app and that +file permissions are properly set. + +If you want the frida gadget to start, you need to explicitly specify the bundled frida-gadget at +`/data/local/tmp/re.zyg.fri/libgadget.so`.\ +You can also choose to specify your own gadget this way or omit the gadget altogether. + ## How to build - Checkout the project @@ -67,10 +94,6 @@ You can also build and install the module to your device directly with `./gradle - For emulators this will start the gadget in native realm. This means that you will be able to hook Java but not native functions. -- This is not yet tested very well on different devices.\ - In case this is not working reports with logs `adb logcat -S ZygiskFrida` are welcome. - - ## Credits - Inspired by https://github.com/Perfare/Zygisk-Il2CppDumper diff --git a/cpplint.cfg b/cpplint.cfg index 0c3899d..5b5024d 100644 --- a/cpplint.cfg +++ b/cpplint.cfg @@ -1,4 +1,4 @@ -linelength=100 +linelength=120 filter=-build/c++11 filter=-build/header_guard diff --git a/module.gradle b/module.gradle index 353e9f0..6d9f12d 100644 --- a/module.gradle +++ b/module.gradle @@ -4,8 +4,8 @@ ext { moduleName = "ZygiskFrida" moduleAuthor = "lico-n" moduleDescription = "Injects frida gadget via zygisk." - moduleVersion = "v1.2.0" - moduleVersionCode = 3 + moduleVersion = "v1.3.0" + moduleVersionCode = 4 // Riru moduleMinRiruApiVersion = 24 diff --git a/module/src/jni/config.cpp b/module/src/jni/config.cpp index 8af6e28..7233909 100644 --- a/module/src/jni/config.cpp +++ b/module/src/jni/config.cpp @@ -3,9 +3,8 @@ #include #include #include -#include -static std::vector split(std::string const& str, char delimiter) { +static std::vector split(std::string const &str, char delimiter) { std::vector result; std::stringstream ss(str); @@ -17,23 +16,51 @@ static std::vector split(std::string const& str, char delimiter) { return result; } -static std::unique_ptr parse_target_config(std::string const& line) { - std::unique_ptr tcfg (new target_config); +// parse_injected_libraries parses a config line from the `target_packages` file. +// Each line consist of the app package name for the app that will be targeted by this module. +// Optionally there might be a start up delay in milliseconds specified separated by a comma +// from the package name. +static std::unique_ptr parse_target_config(std::string const &line) { + std::unique_ptr tcfg(new target_config); auto parts = split(line, ','); tcfg->app_name = parts[0]; if (parts.size() >= 2) { - tcfg->start_up_delay = std::stoul(parts[1]); + tcfg->start_up_delay_ms = std::stoul(parts[1]); } else { - tcfg->start_up_delay = 0; + tcfg->start_up_delay_ms = 0; } return tcfg; } -std::unique_ptr load_config(std::string const& module_dir, std::string const& app_name) { +// parse_injected_libraries returns a vector with all libraries that will be injected +// into the process. By default this is the bundled frida-gadget. +// If the file `injected_libraries` exists in the re.zyg.fri directory, then +// it will load the libraries specified in that file. One lib file path per line. +static std::vector parse_injected_libraries(std::string const &module_dir) { + auto config_file_path = module_dir + "/injected_libraries"; + + std::ifstream config_file(config_file_path); + if (!config_file.is_open()) { + return {module_dir + "/libgadget.so"}; + } + + std::vector injected_libraries; + + std::string libpath; + while (getline(config_file, libpath)) { + if (!libpath.empty()) { + injected_libraries.push_back(libpath); + } + } + + return injected_libraries; +} + +std::unique_ptr load_config(std::string const &module_dir, std::string const &app_name) { auto config_file_path = module_dir + "/target_packages"; std::ifstream config_file(config_file_path); @@ -45,6 +72,7 @@ std::unique_ptr load_config(std::string const& module_dir, std::s while (getline(config_file, line)) { auto cfg = parse_target_config(line); if (cfg->app_name == app_name) { + cfg->injected_libraries = parse_injected_libraries(module_dir); return cfg; } } diff --git a/module/src/jni/config.h b/module/src/jni/config.h index be58e2c..e9356d9 100644 --- a/module/src/jni/config.h +++ b/module/src/jni/config.h @@ -1,11 +1,14 @@ #ifndef ZYGISKFRIDA_CONFIG_H #define ZYGISKFRIDA_CONFIG_H +#include #include +#include struct target_config{ std::string app_name; - uint64_t start_up_delay; + uint64_t start_up_delay_ms; + std::vector injected_libraries; }; std::unique_ptr load_config(std::string const& module_dir, std::string const& app_name); diff --git a/module/src/jni/inject.cpp b/module/src/jni/inject.cpp index d833c21..df438bd 100644 --- a/module/src/jni/inject.cpp +++ b/module/src/jni/inject.cpp @@ -4,9 +4,11 @@ #include #include #include +#include #include #include #include +#include #include "config.h" #include "log.h" @@ -22,7 +24,7 @@ static std::string get_process_name() { return buffer.str(); } -static void wait_for_init(std::string const& app_name) { +static void wait_for_init(std::string const &app_name) { LOGI("Wait for process to complete init"); // wait until the process is renamed to the package name @@ -36,15 +38,15 @@ static void wait_for_init(std::string const& app_name) { LOGI("Process init completed"); } -static void delay_start_up(uint64_t start_up_delay) { - if (start_up_delay <= 0) { +static void delay_start_up(uint64_t start_up_delay_ms) { + if (start_up_delay_ms <= 0) { return; } - LOGI("Waiting for configured start up delay %" PRIu64"ms", start_up_delay); + LOGI("Waiting for configured start up delay %" PRIu64"ms", start_up_delay_ms); int countdown = 0; - uint64_t delay = start_up_delay; + uint64_t delay = start_up_delay_ms; for (int i = 0; i < 10 && delay > 1000; i++) { delay -= 1000; @@ -54,32 +56,33 @@ static void delay_start_up(uint64_t start_up_delay) { std::this_thread::sleep_for(std::chrono::milliseconds(delay)); for (int i = countdown; i > 0; i--) { - LOGI("Starting gadget in %d seconds", i); + LOGI("Injecting libs in %d seconds", i); std::this_thread::sleep_for(std::chrono::seconds(1)); } } -void inject_gadget(std::string const gadget_path, std::unique_ptr cfg) { +void inject_libs(std::unique_ptr cfg) { // We need to wait for process initialization to complete. // Loading the gadget before that will freeze the process // before the init has completed. This make the process // undiscoverable or otherwise cause issue attaching. wait_for_init(cfg->app_name); - delay_start_up(cfg->start_up_delay); + delay_start_up(cfg->start_up_delay_ms); - LOGI("Starting gadget %s", gadget_path.c_str()); - auto *handle = xdl_open(gadget_path.c_str(), XDL_ALWAYS_FORCE_LOAD); - if (handle) { - LOGI("Gadget connected"); - } else { - LOGE("Failed to start gadget: %s", dlerror()); + for (auto & lib_path : cfg->injected_libraries) { + LOGI("Injecting %s", lib_path.c_str()); + auto *handle = xdl_open(lib_path.c_str(), XDL_TRY_FORCE_LOAD); + if (handle) { + LOGI("Injected %s", lib_path.c_str()); + } else { + LOGE("Failed to inject %s : %s", lib_path.c_str(), dlerror()); + } } } -bool check_and_inject(std::string const& app_name) { +bool check_and_inject(std::string const &app_name) { std::string module_dir = std::string("/data/local/tmp/re.zyg.fri"); - std::string gadget_path = module_dir + "/libgadget.so"; std::unique_ptr cfg = load_config(module_dir, app_name); if (cfg == nullptr) { @@ -88,7 +91,13 @@ bool check_and_inject(std::string const& app_name) { LOGI("App detected: %s", app_name.c_str()); - std::thread inject_thread(inject_gadget, gadget_path, std::move(cfg)); + if (cfg->injected_libraries.empty()) { + LOGI("No libraries configured for injection. " + "Check the content of the `injected_libraries` file or delete it."); + return false; + } + + std::thread inject_thread(inject_libs, std::move(cfg)); inject_thread.detach(); return true;