From 1076c1e711e73be745cbd0f53042d936719a6b79 Mon Sep 17 00:00:00 2001 From: jiqiu2021 Date: Wed, 25 Jun 2025 21:21:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=88=90=E5=8A=9F=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E4=BA=86so=20=E5=B9=B6=E4=B8=94=E8=B0=83=E7=94=A8=E4=BA=86riru?= =?UTF-8?q?=20hide=E7=AD=89=E9=80=BB=E8=BE=91=20=E5=87=86=E5=A4=87?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=87=AA=E5=AE=9A=E4=B9=89linker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- configapp/build.gradle | 4 +- .../com/jiqiu/configapp/AppListFragment.java | 2 + .../com/jiqiu/configapp/ConfigManager.java | 199 +++++++++++++++++- .../jiqiu/configapp/SoManagerFragment.java | 2 + module/src/main/cpp/config.cpp | 2 + module/src/main/cpp/hack.h | 2 +- module/src/main/cpp/hack_new.cpp | 83 +++----- module/src/main/cpp/main.cpp | 15 +- 8 files changed, 246 insertions(+), 63 deletions(-) diff --git a/configapp/build.gradle b/configapp/build.gradle index 19abb87..75ceb89 100644 --- a/configapp/build.gradle +++ b/configapp/build.gradle @@ -23,8 +23,8 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 } } diff --git a/configapp/src/main/java/com/jiqiu/configapp/AppListFragment.java b/configapp/src/main/java/com/jiqiu/configapp/AppListFragment.java index 0c9927e..0916b71 100644 --- a/configapp/src/main/java/com/jiqiu/configapp/AppListFragment.java +++ b/configapp/src/main/java/com/jiqiu/configapp/AppListFragment.java @@ -56,6 +56,8 @@ public class AppListFragment extends Fragment implements AppListAdapter.OnAppTog super.onViewCreated(view, savedInstanceState); configManager = new ConfigManager(requireContext()); + // Ensure module directories exist + configManager.ensureModuleDirectories(); initViews(view); setupRecyclerView(); diff --git a/configapp/src/main/java/com/jiqiu/configapp/ConfigManager.java b/configapp/src/main/java/com/jiqiu/configapp/ConfigManager.java index 7483825..e1c2e7e 100644 --- a/configapp/src/main/java/com/jiqiu/configapp/ConfigManager.java +++ b/configapp/src/main/java/com/jiqiu/configapp/ConfigManager.java @@ -27,13 +27,17 @@ public class ConfigManager { // Configure Shell to use root Shell.enableVerboseLogging = BuildConfig.DEBUG; Shell.setDefaultBuilder(Shell.Builder.create() - .setFlags(Shell.FLAG_REDIRECT_STDERR) - .setTimeout(10)); + .setFlags(Shell.FLAG_REDIRECT_STDERR | Shell.FLAG_MOUNT_MASTER) + .setTimeout(30)); } public ConfigManager(Context context) { this.context = context; this.gson = new GsonBuilder().setPrettyPrinting().create(); + + // Ensure we get root shell on creation + Shell.getShell(); + loadConfig(); } @@ -42,8 +46,32 @@ public class ConfigManager { } public void ensureModuleDirectories() { - Shell.cmd("mkdir -p " + MODULE_PATH).exec(); - Shell.cmd("mkdir -p " + SO_STORAGE_DIR).exec(); + // Check root access first + if (!isRootAvailable()) { + Log.e(TAG, "Root access not available!"); + return; + } + + // Create module directories + Shell.Result result1 = Shell.cmd("mkdir -p " + MODULE_PATH).exec(); + if (!result1.isSuccess()) { + Log.e(TAG, "Failed to create module directory: " + MODULE_PATH); + } + + Shell.Result result2 = Shell.cmd("mkdir -p " + SO_STORAGE_DIR).exec(); + if (!result2.isSuccess()) { + Log.e(TAG, "Failed to create SO storage directory: " + SO_STORAGE_DIR); + } + + // Set permissions + Shell.cmd("chmod 755 " + MODULE_PATH).exec(); + Shell.cmd("chmod 755 " + SO_STORAGE_DIR).exec(); + + // Verify directories exist + Shell.Result verify = Shell.cmd("ls -la " + MODULE_PATH).exec(); + if (verify.isSuccess()) { + Log.i(TAG, "Module directory ready: " + String.join("\n", verify.getOut())); + } } private void loadConfig() { @@ -94,6 +122,13 @@ public class ConfigManager { } appConfig.enabled = enabled; saveConfig(); + + // 自动部署或清理 SO 文件 + if (enabled) { + deploySoFilesToApp(packageName); + } else { + cleanupAppSoFiles(packageName); + } } public List getAppSoFiles(String packageName) { @@ -163,6 +198,11 @@ public class ConfigManager { // Add reference to the global SO file appConfig.soFiles.add(globalSoFile); saveConfig(); + + // If app is enabled, deploy the new SO file + if (appConfig.enabled) { + deploySoFilesToApp(packageName); + } } public void removeSoFileFromApp(String packageName, SoFile soFile) { @@ -171,6 +211,11 @@ public class ConfigManager { appConfig.soFiles.removeIf(s -> s.storedPath.equals(soFile.storedPath)); saveConfig(); + + // If app is enabled, re-deploy to update SO files + if (appConfig.enabled) { + deploySoFilesToApp(packageName); + } } public boolean getHideInjection() { @@ -182,6 +227,152 @@ public class ConfigManager { saveConfig(); } + // Copy SO files directly to app's data directory + private void deploySoFilesToApp(String packageName) { + AppConfig appConfig = config.perAppConfig.get(packageName); + if (appConfig == null || appConfig.soFiles.isEmpty()) { + Log.w(TAG, "No SO files to deploy for: " + packageName); + return; + } + + // First check if we have root access + if (!Shell.getShell().isRoot()) { + Log.e(TAG, "No root access available!"); + return; + } + + // Create files directory in app's data dir + String filesDir = "/data/data/" + packageName + "/files"; + + // Use su -c for better compatibility + Shell.Result mkdirResult = Shell.cmd("su -c 'mkdir -p " + filesDir + "'").exec(); + if (!mkdirResult.isSuccess()) { + Log.e(TAG, "Failed to create directory: " + filesDir); + Log.e(TAG, "Error: " + String.join("\n", mkdirResult.getErr())); + // Try without su -c + mkdirResult = Shell.cmd("mkdir -p " + filesDir).exec(); + if (!mkdirResult.isSuccess()) { + Log.e(TAG, "Also failed without su -c"); + return; + } + } + + // Set proper permissions and ownership + Shell.cmd("chmod 755 " + filesDir).exec(); + + // Get UID for the package + Shell.Result uidResult = Shell.cmd("stat -c %u /data/data/" + packageName).exec(); + String uid = ""; + if (uidResult.isSuccess() && !uidResult.getOut().isEmpty()) { + uid = uidResult.getOut().get(0).trim(); + Log.i(TAG, "Package UID: " + uid); + } + + // Copy each SO file configured for this app + for (SoFile soFile : appConfig.soFiles) { + // Extract mapped filename + String mappedName = new File(soFile.storedPath).getName(); + String destPath = filesDir + "/" + mappedName; + + // Check if source file exists + Shell.Result checkResult = Shell.cmd("test -f \"" + soFile.storedPath + "\" && echo 'exists'").exec(); + if (!checkResult.isSuccess() || checkResult.getOut().isEmpty()) { + Log.e(TAG, "Source SO file not found: " + soFile.storedPath); + continue; + } + + Log.i(TAG, "Copying: " + soFile.storedPath + " to " + destPath); + + // Copy file using cat to avoid permission issues + String copyCmd = "cat \"" + soFile.storedPath + "\" > \"" + destPath + "\""; + Shell.Result result = Shell.cmd(copyCmd).exec(); + + if (!result.isSuccess()) { + Log.e(TAG, "Failed with cat, trying cp"); + // Fallback to cp + result = Shell.cmd("cp -f \"" + soFile.storedPath + "\" \"" + destPath + "\"").exec(); + } + + // Set permissions + Shell.cmd("chmod 755 \"" + destPath + "\"").exec(); + + // Set ownership if we have the UID + if (!uid.isEmpty()) { + Shell.cmd("chown " + uid + ":" + uid + " \"" + destPath + "\"").exec(); + } + + // Verify the file was copied + Shell.Result verifyResult = Shell.cmd("ls -la \"" + destPath + "\" 2>/dev/null").exec(); + if (verifyResult.isSuccess() && !verifyResult.getOut().isEmpty()) { + Log.i(TAG, "Successfully deployed: " + String.join(" ", verifyResult.getOut())); + } else { + Log.e(TAG, "Failed to verify SO file copy: " + destPath); + // Try another verification method + Shell.Result sizeResult = Shell.cmd("stat -c %s \"" + destPath + "\" 2>/dev/null").exec(); + if (sizeResult.isSuccess() && !sizeResult.getOut().isEmpty()) { + Log.i(TAG, "File exists with size: " + sizeResult.getOut().get(0) + " bytes"); + } + } + } + + Log.i(TAG, "Deployment complete for: " + packageName); + } + + // Clean up deployed SO files when app is disabled + private void cleanupAppSoFiles(String packageName) { + AppConfig appConfig = config.perAppConfig.get(packageName); + if (appConfig == null || appConfig.soFiles.isEmpty()) { + Log.w(TAG, "No SO files to clean up for: " + packageName); + return; + } + + // First check if we have root access + if (!Shell.getShell().isRoot()) { + Log.e(TAG, "No root access available!"); + return; + } + + String filesDir = "/data/data/" + packageName + "/files"; + + // Only delete the SO files we deployed, not the entire directory + for (SoFile soFile : appConfig.soFiles) { + String mappedName = new File(soFile.storedPath).getName(); + String filePath = filesDir + "/" + mappedName; + + Log.i(TAG, "Cleaning up: " + filePath); + + // Check if file exists before trying to delete + Shell.Result checkResult = Shell.cmd("test -f \"" + filePath + "\" && echo 'exists'").exec(); + if (checkResult.isSuccess() && !checkResult.getOut().isEmpty()) { + // Try to remove the file + Shell.Result result = Shell.cmd("rm -f \"" + filePath + "\"").exec(); + + // Verify deletion + Shell.Result verifyResult = Shell.cmd("test -f \"" + filePath + "\" && echo 'still_exists'").exec(); + if (!verifyResult.isSuccess() || verifyResult.getOut().isEmpty()) { + Log.i(TAG, "Successfully deleted SO file: " + filePath); + } else { + Log.e(TAG, "Failed to delete SO file: " + filePath); + // Try with su -c + Shell.cmd("su -c 'rm -f \"" + filePath + "\"'").exec(); + } + } else { + Log.w(TAG, "SO file not found for cleanup: " + filePath); + } + } + + Log.i(TAG, "Cleanup complete for: " + packageName); + } + + // Deploy SO files for all enabled apps + public void deployAllSoFiles() { + for (Map.Entry entry : config.perAppConfig.entrySet()) { + if (entry.getValue().enabled) { + deploySoFilesToApp(entry.getKey()); + } + } + } + // Data classes public static class ModuleConfig { public boolean enabled = true; diff --git a/configapp/src/main/java/com/jiqiu/configapp/SoManagerFragment.java b/configapp/src/main/java/com/jiqiu/configapp/SoManagerFragment.java index e996bbd..1f4ea15 100644 --- a/configapp/src/main/java/com/jiqiu/configapp/SoManagerFragment.java +++ b/configapp/src/main/java/com/jiqiu/configapp/SoManagerFragment.java @@ -43,6 +43,8 @@ public class SoManagerFragment extends Fragment { super.onCreate(savedInstanceState); configManager = new ConfigManager(requireContext()); + // Ensure module directories exist + configManager.ensureModuleDirectories(); // Initialize file picker filePickerLauncher = registerForActivityResult( diff --git a/module/src/main/cpp/config.cpp b/module/src/main/cpp/config.cpp index c4ce30f..1f3cab3 100644 --- a/module/src/main/cpp/config.cpp +++ b/module/src/main/cpp/config.cpp @@ -175,8 +175,10 @@ namespace Config { auto it = g_config.perAppConfig.find(packageName); if (it != g_config.perAppConfig.end()) { + LOGD("Found app config for %s with %zu SO files", packageName.c_str(), it->second.soFiles.size()); return it->second.soFiles; } + LOGD("No app config found for %s", packageName.c_str()); return {}; } diff --git a/module/src/main/cpp/hack.h b/module/src/main/cpp/hack.h index 30912ed..0f787bf 100644 --- a/module/src/main/cpp/hack.h +++ b/module/src/main/cpp/hack.h @@ -7,6 +7,6 @@ #include -void hack_prepare(const char *game_data_dir, void *data, size_t length); +void hack_prepare(const char *game_data_dir, const char *package_name, void *data, size_t length); #endif //ZYGISK_IL2CPPDUMPER_HACK_H diff --git a/module/src/main/cpp/hack_new.cpp b/module/src/main/cpp/hack_new.cpp index 6274132..68c5f21 100644 --- a/module/src/main/cpp/hack_new.cpp +++ b/module/src/main/cpp/hack_new.cpp @@ -7,63 +7,51 @@ #include #include #include +#include + +// External function from newriruhide.cpp +extern "C" void riru_hide(const char *name); void load_so_file(const char *game_data_dir, const Config::SoFile &soFile) { - char dest_path[512]; - snprintf(dest_path, sizeof(dest_path), "%s/files/%s", game_data_dir, soFile.name.c_str()); + // Extract the mapped filename from storedPath (e.g., "1750851324251_libmylib.so") + const char *mapped_name = strrchr(soFile.storedPath.c_str(), '/'); + if (!mapped_name) { + mapped_name = soFile.storedPath.c_str(); + } else { + mapped_name++; // Skip the '/' + } - // Copy SO file from storage to app directory - int src_fd = open(soFile.storedPath.c_str(), O_RDONLY); - if (src_fd < 0) { - LOGE("Failed to open source SO: %s", soFile.storedPath.c_str()); + // The file should already be in app's files directory + char so_path[512]; + snprintf(so_path, sizeof(so_path), "%s/files/%s", game_data_dir, mapped_name); + + // Check if file exists + if (access(so_path, F_OK) != 0) { + LOGE("SO file not found: %s", so_path); return; } - int dest_fd = open(dest_path, O_WRONLY | O_CREAT | O_TRUNC, 0755); - if (dest_fd < 0) { - LOGE("Failed to create dest SO: %s", dest_path); - close(src_fd); - return; - } - - char buffer[4096]; - ssize_t bytes; - while ((bytes = read(src_fd, buffer, sizeof(buffer))) > 0) { - if (write(dest_fd, buffer, bytes) != bytes) { - LOGE("Failed to write SO file"); - close(src_fd); - close(dest_fd); - return; - } - } - - close(src_fd); - close(dest_fd); - chmod(dest_path, 0755); - // Load the SO file - void *handle = dlopen(dest_path, RTLD_NOW | RTLD_LOCAL); + void *handle = dlopen(so_path, RTLD_NOW | RTLD_LOCAL); if (handle) { - LOGI("Successfully loaded SO: %s", soFile.name.c_str()); + LOGI("Successfully loaded SO: %s (mapped: %s)", soFile.name.c_str(), mapped_name); // Hide if configured if (Config::shouldHideInjection()) { - // Call hide function if available - void (*hide_func)(const char*) = (void(*)(const char*))dlsym(handle, "riru_hide"); - if (hide_func) { - hide_func(soFile.name.c_str()); - } + // Hide using the mapped name since that's what we loaded + riru_hide(mapped_name); + LOGI("Applied riru_hide to: %s", mapped_name); } } else { - LOGE("Failed to load SO: %s - %s", dest_path, dlerror()); + LOGE("Failed to load SO: %s - %s", so_path, dlerror()); } } void hack_thread_func(const char *game_data_dir, const char *package_name) { LOGI("Hack thread started for package: %s", package_name); - // Wait a bit for app to initialize - sleep(1); + // Wait a bit for app to initialize and files to be copied + sleep(2); // Get SO files for this app auto soFiles = Config::getAppSoFiles(package_name); @@ -71,26 +59,13 @@ void hack_thread_func(const char *game_data_dir, const char *package_name) { // Load each SO file for (const auto &soFile : soFiles) { - LOGI("Loading SO: %s", soFile.name.c_str()); + LOGI("Loading SO: %s (stored as: %s)", soFile.name.c_str(), soFile.storedPath.c_str()); load_so_file(game_data_dir, soFile); } } -void hack_prepare(const char *game_data_dir, void *data, size_t length) { - // Get package name from game_data_dir - // Format: /data/user/0/com.example.app or /data/data/com.example.app - const char *package_name = nullptr; - if (strstr(game_data_dir, "/data/user/")) { - package_name = strrchr(game_data_dir, '/'); - if (package_name) package_name++; - } else if (strstr(game_data_dir, "/data/data/")) { - package_name = game_data_dir + strlen("/data/data/"); - } - - if (!package_name) { - LOGE("Failed to extract package name from: %s", game_data_dir); - return; - } +void hack_prepare(const char *game_data_dir, const char *package_name, void *data, size_t length) { + LOGI("hack_prepare called for package: %s, dir: %s", package_name, game_data_dir); std::thread hack_thread(hack_thread_func, game_data_dir, package_name); hack_thread.detach(); diff --git a/module/src/main/cpp/main.cpp b/module/src/main/cpp/main.cpp index 27edb64..59755dd 100644 --- a/module/src/main/cpp/main.cpp +++ b/module/src/main/cpp/main.cpp @@ -6,6 +6,9 @@ #include #include #include +#include +#include +#include #include "hack.h" #include "zygisk.hpp" #include "game.h" @@ -21,6 +24,7 @@ public: void onLoad(Api *api, JNIEnv *env) override { this->api = api; this->env = env; + enable_hack = false; } void preAppSpecialize(AppSpecializeArgs *args) override { @@ -38,7 +42,8 @@ public: void postAppSpecialize(const AppSpecializeArgs *) override { if (enable_hack) { - std::thread hack_thread(hack_prepare, _data_dir, data, length); + // Then start hack thread + std::thread hack_thread(hack_prepare, _data_dir, _package_name, data, length); hack_thread.detach(); } } @@ -48,9 +53,10 @@ private: JNIEnv *env; bool enable_hack; char *_data_dir; + char *_package_name; void *data; size_t length; - + void preSpecialize(const char *package_name, const char *app_data_dir) { // Read configuration Config::readConfig(); @@ -61,6 +67,11 @@ private: enable_hack = true; _data_dir = new char[strlen(app_data_dir) + 1]; strcpy(_data_dir, app_data_dir); + _package_name = new char[strlen(package_name) + 1]; + strcpy(_package_name, package_name); + + // ConfigApp is responsible for copying SO files + // We just need to load them #if defined(__i386__) auto path = "zygisk/armeabi-v7a.so";