From ef26f8a85799b6f22c690c0a36d77efe1636f49b Mon Sep 17 00:00:00 2001 From: ABS Date: Thu, 18 Dec 2025 20:48:47 +0000 Subject: [PATCH 01/15] v5.0 --- README.md | 42 +- app/build.gradle | 8 +- .../com/android/support/CrashHandler.java | 321 ++- .../main/java/com/android/support/Main.java | 12 +- .../main/java/com/android/support/Menu.java | 74 +- .../jni/And64InlineHook/And64InlineHook.cpp | 596 ------ .../jni/And64InlineHook/And64InlineHook.hpp | 42 - app/src/main/jni/And64InlineHook/LICENSE | 21 - app/src/main/jni/And64InlineHook/README.md | 7 - app/src/main/jni/Android.mk | 21 +- app/src/main/jni/Includes/Macros.h | 131 +- app/src/main/jni/Includes/Utils.cpp | 96 +- app/src/main/jni/Includes/Utils.hpp | 12 +- .../Includes/get_device_api_level_inlines.h | 49 - app/src/main/jni/KittyMemory/KittyArm64.cpp | 135 -- app/src/main/jni/KittyMemory/KittyArm64.hpp | 35 - app/src/main/jni/KittyMemory/KittyAsm.cpp | 698 ++++++ app/src/main/jni/KittyMemory/KittyAsm.hpp | 225 ++ app/src/main/jni/KittyMemory/KittyIOFile.cpp | 318 +++ app/src/main/jni/KittyMemory/KittyIOFile.hpp | 84 + app/src/main/jni/KittyMemory/KittyInclude.hpp | 15 +- app/src/main/jni/KittyMemory/KittyMemory.cpp | 555 +++-- app/src/main/jni/KittyMemory/KittyMemory.hpp | 280 +-- .../jni/KittyMemory/KittyPtrValidator.cpp | 276 +++ .../jni/KittyMemory/KittyPtrValidator.hpp | 283 +++ app/src/main/jni/KittyMemory/KittyScanner.cpp | 1887 +++++++++++++++-- app/src/main/jni/KittyMemory/KittyScanner.hpp | 563 ++++- app/src/main/jni/KittyMemory/KittyUtils.cpp | 377 +++- app/src/main/jni/KittyMemory/KittyUtils.hpp | 202 +- app/src/main/jni/Main.cpp | 143 +- app/src/main/jni/Menu/Jni.cpp | 16 +- app/src/main/jni/Menu/Jni.hpp | 4 +- app/src/main/jni/Menu/Menu.cpp | 22 +- app/src/main/jni/Menu/Menu.hpp | 4 +- app/src/main/jni/Menu/Setup.cpp | 22 +- app/src/main/jni/Substrate/Buffer.hpp | 38 - app/src/main/jni/Substrate/CydiaSubstrate.h | 152 -- app/src/main/jni/Substrate/SubstrateARM.hpp | 65 - app/src/main/jni/Substrate/SubstrateDebug.cpp | 97 - app/src/main/jni/Substrate/SubstrateDebug.hpp | 33 - app/src/main/jni/Substrate/SubstrateHook.cpp | 951 --------- app/src/main/jni/Substrate/SubstrateHook.h | 19 - app/src/main/jni/Substrate/SubstrateLog.hpp | 40 - .../jni/Substrate/SubstratePosixMemory.cpp | 76 - app/src/main/jni/Substrate/SubstrateX86.hpp | 200 -- app/src/main/jni/Substrate/SymbolFinder.cpp | 433 ---- app/src/main/jni/Substrate/SymbolFinder.h | 8 - app/src/main/jni/Substrate/hde64.c | 332 --- app/src/main/jni/Substrate/hde64.h | 112 - app/src/main/jni/Substrate/table64.h | 74 - app/src/main/jni/xDL/xdl.c | 1014 +++++++++ app/src/main/jni/xDL/xdl.h | 95 + app/src/main/jni/xDL/xdl.map.txt | 16 + app/src/main/jni/xDL/xdl_iterate.c | 297 +++ app/src/main/jni/xDL/xdl_iterate.h | 43 + app/src/main/jni/xDL/xdl_linker.c | 231 ++ app/src/main/jni/xDL/xdl_linker.h | 40 + app/src/main/jni/xDL/xdl_lzma.c | 187 ++ app/src/main/jni/xDL/xdl_lzma.h | 40 + app/src/main/jni/xDL/xdl_util.c | 95 + app/src/main/jni/xDL/xdl_util.h | 71 + build.gradle | 6 +- gradle.properties | 1 - gradle/wrapper/gradle-wrapper.properties | 2 +- 64 files changed, 7697 insertions(+), 4647 deletions(-) delete mode 100644 app/src/main/jni/And64InlineHook/And64InlineHook.cpp delete mode 100644 app/src/main/jni/And64InlineHook/And64InlineHook.hpp delete mode 100644 app/src/main/jni/And64InlineHook/LICENSE delete mode 100644 app/src/main/jni/And64InlineHook/README.md delete mode 100644 app/src/main/jni/Includes/get_device_api_level_inlines.h delete mode 100644 app/src/main/jni/KittyMemory/KittyArm64.cpp delete mode 100644 app/src/main/jni/KittyMemory/KittyArm64.hpp create mode 100644 app/src/main/jni/KittyMemory/KittyAsm.cpp create mode 100644 app/src/main/jni/KittyMemory/KittyAsm.hpp create mode 100644 app/src/main/jni/KittyMemory/KittyIOFile.cpp create mode 100644 app/src/main/jni/KittyMemory/KittyIOFile.hpp create mode 100644 app/src/main/jni/KittyMemory/KittyPtrValidator.cpp create mode 100644 app/src/main/jni/KittyMemory/KittyPtrValidator.hpp delete mode 100644 app/src/main/jni/Substrate/Buffer.hpp delete mode 100644 app/src/main/jni/Substrate/CydiaSubstrate.h delete mode 100644 app/src/main/jni/Substrate/SubstrateARM.hpp delete mode 100644 app/src/main/jni/Substrate/SubstrateDebug.cpp delete mode 100644 app/src/main/jni/Substrate/SubstrateDebug.hpp delete mode 100644 app/src/main/jni/Substrate/SubstrateHook.cpp delete mode 100644 app/src/main/jni/Substrate/SubstrateHook.h delete mode 100644 app/src/main/jni/Substrate/SubstrateLog.hpp delete mode 100644 app/src/main/jni/Substrate/SubstratePosixMemory.cpp delete mode 100644 app/src/main/jni/Substrate/SubstrateX86.hpp delete mode 100644 app/src/main/jni/Substrate/SymbolFinder.cpp delete mode 100644 app/src/main/jni/Substrate/SymbolFinder.h delete mode 100644 app/src/main/jni/Substrate/hde64.c delete mode 100644 app/src/main/jni/Substrate/hde64.h delete mode 100644 app/src/main/jni/Substrate/table64.h create mode 100644 app/src/main/jni/xDL/xdl.c create mode 100644 app/src/main/jni/xDL/xdl.h create mode 100644 app/src/main/jni/xDL/xdl.map.txt create mode 100644 app/src/main/jni/xDL/xdl_iterate.c create mode 100644 app/src/main/jni/xDL/xdl_iterate.h create mode 100644 app/src/main/jni/xDL/xdl_linker.c create mode 100644 app/src/main/jni/xDL/xdl_linker.h create mode 100644 app/src/main/jni/xDL/xdl_lzma.c create mode 100644 app/src/main/jni/xDL/xdl_lzma.h create mode 100644 app/src/main/jni/xDL/xdl_util.c create mode 100644 app/src/main/jni/xDL/xdl_util.h diff --git a/README.md b/README.md index 704352b..b035c51 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,40 @@ +# Fork Android-Mod-Menu by ABS +This is just my vision of what version 5 should be like. + +# 5.0 what's new: +- C++: +- Added xDL lib +- KittyMemory lib updated +- Macros.h & Utils.cpp: +- - xDL sym resolver added +- - HOOKs reworked with Dobby usage +- - PATCH_SWITCH, RESTORE has been returned and reworked +- - INST added (Dobby) +- - asm support added (KittyMemory::Keystone assembler) +- - Macro defines have been optimized +- Init() from Setup migrated to Menu for convenience +- And64InlineHook and Substrate libs removed (replaced by Dobby) +- get_device_api_level_inlines.h removed (replaced by JNI get_api_sdk) + +- Java: +- CrashHandler.java updated: +- - more debug info +- - save path has been changed to: android/media/PACKAGE/files/LOG_DIR for all devices +- - clear old crash logs (keep last 5) +- Main.java minor changes +- Menu.java minor changes + +- Project settings: +- Updated to SDK 36 +- Upgraded gradle +- Minor gradle settings changes # Introduction ![GitHub](https://img.shields.io/github/license/LGLTeam/Android-Mod-Menu?style=flat-square) Floating mod menu for il2cpp and other native android games. KittyMemory, MSHook, And64InlineHook and basic string obfuscator (AY obfuscator) included. Assets are stored as base64 in cpp and does not need to be stored under assets folder. -Support Android 4.4.x up to Android 15. ARMv7 and ARM64 are supported. +Support Android 4.4.x up to Android 16. ARMv7 and ARM64 are supported. ![](Intro.gif) @@ -23,11 +53,13 @@ If you have an issue with Hooking and game crashes, you should go to the **suppo # Credits Thanks to the following individuals whose code helped me develop this mod menu -* Octowolve/Escanor - Mod menu: https://github.com/z3r0Sec/Substrate-Template-With-Mod-Menu and Hooking: https://github.com/z3r0Sec/Substrate-Hooking-Example +* LGLTeam - (parent of fork) Mod menu - https://github.com/LGLTeam/Android-Mod-Menu +* Octowolve/Escanor - Mod menu: https://github.com/z3r0Sec/Substrate-Template-With-Mod-Menu * VanHoevenTR - Mod menu - https://github.com/LGLTeam/VanHoevenTR_Android_Mod_Menu * MrIkso - First mod menu template https://github.com/MrIkso/FloatingModMenu * MJx0 A.K.A Ruit - https://github.com/MJx0/KittyMemory -* Rprop - https://github.com/Rprop/And64InlineHook +* jmpews(AKA.zz) - https://github.com/jmpews/Dobby +* Kelun Cai & ruki - https://github.com/hexhacking/xDL * And everyone else who provided input and contributions to this project! # License @@ -36,6 +68,4 @@ Thanks to the following individuals whose code helped me develop this mod menu # Disclaimer This project is for Educational Use only. We do not condone this project being used to gain an advantage against other people. This project was made for fun. If you are using this project for modding/hacking PU*G, C*DM, and any other Tencent games, we ask you to STOP immediately! -Do not buy any source codes on Telegram even if the author can be trusted, there is always a risk getting scammed. We will not be responsible for that. This project is always FREE to use - -Telegram: https://t.me/LGLTeams +Do not buy any source codes on Telegram even if the author can be trusted, there is always a risk getting scammed. We will not be responsible for that. This project is always FREE to use \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 104757e..3664126 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,17 +1,19 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 34 + compileSdkVersion 36 defaultConfig { applicationId "com.android.support" minSdkVersion 21 - targetSdkVersion 34 + targetSdkVersion 36 versionCode 1 - versionName "4.0" + versionName "5.0" ndk { + //noinspection ChromeOsAbiSupport abiFilters 'armeabi-v7a', 'arm64-v8a' } } + android.buildFeatures.buildConfig = true buildTypes { release { shrinkResources true diff --git a/app/src/main/java/com/android/support/CrashHandler.java b/app/src/main/java/com/android/support/CrashHandler.java index 83f18be..2b9bdf6 100644 --- a/app/src/main/java/com/android/support/CrashHandler.java +++ b/app/src/main/java/com/android/support/CrashHandler.java @@ -1,39 +1,10 @@ -//Credit: Raunak Mods - https://t.me/raunakmods786 - package com.android.support; -import android.app.Activity; -import android.app.AlertDialog; -import android.app.Application; -import android.content.ActivityNotFoundException; -import android.content.ClipData; -import android.content.ClipboardManager; import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.content.res.Resources; import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import android.text.Html; -import android.text.InputFilter; -import android.text.InputType; -import android.text.TextUtils; -import android.text.method.DigitsKeyListener; import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.inputmethod.InputMethodManager; -import android.widget.EditText; -import android.widget.HorizontalScrollView; -import android.widget.LinearLayout; -import android.widget.ScrollView; -import android.widget.TextView; import android.widget.Toast; import java.io.File; @@ -42,103 +13,251 @@ import java.io.PrintWriter; import java.io.StringWriter; import java.lang.Thread.UncaughtExceptionHandler; +import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Date; +import java.util.Locale; +import java.util.Map; public final class CrashHandler { - public static final UncaughtExceptionHandler DEFAULT_UNCAUGHT_EXCEPTION_HANDLER = Thread.getDefaultUncaughtExceptionHandler(); + private static final String TAG = "AppCrash"; + private static final String LOG_DIR = "crash_logs"; + private static final String FILE_PREFIX = "crash_"; + private static final String FILE_EXTENSION = ".txt"; + private static UncaughtExceptionHandler defaultHandler; + private static Context appContext; + final static boolean showToasts = true; + + /// Due to changes in access to application folders and to maintain convenience, the path has been changed to: android/media/PACKAGE/files/LOG_DIR. + /// This path should be accessible on all devices + public static void init(final Context context) { + appContext = context.getApplicationContext(); + defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); - public static void init(final Context app, final boolean overlayRequired) { - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + cleanOldLogs(); + Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { @Override public void uncaughtException(Thread thread, Throwable throwable) { - Log.e("AppCrash", "Error just lunched "); + Log.e(TAG, "Uncaught exception detected", throwable); + try { - tryUncaughtException(thread, throwable); - } catch (Throwable e) { - e.printStackTrace(); - if (DEFAULT_UNCAUGHT_EXCEPTION_HANDLER != null) - DEFAULT_UNCAUGHT_EXCEPTION_HANDLER.uncaughtException(thread, throwable); - else - System.exit(2); + handleException(thread, throwable); + } catch (Exception e) { + Log.e(TAG, "Error in crash handler", e); + if (defaultHandler != null) { + defaultHandler.uncaughtException(thread, throwable); + } else { + android.os.Process.killProcess(android.os.Process.myPid()); + System.exit(10); + } } } + }); + } - private void tryUncaughtException(Thread thread, Throwable throwable) throws InterruptedException { - Log.e("AppCrash", "Try saving log"); + private static void handleException(Thread thread, Throwable throwable) { + // Save crash log + File crashFile = saveCrashLog(throwable); - final String time = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss").format(new Date()); - String fileName = "mod_menu_crash_" + time + ".txt"; - String dirName; + // Show notification to user + if (showToasts && appContext != null) { + showCrashNotification(crashFile); + } - if (Build.VERSION.SDK_INT >= 30) { //Android R. AIDE didn't support Build.VERSION_CODES.R - dirName = "/storage/emulated/0/Documents/"; - } else { - dirName = String.valueOf(app.getExternalFilesDir(null)); - } + // Wait a bit for toast to show and log to be saved + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } - File crashFile = new File(dirName, fileName); + // Call default handler + if (defaultHandler != null) { + defaultHandler.uncaughtException(thread, throwable); + } else { + android.os.Process.killProcess(android.os.Process.myPid()); + System.exit(10); + } + } - String versionName = "unknown"; - long versionCode = 0; - try { - PackageInfo packageInfo = app.getPackageManager().getPackageInfo(app.getPackageName(), 0); - versionName = packageInfo.versionName; - versionCode = Build.VERSION.SDK_INT >= 28 ? packageInfo.getLongVersionCode() - : packageInfo.versionCode; - } catch (PackageManager.NameNotFoundException ignored) { - } + private static File saveCrashLog(Throwable throwable) { + String timeStamp = new SimpleDateFormat("yyyy_MM_dd-HH_mm_ss", Locale.US) + .format(new Date()); - String fullStackTrace; - { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - throwable.printStackTrace(pw); - fullStackTrace = sw.toString(); - pw.close(); - } + String fileName = FILE_PREFIX + timeStamp + FILE_EXTENSION; + File logFile = new File(getLogDirectory(), fileName); + + try { + writeCrashLog(logFile, throwable); + Log.i(TAG, "Crash log saved to: " + logFile.getAbsolutePath()); + return logFile; + } catch (IOException e) { + Log.e(TAG, "Failed to save crash log", e); + return null; + } + } - StringBuilder devInfo = new StringBuilder(); - devInfo.append("************* Crash Head ****************\n"); - devInfo.append("Time Of Crash : ").append(time).append("\n"); - devInfo.append("Device Manufacturer: ").append(Build.MANUFACTURER).append("\n"); - devInfo.append("Device Model : ").append(Build.MODEL).append("\n"); - devInfo.append("Android Version : ").append(Build.VERSION.RELEASE).append("\n"); - devInfo.append("Android SDK : ").append(Build.VERSION.SDK_INT).append("\n"); - devInfo.append("App VersionName : ").append(versionName).append("\n"); - devInfo.append("App VersionCode : ").append(versionCode).append("\n"); - devInfo.append("************* Crash Head ****************\n"); - devInfo.append("\n").append(fullStackTrace); + private static void writeCrashLog(File file, Throwable throwable) throws IOException { + File parentDir = file.getParentFile(); + if (parentDir != null && !parentDir.exists()) { + if (!parentDir.mkdirs()) { + Log.w(TAG, "Failed to create directory: " + parentDir.getAbsolutePath()); + } + } - String errorLog = devInfo.toString(); + String logContent = buildCrashReport(throwable); - try { - writeFile(crashFile, errorLog); - } catch (IOException ignored) { - } + try (FileOutputStream fos = new FileOutputStream(file)) { + fos.write(logContent.getBytes(StandardCharsets.UTF_8)); + fos.flush(); + } + } + + private static String buildCrashReport(Throwable throwable) { + StringBuilder report = new StringBuilder(); + + // App and device info + report.append("=============== CRASH REPORT ===============\n"); + report.append("Time: ").append(getCurrentTime()).append("\n"); + report.append("App Version: ").append(getAppVersion()).append("\n"); + report.append("Android API: ").append(Build.VERSION.SDK_INT).append("\n"); + report.append("Android Version: ").append(Build.VERSION.RELEASE).append("\n"); + report.append("Device: ").append(Build.MANUFACTURER).append(" ").append(Build.MODEL).append("\n"); + report.append("Product: ").append(Build.PRODUCT).append("\n"); + report.append("Board: ").append(Build.BOARD).append("\n"); + report.append("Fingerprint: ").append(Build.FINGERPRINT).append("\n"); + report.append("Thread: ").append(Thread.currentThread().getName()).append("\n"); + report.append("=============== STACK TRACE ===============\n"); - Toast.makeText(app, "Game has crashed unexpectedly", Toast.LENGTH_LONG).show(); - Toast.makeText(app, "Log saved to: " + String.valueOf(crashFile).replace("/storage/emulated/0/", ""), Toast.LENGTH_LONG).show(); + // Stack trace + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + report.append(sw.toString()); - Log.e("AppCrash", "Done"); + // Cause chain + Throwable cause = throwable.getCause(); + int depth = 0; + while (cause != null && depth < 10) { + report.append("\nCaused by: "); + cause.printStackTrace(pw); + cause = cause.getCause(); + depth++; + } + + // Thread dump + report.append("\n\n=============== ALL THREADS ===============\n"); + Map allThreads = Thread.getAllStackTraces(); + for (Map.Entry entry : allThreads.entrySet()) { + Thread thread = entry.getKey(); + report.append("\nThread: ").append(thread.getName()) + .append(" [ID: ").append(thread.getId()) + .append(", State: ").append(thread.getState()) + .append(", Priority: ").append(thread.getPriority()) + .append("]\n"); + + for (StackTraceElement element : entry.getValue()) { + report.append(" at ").append(element.toString()).append("\n"); } + } - private void writeFile(File file, String content) throws IOException { - File parentFile = file.getParentFile(); - if (parentFile != null && !parentFile.exists()) { - parentFile.mkdirs(); - } - file.createNewFile(); - FileOutputStream fos = new FileOutputStream(file); - fos.write(content.getBytes()); - try { - fos.close(); - } catch (IOException e) { - } + pw.close(); + return report.toString(); + } + + private static File getLogDirectory() { + File standardAppDir = appContext.getExternalFilesDir(null); + if (standardAppDir == null) { + Log.e(TAG, "Cannot get app directory, using internal storage"); + return new File(appContext.getFilesDir(), LOG_DIR); + } + + String standardPath = standardAppDir.getAbsolutePath(); + String targetPath = standardPath.replace( + "Android/data/" + appContext.getPackageName(), + "Android/media/" + appContext.getPackageName() + ); + File logDir = new File(targetPath, LOG_DIR); + + if (!logDir.exists()) { + if (logDir.mkdirs()) { + Log.i(TAG, "Created log directory: " + logDir.getAbsolutePath()); + } else { + Log.e(TAG, "Failed to create Android/media directory, falling back to standard location"); + return new File(standardAppDir, LOG_DIR); } - }); + } + return logDir; + } + + private static String getDisplayPath(File file) { + if (file == null) return null; + String absolutePath = file.getAbsolutePath(); + int androidIndex = absolutePath.indexOf("/Android/"); + if (androidIndex != -1) { + return absolutePath.substring(androidIndex + 1); + } + return file.getName(); } -} + private static void showCrashNotification(File crashFile) { + String message; + + if (crashFile != null && crashFile.exists()) { + String shortPath = getDisplayPath(crashFile); + message = "CRASH! check:\n" + shortPath; + } else { + message = "CRASH! Could not save log."; + } + + Toast.makeText(appContext, message, Toast.LENGTH_LONG).show(); + } + + private static String getAppVersion() { + if (appContext == null) return "unknown"; + + try { + PackageInfo pInfo = appContext.getPackageManager() + .getPackageInfo(appContext.getPackageName(), 0); + long versionCode = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? + pInfo.getLongVersionCode() : pInfo.versionCode; + return pInfo.versionName + " (" + versionCode + ")"; + } catch (PackageManager.NameNotFoundException e) { + return "unknown"; + } + } + + private static String getCurrentTime() { + return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.US) + .format(new Date()); + } + + /// utility method to manually log an exception + public static void logException(Throwable throwable) { + Log.e(TAG, "Manual exception logging", throwable); + saveCrashLog(throwable); + } + + /// clear old crash logs (keep last 5) + public static void cleanOldLogs() { + File logDir = getLogDirectory(); + if (!logDir.exists() || !logDir.isDirectory()) return; + + File[] logs = logDir.listFiles((dir, name) -> + name.startsWith(FILE_PREFIX) && name.endsWith(FILE_EXTENSION)); + + if (logs != null && logs.length > 5) { + Arrays.sort(logs, (f1, f2) -> + Long.compare(f2.lastModified(), f1.lastModified())); + + for (int i = 10; i < logs.length; i++) { + if (!logs[i].delete()) { + Log.w(TAG, "Failed to delete old log: " + logs[i].getName()); + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/android/support/Main.java b/app/src/main/java/com/android/support/Main.java index 484fdf2..efc56cb 100644 --- a/app/src/main/java/com/android/support/Main.java +++ b/app/src/main/java/com/android/support/Main.java @@ -2,11 +2,6 @@ import android.app.Activity; import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.os.Handler; -import android.provider.Settings; import android.widget.Toast; public class Main { @@ -21,7 +16,7 @@ public class Main { private static native void CheckOverlayPermission(Context context); public static void StartWithoutPermission(Context context) { - CrashHandler.init(context, true); + CrashHandler.init(context); if (context instanceof Activity) { //Check if context is an Activity. Menu menu = new Menu(context); @@ -33,8 +28,7 @@ public static void StartWithoutPermission(Context context) { } public static void Start(Context context) { - CrashHandler.init(context, false); - + CrashHandler.init(context); CheckOverlayPermission(context); } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/android/support/Menu.java b/app/src/main/java/com/android/support/Menu.java index b3bea8c..b331481 100644 --- a/app/src/main/java/com/android/support/Menu.java +++ b/app/src/main/java/com/android/support/Menu.java @@ -5,7 +5,6 @@ import android.annotation.SuppressLint; import android.app.Activity; import android.app.AlertDialog; -import android.app.Service; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -57,6 +56,7 @@ import java.util.Arrays; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; @@ -64,12 +64,8 @@ import static android.widget.RelativeLayout.ALIGN_PARENT_LEFT; import static android.widget.RelativeLayout.ALIGN_PARENT_RIGHT; -import org.xml.sax.ErrorHandler; - public class Menu { //********** Here you can easly change the menu appearance **********// - - //region Variable public static final String TAG = "Mod_Menu"; //Tag for logcat int TEXT_COLOR = Color.parseColor("#82CAFD"); @@ -94,7 +90,7 @@ public class Menu { int SeekBarProgressColor = Color.parseColor("#80CBC4"); int CheckBoxColor = Color.parseColor("#80CBC4"); int RadioColor = Color.parseColor("#FFFFFF"); - int CollapseColor = Color.parseColor("#232F2C"); + int CollapseColor = Color.parseColor("#232F2C"); String NumberTxtColor = "#41c300"; //********************************************************************// @@ -151,7 +147,7 @@ public Menu(Context context) { //********** The icon to open mod menu ********** startimage = new ImageView(context); startimage.setLayoutParams(new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - int applyDimension = (int) TypedValue.applyDimension(1, ICON_SIZE, context.getResources().getDisplayMetrics()); //Icon size + int applyDimension = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ICON_SIZE, context.getResources().getDisplayMetrics()); //Icon size startimage.getLayoutParams().height = applyDimension; startimage.getLayoutParams().width = applyDimension; //startimage.requestLayout(); @@ -171,7 +167,7 @@ public void onClick(View view) { //********** The icon in Webview to open mod menu ********** WebView wView = new WebView(context); //Icon size width=\"50\" height=\"50\" wView.setLayoutParams(new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - int applyDimension2 = (int) TypedValue.applyDimension(1, ICON_SIZE, context.getResources().getDisplayMetrics()); //Icon size + int applyDimension2 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, ICON_SIZE, context.getResources().getDisplayMetrics()); //Icon size wView.getLayoutParams().height = applyDimension2; wView.getLayoutParams().width = applyDimension2; wView.loadData("" + @@ -209,7 +205,7 @@ public void onClick(View v) { scrollView.removeView(mSettings); scrollView.addView(mods); } - } catch (IllegalStateException e) { + } catch (IllegalStateException ignored) { } } }); @@ -364,7 +360,7 @@ public void SetWindowManagerWindowService() { vmParams.x = POS_X; vmParams.y = POS_Y; - mWindowManager = (WindowManager) getContext.getSystemService(getContext.WINDOW_SERVICE); + mWindowManager = (WindowManager) getContext.getSystemService(Context.WINDOW_SERVICE); mWindowManager.addView(rootFrame, vmParams); overlayRequired = true; @@ -421,8 +417,7 @@ public boolean onTouch(View view, MotionEvent motionEvent) { try { collapsedView.setVisibility(View.GONE); expandedView.setVisibility(View.VISIBLE); - } catch (NullPointerException e) { - + } catch (NullPointerException ignored) { } } return true; @@ -499,7 +494,7 @@ private void featureList(String[] listFT, LinearLayout linearLayout) { InputNum(linearLayout, featNum, strSplit[2], Integer.parseInt(strSplit[1])); if (strSplit.length == 2) InputNum(linearLayout, featNum, strSplit[1], 0); - break; + break; case "InputLValue": if (strSplit.length == 3) InputLNum(linearLayout, featNum, strSplit[2], Long.parseLong(strSplit[1])); @@ -551,13 +546,11 @@ private void Switch(LinearLayout linLayout, final int featNum, final String feat } ); //Set colors of the switch. Comment out if you don't like it - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - try { - switchR.getThumbDrawable().setTintList(buttonStates); - switchR.getTrackDrawable().setTintList(buttonStates); - } catch (NullPointerException ex) { - Log.d(TAG, String.valueOf(ex)); - } + try { + switchR.getThumbDrawable().setTintList(buttonStates); + switchR.getTrackDrawable().setTintList(buttonStates); + } catch (NullPointerException ex) { + Log.d(TAG, String.valueOf(ex)); } switchR.setText(featName); switchR.setTextColor(TEXT_COLOR_2); @@ -569,8 +562,7 @@ public void onCheckedChanged(CompoundButton compoundButton, boolean bool) { switch (featNum) { case -1: //Save perferences Preferences.with(switchR.getContext()).writeBoolean(-1, bool); - if (bool == false) - Preferences.with(switchR.getContext()).clear(); //Clear perferences if switched off + if (!bool) Preferences.with(switchR.getContext()).clear(); //Clear perferences if switched off break; case -3: Preferences.isExpanded = bool; @@ -774,11 +766,11 @@ public void onClick(View view) { editText.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { - InputMethodManager imm = (InputMethodManager) getContext.getSystemService(getContext.INPUT_METHOD_SERVICE); + InputMethodManager imm = (InputMethodManager) getContext.getSystemService(Context.INPUT_METHOD_SERVICE); if (hasFocus) { imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); } else { - imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0); + imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0); } } }); @@ -808,21 +800,21 @@ public void onClick(DialogInterface dialog, int whichButton) { button.setText(Html.fromHtml(featName + ": " + num + "")); Preferences.changeFeatureInt(featName, featNum, num); -editText.setFocusable(false); + editText.setFocusable(false); } }); alertName.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { // dialog.cancel(); // closes dialog - InputMethodManager imm = (InputMethodManager) getContext.getSystemService(getContext.INPUT_METHOD_SERVICE); - imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0); + InputMethodManager imm = (InputMethodManager) getContext.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0); } }); if (overlayRequired) { AlertDialog dialog = alertName.create(); // display the dialog - dialog.getWindow().setType(Build.VERSION.SDK_INT >= 26 ? 2038 : 2002); + Objects.requireNonNull(dialog.getWindow()).setType(Build.VERSION.SDK_INT >= 26 ? 2038 : 2002); dialog.show(); } else { alertName.show(); @@ -861,11 +853,11 @@ public void onClick(View view) { editText.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { - InputMethodManager imm = (InputMethodManager) getContext.getSystemService(getContext.INPUT_METHOD_SERVICE); + InputMethodManager imm = (InputMethodManager) getContext.getSystemService(Context.INPUT_METHOD_SERVICE); if (hasFocus) { imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); } else { - imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0); + imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0); } } }); @@ -903,14 +895,14 @@ public void onClick(DialogInterface dialog, int whichButton) { alertName.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { // dialog.cancel(); // closes dialog - InputMethodManager imm = (InputMethodManager) getContext.getSystemService(getContext.INPUT_METHOD_SERVICE); - imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0); + InputMethodManager imm = (InputMethodManager) getContext.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0); } }); if (overlayRequired) { AlertDialog dialog = alertName.create(); // display the dialog - dialog.getWindow().setType(Build.VERSION.SDK_INT >= 26 ? 2038 : 2002); + Objects.requireNonNull(dialog.getWindow()).setType(Build.VERSION.SDK_INT >= 26 ? 2038 : 2002); dialog.show(); } else { alertName.show(); @@ -945,11 +937,11 @@ public void onClick(View view) { editText.setOnFocusChangeListener(new View.OnFocusChangeListener() { @Override public void onFocusChange(View v, boolean hasFocus) { - InputMethodManager imm = (InputMethodManager) getContext.getSystemService(getContext.INPUT_METHOD_SERVICE); + InputMethodManager imm = (InputMethodManager) getContext.getSystemService(Context.INPUT_METHOD_SERVICE); if (hasFocus) { imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); } else { - imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0); + imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0); } } }); @@ -974,8 +966,8 @@ public void onClick(DialogInterface dialog, int whichButton) { alertName.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { //dialog.cancel(); // closes dialog - InputMethodManager imm = (InputMethodManager) getContext.getSystemService(getContext.INPUT_METHOD_SERVICE); - imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0); + InputMethodManager imm = (InputMethodManager) getContext.getSystemService(Context.INPUT_METHOD_SERVICE); + imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, 0); } }); @@ -998,8 +990,7 @@ private void CheckBox(LinearLayout linLayout, final int featNum, final String fe final CheckBox checkBox = new CheckBox(getContext); checkBox.setText(featName); checkBox.setTextColor(TEXT_COLOR_2); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - checkBox.setButtonTintList(ColorStateList.valueOf(CheckBoxColor)); + checkBox.setButtonTintList(ColorStateList.valueOf(CheckBoxColor)); checkBox.setChecked(Preferences.loadPrefBool(featName, featNum, switchedOn)); checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override @@ -1014,6 +1005,7 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { linLayout.addView(checkBox); } + @SuppressLint("SetTextI18n") private void RadioButton(LinearLayout linLayout, final int featNum, String featName, final String list) { //Credit: LoraZalora final List lists = new LinkedList<>(Arrays.asList(list.split(","))); @@ -1039,8 +1031,7 @@ public void onClick(View v) { System.out.println(lists.get(i)); Radioo.setText(lists.get(i)); Radioo.setTextColor(Color.LTGRAY); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - Radioo.setButtonTintList(ColorStateList.valueOf(RadioColor)); + Radioo.setButtonTintList(ColorStateList.valueOf(RadioColor)); Radioo.setOnClickListener(first_radio_listener); radioGroup.addView(Radioo); } @@ -1053,6 +1044,7 @@ public void onClick(View v) { linLayout.addView(radioGroup); } + @SuppressLint("SetTextI18n") private void Collapse(LinearLayout linLayout, final String text, final boolean expanded) { LinearLayout.LayoutParams layoutParamsLL = new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT); layoutParamsLL.setMargins(0, 5, 0, 0); diff --git a/app/src/main/jni/And64InlineHook/And64InlineHook.cpp b/app/src/main/jni/And64InlineHook/And64InlineHook.cpp deleted file mode 100644 index 53617b7..0000000 --- a/app/src/main/jni/And64InlineHook/And64InlineHook.cpp +++ /dev/null @@ -1,596 +0,0 @@ -/* - * @date : 2018/04/18 - * @author : Rprop (r_prop@outlook.com) - * https://github.com/Rprop/And64InlineHook - */ -/* - MIT License - - Copyright (c) 2018 Rprop (r_prop@outlook.com) - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - */ -#define __STDC_FORMAT_MACROS -#include -#include -#include -#include -#include -#include - -#if defined(__aarch64__) - -#include "And64InlineHook.hpp" -#define A64_MAX_INSTRUCTIONS 5 -#define A64_MAX_REFERENCES (A64_MAX_INSTRUCTIONS * 2) -#define A64_NOP 0xd503201fu -#define A64_JNIEXPORT __attribute__((visibility("default"))) -#define A64_LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, "A64_HOOK", __VA_ARGS__)) -#ifndef NDEBUG -# define A64_LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, "A64_HOOK", __VA_ARGS__)) -#else -# define A64_LOGI(...) ((void)0) -#endif // NDEBUG -typedef uint32_t *__restrict *__restrict instruction; -struct context -{ - struct fix_info - { - uint32_t *bp; - uint32_t ls; // left-shift counts - uint32_t ad; // & operand - }; - struct insns_info - { - union - { - uint64_t insu; - int64_t ins; - void *insp; - }; - fix_info fmap[A64_MAX_REFERENCES]; - }; - int64_t basep; - int64_t endp; - insns_info dat[A64_MAX_INSTRUCTIONS]; - -public: - inline bool is_in_fixing_range(const int64_t absolute_addr) { - return absolute_addr >= this->basep && absolute_addr < this->endp; - } - inline intptr_t get_ref_ins_index(const int64_t absolute_addr) { - return static_cast((absolute_addr - this->basep) / sizeof(uint32_t)); - } - inline intptr_t get_and_set_current_index(uint32_t *__restrict inp, uint32_t *__restrict outp) { - intptr_t current_idx = this->get_ref_ins_index(reinterpret_cast(inp)); - this->dat[current_idx].insp = outp; - return current_idx; - } - inline void reset_current_ins(const intptr_t idx, uint32_t *__restrict outp) { - this->dat[idx].insp = outp; - } - void insert_fix_map(const intptr_t idx, uint32_t *bp, uint32_t ls = 0u, uint32_t ad = 0xffffffffu) { - for (auto &f : this->dat[idx].fmap) { - if (f.bp == NULL) { - f.bp = bp; - f.ls = ls; - f.ad = ad; - return; - } //if - } - // What? GGing.. - } - void process_fix_map(const intptr_t idx) { - for (auto &f : this->dat[idx].fmap) { - if (f.bp == NULL) break; - *(f.bp) = *(f.bp) | (((int32_t(this->dat[idx].ins - reinterpret_cast(f.bp)) >> 2) << f.ls) & f.ad); - f.bp = NULL; - } - } -}; - -//------------------------------------------------------------------------- - -#define __intval(p) reinterpret_cast(p) -#define __uintval(p) reinterpret_cast(p) -#define __ptr(p) reinterpret_cast(p) -#define __page_size 4096 -#define __page_align(n) __align_up(static_cast(n), __page_size) -#define __ptr_align(x) __ptr(__align_down(reinterpret_cast(x), __page_size)) -#define __align_up(x, n) (((x) + ((n) - 1)) & ~((n) - 1)) -#define __align_down(x, n) ((x) & -(n)) -#define __countof(x) static_cast(sizeof(x) / sizeof((x)[0])) // must be signed -#define __atomic_increase(p) __sync_add_and_fetch(p, 1) -#define __sync_cmpswap(p, v, n) __sync_bool_compare_and_swap(p, v, n) -#define __predict_true(exp) __builtin_expect((exp) != 0, 1) -#define __flush_cache(c, n) __builtin___clear_cache(reinterpret_cast(c), reinterpret_cast(c) + n) -#define __make_rwx(p, n) ::mprotect(__ptr_align(p), \ - __page_align(__uintval(p) + n) != __page_align(__uintval(p)) ? __page_align(n) + __page_size : __page_align(n), \ - PROT_READ | PROT_WRITE | PROT_EXEC) - -//------------------------------------------------------------------------- - -static bool __fix_branch_imm(instruction inpp, instruction outpp, context *ctxp) -{ - static constexpr uint32_t mbits = 6u; - static constexpr uint32_t mask = 0xfc000000u; // 0b11111100000000000000000000000000 - static constexpr uint32_t rmask = 0x03ffffffu; // 0b00000011111111111111111111111111 - static constexpr uint32_t op_b = 0x14000000u; // "b" ADDR_PCREL26 - static constexpr uint32_t op_bl = 0x94000000u; // "bl" ADDR_PCREL26 - - const uint32_t ins = *(*inpp); - const uint32_t opc = ins & mask; - switch (opc) { - case op_b: - case op_bl: - { - intptr_t current_idx = ctxp->get_and_set_current_index(*inpp, *outpp); - int64_t absolute_addr = reinterpret_cast(*inpp) + (static_cast(ins << mbits) >> (mbits - 2u)); // sign-extended - int64_t new_pc_offset = static_cast(absolute_addr - reinterpret_cast(*outpp)) >> 2; // shifted - bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr); - // whether the branch should be converted to absolute jump - if (!special_fix_type && llabs(new_pc_offset) >= (rmask >> 1)) { - bool b_aligned = (reinterpret_cast(*outpp + 2) & 7u) == 0u; - if (opc == op_b) { - if (b_aligned != true) { - (*outpp)[0] = A64_NOP; - ctxp->reset_current_ins(current_idx, ++(*outpp)); - } //if - (*outpp)[0] = 0x58000051u; // LDR X17, #0x8 - (*outpp)[1] = 0xd61f0220u; // BR X17 - memcpy(*outpp + 2, &absolute_addr, sizeof(absolute_addr)); - *outpp += 4; - } else { - if (b_aligned == true) { - (*outpp)[0] = A64_NOP; - ctxp->reset_current_ins(current_idx, ++(*outpp)); - } //if - (*outpp)[0] = 0x58000071u; // LDR X17, #12 - (*outpp)[1] = 0x1000009eu; // ADR X30, #16 - (*outpp)[2] = 0xd61f0220u; // BR X17 - memcpy(*outpp + 3, &absolute_addr, sizeof(absolute_addr)); - *outpp += 5; - } //if - } else { - if (special_fix_type) { - intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr); - if (ref_idx <= current_idx) { - new_pc_offset = static_cast(ctxp->dat[ref_idx].ins - reinterpret_cast(*outpp)) >> 2; - } else { - ctxp->insert_fix_map(ref_idx, *outpp, 0u, rmask); - new_pc_offset = 0; - } //if - } //if - - (*outpp)[0] = opc | (new_pc_offset & ~mask); - ++(*outpp); - } //if - - ++(*inpp); - return ctxp->process_fix_map(current_idx), true; - } - } - return false; -} - -//------------------------------------------------------------------------- - -static bool __fix_cond_comp_test_branch(instruction inpp, instruction outpp, context *ctxp) -{ - static constexpr uint32_t lsb = 5u; - static constexpr uint32_t lmask01 = 0xff00001fu; // 0b11111111000000000000000000011111 - static constexpr uint32_t mask0 = 0xff000010u; // 0b11111111000000000000000000010000 - static constexpr uint32_t op_bc = 0x54000000u; // "b.c" ADDR_PCREL19 - static constexpr uint32_t mask1 = 0x7f000000u; // 0b01111111000000000000000000000000 - static constexpr uint32_t op_cbz = 0x34000000u; // "cbz" Rt, ADDR_PCREL19 - static constexpr uint32_t op_cbnz = 0x35000000u; // "cbnz" Rt, ADDR_PCREL19 - static constexpr uint32_t lmask2 = 0xfff8001fu; // 0b11111111111110000000000000011111 - static constexpr uint32_t mask2 = 0x7f000000u; // 0b01111111000000000000000000000000 - static constexpr uint32_t op_tbz = 0x36000000u; // 0b00110110000000000000000000000000 "tbz" Rt, BIT_NUM, ADDR_PCREL14 - static constexpr uint32_t op_tbnz = 0x37000000u; // 0b00110111000000000000000000000000 "tbnz" Rt, BIT_NUM, ADDR_PCREL14 - - const uint32_t ins = *(*inpp); - uint32_t lmask = lmask01; - if ((ins & mask0) != op_bc) { - uint32_t opc = ins & mask1; - if (opc != op_cbz && opc != op_cbnz) { - opc = ins & mask2; - if (opc != op_tbz && opc != op_tbnz) { - return false; - } //if - lmask = lmask2; - } //if - } //if - - const uint32_t msb = __builtin_clz(~lmask); - intptr_t current_idx = ctxp->get_and_set_current_index(*inpp, *outpp); - int64_t absolute_addr = reinterpret_cast(*inpp) + (static_cast((ins & ~lmask) << msb) >> (lsb - 2u + msb)); - int64_t new_pc_offset = static_cast(absolute_addr - reinterpret_cast(*outpp)) >> 2; // shifted - bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr); - if (!special_fix_type && llabs(new_pc_offset) >= (~lmask >> (lsb + 1))) { - if ((reinterpret_cast(*outpp + 4) & 7u) != 0u) { - (*outpp)[0] = A64_NOP; - ctxp->reset_current_ins(current_idx, ++(*outpp)); - } //if - (*outpp)[0] = (((8u >> 2u) << lsb) & ~lmask) | (ins & lmask); // B.C #0x8 - (*outpp)[1] = 0x14000005u; // B #0x14 - (*outpp)[2] = 0x58000051u; // LDR X17, #0x8 - (*outpp)[3] = 0xd61f0220u; // BR X17 - memcpy(*outpp + 4, &absolute_addr, sizeof(absolute_addr)); - *outpp += 6; - } else { - if (special_fix_type) { - intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr); - if (ref_idx <= current_idx) { - new_pc_offset = static_cast(ctxp->dat[ref_idx].ins - reinterpret_cast(*outpp)) >> 2; - } else { - ctxp->insert_fix_map(ref_idx, *outpp, lsb, ~lmask); - new_pc_offset = 0; - } //if - } //if - - (*outpp)[0] = (static_cast(new_pc_offset << lsb) & ~lmask) | (ins & lmask); - ++(*outpp); - } //if - - ++(*inpp); - return ctxp->process_fix_map(current_idx), true; -} - -//------------------------------------------------------------------------- - -static bool __fix_loadlit(instruction inpp, instruction outpp, context *ctxp) -{ - const uint32_t ins = *(*inpp); - - // memory prefetch("prfm"), just skip it - // http://infocenter.arm.com/help/topic/com.arm.doc.100069_0608_00_en/pge1427897420050.html - if ((ins & 0xff000000u) == 0xd8000000u) { - ctxp->process_fix_map(ctxp->get_and_set_current_index(*inpp, *outpp)); - ++(*inpp); - return true; - } //if - - static constexpr uint32_t msb = 8u; - static constexpr uint32_t lsb = 5u; - static constexpr uint32_t mask_30 = 0x40000000u; // 0b01000000000000000000000000000000 - static constexpr uint32_t mask_31 = 0x80000000u; // 0b10000000000000000000000000000000 - static constexpr uint32_t lmask = 0xff00001fu; // 0b11111111000000000000000000011111 - static constexpr uint32_t mask_ldr = 0xbf000000u; // 0b10111111000000000000000000000000 - static constexpr uint32_t op_ldr = 0x18000000u; // 0b00011000000000000000000000000000 "LDR Wt/Xt, label" | ADDR_PCREL19 - static constexpr uint32_t mask_ldrv = 0x3f000000u; // 0b00111111000000000000000000000000 - static constexpr uint32_t op_ldrv = 0x1c000000u; // 0b00011100000000000000000000000000 "LDR St/Dt/Qt, label" | ADDR_PCREL19 - static constexpr uint32_t mask_ldrsw = 0xff000000u; // 0b11111111000000000000000000000000 - static constexpr uint32_t op_ldrsw = 0x98000000u; // "LDRSW Xt, label" | ADDR_PCREL19 | load register signed word - // LDR S0, #0 | 0b00011100000000000000000000000000 | 32-bit - // LDR D0, #0 | 0b01011100000000000000000000000000 | 64-bit - // LDR Q0, #0 | 0b10011100000000000000000000000000 | 128-bit - // INVALID | 0b11011100000000000000000000000000 | may be 256-bit - - uint32_t mask = mask_ldr; - uintptr_t faligned = (ins & mask_30) ? 7u : 3u; - if ((ins & mask_ldr) != op_ldr) { - mask = mask_ldrv; - if (faligned != 7u) - faligned = (ins & mask_31) ? 15u : 3u; - if ((ins & mask_ldrv) != op_ldrv) { - if ((ins & mask_ldrsw) != op_ldrsw) { - return false; - } //if - mask = mask_ldrsw; - faligned = 7u; - } //if - } //if - - intptr_t current_idx = ctxp->get_and_set_current_index(*inpp, *outpp); - int64_t absolute_addr = reinterpret_cast(*inpp) + ((static_cast(ins << msb) >> (msb + lsb - 2u)) & ~3u); - int64_t new_pc_offset = static_cast(absolute_addr - reinterpret_cast(*outpp)) >> 2; // shifted - bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr); - // special_fix_type may encounter issue when there are mixed data and code - if (special_fix_type || (llabs(new_pc_offset) + (faligned + 1u - 4u) / 4u) >= (~lmask >> (lsb + 1))) { // inaccurate, but it works - while ((reinterpret_cast(*outpp + 2) & faligned) != 0u) { - *(*outpp)++ = A64_NOP; - } - ctxp->reset_current_ins(current_idx, *outpp); - - // Note that if memory at absolute_addr is writeable (non-const), we will fail to fetch it. - // And what's worse, we may unexpectedly overwrite something if special_fix_type is true... - uint32_t ns = static_cast((faligned + 1) / sizeof(uint32_t)); - (*outpp)[0] = (((8u >> 2u) << lsb) & ~mask) | (ins & lmask); // LDR #0x8 - (*outpp)[1] = 0x14000001u + ns; // B #0xc - memcpy(*outpp + 2, reinterpret_cast(absolute_addr), faligned + 1); - *outpp += 2 + ns; - } else { - faligned >>= 2; // new_pc_offset is shifted and 4-byte aligned - while ((new_pc_offset & faligned) != 0) { - *(*outpp)++ = A64_NOP; - new_pc_offset = static_cast(absolute_addr - reinterpret_cast(*outpp)) >> 2; - } - ctxp->reset_current_ins(current_idx, *outpp); - - (*outpp)[0] = (static_cast(new_pc_offset << lsb) & ~mask) | (ins & lmask); - ++(*outpp); - } //if - - ++(*inpp); - return ctxp->process_fix_map(current_idx), true; -} - -//------------------------------------------------------------------------- - -static bool __fix_pcreladdr(instruction inpp, instruction outpp, context *ctxp) -{ - // Load a PC-relative address into a register - // http://infocenter.arm.com/help/topic/com.arm.doc.100069_0608_00_en/pge1427897645644.html - static constexpr uint32_t msb = 8u; - static constexpr uint32_t lsb = 5u; - static constexpr uint32_t mask = 0x9f000000u; // 0b10011111000000000000000000000000 - static constexpr uint32_t rmask = 0x0000001fu; // 0b00000000000000000000000000011111 - static constexpr uint32_t lmask = 0xff00001fu; // 0b11111111000000000000000000011111 - static constexpr uint32_t fmask = 0x00ffffffu; // 0b00000000111111111111111111111111 - static constexpr uint32_t max_val = 0x001fffffu; // 0b00000000000111111111111111111111 - static constexpr uint32_t op_adr = 0x10000000u; // "adr" Rd, ADDR_PCREL21 - static constexpr uint32_t op_adrp = 0x90000000u; // "adrp" Rd, ADDR_ADRP - - const uint32_t ins = *(*inpp); - intptr_t current_idx; - switch (ins & mask) { - case op_adr: - { - current_idx = ctxp->get_and_set_current_index(*inpp, *outpp); - int64_t lsb_bytes = static_cast(ins << 1u) >> 30u; - int64_t absolute_addr = reinterpret_cast(*inpp) + (((static_cast(ins << msb) >> (msb + lsb - 2u)) & ~3u) | lsb_bytes); - int64_t new_pc_offset = static_cast(absolute_addr - reinterpret_cast(*outpp)); - bool special_fix_type = ctxp->is_in_fixing_range(absolute_addr); - if (!special_fix_type && llabs(new_pc_offset) >= (max_val >> 1)) { - if ((reinterpret_cast(*outpp + 2) & 7u) != 0u) { - (*outpp)[0] = A64_NOP; - ctxp->reset_current_ins(current_idx, ++(*outpp)); - } //if - - (*outpp)[0] = 0x58000000u | (((8u >> 2u) << lsb) & ~mask) | (ins & rmask); // LDR #0x8 - (*outpp)[1] = 0x14000003u; // B #0xc - memcpy(*outpp + 2, &absolute_addr, sizeof(absolute_addr)); - *outpp += 4; - } else { - if (special_fix_type) { - intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr & ~3ull); - if (ref_idx <= current_idx) { - new_pc_offset = static_cast(ctxp->dat[ref_idx].ins - reinterpret_cast(*outpp)); - } else { - ctxp->insert_fix_map(ref_idx, *outpp, lsb, fmask); - new_pc_offset = 0; - } //if - } //if - - // the lsb_bytes will never be changed, so we can use lmask to keep it - (*outpp)[0] = (static_cast(new_pc_offset << (lsb - 2u)) & fmask) | (ins & lmask); - ++(*outpp); - } //if - } - break; - case op_adrp: - { - current_idx = ctxp->get_and_set_current_index(*inpp, *outpp); - int32_t lsb_bytes = static_cast(ins << 1u) >> 30u; - int64_t absolute_addr = (reinterpret_cast(*inpp) & ~0xfffll) + ((((static_cast(ins << msb) >> (msb + lsb - 2u)) & ~3u) | lsb_bytes) << 12); - A64_LOGI("ins = 0x%.8X, pc = %p, abs_addr = %p", - ins, *inpp, reinterpret_cast(absolute_addr)); - if (ctxp->is_in_fixing_range(absolute_addr)) { - intptr_t ref_idx = ctxp->get_ref_ins_index(absolute_addr/* & ~3ull*/); - if (ref_idx > current_idx) { - // the bottom 12 bits of absolute_addr are masked out, - // so ref_idx must be less than or equal to current_idx! - A64_LOGE("ref_idx must be less than or equal to current_idx!"); - } //if - - // *absolute_addr may be changed due to relocation fixing - A64_LOGI("What is the correct way to fix this?"); - *(*outpp)++ = ins; // 0x90000000u; - } else { - if ((reinterpret_cast(*outpp + 2) & 7u) != 0u) { - (*outpp)[0] = A64_NOP; - ctxp->reset_current_ins(current_idx, ++(*outpp)); - } //if - - (*outpp)[0] = 0x58000000u | (((8u >> 2u) << lsb) & ~mask) | (ins & rmask); // LDR #0x8 - (*outpp)[1] = 0x14000003u; // B #0xc - memcpy(*outpp + 2, &absolute_addr, sizeof(absolute_addr)); // potential overflow? - *outpp += 4; - } //if - } - break; - default: - return false; - } - - ctxp->process_fix_map(current_idx); - ++(*inpp); - return true; -} - -//------------------------------------------------------------------------- - -static void __fix_instructions(uint32_t *__restrict inp, int32_t count, uint32_t *__restrict outp) -{ - context ctx; - ctx.basep = reinterpret_cast(inp); - ctx.endp = reinterpret_cast(inp + count); - memset(ctx.dat, 0, sizeof(ctx.dat)); - static_assert(sizeof(ctx.dat) / sizeof(ctx.dat[0]) == A64_MAX_INSTRUCTIONS, - "please use A64_MAX_INSTRUCTIONS!"); -#ifndef NDEBUG - if (count > A64_MAX_INSTRUCTIONS) { - A64_LOGE("too many fixing instructions!"); - } //if -#endif // NDEBUG - - uint32_t *const outp_base = outp; - - while (--count >= 0) { - if (__fix_branch_imm(&inp, &outp, &ctx)) continue; - if (__fix_cond_comp_test_branch(&inp, &outp, &ctx)) continue; - if (__fix_loadlit(&inp, &outp, &ctx)) continue; - if (__fix_pcreladdr(&inp, &outp, &ctx)) continue; - - // without PC-relative offset - ctx.process_fix_map(ctx.get_and_set_current_index(inp, outp)); - *(outp++) = *(inp++); - } - - static constexpr uint_fast64_t mask = 0x03ffffffu; // 0b00000011111111111111111111111111 - auto callback = reinterpret_cast(inp); - auto pc_offset = static_cast(callback - reinterpret_cast(outp)) >> 2; - if (llabs(pc_offset) >= (mask >> 1)) { - if ((reinterpret_cast(outp + 2) & 7u) != 0u) { - outp[0] = A64_NOP; - ++outp; - } //if - outp[0] = 0x58000051u; // LDR X17, #0x8 - outp[1] = 0xd61f0220u; // BR X17 - *reinterpret_cast(outp + 2) = callback; - outp += 4; - } else { - outp[0] = 0x14000000u | (pc_offset & mask); // "B" ADDR_PCREL26 - ++outp; - } //if - - const uintptr_t total = (outp - outp_base) * sizeof(uint32_t); - __flush_cache(outp_base, total); // necessary -} - -//------------------------------------------------------------------------- - -extern "C" { - static __attribute__((__aligned__(__page_size))) uint32_t __insns_pool[A64_MAX_BACKUPS][A64_MAX_INSTRUCTIONS * 10]; - - //------------------------------------------------------------------------- - - class A64HookInit - { - public: - A64HookInit() - { - __make_rwx(__insns_pool, sizeof(__insns_pool)); - A64_LOGI("insns pool initialized."); - } - }; - static A64HookInit __init; - - //------------------------------------------------------------------------- - - static uint32_t *FastAllocateTrampoline() - { - static_assert((A64_MAX_INSTRUCTIONS * 10 * sizeof(uint32_t)) % 8 == 0, "8-byte align"); - static volatile int32_t __index = -1; - - int32_t i = __atomic_increase(&__index); - if (__predict_true(i >= 0 && i < __countof(__insns_pool))) { - return __insns_pool[i]; - } //if - - A64_LOGE("failed to allocate trampoline!"); - return NULL; - } - - //------------------------------------------------------------------------- - - A64_JNIEXPORT void *A64HookFunctionV(void *const symbol, void *const replace, - void *const rwx, const uintptr_t rwx_size) - { - static constexpr uint_fast64_t mask = 0x03ffffffu; // 0b00000011111111111111111111111111 - - uint32_t *trampoline = static_cast(rwx), *original = static_cast(symbol); - - static_assert(A64_MAX_INSTRUCTIONS >= 5, "please fix A64_MAX_INSTRUCTIONS!"); - auto pc_offset = static_cast(__intval(replace) - __intval(symbol)) >> 2; - if (llabs(pc_offset) >= (mask >>1)) { - int32_t count = (reinterpret_cast(original + 2) & 7u) != 0u ? 5 : 4; - if (trampoline) { - if (rwx_size < count * 10u) { - A64_LOGE("rwx size is too small to hold %u bytes backup instructions!", count * 10u); - return NULL; - } //if - __fix_instructions(original, count, trampoline); - } //if - - if (__make_rwx(original, 5 * sizeof(uint32_t)) == 0) { - if (count == 5) { - original[0] = A64_NOP; - ++original; - } //if - original[0] = 0x58000051u; // LDR X17, #0x8 - original[1] = 0xd61f0220u; // BR X17 - *reinterpret_cast(original + 2) = __intval(replace); - __flush_cache(symbol, 5 * sizeof(uint32_t)); - - A64_LOGI("inline hook %p->%p successfully! %zu bytes overwritten", - symbol, replace, 5 * sizeof(uint32_t)); - } else { - A64_LOGE("mprotect failed with errno = %d, p = %p, size = %zu", - errno, original, 5 * sizeof(uint32_t)); - trampoline = NULL; - } //if - } else { - if (trampoline) { - if (rwx_size < 1u * 10u) { - A64_LOGE("rwx size is too small to hold %u bytes backup instructions!", 1u * 10u); - return NULL; - } //if - __fix_instructions(original, 1, trampoline); - } //if - - if (__make_rwx(original, 1 * sizeof(uint32_t)) == 0) { - __sync_cmpswap(original, *original, 0x14000000u | (pc_offset & mask)); // "B" ADDR_PCREL26 - __flush_cache(symbol, 1 * sizeof(uint32_t)); - - A64_LOGI("inline hook %p->%p successfully! %zu bytes overwritten", - symbol, replace, 1 * sizeof(uint32_t)); - } else { - A64_LOGE("mprotect failed with errno = %d, p = %p, size = %zu", - errno, original, 1 * sizeof(uint32_t)); - trampoline = NULL; - } //if - } //if - - return trampoline; - } - - //------------------------------------------------------------------------- - - A64_JNIEXPORT void A64HookFunction(void *const symbol, void *const replace, void **result) - { - void *trampoline = NULL; - if (result != NULL) { - trampoline = FastAllocateTrampoline(); - *result = trampoline; - if (trampoline == NULL) return; - } //if - - // fix Android 10 .text segment is read-only by default - __make_rwx(symbol, 5 * sizeof(size_t)); - - trampoline = A64HookFunctionV(symbol, replace, trampoline, A64_MAX_INSTRUCTIONS * 10u); - if (trampoline == NULL && result != NULL) { - *result = NULL; - } //if - } -} - -#endif // defined(__aarch64__) diff --git a/app/src/main/jni/And64InlineHook/And64InlineHook.hpp b/app/src/main/jni/And64InlineHook/And64InlineHook.hpp deleted file mode 100644 index 7af5090..0000000 --- a/app/src/main/jni/And64InlineHook/And64InlineHook.hpp +++ /dev/null @@ -1,42 +0,0 @@ -/* - * @date : 2018/04/18 - * @author : Rprop (r_prop@outlook.com) - * https://github.com/Rprop/And64InlineHook - */ -/* - MIT License - - Copyright (c) 2018 Rprop (r_prop@outlook.com) - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - */ -#pragma once -#define A64_MAX_BACKUPS 256 - -#ifdef __cplusplus -extern "C" { -#endif - - void A64HookFunction(void *const symbol, void *const replace, void **result); - void *A64HookFunctionV(void *const symbol, void *const replace, - void *const rwx, const uintptr_t rwx_size); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/app/src/main/jni/And64InlineHook/LICENSE b/app/src/main/jni/And64InlineHook/LICENSE deleted file mode 100644 index 75a6020..0000000 --- a/app/src/main/jni/And64InlineHook/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2017 RLib - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/app/src/main/jni/And64InlineHook/README.md b/app/src/main/jni/And64InlineHook/README.md deleted file mode 100644 index 44f651d..0000000 --- a/app/src/main/jni/And64InlineHook/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# And64InlineHook -Lightweight ARMv8-A(ARM64, AArch64, Little-Endian) Inline Hook Library for Android C/C++ - -# References -[Arm Compiler armasm User Guide](http://infocenter.arm.com/help/topic/com.arm.doc.100069_0610_00_en/pge1427898258836.html) -[Procedure Call Standard for the Arm® 64-bit Architecture (AArch64)](https://github.com/ARM-software/abi-aa/blob/master/aapcs64/aapcs64.rst) - diff --git a/app/src/main/jni/Android.mk b/app/src/main/jni/Android.mk index f24990a..39879de 100644 --- a/app/src/main/jni/Android.mk +++ b/app/src/main/jni/Android.mk @@ -38,19 +38,20 @@ LOCAL_SRC_FILES := Main.cpp \ Menu/Menu.cpp \ Menu/Setup.cpp \ Includes/Utils.cpp \ - Substrate/hde64.c \ - Substrate/SubstrateDebug.cpp \ - Substrate/SubstrateHook.cpp \ - Substrate/SubstratePosixMemory.cpp \ - Substrate/SymbolFinder.cpp \ - KittyMemory/KittyArm64.cpp \ - KittyMemory/KittyScanner.cpp \ + KittyMemory/KittyAsm.cpp \ + KittyMemory/KittyIOFile.cpp \ KittyMemory/KittyMemory.cpp \ + KittyMemory/KittyPtrValidator.cpp \ + KittyMemory/KittyScanner.cpp \ KittyMemory/KittyUtils.cpp \ - KittyMemory/MemoryPatch.cpp \ KittyMemory/MemoryBackup.cpp \ - And64InlineHook/And64InlineHook.cpp \ + KittyMemory/MemoryPatch.cpp \ + xDL/xdl.c \ + xDL/xdl_iterate.c \ + xDL/xdl_linker.c \ + xDL/xdl_lzma.c \ + xDL/xdl_util.c \ LOCAL_STATIC_LIBRARIES := Keystone Dobby -include $(BUILD_SHARED_LIBRARY) +include $(BUILD_SHARED_LIBRARY) \ No newline at end of file diff --git a/app/src/main/jni/Includes/Macros.h b/app/src/main/jni/Includes/Macros.h index c0558b0..03196ad 100644 --- a/app/src/main/jni/Includes/Macros.h +++ b/app/src/main/jni/Includes/Macros.h @@ -4,68 +4,101 @@ #include "KittyMemory/MemoryPatch.hpp" #include "KittyMemory/KittyInclude.hpp" +#include "Dobby/dobby.h" -#if defined(__aarch64__) //Compile for arm64 lib only -#include -#else //Compile for armv7 lib only. -#include -#include -#endif - -void hook(void *offset, void* ptr, void **orig) -{ #if defined(__aarch64__) - A64HookFunction(offset, ptr, orig); +int MP_ASM = 1; #else - MSHookFunction(offset, ptr, orig); +int MP_ASM = 0; #endif -} - -#define HOOK(lib, offset, ptr, orig) hook((void *)getAbsoluteAddress(lib, offset), (void *)ptr, (void **)&orig) -#define HOOK_NO_ORIG(lib, offset, ptr) hook((void *)getAbsoluteAddress(lib, offset), (void *)ptr, NULL) -#define HOOKSYM(lib, sym, ptr, org) hook(dlsym(dlopen(lib, 4), sym), (void *)ptr, (void **)&org) -#define HOOKSYM_NO_ORIG(lib, sym, ptr) hook(dlsym(dlopen(lib, 4), sym), (void *)ptr, NULL) -// Patching a offset without switch. -void patchOffset(const char *libName, uint64_t offset, std::string hexBytes) -{ - ElfScanner g_il2cppELF = ElfScanner::createWithPath(libName); - uintptr_t il2cppBase = g_il2cppELF.base(); - MemoryPatch patch = MemoryPatch::createWithHex(il2cppBase + offset, hexBytes); +/// classic hook (offset || sym) +#define HOOK(lib, off_sym, ptr, orig) DobbyHookWrapper(lib, off_sym, (void*)(ptr), (void**)&(orig)) +/// hook (offset || sym) without original +#define HOOK_NO_ORIG(lib, off_sym, ptr) DobbyHookWrapper(lib, off_sym, (void*)(ptr), nullptr) - // LOGI(OBFUSCATE("Base: 0x%llx"), il2cppBase); - // LOGI(OBFUSCATE("Off: 0x%llx"), offset); +void DobbyHookWrapper(const char *lib, const char *relative, void* hook_function, void** original_function) { + void *abs = getAbsoluteAddress(lib, relative); - // LOGI("Current Bytes: %s", patch.get_CurrBytes().c_str()); + // LOGI(OBFUSCATE("Off: 0x%llx, Addr: 0x%llx"), offset, (uintptr_t) abs); - if (!patch.isValid()) - { - LOGE(OBFUSCATE("Failing offset: 0x%llx, please re-check the hex you entered."), offset); - return; - } - if (!patch.Modify()) - { - LOGE(OBFUSCATE("Something went wrong while patching this offset: 0x%llx"), offset); - return; + if (original_function != nullptr) { + DobbyHook(abs, (dobby_dummy_func_t)hook_function, (dobby_dummy_func_t*)original_function); + } else { + DobbyHook(abs, (dobby_dummy_func_t)hook_function, nullptr); } } -void patchOffsetSym(uintptr_t offset, std::string hexBytes) -{ - MemoryPatch patch = MemoryPatch::createWithHex(offset, hexBytes); - if (!patch.isValid()) - { - LOGE(OBFUSCATE("Failing offset: 0x%llu, please re-check the hex you entered."), offset); - return; - } - if (!patch.Modify()) - { - LOGE(OBFUSCATE("Something went wrong while patching this offset: 0x%llu"), offset); - return; +/// (offset || sym) you can use instrument for logging, counting function calls, executing side code before the function is executed +#define INST(lib, off_sym, name) DobbyInstrumentWrapper(lib, off_sym, name) + +std::map detecting_functions; +void Detector(void *address, DobbyRegisterContext *ctx) { + if(detecting_functions.count(address)) LOGW(OBFUSCATE("()0_0) %s >>>>>>>>>>>>> execute detected"), detecting_functions[address]); +} + +/// an example of a wrapper with a function for detecting execution +void DobbyInstrumentWrapper(const char *lib, const char *relative, const char *name) { + void *abs = getAbsoluteAddress(lib, relative); + detecting_functions[abs] = name; + + // not access to the arguments "directly," as in a hook + // accessing the arguments requires low-level register reading + DobbyInstrument(abs, (dobby_instrument_callback_t)(Detector)); +} + +std::map memoryPatches; +void patchOffsetWrapper(const char *libName, const char *relative, std::string data, bool change) { + auto it = memoryPatches.find(relative); + + if(change) { + if(it != memoryPatches.end()) { + MemoryPatch& existingPatch = it->second; + if(!existingPatch.Modify()) { + LOGE(OBFUSCATE("Failed to modify existing patch at: %s"), relative); + return; + } + // LOGI(OBFUSCATE("Existing patch modified at: %s"), relative); + } else { + MemoryPatch patch; + auto address = (uintptr_t) getAbsoluteAddress(libName, relative); + // LOGI(OBFUSCATE("Rel: %s, Addr: 0x%llx"), relative, address); + + std::string asm_data = data; + if(KittyUtils::String::ValidateHex(data)) { + patch = MemoryPatch::createWithHex(address, data); + } else { + patch = MemoryPatch::createWithAsm(address, MP_ASM_ARCH(MP_ASM), asm_data, 0); + } + + if(!patch.isValid()) { + LOGE(OBFUSCATE("Failed to create patch at: 0x%llx"), address); + return; + } + if(!patch.Modify()) { + LOGE(OBFUSCATE("Failed to apply patch at: 0x%llx"), address); + return; + } + memoryPatches[relative] = patch; + // LOGI(OBFUSCATE("New patch applied at: %s"), relative); + } + } else { + if(it != memoryPatches.end()) { + if(!it->second.Restore()) { + LOGE(OBFUSCATE("Failed to restore patch at: %s"), relative); + return; + } + // LOGI(OBFUSCATE("Patch restored at: %s"), relative); + } } } -#define PATCH(lib, offset, hex) patchOffset(lib, offset, hex) -#define PATCH_SYM(lib, sym, hex) patchOffset(dlsym(dlopen(lib, 4), sym)), hex), true) +/// classic patch (offset || sym) (hex || asm) +#define PATCH(lib, off_sym, hex_asm) patchOffsetWrapper(lib, off_sym, hex_asm, true) +/// patch original restore (offset || sym) (hex || asm) +#define RESTORE(lib, off_sym) patchOffsetWrapper(lib, off_sym, "", false) + +/// patch switch (offset || sym) (hex || asm) +#define PATCH_SWITCH(lib, off_sym, hex_asm, boolean) patchOffsetWrapper(lib, off_sym, hex_asm, boolean) #endif //ANDROID_MOD_MENU_MACROS_H \ No newline at end of file diff --git a/app/src/main/jni/Includes/Utils.cpp b/app/src/main/jni/Includes/Utils.cpp index eba71b3..5c3f709 100644 --- a/app/src/main/jni/Includes/Utils.cpp +++ b/app/src/main/jni/Includes/Utils.cpp @@ -1,61 +1,82 @@ -#include "Utils.hpp" #include "obfuscate.h" +#include "Utils.hpp" -static uintptr_t libBase; - -bool libLoaded = false; - -DWORD findLibrary(const char *library) { - char filename[0xFF] = {0}, - buffer[1024] = {0}; - FILE *fp = NULL; - DWORD address = 0; - - sprintf(filename, OBFUSCATE("/proc/self/maps")); +std::map lib_links; +bool mainLibLoaded = false; + +uintptr_t getLibraryAddress(const char *libraryName) { + if (!lib_links.count(libraryName)) { + xdl_info_t info; + void *handle = xdl_open(libraryName, XDL_DEFAULT); + memset(&info, 0, sizeof(xdl_info_t)); + if (0 > xdl_info(handle, XDL_DI_DLINFO, &info)) { + LOGI(OBFUSCATE(">>> xdl_info(XDL_DI_DLINFO, %llx" ") : FAILED"), (uintptr_t) handle); + xdl_close(handle); + return 0; + } else { + lib_links[libraryName] = (uintptr_t) info.dli_fbase; + xdl_close(handle); + } + } + return lib_links[libraryName]; +} - fp = fopen(filename, OBFUSCATE("rt")); - if (fp == NULL) { - perror(OBFUSCATE("fopen")); - goto done; +void* getSymAddress(const char *libraryName, const char *SymName) { + xdl_info_t info; + void *handle = xdl_open(libraryName, XDL_DEFAULT); + if (handle == nullptr) { + LOGE(OBFUSCATE("xdl_open failed for %s"), libraryName); + return nullptr; } - while (fgets(buffer, sizeof(buffer), fp)) { - if (strstr(buffer, library)) { - address = (DWORD) strtoul(buffer, NULL, 16); - goto done; - } + memset(&info, 0, sizeof(xdl_info_t)); + if (0 > xdl_info(handle, XDL_DI_DLINFO, &info)) { + LOGE(OBFUSCATE(">>> getsym_xdl_info(XDL_DI_DLINFO, %llx, %s" ") : FAILED"), (uintptr_t) handle, SymName); } - done: + void *symbol_addr = xdl_sym(handle, SymName, nullptr); // lookup "dynamic link symbols" in .dynsym - if (fp) { - fclose(fp); + if (symbol_addr == nullptr) { + LOGW(OBFUSCATE(">>> !xdl_sym -> xdl_dsym...")); + symbol_addr = xdl_dsym(handle, SymName, nullptr); // lookup "debugging symbols" in .symtab and ".symtab in .gnu_debugdata } - return address; + xdl_close(handle); + return symbol_addr; } -DWORD getAbsoluteAddress(const char *libraryName, DWORD relativeAddr) { - libBase = findLibrary(libraryName); - if (libBase == 0) - return 0; - return (reinterpret_cast(libBase + relativeAddr)); +void* getAbsAddress(const char *libraryName, uintptr_t relativeAddr) { + if (!lib_links.count(libraryName)) { + lib_links[libraryName] = getLibraryAddress(libraryName); + } + if (!lib_links.count(libraryName)) return nullptr; + return (void*)(lib_links[libraryName] + relativeAddr); } +void* getAbsoluteAddress(const char *libraryName, const char *relative) { + uintptr_t offset = str2offset(relative); + + if(offset != 0) { + return getAbsAddress(libraryName, offset); + } else { + return getSymAddress(libraryName, relative); + // ElfScanner is still available... you can use it for advanced searches + } +} jboolean isGameLibLoaded(JNIEnv *env, jobject thiz) { - return libLoaded; + return mainLibLoaded; } bool isLibraryLoaded(const char *libraryName) { - //libLoaded = true; char line[512] = {0}; FILE *fp = fopen(OBFUSCATE("/proc/self/maps"), OBFUSCATE("rt")); - if (fp != NULL) { + if (fp != nullptr) { while (fgets(line, sizeof(line), fp)) { std::string a = line; if (strstr(line, libraryName)) { - libLoaded = true; + // LOGI(OBFUSCATE("main library (%s) loaded: 0x%llx"), libraryName, getLibraryAddress(libraryName)); + mainLibLoaded = true; return true; } } @@ -64,15 +85,14 @@ bool isLibraryLoaded(const char *libraryName) { return false; } -uintptr_t str2Offset(const char *c) { +uintptr_t str2offset(const char *c) { int base = 16; // See if this function catches all possibilities. // If it doesn't, the function would have to be amended // whenever you add a combination of architecture and // compiler that is not yet addressed. static_assert(sizeof(uintptr_t) == sizeof(unsigned long) - || sizeof(uintptr_t) == sizeof(unsigned long long), - "Please add string to handle conversion for this architecture."); + || sizeof(uintptr_t) == sizeof(unsigned long long)); // Now choose the correct function ... if (sizeof(uintptr_t) == sizeof(unsigned long)) { @@ -81,4 +101,4 @@ uintptr_t str2Offset(const char *c) { // All other options exhausted, sizeof(uintptr_t) == sizeof(unsigned long long)) return strtoull(c, nullptr, base); -} +} \ No newline at end of file diff --git a/app/src/main/jni/Includes/Utils.hpp b/app/src/main/jni/Includes/Utils.hpp index 3231627..fc10c52 100644 --- a/app/src/main/jni/Includes/Utils.hpp +++ b/app/src/main/jni/Includes/Utils.hpp @@ -5,20 +5,20 @@ #include #include #include -#include #include +#include +#include "xDL/xdl.h" #include "Includes/Logger.h" +#include "KittyMemory/MemoryPatch.hpp" -typedef unsigned long DWORD; +uintptr_t getLibraryAddress(const char *library); -DWORD findLibrary(const char *library); - -DWORD getAbsoluteAddress(const char *libraryName, DWORD relativeAddr); +void* getAbsoluteAddress(const char *libraryName, const char *relative); jboolean isGameLibLoaded(JNIEnv *env, jobject thiz); bool isLibraryLoaded(const char *libraryName); -uintptr_t str2Offset(const char *c); +uintptr_t str2offset(const char *c); #endif \ No newline at end of file diff --git a/app/src/main/jni/Includes/get_device_api_level_inlines.h b/app/src/main/jni/Includes/get_device_api_level_inlines.h deleted file mode 100644 index 1b499a2..0000000 --- a/app/src/main/jni/Includes/get_device_api_level_inlines.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2018 The Android Open Source Project - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ - -#pragma once - - -#include - -__BEGIN_DECLS - -// Avoid circular dependencies since this is exposed from . -int __system_property_get(const char* _Nonnull __name, char* _Nonnull __value); -int atoi(const char* _Nonnull __s) __attribute_pure__; - -int api_level() { - char value[92] = { 0 }; - if (__system_property_get("ro.build.version.sdk", value) < 1) return -1; - int api_level = atoi(value); - return (api_level > 0) ? api_level : -1; -} - - -__END_DECLS - diff --git a/app/src/main/jni/KittyMemory/KittyArm64.cpp b/app/src/main/jni/KittyMemory/KittyArm64.cpp deleted file mode 100644 index 5ab0082..0000000 --- a/app/src/main/jni/KittyMemory/KittyArm64.cpp +++ /dev/null @@ -1,135 +0,0 @@ -#include "KittyArm64.hpp" - -// refs to -// https://github.com/CAS-Atlantic/AArch64-Encoding -// https://github.com/bminor/binutils-gdb -// https://github.com/capstone-engine/capstone -// https://github.com/qemu/QEMU -// https://reverseengineering.stackexchange.com/questions/15418/getting-function-address-by-reading-adrp-and-add-instruction-values -// https://stackoverflow.com/questions/41906688/what-are-the-semantics-of-adrp-and-adrl-instructions-in-arm-assembly - -namespace KittyArm64 -{ - - int32_t bit_from(uint32_t insn, int pos) - { - return ((1 << pos) & insn) >> pos; - } - - int32_t bits_from(uint32_t insn, int pos, int l) - { - return (insn >> pos) & ((1 << l) - 1); - } - - bool is_insn_adr(uint32_t insn) - { - return (insn & 0x9F000000) == 0x10000000; - } - - bool is_insn_adrp(uint32_t insn) - { - return (insn & 0x9F000000) == 0x90000000; - } - - // decode adr/adrp - bool decode_adr_imm(uint32_t insn, int64_t *imm) - { - if (is_insn_adr(insn) || is_insn_adrp(insn)) - { - // 21-bit imm encoded in adrp. - int64_t imm_val = bits_from(insn, 5, 19) << 2; // immhi - imm_val |= bits_from(insn, 29, 2); // immlo - - if (is_insn_adrp(insn)) - { - // Retrieve msb of 21-bit-signed imm for sign extension. - uint64_t msbt = (imm_val >> 20) & 1; - - // Real value is imm multiplied by 4k. Value now has 33-bit information. - imm_val <<= 12; - - // Sign extend to 64-bit by repeating msbt 31 (64-33) times and merge it - // with value. - *imm = ((((uint64_t)(1) << 32) - msbt) << 33) | imm_val; - } - else // adr - { - // Sign-extend the 21-bit immediate. - if (imm_val & (1 << (21 - 1))) - imm_val |= ~((1LL << 21) - 1); - - *imm = imm_val; - } - - return true; - } - - return false; - } - - /* - * 31 30 29 28 23 22 21 10 9 5 4 0 - * +--+--+--+-------------+--+-------------+-----+-----+ - * |sf|op| S| 1 0 0 0 1 0 |sh| imm12 | Rn | Rd | - * +--+--+--+-------------+--+-------------+-----+-----+ - * - * sf: 0 -> 32bit, 1 -> 64bit - * op: 0 -> add , 1 -> sub - * S: 1 -> set flags - * sh: 1 -> LSL imm by 12 - */ - - int32_t decode_addsub_imm(uint32_t insn) - { - int32_t imm12 = bits_from(insn, 10, 12); - - bool shift = bit_from(insn, 22) == 1; - - if (shift) - { - imm12 <<= 12; - } - - return imm12; - } - - bool is_insn_ld(uint32_t insn) - { - // L bit - return bit_from(insn, 22) == 1; - } - - bool is_insn_ldst(uint32_t insn) - { - return (insn & 0x0a000000) == 0x08000000; - } - - bool is_insn_ldst_uimm(uint32_t insn) - { - return (insn & 0x3b000000) == 0x39000000; - } - - // decode Load/store unsigned immediate - bool decode_ldrstr_uimm(uint32_t insn, int32_t *imm12) - { - if (is_insn_ldst_uimm(insn)) - { - *imm12 = bits_from(insn, 10, 12); - // shift with scale value - *imm12 <<= bits_from(insn, 30, 2); // size bits - - return true; - } - - return false; - } - -} - -namespace KittyArm -{ - int32_t decode_ldr_literal(uint32_t insn) - { - return KittyArm64::bits_from(insn, 0, 12); - } -} \ No newline at end of file diff --git a/app/src/main/jni/KittyMemory/KittyArm64.hpp b/app/src/main/jni/KittyMemory/KittyArm64.hpp deleted file mode 100644 index 3193dbf..0000000 --- a/app/src/main/jni/KittyMemory/KittyArm64.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace KittyArm64 -{ - - int32_t bit_from(uint32_t insn, int pos); - - int32_t bits_from(uint32_t insn, int pos, int l); - - bool is_insn_adr(uint32_t insn); - - bool is_insn_adrp(uint32_t insn); - - bool decode_adr_imm(uint32_t insn, int64_t *imm); - - int32_t decode_addsub_imm(uint32_t insn); - - bool is_insn_ld(uint32_t insn); - - bool is_insn_ldst(uint32_t insn); - - bool is_insn_ldst_uimm(uint32_t insn); - - bool decode_ldrstr_uimm(uint32_t insn, int32_t *offset); - -} - -namespace KittyArm -{ - int32_t decode_ldr_literal(uint32_t insn); -} \ No newline at end of file diff --git a/app/src/main/jni/KittyMemory/KittyAsm.cpp b/app/src/main/jni/KittyMemory/KittyAsm.cpp new file mode 100644 index 0000000..3359056 --- /dev/null +++ b/app/src/main/jni/KittyMemory/KittyAsm.cpp @@ -0,0 +1,698 @@ +#include "KittyAsm.hpp" + +// refs to +// https://github.com/CAS-Atlantic/AArch64-Encoding +// https://github.com/bminor/binutils-gdb +// https://github.com/capstone-engine/capstone +// https://github.com/qemu/QEMU +// https://reverseengineering.stackexchange.com/questions/15418/getting-function-address-by-reading-adrp-and-add-instruction-values +// https://stackoverflow.com/questions/41906688/what-are-the-semantics-of-adrp-and-adrl-instructions-in-arm-assembly + +namespace KittyAsm +{ + uint32_t bits(uint32_t v, int hi, int lo) + { + if (hi < lo) + return 0u; + + int width = hi - lo + 1; + if (width >= 32) + return v >> lo; + + uint32_t mask = (width == 32) ? 0xFFFFFFFFu : ((1u << width) - 1u); + return (v >> lo) & mask; + } +} // namespace KittyAsm + +using namespace KittyAsm; + +namespace KittyArm32 +{ + EKittyInsnTypeArm32 decodeInsnType(uint32_t instr) + { + if ((instr & 0x0C000000) == 0x00000000) + { + if ((instr & 0x01E00000) == 0x00800000) + return bits(instr, 19, 16) == 15 ? EKittyInsnTypeArm32::ADR : EKittyInsnTypeArm32::ADD; + + if ((instr & 0x01E00000) == 0x00400000) + return bits(instr, 19, 16) == 15 ? EKittyInsnTypeArm32::ADR : EKittyInsnTypeArm32::SUB; + + if ((instr & 0x01E00000) == 0x01A00000) + return EKittyInsnTypeArm32::MOV; + } + + if ((instr & 0x0FF00000) == 0x02800000) + return EKittyInsnTypeArm32::LDR_LITERAL; + + if ((instr & 0x0C500000) == 0x04100000) + return bits(instr, 19, 16) == 15 ? EKittyInsnTypeArm32::LDR_LITERAL : EKittyInsnTypeArm32::LDR; + + if ((instr & 0x0C500000) == 0x04000000) + return EKittyInsnTypeArm32::STR; + + if ((instr & 0x0C500000) == 0x04500000) + return bits(instr, 19, 16) == 15 ? EKittyInsnTypeArm32::LDR_LITERAL : EKittyInsnTypeArm32::LDRB; + + if ((instr & 0x0C500000) == 0x04400000) + return EKittyInsnTypeArm32::STRB; + + if ((instr & 0x0F1000F0) == 0x011000B0) + return EKittyInsnTypeArm32::LDRH; + + if ((instr & 0x0F1000F0) == 0x010000B0) + return EKittyInsnTypeArm32::STRH; + + if ((instr & 0x0F1000F0) == 0x011000D0) + return EKittyInsnTypeArm32::LDRSB; + + if ((instr & 0x0F1000F0) == 0x011000F0) + return EKittyInsnTypeArm32::LDRSH; + + if ((instr & 0x0F000000) == 0x0A000000) + return bits(instr, 31, 28) == 0xE ? EKittyInsnTypeArm32::B : EKittyInsnTypeArm32::B_COND; + + if ((instr & 0x0F000000) == 0x0B000000) + return EKittyInsnTypeArm32::BL; + + return EKittyInsnTypeArm32::UNKNOWN; + } + + KittyInsnArm32 decodeInsn(uint32_t instr, uint32_t address) + { + KittyInsnArm32 insn{}; + + EKittyInsnTypeArm32 insn_type = decodeInsnType(instr); + if (insn_type == EKittyInsnTypeArm32::UNKNOWN) + return insn; + + insn.bytes = instr; + insn.address = address; + insn.type = insn_type; + insn.typeStr = typeToString(insn_type); + + switch (insn_type) + { + case EKittyInsnTypeArm32::UNKNOWN: + return insn; + + case EKittyInsnTypeArm32::ADD: + case EKittyInsnTypeArm32::SUB: + case EKittyInsnTypeArm32::MOV: + case EKittyInsnTypeArm32::ADR: + { + bool I = bit(instr, 25); + uint32_t rn = bits(instr, 19, 16); + uint32_t rd = bits(instr, 15, 12); + uint32_t imm12 = bits(instr, 11, 0); + uint32_t imm8 = bits(imm12, 7, 0); + uint32_t rot = bits(imm12, 11, 8) * 2u; + uint32_t imm32 = ror32(imm8, rot); + insn.rd = regName(rd); + + if (insn_type != EKittyInsnTypeArm32::MOV) + insn.rn = regName(rn); + + if (!I) + insn.rt = regName(imm12); + else + insn.immediate = I ? imm32 : 0; + + if (rn == 15) + insn.target = address + 8u + insn.immediate; + + break; + } + + case EKittyInsnTypeArm32::LDRH: + case EKittyInsnTypeArm32::LDRSH: + case EKittyInsnTypeArm32::LDRSB: + case EKittyInsnTypeArm32::STRH: + { + bool U = bit(instr, 23); + uint32_t rn = bits(instr, 19, 16); + uint32_t rd = bits(instr, 15, 12); + uint32_t immH = bits(instr, 11, 8); + uint32_t immL = bits(instr, 3, 0); + uint32_t offset = (immH << 4) | immL; + insn.rd = regName(rd); + insn.rn = regName(rn); + insn.immediate = U ? offset : -((int32_t)offset); + break; + } + + case EKittyInsnTypeArm32::LDR: + case EKittyInsnTypeArm32::LDRB: + case EKittyInsnTypeArm32::STR: + case EKittyInsnTypeArm32::STRB: + case EKittyInsnTypeArm32::LDR_LITERAL: + { + bool U = bit(instr, 23); + uint32_t rn = bits(instr, 19, 16); + uint32_t rd = bits(instr, 15, 12); + uint32_t imm12 = bits(instr, 11, 0); + insn.rd = regName(rd); + insn.rn = regName(rn); + insn.immediate = U ? imm12 : -((int32_t)imm12); + // PC + if (rn == 15) + { + insn.target = address + 8u + insn.immediate; + } + break; + } + + case EKittyInsnTypeArm32::B: + case EKittyInsnTypeArm32::BL: + case EKittyInsnTypeArm32::B_COND: + { + uint32_t cond = bits(instr, 31, 28); + uint32_t imm24 = bits(instr, 23, 0); + int32_t simm = signExtend(imm24, 24) << 2; + insn.immediate = simm; + insn.target = address + 8u + simm; + if (insn_type == EKittyInsnTypeArm32::B_COND) + { + insn.cond = branchCondName(cond); + } + break; + } + } + + return insn; + } + + std::string typeToString(EKittyInsnTypeArm32 t) + { +#define CASE(x) \ + case EKittyInsnTypeArm32::x: \ + return #x; + switch (t) + { + CASE(UNKNOWN) + CASE(ADR) + CASE(ADD) + CASE(SUB) + CASE(MOV) + CASE(LDR) + CASE(STR) + CASE(LDRB) + CASE(STRB) + CASE(LDRH) + CASE(STRH) + CASE(LDRSH) + CASE(LDRSB) + CASE(LDR_LITERAL) + CASE(B) + CASE(BL) + CASE(B_COND) + } +#undef CASE + return "UNKNOWN"; + } +} // namespace KittyArm32 + +namespace KittyArm64 +{ + EKittyInsnTypeArm64 decodeInsnType(uint32_t instr) + { + // ADR + if ((instr & 0x9F000000u) == 0x10000000u) + { + return EKittyInsnTypeArm64::ADR; + } + // ADRP + if ((instr & 0x9F000000u) == 0x90000000u) + { + return EKittyInsnTypeArm64::ADRP; + } + + // ADD + if ((instr & 0xFF000000u) == 0x11000000u || (instr & 0xFF000000u) == 0x91000000u) + { + return EKittyInsnTypeArm64::ADD; + } + // SUB + if ((instr & 0xFF000000u) == 0x51000000u || (instr & 0xFF000000u) == 0xD1000000u) + { + return EKittyInsnTypeArm64::SUB; + } + + // MOVZ + if ((instr & 0x7F800000u) == 0x52800000u) + { + return EKittyInsnTypeArm64::MOVZ; + } + // MOVK + if ((instr & 0x7F800000u) == 0x72800000u) + { + return EKittyInsnTypeArm64::MOVK; + } + // MOVN + if ((instr & 0x7F800000u) == 0x12800000u) + { + return EKittyInsnTypeArm64::MOVN; + } + + // Load/Store (immediate offset) + { + if ((instr & 0xFFC00000) == 0xF9400000) + return EKittyInsnTypeArm64::LDR; + if ((instr & 0xFFC00000) == 0xF9000000) + return EKittyInsnTypeArm64::STR; + if ((instr & 0xFFC00000) == 0xB9400000) + return EKittyInsnTypeArm64::LDRW; + if ((instr & 0xFFC00000) == 0xB9000000) + return EKittyInsnTypeArm64::STRW; + + if ((instr & 0xFFC00000) == 0x39400000) + return EKittyInsnTypeArm64::LDRB; + if ((instr & 0xFFC00000) == 0x39000000) + return EKittyInsnTypeArm64::STRB; + + if ((instr & 0xFFC00000) == 0x79400000) + return EKittyInsnTypeArm64::LDRH; + if ((instr & 0xFFC00000) == 0x79000000) + return EKittyInsnTypeArm64::STRH; + + if ((instr & 0xFFC00000) == 0x39C00000 || (instr & 0xFFC00000) == 0x39800000) + return EKittyInsnTypeArm64::LDRSB; + if ((instr & 0xFFC00000) == 0x79C00000 || (instr & 0xFFC00000) == 0x79800000) + return EKittyInsnTypeArm64::LDRSH; + if ((instr & 0xFFC00000) == 0xB9800000) + return EKittyInsnTypeArm64::LDRSW; + } + + // Load/Store (post-indexed) + { + if ((instr & 0xFFC00C00) == 0xB8400400 || (instr & 0xFFC00C00) == 0xF8400400) + return EKittyInsnTypeArm64::LDR_POST; + if ((instr & 0xFFC00C00) == 0xB8000400 || (instr & 0xFFC00C00) == 0xF8000400) + return EKittyInsnTypeArm64::STR_POST; + + if ((instr & 0xFFC00C00) == 0x38400400) + return EKittyInsnTypeArm64::LDRB_POST; + if ((instr & 0xFFC00C00) == 0x38000400) + return EKittyInsnTypeArm64::STRB_POST; + + if ((instr & 0xFFC00C00) == 0x78400400) + return EKittyInsnTypeArm64::LDRH_POST; + if ((instr & 0xFFC00C00) == 0x78000400) + return EKittyInsnTypeArm64::STRH_POST; + + if ((instr & 0xFFC00C00) == 0x38C00400 || (instr & 0xFFC00C00) == 0x38800400) + return EKittyInsnTypeArm64::LDRSB_POST; + if ((instr & 0xFFC00C00) == 0x78C00400 || (instr & 0xFFC00C00) == 0x78800400) + return EKittyInsnTypeArm64::LDRSH_POST; + if ((instr & 0xFFC00C00) == 0xB8800400) + return EKittyInsnTypeArm64::LDRSW_POST; + } + + // Load/Store (pre-indexed) + { + if ((instr & 0xFFC00C00) == 0xB8400C00 || (instr & 0xFFC00C00) == 0xF8400C00) + return EKittyInsnTypeArm64::LDR_PRE; + if ((instr & 0xFFC00C00) == 0xB8000C00 || (instr & 0xFFC00C00) == 0xF8000C00) + return EKittyInsnTypeArm64::STR_PRE; + + if ((instr & 0xFFC00C00) == 0x38400C00) + return EKittyInsnTypeArm64::LDRB_PRE; + if ((instr & 0xFFC00C00) == 0x38000C00) + return EKittyInsnTypeArm64::STRB_PRE; + + if ((instr & 0xFFC00C00) == 0x78400C00) + return EKittyInsnTypeArm64::LDRH_PRE; + if ((instr & 0xFFC00C00) == 0x78000C00) + return EKittyInsnTypeArm64::STRH_PRE; + + if ((instr & 0xFFC00C00) == 0x38C00C00 || (instr & 0xFFC00C00) == 0x38800C00) + return EKittyInsnTypeArm64::LDRSB_PRE; + if ((instr & 0xFFC00C00) == 0x78C00C00 || (instr & 0xFFC00C00) == 0x78800C00) + return EKittyInsnTypeArm64::LDRSH_PRE; + if ((instr & 0xFFC00C00) == 0xB8800C00) + return EKittyInsnTypeArm64::LDRSW_PRE; + } + + // === Load/Store (unscaled) + { + if ((instr & 0xFFC00000) == 0xF8400000) + return EKittyInsnTypeArm64::LDUR; + if ((instr & 0xFFC00000) == 0xF8000000) + return EKittyInsnTypeArm64::STUR; + if ((instr & 0xFFC00000) == 0xB8400000) + return EKittyInsnTypeArm64::LDURW; + if ((instr & 0xFFC00000) == 0xB8000000) + return EKittyInsnTypeArm64::STURW; + if ((instr & 0xFFC00000) == 0x38400000) + return EKittyInsnTypeArm64::LDURB; + if ((instr & 0xFFC00000) == 0x38000000) + return EKittyInsnTypeArm64::STURB; + if ((instr & 0xFFC00000) == 0x78400000) + return EKittyInsnTypeArm64::LDURH; + if ((instr & 0xFFC00000) == 0x78000000) + return EKittyInsnTypeArm64::STURH; + if ((instr & 0xFFC00000) == 0xB8800000) + return EKittyInsnTypeArm64::LDURSW; + if ((instr & 0xFFC00000) == 0x38800000u || (instr & 0xFFC00000) == 0x38C00000u) + return EKittyInsnTypeArm64::LDURSB; + if ((instr & 0xFFC00000) == 0x78800000u || (instr & 0xFFC00000) == 0x78C00000u) + return EKittyInsnTypeArm64::LDURSH; + } + + // Load/Store (Literal) + { + if ((instr & 0xFFC00000) == 0x18000000) + return EKittyInsnTypeArm64::LDRW_LITERAL; + if ((instr & 0xFFC00000) == 0x58000000) + return EKittyInsnTypeArm64::LDR_LITERAL; + if ((instr & 0xFFC00000) == 0x98000000) + return EKittyInsnTypeArm64::LDRSW_LITERAL; + } + + // B + if ((instr & 0xFC000000u) == 0x14000000u) + { + return EKittyInsnTypeArm64::B; + } + + // BL + if ((instr & 0xFC000000u) == 0x94000000u) + { + return EKittyInsnTypeArm64::BL; + } + + // B.Cond + if ((instr & 0xFF000010u) == 0x54000000u) + { + return EKittyInsnTypeArm64::B_COND; + } + + // CBZ/CBNZ + { + if ((instr & 0x7F000000u) == 0x34000000u) + return EKittyInsnTypeArm64::CBZ; + if ((instr & 0x7F000000u) == 0x35000000u) + return EKittyInsnTypeArm64::CBNZ; + } + + // TBZ/TBNZ + { + if ((instr & 0x7F000000u) == 0x36000000u) + return EKittyInsnTypeArm64::TBZ; + if ((instr & 0x7F000000u) == 0x37000000u) + return EKittyInsnTypeArm64::TBNZ; + } + + return EKittyInsnTypeArm64::UNKNOWN; + } + + KittyInsnArm64 decodeInsn(uint32_t instr, uint64_t address) + { + KittyInsnArm64 insn{}; + + EKittyInsnTypeArm64 insn_type = decodeInsnType(instr); + if (insn_type == EKittyInsnTypeArm64::UNKNOWN) + return insn; + + insn.bytes = instr; + insn.address = address; + insn.type = insn_type; + insn.typeStr = typeToString(insn_type); + + switch (insn_type) + { + case EKittyInsnTypeArm64::UNKNOWN: + return insn; + + case EKittyInsnTypeArm64::ADR: + case EKittyInsnTypeArm64::ADRP: + { + uint32_t rd = bits(instr, 4, 0); + uint32_t immlo = bits(instr, 30, 29); + uint32_t immhi = bits(instr, 23, 5); + uint64_t imm = (uint64_t)((immhi << 2) | immlo); + insn.rd = xRegName(rd, false); + if (insn_type == EKittyInsnTypeArm64::ADR) + { + + int64_t simm = signExtend(imm, 21); + insn.immediate = simm; + insn.target = address + simm; + } + else + { + int64_t simm = signExtend(imm, 21) << 12; + insn.immediate = simm; + insn.target = (address & ~0xFFFULL) + simm; + } + break; + } + + case EKittyInsnTypeArm64::MOVZ: + case EKittyInsnTypeArm64::MOVK: + case EKittyInsnTypeArm64::MOVN: + { + bool is64 = bit(instr, 31); + uint32_t rd = bits(instr, 4, 0); + uint32_t imm16 = bits(instr, 20, 5); + uint32_t hw = bits(instr, 22, 21); + uint64_t imm = (uint64_t)(imm16 << (hw * 16)); + insn.rd = is64 ? xRegName(rd, false) : wRegName(rd, false); + insn.immediate = insn_type != EKittyInsnTypeArm64::MOVN ? imm : (int64_t)~imm; + break; + } + + case EKittyInsnTypeArm64::ADD: + case EKittyInsnTypeArm64::SUB: + { + bool is64 = bit(instr, 31); + uint32_t rd = bits(instr, 4, 0); + uint32_t rn = bits(instr, 9, 5); + uint32_t imm12 = bits(instr, 21, 10); + uint32_t sh = bits(instr, 23, 22); + uint64_t imm = (uint64_t)(sh == 1 ? imm12 << 12 : imm12); + insn.rd = is64 ? xRegName(rd, false) : wRegName(rd, false); + insn.rn = is64 ? xRegName(rn, true) : wRegName(rn, true); + insn.immediate = imm; + break; + } + + // ldr/str uimm12 + case EKittyInsnTypeArm64::LDR: + case EKittyInsnTypeArm64::STR: + case EKittyInsnTypeArm64::LDRW: + case EKittyInsnTypeArm64::STRW: + case EKittyInsnTypeArm64::LDRB: + case EKittyInsnTypeArm64::STRB: + case EKittyInsnTypeArm64::LDRH: + case EKittyInsnTypeArm64::STRH: + case EKittyInsnTypeArm64::LDRSB: + case EKittyInsnTypeArm64::LDRSH: + case EKittyInsnTypeArm64::LDRSW: + { + uint32_t size = bits(instr, 31, 30); + uint32_t rn = bits(instr, 9, 5); + uint32_t rt = bits(instr, 4, 0); + uint32_t imm12 = bits(instr, 21, 10); + uint64_t offset = (uint64_t)(imm12 << size); + insn.rn = xRegName(rn, true); + insn.rt = size == 3 ? xRegName(rt, false) : wRegName(rt, false); + insn.immediate = offset; + break; + } + + // ldr/str post/pre indexed imm9 + case EKittyInsnTypeArm64::LDR_PRE: + case EKittyInsnTypeArm64::STR_PRE: + case EKittyInsnTypeArm64::LDRB_PRE: + case EKittyInsnTypeArm64::STRB_PRE: + case EKittyInsnTypeArm64::LDRH_PRE: + case EKittyInsnTypeArm64::STRH_PRE: + case EKittyInsnTypeArm64::LDRSB_PRE: + case EKittyInsnTypeArm64::LDRSH_PRE: + case EKittyInsnTypeArm64::LDRSW_PRE: + case EKittyInsnTypeArm64::LDR_POST: + case EKittyInsnTypeArm64::STR_POST: + case EKittyInsnTypeArm64::LDRB_POST: + case EKittyInsnTypeArm64::STRB_POST: + case EKittyInsnTypeArm64::LDRH_POST: + case EKittyInsnTypeArm64::STRH_POST: + case EKittyInsnTypeArm64::LDRSB_POST: + case EKittyInsnTypeArm64::LDRSH_POST: + case EKittyInsnTypeArm64::LDRSW_POST: + { + uint32_t size = bits(instr, 31, 30); + uint32_t rn = bits(instr, 9, 5); + uint32_t rt = bits(instr, 4, 0); + uint32_t imm9 = bits(instr, 20, 12); + int64_t simm = signExtend(imm9, 9); + insn.rn = xRegName(rn, true); + insn.rt = size == 3 ? xRegName(rt, false) : wRegName(rt, false); + insn.immediate = simm; + break; + } + + // imm9 unscaled ldr/str + case EKittyInsnTypeArm64::LDUR: + case EKittyInsnTypeArm64::STUR: + case EKittyInsnTypeArm64::LDURW: + case EKittyInsnTypeArm64::STURW: + case EKittyInsnTypeArm64::LDURB: + case EKittyInsnTypeArm64::STURB: + case EKittyInsnTypeArm64::LDURH: + case EKittyInsnTypeArm64::STURH: + case EKittyInsnTypeArm64::LDURSB: + case EKittyInsnTypeArm64::LDURSH: + case EKittyInsnTypeArm64::LDURSW: + { + uint32_t size = bits(instr, 31, 30); + uint32_t rn = bits(instr, 9, 5); + uint32_t rt = bits(instr, 4, 0); + uint32_t imm9 = bits(instr, 20, 12); + int64_t simm = signExtend(imm9, 9); + insn.rn = xRegName(rn, true); + insn.rt = size == 3 ? xRegName(rt, false) : wRegName(rt, false); + insn.immediate = simm; + break; + } + + case EKittyInsnTypeArm64::LDR_LITERAL: + case EKittyInsnTypeArm64::LDRW_LITERAL: + case EKittyInsnTypeArm64::LDRSW_LITERAL: + { + uint32_t size = bits(instr, 31, 30); + uint32_t rt = bits(instr, 4, 0); + uint32_t imm19 = bits(instr, 23, 5); + int64_t simm = signExtend(imm19, 19) << 2; + insn.rn = "PC"; + insn.rt = size == 3 ? xRegName(rt, false) : wRegName(rt, false); + insn.immediate = simm; + insn.target = address + simm; + break; + } + + case EKittyInsnTypeArm64::B: + case EKittyInsnTypeArm64::BL: + { + uint32_t imm26 = bits(instr, 25, 0); + int64_t simm = signExtend(imm26, 26) << 2; + insn.immediate = simm; + insn.target = address + simm; + break; + } + + case EKittyInsnTypeArm64::B_COND: + { + uint32_t cond = bits(instr, 3, 0); + uint32_t imm19 = bits(instr, 23, 5); + int64_t simm = signExtend(imm19, 19) << 2; + insn.immediate = simm; + insn.target = address + simm; + insn.cond = branchCondName(cond); + break; + } + + case EKittyInsnTypeArm64::CBZ: + case EKittyInsnTypeArm64::CBNZ: + { + bool is64 = bit(instr, 32); + uint32_t imm19 = bits(instr, 23, 5); + uint32_t rt = bits(instr, 4, 0); + int64_t simm = signExtend(imm19, 19) << 2; + insn.rt = is64 ? xRegName(rt, false) : wRegName(rt, false); + insn.immediate = simm; + insn.target = address + simm; + break; + } + + case EKittyInsnTypeArm64::TBZ: + case EKittyInsnTypeArm64::TBNZ: + { + bool is64 = bit(instr, 32); + uint32_t rt = bits(instr, 4, 0); + uint32_t bit5 = (bits(instr, 31, 31) & 1) << 5; + uint32_t bit_lo = bits(instr, 23, 19); + uint32_t bitpos = bit5 | bit_lo; + uint32_t imm14 = bits(instr, 18, 5); + int64_t simm = signExtend(imm14, 14) << 2; + insn.rt = is64 ? xRegName(rt, false) : wRegName(rt, false); + insn.immediate = simm; + insn.bitpos = bitpos; + insn.target = address + simm; + break; + } + } + + return insn; + } + + std::string typeToString(EKittyInsnTypeArm64 t) + { +#define CASE(x) \ + case EKittyInsnTypeArm64::x: \ + return #x; + switch (t) + { + CASE(UNKNOWN) + CASE(ADR) + CASE(ADRP) + CASE(ADD) + CASE(SUB) + CASE(MOVZ) + CASE(MOVN) + CASE(MOVK) + CASE(LDR) + CASE(STR) + CASE(LDRW) + CASE(STRW) + CASE(LDRB) + CASE(STRB) + CASE(LDRH) + CASE(STRH) + CASE(LDRSB) + CASE(LDRSH) + CASE(LDRSW) + CASE(LDR_PRE) + CASE(STR_PRE) + CASE(LDRB_PRE) + CASE(STRB_PRE) + CASE(LDRH_PRE) + CASE(STRH_PRE) + CASE(LDRSB_PRE) + CASE(LDRSH_PRE) + CASE(LDRSW_PRE) + CASE(LDR_POST) + CASE(STR_POST) + CASE(LDRB_POST) + CASE(STRB_POST) + CASE(LDRH_POST) + CASE(STRH_POST) + CASE(LDRSB_POST) + CASE(LDRSH_POST) + CASE(LDRSW_POST) + CASE(LDUR) + CASE(STUR) + CASE(LDURW) + CASE(STURW) + CASE(LDURB) + CASE(STURB) + CASE(LDURH) + CASE(STURH) + CASE(LDURSB) + CASE(LDURSH) + CASE(LDURSW) + CASE(LDR_LITERAL) + CASE(LDRW_LITERAL) + CASE(LDRSW_LITERAL) + CASE(B) + CASE(BL) + CASE(B_COND) + CASE(CBZ) + CASE(CBNZ) + CASE(TBZ) + CASE(TBNZ) + } +#undef CASE + return "UNKNOWN"; + } +} // namespace KittyArm64 \ No newline at end of file diff --git a/app/src/main/jni/KittyMemory/KittyAsm.hpp b/app/src/main/jni/KittyMemory/KittyAsm.hpp new file mode 100644 index 0000000..0697ad8 --- /dev/null +++ b/app/src/main/jni/KittyMemory/KittyAsm.hpp @@ -0,0 +1,225 @@ +#pragma once + +#include +#include + +enum class EKittyInsnTypeArm32 +{ + UNKNOWN, + ADR, + ADD, + SUB, + MOV, + LDR, + STR, + LDRB, + STRB, + LDRH, + STRH, + LDRSB, + LDRSH, + LDR_LITERAL, + B, + BL, + B_COND +}; + +struct KittyInsnArm32 +{ + EKittyInsnTypeArm32 type; + std::string typeStr; + std::string rd, rn, rt; + uint32_t bytes; + uint32_t address; + int32_t immediate; + uint32_t target; + std::string cond; + KittyInsnArm32() : type(EKittyInsnTypeArm32::UNKNOWN), bytes(0), address(0), immediate(0), target(0) + { + } + inline bool isValid() const + { + return bytes != 0 && type != EKittyInsnTypeArm32::UNKNOWN; + } +}; + +enum class EKittyInsnTypeArm64 +{ + UNKNOWN, + ADR, + ADRP, + ADD, + SUB, + MOVZ, + MOVN, + MOVK, + LDR, + STR, + LDRW, + STRW, + LDRB, + STRB, + LDRH, + STRH, + LDRSB, + LDRSH, + LDRSW, + LDR_PRE, + STR_PRE, + LDRB_PRE, + STRB_PRE, + LDRH_PRE, + STRH_PRE, + LDRSB_PRE, + LDRSH_PRE, + LDRSW_PRE, + LDR_POST, + STR_POST, + LDRB_POST, + STRB_POST, + LDRH_POST, + STRH_POST, + LDRSB_POST, + LDRSH_POST, + LDRSW_POST, + LDUR, + STUR, + LDURW, + STURW, + LDURB, + STURB, + LDURH, + STURH, + LDURSB, + LDURSH, + LDURSW, + LDR_LITERAL, + LDRW_LITERAL, + LDRSW_LITERAL, + B, + BL, + B_COND, + CBZ, + CBNZ, + TBZ, + TBNZ, +}; + +struct KittyInsnArm64 +{ + EKittyInsnTypeArm64 type = EKittyInsnTypeArm64::UNKNOWN; + std::string typeStr; + std::string rd, rn, rt; + uint32_t bytes; + uint64_t address; + int64_t immediate; + int64_t bitpos; + uint64_t target; + std::string cond; + KittyInsnArm64() : type(EKittyInsnTypeArm64::UNKNOWN), bytes(0), address(0), immediate(0), bitpos(0), target(0) + { + } + inline bool isValid() const + { + return bytes != 0 && type != EKittyInsnTypeArm64::UNKNOWN; + } +}; + +namespace KittyAsm +{ + uint32_t bits(uint32_t v, int hi, int lo); + + inline bool bit(uint32_t v, int pos) + { + return bits(v, pos, pos) != 0; + } + + inline uint32_t ror32(uint32_t value, unsigned int shift) + { + shift &= 31u; + return (value >> shift) | (value << ((32 - shift) & 31u)); + } +} // namespace KittyAsm + +namespace KittyArm32 +{ + inline int32_t signExtend(uint32_t val, int bits) + { + if (bits <= 0 || bits >= 64) + return (int32_t)val; + + uint32_t m = 1ULL << (bits - 1); + return (int32_t)((val ^ m) - m); + } + + inline std::string regName(unsigned r) + { + if (r == 13) + return "sp"; + if (r == 14) + return "lr"; + if (r == 15) + return "pc"; + + std::string reg = "r"; + return reg + std::to_string(r); + } + + inline std::string branchCondName(uint32_t cond) + { + static const char *names[16] = {"EQ", "NE", "CS/HS", "CC/LO", "MI", "PL", "VS", "VC", + "HI", "LS", "GE", "LT", "GT", "LE", "AL", "NV"}; + uint32_t index = cond & 0xF; + return index < 16 ? names[index] : ""; + } + + EKittyInsnTypeArm32 decodeInsnType(uint32_t instr); + + KittyInsnArm32 decodeInsn(uint32_t instr, uint32_t address = 0); + + std::string typeToString(EKittyInsnTypeArm32 t); +} // namespace KittyArm32 + +namespace KittyArm64 +{ + inline int64_t signExtend(uint32_t val, int bits) + { + if (bits <= 0 || bits >= 64) + return (int64_t)val; + + uint64_t m = 1ULL << (bits - 1); + return (int64_t)((val ^ m) - m); + } + + inline std::string xRegName(unsigned reg, bool isRn) + { + if (reg == 31) + { + return isRn ? "SP" : "XZR"; + } + return std::string("X") + std::to_string(reg); + } + + inline std::string wRegName(unsigned reg, bool isRn) + { + if (reg == 31) + { + return isRn ? "SP" : "WZR"; + } + return std::string("W") + std::to_string(reg); + } + + inline std::string branchCondName(uint32_t cond) + { + static const char *names[16] = {"EQ", "NE", "HS", "LO", "MI", "PL", "VS", "VC", + "HI", "LS", "GE", "LT", "GT", "LE", "AL", "NV"}; + uint32_t index = cond & 0xF; + return index < 16 ? names[index] : ""; + } + + EKittyInsnTypeArm64 decodeInsnType(uint32_t instr); + + KittyInsnArm64 decodeInsn(uint32_t instr, uint64_t address = 0); + + std::string typeToString(EKittyInsnTypeArm64 t); +} // namespace KittyArm64 \ No newline at end of file diff --git a/app/src/main/jni/KittyMemory/KittyIOFile.cpp b/app/src/main/jni/KittyMemory/KittyIOFile.cpp new file mode 100644 index 0000000..0c4be51 --- /dev/null +++ b/app/src/main/jni/KittyMemory/KittyIOFile.cpp @@ -0,0 +1,318 @@ +#include "KittyIOFile.hpp" + +bool KittyIOFile::Open() +{ + if (_fd <= 0) + { + errno = 0, _error = 0; + if (_mode) + _fd = open(_filePath.c_str(), _flags, _mode); + else + _fd = open(_filePath.c_str(), _flags); + + _error = _fd > 0 ? 0 : errno; + } + return _fd > 0; +} + +bool KittyIOFile::Close() +{ + bool rt = true; + if (_fd > 0) + { + errno = 0, _error = 0; + rt = close(_fd) != -1; + if (!rt) + _error = errno; + + _fd = 0; + } + return rt; +} + +ssize_t KittyIOFile::Read(void *buffer, size_t len) +{ + char *buf = (char *)buffer; + size_t bytesRead = 0; + do + { + errno = 0, _error = 0; + ssize_t readSize = KT_EINTR_RETRY(read(_fd, buf + bytesRead, len - bytesRead)); + if (readSize <= 0) + { + if (readSize < 0) + _error = errno; + break; + } + + bytesRead += readSize; + } while (bytesRead < len); + return bytesRead; +} + +ssize_t KittyIOFile::Write(const void *buffer, size_t len) +{ + const char *buf = (const char *)buffer; + size_t bytesWritten = 0; + do + { + errno = 0, _error = 0; + ssize_t writeSize = KT_EINTR_RETRY(write(_fd, buf + bytesWritten, len - bytesWritten)); + if (writeSize <= 0) + { + if (writeSize < 0) + _error = errno; + break; + } + + bytesWritten += writeSize; + } while (bytesWritten < len); + return bytesWritten; +} + +ssize_t KittyIOFile::Read(uintptr_t offset, void *buffer, size_t len) +{ + char *buf = (char *)buffer; + size_t bytesRead = 0; + do + { + errno = 0, _error = 0; +#ifdef __APPLE__ + ssize_t readSize = KT_EINTR_RETRY(pread(_fd, buf + bytesRead, len - bytesRead, offset + bytesRead)); +#else + ssize_t readSize = KT_EINTR_RETRY(pread64(_fd, buf + bytesRead, len - bytesRead, offset + bytesRead)); +#endif + if (readSize <= 0) + { + if (readSize < 0) + _error = errno; + break; + } + + bytesRead += readSize; + } while (bytesRead < len); + return bytesRead; +} + +ssize_t KittyIOFile::Write(uintptr_t offset, const void *buffer, size_t len) +{ + const char *buf = (const char *)buffer; + size_t bytesWritten = 0; + do + { + errno = 0, _error = 0; +#ifdef __APPLE__ + ssize_t writeSize = KT_EINTR_RETRY(pwrite(_fd, buf + bytesWritten, len - bytesWritten, offset + bytesWritten)); +#else + ssize_t writeSize = KT_EINTR_RETRY(pwrite64(_fd, buf + bytesWritten, len - bytesWritten, offset + bytesWritten)); +#endif + if (writeSize <= 0) + { + if (writeSize < 0) + _error = errno; + break; + } + + bytesWritten += writeSize; + } while (bytesWritten < len); + return bytesWritten; +} + +#ifdef __APPLE__ +struct stat KittyIOFile::Stat() +{ + errno = 0, _error = 0; + struct stat s; + if (stat(_filePath.c_str(), &s) == -1) + _error = errno; + return s; +} +#else +struct stat64 KittyIOFile::Stat() +{ + errno = 0, _error = 0; + struct stat64 s; + if (stat64(_filePath.c_str(), &s) == -1) + _error = errno; + return s; +} +#endif + +bool KittyIOFile::readToString(std::string *str) +{ + if (!str) + return false; + + str->clear(); + + const ssize_t flen = Stat().st_size; + if (flen > 0) + { + str->resize(flen, 0); + return Read(0, (void*)(str->data()), flen) == flen; + } + + // incase stat fails to get file size + std::vector tmp_buf(KT_IO_BUFFER_MAX_SIZE, 0); + ssize_t n = 0, off = 0; + while ((n = Read(off, tmp_buf.data(), KT_IO_BUFFER_MAX_SIZE)) > 0) + { + off += n; + str->append(tmp_buf.data(), n); + } + + return n != -1; +} + +bool KittyIOFile::readToBuffer(std::vector *buf) +{ + if (!buf) + return false; + + buf->clear(); + + const ssize_t flen = Stat().st_size; + if (flen > 0) + { + buf->resize(flen, 0); + return Read(0, buf->data(), flen) == flen; + } + + // incase stat fails to get file size + std::vector tmp_buf(KT_IO_BUFFER_MAX_SIZE, 0); + ssize_t n = 0, off = 0; + while ((n = Read(off, tmp_buf.data(), KT_IO_BUFFER_MAX_SIZE)) > 0) + { + off += n; + buf->insert(buf->end(), tmp_buf.data(), tmp_buf.data() + n); + } + + return n != -1; +} + +bool KittyIOFile::writeToFile(uintptr_t offset, size_t len, const std::string &filePath) +{ + KittyIOFile of(filePath, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0666); + of.Delete(); + if (!of.Open()) return false; + + uintptr_t woff = 0; + std::vector buf(KT_IO_BUFFER_MAX_SIZE); + + while (len) + { + memset(buf.data(), 0, buf.size()); + + ssize_t nread = Read(offset, buf.data(), buf.size()); + if (nread <= 0) + break; + + if (of.Write(woff, buf.data(), nread) != nread) + break; + + offset += nread; + len -= nread; + woff += nread; + } + + return ssize_t(len) <= 0; +} + +bool KittyIOFile::writeToFile(const std::string &filePath) +{ + ssize_t size = Stat().st_size; + if (size) + { + return writeToFile(0, size, filePath); + } + + KittyIOFile of(filePath, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0666); + of.Delete(); + if (!of.Open()) return false; + + uintptr_t offset = 0; + std::vector buf(KT_IO_BUFFER_MAX_SIZE); + + while (true) + { + memset(buf.data(), 0, buf.size()); + + ssize_t nread = Read(offset, buf.data(), buf.size()); + if (nread <= 0) + break; + + if (of.Write(offset, buf.data(), nread) != nread) + return false; + + offset += nread; + } + + return true; +} + +bool KittyIOFile::writeToFd(int fd) +{ + if (fd <= 0) + return false; + + std::vector buf; + if (!readToBuffer(&buf) || buf.empty()) + return false; + + char *ptr = buf.data(); + ssize_t len = buf.size(); + + do + { + ssize_t nwritten = KT_EINTR_RETRY(write(fd, ptr, len)); + if (nwritten <= 0) + { + _error = errno; + return false; + } + + ptr += nwritten; + len -= nwritten; + } while (len > 0); + + return true; +} + +bool KittyIOFile::readFileToString(const std::string& filePath, std::string* str) +{ + KittyIOFile of(filePath, O_RDONLY | O_CLOEXEC); + return of.Open() && of.readToString(str); +} + +bool KittyIOFile::readFileToBuffer(const std::string& filePath, std::vector* buf) +{ + KittyIOFile of(filePath, O_RDONLY | O_CLOEXEC); + return of.Open() && of.readToBuffer(buf); +} + +bool KittyIOFile::copy(const std::string &srcFilePath, const std::string &dstFilePath) +{ + KittyIOFile src(srcFilePath, O_RDONLY | O_CLOEXEC); + return src.Open() && src.writeToFile(dstFilePath); +} + +void KittyIOFile::listFilesCallback(const std::string& dirPath, std::function cb) +{ + if (auto dir = opendir(dirPath.c_str())) + { + while (auto f = readdir(dir)) + { + if (f->d_name[0] == '.') + continue; + + if (f->d_type == DT_DIR) + listFilesCallback(dirPath + f->d_name + "/", cb); + + if (f->d_type == DT_REG) + { + if (cb && cb(dirPath + f->d_name)) return; + } + } + closedir(dir); + } +} \ No newline at end of file diff --git a/app/src/main/jni/KittyMemory/KittyIOFile.hpp b/app/src/main/jni/KittyMemory/KittyIOFile.hpp new file mode 100644 index 0000000..40261ff --- /dev/null +++ b/app/src/main/jni/KittyMemory/KittyIOFile.hpp @@ -0,0 +1,84 @@ +#pragma once + +#include "KittyUtils.hpp" + +#define KT_IO_BUFFER_MAX_SIZE (1024*1024) + +class KittyIOFile +{ +private: + int _fd; + std::string _filePath; + int _flags; + mode_t _mode; + int _error; + +public: + KittyIOFile() : _fd(0), _flags(0), _mode(0), _error(0) {} + + KittyIOFile(const std::string &filePath, int flags, mode_t mode) + : _fd(0), _filePath(filePath), _flags(flags), _mode(mode), _error(0) {} + + KittyIOFile(const std::string &filePath, int flags) + : _fd(0), _filePath(filePath), _flags(flags), _mode(0), _error(0) {} + + ~KittyIOFile() + { + if (_fd > 0) + { + close(_fd); + } + } + + bool Open(); + bool Close(); + + inline int lastError() const { return _error; } + inline std::string lastStrError() const { return _error ? strerror(_error) : ""; } + + inline int FD() const { return _fd; } + inline std::string Path() const { return _filePath; } + inline int Flags() const { return _flags; } + inline mode_t Mode() const { return _mode; } + + ssize_t Read(void *buffer, size_t len); + ssize_t Write(const void *buffer, size_t len); + + ssize_t Read(uintptr_t offset, void *buffer, size_t len); + ssize_t Write(uintptr_t offset, const void *buffer, size_t len); + + inline bool Exists() { return access(_filePath.c_str(), F_OK) != -1; } + + inline bool canRead() { return access(_filePath.c_str(), R_OK) != -1; } + inline bool canWrite() { return access(_filePath.c_str(), W_OK) != -1; } + inline bool canExecute() { return access(_filePath.c_str(), X_OK) != -1; } + + inline bool isFile() + { + struct stat s; + return stat(_filePath.c_str(), &s) != -1 && S_ISREG(s.st_mode); + } + + inline bool Delete() { return unlink(_filePath.c_str()) != -1; } + +#ifdef __APPLE__ + struct stat Stat(); +#else + struct stat64 Stat(); +#endif + + bool readToString(std::string *str); + bool readToBuffer(std::vector *buf); + + bool writeToFile(uintptr_t offset, size_t len, const std::string &filePath); + bool writeToFile(const std::string &filePath); + + bool writeToFd(int fd); + + static bool readFileToString(const std::string &filePath, std::string *str); + static bool readFileToBuffer(const std::string &filePath, std::vector *buf); + + static bool copy(const std::string &srcFilePath, const std::string &dstFilePath); + + static void listFilesCallback(const std::string& dir, std::function cb); +}; \ No newline at end of file diff --git a/app/src/main/jni/KittyMemory/KittyInclude.hpp b/app/src/main/jni/KittyMemory/KittyInclude.hpp index 542276f..e3b043a 100644 --- a/app/src/main/jni/KittyMemory/KittyInclude.hpp +++ b/app/src/main/jni/KittyMemory/KittyInclude.hpp @@ -4,15 +4,26 @@ #include "KittyMemory.hpp" #include "MemoryPatch.hpp" #include "KittyScanner.hpp" -#include "KittyArm64.hpp" +#include "KittyAsm.hpp" +#include "KittyPtrValidator.hpp" +#include "KittyIOFile.hpp" #ifdef __ANDROID__ using KittyMemory::ProcMap; +using KittyMemory::EProcMapFilter; using KittyScanner::RegisterNativeFn; using KittyScanner::ElfScanner; +using KittyScanner::LinkerScanner; +using KittyScanner::EScanElfType; +using KittyScanner::EScanElfFilter; +using KittyScanner::kitty_soinfo_t; +using KittyScanner::NativeBridgeScanner; +using KittyScanner::nbItf_data_t; +using KittyScanner::KT_JNICallType; +using KittyScanner::NativeBridgeLinker; #elif __APPLE__ #include "writeData.hpp" using KittyMemory::seg_data_t; using KittyMemory::MemoryFileInfo; -#endif \ No newline at end of file +#endif diff --git a/app/src/main/jni/KittyMemory/KittyMemory.cpp b/app/src/main/jni/KittyMemory/KittyMemory.cpp index 6aecb59..218fc17 100644 --- a/app/src/main/jni/KittyMemory/KittyMemory.cpp +++ b/app/src/main/jni/KittyMemory/KittyMemory.cpp @@ -12,68 +12,79 @@ bool findMSHookMemory(void *dst, const void *src, size_t len); #endif extern "C" { - kern_return_t mach_vm_protect(vm_map_t target_task, mach_vm_address_t address, mach_vm_size_t size, boolean_t set_maximum, vm_prot_t new_protection); - - kern_return_t mach_vm_write(vm_map_t target_task, mach_vm_address_t address, vm_offset_t data, mach_msg_type_number_t dataCnt); - - kern_return_t mach_vm_read_overwrite(vm_map_read_t target_task, mach_vm_address_t address, mach_vm_size_t size, mach_vm_address_t data, mach_vm_size_t *outsize); + kern_return_t mach_vm_protect(vm_map_t target_task, mach_vm_address_t address, mach_vm_size_t size, + boolean_t set_maximum, vm_prot_t new_protection); + + kern_return_t mach_vm_write(vm_map_t target_task, mach_vm_address_t address, vm_offset_t data, + mach_msg_type_number_t dataCnt); + + kern_return_t mach_vm_read_overwrite(vm_map_read_t target_task, mach_vm_address_t address, mach_vm_size_t size, + mach_vm_address_t data, mach_vm_size_t *outsize); } #endif -namespace KittyMemory { +namespace KittyMemory +{ #ifdef __ANDROID__ - - int setAddressProtection(const void *address, size_t length, int protection) + + int memProtect(const void *address, size_t length, int protection) { uintptr_t pageStart = KT_PAGE_START(address); - uintptr_t pageLen = KT_PAGE_LEN2(address, length); + size_t pageLen = KT_PAGE_LEN2(address, length); int ret = mprotect(reinterpret_cast(pageStart), pageLen, protection); - KITTY_LOGD("mprotect(%p, %zu, %d) = %d", address, length, protection, ret); + KITTY_LOGD("mprotect(%p, %zu, %d) = %d", (void *)pageStart, pageLen, protection, ret); return ret; } - bool memRead(const void* address, void* buffer, size_t len) + bool memRead(const void *address, void *buffer, size_t len) { KITTY_LOGD("memRead(%p, %p, %zu)", address, buffer, len); - if (!address) { + if (!address) + { KITTY_LOGE("memRead err address (%p) is null", address); return false; } - if (!buffer) { + if (!buffer) + { KITTY_LOGE("memRead err buffer (%p) is null", buffer); return false; } - if (!len) { + if (!len) + { KITTY_LOGE("memRead err invalid len"); return false; } ProcMap addressMap = getAddressMap(address); - if (!addressMap.isValid()) { + if (!addressMap.isValid()) + { KITTY_LOGE("memRead err couldn't find address (%p) in any map", address); return false; } - if (addressMap.protection & PROT_READ) { + if (addressMap.protection & PROT_READ) + { memcpy(buffer, address, len); return true; } - if (setAddressProtection(address, len, addressMap.protection | PROT_READ) != 0) { - KITTY_LOGE("memRead err couldn't add write perm to address (%p, len: %zu, prot: %d)", - address, len, addressMap.protection); + if (memProtect(address, len, addressMap.protection | PROT_READ) != 0) + { + KITTY_LOGE("memRead err couldn't add write perm to address (%p, len: %zu, prot: %d)", address, len, + addressMap.protection); return false; } memcpy(buffer, address, len); - if (setAddressProtection(address, len, addressMap.protection) != 0) { - KITTY_LOGE("memRead err couldn't revert protection of address (%p, len: %zu, prot: %d)", - address, len, addressMap.protection); + if (memProtect(address, len, addressMap.protection) != 0) + { + KITTY_LOGE("memRead err couldn't revert protection of address (%p, len: %zu, prot: %d)", address, len, + addressMap.protection); return false; } @@ -84,43 +95,50 @@ namespace KittyMemory { { KITTY_LOGD("memWrite(%p, %p, %zu)", address, buffer, len); - if (!address) { + if (!address) + { KITTY_LOGE("memWrite err address (%p) is null", address); return false; } - if (!buffer) { + if (!buffer) + { KITTY_LOGE("memWrite err buffer (%p) is null", buffer); return false; } - if (!len) { + if (!len) + { KITTY_LOGE("memWrite err invalid len"); return false; } ProcMap addressMap = getAddressMap(address); - if (!addressMap.isValid()) { + if (!addressMap.isValid()) + { KITTY_LOGE("memWrite err couldn't find address (%p) in any map", address); return false; } - if (addressMap.protection & PROT_WRITE) { + if (addressMap.protection & PROT_WRITE) + { memcpy(address, buffer, len); return true; } - if (setAddressProtection(address, len, addressMap.protection | PROT_WRITE) != 0) { - KITTY_LOGE("memWrite err couldn't add write perm to address (%p, len: %zu, prot: %d)", - address, len, addressMap.protection); + if (memProtect(address, len, KT_PROT_RWX) != 0) + { + KITTY_LOGE("memWrite err couldn't add write perm to address (%p, len: %zu, prot: %d)", address, len, + KT_PROT_RWX); return false; } memcpy(address, buffer, len); - if (setAddressProtection(address, len, addressMap.protection) != 0) { - KITTY_LOGE("memWrite err couldn't revert protection of address (%p, len: %zu, prot: %d)", - address, len, addressMap.protection); + if (memProtect(address, len, KT_PROT_RX) != 0) + { + KITTY_LOGE("memWrite err couldn't revert protection of address (%p, len: %zu, prot: %d)", address, len, + KT_PROT_RX); return false; } @@ -132,7 +150,8 @@ namespace KittyMemory { const char *file = "/proc/self/cmdline"; char cmdline[128] = {0}; FILE *fp = fopen(file, "r"); - if (!fp) { + if (!fp) + { KITTY_LOGE("Couldn't open file %s.", file); return ""; } @@ -148,34 +167,38 @@ namespace KittyMemory { char line[512] = {0}; FILE *fp = fopen(file, "r"); - if (!fp) { + if (!fp) + { KITTY_LOGE("Couldn't open file %s.", file); return retMaps; } - while (fgets(line, sizeof(line), fp)) { + while (fgets(line, sizeof(line), fp)) + { ProcMap map; char perms[5] = {0}, dev[11] = {0}, pathname[256] = {0}; // parse a line in maps file // (format) startAddress-endAddress perms offset dev inode pathname - sscanf(line, "%llx-%llx %s %llx %s %lu %s", - &map.startAddress, &map.endAddress, - perms, &map.offset, dev, &map.inode, pathname); + sscanf(line, "%" SCNxPTR "-%" SCNxPTR " %4s %" SCNxPTR " %s %lu %s", &map.startAddress, &map.endAddress, perms, &map.offset, dev, + &map.inode, pathname); map.length = map.endAddress - map.startAddress; map.dev = dev; map.pathname = pathname; - if (perms[0] == 'r') { + if (perms[0] == 'r') + { map.protection |= PROT_READ; map.readable = true; } - if (perms[1] == 'w') { + if (perms[1] == 'w') + { map.protection |= PROT_WRITE; map.writeable = true; } - if (perms[2] == 'x') { + if (perms[2] == 'x') + { map.protection |= PROT_EXEC; map.executable = true; } @@ -192,173 +215,301 @@ namespace KittyMemory { fclose(fp); - if (retMaps.empty()) { + if (retMaps.empty()) + { KITTY_LOGE("getAllMaps err couldn't find any map"); } + else + { + std::sort(retMaps.begin(), retMaps.end(), [](const KittyMemory::ProcMap &a, const KittyMemory::ProcMap &b) { + return a.startAddress < b.startAddress; + }); + } + return retMaps; } - std::vector getMapsEqual(const std::vector &maps, const std::string& name) + std::vector getMaps(EProcMapFilter filter, const std::string &name, const std::vector &maps) { - if (name.empty()) return {}; - - KITTY_LOGD("getMapsEqual(%s)", name.c_str()); - std::vector retMaps; - for(auto &it : maps) { - if (it.isValid() && !it.isUnknown() && it.pathname == name) { - retMaps.push_back(it); + for (auto &it : maps) + { + if (it.isValid()) + { + switch (filter) + { + case EProcMapFilter::Equal: + if (it.pathname == name) + retMaps.push_back(it); + break; + case EProcMapFilter::StartWith: + if (KittyUtils::String::StartsWith(it.pathname, name)) + retMaps.push_back(it); + break; + case EProcMapFilter::EndWith: + if (KittyUtils::String::EndsWith(it.pathname, name)) + retMaps.push_back(it); + break; + case EProcMapFilter::Contains: + default: + if (KittyUtils::String::Contains(it.pathname, name)) + retMaps.push_back(it); + break; + } } } return retMaps; } - std::vector getMapsContain(const std::vector &maps, const std::string &name) + ProcMap getAddressMap(const void *address, const std::vector &maps) { - if (name.empty()) return {}; - - KITTY_LOGD("getMapsContain(%s)", name.c_str()); + if (!address) + return {}; - std::vector retMaps; + ProcMap retMap{}; - for(auto &it : maps) { - if (it.isValid() && !it.isUnknown() && strstr(it.pathname.c_str(), name.c_str())) { - retMaps.push_back(it); + for (auto &it : maps) + { + if (it.isValid() && it.contains((uintptr_t)address)) + { + retMap = it; + break; } } - return retMaps; + return retMap; } - std::vector getMapsEndWith(const std::vector &maps, const std::string &name) + bool dumpMemToDisk(uintptr_t address, size_t size, const std::string &destination) { - if (name.empty()) return {}; + if (!address || !size || destination.empty()) + return false; - KITTY_LOGD("getMapsEndWith(%s)", name.c_str()); + auto allMaps = getAllMaps(); - std::vector retMaps; + if (!getAddressMap(address, allMaps).isValid()) + return false; - for(auto &it : maps) { - if (it.isValid() && !it.isUnknown() && KittyUtils::String::EndsWith(it.pathname, name)) { - retMaps.push_back(it); + uintptr_t endAddress = address + size; + + KittyIOFile dest(destination, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, 0666); + if (!dest.Open()) + return false; + + size_t bytesWritten = 0; + for (const auto &it : allMaps) + { + if (it.startAddress < address) + continue; + if (it.startAddress >= endAddress) + break; + + if (!it.readable && KittyMemory::memProtect((void *) (it.startAddress), it.length, + it.protection | PROT_READ) != 0) + { + std::vector zeroData(it.length, 0); + bytesWritten += dest.Write(zeroData.data(), zeroData.size()); + } + else + { + size_t n = dest.Write((const void *)(it.startAddress), it.length); + bytesWritten += n; + if (n < it.length) + { + std::vector zeroData(it.length - n, 0); + bytesWritten += dest.Write(zeroData.data(), zeroData.size()); + } + + if (!it.readable) + { + KittyMemory::memProtect((const void *) (it.startAddress), it.length, it.protection); + } } } - return retMaps; + dest.Close(); + return bytesWritten == size; } - ProcMap getAddressMap(const std::vector &maps, const void *address) + bool dumpMemFileToDisk(const std::string &memFile, const std::string &destination) { - KITTY_LOGD("getAddressMap(%p)", address); + if (!memFile.empty() || destination.empty()) + return false; - if (!address) return {}; + auto fileMaps = KittyMemory::getMaps(EProcMapFilter::EndWith, memFile); + if (fileMaps.empty()) + return false; - ProcMap retMap{}; + auto firstMap = fileMaps.front(); + fileMaps.erase(fileMaps.begin()); - for(auto &it : maps) { - if (it.isValid() && it.contains((uintptr_t)address)) { - retMap = it; - break; + uintptr_t lastEnd = firstMap.endAddress; + if (fileMaps.size() > 1) + { + for (auto &it : fileMaps) + { + if (firstMap.inode != it.inode || it.startAddress != lastEnd) + break; + + lastEnd = it.endAddress; } } - return retMap; + return dumpMemToDisk(firstMap.startAddress, lastEnd - firstMap.startAddress, destination); } - ProcMap getElfBaseMap(const std::string& name) + +#if defined(__aarch64__) +#define syscall_rpmv_n 270 +#define syscall_wpmv_n 271 +#elif defined(__arm__) +#define syscall_rpmv_n 376 +#define syscall_wpmv_n 377 +#elif defined(__i386__) +#define syscall_rpmv_n 347 +#define syscall_wpmv_n 348 +#elif defined(__x86_64__) +#define syscall_rpmv_n 310 +#define syscall_wpmv_n 311 +#else +#error "Unsupported ABI" +#endif + + static ssize_t syscall_process_vm_readv(pid_t pid, const iovec *lvec, unsigned long liovcnt, const iovec *rvec, + unsigned long riovcnt, unsigned long flags) { - ProcMap retMap{}; + return syscall(syscall_rpmv_n, pid, lvec, liovcnt, rvec, riovcnt, flags); + } - if (name.empty()) - return retMap; + static ssize_t syscall_process_vm_writev(pid_t pid, const iovec *lvec, unsigned long liovcnt, const iovec *rvec, + unsigned long riovcnt, unsigned long flags) + { + return syscall(syscall_wpmv_n, pid, lvec, liovcnt, rvec, riovcnt, flags); + } - bool isZippedInAPK = false; - auto maps = getMapsEndWith(name); - if (maps.empty()) - { - // some apps use dlopen on zipped libraries like xxx.apk!/lib/xxx/libxxx.so - // so we'll search in app's base.apk maps too - maps = getMapsEndWith(".apk"); - if (maps.empty()) { - return retMap; - } - isZippedInAPK = true; - } + size_t syscallMemOp(EPROCESS_VM_OP op, uintptr_t address, void *buffer, size_t len) + { + if (!address || !buffer || !len) + return 0; + + pid_t pid = getpid(); - for (auto &it: maps) + struct iovec lvec{.iov_base = buffer, .iov_len = 0}; + struct iovec rvec{.iov_base = reinterpret_cast(address), .iov_len = 0}; + + ssize_t n = 0; + size_t bytes_op = 0, remaining = len; + bool op_one_page = false; + do { - if (!it.readable || it.offset != 0 || it.isUnknown() || it.inode == 0 || !it.is_private || !it.isValidELF()) - continue; + size_t remaining_or_pglen = remaining; + if (op_one_page) + remaining_or_pglen = std::min(KT_PAGE_LEN(rvec.iov_base), remaining); - // skip dladdr check for linker/linker64 - if (strstr(it.pathname.c_str(), "/bin/linker")) { - retMap = it; - break; - } + lvec.iov_len = remaining_or_pglen; + rvec.iov_len = remaining_or_pglen; - Dl_info info{}; - int rt = dladdr((void *) it.startAddress, &info); - // check dli_fname and dli_fbase if NULL - if (rt == 0 || !info.dli_fname || !info.dli_fbase || it.startAddress != (uintptr_t) info.dli_fbase) - continue; + errno = 0; - if (!isZippedInAPK) { - retMap = it; - break; - } + if (op == EPROCESS_VM_OP::READV) + n = KT_EINTR_RETRY(syscall_process_vm_readv(pid, &lvec, 1, &rvec, 1, 0)); + else + n = KT_EINTR_RETRY(syscall_process_vm_writev(pid, &lvec, 1, &rvec, 1, 0)); - // if library is zipped inside base.apk, compare dli_fname and fix pathname - if (KittyUtils::String::EndsWith(info.dli_fname, name)) { - retMap = it; - retMap.pathname = info.dli_fname; - break; + if (n > 0) + { + remaining -= n; + bytes_op += n; + lvec.iov_base = reinterpret_cast(lvec.iov_base) + n; + rvec.iov_base = reinterpret_cast(rvec.iov_base) + n; } - } - - return retMap; + else + { + if (n == -1) + { + int err = errno; + switch (err) + { + case EPERM: + KITTY_LOGD("Failed syscallMemOP(%p + %p, %p) | Can't access the " + "address space of process ID (%d).", + (void *)address, (void *)(uintptr_t(rvec.iov_base) - address), (void *)rvec.iov_len, + pid); + break; + case ESRCH: + KITTY_LOGD("Failed syscallMemOP(%p + %p, %p) | No process with ID " + "(%d) exists.", + (void *)address, (void *)(uintptr_t(rvec.iov_base) - address), (void *)rvec.iov_len, + pid); + break; + case ENOMEM: + KITTY_LOGD("Failed syscallMemOP(%p + %p, %p) | Could not allocate " + "memory for internal copies of " + "the iovec structures.", + (void *)address, (void *)(uintptr_t(rvec.iov_base) - address), (void *)rvec.iov_len); + break; + default: + KITTY_LOGD("Failed syscallMemOP(%p + %p, %p) | error(%d): %s.", (void *)address, + (void *)(uintptr_t(rvec.iov_base) - address), (void *)rvec.iov_len, err, + strerror(err)); + } + } + if (op_one_page) + { + remaining -= remaining_or_pglen; + lvec.iov_base = reinterpret_cast(lvec.iov_base) + remaining_or_pglen; + rvec.iov_base = reinterpret_cast(rvec.iov_base) + remaining_or_pglen; + } + } + op_one_page = n == -1 || size_t(n) != remaining_or_pglen; + } while (remaining > 0); + return bytes_op; } #elif __APPLE__ kern_return_t getPageInfo(vm_address_t region, vm_region_submap_short_info_64 *info_out) { - vm_size_t region_len = 0; - mach_msg_type_number_t info_count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64; - unsigned int depth = 0x1000; - return vm_region_recurse_64(mach_task_self(), ®ion, ®ion_len, - &depth, (vm_region_recurse_info_t)info_out, - &info_count); + vm_size_t region_len = 0; + mach_msg_type_number_t info_count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64; + unsigned int depth = 0x1000; + return vm_region_recurse_64(mach_task_self(), ®ion, ®ion_len, &depth, (vm_region_recurse_info_t)info_out, + &info_count); } bool memRead(const void *address, void *buffer, size_t len) { KITTY_LOGD("memRead(%p, %p, %zu)", address, buffer, len); - if (!address) { + if (!address) + { KITTY_LOGE("memRead err address (%p) is null", address); return false; } - if (!buffer) { + if (!buffer) + { KITTY_LOGE("memRead err buffer (%p) is null", buffer); return false; } - if (!len) { + if (!len) + { KITTY_LOGE("memRead err invalid len"); return false; } - + mach_vm_size_t nread = 0; - kern_return_t kret = mach_vm_read_overwrite(mach_task_self(), mach_vm_address_t(address), mach_vm_size_t(len), mach_vm_address_t(buffer), &nread); - if (kret != KERN_SUCCESS || nread != len) { - KITTY_LOGE("memRead err vm_read failed - [ nread(%p) - kerror(%d) ]", - (void*)nread, kret); + kern_return_t kret = mach_vm_read_overwrite(mach_task_self(), mach_vm_address_t(address), mach_vm_size_t(len), + mach_vm_address_t(buffer), &nread); + if (kret != KERN_SUCCESS || nread != len) + { + KITTY_LOGE("memRead err vm_read failed - [ nread(%p) - kerror(%d) ]", (void *)nread, kret); return false; } - + return true; } @@ -370,82 +521,86 @@ namespace KittyMemory { Memory_Status memWrite(void *address, const void *buffer, size_t len) { KITTY_LOGD("memWrite(%p, %p, %zu)", address, buffer, len); - - if (!address) { + + if (!address) + { KITTY_LOGE("memWrite err address (%p) is null.", address); return KMS_INV_ADDR; } - - if (!buffer) { + + if (!buffer) + { KITTY_LOGE("memWrite err buffer (%p) is null.", buffer); return KMS_INV_BUF; } - - if (!len) { + + if (!len) + { KITTY_LOGE("memWrite err invalid len."); return KMS_INV_LEN; } - + task_t self_task = mach_task_self(); mach_vm_address_t page_start = mach_vm_address_t(KT_PAGE_START(address)); size_t page_len = KT_PAGE_LEN2(address, len); - + vm_region_submap_short_info_64 page_info = {}; kern_return_t kret = getPageInfo(page_start, &page_info); if (kret != KERN_SUCCESS) { - KITTY_LOGE("memWrite err failed to get page info of address (%p) - kerror(%d).", - address, kret); + KITTY_LOGE("memWrite err failed to get page info of address (%p) - kerror(%d).", address, kret); return KMS_ERR_GET_PAGEINFO; } - + // already has write perm if (page_info.protection & VM_PROT_WRITE) { - kret = mach_vm_write(self_task, mach_vm_address_t(address), vm_offset_t(buffer), mach_msg_type_number_t(len)); + kret = mach_vm_write(self_task, mach_vm_address_t(address), vm_offset_t(buffer), + mach_msg_type_number_t(len)); if (kret != KERN_SUCCESS) { - KITTY_LOGE("memWrite err vm_write failed to write data to address (%p) - kerror(%d).", + KITTY_LOGE("memWrite err vm_write failed to write data to address (%p) - " + "kerror(%d).", address, kret); return KMS_ERR_VMWRITE; } return KMS_SUCCESS; } - + #if 0 // check for Substrate/ellekit MSHookMemory existance first if (findMSHookMemory(address, buffer, len)) return KMS_SUCCESS; #endif - + // copy-on-write, see vm_map_protect in vm_map.c - kret = mach_vm_protect(self_task, page_start, page_len, false, - VM_PROT_READ|VM_PROT_WRITE|VM_PROT_COPY); + kret = mach_vm_protect(self_task, page_start, page_len, false, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_COPY); if (kret != KERN_SUCCESS) { - KITTY_LOGE("memWrite err vm_protect(page: %p, len: %zu, prot: %d) COW failed - kerror(%d).", - (void*)page_start, page_len, page_info.protection, kret); + KITTY_LOGE("memWrite err vm_protect(page: %p, len: %zu, prot: %d) COW failed - " + "kerror(%d).", + (void *)page_start, page_len, page_info.protection, kret); return KMS_ERR_PROT; } - + kret = mach_vm_write(self_task, mach_vm_address_t(address), vm_offset_t(buffer), mach_msg_type_number_t(len)); if (kret != KERN_SUCCESS) { - KITTY_LOGE("memWrite err vm_write failed to write data to address (%p) - kerror(%d).", - address, kret); + KITTY_LOGE("memWrite err vm_write failed to write data to address (%p) - kerror(%d).", address, kret); return KMS_ERR_VMWRITE; } - + kret = mach_vm_protect(self_task, page_start, page_len, false, page_info.protection); if (kret != KERN_SUCCESS) { - KITTY_LOGE("memWrite err vm_protect(page: %p, len: %zu, prot: %d) restore failed - kerror(%d).", - (void*)page_start, page_len, page_info.protection, kret); + KITTY_LOGE("memWrite err vm_protect(page: %p, len: %zu, prot: %d) restore failed " + "- kerror(%d).", + (void *)page_start, page_len, page_info.protection, kret); return KMS_ERR_PROT; } - - sys_icache_invalidate(reinterpret_cast(page_start), page_len); - + + sys_icache_invalidate(reinterpret_cast(page_start), page_len); + return KMS_SUCCESS; } @@ -456,7 +611,7 @@ namespace KittyMemory { if (_NSGetExecutablePath(exeBuf.data(), &exeBufSize) == -1) { exeBuf.clear(); - exeBuf.resize(exeBufSize+1, 0); + exeBuf.resize(exeBufSize + 1, 0); _NSGetExecutablePath(exeBuf.data(), &exeBufSize); } @@ -466,41 +621,42 @@ namespace KittyMemory { for (uint32_t i = 0; i < imageCount; i++) { const mach_header *hdr = _dyld_get_image_header(i); - if (!hdr || hdr->filetype != MH_EXECUTE) continue; - + if (!hdr || hdr->filetype != MH_EXECUTE) + continue; + // first executable if (exeIdx == -1) exeIdx = i; - + const char *name = _dyld_get_image_name(i); if (!name || strlen(name) != strlen(exeBuf.data()) || strcmp(name, exeBuf.data()) != 0) continue; - + exeIdx = i; break; } - + MemoryFileInfo _info = {}; if (exeIdx >= 0) { _info.index = exeIdx; #ifdef __LP64__ - _info.header = (const mach_header_64*)_dyld_get_image_header(exeIdx); + _info.header = (const mach_header_64 *)_dyld_get_image_header(exeIdx); #else _info.header = _dyld_get_image_header(exeIdx); #endif _info.name = _dyld_get_image_name(exeIdx); _info.address = _dyld_get_image_vmaddr_slide(exeIdx); } - + return _info; } - MemoryFileInfo getMemoryFileInfo(const std::string& fileName) + MemoryFileInfo getMemoryFileInfo(const std::string &fileName) { MemoryFileInfo _info = {}; - + if (fileName.empty()) return _info; @@ -509,7 +665,8 @@ namespace KittyMemory { for (uint32_t i = 0; i < imageCount; i++) { const char *name = _dyld_get_image_name(i); - if (!name) continue; + if (!name) + continue; std::string fullpath(name); if (!KittyUtils::String::EndsWith(fullpath, fileName)) @@ -517,7 +674,7 @@ namespace KittyMemory { _info.index = i; #ifdef __LP64__ - _info.header = (const mach_header_64*)_dyld_get_image_header(i); + _info.header = (const mach_header_64 *)_dyld_get_image_header(i); #else _info.header = _dyld_get_image_header(i); #endif @@ -526,7 +683,7 @@ namespace KittyMemory { break; } - + return _info; } @@ -538,17 +695,16 @@ namespace KittyMemory { info = getMemoryFileInfo(fileName); else info = getBaseInfo(); - + if (!info.address) return 0; - + return info.address + address; } #endif // __APPLE__ -} // KittyMemory - +} // namespace KittyMemory #ifdef __APPLE__ @@ -587,9 +743,9 @@ namespace KittyScanner { if (!info.header || !info.address || symbol.empty()) return 0; - + uintptr_t slide = info.address; - + #ifdef __LP64__ struct mach_header_64 *header = (struct mach_header_64 *)info.header; const int lc_seg = LC_SEGMENT_64; @@ -598,54 +754,55 @@ namespace KittyScanner struct symtab_command *symtab_cmd = nullptr; struct nlist_64 *symtab = nullptr; #else - struct mach_header *header = (struct mach_header *)libInfo.header; + struct mach_header *header = (struct mach_header *)info.header; const int lc_seg = LC_SEGMENT; struct segment_command *curr_seg_cmd = nullptr; struct segment_command *linkedit_segment_cmd = nullptr; struct symtab_command *symtab_cmd = nullptr; struct nlist *symtab = nullptr; #endif - + uintptr_t curr = uintptr_t(header) + sizeof(*header); for (uint32_t i = 0; i < header->ncmds; i++, curr += curr_seg_cmd->cmdsize) { - *(uintptr_t*)&curr_seg_cmd = curr; - + *(uintptr_t *)&curr_seg_cmd = curr; + if (curr_seg_cmd->cmd == lc_seg && (strcmp(curr_seg_cmd->segname, SEG_LINKEDIT) == 0)) - *(uintptr_t*)&linkedit_segment_cmd = curr; + *(uintptr_t *)&linkedit_segment_cmd = curr; else if (curr_seg_cmd->cmd == LC_SYMTAB) - *(uintptr_t*)&symtab_cmd = curr; + *(uintptr_t *)&symtab_cmd = curr; } - + if (!linkedit_segment_cmd || !symtab_cmd) return 0; - + uintptr_t linkedit_base = (slide + linkedit_segment_cmd->vmaddr) - linkedit_segment_cmd->fileoff; - *(uintptr_t*)&symtab = (linkedit_base + symtab_cmd->symoff); + *(uintptr_t *)&symtab = (linkedit_base + symtab_cmd->symoff); char *strtab = (char *)(linkedit_base + symtab_cmd->stroff); - + for (uint32_t i = 0; i < symtab_cmd->nsyms; i++) { if (symtab[i].n_value == 0) continue; - + std::string curr_sym_str = std::string(strtab + symtab[i].n_un.n_strx); - - //KITTY_LOGI("syms[%d] = [%{public}s, %p]", i, curr_sym_str.c_str(), (void*)symtab[i].n_value); - + + // KITTY_LOGI("syms[%d] = [%{public}s, %p]", i, curr_sym_str.c_str(), + // (void*)symtab[i].n_value); + if (curr_sym_str.empty() || curr_sym_str != symbol) continue; - + return slide + symtab[i].n_value; } - + return 0; } - + uintptr_t findSymbol(const std::string &lib, const std::string &symbol) { return findSymbol(KittyMemory::getMemoryFileInfo(lib), symbol); } -} +} // namespace KittyScanner -#endif // __APPLE__ \ No newline at end of file +#endif // __APPLE__ diff --git a/app/src/main/jni/KittyMemory/KittyMemory.hpp b/app/src/main/jni/KittyMemory/KittyMemory.hpp index 3bd7b2a..cad6c59 100644 --- a/app/src/main/jni/KittyMemory/KittyMemory.hpp +++ b/app/src/main/jni/KittyMemory/KittyMemory.hpp @@ -6,15 +6,16 @@ #pragma once -#include +#include +#include #include #include #include #include #ifdef __ANDROID__ -#include #include +#include #elif __APPLE__ #include @@ -27,48 +28,7 @@ #endif #include "KittyUtils.hpp" - -#define KT_PAGE_SIZE (sysconf(_SC_PAGE_SIZE)) - -#define KT_PAGE_START(x) (uintptr_t(x) & ~(KT_PAGE_SIZE - 1)) -#define KT_PAGE_END(x) (KT_PAGE_START(uintptr_t(x) + KT_PAGE_SIZE - 1)) -#define KT_PAGE_OFFSET(x) (uintptr_t(x) - KT_PAGE_START(x)) -#define KT_PAGE_LEN(x) (size_t(KT_PAGE_SIZE - KT_PAGE_OFFSET(x))) - -#define KT_PAGE_END2(x, len) (KT_PAGE_START(uintptr_t(x) + len - 1)) -#define KT_PAGE_LEN2(x, len) (KT_PAGE_END2(x, len) - KT_PAGE_START(x) + KT_PAGE_SIZE) - -#define _PROT_RWX_ (PROT_READ | PROT_WRITE | PROT_EXEC) -#define _PROT_RX_ (PROT_READ | PROT_EXEC) -#define _PROT_RW_ (PROT_READ | PROT_WRITE) - -#define KITTY_LOG_TAG "KittyMemory" - -#ifdef __ANDROID__ -#include - -#ifdef kITTYMEMORY_DEBUG -#define KITTY_LOGD(fmt, ...) ((void)__android_log_print(ANDROID_LOG_DEBUG, KITTY_LOG_TAG, fmt, ##__VA_ARGS__)) -#else -#define KITTY_LOGD(fmt, ...) do {} while(0) -#endif - -#define KITTY_LOGI(fmt, ...) ((void)__android_log_print(ANDROID_LOG_INFO, KITTY_LOG_TAG, fmt, ##__VA_ARGS__)) -#define KITTY_LOGE(fmt, ...) ((void)__android_log_print(ANDROID_LOG_ERROR, KITTY_LOG_TAG, fmt, ##__VA_ARGS__)) - -#elif __APPLE__ -#include - -#ifdef kITTYMEMORY_DEBUG -#define KITTY_LOGD(fmt, ...) os_log(OS_LOG_DEFAULT, "D " KITTY_LOG_TAG ": " fmt, ##__VA_ARGS__) -#else -#define KITTY_LOGD(fmt, ...) do {} while(0) -#endif - -#define KITTY_LOGI(fmt, ...) os_log(OS_LOG_DEFAULT, "I " KITTY_LOG_TAG ": " fmt, ##__VA_ARGS__) -#define KITTY_LOGE(fmt, ...) os_log_error(OS_LOG_DEFAULT, "E " KITTY_LOG_TAG ": " fmt, ##__VA_ARGS__) - -#endif +#include "KittyIOFile.hpp" namespace KittyMemory { @@ -78,42 +38,63 @@ namespace KittyMemory bool memRead(const void *address, void *buffer, size_t len); #ifdef __ANDROID__ - - class ProcMap { + + class ProcMap + { public: - unsigned long long startAddress; - unsigned long long endAddress; + uintptr_t startAddress; + uintptr_t endAddress; size_t length; int protection; bool readable, writeable, executable, is_private, is_shared, is_ro, is_rw, is_rx; - unsigned long long offset; + uintptr_t offset; std::string dev; unsigned long inode; std::string pathname; - ProcMap() : startAddress(0), endAddress(0), length(0), protection(0), - readable(false), writeable(false), executable(false), - is_private(false), is_shared(false), - is_ro(false), is_rw(false), is_rx(false), - offset(0), inode(0) {} - - inline bool isValid() const { return (startAddress && endAddress && length); } - inline bool isUnknown() const { return pathname.empty(); } - inline bool isValidELF() const { return isValid() && length > 4 && readable && memcmp((const void *) startAddress, "\177ELF", 4) == 0; } - inline bool contains(uintptr_t address) const { return address >= startAddress && address < endAddress; } - inline std::string toString() + ProcMap() + : startAddress(0), endAddress(0), length(0), protection(0), readable(false), writeable(false), + executable(false), is_private(false), is_shared(false), is_ro(false), is_rw(false), is_rx(false), + offset(0), inode(0) + { + } + + inline bool isValid() const + { + return (startAddress && endAddress && length); + } + inline bool isUnknown() const + { + return pathname.empty(); + } + inline bool isValidELF() const + { + return isValid() && length > 4 && readable && memcmp((const void *)startAddress, "\177ELF", 4) == 0; + } + inline bool contains(uintptr_t address) const + { + return address >= startAddress && address < endAddress; + } + inline std::string toString() const { - return KittyUtils::String::Fmt("%llx-%llx %c%c%c%c %llx %s %lu %s", - startAddress, endAddress, - readable ? 'r' : '-', writeable ? 'w' : '-', executable ? 'x' : '-', is_private ? 'p' : 's', - offset, dev.c_str(), inode, pathname.c_str()); + return KittyUtils::String::Fmt("%" PRIxPTR "-%" PRIxPTR " %c%c%c%c %" PRIxPTR " %s %lu %s", startAddress, endAddress, + readable ? 'r' : '-', writeable ? 'w' : '-', executable ? 'x' : '-', + is_private ? 'p' : 's', offset, dev.c_str(), inode, pathname.c_str()); } }; - + + enum class EProcMapFilter + { + Equal, + Contains, + StartWith, + EndWith + }; + /* * mprotect wrapper */ - int setAddressProtection(const void *address, size_t length, int protection); + int memProtect(const void *address, size_t length, int protection); /* * Writes buffer content to an address @@ -131,99 +112,119 @@ namespace KittyMemory std::vector getAllMaps(); /* - * Gets info of all maps which pathname equals @name in current process - */ - std::vector getMapsEqual(const std::vector &maps, const std::string& name); - - /* - * Gets info of all maps which pathname contains @name in current process + * Gets info of all maps with filter @name in current process */ - std::vector getMapsContain(const std::vector &maps, const std::string& name); + std::vector getMaps(EProcMapFilter filter, const std::string &name, + const std::vector &maps = getAllMaps()); /* - * Gets info of all maps which pathname ends with @name in current process + * Gets map info of an address in self process */ - std::vector getMapsEndWith(const std::vector &maps, const std::string& name); - + ProcMap getAddressMap(const void *address, const std::vector &maps = getAllMaps()); /* * Gets map info of an address in self process */ - ProcMap getAddressMap(const std::vector &maps, const void *address); + inline ProcMap getAddressMap(uintptr_t address, const std::vector &maps = getAllMaps()) + { + return getAddressMap((const void *)address, maps); + } - /* - * Gets info of all maps which pathname equals @name in current process + /** + * Dump memory range */ - inline std::vector getMapsEqual(const std::string& name) { return getMapsEqual(getAllMaps(), name); } + bool dumpMemToDisk(uintptr_t address, size_t size, const std::string &destination); - /* - * Gets info of all maps which pathname contains @name in current process + /** + * Dump memory mapped file */ - inline std::vector getMapsContain(const std::string& name) { return getMapsContain(getAllMaps(), name); } + bool dumpMemFileToDisk(const std::string &memFile, const std::string &destination); - /* - * Gets info of all maps which pathname ends with @name in current process - */ - inline std::vector getMapsEndWith(const std::string& name) { return getMapsEndWith(getAllMaps(), name); } + enum class EPROCESS_VM_OP + { + READV, + WRITEV + }; - /* - * Gets map info of an address in self process - */ - inline ProcMap getAddressMap(const void *address) { return getAddressMap(getAllMaps(), address); } + size_t syscallMemOp(EPROCESS_VM_OP op, uintptr_t address, void *buffer, size_t len); - /* - * Gets the base map of a loaded shared object - */ - ProcMap getElfBaseMap(const std::string& name); + inline size_t syscallMemRead(uintptr_t address, void *buffer, size_t len) + { + return syscallMemOp(EPROCESS_VM_OP::READV, address, buffer, len); + } + + inline size_t syscallMemRead(void *address, void *buffer, size_t len) + { + return syscallMemOp(EPROCESS_VM_OP::READV, uintptr_t(address), buffer, len); + } + + inline size_t syscallMemWrite(uintptr_t address, void *buffer, size_t len) + { + return syscallMemOp(EPROCESS_VM_OP::WRITEV, address, buffer, len); + } + + inline size_t syscallMemWrite(void *address, void *buffer, size_t len) + { + return syscallMemOp(EPROCESS_VM_OP::WRITEV, uintptr_t(address), buffer, len); + } #elif __APPLE__ - enum Memory_Status { - KMS_FAILED = 0, - KMS_SUCCESS, - KMS_INV_ADDR, - KMS_INV_LEN, - KMS_INV_BUF, - KMS_ERR_PROT, - KMS_ERR_GET_PAGEINFO, - KMS_ERR_VMWRITE, + enum Memory_Status + { + KMS_FAILED = 0, + KMS_SUCCESS, + KMS_INV_ADDR, + KMS_INV_LEN, + KMS_INV_BUF, + KMS_ERR_PROT, + KMS_ERR_GET_PAGEINFO, + KMS_ERR_VMWRITE, }; - struct seg_data_t { - uintptr_t start, end; - unsigned long size; - seg_data_t() : start(0), end(0), size(0) {} + struct seg_data_t + { + uintptr_t start, end; + unsigned long size; + seg_data_t() : start(0), end(0), size(0) + { + } }; - - class MemoryFileInfo { + + class MemoryFileInfo + { public: - uint32_t index; + uint32_t index; #ifdef __LP64__ - const mach_header_64 *header; + const mach_header_64 *header; #else - const mach_header *header; + const mach_header *header; #endif - const char *name; - intptr_t address; - - MemoryFileInfo() : index(0), header(nullptr), name(nullptr), address(0) {} - - inline seg_data_t getSegment(const char *seg_name) const - { - seg_data_t data {}; - if (!header || !seg_name) return data; - data.start = uintptr_t(getsegmentdata(header, seg_name, &data.size)); - data.end = data.start + data.size; - return data; - } - - inline seg_data_t getSection(const char *seg_name, const char *sect_name) const - { - seg_data_t data {}; - if (!header || !seg_name || !sect_name) return data; - data.start = uintptr_t(getsectiondata(header, seg_name, sect_name, &data.size)); - data.end = data.start + data.size; - return data; - } + const char *name; + intptr_t address; + + MemoryFileInfo() : index(0), header(nullptr), name(nullptr), address(0) + { + } + + inline seg_data_t getSegment(const char *seg_name) const + { + seg_data_t data{}; + if (!header || !seg_name) + return data; + data.start = uintptr_t(getsegmentdata(header, seg_name, &data.size)); + data.end = data.start + data.size; + return data; + } + + inline seg_data_t getSection(const char *seg_name, const char *sect_name) const + { + seg_data_t data{}; + if (!header || !seg_name || !sect_name) + return data; + data.start = uintptr_t(getsectiondata(header, seg_name, sect_name, &data.size)); + data.end = data.start + data.size; + return data; + } }; /* @@ -244,16 +245,17 @@ namespace KittyMemory /* * find in memory file info by checking if target loaded object file ends with @fileName */ - MemoryFileInfo getMemoryFileInfo(const std::string& fileName); + MemoryFileInfo getMemoryFileInfo(const std::string &fileName); /* - * returns the absolue address of a relative offset of a file in memory or NULL as fileName for base executable + * returns the absolue address of a relative offset of a file in memory or NULL as + * fileName for base executable */ uintptr_t getAbsoluteAddress(const char *fileName, uintptr_t address); #endif -} +} // namespace KittyMemory #ifdef __APPLE__ @@ -261,6 +263,6 @@ namespace KittyScanner { uintptr_t findSymbol(const KittyMemory::MemoryFileInfo &info, const std::string &symbol); uintptr_t findSymbol(const std::string &lib, const std::string &symbol); -} +} // namespace KittyScanner #endif // __APPLE__ diff --git a/app/src/main/jni/KittyMemory/KittyPtrValidator.cpp b/app/src/main/jni/KittyMemory/KittyPtrValidator.cpp new file mode 100644 index 0000000..2688b25 --- /dev/null +++ b/app/src/main/jni/KittyMemory/KittyPtrValidator.cpp @@ -0,0 +1,276 @@ +#include "KittyPtrValidator.hpp" + +#ifdef __APPLE__ + +bool KittyPtrValidator::_findRegion(uintptr_t addr, RegionInfo *region) +{ + if (!use_cache_) + { + vm_address_t address = addr & ~(page_size_ - 1); + vm_size_t size = 0; + natural_t nesting_depth = 0; + vm_region_submap_short_info_data_64_t info{}; + mach_msg_type_number_t info_count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64; + kern_return_t kret = vm_region_recurse_64(task_, &address, &size, &nesting_depth, + (vm_region_recurse_info_t)&info, + &info_count); + if (kret != KERN_SUCCESS) + return false; + + bool readable = (info.protection & VM_PROT_READ) != 0; + bool writable = (info.protection & VM_PROT_WRITE) != 0; + bool executable = (info.protection & VM_PROT_EXECUTE) != 0; + *region = RegionInfo(address, address + size, readable, writable, executable); + return address <= addr && addr < address + size; + } + + if (!cachedRegions_.empty()) + { + if (last_region_index_ < cachedRegions_.size() && + cachedRegions_[last_region_index_].start <= addr && + addr < cachedRegions_[last_region_index_].end) + { + *region = cachedRegions_[last_region_index_]; + return true; + } + + size_t left = 0; + size_t right = cachedRegions_.size(); + size_t best_match = right; + + while (left < right) + { + size_t mid = left + (right - left) / 2; + if (cachedRegions_[mid].end <= addr) + { + left = mid + 1; + } + else + { + best_match = mid; + right = mid; + } + } + + if (best_match < cachedRegions_.size() && + cachedRegions_[best_match].start <= addr && + addr < cachedRegions_[best_match].end) + { + last_region_index_ = best_match; + *region = cachedRegions_[best_match]; + return true; + } + } + + return false; +} + +void KittyPtrValidator::refreshRegionCache() +{ + cachedRegions_.clear(); + vm_address_t address = 0; + + while (true) + { + vm_size_t size = 0; + natural_t nesting_depth = 0; + vm_region_submap_short_info_data_64_t info{}; + mach_msg_type_number_t info_count = VM_REGION_SUBMAP_SHORT_INFO_COUNT_64; + kern_return_t kret = vm_region_recurse_64(task_, &address, &size, &nesting_depth, + (vm_region_recurse_info_t)&info, + &info_count); + if (kret != KERN_SUCCESS) + break; + + bool readable = (info.protection & VM_PROT_READ) != 0; + bool writable = (info.protection & VM_PROT_WRITE) != 0; + bool executable = (info.protection & VM_PROT_EXECUTE) != 0; + RegionInfo new_region(address, address + size, readable, writable, executable); + + if (!cachedRegions_.empty() && cachedRegions_.back().canMergeWith(new_region)) + { + cachedRegions_.back().end = new_region.end; + } + else + { + cachedRegions_.emplace_back(new_region); + } + + address += size; + } + + if (!cachedRegions_.empty()) + { + std::sort( + cachedRegions_.begin(), cachedRegions_.end(), + [](const RegionInfo &a, const RegionInfo &b) { return a.start < b.start; }); + } + + last_region_index_ = 0; +} + +#else + +std::string KittyPtrValidator::_readMapsFile() +{ + std::string buffer; + + char filePath[256] = {0}; + snprintf(filePath, sizeof(filePath), "/proc/%d/maps", pid_); + + int fd = open(filePath, O_RDONLY); + if (fd < 0) + return buffer; + + char tmp_buf[4096] = {0}; + ssize_t n = 0, off = 0; + while ((n = pread64(fd, tmp_buf, 4096, off)) > 0) + { + buffer.append(tmp_buf, n); + off += n; + } + + close(fd); + + return buffer; +} + +bool KittyPtrValidator::_parseMapsLine(const std::string &line, RegionInfo *region) +{ + if (line.empty()) + return false; + + uintptr_t start, end; + char perms[5] = {0}; + int parsed = sscanf(line.c_str(), "%" SCNxPTR "-%" SCNxPTR " %4s", &start, &end, perms); + if (parsed != 3) + return false; + + *region = RegionInfo(start, end, perms[0] == 'r', perms[1] == 'w', perms[2] == 'x'); + return true; +} + +void KittyPtrValidator::_parseMapsFromBuffer(const std::string &buffer, + std::vector *output) +{ + if (!output) + return; + + output->clear(); + size_t pos = 0; + while (pos < buffer.size()) + { + size_t end = buffer.find('\n', pos); + if (end == std::string::npos) + { + end = buffer.size(); + } + + std::string line(buffer.data() + pos, end - pos); + pos = end + 1; + + RegionInfo new_region(0, 0, false, false, false); + if (!_parseMapsLine(line, &new_region)) + continue; + + if (!output->empty() && output->back().canMergeWith(new_region)) + { + output->back().end = new_region.end; + } + else + { + output->emplace_back(new_region); + } + } + + if (!output->empty()) + { + std::sort( + output->begin(), output->end(), + [](const RegionInfo &a, const RegionInfo &b) { return a.start < b.start; }); + } +} + +bool KittyPtrValidator::_findRegion(uintptr_t addr, RegionInfo *region) +{ + if (!use_cache_) + { + std::string maps_data = _readMapsFile(); + if (maps_data.empty()) + return false; + + size_t pos = 0; + while (pos < maps_data.size()) + { + size_t end = maps_data.find('\n', pos); + if (end == std::string::npos) + { + end = maps_data.size(); + } + + std::string line(maps_data.data() + pos, end - pos); + pos = end + 1; + + RegionInfo new_region(0, 0, false, false, false); + if (!_parseMapsLine(line, &new_region)) + continue; + + *region = new_region; + return (new_region.start <= addr && addr < new_region.end); + } + return false; + } + + if (!cachedRegions_.empty()) + { + if (last_region_index_ < cachedRegions_.size() && + cachedRegions_[last_region_index_].start <= addr && + addr < cachedRegions_[last_region_index_].end) + { + *region = cachedRegions_[last_region_index_]; + return true; + } + + size_t left = 0; + size_t right = cachedRegions_.size(); + size_t best_match = right; + + while (left < right) + { + size_t mid = left + (right - left) / 2; + if (cachedRegions_[mid].end <= addr) + { + left = mid + 1; + } + else + { + best_match = mid; + right = mid; + } + } + + if (best_match < cachedRegions_.size() && + cachedRegions_[best_match].start <= addr && + addr < cachedRegions_[best_match].end) + { + last_region_index_ = best_match; + *region = cachedRegions_[best_match]; + return true; + } + } + + return false; +} + +void KittyPtrValidator::refreshRegionCache() +{ + cachedRegions_.clear(); + std::string maps_data = _readMapsFile(); + if (maps_data.empty()) + return; + + _parseMapsFromBuffer(maps_data, &cachedRegions_); + last_region_index_ = 0; +} + +#endif diff --git a/app/src/main/jni/KittyMemory/KittyPtrValidator.hpp b/app/src/main/jni/KittyMemory/KittyPtrValidator.hpp new file mode 100644 index 0000000..8b38d2e --- /dev/null +++ b/app/src/main/jni/KittyMemory/KittyPtrValidator.hpp @@ -0,0 +1,283 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __APPLE__ +#include + +class KittyPtrValidator +{ +private: + struct RegionInfo + { + uintptr_t start; + uintptr_t end; + bool readable; + bool writable; + bool executable; + + RegionInfo(uintptr_t s, uintptr_t e, bool r, bool w, bool x) + : start(s), end(e), readable(r), writable(w), executable(x) + { + } + + inline bool canMergeWith(const RegionInfo &other) const + { + return end == other.start && readable == other.readable && + writable == other.writable && executable == other.executable; + } + }; + + std::vector cachedRegions_; + const mach_port_t task_ = mach_task_self(); + const size_t page_size_ = sysconf(_SC_PAGESIZE); + bool use_cache_ = true; + size_t last_region_index_ = 0; + + bool _findRegion(uintptr_t addr, RegionInfo *region); + +public: + KittyPtrValidator() + : task_(mach_task_self()), page_size_(sysconf(_SC_PAGESIZE)), use_cache_(false), + last_region_index_(0) + { + } + + KittyPtrValidator(mach_port_t task, bool use_cache) + : task_(task), page_size_(sysconf(_SC_PAGESIZE)), use_cache_(use_cache), + last_region_index_(0) + { + if (use_cache_) + refreshRegionCache(); + } + + inline void setUseCache(bool use_cache) + { + use_cache_ = use_cache; + if (!use_cache_) + { + cachedRegions_.clear(); + last_region_index_ = 0; + } + else + { + refreshRegionCache(); + } + } + + inline bool isPtrReadable(uintptr_t ptr, size_t len = sizeof(void *)) + { + if (ptr == 0) + return false; + RegionInfo region(0, 0, false, false, false); + return _findRegion(ptr, ®ion) && region.readable && (ptr + len) <= region.end; + } + + inline bool isPtrWritable(uintptr_t ptr, size_t len = sizeof(void *)) + { + if (ptr == 0) + return false; + RegionInfo region(0, 0, false, false, false); + return _findRegion(ptr, ®ion) && region.writable && (ptr + len) <= region.end; + } + + inline bool isPtrExecutable(uintptr_t ptr, size_t len = sizeof(void *)) + { + if (ptr == 0) + return false; + RegionInfo region(0, 0, false, false, false); + return _findRegion(ptr, ®ion) && region.executable && + (ptr + len) <= region.end; + } + + inline bool isPtrInAddressSpace(uintptr_t ptr) + { + if (ptr == 0) + return false; + RegionInfo region(0, 0, false, false, false); + return _findRegion(ptr, ®ion); + } + + inline bool isPtrReadable(const void *ptr, size_t len = sizeof(void *)) + { + return ptr && isPtrReadable(uintptr_t(ptr), len); + } + inline bool isPtrWritable(const void *ptr, size_t len = sizeof(void *)) + { + return ptr && isPtrWritable(uintptr_t(ptr), len); + } + inline bool isPtrExecutable(const void *ptr, size_t len = sizeof(void *)) + { + return ptr && isPtrExecutable(uintptr_t(ptr), len); + } + inline bool isPtrInAddressSpace(const void *ptr) + { + return ptr && isPtrInAddressSpace(uintptr_t(ptr)); + } + + inline void clearCache() + { + cachedRegions_.clear(); + last_region_index_ = 0; + } + + void refreshRegionCache(); + + inline std::vector cachedRegions() const + { + return cachedRegions_; + } +}; + +#else + +class KittyPtrValidator +{ +private: + struct RegionInfo + { + uintptr_t start; + uintptr_t end; + bool readable; + bool writable; + bool executable; + + RegionInfo(uintptr_t s, uintptr_t e, bool r, bool w, bool x) + : start(s), end(e), readable(r), writable(w), executable(x) + { + } + + inline bool canMergeWith(const RegionInfo &other) const + { + return end == other.start && readable == other.readable && + writable == other.writable && executable == other.executable; + } + }; + + std::vector cachedRegions_; + pid_t pid_ = getpid(); + const size_t page_size_ = sysconf(_SC_PAGESIZE); + bool use_cache_ = true; + size_t last_region_index_ = 0; + + std::string _readMapsFile(); + + bool _parseMapsLine(const std::string &line, RegionInfo *region); + + void _parseMapsFromBuffer(const std::string &buffer, std::vector *output); + + bool _findRegion(uintptr_t addr, RegionInfo *region); + +public: + KittyPtrValidator() + : pid_(getpid()), page_size_(sysconf(_SC_PAGESIZE)), use_cache_(false), + last_region_index_(0) + { + } + + KittyPtrValidator(pid_t pid, bool use_cache) + : pid_(pid), page_size_(sysconf(_SC_PAGESIZE)), use_cache_(use_cache), + last_region_index_(0) + { + if (use_cache_) + refreshRegionCache(); + } + + inline void setUseCache(bool use_cache) + { + use_cache_ = use_cache; + if (!use_cache_) + { + cachedRegions_.clear(); + last_region_index_ = 0; + } + else + { + refreshRegionCache(); + } + } + + inline void setPID(pid_t pid) + { + cachedRegions_.clear(); + last_region_index_ = 0; + pid_ = pid; + + if (use_cache_) + { + refreshRegionCache(); + } + } + + inline bool isPtrReadable(uintptr_t ptr, size_t len = sizeof(void *)) + { + if (ptr == 0) + return false; + RegionInfo region(0, 0, false, false, false); + return _findRegion(ptr, ®ion) && region.readable && (ptr + len) <= region.end; + } + + inline bool isPtrWritable(uintptr_t ptr, size_t len = sizeof(void *)) + { + if (ptr == 0) + return false; + RegionInfo region(0, 0, false, false, false); + return _findRegion(ptr, ®ion) && region.writable && (ptr + len) <= region.end; + } + + inline bool isPtrExecutable(uintptr_t ptr, size_t len = sizeof(void *)) + { + if (ptr == 0) + return false; + RegionInfo region(0, 0, false, false, false); + return _findRegion(ptr, ®ion) && region.executable && + (ptr + len) <= region.end; + } + + inline bool isPtrInAddressSpace(uintptr_t ptr) + { + if (ptr == 0) + return false; + RegionInfo region(0, 0, false, false, false); + return _findRegion(ptr, ®ion); + } + + inline bool isPtrReadable(const void *ptr, size_t len = sizeof(void *)) + { + return ptr && isPtrReadable(uintptr_t(ptr), len); + } + inline bool isPtrWritable(const void *ptr, size_t len = sizeof(void *)) + { + return ptr && isPtrWritable(uintptr_t(ptr), len); + } + inline bool isPtrExecutable(const void *ptr, size_t len = sizeof(void *)) + { + return ptr && isPtrExecutable(uintptr_t(ptr), len); + } + inline bool isPtrInAddressSpace(const void *ptr) + { + return ptr && isPtrInAddressSpace(uintptr_t(ptr)); + } + + inline void clearCache() + { + cachedRegions_.clear(); + last_region_index_ = 0; + } + + void refreshRegionCache(); + + inline std::vector cachedRegions() const + { + return cachedRegions_; + } +}; + +#endif diff --git a/app/src/main/jni/KittyMemory/KittyScanner.cpp b/app/src/main/jni/KittyMemory/KittyScanner.cpp index 3cd5863..5637dbb 100644 --- a/app/src/main/jni/KittyMemory/KittyScanner.cpp +++ b/app/src/main/jni/KittyMemory/KittyScanner.cpp @@ -1,9 +1,9 @@ #include "KittyScanner.hpp" - -#ifdef __ANDROID__ -#include -#include -#endif +#include "KittyPtrValidator.hpp" +#include +#include +#include +#include #include "KittyUtils.hpp" @@ -23,8 +23,7 @@ namespace KittyScanner return !*mask; } - uintptr_t findInRange(const uintptr_t start, const uintptr_t end, - const char *pattern, const std::string& mask) + uintptr_t findInRange(const uintptr_t start, const uintptr_t end, const char *pattern, const std::string &mask) { const size_t scan_size = mask.length(); @@ -47,8 +46,8 @@ namespace KittyScanner return 0; } - std::vector findBytesAll(const uintptr_t start, const uintptr_t end, - const char *bytes, const std::string& mask) + std::vector findBytesAll(const uintptr_t start, const uintptr_t end, const char *bytes, + const std::string &mask) { std::vector list; @@ -57,11 +56,14 @@ namespace KittyScanner uintptr_t curr_search_address = start; const size_t scan_size = mask.length(); - do { - if (!list.empty()) curr_search_address = list.back() + scan_size; - + do + { + if (!list.empty()) + curr_search_address = list.back() + scan_size; + uintptr_t found = findInRange(curr_search_address, end, bytes, mask); - if (!found) break; + if (!found) + break; list.push_back(found); } while (true); @@ -69,7 +71,7 @@ namespace KittyScanner return list; } - uintptr_t findBytesFirst(const uintptr_t start, const uintptr_t end, const char *bytes, const std::string& mask) + uintptr_t findBytesFirst(const uintptr_t start, const uintptr_t end, const char *bytes, const std::string &mask) { if (start >= end || !bytes || mask.empty()) return 0; @@ -77,15 +79,18 @@ namespace KittyScanner return findInRange(start, end, bytes, mask); } - std::vector findHexAll(const uintptr_t start, const uintptr_t end, std::string hex, const std::string& mask) + std::vector findHexAll(const uintptr_t start, const uintptr_t end, std::string hex, + const std::string &mask) { std::vector list; - - if (start >= end || mask.empty() || !KittyUtils::String::ValidateHex(hex)) return list; + + if (start >= end || mask.empty() || !KittyUtils::String::ValidateHex(hex)) + return list; const size_t scan_size = mask.length(); - if((hex.length() / 2) != scan_size) return list; - + if ((hex.length() / 2) != scan_size) + return list; + std::vector pattern(scan_size); KittyUtils::dataFromHex(hex, &pattern[0]); @@ -93,12 +98,14 @@ namespace KittyScanner return list; } - uintptr_t findHexFirst(const uintptr_t start, const uintptr_t end, std::string hex, const std::string& mask) + uintptr_t findHexFirst(const uintptr_t start, const uintptr_t end, std::string hex, const std::string &mask) { - if (start >= end || mask.empty() || !KittyUtils::String::ValidateHex(hex)) return 0; + if (start >= end || mask.empty() || !KittyUtils::String::ValidateHex(hex)) + return 0; const size_t scan_size = mask.length(); - if((hex.length() / 2) != scan_size) return 0; + if ((hex.length() / 2) != scan_size) + return 0; std::vector pattern(scan_size); KittyUtils::dataFromHex(hex, &pattern[0]); @@ -108,66 +115,69 @@ namespace KittyScanner std::vector findIdaPatternAll(const uintptr_t start, const uintptr_t end, const std::string &pattern) { - std::vector list; - - if (start >= end) - return list; + std::vector list; - std::string mask; - std::vector bytes; + if (start >= end) + return list; - const size_t pattren_len = pattern.length(); - for (std::size_t i = 0; i < pattren_len; i++) - { - if (pattern[i] == ' ') continue; + std::string mask; + std::vector bytes; - if (pattern[i] == '?') + const size_t pattren_len = pattern.length(); + for (std::size_t i = 0; i < pattren_len; i++) { - bytes.push_back(0); - mask += '?'; - } - else if (pattren_len > i + 1 && std::isxdigit(pattern[i]) && std::isxdigit(pattern[i + 1])) - { - bytes.push_back(std::stoi(pattern.substr(i++, 2), nullptr, 16)); - mask += 'x'; + if (pattern[i] == ' ') + continue; + + if (pattern[i] == '?') + { + bytes.push_back(0); + mask += '?'; + } + else if (pattren_len > i + 1 && std::isxdigit(pattern[i]) && std::isxdigit(pattern[i + 1])) + { + bytes.push_back(std::stoi(pattern.substr(i++, 2), nullptr, 16)); + mask += 'x'; + } } - } - if (bytes.empty() || mask.empty() || bytes.size() != mask.size()) - return list; + if (bytes.empty() || mask.empty() || bytes.size() != mask.size()) + return list; - list = findBytesAll(start, end, bytes.data(), mask); - return list; + list = findBytesAll(start, end, bytes.data(), mask); + return list; } - uintptr_t findIdaPatternFirst(const uintptr_t start, const uintptr_t end, const std::string &pattern) { - if (start >= end) - return 0; + uintptr_t findIdaPatternFirst(const uintptr_t start, const uintptr_t end, const std::string &pattern) + { + if (start >= end) + return 0; - std::string mask; - std::vector bytes; + std::string mask; + std::vector bytes; - const size_t pattren_len = pattern.length(); - for (std::size_t i = 0; i < pattren_len; i++) - { - if (pattern[i] == ' ') continue; - - if (pattern[i] == '?') + const size_t pattren_len = pattern.length(); + for (std::size_t i = 0; i < pattren_len; i++) { - bytes.push_back(0); - mask += '?'; - } - else if (pattren_len > i + 1 && std::isxdigit(pattern[i]) && std::isxdigit(pattern[i + 1])) - { - bytes.push_back(std::stoi(pattern.substr(i++, 2), nullptr, 16)); - mask += 'x'; + if (pattern[i] == ' ') + continue; + + if (pattern[i] == '?') + { + bytes.push_back(0); + mask += '?'; + } + else if (pattren_len > i + 1 && std::isxdigit(pattern[i]) && std::isxdigit(pattern[i + 1])) + { + bytes.push_back(std::stoi(pattern.substr(i++, 2), nullptr, 16)); + mask += 'x'; + } } - } - if (bytes.empty() || mask.empty() || bytes.size() != mask.size()) - return 0; + if (bytes.empty() || mask.empty() || bytes.size() != mask.size()) + return 0; - return findBytesFirst(start, end, bytes.data(), mask); + return findBytesFirst(start, end, bytes.data(), mask); } std::vector findDataAll(const uintptr_t start, const uintptr_t end, const void *data, size_t size) @@ -195,48 +205,14 @@ namespace KittyScanner #ifdef __ANDROID__ - RegisterNativeFn findRegisterNativeFn(const class ElfScanner &elf, const std::string &name) - { - uintptr_t string_loc = 0, string_xref = 0, fn_loc = 0; - RegisterNativeFn fn; - - if (name.empty() || !elf.isValid()) - return fn; - - string_loc = KittyScanner::findDataFirst(elf.baseSegment().startAddress, elf.baseSegment().endAddress, name.data(), name.length()); - if (!string_loc) { - KITTY_LOGE("findRegisterNativeFn: Couldn't find string (%s) in selected maps", name.c_str()); - return fn; - } - - KITTY_LOGD("findRegisterNativeFn: String (%s) at %p", name.c_str(), (void*)string_loc); - - for (auto &it : elf.segments()) { - if (it.is_rw) { - string_xref = KittyScanner::findDataFirst(it.startAddress, it.endAddress, &string_loc, sizeof(uintptr_t)); - if (!string_xref) continue; - - KITTY_LOGD("findRegisterNativeFn: String at (%p) referenced at %p", (void *)string_loc, (void *)string_xref); - - fn_loc = string_xref; - break; - } - } - - if(!fn_loc) return fn; - - memcpy(&fn, (void *)fn_loc, sizeof(RegisterNativeFn)); - return fn; - } - // for old ndk #ifndef DT_GNU_HASH #define DT_GNU_HASH 0x6ffffef5 #endif -/* ======================= ElfScanner ======================= */ + /* ======================= ElfScanner ======================= */ -// refs https://gist.github.com/resilar/24bb92087aaec5649c9a2afc0b4350c8 + // refs https://gist.github.com/resilar/24bb92087aaec5649c9a2afc0b4350c8 ElfScanner::ElfScanner(uintptr_t elfBase, const std::vector &maps) { @@ -246,69 +222,93 @@ namespace KittyScanner _loads = 0; _loadBias = 0; _loadSize = 0; - _bss = 0; - _bssSize = 0; _dynamic = 0; _stringTable = 0; _symbolTable = 0; _elfHashTable = 0; _gnuHashTable = 0; _strsz = 0; - _syment = 0; - - if (!elfBase) - return; + _syment = sizeof(KT_ElfW(Sym)); + _fixedBySoInfo = false; + _dsymbols_init = false; // verify address - auto elfBaseMap = KittyMemory::getAddressMap(maps, (const void *)elfBase); + auto elfBaseMap = KittyMemory::getAddressMap(elfBase, maps); if (!elfBaseMap.isValid() || !elfBaseMap.readable || elfBase != elfBaseMap.startAddress) { - KITTY_LOGD("ElfScanner: (%p) is not a valid ELF base address.", (void*)elfBase); + KITTY_LOGD("ElfScanner: (%p) is not a valid ELF base address.", (void *)elfBase); return; } // verify ELF header if (!elfBaseMap.isValidELF()) { - KITTY_LOGD("ElfScanner: (%p) is not a valid ELF.", (void*)elfBase); + KITTY_LOGD("ElfScanner: (%p) is not a valid ELF.", (void *)elfBase); return; } _elfBase = elfBase; // read ELF header - _ehdr = *(ElfW_(Ehdr)*)elfBase; + _ehdr = *(KT_ElfW(Ehdr) *)_elfBase; // check ELF bit - if (_ehdr.e_ident[EI_CLASS] != ELF_EICLASS_) { - KITTY_LOGD("ElfScanner: ELF class mismatch (%p).", (void*)elfBase); + if (_ehdr.e_ident[EI_CLASS] != KT_ELF_EICLASS) + { + KITTY_LOGD("ElfScanner: ELF class mismatch (%p).", (void *)_elfBase); return; } - // check common header values - if (!_ehdr.e_phnum || !_ehdr.e_phentsize || !_ehdr.e_shnum || !_ehdr.e_shentsize) { - KITTY_LOGD("ElfScanner: Invalid header values (%p).", (void*)elfBase); + if (_ehdr.e_ident[EI_DATA] != ELFDATA2LSB) + { + KITTY_LOGD("ElfScanner: (%p) data encoding is not little endian.", (void *)elfBase); + return; + } + + if (_ehdr.e_ident[EI_VERSION] != EV_CURRENT) + { + KITTY_LOGD("ElfScanner: (%p) ELF header version mismatch.", (void *)elfBase); + return; + } + + if (_ehdr.e_type != ET_EXEC && _ehdr.e_type != ET_DYN) + { + KITTY_LOGD("ElfScanner: (%p) is not a executable or dynamic " + "library.", + (void *)elfBase); return; } - _phdr = elfBase + _ehdr.e_phoff; + // check common header values + if (!_ehdr.e_phoff || !_ehdr.e_phnum || !_ehdr.e_phentsize) + { + KITTY_LOGD("ElfScanner: Invalid header values (%p).", (void *)_elfBase); + return; + } - // read all program headers - std::vector phdrs_buf(_ehdr.e_phnum * _ehdr.e_phentsize); - if (!memcpy(&phdrs_buf[0], (const void*)_phdr, phdrs_buf.size())) { - KITTY_LOGD("ElfScanner: Failed to read ELF (%p) program headers.", (void*)elfBase); + if (!KittyMemory::getAddressMap(_elfBase + _ehdr.e_phoff, maps).readable) + { + KITTY_LOGD("ElfScanner: Invalid phdr (%p + %p) = %p.", (void *)_elfBase, (void *)_ehdr.e_phoff, + (void *)(_elfBase + _ehdr.e_phoff)); return; } + _phdr = _elfBase + _ehdr.e_phoff; + // find load bias uintptr_t min_vaddr = UINTPTR_MAX, max_vaddr = 0; uintptr_t load_vaddr = 0, load_memsz = 0, load_filesz = 0; - for (ElfW_(Half) i = 0; i < _ehdr.e_phnum; i++) { - ElfW_(Phdr) phdr_entry = {}; - memcpy(&phdr_entry, phdrs_buf.data() + (i * _ehdr.e_phentsize), _ehdr.e_phentsize); + for (KT_ElfW(Half) i = 0; i < _ehdr.e_phnum; i++) + { + if (!KittyMemory::getAddressMap(_phdr + (i * _ehdr.e_phentsize), maps).readable) + continue; + + KT_ElfW(Phdr) phdr_entry = {}; + memcpy(&phdr_entry, (const void *)(_phdr + (i * _ehdr.e_phentsize)), _ehdr.e_phentsize); _phdrs.push_back(phdr_entry); - if (phdr_entry.p_type == PT_LOAD) { + if (phdr_entry.p_type == PT_LOAD) + { _loads++; load_vaddr = phdr_entry.p_vaddr; @@ -323,46 +323,82 @@ namespace KittyScanner } } - if (!_loads) { - KITTY_LOGD("ElfScanner: No loads entry for ELF (%p).", (void*)elfBase); + if (!_loads) + { + KITTY_LOGD("ElfScanner: No loads entry for ELF (%p).", (void *)_elfBase); return; } - if (!max_vaddr) { - KITTY_LOGD("ElfScanner: Failed to find load size for ELF (%p).", (void*)elfBase); + if (!max_vaddr) + { + KITTY_LOGD("ElfScanner: Failed to find max_vaddr for ELF (%p).", (void *)_elfBase); return; } min_vaddr = KT_PAGE_START(min_vaddr); max_vaddr = KT_PAGE_END(max_vaddr); - _loadBias = elfBase - min_vaddr; + _loadBias = _elfBase - min_vaddr; _loadSize = max_vaddr - min_vaddr; uintptr_t seg_start = load_vaddr + _loadBias; uintptr_t seg_mem_end = KT_PAGE_END((seg_start + load_memsz)); uintptr_t seg_file_end = KT_PAGE_END((seg_start + load_filesz)); - if (seg_mem_end > seg_file_end) { - _bss = seg_file_end; - _bssSize = size_t(seg_mem_end - seg_file_end); + uintptr_t bss_start = 0, bss_end = 0; + if (seg_mem_end > seg_file_end) + { + bss_start = seg_file_end; + bss_end = seg_mem_end; + } + + for (const auto &it : maps) + { + if (it.startAddress >= _elfBase && it.endAddress <= (_elfBase + _loadSize)) + { + if (it.startAddress == _elfBase) + { + _baseSegment = it; + } + + _segments.push_back(it); + + if (it.readable && !it.executable && + (it.pathname == "[anon:.bss]" || (elfBaseMap.inode != 0 && it.inode == 0) || + (it.startAddress >= bss_start && it.endAddress <= bss_end))) + { + _bssSegments.push_back(it); + } + } + + if (it.endAddress >= (_elfBase + _loadSize)) + break; } // read all dynamics - for (auto& phdr : _phdrs) { - if (phdr.p_type == PT_DYNAMIC) { - _dynamic = _loadBias + phdr.p_vaddr; - std::vector dyn_buff(phdr.p_memsz / sizeof(ElfW_(Dyn))); - if (!memcpy(&dyn_buff[0], (const void*)_dynamic, phdr.p_memsz)) { - KITTY_LOGD("ElfScanner: Failed to read dynamic for ELF (%p).", (void*)elfBase); + for (auto &phdr : _phdrs) + { + if (phdr.p_type == PT_DYNAMIC) + { + if (phdr.p_vaddr == 0 || phdr.p_memsz == 0) break; - } + if (!KittyMemory::getAddressMap(_loadBias + phdr.p_vaddr, maps).readable) + break; + if (!KittyMemory::getAddressMap((_loadBias + phdr.p_vaddr) + phdr.p_memsz - 1, maps).readable) + break; + + _dynamic = _loadBias + phdr.p_vaddr; - for (auto& dyn : dyn_buff) { + std::vector dyn_buff(phdr.p_memsz / sizeof(KT_ElfW(Dyn))); + memcpy(&dyn_buff[0], (const void *)_dynamic, phdr.p_memsz); + + for (auto &dyn : dyn_buff) + { if (dyn.d_tag == DT_NULL) break; // set required dynamics for symbol lookup - switch (dyn.d_tag) { + switch (dyn.d_tag) + { // mandatory case DT_STRTAB: // string table _stringTable = dyn.d_un.d_ptr; @@ -391,20 +427,17 @@ namespace KittyScanner _dynamics.push_back(dyn); } - } - } - // check required dynamics for symbol lookup - if (!_stringTable || !_symbolTable || !_strsz || !_syment) { - KITTY_LOGD("ElfScanner: Failed to require dynamics for symbol lookup."); - KITTY_LOGD("ElfScanner: elfBase: %p | strtab=%p | symtab=%p | strsz=%p | syment=%p", - (void*)elfBase, (void*)_stringTable, (void*)_symbolTable, (void*)_strsz, (void*)_syment); - return; + break; + } } - auto fix_table_address = [&](uintptr_t& table_addr) { + auto fix_table_address = [&](uintptr_t &table_addr) { if (table_addr && table_addr < _loadBias) table_addr += _loadBias; + + if (!KittyMemory::getAddressMap(table_addr, maps).readable) + table_addr = 0; }; fix_table_address(_stringTable); @@ -412,109 +445,1519 @@ namespace KittyScanner fix_table_address(_elfHashTable); fix_table_address(_gnuHashTable); - bool fixBSS = !_bss; + _filepath = elfBaseMap.pathname; + _realpath = elfBaseMap.pathname; + if (!elfBaseMap.pathname.empty() && elfBaseMap.offset != 0) + { + std::string inZipPath = + KittyUtils::Zip::GetFileInfoByDataOffset(elfBaseMap.pathname, elfBaseMap.offset).fileName; + if (!inZipPath.empty()) + { + _realpath += '!'; + _realpath += inZipPath; + } + } + } - for (auto& it : maps) { - if (it.startAddress >= _elfBase && it.endAddress <= (_elfBase + _loadSize)) { - _segments.push_back(it); - if (fixBSS && it.pathname == "[anon:.bss]") { - if (!_bss) - _bss = it.startAddress; + ElfScanner::ElfScanner(const kitty_soinfo_t &soinfo, const std::vector &maps) + { + _elfBase = 0; + _ehdr = {}; + _phdr = 0; + _loads = 0; + _loadBias = 0; + _loadSize = 0; + _dynamic = 0; + _stringTable = 0; + _symbolTable = 0; + _elfHashTable = 0; + _gnuHashTable = 0; + _strsz = 0; + _syment = 0; + _fixedBySoInfo = false; + _dsymbols_init = false; + + _elfBase = soinfo.base; + _phdr = soinfo.phdr; + _loadBias = soinfo.bias; + _loadSize = soinfo.size; + _dynamic = soinfo.dyn; + _stringTable = soinfo.strtab; + _symbolTable = soinfo.symtab; + _strsz = soinfo.strsz; + _syment = sizeof(KT_ElfW(Sym)); + _filepath = soinfo.path; + _realpath = soinfo.realpath; + + bool isLinker = KittyUtils::String::EndsWith(soinfo.path, "/linker") || + KittyUtils::String::EndsWith(soinfo.path, "/linker64"); + if (!isLinker && (_elfBase == 0 || _loadSize == 0 || _loadBias == 0 || _phdr == 0 || _dynamic == 0 || + _stringTable == 0 || _symbolTable == 0)) + { + KITTY_LOGD("ElfScanner: Invalid soinfo!"); + KITTY_LOGD("ElfScanner: elfBase: %p | bias: %p | phdr: %p | dyn: %p | strtab=%p | symtab=%p | strsz=%p | " + "syment=%p", + (void *)_elfBase, (void *)_loadBias, (void *)_phdr, (void *)_dynamic, (void *)_stringTable, + (void *)_symbolTable, (void *)_strsz, (void *)_syment); + *this = ElfScanner(); + return; + } - _bssSize = it.endAddress - _bss; - } - } + // fix for linker + if (_elfBase == 0) + _elfBase = KittyMemory::getAddressMap(soinfo.bias, maps).startAddress; + if (_elfBase == 0) + _elfBase = KittyMemory::getAddressMap(soinfo.phdr, maps).startAddress; + if (_elfBase == 0) + _elfBase = KittyMemory::getAddressMap(soinfo.dyn, maps).startAddress; + if (_elfBase == 0) + _elfBase = KittyMemory::getAddressMap(soinfo.symtab, maps).startAddress; + if (_elfBase == 0) + _elfBase = KittyMemory::getAddressMap(soinfo.strtab, maps).startAddress; - if (it.endAddress > (_elfBase + _loadSize)) - break; + // verify address + auto elfBaseMap = KittyMemory::getAddressMap(_elfBase, maps); + if (!elfBaseMap.isValid() || !elfBaseMap.readable || _elfBase != elfBaseMap.startAddress) + { + KITTY_LOGD("ElfScanner: Invalid base(%p) for soinfo(%p)", (void *)_elfBase, (void *)soinfo.ptr); + *this = ElfScanner(); + return; } - if (!_segments.empty()) - _base_segment = _segments.front(); - } + // check if header is corrupted + // some games like farlight have corrupted header and needs to be fixed by soinfo + if (!isLinker && (memcmp(_ehdr.e_ident, "\177ELF", 4) != 0 || _ehdr.e_ident[EI_CLASS] != KT_ELF_EICLASS || + _ehdr.e_ident[EI_DATA] != ELFDATA2LSB || _ehdr.e_ident[EI_VERSION] != EV_CURRENT || + (_ehdr.e_type != ET_EXEC && _ehdr.e_type != ET_DYN) || + _ehdr.e_ehsize != sizeof(KT_ElfW(Ehdr)) || _ehdr.e_phentsize != sizeof(KT_ElfW(Phdr)) || + _ehdr.e_phnum != soinfo.phnum || _ehdr.e_phoff != (soinfo.phdr - soinfo.base))) + { + KITTY_LOGD("ElfScanner: soinfo(%p) has corrupted header, fixing by soinfo...", (void *)soinfo.ptr); + + _ehdr.e_ident[EI_MAG0] = 0x7F; + _ehdr.e_ident[EI_MAG1] = 'E'; + _ehdr.e_ident[EI_MAG2] = 'L'; + _ehdr.e_ident[EI_MAG3] = 'F'; + _ehdr.e_ident[EI_CLASS] = KT_ELF_EICLASS; + _ehdr.e_ident[EI_DATA] = ELFDATA2LSB; + _ehdr.e_ident[EI_VERSION] = EV_CURRENT; + _ehdr.e_ident[EI_OSABI] = ELFOSABI_SYSV; + _ehdr.e_ident[EI_ABIVERSION] = 0; + + _ehdr.e_type = ET_DYN; + _ehdr.e_machine = soinfo.e_machine; + _ehdr.e_version = EV_CURRENT; + _ehdr.e_entry = 0; + _ehdr.e_phoff = soinfo.phdr ? (soinfo.phdr - soinfo.base) : 0; + _ehdr.e_phnum = soinfo.phnum; + _ehdr.e_ehsize = sizeof(KT_ElfW(Ehdr)); + _ehdr.e_phentsize = sizeof(KT_ElfW(Phdr)); + _ehdr.e_shoff = 0; + _ehdr.e_shentsize = sizeof(KT_ElfW(Shdr)); + _ehdr.e_shnum = 0; + _ehdr.e_shstrndx = 0; + _ehdr.e_flags = 0; + + _fixedBySoInfo = true; + } - uintptr_t ElfScanner::findSymbol(const std::string& symbolName) const - { - if (!isValid()) return 0; + // fix for linker + if (_phdr == 0) + _phdr = _elfBase + _ehdr.e_phoff; - auto get_sym_address = [&](const ElfW_(Sym) *sym_ent) -> uintptr_t { - return sym_ent->st_value < _loadBias ? _loadBias + sym_ent->st_value : sym_ent->st_value; - }; + auto phdrMap = KittyMemory::getAddressMap(_phdr, maps); + if (!phdrMap.readable || phdrMap.startAddress < _elfBase || + (_loadSize && phdrMap.endAddress > (_elfBase + _loadSize))) + { + KITTY_LOGD("ElfScanner: Invalid phdr(%p) for soinfo(%p).", (void *)_phdr, (void *)soinfo.ptr); + *this = ElfScanner(); + return; + } - // try gnu hash first - if (_gnuHashTable) { - const auto *sym = KittyUtils::Elf::GnuHash::LookupByName(_gnuHashTable, _symbolTable, _stringTable, _syment, _strsz, symbolName.c_str()); - if (sym && sym->st_value) { - return get_sym_address(sym); + if (!isLinker) + { + auto dynMap = KittyMemory::getAddressMap(_dynamic, maps); + if (!(dynMap.readable && dynMap.startAddress >= _elfBase && dynMap.endAddress <= (_elfBase + _loadSize))) + { + KITTY_LOGD("ElfScanner: Invalid dyn(%p) for soinfo(%p).", (void *)_dynamic, (void *)soinfo.ptr); + *this = ElfScanner(); + return; } } - if (_elfHashTable) { - const auto *sym = KittyUtils::Elf::ElfHash::LookupByName(_elfHashTable, _symbolTable, _stringTable, _syment, _strsz, symbolName.c_str()); - if (sym && sym->st_value) { - return get_sym_address(sym); + // fix for ldplayer + auto biasMap = KittyMemory::getAddressMap(_loadBias, maps); + if (!(biasMap.readable && biasMap.startAddress >= _elfBase && biasMap.endAddress <= (_elfBase + _loadSize))) + { + KITTY_LOGD("ElfScanner: Invalid bias(%p) for soinfo(%p).", (void *)_loadBias, (void *)soinfo.ptr); + _loadBias = 0; + } + + uintptr_t min_vaddr = UINTPTR_MAX, max_vaddr = 0; + uintptr_t load_vaddr = 0, load_memsz = 0, load_filesz = 0; + for (KT_ElfW(Half) i = 0; i < _ehdr.e_phnum; i++) + { + if (!KittyMemory::getAddressMap(_phdr + (i * _ehdr.e_phentsize), maps).readable) + continue; + + KT_ElfW(Phdr) phdr_entry = {}; + memcpy(&phdr_entry, (const void *)(_phdr + (i * _ehdr.e_phentsize)), _ehdr.e_phentsize); + _phdrs.push_back(phdr_entry); + + if (phdr_entry.p_type == PT_LOAD) + { + _loads++; + + load_vaddr = phdr_entry.p_vaddr; + load_memsz = phdr_entry.p_memsz; + load_filesz = phdr_entry.p_filesz; + + if (phdr_entry.p_vaddr < min_vaddr) + min_vaddr = phdr_entry.p_vaddr; + + if (phdr_entry.p_vaddr + phdr_entry.p_memsz > max_vaddr) + max_vaddr = phdr_entry.p_vaddr + phdr_entry.p_memsz; } } - return 0; - } + if (!_loads) + { + KITTY_LOGD("ElfScanner: No loads entry for ELF (%p).", (void *)_elfBase); + *this = ElfScanner(); + return; + } - std::vector ElfScanner::getAllELFs() - { - std::vector elfs; + if (!max_vaddr) + { + KITTY_LOGD("ElfScanner: Failed to find max_vaddr for ELF (%p).", (void *)_elfBase); + *this = ElfScanner(); + return; + } - auto maps = KittyMemory::getAllMaps(); - if (maps.empty()) { - KITTY_LOGE("getAllELFs: Failed to get process maps."); - return elfs; + min_vaddr = KT_PAGE_START(min_vaddr); + max_vaddr = KT_PAGE_END(max_vaddr); + + // fix for linker + { + if (_loadBias == 0) + _loadBias = _elfBase - min_vaddr; + + if (_loadSize == 0) + _loadSize = max_vaddr - min_vaddr; } - std::map checkedMaps{}; - for (auto &it: maps) { - if (checkedMaps.count(it.startAddress) > 0) - continue; + uintptr_t seg_start = load_vaddr + _loadBias; + uintptr_t seg_mem_end = KT_PAGE_END((seg_start + load_memsz)); + uintptr_t seg_file_end = KT_PAGE_END((seg_start + load_filesz)); + uintptr_t bss_start = 0, bss_end = 0; + if (seg_mem_end > seg_file_end) + { + bss_start = seg_file_end; + bss_end = seg_mem_end; + } - if (!it.readable || it.offset != 0 || it.isUnknown() || it.inode == 0 || !it.is_private || !it.isValidELF()) - continue; + for (const auto &it : maps) + { + if (it.startAddress >= _elfBase && it.endAddress <= (_elfBase + _loadSize)) + { + if (it.startAddress == _elfBase) + { + _baseSegment = it; + } - // skip dladdr check for linker/linker64 - if (!strstr(it.pathname.c_str(), "/bin/linker")) { - Dl_info info{}; - int rt = dladdr((void *) it.startAddress, &info); - // check dli_fname and dli_fbase if NULL - if (rt == 0 || !info.dli_fname || !info.dli_fbase || it.startAddress != (uintptr_t) info.dli_fbase) - continue; + _segments.push_back(it); - // re-assigning the pathname in case when library is zipped inside base.apk - // dli_fname returns basename sometimes, so check basename before re-assigning the full pathname - if (KittyUtils::fileNameFromPath(it.pathname) != - KittyUtils::fileNameFromPath(info.dli_fname)) { - it.pathname = info.dli_fname; + if (it.readable && !it.executable && + (it.pathname == "[anon:.bss]" || (elfBaseMap.inode != 0 && it.inode == 0) || + (it.startAddress >= bss_start && it.endAddress <= bss_end))) + { + _bssSegments.push_back(it); } } - checkedMaps[it.startAddress] = true; - elfs.push_back(ElfScanner(it.startAddress, maps)); + if (it.endAddress >= (_elfBase + _loadSize)) + break; } - return elfs; + // read all dynamics + for (auto &phdr : _phdrs) + { + if (phdr.p_type == PT_DYNAMIC) + { + // fix for linker + if (_dynamic == 0 && phdr.p_vaddr) + _dynamic = _loadBias + phdr.p_vaddr; + + if (_dynamic == 0 || phdr.p_memsz == 0) + break; + if (!KittyMemory::getAddressMap(_dynamic, maps).readable) + break; + if (!KittyMemory::getAddressMap(_dynamic + phdr.p_memsz - 1, maps).readable) + break; + + std::vector dyn_buff(phdr.p_memsz / sizeof(KT_ElfW(Dyn))); + memcpy(&dyn_buff[0], (const void *)_dynamic, phdr.p_memsz); + + for (auto &dyn : dyn_buff) + { + if (dyn.d_tag == DT_NULL) + break; + + switch (dyn.d_tag) + { + case DT_STRTAB: + if (_stringTable == 0) + _stringTable = dyn.d_un.d_ptr; + break; + case DT_SYMTAB: + if (_symbolTable == 0) + _symbolTable = dyn.d_un.d_ptr; + break; + case DT_STRSZ: + if (_strsz == 0) + _strsz = dyn.d_un.d_val; + break; + case DT_SYMENT: + _syment = dyn.d_un.d_val; + break; + case DT_HASH: // hash table + _elfHashTable = dyn.d_un.d_ptr; + break; + case DT_GNU_HASH: // gnu hash table + _gnuHashTable = dyn.d_un.d_ptr; + break; + default: + break; + } + + _dynamics.push_back(dyn); + } + + break; + } + } + + auto fix_table_address = [&](uintptr_t &table_addr) { + if (table_addr && table_addr < _loadBias) + table_addr += _loadBias; + + if (!KittyMemory::getAddressMap(table_addr, maps).readable) + table_addr = 0; + }; + + fix_table_address(_symbolTable); + fix_table_address(_stringTable); + fix_table_address(_gnuHashTable); + fix_table_address(_gnuHashTable); } - std::vector> ElfScanner::findSymbolAll(const std::string &symbolName) + uintptr_t ElfScanner::findSymbol(const std::string &symbolName) const { - std::vector> ret{}; + if (_loadBias && _stringTable && _symbolTable && _strsz && _syment) + { + auto get_sym_address = [&](const KT_ElfW(Sym) * sym_ent) -> uintptr_t { + return sym_ent->st_value < _loadBias ? _loadBias + sym_ent->st_value : sym_ent->st_value; + }; + + // try gnu hash first + if (_gnuHashTable) + { + const auto *sym = KittyUtils::Elf::GnuHash::LookupByName(_gnuHashTable, _symbolTable, _stringTable, + _syment, _strsz, symbolName.c_str()); + if (sym && sym->st_value) + { + return get_sym_address(sym); + } + } - auto elfs = getAllELFs(); - for (auto &it: elfs) { - uintptr_t sym = it.findSymbol(symbolName); - if (sym != 0) { - ret.emplace_back(sym, it); + if (_elfHashTable) + { + const auto *sym = KittyUtils::Elf::ElfHash::LookupByName(_elfHashTable, _symbolTable, _stringTable, + _syment, _strsz, symbolName.c_str()); + if (sym && sym->st_value) + { + return get_sym_address(sym); + } } } - return ret; + return 0; + } + + std::unordered_map ElfScanner::dsymbols() + { + if (!_dsymbols_init && _loadBias && !_filepath.empty()) + { + _dsymbols_init = true; + + auto get_sym_address = [&](const KT_ElfW(Sym) * sym_ent) -> uintptr_t { + return sym_ent->st_value < _loadBias ? _loadBias + sym_ent->st_value : sym_ent->st_value; + }; + + KittyUtils::Zip::ZipFileMMap mmap_info = {nullptr, 0}; + auto baseSeg = baseSegment(); + if (baseSeg.offset != 0) + { + mmap_info = KittyUtils::Zip::MMapFileByDataOffset(_filepath, baseSeg.offset); + } + else + { + errno = 0; + int fd = KT_EINTR_RETRY(open(_filepath.c_str(), O_RDONLY)); + if (fd < 0) + { + KITTY_LOGD("Failed to open file <%s> err(%d)", _filepath.c_str(), errno); + return _dsymbolsMap; + } + + struct stat flstats; + memset(&flstats, 0, sizeof(struct stat)); + int fstat_ret = fstat(fd, &flstats); + size_t elfSize = flstats.st_size; + if (fstat_ret == -1 || elfSize <= 0) + { + close(fd); + KITTY_LOGD("stat failed for <%s>", _filepath.c_str()); + return _dsymbolsMap; + } + mmap_info.data = mmap(nullptr, elfSize, PROT_READ, MAP_PRIVATE, fd, 0); + mmap_info.size = elfSize; + close(fd); + } + + if (mmap_info.size == 0 || !mmap_info.data || mmap_info.data == ((void *)-1)) + { + KITTY_LOGD("Failed to mmap <%s>", realPath().c_str()); + return _dsymbolsMap; + } + + auto cleanup = [&] { munmap(mmap_info.data, mmap_info.size); }; + + KT_ElfW(Ehdr) *ehdr = static_cast(mmap_info.data); + + if (memcmp(ehdr->e_ident, "\177ELF", 4) != 0) + { + KITTY_LOGD("<%s> is not a valid ELF", realPath().c_str()); + cleanup(); + return _dsymbolsMap; + } + + if (ehdr->e_phoff == 0 || ehdr->e_phentsize == 0 || ehdr->e_phnum == 0 || + ehdr->e_phoff + ehdr->e_phnum * sizeof(KT_ElfW(Phdr)) > mmap_info.size) + { + KITTY_LOGD("Invalid program header table in <%s>", filePath().c_str()); + cleanup(); + return _dsymbolsMap; + } + + if (ehdr->e_shoff == 0 || ehdr->e_shentsize == 0 || ehdr->e_shnum == 0 || + ehdr->e_shoff + ehdr->e_shnum * sizeof(KT_ElfW(Shdr)) > mmap_info.size) + { + KITTY_LOGD("Invalid section header table in <%s>", filePath().c_str()); + cleanup(); + return _dsymbolsMap; + } + + const KT_ElfW(Shdr) *shdr = reinterpret_cast(static_cast(mmap_info.data) + + ehdr->e_shoff); + const KT_ElfW(Shdr) *shstrtab_shdr = shdr + ehdr->e_shstrndx; + const char *sectionstr = reinterpret_cast(static_cast(mmap_info.data) + + shstrtab_shdr->sh_offset); + for (uint16_t i = 0; i < ehdr->e_shnum; ++i) + { + if (shdr[i].sh_type != SHT_SYMTAB) + continue; + + std::string section_name = std::string(reinterpret_cast(sectionstr + shdr[i].sh_name)); + if (section_name.compare(".symtab") != 0) + continue; + + if ((shdr[i].sh_offset + shdr[i].sh_size) > mmap_info.size || shdr[i].sh_link >= ehdr->e_shnum || + (shdr[shdr[i].sh_link].sh_offset + shdr[shdr[i].sh_link].sh_size) > mmap_info.size) + continue; + + const KT_ElfW(Sym) *symtab = reinterpret_cast(static_cast(mmap_info.data) + + shdr[i].sh_offset); + const size_t symCount = shdr[i].sh_size / shdr[i].sh_entsize; + const KT_ElfW(Shdr) *strtabShdr = &shdr[shdr[i].sh_link]; + const char *strtab = reinterpret_cast(static_cast(mmap_info.data) + + strtabShdr->sh_offset); + + for (size_t j = 0; j < symCount; ++j) + { + const KT_ElfW(Sym) *curr_sym = &symtab[j]; + if (!curr_sym || curr_sym->st_name >= strtabShdr->sh_size) + continue; + + if (intptr_t(curr_sym->st_value) <= 0 || intptr_t(curr_sym->st_size) <= 0) + continue; + + if (KT_ELF_ST_TYPE(curr_sym->st_info) != STT_OBJECT && + KT_ELF_ST_TYPE(curr_sym->st_info) != STT_FUNC) + continue; + + std::string sym_str = std::string(reinterpret_cast(strtab + curr_sym->st_name)); + if (!sym_str.empty() && sym_str.data()) + _dsymbolsMap[sym_str] = get_sym_address(curr_sym); + } + } + cleanup(); + } + return _dsymbolsMap; + } + + uintptr_t ElfScanner::findDebugSymbol(const std::string &symbolName) + { + const auto &syms = dsymbols(); + auto it = syms.find(symbolName); + return it != syms.end() ? it->second : 0; + } + + RegisterNativeFn ElfScanner::findRegisterNativeFn(const std::string &name, const std::string &signature) const + { + uintptr_t fn_loc = 0; + RegisterNativeFn fn; + + if (name.empty() || !isValid()) + return fn; + + std::vector string_locs; + for (auto &it : segments()) + { + if (it.readable && it.inode != 0) + { + uintptr_t string_loc = KittyScanner::findDataFirst(it.startAddress, it.endAddress, name.data(), + name.length()); + if (string_loc != 0) + string_locs.push_back(string_loc); + } + } + + if (string_locs.empty()) + { + KITTY_LOGD("findRegisterNativeFn: Couldn't find string (%s) " + "in selected maps", + name.c_str()); + return fn; + } + + for (auto &it : segments()) + { + if (it.readable && it.inode != 0) + { + for (auto &string_loc : string_locs) + { + uintptr_t string_xref = KittyScanner::findDataFirst(it.startAddress, it.endAddress, &string_loc, + sizeof(uintptr_t)); + if (!string_xref) + continue; + + uintptr_t signature_ptr = *(uintptr_t *)(string_xref + sizeof(uintptr_t)); + if (signature_ptr == 0) + continue; + + std::vector buf(signature.length() + 1, 0); + KittyMemory::syscallMemRead(signature_ptr, buf.data(), buf.size()); + + if (std::string(buf.data()) == signature) + { + fn_loc = string_xref; + break; + } + } + } + } + + if (fn_loc != 0) + { + memcpy(&fn, (void *)fn_loc, sizeof(RegisterNativeFn)); + } + + return fn; + } + + bool ElfScanner::dumpToDisk(const std::string &destination) const + { + bool dumped = (isValid() && KittyMemory::dumpMemToDisk(_elfBase, _loadSize, destination)); + if (dumped && _fixedBySoInfo) + { + KittyIOFile destIO(destination, O_WRONLY); + destIO.Open(); + KT_ElfW(Ehdr) fixedHdr = header(); + destIO.Write(0, &fixedHdr, sizeof(fixedHdr)); + destIO.Close(); + } + return dumped; + } + + ElfScanner &ElfScanner::getProgramElf() + { + static ElfScanner progElf{}; + if (!progElf.isValid() || !progElf.dynamic()) + { + const char *path = "/proc/self/exe"; + char exePath[0xff] = {0}; + errno = 0; + int ret = int(KT_EINTR_RETRY(readlink(path, exePath, 0xff))); + if (ret == -1) + { + int err = errno; + KITTY_LOGE("Failed to readlink \"%s\", error(%d): %s.", path, err, strerror(err)); + return progElf; + } + + const auto allMaps = KittyMemory::getAllMaps(); + const auto maps = KittyMemory::getMaps(KittyMemory::EProcMapFilter::Equal, exePath, allMaps); + for (const auto &it : maps) + { + if (!it.readable || it.writeable) + continue; + + progElf = ElfScanner(it.startAddress, allMaps); + if (progElf.isValid() && progElf.dynamic()) + break; + } + } + return progElf; + } + + std::vector ElfScanner::getAllELFs(EScanElfType type, EScanElfFilter filter) + { + static std::mutex mtx; + std::lock_guard lock(mtx); + + static std::unordered_map cached_elfs; + std::vector elfs; + + auto maps = KittyMemory::getAllMaps(); + if (maps.empty()) + { + KITTY_LOGD("getAllELFs: Failed to get process maps."); + return elfs; + } + + std::vector invalid_keys; + for (auto &it : cached_elfs) + { + if (it.first && !KittyMemory::getAddressMap(it.first, maps).readable) + { + invalid_keys.push_back(it.first); + } + } + + for (auto &it : invalid_keys) + { + cached_elfs.erase(it); + } + + const auto progMachine = getProgramElf().header().e_machine; + static auto eMachineCheck = [](EScanElfType type, int a, int b) -> bool { + return a == 0 || b == 0 || type == EScanElfType::Any || (type == EScanElfType::Native && a == b) || + (type == EScanElfType::Emulated && a != b); + }; + + const bool isAppFilter = filter == EScanElfFilter::App; + const bool isSysFilter = filter == EScanElfFilter::System; + + unsigned long lastElfNode = 0; + + for (const auto &it : maps) + { +#ifdef __LP64__ + if (it.startAddress >= (0x7fffffffffff-0x1000)) + continue; +#else + if (it.startAddress >= (0xffffffff-0x1000)) + continue; +#endif + + if (!it.isValid() || !it.readable || it.writeable || it.is_shared || + (it.inode != 0 && it.inode == lastElfNode)) + continue; + + if (isAppFilter) + { + if (it.inode == 0 || (!KittyUtils::String::StartsWith(it.pathname, "/data/") && + !KittyUtils::String::StartsWith(it.pathname, "/proc/") && + !KittyUtils::String::StartsWith(it.pathname, "/memfd:"))) + continue; + } + else if (isSysFilter) + { + if ((it.inode == 0 && it.pathname != "[vdso]") || + (!KittyUtils::String::StartsWith(it.pathname, "/system/") && + !KittyUtils::String::StartsWith(it.pathname, "/apex/"))) + continue; + } + + if (cached_elfs.size() && cached_elfs.count(it.startAddress) > 0) + { + auto elf = cached_elfs[it.startAddress]; + if (elf.filePath() == it.pathname) + { + if (eMachineCheck(type, progMachine, elf.header().e_machine)) + { + elfs.push_back(elf); + } + lastElfNode = elf.baseSegment().inode; + continue; + } + else + { + cached_elfs.erase(it.startAddress); + } + } + + bool isFile = (!it.pathname.empty() && it.inode != 0); + if (!isFile && it.pathname != "[vdso]" && !KittyUtils::String::StartsWith(it.pathname, "/memfd:")) + continue; + + if (it.pathname == "cfi shadow") + continue; + + if (KittyUtils::String::StartsWith(it.pathname, "/dev/") || + KittyUtils::String::StartsWith(it.pathname, "/system/fonts/") || + KittyUtils::String::StartsWith(it.pathname, "/data/priv-downloads/") || + KittyUtils::String::StartsWith(it.pathname, "/data/misc/")) + continue; + + if (KittyUtils::String::StartsWith(it.pathname, "/system/etc/") && + !KittyUtils::String::EndsWith(it.pathname, ".so")) + continue; + + if ((KittyUtils::String::StartsWith(it.pathname, "/data/dalvik-cache/") || + KittyUtils::String::StartsWith(it.pathname, "/system/") || + KittyUtils::String::StartsWith(it.pathname, "/apex/com.android.") || + (KittyUtils::String::StartsWith(it.pathname, "/data/app/") && + KittyUtils::String::Contains(it.pathname, "/oat/"))) && + (KittyUtils::String::EndsWith(it.pathname, ".jar") || + KittyUtils::String::EndsWith(it.pathname, ".art") || + KittyUtils::String::EndsWith(it.pathname, ".oat") || + KittyUtils::String::EndsWith(it.pathname, ".odex") || + KittyUtils::String::EndsWith(it.pathname, ".dex"))) + continue; + + auto elf = ElfScanner(it.startAddress, maps); + if (elf.isValid()) + { + if (eMachineCheck(type, progMachine, elf.header().e_machine)) + { + elfs.push_back(elf); + } + lastElfNode = elf.baseSegment().inode; + cached_elfs[it.startAddress] = elf; + } + } + + return elfs; + } + + ElfScanner ElfScanner::findElf(const std::string &path, EScanElfType type, EScanElfFilter filter) + { + ElfScanner ret{}; + + if (path.empty()) + return ret; + + std::vector elfs; + std::vector dyn_elfs; + + const auto allElfs = ElfScanner::getAllELFs(type, filter); + for (const auto &it : allElfs) + { + if (it.isValid() && KittyUtils::String::EndsWith(it.realPath(), path)) + { + if (it.dynamic() && it.dynamics().size() > 0) + dyn_elfs.push_back(it); + else + elfs.push_back(it); + } + } + + if (elfs.empty() && dyn_elfs.empty()) + return ret; + + if (dyn_elfs.size() > 0) + { + if (dyn_elfs.size() == 1) + return dyn_elfs[0]; + + int nMostSegments = 0; + for (auto &it : dyn_elfs) + { + int numSegments = it.segments().size(); + if (numSegments > nMostSegments) + { + ret = it; + nMostSegments = numSegments; + } + } + } + else if (elfs.size() > 0) + { + if (elfs.size() == 1) + return elfs[0]; + + int nMostSegments = 0; + for (auto &it : elfs) + { + int numSegments = it.segments().size(); + if (numSegments > nMostSegments) + { + ret = it; + nMostSegments = numSegments; + } + } + } + + return ret; + } + + std::vector> ElfScanner::findSymbolAll(const std::string &symbolName, + EScanElfType type, EScanElfFilter filter) + { + std::vector> ret{}; + + auto elfs = getAllELFs(type, filter); + for (auto &it : elfs) + { + uintptr_t sym = it.findSymbol(symbolName); + if (sym != 0) + { + ret.emplace_back(sym, it); + } + } + + return ret; + } + + LinkerScanner::LinkerScanner(uintptr_t linkerBase) : ElfScanner(linkerBase) + { + memset(&_linker_syms, 0, sizeof(_linker_syms)); + memset(&_soinfo_offsets, 0, sizeof(_soinfo_offsets)); + _init = false; + + if (!isValid()) + return; + + init(); + } + + LinkerScanner::LinkerScanner(const ElfScanner &linkerElf) : ElfScanner(linkerElf) + { + memset(&_linker_syms, 0, sizeof(_linker_syms)); + memset(&_soinfo_offsets, 0, sizeof(_soinfo_offsets)); + _init = false; + + if (!isValid()) + return; + + init(); + } + + bool LinkerScanner::init() + { + if (!isValid()) + return false; + + if (_init) + return true; + + for (const auto &sym : dsymbols()) + { + if (KittyUtils::String::StartsWith(sym.first, "__dl__ZL6solist")) + { + _linker_syms.solist = sym.second; + continue; + } + if (KittyUtils::String::StartsWith(sym.first, "__dl__ZL6somain")) + { + _linker_syms.somain = sym.second; + continue; + } + if (KittyUtils::String::StartsWith(sym.first, "__dl__ZL6sonext")) + { + _linker_syms.sonext = sym.second; + continue; + } + if (_linker_syms.solist && _linker_syms.somain && _linker_syms.sonext) + break; + } + + if (!(_linker_syms.solist && _linker_syms.somain && _linker_syms.sonext)) + { + return false; + } + + KITTY_LOGD("solist(%zx) | somain(%zx) | sonext(%zx)", solist(), somain(), sonext()); + + auto maps = KittyMemory::getAllMaps(); + + uintptr_t solist_ptr = solist(); + std::vector solist_buf(KT_SOINFO_BUFFER_SZ, 0); + for (size_t i = 0; i < solist_buf.size(); i += sizeof(uintptr_t)) + { + if (KittyMemory::getAddressMap(solist_ptr + i, maps).readable) + { + memcpy((void *)(solist_buf.data() + i), (const void *)(solist_ptr + i), sizeof(uintptr_t)); + } + } + + std::vector si_buf(KT_SOINFO_BUFFER_SZ, 0); + uintptr_t somain_ptr = (somain() ? somain() : sonext()); + for (size_t i = 0; i < si_buf.size(); i += sizeof(uintptr_t)) + { + if (KittyMemory::getAddressMap(somain_ptr + i, maps).readable) + { + memcpy((void *)(si_buf.data() + i), (const void *)(somain_ptr + i), sizeof(uintptr_t)); + } + } + + ElfScanner si_elf{}; + for (size_t i = 0; i < si_buf.size(); i += sizeof(uintptr_t)) + { + uintptr_t possible_base = *(uintptr_t *)&si_buf[i]; + + auto tmp_map = KittyMemory::getAddressMap(possible_base, maps); + if (possible_base != tmp_map.startAddress || !tmp_map.isValid() || !tmp_map.readable || tmp_map.writeable || + tmp_map.is_shared) + continue; + + si_elf = ElfScanner(possible_base, maps); + if (si_elf.isValid()) + { + _soinfo_offsets.base = i; + break; + } + } + + KITTY_LOGD("soinfo_base(%zx)", _soinfo_offsets.base); + + if (_soinfo_offsets.base == 0) + return false; + + for (size_t i = 0; i < si_buf.size(); i += sizeof(uintptr_t)) + { + uintptr_t value = *(uintptr_t *)&si_buf[i]; + + if (value == si_elf.phdr()) + _soinfo_offsets.phdr = i; + if (value == si_elf.programHeaders().size()) + _soinfo_offsets.phnum = i; + else if (value == si_elf.loadSize() || + value == (si_elf.loadSize() + KittyMemory::getAddressMap(si_elf.end(), maps).length)) + _soinfo_offsets.size = i; + else if (value == si_elf.dynamic()) + _soinfo_offsets.dyn = i; + else if (value == si_elf.stringTable()) + _soinfo_offsets.strtab = i; + else if (value == si_elf.symbolTable()) + _soinfo_offsets.symtab = i; + else if (value == si_elf.loadBias() && i != _soinfo_offsets.base) + _soinfo_offsets.bias = i; + else if (value == si_elf.stringTableSize()) + _soinfo_offsets.strsz = i; + } + + KITTY_LOGD("soinfo_bias(%zx) | soinfo_size(%zx)", _soinfo_offsets.bias, _soinfo_offsets.size); + KITTY_LOGD("soinfo_phdr(%zx, %zx) | soinfo_dyn(%zx)", _soinfo_offsets.phdr, _soinfo_offsets.phnum, + _soinfo_offsets.dyn); + KITTY_LOGD("soinfo_strtab(%zx, %zx) | soinfo_symtab(%zx)", _soinfo_offsets.strtab, _soinfo_offsets.strsz, + _soinfo_offsets.symtab); + + if (!(_soinfo_offsets.size && _soinfo_offsets.bias && _soinfo_offsets.dyn && _soinfo_offsets.symtab && + _soinfo_offsets.strtab)) + { + return false; + } + + for (size_t i = 0; i < solist_buf.size(); i += sizeof(uintptr_t)) + { + uintptr_t possible_next = *(uintptr_t *)&solist_buf[i]; + + if (!KittyMemory::getAddressMap(possible_next + _soinfo_offsets.base, maps).readable) + continue; + + uintptr_t possible_base = *(uintptr_t *)(possible_next + _soinfo_offsets.base); + auto tmp_map = KittyMemory::getAddressMap(possible_base, maps); + if (!tmp_map.isValid() || !tmp_map.readable || tmp_map.writeable || tmp_map.is_shared) + continue; + + auto tmp_elf = ElfScanner(possible_base, maps); + if (tmp_elf.isValid()) + { + if (!KittyMemory::getAddressMap(possible_next + _soinfo_offsets.size, maps).readable) + continue; + + size_t possible_size = *(uintptr_t *)(possible_next + _soinfo_offsets.size); + if (possible_size == tmp_elf.loadSize() || + possible_size == (tmp_elf.loadSize() + KittyMemory::getAddressMap(tmp_elf.end(), maps).length)) + { + _soinfo_offsets.next = i; + break; + } + } + } + + KITTY_LOGD("soinfo_sonext(%zx)", _soinfo_offsets.next); + + _init = _soinfo_offsets.next != 0; + return _init; + } + + std::vector LinkerScanner::allSoInfo() const + { + std::vector infos{}; + + if (!isValid() || !_init) + return infos; + + auto maps = KittyMemory::getAllMaps(); + uintptr_t si = solist(); + while (si && KittyMemory::getAddressMap(si, maps).readable) + { + kitty_soinfo_t info = infoFromSoInfo_(si, maps); + infos.push_back(info); + + si = *(uintptr_t *)(si + _soinfo_offsets.next); + } + return infos; + } + + kitty_soinfo_t LinkerScanner::findSoInfo(const std::string &name) const + { + const auto list = allSoInfo(); + for (const auto &it : list) + { + if (KittyUtils::String::EndsWith(it.realpath, name)) + { + return it; + } + } + return {}; + } + + kitty_soinfo_t LinkerScanner::infoFromSoInfo_(uintptr_t si, const std::vector &maps) const + { + kitty_soinfo_t info{}; + + if (!_init) + return info; + + info.ptr = si; + info.base = *(uintptr_t *)(si + _soinfo_offsets.base); + info.size = *(uintptr_t *)(si + _soinfo_offsets.size); + info.phdr = *(uintptr_t *)(si + _soinfo_offsets.phdr); + info.phnum = *(uintptr_t *)(si + _soinfo_offsets.phnum); + info.dyn = *(uintptr_t *)(si + _soinfo_offsets.dyn); + info.strtab = *(uintptr_t *)(si + _soinfo_offsets.strtab); + info.symtab = *(uintptr_t *)(si + _soinfo_offsets.symtab); + info.strsz = _soinfo_offsets.strsz ? *(uintptr_t *)(si + _soinfo_offsets.strsz) : 0; + info.bias = *(uintptr_t *)(si + _soinfo_offsets.bias); + info.next = *(uintptr_t *)(si + _soinfo_offsets.next); + info.e_machine = header().e_machine; + + uintptr_t start_map_addr = info.base; + if (start_map_addr == 0) + start_map_addr = info.base; + if (start_map_addr == 0) + start_map_addr = info.bias; + if (start_map_addr == 0) + start_map_addr = info.phdr; + if (start_map_addr == 0) + start_map_addr = info.dyn; + if (start_map_addr == 0) + start_map_addr = info.strtab; + if (start_map_addr == 0) + start_map_addr = info.symtab; + + auto si_map = KittyMemory::getAddressMap(start_map_addr, maps); + if (si_map.isValid()) + { + info.path = si_map.pathname; + info.realpath = si_map.pathname; + if (si_map.offset != 0) + { + std::string inZipPath = + KittyUtils::Zip::GetFileInfoByDataOffset(si_map.pathname, si_map.offset).fileName; + if (!inZipPath.empty()) + { + info.realpath += '!'; + info.realpath += inZipPath; + } + } + } + + return info; + } + + bool NativeBridgeScanner::init() + { + if (_init) + return true; + + _nbElf = ElfScanner::findElf("/libnativebridge.so", EScanElfType::Native, EScanElfFilter::System); + if (!_nbElf.isValid()) + { + KITTY_LOGD("NativeBridgeScanner: Failed to find libnativebrdge.so"); + return false; + } + + _nbImplElf = ElfScanner::findElf("/libhoudini.so", EScanElfType::Native, EScanElfFilter::System); + if (_nbImplElf.isValid()) + _isHoudini = true; + else + _nbImplElf = ElfScanner::findElf("/libndk_translation.so", EScanElfType::Native, EScanElfFilter::System); + + if (!_nbImplElf.isValid()) + { + KITTY_LOGD("NativeBridgeScanner: Failed to find nativebridge implementation"); + return false; + } + + _nbItf = _nbImplElf.findSymbol("NativeBridgeItf"); + if (_nbItf == 0) + { + KITTY_LOGD("NativeBridgeScanner: Failed to find export NativeBridgeItf"); + return false; + } + + _nbItf_data.version = *(int *)_nbItf; + switch (_nbItf_data.version) + { + case 2: // SIGNAL_VERSION + _nbItf_data_size = sizeof(uintptr_t) * 8; + break; + case 3: // NAMESPACE_VERSION + _nbItf_data_size = sizeof(uintptr_t) * 15; + break; + case 4: // VENDOR_NAMESPACE_VERSION + _nbItf_data_size = sizeof(uintptr_t) * 16; + break; + case 5: // RUNTIME_NAMESPACE_VERSION + _nbItf_data_size = sizeof(uintptr_t) * 17; + break; + case 6: // PRE_ZYGOTE_FORK_VERSION + _nbItf_data_size = sizeof(uintptr_t) * 18; + break; + case 7: // CRITICAL_NATIVE_SUPPORT_VERSION + _nbItf_data_size = sizeof(uintptr_t) * 19; + break; + case 8: // IDENTIFY_NATIVELY_BRIDGED_FUNCTION_POINTERS_VERSION + _nbItf_data_size = sizeof(uintptr_t) * 21; + break; + default: + KITTY_LOGD("NativeBridgeScanner: Unsupported nativebridge version (%d)", _nbItf_data.version); + return false; + } + + KITTY_LOGD("NativeBridgeScanner: Using nativebridge version (%d), data size (%p)", _nbItf_data.version, + (void *)_nbItf_data_size); + + memcpy(&_nbItf_data, (const void *)(_nbItf), _nbItf_data_size); + + *(uintptr_t *)&fnNativeBridgeInitialized = _nbElf.findSymbol("NativeBridgeInitialized"); + if (fnNativeBridgeInitialized == nullptr) + *(uintptr_t *)&fnNativeBridgeInitialized = _nbElf.findSymbol("_ZN7android23NativeBridgeInitializedEv"); + + // replace for nb v2 + if (_nbItf_data.version < 3) + { + uintptr_t pLoadLibrary = _nbElf.findSymbol("NativeBridgeLoadLibrary"); + if (pLoadLibrary == 0) + pLoadLibrary = _nbElf.findSymbol("_ZN7android23NativeBridgeLoadLibraryEPKci"); + + uintptr_t pGetTrampoline = _nbElf.findSymbol("NativeBridgeGetTrampoline"); + if (pGetTrampoline == 0) + pGetTrampoline = _nbElf.findSymbol("_ZN7android25NativeBridgeGetTrampolineEPvPKcS2_j"); + + if (pLoadLibrary != 0) + *(uintptr_t *)&_nbItf_data.loadLibrary = pLoadLibrary; + + if (pGetTrampoline != 0) + *(uintptr_t *)&_nbItf_data.getTrampoline = pGetTrampoline; + } + + _sodlElf = ElfScanner::findElf("/libdl.so", EScanElfType::Emulated, EScanElfFilter::System); + if (!_sodlElf.isValid()) + { + KITTY_LOGD("NativeBridgeScanner: Failed to find emulated libdl.so"); + return false; + } + + struct + { + uintptr_t phdr = 0; + size_t phnum = 0; + } data; + + data.phdr = _sodlElf.phdr(); + data.phnum = _sodlElf.programHeaders().size(); + + KITTY_LOGD("NativeBridgeScanner: sodl phdr { %p, %zu }", (void *)(data.phdr), data.phnum); + + auto maps = KittyMemory::getAllMaps(); + + // search in bss frst + for (auto &it : _nbImplElf.bssSegments()) + { + _sodl = findDataFirst(it.startAddress, it.endAddress, &data, sizeof(data)); + if (_sodl) + { + KITTY_LOGD("NativeBridgeScanner: Found sodl->phdr ref (%p) at %s", (void *)_sodl, + it.toString().c_str()); + break; + } + } + + if (_sodl == 0) + { + // search in read-only "[anon:Mem_" or "[anon:linker_alloc]" + for (auto &it : maps) + { + if (!it.is_private || !it.is_ro || it.inode != 0) + continue; + + if (!KittyUtils::String::StartsWith(it.pathname, "[anon:Mem_") && it.pathname != "[anon:linker_alloc]") + continue; + + _sodl = findDataFirst(it.startAddress, it.endAddress, &data, sizeof(data)); + if (_sodl) + { + KITTY_LOGD("NativeBridgeScanner: Found sodl->phdr ref (%p) at %s", (void *)_sodl, + it.toString().c_str()); + break; + } + } + } + + if (_sodl == 0) + { + KITTY_LOGD("NativeBridgeScanner: Failed to find refs to emulated libdl.so phdr data"); + return false; + } + + std::vector si_buf(KT_SOINFO_BUFFER_SZ, 0); + for (size_t i = 0; i < si_buf.size(); i += sizeof(uintptr_t)) + { + if (KittyMemory::getAddressMap(_sodl + i, maps).readable) + { + memcpy((void *)(si_buf.data() + i), (const void *)(_sodl + i), sizeof(uintptr_t)); + } + } + + for (size_t i = 0; i < si_buf.size(); i += sizeof(uintptr_t)) + { + uintptr_t possible_next = *(uintptr_t *)&si_buf[i]; + if (!KittyMemory::getAddressMap(possible_next, maps).readable) + continue; + + std::vector si_buf_inner(KT_SOINFO_BUFFER_SZ, 0); + for (size_t j = 0; j < si_buf_inner.size(); j += sizeof(uintptr_t)) + { + if (KittyMemory::getAddressMap(possible_next + j, maps).readable) + { + memcpy((void *)(si_buf_inner.data() + j), (const void *)(possible_next + j), sizeof(uintptr_t)); + } + } + + ElfScanner si_elf{}; + for (size_t j = 0; j < si_buf_inner.size(); j += sizeof(uintptr_t)) + { + uintptr_t possible_base = *(uintptr_t *)&si_buf_inner[j]; + + auto tmp_map = KittyMemory::getAddressMap(possible_base, maps); + if (possible_base != tmp_map.startAddress || !tmp_map.isValid() || !tmp_map.readable || + tmp_map.writeable || tmp_map.is_shared) + continue; + + si_elf = ElfScanner(possible_base, maps); + if (si_elf.isValid()) + { + _soinfo_offsets.base = j; + break; + } + } + + if (_soinfo_offsets.base == 0) + continue; + + for (size_t j = 0; j < si_buf_inner.size(); j += sizeof(uintptr_t)) + { + uintptr_t value = *(uintptr_t *)&si_buf_inner[j]; + if (value == si_elf.phdr()) + _soinfo_offsets.phdr = j; + if (value == si_elf.programHeaders().size()) + _soinfo_offsets.phnum = j; + else if (value == si_elf.loadSize() || + value == (si_elf.loadSize() + KittyMemory::getAddressMap(si_elf.end(), maps).length)) + _soinfo_offsets.size = j; + else if (value == si_elf.dynamic()) + _soinfo_offsets.dyn = j; + else if (value == si_elf.stringTable()) + _soinfo_offsets.strtab = j; + else if (value == si_elf.symbolTable()) + _soinfo_offsets.symtab = j; + else if (j > _soinfo_offsets.size && value == si_elf.loadBias()) + _soinfo_offsets.bias = j; + else if (value == si_elf.stringTableSize()) + _soinfo_offsets.strsz = j; + } + + if (_soinfo_offsets.size && _soinfo_offsets.bias && _soinfo_offsets.dyn && _soinfo_offsets.symtab && + _soinfo_offsets.strtab) + { + // phdr offset might not be 0 + _sodl -= _soinfo_offsets.phdr; + _soinfo_offsets.next = _soinfo_offsets.phdr + i; + break; + } + } + + KITTY_LOGD("nb_soinfo_base(%zx) | nb_soinfo_size(%zx) | nb_soinfo_bias(%zx)", _soinfo_offsets.base, + _soinfo_offsets.size, _soinfo_offsets.bias); + KITTY_LOGD("nb_soinfo_phdr(%zx, %zx) | nb_soinfo_dyn(%zx)", _soinfo_offsets.phdr, _soinfo_offsets.phnum, + _soinfo_offsets.dyn); + KITTY_LOGD("nb_soinfo_strtab(%zx, %zx) | nb_soinfo_symtab(%zx)", _soinfo_offsets.strtab, _soinfo_offsets.strsz, + _soinfo_offsets.symtab); + + KITTY_LOGD("nb_soinfo_next(%zx)", _soinfo_offsets.next); + + _init = _soinfo_offsets.next != 0; + return _init; + } + + std::vector NativeBridgeScanner::allSoInfo() const + { + std::vector infos{}; + + if (!_init) + return infos; + + auto maps = KittyMemory::getAllMaps(); + uintptr_t si = _sodl; + while (si && KittyMemory::getAddressMap(si, maps).readable) + { + kitty_soinfo_t info = infoFromSoInfo_(si, maps); + infos.push_back(info); + + si = *(uintptr_t *)(si + _soinfo_offsets.next); + } + return infos; + } + + kitty_soinfo_t NativeBridgeScanner::findSoInfo(const std::string &name) const + { + kitty_soinfo_t ret{}; + const auto list = allSoInfo(); + for (const auto &it : list) + { + if (KittyUtils::String::EndsWith(it.realpath, name)) + { + ret = it; + break; + } + } + return ret; + } + + kitty_soinfo_t NativeBridgeScanner::infoFromSoInfo_(uintptr_t si, + const std::vector &maps) const + { + kitty_soinfo_t info{}; + + if (!_init) + return info; + + info.ptr = si; + info.base = *(uintptr_t *)(si + _soinfo_offsets.base); + info.size = *(uintptr_t *)(si + _soinfo_offsets.size); + info.phdr = *(uintptr_t *)(si + _soinfo_offsets.phdr); + info.phnum = *(uintptr_t *)(si + _soinfo_offsets.phnum); + info.dyn = *(uintptr_t *)(si + _soinfo_offsets.dyn); + info.strtab = *(uintptr_t *)(si + _soinfo_offsets.strtab); + info.symtab = *(uintptr_t *)(si + _soinfo_offsets.symtab); + info.strsz = _soinfo_offsets.strsz ? *(uintptr_t *)(si + _soinfo_offsets.strsz) : 0; + info.bias = *(uintptr_t *)(si + _soinfo_offsets.bias); + info.next = *(uintptr_t *)(si + _soinfo_offsets.next); + info.e_machine = _sodlElf.header().e_machine; + + uintptr_t start_map_addr = info.base; + if (start_map_addr == 0) + start_map_addr = info.base; + if (start_map_addr == 0) + start_map_addr = info.bias; + if (start_map_addr == 0) + start_map_addr = info.phdr; + if (start_map_addr == 0) + start_map_addr = info.dyn; + if (start_map_addr == 0) + start_map_addr = info.strtab; + if (start_map_addr == 0) + start_map_addr = info.symtab; + + auto si_map = KittyMemory::getAddressMap(start_map_addr, maps); + if (si_map.isValid()) + { + info.path = si_map.pathname; + info.realpath = si_map.pathname; + if (si_map.offset != 0) + { + std::string inZipPath = + KittyUtils::Zip::GetFileInfoByDataOffset(si_map.pathname, si_map.offset).fileName; + if (!inZipPath.empty()) + { + info.realpath += '!'; + info.realpath += inZipPath; + } + } + } + + return info; + } + + void *NativeBridgeLinker::dlopen(const std::string &path, int flags) + { +#if !defined(__x86_64__) && !defined(__i386__) + return nullptr; +#endif + auto &nb = NativeBridgeScanner::Get(); + auto nbData = nb.nbItfData(); + + if (path.empty() || !nb.init()) + return nullptr; + + if (nbData.version < 2) + { + KITTY_LOGD("nb_dlopen: nativebridge version (%d) is not supported", nbData.version); + return nullptr; + } + + if (nb.fnNativeBridgeInitialized && !nb.fnNativeBridgeInitialized()) + { + KITTY_LOGD("nb_dlopen: nativebridge is not initialized"); + return nullptr; + } + + /*if ((nbData..version == 2 && !nbData.isSupported(path.c_str())) || + !nbData.isPathSupported(path.c_str())) + { + KITTY_LOGD("nb_dlopen: path not supported (%s)", path.c_str()); + return nullptr; + }*/ + + if (nbData.version == 2) + return nbData.loadLibrary(path.c_str(), flags); + + void *default_ns = nullptr; + if (nb.isHoudini()) + { + default_ns = (void *)uintptr_t(nbData.version >= 5 ? 5 : 3); + if (nbData.version >= 5) + { + uintptr_t tmp_ns = (uintptr_t)nbData.getExportedNamespace("classloader-namespace"); + if (tmp_ns > 0 && tmp_ns <= 25) + default_ns = (void *)tmp_ns; + } + } + else + { + if (nbData.getExportedNamespace) + default_ns = nbData.getExportedNamespace("default"); + else if (nbData.getVendorNamespace) + default_ns = nbData.getVendorNamespace(); + } + + if (!default_ns) + { + KITTY_LOGD("nb_dlopen: Failed to find default namespace"); + return nullptr; + } + + return nbData.loadLibraryExt(path.c_str(), flags, default_ns); + } + + void *NativeBridgeLinker::dlsym(void *handle, const std::string &sym_name) + { +#if !defined(__x86_64__) && !defined(__i386__) + return nullptr; +#endif + auto &nb = NativeBridgeScanner::Get(); + auto nbData = nb.nbItfData(); + + if (!handle || !nb.init()) + return nullptr; + + if (nbData.version < 28) + { + KITTY_LOGD("nb_dlsym: nativebridge version (%d) is not supported", nbData.version); + return nullptr; + } + + if (nb.fnNativeBridgeInitialized && !nb.fnNativeBridgeInitialized()) + { + KITTY_LOGD("nb_dlsym: nativebridge is not initialized"); + return nullptr; + } + + if (nbData.version < 7) + { + return nbData.getTrampoline(handle, sym_name.c_str(), nullptr, 0); + } + + return nbData.getTrampolineWithJNICallType(handle, sym_name.c_str(), nullptr, 0, KT_JNICallTypeRegular); + } + + const char *NativeBridgeLinker::dlerror() + { +#if !defined(__x86_64__) && !defined(__i386__) + return nullptr; +#endif + auto &nb = NativeBridgeScanner::Get(); + auto nbData = nb.nbItfData(); + + if (nbData.version < 3) + { + KITTY_LOGD("nb_dlerror: nativebridge version (%d) is not supported", nbData.version); + return nullptr; + } + + if (nb.fnNativeBridgeInitialized && !nb.fnNativeBridgeInitialized()) + { + KITTY_LOGD("nb_dlerror: nativebridge is not initialized"); + return nullptr; + } + + return nbData.getError ? nbData.getError() : nullptr; + } + + bool NativeBridgeLinker::dladdr(const void *addr, kitty_soinfo_t *info) + { + for (const auto &it : NativeBridgeScanner::Get().allSoInfo()) + { + if (uintptr_t(addr) >= it.base && uintptr_t(addr) < (it.base + it.size)) + { + if (info) + *info = it; + return true; + } + } + + return false; + } + + void NativeBridgeLinker::dl_iterate_phdr(const std::function &callback) + { + if (!callback) + return; + + for (const auto &it : NativeBridgeScanner::Get().allSoInfo()) + { + if (callback(&it)) + break; + } } #endif // __ANDROID__ -} \ No newline at end of file +} // namespace KittyScanner diff --git a/app/src/main/jni/KittyMemory/KittyScanner.hpp b/app/src/main/jni/KittyMemory/KittyScanner.hpp index 47aca1c..1f64db4 100644 --- a/app/src/main/jni/KittyMemory/KittyScanner.hpp +++ b/app/src/main/jni/KittyMemory/KittyScanner.hpp @@ -5,13 +5,19 @@ #include #include +#ifdef __ANDROID__ +#include +#include +#include +#endif + #include "KittyMemory.hpp" namespace KittyScanner { /** * Search for bytes within a memory range and return all results - * + * * @start: search start address * @end: search end address * @bytes: bytes to search @@ -19,11 +25,12 @@ namespace KittyScanner * * @return vector list of all found bytes addresses */ - std::vector findBytesAll(const uintptr_t start, const uintptr_t end, const char *bytes, const std::string& mask); - + std::vector findBytesAll(const uintptr_t start, const uintptr_t end, const char *bytes, + const std::string &mask); + /** * Search for bytes within a memory range and return first result - * + * * @start: search start address * @end: search end address * @bytes: bytes to search @@ -31,11 +38,11 @@ namespace KittyScanner * * @return first found bytes address */ - uintptr_t findBytesFirst(const uintptr_t start, const uintptr_t end, const char *bytes, const std::string& mask); + uintptr_t findBytesFirst(const uintptr_t start, const uintptr_t end, const char *bytes, const std::string &mask); /** * Search for hex within a memory range and return all results - * + * * @start: search start address * @end: search end address * @hex: hex to search @@ -43,11 +50,12 @@ namespace KittyScanner * * @return vector list of all found hex addresses */ - std::vector findHexAll(const uintptr_t start, const uintptr_t end, std::string hex, const std::string& mask); - + std::vector findHexAll(const uintptr_t start, const uintptr_t end, std::string hex, + const std::string &mask); + /** * Search for hex within a memory range and return first result - * + * * @start: search start address * @end: search end address * @hex: hex to search @@ -55,7 +63,7 @@ namespace KittyScanner * * @return first found hex address */ - uintptr_t findHexFirst(const uintptr_t start, const uintptr_t end, std::string hex, const std::string& mask); + uintptr_t findHexFirst(const uintptr_t start, const uintptr_t end, std::string hex, const std::string &mask); /** * Search for ida pattern within a memory range and return all results @@ -66,7 +74,7 @@ namespace KittyScanner * * @return vector list of all found pattern addresses */ - std::vector findIdaPatternAll(const uintptr_t start, const uintptr_t end, const std::string& pattern); + std::vector findIdaPatternAll(const uintptr_t start, const uintptr_t end, const std::string &pattern); /** * Search for ida pattern within a memory range and return first result @@ -77,11 +85,11 @@ namespace KittyScanner * * @return first found pattern address */ - uintptr_t findIdaPatternFirst(const uintptr_t start, const uintptr_t end, const std::string& pattern); + uintptr_t findIdaPatternFirst(const uintptr_t start, const uintptr_t end, const std::string &pattern); /** * Search for data within a memory range and return all results - * + * * @start: search start address * @end: search end address * @data: data to search @@ -90,11 +98,10 @@ namespace KittyScanner * @return vector list of all found data addresses */ std::vector findDataAll(const uintptr_t start, const uintptr_t end, const void *data, size_t size); - - + /** * Search for data within a memory range and return first result - * + * * @start: search start address * @end: search end address * @data: data to search @@ -105,7 +112,7 @@ namespace KittyScanner uintptr_t findDataFirst(const uintptr_t start, const uintptr_t end, const void *data, size_t size); #ifdef __ANDROID__ - + class RegisterNativeFn { public: @@ -113,130 +120,524 @@ namespace KittyScanner char *signature; void *fnPtr; - RegisterNativeFn() : name(nullptr), signature(nullptr), fnPtr(nullptr) {} - inline bool isValid() const { return (name != nullptr && signature != nullptr && fnPtr != nullptr); } + RegisterNativeFn() : name(nullptr), signature(nullptr), fnPtr(nullptr) + { + } + inline bool isValid() const + { + return (name != nullptr && signature != nullptr && fnPtr != nullptr); + } }; - /** - * search for string "name" references to find the JNINativeMethod array - */ - RegisterNativeFn findRegisterNativeFn(const class ElfScanner &elf, const std::string &name); +#define KT_SOINFO_BUFFER_SZ (0x250) + struct kitty_soinfo_t + { + uintptr_t ptr = 0; + uintptr_t base = 0; + size_t size = 0; + uintptr_t phdr = 0; + size_t phnum = 0; + uintptr_t dyn = 0; + uintptr_t strtab = 0; + uintptr_t symtab = 0; + size_t strsz = 0; + uintptr_t bias = 0; + uintptr_t next = 0; + uint16_t e_machine = 0; + std::string path; + std::string realpath; + }; + + enum class EScanElfType : uint32_t + { + Any, + Native, + Emulated, + }; + enum class EScanElfFilter : uint32_t + { + Any, + System, + App, + }; class ElfScanner { private: uintptr_t _elfBase; - ElfW_(Ehdr) _ehdr; + KT_ElfW(Ehdr) _ehdr; uintptr_t _phdr; - std::vector _phdrs; + std::vector _phdrs; int _loads; uintptr_t _loadBias, _loadSize; - uintptr_t _bss; - size_t _bssSize; uintptr_t _dynamic; - std::vector _dynamics; + std::vector _dynamics; uintptr_t _stringTable, _symbolTable, _elfHashTable, _gnuHashTable; size_t _strsz, _syment; - KittyMemory::ProcMap _base_segment; + std::string _filepath; + std::string _realpath; + bool _fixedBySoInfo; + bool _dsymbols_init; + std::unordered_map _dsymbolsMap; std::vector _segments; + std::vector _bssSegments; + KittyMemory::ProcMap _baseSegment; public: ElfScanner() - : _elfBase(0) - , _phdr(0) - , _loads(0) - , _loadBias(0) - , _loadSize(0) - , _bss(0) - , _bssSize(0) - , _dynamic(0) - , _stringTable(0) - , _symbolTable(0) - , _elfHashTable(0) - , _gnuHashTable(0) - , _strsz(0) - , _syment(0) + : _elfBase(0), _phdr(0), _loads(0), _loadBias(0), _loadSize(0), _dynamic(0), _stringTable(0), + _symbolTable(0), _elfHashTable(0), _gnuHashTable(0), _strsz(0), _syment(0), _fixedBySoInfo(false), + _dsymbols_init(false) { } - ElfScanner(uintptr_t elfBase, const std::vector &maps); - ElfScanner(uintptr_t elfBase) : ElfScanner(elfBase, KittyMemory::getAllMaps()) {} + ElfScanner(uintptr_t elfBase, const std::vector &maps = KittyMemory::getAllMaps()); + ElfScanner(const kitty_soinfo_t &soinfo, + const std::vector &maps = KittyMemory::getAllMaps()); - static inline ElfScanner createWithBase(uintptr_t elfBase) + inline void refresh() { - return ElfScanner(elfBase); + *this = ElfScanner(_elfBase); } - static inline ElfScanner createWithMap(const KittyMemory::ProcMap& map) + + inline bool isValid() const { - return ElfScanner(map.startAddress); + return _elfBase && _loadSize && _phdr && _loadBias; } - static inline ElfScanner createWithPath(const std::string &path) + + inline bool isFixedBySoInfo() const { - return ElfScanner(KittyMemory::getElfBaseMap(path).startAddress); + return _fixedBySoInfo; } - inline bool isValid() const + inline uintptr_t base() const + { + return _elfBase; + } + + inline uintptr_t end() const + { + return _elfBase + _loadSize; + } + + inline KT_ElfW(Ehdr) header() const + { + return _ehdr; + } + + inline uintptr_t phdr() const + { + return _phdr; + } + + inline std::vector programHeaders() const + { + return _phdrs; + } + + inline int loads() const + { + return _loads; + } + + inline uintptr_t loadBias() const { - return _loads && !_phdrs.empty() && _loadBias && _loadSize && !_dynamics.empty() && _stringTable && _symbolTable && _strsz && _syment; + return _loadBias; } - inline uintptr_t base() const { return _elfBase; } + inline uintptr_t loadSize() const + { + return _loadSize; + } - inline uintptr_t end() const { return _elfBase + _loadSize; } + inline uintptr_t dynamic() const + { + return _dynamic; + } - inline ElfW_(Ehdr) header() const { return _ehdr; } + inline std::vector dynamics() const + { + return _dynamics; + } - inline uintptr_t phdr() const { return _phdr; } + inline uintptr_t stringTable() const + { + return _stringTable; + } - inline std::vector programHeaders() const { return _phdrs; } + inline uintptr_t symbolTable() const + { + return _symbolTable; + } - inline int loads() const { return _loads; } + inline size_t stringTableSize() const + { + return _strsz; + } - inline uintptr_t loadBias() const { return _loadBias; } + inline size_t symbolEntrySize() const + { + return _syment; + } - inline uintptr_t loadSize() const { return _loadSize; } + inline uintptr_t elfHashTable() const + { + return _elfHashTable; + } - inline uintptr_t bss() const { return _bss; } + inline uintptr_t gnuHashTable() const + { + return _gnuHashTable; + } - inline size_t bssSize() const { return _bssSize; } + uintptr_t findSymbol(const std::string &symbolName) const; - inline uintptr_t dynamic() const { return _dynamic; } + // debug symbols from SHT_SYMTAB on disk + std::unordered_map dsymbols(); + uintptr_t findDebugSymbol(const std::string &symbolName); - inline std::vector dynamics() const { return _dynamics; } + KittyMemory::ProcMap baseSegment() const + { + return _baseSegment; + } - inline uintptr_t stringTable() const { return _stringTable; } + std::vector segments() const + { + return _segments; + } - inline uintptr_t symbolTable() const { return _symbolTable; } + std::vector bssSegments() const + { + return _bssSegments; + } + + inline std::string filePath() const + { + return _filepath; + } - inline uintptr_t elfHashTable() const { return _elfHashTable; } + inline std::string realPath() const + { + return _realpath; + } - inline uintptr_t gnuHashTable() const { return _gnuHashTable; } + inline bool isZipped() const + { + return _baseSegment.offset != 0; + } - inline size_t stringTableSize() const { return _strsz; } + inline bool isNative() const + { + int a = getProgramElf().header().e_machine, b = _ehdr.e_machine; + return a != 0 && b != 0 && a == b; + } - inline size_t symbolEntrySize() const { return _syment; } + inline bool isEmulated() const + { + int a = getProgramElf().header().e_machine, b = _ehdr.e_machine; + return a != 0 && b != 0 && a != b; + } /** - * lookup symbol name in current ELF - * @return absolute address of symbol + * search for string "name" references to find the JNINativeMethod array */ - uintptr_t findSymbol(const std::string& symbolName) const; + RegisterNativeFn findRegisterNativeFn(const std::string &name, const std::string &signature) const; - inline KittyMemory::ProcMap baseSegment() const { return _base_segment; } + // dump ELF to disk + bool dumpToDisk(const std::string &destination) const; - inline std::vector segments() const { return _segments; } + static ElfScanner &getProgramElf(); - inline std::string filePath() const { return _base_segment.pathname; } + /** + * Fetch all in-memory loaded ELFs + */ + static std::vector getAllELFs(EScanElfType type = EScanElfType::Any, + EScanElfFilter filter = EScanElfFilter::Any); + + /** + * Find in-memory loaded ELF with name + */ + static ElfScanner findElf(const std::string &path, EScanElfType type = EScanElfType::Any, + EScanElfFilter filter = EScanElfFilter::Any); - static std::vector getAllELFs(); - /** * lookup symbol name in all loaded ELFs * @return a vector of symbol absolute address and the ELF where the symbol was found in */ - static std::vector> findSymbolAll(const std::string& symbolName); + static std::vector> findSymbolAll(const std::string &symbolName, + EScanElfType type = EScanElfType::Any, + EScanElfFilter filter = EScanElfFilter::Any); + + static ElfScanner createWithBase(uintptr_t elfBase, + const std::vector &maps = KittyMemory::getAllMaps()) + { + return ElfScanner(elfBase, maps); + } + + static ElfScanner createWithMap(const KittyMemory::ProcMap &elfMap, + const std::vector &maps = KittyMemory::getAllMaps()) + { + return ElfScanner(elfMap.startAddress, maps); + } + + static ElfScanner createWithSoInfo(const kitty_soinfo_t &soinfo, + const std::vector &maps = KittyMemory::getAllMaps()) + { + return ElfScanner(soinfo, maps); + } + }; + + class LinkerScanner : public ElfScanner + { + protected: + struct + { + uintptr_t solist; + uintptr_t somain; + uintptr_t sonext; + } _linker_syms; + struct + { + uintptr_t base; + uintptr_t size; + uintptr_t phdr; + uintptr_t phnum; + uintptr_t dyn; + uintptr_t strtab; + uintptr_t symtab; + uintptr_t strsz; + uintptr_t bias; + uintptr_t next; + } _soinfo_offsets; + bool _init; + + bool init(); + + public: + LinkerScanner() : ElfScanner(), _init(false) + { + memset(&_linker_syms, 0, sizeof(_linker_syms)); + memset(&_soinfo_offsets, 0, sizeof(_soinfo_offsets)); + } + + LinkerScanner(uintptr_t linkerBase); + LinkerScanner(const ElfScanner &linkerElf); + + inline static LinkerScanner &Get() + { + static LinkerScanner linker{}; + if (!linker.isValid() || !linker.init()) + { + LinkerScanner tmp_linker{}; +#ifdef __LP64__ + tmp_linker = LinkerScanner( + ElfScanner::findElf("/bin/linker64", EScanElfType::Native, EScanElfFilter::System)); +#else + tmp_linker = LinkerScanner( + ElfScanner::findElf("/bin/linker", EScanElfType::Native, EScanElfFilter::System)); +#endif + if (tmp_linker.isValid() && tmp_linker.init()) + linker = tmp_linker; + } + return linker; + } + + inline ElfScanner *asELF() const + { + return (ElfScanner *)this; + } + + inline uintptr_t solist() const + { + if (!isValid() || !_linker_syms.solist) + return 0; + + return *(uintptr_t *)(_linker_syms.solist); + } + + inline uintptr_t somain() const + { + if (!isValid() || !_linker_syms.somain) + return 0; + + return *(uintptr_t *)(_linker_syms.somain); + } + + inline uintptr_t sonext() const + { + if (!isValid() || !_linker_syms.sonext) + return 0; + + return *(uintptr_t *)(_linker_syms.sonext); + } + + inline kitty_soinfo_t somainInfo() const + { + if (!isValid() || !_linker_syms.somain) + return {}; + + return infoFromSoInfo_(somain(), KittyMemory::getAllMaps()); + } + + inline kitty_soinfo_t sonextInfo() const + { + if (!isValid() || _linker_syms.sonext) + return {}; + + return infoFromSoInfo_(sonext(), KittyMemory::getAllMaps()); + } + + std::vector allSoInfo() const; + + kitty_soinfo_t findSoInfo(const std::string &name) const; + + private: + kitty_soinfo_t infoFromSoInfo_(uintptr_t si, const std::vector &maps) const; + }; + + enum KT_JNICallType + { + KT_JNICallTypeRegular = 1, + KT_JNICallTypeCriticalNative = 2, + }; + + struct nbItf_data_t + { + inline nbItf_data_t() + { + memset(this, 0, sizeof(nbItf_data_t)); + } + + int version; +#ifdef __LP64__ + uint32_t pad1; +#endif + bool (*initialize)(const void *runtime_cbs, const char *private_dir, const char *instruction_set); + void *(*loadLibrary)(const char *libpath, int flag); + void *(*getTrampoline)(void *handle, const char *name, const char *shorty, uint32_t len); + bool (*isSupported)(const char *libpath); + const void *(*getAppEnv)(const char *instruction_set); + bool (*isCompatibleWith)(uint32_t bridge_version); + void *(*getSignalHandler)(int signal); + int (*unloadLibrary)(void *handle); + const char *(*getError)(); + bool (*isPathSupported)(const char *library_path); + bool (*initAnonymousNamespace)(const char *public_ns_sonames, const char *anon_ns_library_path); + void *(*createNamespace)(const char *name, const char *ld_library_path, const char *default_library_path, + uint64_t type, const char *permitted_when_isolated_path, void *parent_ns); + bool (*linkNamespaces)(void *from, void *to, const char *shared_libs_sonames); + void *(*loadLibraryExt)(const char *libpath, int flag, void *ns); + void *(*getVendorNamespace)(); + void *(*getExportedNamespace)(const char *name); + void (*preZygoteFork)(); + void *(*getTrampolineWithJNICallType)(void *handle, const char *name, const char *shorty, uint32_t len, + enum KT_JNICallType jni_call_type); + void *(*getTrampolineForFunctionPointer)(const void *method, const char *shorty, uint32_t len, + enum KT_JNICallType jni_call_type); + bool (*isNativeBridgeFunctionPointer)(const void *method); + }; + + class NativeBridgeScanner + { + private: + ElfScanner _nbElf, _nbImplElf, _sodlElf; + uintptr_t _sodl; + struct + { + uintptr_t base; + uintptr_t size; + uintptr_t phdr; + uintptr_t phnum; + uintptr_t dyn; + uintptr_t strtab; + uintptr_t symtab; + uintptr_t strsz; + uintptr_t bias; + uintptr_t next; + } _soinfo_offsets; + bool _init; + bool _isHoudini; + + uintptr_t _nbItf; + size_t _nbItf_data_size; + nbItf_data_t _nbItf_data; + + public: + bool (*fnNativeBridgeInitialized)(); + + NativeBridgeScanner() + : _sodl(0), _init(false), _isHoudini(false), _nbItf(0), _nbItf_data_size(0), + fnNativeBridgeInitialized(nullptr) + { + memset(&_nbItf_data, 0, sizeof(_nbItf_data)); + memset(&_soinfo_offsets, 0, sizeof(_soinfo_offsets)); + } + + inline static NativeBridgeScanner &Get() + { + static NativeBridgeScanner nb{}; + ((void)nb.init()); + return nb; + } + + bool init(); + + inline bool isValid() const + { + return _init; + } + + inline uintptr_t sodl() const + { + return _sodl; + } + + inline kitty_soinfo_t sodlInfo() const + { + if (!_init || !_sodl) + return {}; + + return infoFromSoInfo_(_sodl, KittyMemory::getAllMaps()); + } + + std::vector allSoInfo() const; + + kitty_soinfo_t findSoInfo(const std::string &name) const; + + inline size_t nbItfDataSize() const + { + return _nbItf_data_size; + } + + inline nbItf_data_t nbItfData() const + { + return _nbItf_data; + } + + inline bool isHoudini() + { + return _isHoudini; + } + + private: + kitty_soinfo_t infoFromSoInfo_(uintptr_t si, const std::vector &maps) const; + }; + + class NativeBridgeLinker + { + public: + // native bride load library + static void *dlopen(const std::string &path, int flags); + // native bridge get trampoline + static void *dlsym(void *handle, const std::string &sym_name); + // native bridge dlerror + static const char *dlerror(); + // native bridge dlladdr + static bool dladdr(const void *addr, kitty_soinfo_t *info); + // native bridge dl_iterate_phdr + static void dl_iterate_phdr(const std::function &callback); }; #endif // __ANDROID__ -} \ No newline at end of file +} // namespace KittyScanner diff --git a/app/src/main/jni/KittyMemory/KittyUtils.cpp b/app/src/main/jni/KittyMemory/KittyUtils.cpp index 5a21a97..e8c15c5 100644 --- a/app/src/main/jni/KittyMemory/KittyUtils.cpp +++ b/app/src/main/jni/KittyMemory/KittyUtils.cpp @@ -1,6 +1,11 @@ #include "KittyUtils.hpp" -namespace KittyUtils { +#ifdef __ANDROID__ +#include +#endif + +namespace KittyUtils +{ #ifdef __ANDROID__ std::string getExternalStorage() @@ -9,14 +14,13 @@ namespace KittyUtils { return storage ? storage : "/sdcard"; } - //AIDE doesn't like them - /* int getAndroidVersion() + int getAndroidVersion() { static int ver = 0; if (ver > 0) return ver; - char buf[0xff] = { 0 }; + char buf[0xff] = {0}; if (__system_property_get("ro.build.version.release", buf)) ver = std::atoi(buf); @@ -29,12 +33,12 @@ namespace KittyUtils { if (sdk > 0) return sdk; - char buf[0xff] = { 0 }; + char buf[0xff] = {0}; if (__system_property_get("ro.build.version.sdk", buf)) sdk = std::atoi(buf); return sdk; - }*/ + } #endif std::string fileNameFromPath(const std::string &filePath) @@ -64,60 +68,64 @@ namespace KittyUtils { return ext; } - void String::Trim(std::string &str) + void String::Trim(std::string &str) { // https://www.techiedelight.com/remove-whitespaces-string-cpp/ str.erase(std::remove_if(str.begin(), str.end(), [](char c) - { return (c == ' ' || c == '\n' || c == '\r' || - c == '\t' || c == '\v' || c == '\f'); }), + { return (c == ' ' || c == '\n' || c == '\r' || + c == '\t' || c == '\v' || c == '\f'); }), str.end()); } - bool String::ValidateHex(std::string &hex) + bool String::ValidateHex(std::string &hex) { if (hex.empty()) return false; if (hex.compare(0, 2, "0x") == 0) hex.erase(0, 2); - Trim(hex); // first remove spaces - + Trim(hex); // first remove spaces + if (hex.length() < 2 || hex.length() % 2 != 0) return false; - for (size_t i = 0; i < hex.length(); i++) { - if (!std::isxdigit((unsigned char) hex[i])) + for (size_t i = 0; i < hex.length(); i++) + { + if (!std::isxdigit((unsigned char)hex[i])) return false; } - + return true; } std::string String::Fmt(const char *fmt, ...) { - if (!fmt) - return ""; + if (!fmt) + return ""; - va_list args; + va_list args; - va_start(args, fmt); - size_t size = vsnprintf(nullptr, 0, fmt, args) + 1; // extra space for '\0' - va_end(args); + va_start(args, fmt); + size_t size = vsnprintf(nullptr, 0, fmt, args) + 1; // extra space for '\0' + va_end(args); - std::vector buffer(size, '\0'); + std::vector buffer(size, '\0'); - va_start(args, fmt); - vsnprintf(&buffer[0], size, fmt, args); - va_end(args); + va_start(args, fmt); + vsnprintf(&buffer[0], size, fmt, args); + va_end(args); - return std::string(&buffer[0]); + return std::string(&buffer[0]); } std::string String::Random(size_t length) { static const std::string chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; - - thread_local static std::default_random_engine rnd(std::random_device{}()); - thread_local static std::uniform_int_distribution dist(0, chars.size()-1); + + static std::mutex mtx; + std::lock_guard lock(mtx); + + static std::default_random_engine rnd(std::random_device{}()); + static std::uniform_int_distribution dist(0, chars.size() - 1); std::string str(length, '\0'); for (size_t i = 0; i < length; ++i) @@ -132,9 +140,10 @@ namespace KittyUtils { Convert a block of data to a hex string */ std::string data2Hex( - const void *data, //!< Data to convert - const size_t dataLength //!< Length of the data to convert - ) { + const void *data, //!< Data to convert + const size_t dataLength //!< Length of the data to convert + ) + { const auto *byteData = reinterpret_cast(data); std::stringstream hexStringStream; @@ -148,15 +157,17 @@ namespace KittyUtils { Convert a hex string to a block of data */ void dataFromHex( - const std::string &in, //!< Input hex string - void *data //!< Data store - ) { + const std::string &in, //!< Input hex string + void *data //!< Data store + ) + { size_t length = in.length(); auto *byteData = reinterpret_cast(data); std::stringstream hexStringStream; hexStringStream >> std::hex; - for (size_t strIndex = 0, dataIndex = 0; strIndex < length; ++dataIndex) { + for (size_t strIndex = 0, dataIndex = 0; strIndex < length; ++dataIndex) + { // Read out and convert the string two characters at a time const char tmpStr[3] = {in[strIndex++], in[strIndex++], 0}; @@ -173,16 +184,20 @@ namespace KittyUtils { #ifdef __ANDROID__ -// refs to -// https://refspecs.linuxfoundation.org/elf/elf.pdf -// https://flapenguin.me/elf-dt-hash -// https://flapenguin.me/elf-dt-gnu-hash + // refs to + // https://refspecs.linuxfoundation.org/elf/elf.pdf + // https://flapenguin.me/elf-dt-hash + // https://flapenguin.me/elf-dt-gnu-hash - namespace Elf { - namespace ElfHash { - uint32_t HashSymName(const char *name) { + namespace Elf + { + namespace ElfHash + { + uint32_t HashSymName(const char *name) + { uint32_t h = 0, g; - for (; *name; name++) { + for (; *name; name++) + { h = (h << 4) + *name; g = h & 0xf0000000; if (g) @@ -192,13 +207,13 @@ namespace KittyUtils { return h; } - const ElfW_(Sym) *LookupByName(uintptr_t elfhash, - uintptr_t symtab, - uintptr_t strtab, - size_t syment, - size_t strsz, - const char *symbol_name) { - + const KT_ElfW(Sym) * LookupByName(uintptr_t elfhash, + uintptr_t symtab, + uintptr_t strtab, + size_t syment, + size_t strsz, + const char *symbol_name) + { const auto *elf_hash = reinterpret_cast(elfhash); const auto *symbol_table = reinterpret_cast(symtab); const auto *string_table = reinterpret_cast(strtab); @@ -215,8 +230,9 @@ namespace KittyUtils { const uint32_t *chain = bucket + num_bucket; const uint32_t name_hash = HashSymName(symbol_name); - for (uint32_t i = bucket[name_hash % num_bucket]; i != 0 && i < num_chain; i = chain[i]) { - const auto *symbol = reinterpret_cast(symbol_table + (syment * i)); + for (uint32_t i = bucket[name_hash % num_bucket]; i != 0 && i < num_chain; i = chain[i]) + { + const auto *symbol = reinterpret_cast(symbol_table + (syment * i)); if (!symbol || symbol->st_name >= strsz) break; @@ -227,25 +243,28 @@ namespace KittyUtils { return nullptr; } - } - } + } // namespace ElfHash + } // namespace Elf - namespace Elf { - namespace GnuHash { - uint32_t HashSymName(const char *name) { + namespace Elf + { + namespace GnuHash + { + uint32_t HashSymName(const char *name) + { uint32_t h = 5381; for (; *name; name++) h = (h << 5) + h + *name; return h; } - const ElfW_(Sym) *LookupByName(uintptr_t gnuhash, - uintptr_t symtab, - uintptr_t strtab, - size_t syment, - size_t strsz, - const char *symbol_name) { - + const KT_ElfW(Sym) * LookupByName(uintptr_t gnuhash, + uintptr_t symtab, + uintptr_t strtab, + size_t syment, + size_t strsz, + const char *symbol_name) + { const auto *gnu_hash = reinterpret_cast(gnuhash); const auto *symbol_table = reinterpret_cast(symtab); const auto *string_table = reinterpret_cast(strtab); @@ -268,10 +287,8 @@ namespace KittyUtils { const auto *buckets = reinterpret_cast(&bloom[bloom_size]); const uint32_t *chain = &buckets[num_buckets]; - uintptr_t word = bloom[(name_hash / ELFCLASS_BITS_) % bloom_size]; - uintptr_t mask = 0 - | (uintptr_t) 1 << (name_hash % ELFCLASS_BITS_) - | (uintptr_t) 1 << ((name_hash >> bloom_shift) % ELFCLASS_BITS_); + uintptr_t word = bloom[(name_hash / KT_ELFCLASS_BITS) % bloom_size]; + uintptr_t mask = 0 | (uintptr_t)1 << (name_hash % KT_ELFCLASS_BITS) | (uintptr_t)1 << ((name_hash >> bloom_shift) % KT_ELFCLASS_BITS); // If at least one bit is not set, a symbol is surely missing. if ((word & mask) != mask) @@ -282,13 +299,15 @@ namespace KittyUtils { return nullptr; // Loop through the chain. - while (true) { - const auto *symbol = reinterpret_cast(symbol_table + (syment * sym_idx)); + while (true) + { + const auto *symbol = reinterpret_cast(symbol_table + (syment * sym_idx)); if (!symbol || symbol->st_name >= strsz) break; const uint32_t hash = chain[sym_idx - sym_offset]; - if ((name_hash | 1) == (hash | 1)) { + if ((name_hash | 1) == (hash | 1)) + { std::string sym_str = std::string(string_table + symbol->st_name); if (!sym_str.empty() && sym_str == symbol_name) return symbol; @@ -303,9 +322,217 @@ namespace KittyUtils { return nullptr; } + } // namespace GnuHash + } // namespace Elf + + namespace Zip + { + bool GetCentralDirInfo(int fd, uint64_t fileSize, bool &isZip64, uint64_t &cdOffset, uint64_t &totalEntries) + { + if (fileSize < 22) + { + KITTY_LOGD("File too small: %" PRIx64 " bytes", fileSize); + return false; + } + + void *map = mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0); + if (map == MAP_FAILED) + { + KITTY_LOGD("mmap failed: %s", strerror(errno)); + return false; + } + + uint8_t *data = static_cast(map); + + // Find EOCD or ZIP64 EOCD locator + int64_t offset = fileSize - 22; + uint32_t sig; + while (offset >= 0) + { + sig = *reinterpret_cast(data + offset); + if (sig == KT_EOCD_SIGNATURE || (sig == KT_ZIP64_EOCD_LOCATOR && *reinterpret_cast(data + offset - 20) == KT_ZIP64_EOCD_SIGNATURE)) + { + break; + } + --offset; + } + + if (offset < 0) + { + KITTY_LOGD("EOCD not found"); + munmap(map, fileSize); + return false; + } + + // Read EOCD or ZIP64 EOCD + isZip64 = (sig == KT_ZIP64_EOCD_LOCATOR); + if (isZip64) + { + totalEntries = *reinterpret_cast(data + offset - 20 + 12); + cdOffset = *reinterpret_cast(data + offset - 20 + 36); + } + else + { + totalEntries = *reinterpret_cast(data + offset + 8); + cdOffset = *reinterpret_cast(data + offset + 16); + } + + munmap(map, fileSize); + return true; } - } -#endif // __ANDROID__ + std::vector listFilesInZip(const std::string &zipPath) + { + std::vector files; + int fd = KT_EINTR_RETRY(open(zipPath.c_str(), O_RDONLY)); + if (fd < 0) + { + KITTY_LOGD("open failed: %s, error: %s", zipPath.c_str(), strerror(errno)); + return files; + } + + // Get file size + struct stat st = {}; + if (fstat(fd, &st) < 0) + { + KITTY_LOGD("fstat failed: %s", strerror(errno)); + close(fd); + return files; + } + uint64_t fileSize = st.st_size; + + // Get central directory info + bool isZip64; + uint64_t cdOffset, totalEntries; + if (!GetCentralDirInfo(fd, fileSize, isZip64, cdOffset, totalEntries)) + { + close(fd); + return files; + } + + // Map file for parsing + void *map = mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0); + if (map == MAP_FAILED) + { + KITTY_LOGD("mmap failed: %s", strerror(errno)); + close(fd); + return files; + } + uint8_t *data = static_cast(map); + + // Parse central directory + for (uint64_t offset = cdOffset, i = 0; i < totalEntries; ++i) + { + if (*reinterpret_cast(data + offset) != KT_CENTRAL_DIR_SIGNATURE) + { + KITTY_LOGD("Invalid central directory signature at entry %" PRIu64, i); + break; + } + + ZipFileInfo info; + info.compressionMethod = *reinterpret_cast(data + offset + 10); + info.modTime = *reinterpret_cast(data + offset + 12); + info.modDate = *reinterpret_cast(data + offset + 14); + info.crc32 = *reinterpret_cast(data + offset + 16); + uint32_t compSize32 = *reinterpret_cast(data + offset + 20); + uint32_t uncompSize32 = *reinterpret_cast(data + offset + 24); + info.compressedSize = compSize32; + info.uncompressedSize = uncompSize32; + uint16_t nameLen = *reinterpret_cast(data + offset + 28); + uint16_t extraLen = *reinterpret_cast(data + offset + 30); + uint16_t commentLen = *reinterpret_cast(data + offset + 32); + + // Read file name + if (nameLen <= KT_MAX_NAME_LEN) + { + info.fileName.assign(reinterpret_cast(data + offset + 46), nameLen); + + // Get local header offset + uint64_t localOffset = isZip64 && compSize32 == 0xFFFFFFFF ? *reinterpret_cast(data + offset + 46 + nameLen + (extraLen >= 24 ? 20 : extraLen)) : *reinterpret_cast(data + offset + 42); + + // Update sizes for ZIP64 + if (isZip64 && compSize32 == 0xFFFFFFFF) + { + for (uint16_t j = 0; j < extraLen;) + { + uint16_t id = *reinterpret_cast(data + offset + 46 + nameLen + j); + uint16_t size = *reinterpret_cast(data + offset + 46 + nameLen + j + 2); + if (id == KT_ZIP64_EXTRA_ID && size >= 16) + { + info.uncompressedSize = *reinterpret_cast(data + offset + 46 + nameLen + j + 4); + info.compressedSize = *reinterpret_cast(data + offset + 46 + nameLen + j + 12); + break; + } + j += 4 + size; + } + } + + // Calculate data offset + uint16_t localNameLen = *reinterpret_cast(data + localOffset + 26); + uint16_t localExtraLen = *reinterpret_cast(data + localOffset + 28); + info.dataOffset = localOffset + 30 + localNameLen + localExtraLen; + + files.push_back(info); + } + + offset += 46 + nameLen + extraLen + commentLen; + } + + munmap(map, fileSize); + close(fd); + return files; + } + + ZipFileInfo GetFileInfoByDataOffset(const std::string &zipPath, uint64_t dataOffset) + { + ZipFileInfo info{}; + + const auto files = listFilesInZip(zipPath); + for (const auto &it : files) + { + if (it.dataOffset == dataOffset) + { + info = it; + break; + } + } + + return info; + } + + ZipFileMMap MMapFileByDataOffset(const std::string &zipPath, uint64_t dataOffset) + { + ZipFileMMap result; + int fd = KT_EINTR_RETRY(open(zipPath.c_str(), O_RDONLY)); + if (fd < 0) + { + KITTY_LOGD("open failed: %s, error: %s", zipPath.c_str(), strerror(errno)); + return result; + } + + // Get file info to obtain compressed size + ZipFileInfo info = GetFileInfoByDataOffset(zipPath, dataOffset); + if (info.fileName.empty()) + { + KITTY_LOGD("No file found at offset %" PRIx64, dataOffset); + close(fd); + return result; + } + + // mmap the data + result.size = info.compressedSize; + result.data = mmap(nullptr, result.size, PROT_READ, MAP_PRIVATE, fd, dataOffset); + if (result.data == MAP_FAILED) + { + KITTY_LOGD("mmap failed at offset %" PRIx64 ": %s", dataOffset, strerror(errno)); + result.size = 0; + } + + close(fd); + return result; + } + } // namespace Zip + +#endif // __ANDROID__ -} \ No newline at end of file +} // namespace KittyUtils diff --git a/app/src/main/jni/KittyMemory/KittyUtils.hpp b/app/src/main/jni/KittyMemory/KittyUtils.hpp index d8266ff..8ac79de 100644 --- a/app/src/main/jni/KittyMemory/KittyUtils.hpp +++ b/app/src/main/jni/KittyMemory/KittyUtils.hpp @@ -9,25 +9,96 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include + +#define KT_PAGE_SIZE (sysconf(_SC_PAGE_SIZE)) + +#define KT_PAGE_START(x) (uintptr_t(x) & ~(KT_PAGE_SIZE - 1)) +#define KT_PAGE_END(x) (KT_PAGE_START(uintptr_t(x) + KT_PAGE_SIZE - 1)) +#define KT_PAGE_OFFSET(x) (uintptr_t(x) - KT_PAGE_START(x)) +#define KT_PAGE_LEN(x) (size_t(KT_PAGE_SIZE - KT_PAGE_OFFSET(x))) + +#define KT_PAGE_END2(x, len) (KT_PAGE_START((uintptr_t(x) + len) + KT_PAGE_SIZE - 1)) +#define KT_PAGE_LEN2(x, len) (KT_PAGE_END2(x, len) - KT_PAGE_START(x)) + +#define KT_PROT_RWX (PROT_READ | PROT_WRITE | PROT_EXEC) +#define KT_PROT_RX (PROT_READ | PROT_EXEC) +#define KT_PROT_RW (PROT_READ | PROT_WRITE) + +#define KITTY_LOG_TAG "KittyMemory" + +#ifdef __ANDROID__ +#include + +#ifdef kITTYMEMORY_DEBUG +#define KITTY_LOGD(fmt, ...) ((void)__android_log_print(ANDROID_LOG_DEBUG, KITTY_LOG_TAG, fmt, ##__VA_ARGS__)) +#else +#define KITTY_LOGD(fmt, ...) \ + do \ + { \ + } while (0) +#endif + +#define KITTY_LOGI(fmt, ...) ((void)__android_log_print(ANDROID_LOG_INFO, KITTY_LOG_TAG, fmt, ##__VA_ARGS__)) +#define KITTY_LOGE(fmt, ...) ((void)__android_log_print(ANDROID_LOG_ERROR, KITTY_LOG_TAG, fmt, ##__VA_ARGS__)) + +#elif __APPLE__ +#include + +#ifdef kITTYMEMORY_DEBUG +#define KITTY_LOGD(fmt, ...) os_log(OS_LOG_DEFAULT, "D " KITTY_LOG_TAG ": " fmt, ##__VA_ARGS__) +#else +#define KITTY_LOGD(fmt, ...) \ + do \ + { \ + } while (0) +#endif + +#define KITTY_LOGI(fmt, ...) os_log(OS_LOG_DEFAULT, "I " KITTY_LOG_TAG ": " fmt, ##__VA_ARGS__) +#define KITTY_LOGE(fmt, ...) os_log_error(OS_LOG_DEFAULT, "E " KITTY_LOG_TAG ": " fmt, ##__VA_ARGS__) + +#endif + +#define KT_EINTR_RETRY(exp) \ + ({ \ + __typeof__(exp) _rc; \ + do \ + { \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; \ + }) #ifdef __ANDROID__ #include #ifdef __LP64__ -#define ELFCLASS_BITS_ 64 -#define ELF_EICLASS_ 2 -#define ElfW_(x) Elf64_##x -#define ELFW_(x) ELF64_##x +#define KT_ELFCLASS_BITS 64 +#define KT_ELF_EICLASS 2 +#define KT_ElfW(x) Elf64_##x +#define KT_ELFW(x) ELF64_##x #else -#define ELFCLASS_BITS_ 32 -#define ELF_EICLASS_ 1 -#define ElfW_(x) Elf32_##x -#define ELFW_(x) ELF32_##x +#define KT_ELFCLASS_BITS 32 +#define KT_ELF_EICLASS 1 +#define KT_ElfW(x) Elf32_##x +#define KT_ELFW(x) ELF32_##x #endif +#define KT_ELF_ST_BIND(val) (((unsigned char)(val)) >> 4) +#define KT_ELF_ST_TYPE(val) ((val) & 0xf) +#define KT_ELF_ST_INFO(bind, type) (((bind) << 4) + ((type) & 0xf)) +#define KT_ELF_ST_VISIBILITY(o) ((o) & 0x03) #endif // __ANDROID__ -namespace KittyUtils { +namespace KittyUtils +{ #ifdef __ANDROID__ std::string getExternalStorage(); @@ -45,17 +116,17 @@ namespace KittyUtils { { return str.length() >= str2.length() && str.compare(0, str2.length(), str2) == 0; } - + static inline bool Contains(const std::string &str, const std::string &str2) { return str.length() >= str2.length() && str.find(str2) != std::string::npos; } - + static inline bool EndsWith(const std::string &str, const std::string &str2) { return str.length() >= str2.length() && str.compare(str.length() - str2.length(), str2.length(), str2) == 0; } - + void Trim(std::string &str); bool ValidateHex(std::string &hex); @@ -70,32 +141,36 @@ namespace KittyUtils { { using param_type = typename std::uniform_int_distribution::param_type; - thread_local static std::mt19937 gen{std::random_device{}()}; - thread_local static std::uniform_int_distribution dist; + static std::mutex mtx; + std::lock_guard lock(mtx); + + static std::mt19937 gen{std::random_device{}()}; + static std::uniform_int_distribution dist; return dist(gen, param_type{min, max}); } - template std::string data2Hex(const T &data) + template + std::string data2Hex(const T &data) { - const auto *byteData = reinterpret_cast(&data); - std::stringstream hexStringStream; - - hexStringStream << std::hex << std::setfill('0'); - for (size_t index = 0; index < sizeof(T); ++index) - hexStringStream << std::setw(2) << static_cast(byteData[index]); - - return hexStringStream.str(); + const auto *byteData = reinterpret_cast(&data); + std::stringstream hexStringStream; + + hexStringStream << std::hex << std::setfill('0'); + for (size_t index = 0; index < sizeof(T); ++index) + hexStringStream << std::setw(2) << static_cast(byteData[index]); + + return hexStringStream.str(); } std::string data2Hex(const void *data, const size_t dataLength); void dataFromHex(const std::string &in, void *data); - template + template std::string HexDump(const void *address, size_t len) { if (!address || len == 0 || rowSize == 0) - return ""; + return ""; const unsigned char *data = static_cast(address); @@ -139,11 +214,13 @@ namespace KittyUtils { #ifdef __ANDROID__ - namespace Elf { - namespace ElfHash { + namespace Elf + { + namespace ElfHash + { /** * Lookup symbol by name in hash table - * + * * @elfhash: DT_HASH hash table address * @symtab: DT_SYMTAB symbol table address * @strtab: DT_STRTAB string table address @@ -152,18 +229,15 @@ namespace KittyUtils { * * @return ElfSym pointer */ - const ElfW_(Sym) *LookupByName(uintptr_t elfhash, - uintptr_t symtab, - uintptr_t strtab, - size_t syment, - size_t strsz, - const char *symbol_name); - } + const KT_ElfW(Sym) * LookupByName(uintptr_t elfhash, uintptr_t symtab, uintptr_t strtab, size_t syment, + size_t strsz, const char *symbol_name); + } // namespace ElfHash - namespace GnuHash { + namespace GnuHash + { /** * Lookup symbol by name in gnu hash table - * + * * @elfhash: DT_GNU_HASH gnu hash table address * @symtab: DT_SYMTAB symbol table address * @strtab: DT_STRTAB string table address @@ -172,15 +246,49 @@ namespace KittyUtils { * * @return ElfSym pointer */ - const ElfW_(Sym) *LookupByName(uintptr_t gnuhash, - uintptr_t symtab, - uintptr_t strtab, - size_t syment, - size_t strsz, - const char *symbol_name); - } - } + const KT_ElfW(Sym) * LookupByName(uintptr_t gnuhash, uintptr_t symtab, uintptr_t strtab, size_t syment, + size_t strsz, const char *symbol_name); + } // namespace GnuHash + } // namespace Elf -#endif // __ANDROID__ + namespace Zip + { +#define KT_EOCD_SIGNATURE 0x06054b50 +#define KT_ZIP64_EOCD_SIGNATURE 0x06064b50 +#define KT_ZIP64_EOCD_LOCATOR 0x07064b50 +#define KT_CENTRAL_DIR_SIGNATURE 0x02014b50 +#define KT_LOCAL_HEADER_SIGNATURE 0x04034b50 +#define KT_ZIP64_EXTRA_ID 0x0001 +#define KT_MAX_NAME_LEN 65535 // ZIP max file name length + + struct ZipFileInfo + { + std::string fileName; + uint64_t compressedSize = 0; + uint64_t uncompressedSize = 0; + uint16_t compressionMethod = 0; + uint32_t crc32 = 0; + uint16_t modTime = 0; + uint16_t modDate = 0; + uint64_t dataOffset = 0; + }; + + struct ZipFileMMap + { + void *data = nullptr; + size_t size = 0; + + ZipFileMMap() = default; + ZipFileMMap(void *data, size_t size) : data(data), size(size) {} + }; + + bool GetCentralDirInfo(int fd, uint64_t fileSize, bool &isZip64, uint64_t &cdOffset, uint64_t &totalEntries); + + std::vector listFilesInZip(const std::string &zipPath); -} \ No newline at end of file + ZipFileInfo GetFileInfoByDataOffset(const std::string &zipPath, uint64_t dataOffset); + ZipFileMMap MMapFileByDataOffset(const std::string &zipPath, uint64_t dataOffset); + } // namespace Zip + +#endif // __ANDROID__ +} // namespace KittyUtils diff --git a/app/src/main/jni/Main.cpp b/app/src/main/jni/Main.cpp index 23d8696..e7ceca9 100644 --- a/app/src/main/jni/Main.cpp +++ b/app/src/main/jni/Main.cpp @@ -16,21 +16,10 @@ #include "Menu/Menu.hpp" #include "Menu/Jni.hpp" #include "Includes/Macros.h" +#include "dobby.h" -// Dobby is a very powerful hook framework that including hook, stub, patch, and symbol resolve. -// It can completely replace And64InlineHook and KittyMemory, so they are deprecated. -#include "dobby.h" // https://github.com/jmpews/Dobby - -bool noDeath; int scoreMul = 1, coinsMul = 1; -struct MemPatches { - // let's assume we have patches for these functions for whatever game - // boolean get_canShoot() function - MemoryPatch noDeath; - // etc... -} gPatches; - // Do not change or translate the first text unless you know what you are doing // Assigning feature numbers is optional. Without it, it will automatically count for you, starting from 0 // Assigned feature numbers can be like any numbers 1,3,200,10... instead in order 0,1,2,3,4,5... @@ -102,17 +91,56 @@ jobjectArray GetFeatureList(JNIEnv *env, jobject context) { bool btnPressed = false; +//Target main lib here +#define targetLibName OBFUSCATE("libil2cpp.so") + void Changes(JNIEnv *env, jclass clazz, jobject obj, jint featNum, jstring featName, jint value, jlong Lvalue, jboolean boolean, jstring text) { switch (featNum) { case 0: - { - if (boolean) - gPatches.noDeath.Modify(); - else - gPatches.noDeath.Restore(); + // offset, hex + PATCH_SWITCH(targetLibName, "0x1079728", "C0 03 5F D6", boolean); + // The patch switch has been returned and reworked... so it should work stably + // if you encounter any problems: + // - uncommiting logging for detailed checking of your functions; + // - special attention to the preferences -> this is the only source of this problem in the past that I have noticed: + // -- try rename the preferences file; + // -- for a stable and more flexible save settings, recommend using own system with XML/JSON files. + + // alt possibles usage variants: + // symbol, hex + PATCH_SWITCH(targetLibName, "_example__sym", "C0 03 5F D6", boolean); + // offset, asm + PATCH_SWITCH(targetLibName, "0x1079728", "ret", boolean); + // symbol, asm + PATCH_SWITCH(targetLibName, OBFUSCATE("_example__sym"), OBFUSCATE("ret"), boolean); + + // asm allows you to avoid using hex code, as it is generated automatically from the instructions. + // - this is the awesome option if you know what you're doing + // - this is probably especially useful with creating dynamic deep patches + + // recommended insert ';' to separate statements, example: "mov x0, #1; ret" + // recommended to test your instructions on https://armconverter.com or + // https://shell-storm.org/online/Online-Assembler-and-Disassembler/ + break; + case 4: + if(boolean) { + // offset, hex + PATCH(targetLibName, OBFUSCATE("0x10709AC"), OBFUSCATE("E05F40B2 C0035FD6")); + + // alt possibles usage variants: + // symbol, hex + PATCH(targetLibName, OBFUSCATE("_example__sym"), OBFUSCATE("E0 5F 40 B2 C0 03 5F D6")); + // offset, asm + PATCH(targetLibName, OBFUSCATE("0x10709AC"), OBFUSCATE("mov x0, #0xffffff; ret")); + // symbol, asm + PATCH(targetLibName, OBFUSCATE("_example__sym"), OBFUSCATE("mov x0, #0xffffff; ret")); + } else { + RESTORE(targetLibName, OBFUSCATE("0x10709AC")); + // or + RESTORE(targetLibName, OBFUSCATE("_example__sym")); + } break; - } case 1: btnPressed = true; break; @@ -122,6 +150,8 @@ void Changes(JNIEnv *env, jclass clazz, jobject obj, jint featNum, jstring featN case 3: coinsMul = value; break; + default: + break; } } @@ -129,7 +159,6 @@ void Changes(JNIEnv *env, jclass clazz, jobject obj, jint featNum, jstring featN void (*StartInvcibility)(void *instance, float duration); void (*old_Update)(void *instance); - void Update(void *instance) { if (instance != nullptr) { if (btnPressed) { @@ -140,11 +169,20 @@ void Update(void *instance) { return old_Update(instance); } -// This pattern of orig_xxx and hook_xxx can be completely replaced by macro `install_hook_name` from dobby.h. -// You can modify it if you want. -void (*old_AddScore)(void *instance, int score); -void AddScore(void *instance, int score) { +/* + void (*old_AddScore)(void *instance, int score); + void AddScore(void *instance, int score) { + //default any actions return old_AddScore(instance, score * scoreMul); + } +*/ +// === This function was completely replaced with super-macro `install_hook_name` from dobby.h === +// (base name, return type, ... args) +install_hook_name(AddScore, void *, void *instance, int score) { + // default any actions + + // use orig_ for call original function + return orig_AddScore(instance, score + scoreMul); } void (*old_AddCoins)(void *instance, int count); @@ -152,59 +190,44 @@ void AddCoins(void *instance, int count) { return old_AddCoins(instance, count * coinsMul); } -//Target lib here -#define targetLibName OBFUSCATE("libil2cpp.so") - -ElfScanner g_il2cppELF; // we will run our hacks in a new thread so our while loop doesn't block process main thread void hack_thread() { - LOGI(OBFUSCATE("pthread created")); - // This loop should be always enabled in unity game // because libil2cpp.so is not loaded into memory immediately. while (!isLibraryLoaded(targetLibName)) { sleep(1); // Wait for target lib be loaded. } - // ElfScanner::createWithPath can actually be replaced by xdl_open() and xdl_info(), - // but that's from https://github.com/hexhacking/xDL. - // You can compile it if you want. - do { - sleep(1); - // getElfBaseMap can also find lib base even if it was loaded from zipped base.apk - g_il2cppELF = ElfScanner::createWithPath(targetLibName); - } while (!g_il2cppELF.isValid()); - - LOGI(OBFUSCATE("%s has been loaded"), (const char *) targetLibName); - // In Android Studio, to switch between arm64-v8a and armeabi-v7a syntax highlighting, // You can modify the "Active ABI" in "Build Variants" to switch to another architecture for parsing. #if defined(__aarch64__) - uintptr_t il2cppBase = g_il2cppELF.base(); - //Il2Cpp: Use RVA offset - StartInvcibility = (void (*)(void *, float)) getAbsoluteAddress(targetLibName, str2Offset( - OBFUSCATE("0x107A3BC"))); + StartInvcibility = (void (*)(void *, float)) getAbsoluteAddress(targetLibName, OBFUSCATE("0x107A3BC")); + // StartInvcibility = (void (*)(void *, float)) getAbsoluteAddress(targetLibName, "_characterPlayer_Update")); - HOOK(targetLibName, str2Offset(OBFUSCATE("0x107A2E0")), AddScore, old_AddScore); - HOOK(targetLibName, str2Offset(OBFUSCATE("0x107A2FC")), AddCoins, old_AddCoins); - HOOK(targetLibName, str2Offset(OBFUSCATE("0x1078C44")), Update, old_Update); + HOOK(targetLibName, OBFUSCATE("0x107A2FC"), AddCoins, old_AddCoins); - // This function can completely replace MemoryPatch::createWithHex: - // int DobbyCodePatch(void *address, uint8_t *buffer, uint32_t buffer_size); (from dobby.h) - // And it is more powerful and intuitive. - gPatches.noDeath = MemoryPatch::createWithHex(il2cppBase + str2Offset(OBFUSCATE("0x1079728")), "C0 03 5F D6"); + // HOOK(targetLibName, OBFUSCATE("0x107A2E0"), AddScore, old_AddScore); + // === This function was completely replaced with super-macro `install_hook_name` from dobby.h === + // don't forget set address for hook super-macro: + install_hook_AddScore(getAbsoluteAddress(targetLibName,OBFUSCATE("0x107A2E0"))); - //HOOK(targetLibName, str2Offset(OBFUSCATE("0x1079728")), Kill, old_Kill); + HOOK(targetLibName, OBFUSCATE("0x1078C44"), Update, old_Update); + //HOOK(targetLibName, OBFUSCATE("0x1079728"), Kill, old_Kill); + //HOOK(targetLibName, OBFUSCATE("_example__sym"), Kill, old_Kill); + //HOOK_NO_ORIG("libFileC.so", OBFUSCATE("0x123456"), FunctionExample); + //HOOK_NO_ORIG("libFileC.so", OBFUSCATE("_example__sym"), FunctionExample); - //PATCH(targetLibName, str2Offset("0x10709AC"), "E05F40B2 C0035FD6"); - //HOOK(OBFUSCATE("libFileB.so"), str2Offset(OBFUSCATE("0x123456")), FunctionExample, old_FunctionExample); - //HOOK("libFileB.so", 4646464, FunctionExample, old_FunctionExample); - //HOOK_NO_ORIG("libFileC.so", str2Offset("0x123456"), FunctionExample); - //HOOKSYM("libFileB.so", "__SymbolNameExample", FunctionExample, old_FunctionExample); - //HOOKSYM_NO_ORIG("libFileB.so", "__SymbolNameExample", FunctionExample); + //PATCH(targetLibName, OBFUSCATE("0x10709AC"), "E05F40B2C0035FD6"); + // you can use this for things as detect log, counting function calls, executing side code before the function is executed + // now instrument wrapper implemented for detecting execution + INST(targetLibName, OBFUSCATE("0x23558C"), OBFUSCATE("AnyNameForDetect")); + INST(targetLibName, OBFUSCATE("0x235630"), OBFUSCATE("AnyNameForDetect2")); + INST(targetLibName, OBFUSCATE("_example__sym"), OBFUSCATE("AnyNameForDetect3")); + + // LOGI("Test SYM: 0x%llx", (uintptr_t)getAbsoluteAddress(targetLibName, "il2cpp_init")); #elif defined(__arm__) //Put your code here if you want the code to be compiled for armv7 only #endif @@ -218,10 +241,6 @@ void hack_thread() { __attribute__((constructor)) void lib_main() { // Create a new thread so it does not block the main thread, means the game would not freeze - // pthread_t ptid; - // pthread_create(&ptid, NULL, hack_thread, NULL); - // In modern C++, you should use std::thread(yourFunction).detach() instead of pthread_create - // because it is cross-platform and more intuitive. std::thread(hack_thread).detach(); } \ No newline at end of file diff --git a/app/src/main/jni/Menu/Jni.cpp b/app/src/main/jni/Menu/Jni.cpp index 5588967..c5feeb7 100644 --- a/app/src/main/jni/Menu/Jni.cpp +++ b/app/src/main/jni/Menu/Jni.cpp @@ -1,4 +1,3 @@ - #include "Jni.hpp" #include #include @@ -12,7 +11,6 @@ #include #include #include "Includes/obfuscate.h" -#include "Includes/get_device_api_level_inlines.h" #include "Menu/Jni.hpp" #include "Includes/Logger.h" @@ -68,6 +66,12 @@ void *exit_thread(void *) { exit(0); } +int get_api_sdk(JNIEnv* env) { + jclass build_version_class = env->FindClass(OBFUSCATE("android/os/Build$VERSION")); + jfieldID sdk_int_field = env->GetStaticFieldID(build_version_class, OBFUSCATE("SDK_INT"), OBFUSCATE("I")); + return env->GetStaticIntField(build_version_class, sdk_int_field); +} + void startActivityPermisson(JNIEnv *env, jobject ctx){ jclass native_context = env->GetObjectClass(ctx); jmethodID startActivity = env->GetMethodID(native_context, OBFUSCATE("startActivity"),OBFUSCATE("(Landroid/content/Intent;)V")); @@ -93,15 +97,14 @@ void startActivityPermisson(JNIEnv *env, jobject ctx){ env->CallVoidMethod(ctx, startActivity, intent); } - //Needed jclass parameter because this is a static java method void CheckOverlayPermission(JNIEnv *env, jclass thiz, jobject ctx){ //If overlay permission option is greyed out, make sure to add android.permission.SYSTEM_ALERT_WINDOW in manifest LOGI(OBFUSCATE("Check overlay permission")); - int sdkVer = api_level(); - if (sdkVer >= 23){ //Android 6.0 + int sdkVer = get_api_sdk(env); + if (sdkVer >= 23) { //Android 6.0 jclass Settings = env->FindClass(OBFUSCATE("android/provider/Settings")); jmethodID canDraw =env->GetStaticMethodID(Settings, OBFUSCATE("canDrawOverlays"), OBFUSCATE("(Landroid/content/Context;)Z")); if (!env->CallStaticBooleanMethod(Settings, canDraw, ctx)){ @@ -115,9 +118,6 @@ void CheckOverlayPermission(JNIEnv *env, jclass thiz, jobject ctx){ } } - LOGI(OBFUSCATE("Start service")); - - //StartMod Normal startService(env, ctx); } \ No newline at end of file diff --git a/app/src/main/jni/Menu/Jni.hpp b/app/src/main/jni/Menu/Jni.hpp index 2429b54..cf90b1c 100644 --- a/app/src/main/jni/Menu/Jni.hpp +++ b/app/src/main/jni/Menu/Jni.hpp @@ -15,6 +15,8 @@ void Toast(JNIEnv *env, jobject thiz, const char *text, int length); void startService(JNIEnv *env, jobject ctx); +int get_api_sdk(JNIEnv *env); + void CheckOverlayPermission(JNIEnv *env, jclass thiz, jobject ctx); -#endif //ANDROID_MOD_MENU_JNI_HPP +#endif //ANDROID_MOD_MENU_JNI_HPP \ No newline at end of file diff --git a/app/src/main/jni/Menu/Menu.cpp b/app/src/main/jni/Menu/Menu.cpp index 56abafc..0ed06f6 100644 --- a/app/src/main/jni/Menu/Menu.cpp +++ b/app/src/main/jni/Menu/Menu.cpp @@ -1,11 +1,22 @@ - #include "Includes/obfuscate.h" #include "Menu/Menu.hpp" -bool iconValid = false; -bool settingsValid = false; -bool featuresValid = false; -bool initValid = false; +void Init(JNIEnv *env, jobject thiz, jobject ctx, jobject title, jobject subtitle) { + //Set sub title + setText(env, title, OBFUSCATE("Modded by (yourname)")); + + //Set sub title + setText(env, subtitle, OBFUSCATE("

" + "

Modded by LGL

| " + "https://github.com/LGLTeam | Lorem Ipsum is simply dummy text of the printing and typesetting

" + "
")); + + //Dialog Example + //setDialog(ctx,env,OBFUSCATE("Title"),OBFUSCATE("Message Example")); + + //Toast Example + Toast(env, ctx, OBFUSCATE("Modded by YOU"), ToastLength::LENGTH_LONG); +} //Big letter cause crash void setText(JNIEnv *env, jobject obj, const char* text){ @@ -25,7 +36,6 @@ void setText(JNIEnv *env, jobject obj, const char* text){ } jstring Icon(JNIEnv *env, jobject thiz) { - //Use https://www.base64encode.org/ to encode your image to base64 return env->NewStringUTF( OBFUSCATE("iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAMAAAC3Ycb+AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAADeUExURUdwTAC8DwC8DwC8DwC8DwC8DwC8DwC8DwC8DwC8DwC8DwC8DwC8DwC8DwC8DwC8DwC8DwC8DwC8DwC8DwC8DwC8DwC8DwC8DwC8DwAAAAC8D2XRAEiaABIyADl6AGDJACNSAD6FACthAAYcAE6kAAEQADNtAFi3AACyDVzAAAglAABCAQC3DQAIAACgClKtAACTCAAwAWPNAABLAgBnBBxCAABzBQCoCwBbAwB8BgBSAl/EAAAZAACZCUyfAAA6AQCOCECJAACGB0aVAEOQADVzABg7AC5nAChbACleAGmQvrQAAAAZdFJOUwDwCxYgcZUr3/nosNDHBKSHVP1jM7lIe0FRwZXpAAAbvElEQVR42u2deX/iqhrH475vUdtJWp06NlXjrlat3Zdpz3n/b+je9qgGAgmJBFD5/XPu59apwLfwLDyAohyg8pHEWTlWjBcy1Ww2mQ6FQvq3QqFQOpnNVjOFeDFWPktE8opUoApXSrF4Jpv+GX5dVXWcNj9K5zLxWKkSlkNHW5FfsXgurfuQ+h+YeOwsKoeRyvKUKBWrSZ2GQrl4uSKXsX3mRSmeS+l0lcrFSxE5tN6VKBeSeiBSdT1ZKCfkEHuAEcuE9KAVysQkFKJlqpDWWSldkMuX89Q4z6k6W6m5czlR0KoUk2svlS0RXU8WK3L4YRrxpM5TybhkYlmpinxprJkU5dr1Y8Vj2T1Hsv1/LQaL7//s+ZuysVO38fmzTMq73egNRm/z2ar7bD4YGizjwXzurmbzt9Gg59meqKnM2QnH8pFzb2a8N3ibL7tmXyNV3+wu52+ewKh68vxEp8mvDHlapPc4X40Nza+M8Wr+SI4llfl1cjTCZVLL0X6ZdR9+/ty1PfXQnb2QWpls+aRy9pEiWTS+mE+bGl01p/MFWRRfPJmVKxonWasG866hBSOjOx+QrFzxk9hCqRTczXj7LjAYOyh37suXWjj6cLGSccXxODM1NjJnj65IMpXTxvGyMjSWMqYvp4sk4YbjbdrX2Ks/fXNDcpQ5laiL7RiteNBYM1mNXGzJ0Zn3cNHRs1osDY2vjKWjN5wqHlVcko85bcn27saaCBrfOQXzodjxJLlKTtn1Acelyr50OcUnydKR2PKqQwJRkMlhnSYOecdq4riNR3tmaOLJmLWP2JSU8EmrxUoTVSu8gU8f9LoVzeDj8a4msrr4GD5zsC6wg281Gmuiazw6Nn8rkTtgHM5Icgdo3PPnqcPG4YgkdX5okySB2xAcPGuHpGdcYJI9rEkSw0yPRVc7NHXRHpeaih2Qc1XFxB1L7RC1xMQl1UNxt0oY52re1w5T/TnG3TqImCQcx9jypna4amKsezx8qNb8AI0HkSkR3raX0dZ8ph2+ZmgHuCz4coVK7D6a2jHIfESmgAVetqLI5aq30o5FK+QOVlZUb+sM6V29PGjHIwNZoxI6EzMYRK1W7ZV2XFqhghJVwCAxXED6uoZ2bDKQHnBBNEMSQaV2e0vtGLVEWZKcWKXZCVQZw6CpHaeaqIxjMiG6OZ9rx6u52Ka9jDDn7a52zOoibLsqSoxYRMWChnbcMlBRYlGIrUGUe3WnHb9QBVwF/huJ4epRx+Ze4/Yqb/c3jHB3F6Z2GjIXiLts+BKJILJXL33tVNRHZFKyPAOSaPI4M+375OTVZFQkHr2pdlqa9sQhguDRHmunprE9IuFEJGGvox48aKenB3siJZ0QY36M+topqj8SYY4geLxpp6o3/kQQPOba6WrOm0gkeeLurrv7m2QZj4Tt8eBSO20t7REiu5gdkS+ZaqeuqX0TkRWRvD2f2NWkurYIscoo91uQPMiI6AU++1G9ZwnjR889HjtWZdvfgeSxJWIbm+B3dUuqXK+82JGgj5AkQpKHJyKhBOOAUPq7Lt5voAGiPQBZSgRuEWKQ4UhB5kt8ZFGCc35jMp/oK9MYVG38mSrz7b6y8WowRaZR2MEayaHHCN6xCgWRi7dleAd9OfIY9QcMMr/w8fP2gxx4rB7gyod44BmT3lgOu4PGvYBzKImUDAj3ChBTiWANiAxAvIYjdM0IbEBe5IB7dn5pmpESXN8uHSwfrlYpsAikJx0sIlerF1Q0UpUZd1+Cc/HVgFJYMoPlN6sVC8TjfZQDTazHAHzfPOTxtg05zsQyoIg9S6Ew6FwaEIpm5Jz6giUNyF5mZO9FKw9t2g7kEHsUFI3k8lQ9rF5TjrBHNXs0PS04JJQ1Dd61pBkeZuQe4f6C9g8z9HJY0uOl4vv6z2mFoXO2wV1h0qxZhTBUZg0l0yD62H+yf8ZANoH+X90KOqPrOxFfZJJzv3pvXUCatF4/wQ9dXqB1/3RjEHzsW/bPNKzfUN/8v1f0u/hCpyYeCkF6gSxYtSFm/N4NjWyk7//xC6TFCojRoxKMVBksWLV77AC2SP/0L758ArkwGQGBF60qDYseTE6x5TCCH6RAJjWfQG5YAYGzjH7seh4qdA/kEqw/ZOPsAuTi0ieQJ2ZATOh+mvzeMXowVQ3XjuNcJwXy2yeQ331WQOCaB+/xegSM0RfBeFj3juP8RArk4tYfEOvgBwykD94+F/J8aiTOIuluWEz4+2vjW6+XHZQXtB3F+8ZGH9d2s7792KRhEwrIKzMgcCLeaw1KNMUiZ2JuR2ZoobQl0kEAsbqqX9t//hf+2G/8l1qBtNgBgTIoKY8pLehoTkBJ3hoyRntHjCoSSNNmbbwCuaixA9Lc5xhPQmWyK1Wz/Yl/65UUiLY/kL/sgEB7VWrCf5a33Q8ciDUiaDAEcs0QSL/tO+tbUdnsgvAHMjHYAYF2RtSK3wmy0I4XyMU/DIFoC59TBJog3WMG8sESSNfnFMmwqmsQAEiHJRCo4iHjb4I8HzWQi0+WQJ59TZECs310EYDUWQKBosOCnyB9fLRA7oEcARsgY3CKRL1nsQItNOELZP0/miyBQFOEJKMVYTdBOANZb8b8YQoEnCKpiNfKhmArsfgCMX9bd7cYAYGmSNFr6c/4iIE0/8vf37MFMvZYElRmeTiHM5Ab6+4WKyDQ9rrrhQJZlodBOAMxral/ZkDAcD3rwuMXoyyWEEDWNS8ttkCgjNYvL1mT1ZEDaVjKs9gBAYq01IwHn7etHSyQSR3QDRrIleXL2QHR2uSe7znbC02CAwKpgwayrnp5YgxkRnzsECyO6xlHD+RyV57FEAhY6utUNHfG+EHbPYHU9gbytWPAEAj0jO4ZaZ53LDqQV9s/9wqkuSvPYglkTLgtApp0BgdufQD5fb3RcFdRt8lHeQaiDbeUWQIBN6rwZj3G1Of1BwStml8g9e2/ZwpkRVboC0TpPe1wgHQ0v0But+VZTIFoPZJoPcH8jXpaQOx1u5NbQJ84IFpnU57FFgho1hMkiffx4QDpPGj+InVtU7c6MTS2QMYkSfgkyzQWRSD3n5p/IP9uyrPYAgETWklkUFhhf2cDFSBPprYHEGOyLs9iDASsYqy476UbggKZdCxqDS//1pCDTQxk7fh2WAMx3PfWk+yvjds3dYIfbHIgaxCfjIGAW7moNavCOggRBMi6DXXWQKZua1aR/YolBJD1LtWQNZB+z8XPSnK49kcIIB/rWIYxEPDm66RLVLg6ISDrXaohayBT59jwnMOKJQaQ/m/QjWYFpO+8TZXjcde+EEC0Jz5AwDuCcnDmXeWwYgkC5IYTEGDNUiNON80YJwXE5AQEiA3VssNeIbPLxMUAAl1JxAwIWMMInRVJc3k+RxAgDU5AgOqTtIPTa54YkCtOQEy84xtjWh8nGhDwUiJ2QMCKuRj2Nr+7kwNyyQnIHe7ev3yIz4t4+951QgvIFycggOMbyuMyvQYHIA2XUQ0WSJMTEAOX8Y1xegBhFwG0dvfbNO9J78uiBkQb8gEC1mfFMIcQGL4Q0rdsjA8vf/Q0/O14oxwhkMmlTXUHIHVOQOaYCsY0ryd0vN+5SAgEoaEDkFtOQLroSCTByYTY8nqQ/jIDonX4ADHQkUiZ2xs6V4739prsgLzzAQIakTKy3oTtI1NDknpEBkD+5QRkjqw9yfJ7l9vE3zV+3WcIZF2exRzIFFXjm08xuIAUpyZmDCcN9EgHBMQyVZkCAa4qTeURYWFPY63PxvB+AsLoPNVNjS2QOh8gYBl8BWHT5UvpbPWCsOpxLnshUj+aIax6Tr6syk9dRKUDkOqVb9ez1QOQ8P3vSj++Nv3UBVj1qO1wunwsnbVGtiPrMfkYNE/NbRn4OI8SOamNrCek1YLNyRrLEWKssc3NSvPKvUt9y5qBV7+3RMLSyRLIzQpDu1MDOT7MNYCyWUCZ9ZscH+Z6g54AjclMFl/NIL83zm93SupbUyi9mJFerzh+r5pRlJxMLfLVAxSIAGFIX44Pe4G1WXmVy0EEqZ2shxJUJSLDEKECEbDCYSRHh4OAbXVwN0TGhTwEHNsBS07kbgj3yFAG6vwFXC4H3sokt6e4h+pg5kTWAPEQ8PwneIXDsxwdzrkTHUxlmXJ0OAi4P0CmsvjrAQ9EprJ4CDjXBh7WkYPDRQCQpAQiFpC0BCIWkJDMvnNXWwKRQKRIgaQkEO6yviWiMH/ERUoCkUCk/AORNkQwINLLkm6vlARyQEBkLou/ZLZXZCByP0QwIDl5Bpe3DLmnLpYeZNWJWDLxdVnyRBsPjfGVi7JQjoee8bW98hAuD03x1e9LOToctMSfD5HHEXhohj9BdSdHh4Pu8GcM5aW9PASeMZSncLkLPIUrz6lzF3hOnfwmB7O2ESag3/68hsiJNWtWNd0+4PhRGg0za3jZP2MgW2ogvsLaBMNpQHaCXtkhTmbtLrq/R/7c8jjLH+gn77ZHKSat10/wQ3XsLfqtj1tHIL4a5nRtv/0zDWRLLdf2bx/OurR88A/RBf/wXSfEtwFZuoAcoFcMkBru0ZZ3gwzI92tUTdoN8wakFSCQMfQwGPF9WZYu1FE/b6GB1PAPf7UMUiAXrSblhnkDcmEGBwS+L4v4oPql/SkOYCW/QANpOfT8gxiI9cU2Kg3zCOQmOCDwjXLEdy5amjdBGKkvNJA/jsNcIwZy8Um3YR6BPAUHBL5zsUIaiFi78I/zjy1Arh1HuU4OpEG3YR6B/O4HBgS+lZT43t5LzFqz1j0aCPHTkW5AhnQb5hGIdUgpA4Hv7SW+2draPPtLUNa3Mi39Niz/5v218a3Xyw7q9+yA/P2z0dff3QTr0G3Y7pHWhk2oX/waFBDD9tgn6d3vl5jFH/EH/gdhUS1/4UYHMcp19O/e+gQTug3z9Iwx5k+HChD73e+kryNcYl5F/U9DdL/RD3S/I0YDA+TdtpDQaZhXIJZW0QVifx2B9P2QSycv1Ji4AXF7wh4D5NUjENKGeQbyNyAg9vdDzggv+QOaB3fjnws3IFZPvkEOpOERCGnDPAO5DgiI/YUd0jeoQL8E+ooPUYCQNswzkF2IQxeI/Q0q0lfaLp3CgpYwQAgb5hnILsShCgTxShvpO4aXDmFB7UIYIIQN8w7kIxAgqHcMCV/6hEIpINt3Iw4QwoZ5B9IJBAjqpU/Ct3AvcX37v54EAkLWMO9Attk0qkBQb+ESvhYNNe/d+rPfAgEha5gPIPUggKBeiyZ8T33TvJY9lXEF/IQXEE8N8wTkHrBONIEg31MHD+1M3Zp33bHlw9ej9soXiKeGeQJyCVgnmkCsu1Nqds0DtOpz136/21Ln6/TEFWcgXhrmCcgf4F/TBDJH2HTIqg9c+/0Fh67NdejU5wzES8M8ATF/WwebJpABwqZDL+dhM/DbfpubbkJfPNQ4A/HSME9AmtfWohaKQPrAyCc2QMAtka5bvzfR7xWUka3zBuKlYd6A3FiLWigC6SLi9G9lSIzIrt+v0J5NZ9Nc3kA8NMwbENOalqEIZA6VAG0UIzEiu35fgXs2n9sqNd5APDTMG5D13GvRBjKw5d4RoSHOiOz6vdljMIGhfOIPxEPDPAJpWH4xPSDA9u0mLPwJDUMEkciu3xtv8gsoLLnhD8RDw3Z76nVAN2ggV5ZO0AMC1MiF8jsgSpXg2I6l33Xrl/cn23HkDoS8Ybiqkw4ayLp25YkuEOCoTtXCAzQibdd+31prm692PeEOhLxhXoFc7sqz6AFpY0wIHImYbv3e1DrdWna9P0QAQt4wr0C+diNLDYiJiUJskcjMtd9PliRFa9dX/kCIG+YVSHPnUVMDsrSVZO0EXOjw6Nrvv7vdOdOyL8QfCHHDvAJZ+wstmkAerYNeAIGU3B1fa79ru43/L8vOKX8gxA3zDKS+bRwtIIDTq5ZBIBHVtVzO2u9NDPzP9v9uiAGEuGGegdxuy7NoAQGcXjUCAgErHV5c+/2+NZj3lq8UAAhpw3ZxyC2gTxyQNeprekBeEPUNO527rllAv/9skhS31gyrAEBIG+YxUt+inhgaJSBgpvccBgI6viu3fjc3g1e37kEIAIS0Yd6B/LtZDCkBmTo4vd9KulWUAv3e+JQ3Q+sunQBASBvmHcg6UfZBCwhwcipp4wHe04Ras8B+r6Ou64l1p0AEIIQN8w5k7fh2KAHpA/UmRTuQituaBfb7CvBN1ntpIgAhbJgPIOsWftIBMsVletFr1qNbv/sTxCEAEYAQNswHkHVf6nSAjFxWLKj2BLFmgf0GD8LciAOEsGE+gKzN05AKEHArJI4CAq5ZS7d+A0fFTIGAkDXMD5D10YYGDSBL1xULWrMWbv2+RRzAEwIIWcP8AFmbpyENIAvXFQv2s8Yu/bYeN/4QCQhZw/wA6UOVwnsAAY56onwse2x459ZvS2H5v0IBIWqYHyBwLf0eQO5cokJEjW/Prd+WoxcPQgEhapgvIDfUgABBSBbDA9zItYUicL9r9kNLYgAhapgvICYtICsdu3kL5OBTTvVZcL93x/caYgEhapgvINCJRf9AgHqsVAQHBKxghM26rd+vnbWuBANC0jB/QBp0gIAmPYPlAR5Zh826rd92CQKEpGH+gFzRAQKa9DM8kDwQivQMCQQ+R3pPA4gBmPRkHg8E2qaaSSAwkEsaQGbOW1N4s96mCST4u05YAPmiAaRNaNLtZn1FEUgD1VOqtwGxANJ0A2JtxF80EMDnVTOOPJRfOtbz9dXvnefe2l2S17xH1HiggfRb7gPHEgiQS7YM87ZHvy1ln7vb126xPq/+yxkIGK0DT+746nffslc0vPzR0/C3LfkHAGnsatFfW8j6HJpAJpc21R2A1NFAdu2cDJ/++y3Dji3p/CPgQR18lI48AArsU/nqN8s7F/0BcfgqFJBbNBDnuz4n2HpFvewGJJzGBYf+gDw5tvQvOZC6GEC0DhJInfiPCQwK02E3IFASfrQvkCvHvxyTHEhNECDvSCDmxKntX7itW1ziHe/5WqaIPyDa0KGhVs/LBciHJgiQf5FArBfM29TCThAXnxe1tz7aF4iJv2v8uk8MZGiIAsRyiaMVSB9vRTo17ASJE/BQoip6ivgEojUxfZ+Ad785AZl8OD1swhaIZcqD4V4Ds2o9mdgJokZJgIBnRXZTxC8QTftsDO/B1k46T3XonBYOyKRzXa9pmjhA6riUoVl/6kDdvB82PvHFP/CZEJwqqi6f/wxGYAyiVsiAQPkT+VIYPQ1IN0Icp0hXDiQldf1NEHiKLORIUtLC3wSxTRH5RC4dLf1OEHiKtPtyMCmo3/Y7QRQlAU6RuRxNCpqDEyThBQgUi+hNOZx7q6n7iUG24XpKJ3w0QYpQYEyYinoDAmW0pOtL2eUly2IBSd+QdH0DdHlDEa9AoEJfxycnpdwFlv7gy3lJi+ZwdzZJkQm8icm5OA4n8D4azBVBUmQCN9L1kqL4UFV3vd1Bikzg8QPwNj8P0SHo+vYMObA+BRbz6qmEPyBQvYPDWy9SznrRvVY2EJUEyUXLp8BLG0hKfwjtelsuWr4WrDYNi47K+soMCoWcibcsry2lBcbrcmfEh8BdED0U3QcIHK/3ZNrXq5q9vWN0IF7P6bLiYS+BdQ16Lr8fEDgYkXtVHgXuSvkPQXDHDmUi3pueodE735uHks9K35eax5vN7w/EtmjJLKPvnCKFBQvhaUkz4teA7Oth4dK+0owQCtq19ZvkdQ8Pew9ysAn00KMaEjrktPSFrJxzVx+KQPbKYbnUoMhMPIHe9D3rTBwT8ZDvK2seXAVVNejZME0gNt8X/8q31I+gPRBaHi/uQgFd743loDtoDBl09wsC9jYjbelqkUfodA0IxowMpKtF7GBRNiDoaETuH2IF7RHSjECAGxlV6Hve5NATObzqmRKM4KSWzGoRZbCopbBcj/HIcIQkAPF6NMeTYc/BXyarHmAt4SHKhYMDokSSugwQPQWEejKiBKkE7GrJXDwgOOOuhxJKsCrBrpa8C8UieAtdV0tK0IJzKJKIA48AMiauNfG63pNE1jzgBJb/Ovf9nN+etCM/9qPH0OEFCoOqurTsBPZcr+bZAEGEI9L7tfu7wQYgLplfeZhnZRuRLDseqADxxLMotnxJ0AGhLRdvJ3LKmca5nUdUUXgTOd1s/Bt/HkoeQWR0mnuI/ZEtPk8z5/Gd1krbiAxOcZ/9YWAbh3RC4SHEHGmfXi3KuC3AeoUn0ju1gGTaE4cHksiJub92d1flyENRIvYIUX85HdPef7F3PxtReAqRRdEXp2LaHxY6z3wJhog903gq2V97dlfXq7x5KEq+YG/WSUTtc0S/C3lFABURLXs89rO6xiOi10VFDJVVe9vax71sPdujD10tK6LoLHRiyxZquQqdKeKogghI9MGx3lTTHCB6m6woIimCcH/13nHWNS4R3pWeiyhiKYxytvTR8dl2Y4TqaCGsCKcYwrTr7WPLbU0R1lxXY4qIQpp2/eWYJonxootuzoFcYxbV3N7x1D+sUNZDz0YVURWOoxqsPx7HvfHmI7J38bAisMopZKOPISc/Q/YsVVbEVgK5bOmLQw/cuwtkv7IJRXRhli19dMhhYnOkH+BytT1CEkK3fn6oO1f9ObpDoZJyGIpW0R1oH2bkvmyju1ONKgejGNq2H6IpwRgPPRVTDkkY267rg8M63fM8wPTjAKw5tJF4jpkk+uhwarfGGFuup87zysEpkdMPGwkWh55LKIeofCx0wEiwONRULK8cqKIZHBH9UWzz3n3EtjwTVQ5YpTS2YwtxM/PTAbbV6ZJy2AoXU9jOtWcipuaNWRvb4lQxrBy8ElUdrzvRjMn4zqG11YRyFColHTo5EGm7ZDVwaGmypByLHPyt7x0sQabJ+K7n0MrQ4fpWyKoUB1PybeCXvK2JsVw4NTBVjChHpmjcEYk+mvJLBvenI8e2peJR5QgVLaiO3dbfuDDpT996js1SC0eJ48fhckOiv0zZrl3G9MWlRWohoRyxEhk3JPrjjFVNhDl7dGuMmjlqHN+quM4SXW/fdYOeKEb3ru3aDrVQUU5AbuZ9HZ/MA4PS784HBC04UlOOdoLTuk4EZUq7NKI5JYKh6+njc3QdU1zlrE6m9susS+cU6UN39tIm/NZsOaycmn5lUjqpeqP5aux/BTPGq/moR/xtqcwv5SQVOU/qXtQbvM2XXZM8WOmb3eX8bdDz9C3J84hyssqfeZgmFjCjt/ls1X02H+xw+g/mc3c1m7+NPIJYT46zvHLaisSy+n5q/1+LweL7P3v+pmwsokgpSqKY1PkrWUxIFLtwMc6XSTJekRAgc1LhNk+SRUkDs3ad51TGMNTcuVypHG18qZBmRiNdKEkrTjJRYtVQ4DBC1ZicGl6glAvBmZRkoSxh+Fq+4rkUZRapXFwuU3s5X4lSsUpnroRy8XIlL4d0byTfc+VXLJ7zb+zTuXjsLCqHkrbClVIsnsmmPYDIxGOlyiFl0v8HKtOuZ5Ocqs4AAAAASUVORK5CYII=")); diff --git a/app/src/main/jni/Menu/Menu.hpp b/app/src/main/jni/Menu/Menu.hpp index d3623af..20044e1 100644 --- a/app/src/main/jni/Menu/Menu.hpp +++ b/app/src/main/jni/Menu/Menu.hpp @@ -1,7 +1,9 @@ #ifndef MENU_HPP #define MENU_HPP -#include +#include "Jni.hpp" + +void Init(JNIEnv *env, jobject thiz, jobject ctx, jobject title, jobject subtitle); void setText(JNIEnv *env, jobject obj, const char* text); diff --git a/app/src/main/jni/Menu/Setup.cpp b/app/src/main/jni/Menu/Setup.cpp index a884606..35b84a1 100644 --- a/app/src/main/jni/Menu/Setup.cpp +++ b/app/src/main/jni/Menu/Setup.cpp @@ -1,26 +1,6 @@ - #include "Includes/obfuscate.h" -#include "Menu/Jni.hpp" #include "Menu/Menu.hpp" -#include "Includes/Utils.hpp" -#include - -void Init(JNIEnv *env, jobject thiz, jobject ctx, jobject title, jobject subtitle) { - //Set sub title - setText(env, title, OBFUSCATE("Modded by (yourname)")); - - //Set sub title - setText(env, subtitle, OBFUSCATE("

" - "

Modded by LGL

| " - "https://github.com/LGLTeam | Lorem Ipsum is simply dummy text of the printing and typesetting

" - "
")); - - //Dialog Example - //setDialog(ctx,env,OBFUSCATE("Title"),OBFUSCATE("Message Example")); - - //Toast Example - Toast(env, ctx, OBFUSCATE("Modded by YOU"), ToastLength::LENGTH_LONG); -} +#include "Utils.hpp" int RegisterMenu(JNIEnv *env) { JNINativeMethod methods[] = { diff --git a/app/src/main/jni/Substrate/Buffer.hpp b/app/src/main/jni/Substrate/Buffer.hpp deleted file mode 100644 index 34d9df3..0000000 --- a/app/src/main/jni/Substrate/Buffer.hpp +++ /dev/null @@ -1,38 +0,0 @@ -/* Cydia Substrate - Powerful Code Insertion Platform - * Copyright (C) 2008-2011 Jay Freeman (saurik) -*/ - -/* GNU Lesser General Public License, Version 3 {{{ */ -/* - * Substrate is free software: you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Substrate is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Substrate. If not, see . -**/ -/* }}} */ - -#ifndef SUBSTRATE_BUFFER_HPP -#define SUBSTRATE_BUFFER_HPP - -#include - -template -_disused static _finline void MSWrite(uint8_t *&buffer, Type_ value) { - *reinterpret_cast(buffer) = value; - buffer += sizeof(Type_); -} - -_disused static _finline void MSWrite(uint8_t *&buffer, uint8_t *data, size_t size) { - memcpy(buffer, data, size); - buffer += size; -} - -#endif//SUBSTRATE_BUFFER_HPP diff --git a/app/src/main/jni/Substrate/CydiaSubstrate.h b/app/src/main/jni/Substrate/CydiaSubstrate.h deleted file mode 100644 index bb806aa..0000000 --- a/app/src/main/jni/Substrate/CydiaSubstrate.h +++ /dev/null @@ -1,152 +0,0 @@ -/* Cydia Substrate - Powerful Code Insertion Platform - * Copyright (C) 2008-2011 Jay Freeman (saurik) -*/ - -/* GNU Lesser General Public License, Version 3 {{{ */ -/* - * Substrate is free software: you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Substrate is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Substrate. If not, see . -**/ -/* }}} */ - -#ifndef SUBSTRATE_H_ -#define SUBSTRATE_H_ - -#ifdef __APPLE__ -#ifdef __cplusplus -extern "C" { -#endif -#include -#ifdef __cplusplus -} -#endif - -#include -#include -#endif - -#include -#include - -#define _finline \ - inline __attribute__((__always_inline__)) -#define _disused \ - __attribute__((__unused__)) - -#define _extern \ - extern "C" __attribute__((__visibility__("default"))) - -#ifdef __cplusplus -#define _default(value) = value -#else -#define _default(value) -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -bool MSHookProcess(pid_t pid, const char *library); - -typedef const void *MSImageRef; - -MSImageRef MSGetImageByName(const char *file); -void *MSFindSymbol(MSImageRef image, const char *name); - -void MSHookFunction(void *symbol, void *replace, void **result); - -#ifdef __APPLE__ -#ifdef __arm__ -__attribute__((__deprecated__)) -IMP MSHookMessage(Class _class, SEL sel, IMP imp, const char *prefix _default(NULL)); -#endif -void MSHookMessageEx(Class _class, SEL sel, IMP imp, IMP *result); -#endif - -#ifdef SubstrateInternal -typedef void *SubstrateAllocatorRef; -typedef struct __SubstrateProcess *SubstrateProcessRef; -typedef struct __SubstrateMemory *SubstrateMemoryRef; - -SubstrateProcessRef SubstrateProcessCreate(SubstrateAllocatorRef allocator, pid_t pid); -void SubstrateProcessRelease(SubstrateProcessRef process); - -SubstrateMemoryRef SubstrateMemoryCreate(SubstrateAllocatorRef allocator, SubstrateProcessRef process, void *data, size_t size); -void SubstrateMemoryRelease(SubstrateMemoryRef memory); -#endif - -#ifdef __cplusplus -} -#endif - -#ifdef __cplusplus - -#ifdef SubstrateInternal -struct SubstrateHookMemory { - SubstrateMemoryRef handle_; - - SubstrateHookMemory(SubstrateProcessRef process, void *data, size_t size) : - handle_(SubstrateMemoryCreate(NULL, NULL, data, size)) - { - } - - ~SubstrateHookMemory() { - if (handle_ != NULL) - SubstrateMemoryRelease(handle_); - } -}; -#endif - - -template -static inline void MSHookFunction(Type_ *symbol, Type_ *replace, Type_ **result) { - MSHookFunction( - reinterpret_cast(symbol), - reinterpret_cast(replace), - reinterpret_cast(result) - ); -} - -template -static inline void MSHookFunction(Type_ *symbol, Type_ *replace) { - return MSHookFunction(symbol, replace, reinterpret_cast(NULL)); -} - -template -static inline void MSHookSymbol(Type_ *&value, const char *name, MSImageRef image = NULL) { - value = reinterpret_cast(MSFindSymbol(image, name)); -} - -template -static inline void MSHookFunction(const char *name, Type_ *replace, Type_ **result = NULL) { - Type_ *symbol; - MSHookSymbol(symbol, name); - return MSHookFunction(symbol, replace, result); -} - -#endif - -#define MSHook(type, name, args...) \ - _disused static type (*_ ## name)(args); \ - static type $ ## name(args) - -#ifdef __cplusplus -#define MSHake(name) \ - &$ ## name, &_ ## name -#else -#define MSHake(name) \ - &$ ## name, (void **) &_ ## name -#endif - - -#endif//SUBSTRATE_H_ diff --git a/app/src/main/jni/Substrate/SubstrateARM.hpp b/app/src/main/jni/Substrate/SubstrateARM.hpp deleted file mode 100644 index 02b3028..0000000 --- a/app/src/main/jni/Substrate/SubstrateARM.hpp +++ /dev/null @@ -1,65 +0,0 @@ -/* Cydia Substrate - Powerful Code Insertion Platform - * Copyright (C) 2008-2011 Jay Freeman (saurik) -*/ - -/* GNU Lesser General Public License, Version 3 {{{ */ -/* - * Substrate is free software: you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Substrate is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Substrate. If not, see . -**/ -/* }}} */ - -#ifndef SUBSTRATE_ARM_HPP -#define SUBSTRATE_ARM_HPP - -enum A$r { - A$r0, A$r1, A$r2, A$r3, - A$r4, A$r5, A$r6, A$r7, - A$r8, A$r9, A$r10, A$r11, - A$r12, A$r13, A$r14, A$r15, - A$sp = A$r13, - A$lr = A$r14, - A$pc = A$r15 -}; - -enum A$c { - A$eq, A$ne, A$cs, A$cc, - A$mi, A$pl, A$vs, A$vc, - A$hi, A$ls, A$ge, A$lt, - A$gt, A$le, A$al, - A$hs = A$cs, - A$lo = A$cc -}; - -#define A$mrs_rm_cpsr(rd) /* mrs rd, cpsr */ \ - (0xe10f0000 | ((rd) << 12)) -#define A$msr_cpsr_f_rm(rm) /* msr cpsr_f, rm */ \ - (0xe128f000 | (rm)) -#define A$ldr_rd_$rn_im$(rd, rn, im) /* ldr rd, [rn, #im] */ \ - (0xe5100000 | ((im) < 0 ? 0 : 1 << 23) | ((rn) << 16) | ((rd) << 12) | abs((int)(im))) -#define A$str_rd_$rn_im$(rd, rn, im) /* sr rd, [rn, #im] */ \ - (0xe5000000 | ((im) < 0 ? 0 : 1 << 23) | ((rn) << 16) | ((rd) << 12) | abs(im)) -#define A$sub_rd_rn_$im(rd, rn, im) /* sub, rd, rn, #im */ \ - (0xe2400000 | ((rn) << 16) | ((rd) << 12) | (im & 0xff)) -#define A$blx_rm(rm) /* blx rm */ \ - (0xe12fff30 | (rm)) -#define A$mov_rd_rm(rd, rm) /* mov rd, rm */ \ - (0xe1a00000 | ((rd) << 12) | (rm)) -#define A$ldmia_sp$_$rs$(rs) /* ldmia sp!, {rs} */ \ - (0xe8b00000 | (A$sp << 16) | (rs)) -#define A$stmdb_sp$_$rs$(rs) /* stmdb sp!, {rs} */ \ - (0xe9200000 | (A$sp << 16) | (rs)) -#define A$stmia_sp$_$r0$ 0xe8ad0001 /* stmia sp!, {r0} */ -#define A$bx_r0 0xe12fff10 /* bx r0 */ - -#endif//SUBSTRATE_ARM_HPP diff --git a/app/src/main/jni/Substrate/SubstrateDebug.cpp b/app/src/main/jni/Substrate/SubstrateDebug.cpp deleted file mode 100644 index 3f5a168..0000000 --- a/app/src/main/jni/Substrate/SubstrateDebug.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* Cydia Substrate - Powerful Code Insertion Platform - * Copyright (C) 2008-2011 Jay Freeman (saurik) -*/ - -/* GNU Lesser General Public License, Version 3 {{{ */ -/* - * Substrate is free software: you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Substrate is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Substrate. If not, see . -**/ -/* }}} */ - -#include "SubstrateHook.h" -#include "SubstrateDebug.hpp" - -#include -#include -#include -#include - -_extern bool MSDebug; -bool MSDebug = false; - -static char _MSHexChar(uint8_t value) { - return value < 0x20 || value >= 0x80 ? '.' : value; -} - -#define HexWidth_ 16 -#define HexDepth_ 4 - -void MSLogHexEx(const void *vdata, size_t size, size_t stride, const char *mark) { - const uint8_t *data((const uint8_t *) vdata); - - size_t i(0), j; - - char d[256]; - size_t b(0); - d[0] = '\0'; - - while (i != size) { - if (i % HexWidth_ == 0) { - if (mark != NULL) - b += sprintf(d + b, OBFUSCATE("\n[%s] "), mark); - b += sprintf(d + b, OBFUSCATE("0x%.3zx:"), i); - } - - b += sprintf(d + b, " "); - - for (size_t q(0); q != stride; ++q) - b += sprintf(d + b, OBFUSCATE("%.2x"), data[i + stride - q - 1]); - - i += stride; - - for (size_t q(1); q != stride; ++q) - b += sprintf(d + b, " "); - - if (i % HexDepth_ == 0) - b += sprintf(d + b, " "); - - if (i % HexWidth_ == 0) { - b += sprintf(d + b, " "); - for (j = i - HexWidth_; j != i; ++j) - b += sprintf(d + b, "%c", _MSHexChar(data[j])); - - lprintf("%s", d); - b = 0; - d[0] = '\0'; - } - } - - if (i % HexWidth_ != 0) { - for (j = i % HexWidth_; j != HexWidth_; ++j) - b += sprintf(d + b, " "); - for (j = 0; j != (HexWidth_ - i % HexWidth_ + HexDepth_ - 1) / HexDepth_; ++j) - b += sprintf(d + b, " "); - b += sprintf(d + b, " "); - for (j = i / HexWidth_ * HexWidth_; j != i; ++j) - b += sprintf(d + b, OBFUSCATE("%c"), _MSHexChar(data[j])); - - // lprintf("%s", d); - b = 0; - d[0] = '\0'; - } -} - -void MSLogHex(const void *vdata, size_t size, const char *mark) { - return MSLogHexEx(vdata, size, 1, mark); -} diff --git a/app/src/main/jni/Substrate/SubstrateDebug.hpp b/app/src/main/jni/Substrate/SubstrateDebug.hpp deleted file mode 100644 index 9c554c8..0000000 --- a/app/src/main/jni/Substrate/SubstrateDebug.hpp +++ /dev/null @@ -1,33 +0,0 @@ -/* Cydia Substrate - Powerful Code Insertion Platform - * Copyright (C) 2008-2011 Jay Freeman (saurik) -*/ - -/* GNU Lesser General Public License, Version 3 {{{ */ -/* - * Substrate is free software: you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Substrate is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Substrate. If not, see . -**/ -/* }}} */ - -#ifndef SUBSTRATE_DEBUG_HPP -#define SUBSTRATE_DEBUG_HPP - -#include "SubstrateLog.hpp" -#define lprintf(format, ...) \ - MSLog(MSLogLevelNotice, format, ## __VA_ARGS__) - -extern "C" bool MSDebug; -void MSLogHexEx(const void *vdata, size_t size, size_t stride, const char *mark = 0); -void MSLogHex(const void *vdata, size_t size, const char *mark = 0); - -#endif//SUBSTRATE_DEBUG_HPP diff --git a/app/src/main/jni/Substrate/SubstrateHook.cpp b/app/src/main/jni/Substrate/SubstrateHook.cpp deleted file mode 100644 index a815f47..0000000 --- a/app/src/main/jni/Substrate/SubstrateHook.cpp +++ /dev/null @@ -1,951 +0,0 @@ -/* Cydia Substrate - Powerful Code Insertion Platform - * Copyright (C) 2008-2011 Jay Freeman (saurik) -*/ - -/* GNU Lesser General Public License, Version 3 {{{ */ -/* - * Substrate is free software: you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Substrate is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Substrate. If not, see . -**/ -/* }}} */ - -#define SubstrateInternal - -#include "CydiaSubstrate.h" - -#include - -#define _trace() do { \ - MSLog(MSLogLevelNotice, "_trace(%u)", __LINE__); \ -} while (false) - -#if defined(__i386__) || defined(__x86_64__) - -#include "hde64.h" - -#endif - -#include "SubstrateDebug.hpp" - -#include -#include -#include -#include - -#ifdef __arm__ -/* WebCore (ARM) PC-Relative: -X 1 ldr r*,[pc,r*] != - 2 fldd d*,[pc,#*] -X 5 str r*,[pc,r*] != - 8 flds s*,[pc,#*] - 400 ldr r*,[pc,r*] == - 515 add r*, pc,r* == -X 4790 ldr r*,[pc,#*] */ - -// x=0; while IFS= read -r line; do if [[ ${#line} -ne 0 && $line == +([^\;]): ]]; then x=2; elif [[ $line == ' +'* && $x -ne 0 ]]; then ((--x)); echo "$x${line}"; fi; done WebCore.pc -// grep pc WebCore.pc | cut -c 40- | sed -Ee 's/^ldr *(ip|r[0-9]*),\[pc,\#0x[0-9a-f]*\].*/ ldr r*,[pc,#*]/;s/^add *r[0-9]*,pc,r[0-9]*.*/ add r*, pc,r*/;s/^(st|ld)r *r([0-9]*),\[pc,r([0-9]*)\].*/ \1r r\2,[pc,r\3]/;s/^fld(s|d) *(s|d)[0-9]*,\[pc,#0x[0-9a-f]*].*/fld\1 \2*,[pc,#*]/' | sort | uniq -c | sort -n - -#include "SubstrateARM.hpp" - -#define T$Label(l, r) \ - (((r) - (l)) * 2 - 4 + ((l) % 2 == 0 ? 0 : 2)) - -#define T$pop_$r0$ 0xbc01 // pop {r0} -#define T$b(im) /* b im */ \ - (0xde00 | (im & 0xff)) -#define T$blx(rm) /* blx rm */ \ - (0x4780 | (rm << 3)) -#define T$bx(rm) /* bx rm */ \ - (0x4700 | (rm << 3)) -#define T$nop /* nop */ \ - (0x46c0) - -#define T$add_rd_rm(rd, rm) /* add rd, rm */ \ - (0x4400 | (((rd) & 0x8) >> 3 << 7) | (((rm) & 0x8) >> 3 << 6) | (((rm) & 0x7) << 3) | ((rd) & 0x7)) -#define T$push_r(r) /* push r... */ \ - (0xb400 | (((r) & (1 << A$lr)) >> A$lr << 8) | ((r) & 0xff)) -#define T$pop_r(r) /* pop r... */ \ - (0xbc00 | (((r) & (1 << A$pc)) >> A$pc << 8) | ((r) & 0xff)) -#define T$mov_rd_rm(rd, rm) /* mov rd, rm */ \ - (0x4600 | (((rd) & 0x8) >> 3 << 7) | (((rm) & 0x8) >> 3 << 6) | (((rm) & 0x7) << 3) | ((rd) & 0x7)) -#define T$ldr_rd_$rn_im_4$(rd, rn, im) /* ldr rd, [rn, #im * 4] */ \ - (0x6800 | (((im) & 0x1f) << 6) | ((rn) << 3) | (rd)) -#define T$ldr_rd_$pc_im_4$(rd, im) /* ldr rd, [PC, #im * 4] */ \ - (0x4800 | ((rd) << 8) | ((im) & 0xff)) -#define T$cmp_rn_$im(rn, im) /* cmp rn, #im */ \ - (0x2000 | ((rn) << 8) | ((im) & 0xff)) -#define T$it$_cd(cd, ms) /* it, cd */ \ - (0xbf00 | ((cd) << 4) | (ms)) -#define T$cbz$_rn_$im(op,rn,im) /* cbz rn, #im */ \ - (0xb100 | ((op) << 11) | (((im) & 0x40) >> 6 << 9) | (((im) & 0x3e) >> 1 << 3) | (rn)) -#define T$b$_$im(cond,im) /* b #im */ \ - (cond == A$al ? 0xe000 | (((im) >> 1) & 0x7ff) : 0xd000 | ((cond) << 8) | (((im) >> 1) & 0xff)) - -#define T1$ldr_rt_$rn_im$(rt, rn, im) /* ldr rt, [rn, #im] */ \ - (0xf850 | ((im < 0 ? 0 : 1) << 7) | (rn)) -#define T2$ldr_rt_$rn_im$(rt, rn, im) /* ldr rt, [rn, #im] */ \ - (((rt) << 12) | abs((int)(im))) - -#define T1$mrs_rd_apsr(rd) /* mrs rd, apsr */ \ - (0xf3ef) -#define T2$mrs_rd_apsr(rd) /* mrs rd, apsr */ \ - (0x8000 | ((rd) << 8)) - -#define T1$msr_apsr_nzcvqg_rn(rn) /* msr apsr, rn */ \ - (0xf380 | (rn)) -#define T2$msr_apsr_nzcvqg_rn(rn) /* msr apsr, rn */ \ - (0x8c00) -#define T$msr_apsr_nzcvqg_rn(rn) /* msr apsr, rn */ \ - (T2$msr_apsr_nzcvqg_rn(rn) << 16 | T1$msr_apsr_nzcvqg_rn(rn)) - -static inline bool A$pcrel$r(uint32_t ic) { - return (ic & 0x0c000000) == 0x04000000 && (ic & 0xf0000000) != 0xf0000000 && (ic & 0x000f0000) == 0x000f0000; -} - -static inline bool T$32bit$i(uint16_t ic) { - return ((ic & 0xe000) == 0xe000 && (ic & 0x1800) != 0x0000); -} - -static inline bool T$pcrel$cbz(uint16_t ic) { - return (ic & 0xf500) == 0xb100; -} - -static inline bool T$pcrel$b(uint16_t ic) { - return (ic & 0xf000) == 0xd000 && (ic & 0x0e00) != 0x0e00; -} - -static inline bool T2$pcrel$b(uint16_t *ic) { - return (ic[0] & 0xf800) == 0xf000 && (((ic[1] & 0xd000) == 0x9000 || (ic[1] & 0xd000) == 0x8000) && (ic[0] & 0x0380) != 0x0380); -} - -static inline bool T$pcrel$bl(uint16_t *ic) { - return (ic[0] & 0xf800) == 0xf000 && ((ic[1] & 0xd000) == 0xd000 || (ic[1] & 0xd001) == 0xc000); -} - -static inline bool T$pcrel$ldr(uint16_t ic) { - return (ic & 0xf800) == 0x4800; -} - -static inline bool T$pcrel$add(uint16_t ic) { - return (ic & 0xff78) == 0x4478; -} - -static inline bool T$pcrel$ldrw(uint16_t ic) { - return (ic & 0xff7f) == 0xf85f; -} - -static size_t MSGetInstructionWidthThumb(void *start) { - uint16_t *thumb(reinterpret_cast(start)); - return T$32bit$i(thumb[0]) ? 4 : 2; -} - -static size_t MSGetInstructionWidthARM(void *start) { - return 4; -} - -extern "C" size_t MSGetInstructionWidth(void *start) { - if ((reinterpret_cast(start) & 0x1) == 0) - return MSGetInstructionWidthARM(start); - else - return MSGetInstructionWidthThumb(reinterpret_cast(reinterpret_cast(start) & ~0x1)); -} - -static size_t SubstrateHookFunctionThumb(SubstrateProcessRef process, void *symbol, void *replace, void **result) { - if (symbol == NULL) - return 0; -printf(OBFUSCATE("SubstrateHookFunctionThumb\n")); - uint16_t *area(reinterpret_cast(symbol)); - - unsigned align((reinterpret_cast(area) & 0x2) == 0 ? 0 : 1); - uint16_t *thumb(area + align); - - uint32_t *arm(reinterpret_cast(thumb + 2)); - uint16_t *trail(reinterpret_cast(arm + 2)); - - if ( - (align == 0 || area[0] == T$nop) && - thumb[0] == T$bx(A$pc) && - thumb[1] == T$nop && - arm[0] == A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8) - ) { - if (result != NULL) - *result = reinterpret_cast(arm[1]); - - SubstrateHookMemory code(process, arm + 1, sizeof(uint32_t) * 1); - - arm[1] = reinterpret_cast(replace); - - return sizeof(arm[0]); - } - - size_t required((trail - area) * sizeof(uint16_t)); - - size_t used(0); - while (used < required) - used += MSGetInstructionWidthThumb(reinterpret_cast(area) + used); - used = (used + sizeof(uint16_t) - 1) / sizeof(uint16_t) * sizeof(uint16_t); - - size_t blank((used - required) / sizeof(uint16_t)); - - uint16_t backup[used / sizeof(uint16_t)]; - memcpy(backup, area, used); - - if (MSDebug) { - char name[16]; - sprintf(name, "%p", area); - MSLogHexEx(area, used + sizeof(uint16_t), 2, name); - } - - if (result != NULL) { - - size_t length(used); - for (unsigned offset(0); offset != used / sizeof(uint16_t); ++offset) - if (T$pcrel$ldr(backup[offset])) - length += 3 * sizeof(uint16_t); - else if (T$pcrel$b(backup[offset])) - length += 6 * sizeof(uint16_t); - else if (T2$pcrel$b(backup + offset)) { - length += 5 * sizeof(uint16_t); - ++offset; - } else if (T$pcrel$bl(backup + offset)) { - length += 5 * sizeof(uint16_t); - ++offset; - } else if (T$pcrel$cbz(backup[offset])) { - length += 16 * sizeof(uint16_t); - } else if (T$pcrel$ldrw(backup[offset])) { - length += 4 * sizeof(uint16_t); - ++offset; - } else if (T$pcrel$add(backup[offset])) - length += 6 * sizeof(uint16_t); - else if (T$32bit$i(backup[offset])) - ++offset; - - unsigned pad((length & 0x2) == 0 ? 0 : 1); - length += (pad + 2) * sizeof(uint16_t) + 2 * sizeof(uint32_t); - - uint16_t *buffer(reinterpret_cast(mmap( - NULL, length, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0 - ))); - - if (buffer == MAP_FAILED) { - MSLog(MSLogLevelError, "MS:Error:mmap() = %d", errno); - *result = NULL; - return 0; - } - - if (false) fail: { - munmap(buffer, length); - *result = NULL; - return 0; - } - - size_t start(pad), end(length / sizeof(uint16_t)); - uint32_t *trailer(reinterpret_cast(buffer + end)); - for (unsigned offset(0); offset != used / sizeof(uint16_t); ++offset) { - if (T$pcrel$ldr(backup[offset])) { - union { - uint16_t value; - - struct { - uint16_t immediate : 8; - uint16_t rd : 3; - uint16_t : 5; - }; - } bits = {backup[offset+0]}; - - buffer[start+0] = T$ldr_rd_$pc_im_4$(bits.rd, T$Label(start+0, end-2) / 4); - buffer[start+1] = T$ldr_rd_$rn_im_4$(bits.rd, bits.rd, 0); - - // XXX: this code "works", but is "wrong": the mechanism is more complex than this - *--trailer = ((reinterpret_cast(area + offset) + 4) & ~0x2) + bits.immediate * 4; - - start += 2; - end -= 2; - } else if (T$pcrel$b(backup[offset])) { - union { - uint16_t value; - - struct { - uint16_t imm8 : 8; - uint16_t cond : 4; - uint16_t /*1101*/ : 4; - }; - } bits = {backup[offset+0]}; - - intptr_t jump(bits.imm8 << 1); - jump |= 1; - jump <<= 23; - jump >>= 23; - - buffer[start+0] = T$b$_$im(bits.cond, (end-6 - (start+0)) * 2 - 4); - - *--trailer = reinterpret_cast(area + offset) + 4 + jump; - *--trailer = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8); - *--trailer = T$nop << 16 | T$bx(A$pc); - - start += 1; - end -= 6; - } else if (T2$pcrel$b(backup + offset)) { - union { - uint16_t value; - - struct { - uint16_t imm6 : 6; - uint16_t cond : 4; - uint16_t s : 1; - uint16_t : 5; - }; - } bits = {backup[offset+0]}; - - union { - uint16_t value; - - struct { - uint16_t imm11 : 11; - uint16_t j2 : 1; - uint16_t a : 1; - uint16_t j1 : 1; - uint16_t : 2; - }; - } exts = {backup[offset+1]}; - - intptr_t jump(1); - jump |= exts.imm11 << 1; - jump |= bits.imm6 << 12; - - if (exts.a) { - jump |= bits.s << 24; - jump |= (~(bits.s ^ exts.j1) & 0x1) << 23; - jump |= (~(bits.s ^ exts.j2) & 0x1) << 22; - jump |= bits.cond << 18; - jump <<= 7; - jump >>= 7; - } else { - jump |= bits.s << 20; - jump |= exts.j2 << 19; - jump |= exts.j1 << 18; - jump <<= 11; - jump >>= 11; - } - - buffer[start+0] = T$b$_$im(exts.a ? A$al : bits.cond, (end-6 - (start+0)) * 2 - 4); - - *--trailer = reinterpret_cast(area + offset) + 4 + jump; - *--trailer = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8); - *--trailer = T$nop << 16 | T$bx(A$pc); - - ++offset; - start += 1; - end -= 6; - } else if (T$pcrel$bl(backup + offset)) { - union { - uint16_t value; - - struct { - uint16_t immediate : 10; - uint16_t s : 1; - uint16_t : 5; - }; - } bits = {backup[offset+0]}; - - union { - uint16_t value; - - struct { - uint16_t immediate : 11; - uint16_t j2 : 1; - uint16_t x : 1; - uint16_t j1 : 1; - uint16_t : 2; - }; - } exts = {backup[offset+1]}; - - int32_t jump(0); - jump |= bits.s << 24; - jump |= (~(bits.s ^ exts.j1) & 0x1) << 23; - jump |= (~(bits.s ^ exts.j2) & 0x1) << 22; - jump |= bits.immediate << 12; - jump |= exts.immediate << 1; - jump |= exts.x; - jump <<= 7; - jump >>= 7; - - buffer[start+0] = T$push_r(1 << A$r7); - buffer[start+1] = T$ldr_rd_$pc_im_4$(A$r7, ((end-2 - (start+1)) * 2 - 4 + 2) / 4); - buffer[start+2] = T$mov_rd_rm(A$lr, A$r7); - buffer[start+3] = T$pop_r(1 << A$r7); - buffer[start+4] = T$blx(A$lr); - - *--trailer = reinterpret_cast(area + offset) + 4 + jump; - - ++offset; - start += 5; - end -= 2; - } else if (T$pcrel$cbz(backup[offset])) { - union { - uint16_t value; - - struct { - uint16_t rn : 3; - uint16_t immediate : 5; - uint16_t : 1; - uint16_t i : 1; - uint16_t : 1; - uint16_t op : 1; - uint16_t : 4; - }; - } bits = {backup[offset+0]}; - - intptr_t jump(1); - jump |= bits.i << 6; - jump |= bits.immediate << 1; - - //jump <<= 24; - //jump >>= 24; - - unsigned rn(bits.rn); - unsigned rt(rn == A$r7 ? A$r6 : A$r7); - - buffer[start+0] = T$push_r(1 << rt); - buffer[start+1] = T1$mrs_rd_apsr(rt); - buffer[start+2] = T2$mrs_rd_apsr(rt); - buffer[start+3] = T$cbz$_rn_$im(bits.op, rn, (end-10 - (start+3)) * 2 - 4); - buffer[start+4] = T1$msr_apsr_nzcvqg_rn(rt); - buffer[start+5] = T2$msr_apsr_nzcvqg_rn(rt); - buffer[start+6] = T$pop_r(1 << rt); - - *--trailer = reinterpret_cast(area + offset) + 4 + jump; - *--trailer = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8); - *--trailer = T$nop << 16 | T$bx(A$pc); - *--trailer = T$nop << 16 | T$pop_r(1 << rt); - *--trailer = T$msr_apsr_nzcvqg_rn(rt); - -#if 0 - if ((start & 0x1) == 0) - buffer[start++] = T$nop; - buffer[start++] = T$bx(A$pc); - buffer[start++] = T$nop; - - uint32_t *arm(reinterpret_cast(buffer + start)); - arm[0] = A$add(A$lr, A$pc, 1); - arm[1] = A$ldr_rd_$rn_im$(A$pc, A$pc, (trailer - arm) * sizeof(uint32_t) - 8); -#endif - - start += 7; - end -= 10; - } else if (T$pcrel$ldrw(backup[offset])) { - union { - uint16_t value; - - struct { - uint16_t : 7; - uint16_t u : 1; - uint16_t : 8; - }; - } bits = {backup[offset+0]}; - - union { - uint16_t value; - - struct { - uint16_t immediate : 12; - uint16_t rt : 4; - }; - } exts = {backup[offset+1]}; - - buffer[start+0] = T1$ldr_rt_$rn_im$(exts.rt, A$pc, T$Label(start+0, end-2)); - buffer[start+1] = T2$ldr_rt_$rn_im$(exts.rt, A$pc, T$Label(start+0, end-2)); - - buffer[start+2] = T1$ldr_rt_$rn_im$(exts.rt, exts.rt, 0); - buffer[start+3] = T2$ldr_rt_$rn_im$(exts.rt, exts.rt, 0); - - // XXX: this code "works", but is "wrong": the mechanism is more complex than this - *--trailer = ((reinterpret_cast(area + offset) + 4) & ~0x2) + (bits.u == 0 ? -exts.immediate : exts.immediate); - - ++offset; - start += 4; - end -= 2; - } else if (T$pcrel$add(backup[offset])) { - union { - uint16_t value; - - struct { - uint16_t rd : 3; - uint16_t rm : 3; - uint16_t h2 : 1; - uint16_t h1 : 1; - uint16_t : 8; - }; - } bits = {backup[offset+0]}; - - if (bits.h1) { - MSLog(MSLogLevelError, "MS:Error:pcrel(%u):add (rd > r7)", offset); - goto fail; - } - - unsigned rt(bits.rd == A$r7 ? A$r6 : A$r7); - - buffer[start+0] = T$push_r(1 << rt); - buffer[start+1] = T$mov_rd_rm(rt, (bits.h1 << 3) | bits.rd); - buffer[start+2] = T$ldr_rd_$pc_im_4$(bits.rd, T$Label(start+2, end-2) / 4); - buffer[start+3] = T$add_rd_rm((bits.h1 << 3) | bits.rd, rt); - buffer[start+4] = T$pop_r(1 << rt); - *--trailer = reinterpret_cast(area + offset) + 4; - - start += 5; - end -= 2; - } else if (T$32bit$i(backup[offset])) { - buffer[start++] = backup[offset]; - buffer[start++] = backup[++offset]; - } else { - buffer[start++] = backup[offset]; - } - } - - buffer[start++] = T$bx(A$pc); - buffer[start++] = T$nop; - - uint32_t *transfer = reinterpret_cast(buffer + start); - transfer[0] = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8); - transfer[1] = reinterpret_cast(area + used / sizeof(uint16_t)) + 1; - - if (mprotect(buffer, length, PROT_READ | PROT_EXEC) == -1) { - MSLog(MSLogLevelError, "MS:Error:mprotect():%d", errno); - return 0; - } - - *result = reinterpret_cast(buffer + pad) + 1; - - if (MSDebug) { - char name[16]; - sprintf(name, "%p", *result); - MSLogHexEx(buffer, length, 2, name); - } - - } - - { - SubstrateHookMemory code(process, area, used); - - if (align != 0) - area[0] = T$nop; - - thumb[0] = T$bx(A$pc); - thumb[1] = T$nop; - - arm[0] = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8); - arm[1] = reinterpret_cast(replace); - - for (unsigned offset(0); offset != blank; ++offset) - trail[offset] = T$nop; - } - - if (MSDebug) { - char name[16]; - sprintf(name, "%p", area); - MSLogHexEx(area, used + sizeof(uint16_t), 2, name); - } - - return used; -} - -static size_t SubstrateHookFunctionARM(SubstrateProcessRef process, void *symbol, void *replace, void **result) { - if (symbol == NULL) - return 0; - printf(OBFUSCATE("SubstrateHookFunctionARM\n")); - uint32_t *area(reinterpret_cast(symbol)); - uint32_t *arm(area); - - const size_t used(8); - - uint32_t backup[used / sizeof(uint32_t)] = {arm[0], arm[1]}; - - if (MSDebug) { - char name[16]; - sprintf(name, "%p", area); - MSLogHexEx(area, used + sizeof(uint32_t), 4, name); - } - - if (result != NULL) { - - if (backup[0] == A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8)) { - *result = reinterpret_cast(backup[1]); - - return sizeof(backup[0]); - } - - size_t length(used); - for (unsigned offset(0); offset != used / sizeof(uint32_t); ++offset) - if (A$pcrel$r(backup[offset])) { - if ((backup[offset] & 0x02000000) == 0 || (backup[offset] & 0x0000f000 >> 12) != (backup[offset] & 0x0000000f)) - length += 2 * sizeof(uint32_t); - else - length += 4 * sizeof(uint32_t); - } - - length += 2 * sizeof(uint32_t); - - uint32_t *buffer(reinterpret_cast(mmap( - NULL, length, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0 - ))); - - if (buffer == MAP_FAILED) { - MSLog(MSLogLevelError, "MS:Error:mmap() = %d", errno); - *result = NULL; - return 0; - } - - if (false) fail: { - munmap(buffer, length); - *result = NULL; - return 0; - } - - size_t start(0), end(length / sizeof(uint32_t)); - uint32_t *trailer(reinterpret_cast(buffer + end)); - for (unsigned offset(0); offset != used / sizeof(uint32_t); ++offset) - if (A$pcrel$r(backup[offset])) { - union { - uint32_t value; - - struct { - uint32_t rm : 4; - uint32_t : 1; - uint32_t shift : 2; - uint32_t shiftamount : 5; - uint32_t rd : 4; - uint32_t rn : 4; - uint32_t l : 1; - uint32_t w : 1; - uint32_t b : 1; - uint32_t u : 1; - uint32_t p : 1; - uint32_t mode : 1; - uint32_t type : 2; - uint32_t cond : 4; - }; - } bits = {backup[offset+0]}, copy(bits); - - bool guard; - if (bits.mode == 0 || bits.rd != bits.rm) { - copy.rn = bits.rd; - guard = false; - } else { - copy.rn = bits.rm != A$r0 ? A$r0 : A$r1; - guard = true; - } - - if (guard) - buffer[start++] = A$stmdb_sp$_$rs$((1 << copy.rn)); - - buffer[start+0] = A$ldr_rd_$rn_im$(copy.rn, A$pc, (end-1 - (start+0)) * 4 - 8); - buffer[start+1] = copy.value; - - start += 2; - - if (guard) - buffer[start++] = A$ldmia_sp$_$rs$((1 << copy.rn)); - - *--trailer = reinterpret_cast(area + offset) + 8; - end -= 1; - } else - buffer[start++] = backup[offset]; - - buffer[start+0] = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8); - buffer[start+1] = reinterpret_cast(area + used / sizeof(uint32_t)); - - if (mprotect(buffer, length, PROT_READ | PROT_EXEC) == -1) { - MSLog(MSLogLevelError, OBFUSCATE("MS:Error:mprotect():%d"), errno); - goto fail; - } - - *result = buffer; - - if (MSDebug) { - char name[16]; - sprintf(name, "%p", *result); - MSLogHexEx(buffer, length, 4, name); - } - - } - - { - SubstrateHookMemory code(process, symbol, used); - - arm[0] = A$ldr_rd_$rn_im$(A$pc, A$pc, 4 - 8); - arm[1] = reinterpret_cast(replace); - } - - if (MSDebug) { - char name[16]; - sprintf(name, "%p", area); - MSLogHexEx(area, used + sizeof(uint32_t), 4, name); - } - - return used; -} - -static size_t SubstrateHookFunction(SubstrateProcessRef process, void *symbol, void *replace, void **result) { - if (MSDebug) - MSLog(MSLogLevelNotice, "SubstrateHookFunction(%p, %p, %p, %p)\n", process, symbol, replace, result); - if ((reinterpret_cast(symbol) & 0x1) == 0) - return SubstrateHookFunctionARM(process, symbol, replace, result); - else - return SubstrateHookFunctionThumb(process, reinterpret_cast(reinterpret_cast(symbol) & ~0x1), replace, result); -} -#endif - -#if defined(__i386__) || defined(__x86_64__) - -#include "SubstrateX86.hpp" - -static size_t MSGetInstructionWidthIntel(void *start) { - hde64s decode; - return hde64_disasm(start, &decode); -} - -static void -SubstrateHookFunction(SubstrateProcessRef process, void *symbol, void *replace, void **result) { - if (MSDebug) - MSLog(MSLogLevelNotice, OBFUSCATE("MSHookFunction(%p, %p, %p)\n"), symbol, replace, result); - if (symbol == NULL) - return; - - uintptr_t source(reinterpret_cast(symbol)); - uintptr_t target(reinterpret_cast(replace)); - - uint8_t *area(reinterpret_cast(symbol)); - - size_t required(MSSizeOfJump(target, source)); - - if (MSDebug) { - char name[16]; - sprintf(name, OBFUSCATE("%p"), area); - MSLogHex(area, 32, name); - } - - size_t used(0); - while (used < required) { - size_t width(MSGetInstructionWidthIntel(area + used)); - if (width == 0) { - //MSLog(MSLogLevelError, "MS:Error:MSGetInstructionWidthIntel(%p) == 0", area + used); - return; - } - - used += width; - } - - size_t blank(used - required); - - if (MSDebug) { - char name[16]; - sprintf(name, OBFUSCATE("%p"), area); - MSLogHex(area, used + sizeof(uint16_t), name); - } - - uint8_t backup[used]; - memcpy(backup, area, used); - - if (result != NULL) { - - if (backup[0] == 0xe9) { - *result = reinterpret_cast(source + 5 + - *reinterpret_cast(backup + 1)); - return; - } - - if (!ia32 && backup[0] == 0xff && backup[1] == 0x25) { - *result = *reinterpret_cast(source + 6 + - *reinterpret_cast(backup + 2)); - return; - } - - size_t length(used + MSSizeOfJump(source + used)); - - for (size_t offset(0), width; offset != used; offset += width) { - hde64s decode; - hde64_disasm(backup + offset, &decode); - width = decode.len; - //_assert(width != 0 && offset + width <= used); - -#ifdef __LP64__ - if ((decode.modrm & 0xc7) == 0x05) { - if (decode.opcode == 0x8b) { - void *destiny(area + offset + width + int32_t(decode.disp.disp32)); - uint8_t reg(decode.rex_r << 3 | decode.modrm_reg); - length -= decode.len; - length += MSSizeOfPushPointer(destiny); - length += MSSizeOfPop(reg); - length += MSSizeOfMove64(); - } else { - MSLog(MSLogLevelError, "MS:Error: Unknown RIP-Relative (%.2x %.2x)", decode.opcode, decode.opcode2); - continue; - } - } else -#endif - - if (backup[offset] == 0xe8) { - int32_t relative(*reinterpret_cast(backup + offset + 1)); - void *destiny(area + offset + decode.len + relative); - - if (relative == 0) { - length -= decode.len; - length += MSSizeOfPushPointer(destiny); - } else { - length += MSSizeOfSkip(); - length += MSSizeOfJump(destiny); - } - } else if (backup[offset] == 0xeb) { - length -= decode.len; - length += MSSizeOfJump(area + offset + decode.len + - *reinterpret_cast(backup + offset + 1)); - } else if (backup[offset] == 0xe9) { - length -= decode.len; - length += MSSizeOfJump(area + offset + decode.len + - *reinterpret_cast(backup + offset + 1)); - } else if ( - backup[offset] == 0xe3 || - (backup[offset] & 0xf0) == 0x70 - // XXX: opcode2 & 0xf0 is 0x80? - ) { - length += decode.len; - length += MSSizeOfJump(area + offset + decode.len + - *reinterpret_cast(backup + offset + 1)); - } - } - - uint8_t *buffer(reinterpret_cast(mmap( - NULL, length, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0 - ))); - - if (buffer == MAP_FAILED) { - MSLog(MSLogLevelError, OBFUSCATE("MS:Error:mmap() = %d"), errno); - *result = NULL; - return; - } - - if (false) - fail: - { - munmap(buffer, length); - *result = NULL; - return; - } - - { - uint8_t *current(buffer); - - for (size_t offset(0), width; offset != used; offset += width) { - hde64s decode; - hde64_disasm(backup + offset, &decode); - width = decode.len; - //_assert(width != 0 && offset + width <= used); - -#ifdef __LP64__ - if ((decode.modrm & 0xc7) == 0x05) { - if (decode.opcode == 0x8b) { - void *destiny(area + offset + width + int32_t(decode.disp.disp32)); - uint8_t reg(decode.rex_r << 3 | decode.modrm_reg); - MSPushPointer(current, destiny); - MSWritePop(current, reg); - MSWriteMove64(current, reg, reg); - } else { - MSLog(MSLogLevelError, "MS:Error: Unknown RIP-Relative (%.2x %.2x)", decode.opcode, decode.opcode2); - goto copy; - } - } else -#endif - - if (backup[offset] == 0xe8) { - int32_t relative(*reinterpret_cast(backup + offset + 1)); - if (relative == 0) - MSPushPointer(current, area + offset + decode.len); - else { - MSWrite(current, 0xe8); - MSWrite(current, MSSizeOfSkip()); - void *destiny(area + offset + decode.len + relative); - MSWriteSkip(current, MSSizeOfJump(destiny, current + MSSizeOfSkip())); - MSWriteJump(current, destiny); - } - } else if (backup[offset] == 0xeb) - MSWriteJump(current, area + offset + decode.len + - *reinterpret_cast(backup + offset + 1)); - else if (backup[offset] == 0xe9) - MSWriteJump(current, area + offset + decode.len + - *reinterpret_cast(backup + offset + 1)); - else if ( - backup[offset] == 0xe3 || - (backup[offset] & 0xf0) == 0x70 - ) { - MSWrite(current, backup[offset]); - MSWrite(current, 2); - MSWrite(current, 0xeb); - void *destiny(area + offset + decode.len + - *reinterpret_cast(backup + offset + 1)); - MSWrite(current, MSSizeOfJump(destiny, current + 1)); - MSWriteJump(current, destiny); - } else -#ifdef __LP64__ - copy: -#endif - { - MSWrite(current, backup + offset, width); - } - } - - MSWriteJump(current, area + used); - } - - if (mprotect(buffer, length, PROT_READ | PROT_EXEC) == -1) { - MSLog(MSLogLevelError, OBFUSCATE("MS:Error:mprotect():%d"), errno); - goto fail; - } - - *result = buffer; - - if (MSDebug) { - char name[16]; - sprintf(name, OBFUSCATE("%p"), *result); - MSLogHex(buffer, length, name); - } - - } - - { - SubstrateHookMemory code(process, area, used); - uint8_t *current(area); - MSWriteJump(current, target); - for (unsigned offset(0); offset != blank; ++offset) - MSWrite(current, 0x90); - } - - if (MSDebug) { - char name[16]; - sprintf(name, OBFUSCATE("%p"), area); - MSLogHex(area, used + sizeof(uint16_t), name); - } -} - -#endif - -void MSHookFunction(void *symbol, void *replace, void **result) { -#if defined(__i386__) || defined(__arm__) - SubstrateHookFunction(NULL, symbol, replace, result); -#endif -} - -#if defined(__APPLE__) && defined(__arm__) -_extern void _Z14MSHookFunctionPvS_PS_(void *symbol, void *replace, void **result) { - return MSHookFunction(symbol, replace, result); -} -#endif diff --git a/app/src/main/jni/Substrate/SubstrateHook.h b/app/src/main/jni/Substrate/SubstrateHook.h deleted file mode 100644 index ab68a60..0000000 --- a/app/src/main/jni/Substrate/SubstrateHook.h +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef __SUBSTRATEHOOK_H__ -#define __SUBSTRATEHOOK_H__ - - -#include - -#define _extern extern "C" __attribute__((__visibility__("hidden"))) - -#ifdef __cplusplus -extern "C" { -#endif - -void MSHookFunction(void *symbol, void *replace, void **result); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/app/src/main/jni/Substrate/SubstrateLog.hpp b/app/src/main/jni/Substrate/SubstrateLog.hpp deleted file mode 100644 index 3e57280..0000000 --- a/app/src/main/jni/Substrate/SubstrateLog.hpp +++ /dev/null @@ -1,40 +0,0 @@ -/* Cydia Substrate - Powerful Code Insertion Platform - * Copyright (C) 2008-2011 Jay Freeman (saurik) -*/ - -/* GNU Lesser General Public License, Version 3 {{{ */ -/* - * Substrate is free software: you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Substrate is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Substrate. If not, see . -**/ -/* }}} */ - -#ifndef SUBSTRATE_LOG_HPP -#define SUBSTRATE_LOG_HPP - -#if 0 -#include - -#define MSLog(level, format, ...) ((void)__android_log_print(level, "NNNN", format, __VA_ARGS__)) - -#define MSLogLevelNotice ANDROID_LOG_INFO -#define MSLogLevelWarning ANDROID_LOG_WARN -#define MSLogLevelError ANDROID_LOG_ERROR - -#else - -#define MSLog(level, format, ...) printf(format, __VA_ARGS__) - -#endif - -#endif//SUBSTRATE_LOG_HPP diff --git a/app/src/main/jni/Substrate/SubstratePosixMemory.cpp b/app/src/main/jni/Substrate/SubstratePosixMemory.cpp deleted file mode 100644 index b25348b..0000000 --- a/app/src/main/jni/Substrate/SubstratePosixMemory.cpp +++ /dev/null @@ -1,76 +0,0 @@ -/* Cydia Substrate - Powerful Code Insertion Platform - * Copyright (C) 2008-2011 Jay Freeman (saurik) -*/ - -/* GNU Lesser General Public License, Version 3 {{{ */ -/* - * Substrate is free software: you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Substrate is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Substrate. If not, see . -**/ -/* }}} */ - -#define SubstrateInternal -#include "CydiaSubstrate.h" -#include "SubstrateLog.hpp" - -#include - -#include -#include -#include -#include - -extern "C" void __clear_cache (void *beg, void *end); - -struct __SubstrateMemory { - void *address_; - size_t width_; - - __SubstrateMemory(void *address, size_t width) : - address_(address), - width_(width) - { - } -}; - -extern "C" SubstrateMemoryRef SubstrateMemoryCreate(SubstrateAllocatorRef allocator, SubstrateProcessRef process, void *data, size_t size) { - if (allocator != NULL) { - MSLog(MSLogLevelError, OBFUSCATE("MS:Error:allocator != %d"), 0); - return NULL; - } - - if (size == 0) - return NULL; - - int page(getpagesize()); - - uintptr_t base(reinterpret_cast(data) / page * page); - size_t width(((reinterpret_cast(data) + size - 1) / page + 1) * page - base); - void *address(reinterpret_cast(base)); - - if (mprotect(address, width, PROT_READ | PROT_WRITE | PROT_EXEC) == -1) { - MSLog(MSLogLevelError, OBFUSCATE("MS:Error:mprotect() = %d"), errno); - return NULL; - } - - return new __SubstrateMemory(address, width); -} - -extern "C" void SubstrateMemoryRelease(SubstrateMemoryRef memory) { - if (mprotect(memory->address_, memory->width_, PROT_READ | PROT_WRITE | PROT_EXEC) == -1) - MSLog(MSLogLevelError, OBFUSCATE("MS:Error:mprotect() = %d"), errno); - - __clear_cache(reinterpret_cast(memory->address_), reinterpret_cast(memory->address_) + memory->width_); - - delete memory; -} diff --git a/app/src/main/jni/Substrate/SubstrateX86.hpp b/app/src/main/jni/Substrate/SubstrateX86.hpp deleted file mode 100644 index ffe2b06..0000000 --- a/app/src/main/jni/Substrate/SubstrateX86.hpp +++ /dev/null @@ -1,200 +0,0 @@ -/* Cydia Substrate - Powerful Code Insertion Platform - * Copyright (C) 2008-2011 Jay Freeman (saurik) -*/ - -/* GNU Lesser General Public License, Version 3 {{{ */ -/* - * Substrate is free software: you can redistribute it and/or modify it under - * the terms of the GNU Lesser General Public License as published by the - * Free Software Foundation, either version 3 of the License, or (at your - * option) any later version. - * - * Substrate is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public - * License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Substrate. If not, see . -**/ -/* }}} */ - -#ifndef SUBSTRATE_X86_HPP -#define SUBSTRATE_X86_HPP - -#include "Buffer.hpp" - -#ifdef __LP64__ -static const bool ia32 = false; -#else -static const bool ia32 = true; -#endif - -enum I$r { - I$rax, I$rcx, I$rdx, I$rbx, - I$rsp, I$rbp, I$rsi, I$rdi, - I$r8, I$r9, I$r10, I$r11, - I$r12, I$r13, I$r14, I$r15, -}; - -_disused static bool MSIs32BitOffset(uintptr_t target, uintptr_t source) { - intptr_t offset(target - source); - return int32_t(offset) == offset; -} - -_disused static size_t MSSizeOfSkip() { - return 5; -} - -_disused static size_t MSSizeOfPushPointer(uintptr_t target) { - return uint64_t(target) >> 32 == 0 ? 5 : 13; -} - -_disused static size_t MSSizeOfPushPointer(void *target) { - return MSSizeOfPushPointer(reinterpret_cast(target)); -} - -_disused static size_t MSSizeOfJump(bool blind, uintptr_t target, uintptr_t source = 0) { - if (ia32 || !blind && MSIs32BitOffset(target, source + 5)) - return MSSizeOfSkip(); - else - return MSSizeOfPushPointer(target) + 1; -} - -_disused static size_t MSSizeOfJump(uintptr_t target, uintptr_t source) { - return MSSizeOfJump(false, target, source); -} - -_disused static size_t MSSizeOfJump(uintptr_t target) { - return MSSizeOfJump(true, target); -} - -_disused static size_t MSSizeOfJump(void *target, void *source) { - return MSSizeOfJump(reinterpret_cast(target), reinterpret_cast(source)); -} - -_disused static size_t MSSizeOfJump(void *target) { - return MSSizeOfJump(reinterpret_cast(target)); -} - -_disused static void MSWriteSkip(uint8_t *¤t, ssize_t size) { - MSWrite(current, 0xe9); - MSWrite(current, size); -} - -_disused static void MSPushPointer(uint8_t *¤t, uintptr_t target) { - MSWrite(current, 0x68); - MSWrite(current, target); - - if (uint32_t high = uint64_t(target) >> 32) { - MSWrite(current, 0xc7); - MSWrite(current, 0x44); - MSWrite(current, 0x24); - MSWrite(current, 0x04); - MSWrite(current, high); - } -} - -_disused static void MSPushPointer(uint8_t *¤t, void *target) { - return MSPushPointer(current, reinterpret_cast(target)); -} - -_disused static void MSWriteCall(uint8_t *¤t, I$r target) { - if (target >> 3 != 0) - MSWrite(current, 0x40 | (target & 0x08) >> 3); - MSWrite(current, 0xff); - MSWrite(current, 0xd0 | target & 0x07); -} - -_disused static void MSWriteCall(uint8_t *¤t, uintptr_t target) { - uintptr_t source(reinterpret_cast(current)); - - if (ia32 || MSIs32BitOffset(target, source + 5)) { - MSWrite(current, 0xe8); - MSWrite(current, target - (source + 5)); - } else { - MSPushPointer(current, target); - - MSWrite(current, 0x83); - MSWrite(current, 0xc4); - MSWrite(current, 0x08); - - MSWrite(current, 0x67); - MSWrite(current, 0xff); - MSWrite(current, 0x54); - MSWrite(current, 0x24); - MSWrite(current, 0xf8); - } -} - -template -_disused static void MSWriteCall(uint8_t *¤t, Type_ *target) { - return MSWriteCall(current, reinterpret_cast(target)); -} - -_disused static void MSWriteJump(uint8_t *¤t, uintptr_t target) { - uintptr_t source(reinterpret_cast(current)); - - if (ia32 || MSIs32BitOffset(target, source + 5)) - MSWriteSkip(current, target - (source + 5)); - else { - MSPushPointer(current, target); - MSWrite(current, 0xc3); - } -} - -_disused static void MSWriteJump(uint8_t *¤t, void *target) { - return MSWriteJump(current, reinterpret_cast(target)); -} - -_disused static void MSWriteJump(uint8_t *¤t, I$r target) { - if (target >> 3 != 0) - MSWrite(current, 0x40 | (target & 0x08) >> 3); - MSWrite(current, 0xff); - MSWrite(current, 0xe0 | target & 0x07); -} - -_disused static void MSWritePop(uint8_t *¤t, uint8_t target) { - if (target >> 3 != 0) - MSWrite(current, 0x40 | (target & 0x08) >> 3); - MSWrite(current, 0x58 | target & 0x07); -} - -_disused static size_t MSSizeOfPop(uint8_t target) { - return target >> 3 != 0 ? 2 : 1; -} - -_disused static void MSWritePush(uint8_t *¤t, I$r target) { - if (target >> 3 != 0) - MSWrite(current, 0x40 | (target & 0x08) >> 3); - MSWrite(current, 0x50 | target & 0x07); -} - -_disused static void MSWriteAdd(uint8_t *¤t, I$r target, uint8_t source) { - MSWrite(current, 0x83); - MSWrite(current, 0xc4 | target & 0x07); - MSWrite(current, source); -} - -_disused static void MSWriteSet64(uint8_t *¤t, I$r target, uintptr_t source) { - MSWrite(current, 0x48 | (target & 0x08) >> 3 << 2); - MSWrite(current, 0xb8 | target & 0x7); - MSWrite(current, source); -} - -template -_disused static void MSWriteSet64(uint8_t *¤t, I$r target, Type_ *source) { - return MSWriteSet64(current, target, reinterpret_cast(source)); -} - -_disused static void MSWriteMove64(uint8_t *¤t, uint8_t source, uint8_t target) { - MSWrite(current, 0x48 | (target & 0x08) >> 3 << 2 | (source & 0x08) >> 3); - MSWrite(current, 0x8b); - MSWrite(current, (target & 0x07) << 3 | source & 0x07); -} - -_disused static size_t MSSizeOfMove64() { - return 3; -} - -#endif//SUBSTRATE_X86_HPP diff --git a/app/src/main/jni/Substrate/SymbolFinder.cpp b/app/src/main/jni/Substrate/SymbolFinder.cpp deleted file mode 100644 index 07c7607..0000000 --- a/app/src/main/jni/Substrate/SymbolFinder.cpp +++ /dev/null @@ -1,433 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "SymbolFinder.h" - -#define TAG "MSHook" -#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__) -#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__) -/* memory map for libraries */ -#define MAX_NAME_LEN 256 -#define MEMORY_ONLY "[memory]" -struct mm { - char name[MAX_NAME_LEN]; - unsigned long start, end; -}; - -typedef struct symtab *symtab_t; -struct symlist { - Elf32_Sym *sym; /* symbols */ - char *str; /* symbol strings */ - unsigned num; /* number of symbols */ -}; -struct symtab { - struct symlist *st; /* "static" symbols */ - struct symlist *dyn; /* dynamic symbols */ -}; - -static void *xmalloc(size_t size) { - void *p; - p = malloc(size); - if (!p) { - printf(OBFUSCATE("Out of memory\n")); - exit(1); - } - return p; -} - -static int my_pread(int fd, void *buf, size_t count, off_t offset) { - lseek(fd, offset, SEEK_SET); - return read(fd, buf, count); -} - -static struct symlist *get_syms(int fd, Elf32_Shdr *symh, Elf32_Shdr *strh) { - struct symlist *sl, *ret; - int rv; - - ret = NULL; - sl = (struct symlist *) xmalloc(sizeof(struct symlist)); - sl->str = NULL; - sl->sym = NULL; - - /* sanity */ - if (symh->sh_size % sizeof(Elf32_Sym)) { - //printf("elf_error\n"); - goto out; - } - - /* symbol table */ - sl->num = symh->sh_size / sizeof(Elf32_Sym); - sl->sym = (Elf32_Sym *) xmalloc(symh->sh_size); - rv = my_pread(fd, sl->sym, symh->sh_size, symh->sh_offset); - if (0 > rv) { - //perror("read"); - goto out; - } - if (rv != symh->sh_size) { - //printf("elf error\n"); - goto out; - } - - /* string table */ - sl->str = (char *) xmalloc(strh->sh_size); - rv = my_pread(fd, sl->str, strh->sh_size, strh->sh_offset); - if (0 > rv) { - //perror("read"); - goto out; - } - if (rv != strh->sh_size) { - //printf("elf error"); - goto out; - } - - ret = sl; - out: - return ret; -} - -static int do_load(int fd, symtab_t symtab) { - int rv; - size_t size; - Elf32_Ehdr ehdr; - Elf32_Shdr *shdr = NULL, *p; - Elf32_Shdr *dynsymh, *dynstrh; - Elf32_Shdr *symh, *strh; - char *shstrtab = NULL; - int i; - int ret = -1; - - /* elf header */ - rv = read(fd, &ehdr, sizeof(ehdr)); - if (0 > rv) { - LOGD(OBFUSCATE("read\n")); - goto out; - } - if (rv != sizeof(ehdr)) { - LOGD(OBFUSCATE("elf error 1\n")); - goto out; - } - if (strncmp((const char *) ELFMAG, (const char *) ehdr.e_ident, SELFMAG)) { /* sanity */ - LOGD(OBFUSCATE("not an elf\n")); - goto out; - } - if (sizeof(Elf32_Shdr) != ehdr.e_shentsize) { /* sanity */ - LOGD(OBFUSCATE("elf error 2\n")); - goto out; - } - - /* section header table */ - size = ehdr.e_shentsize * ehdr.e_shnum; - shdr = (Elf32_Shdr *) xmalloc(size); - rv = my_pread(fd, shdr, size, ehdr.e_shoff); - if (0 > rv) { - LOGD(OBFUSCATE("read\n")); - goto out; - } - if (rv != size) { - LOGD(OBFUSCATE("elf error 3 %d %d\n"), rv, size); - goto out; - } - - /* section header string table */ - size = shdr[ehdr.e_shstrndx].sh_size; - shstrtab = (char *) xmalloc(size); - rv = my_pread(fd, shstrtab, size, shdr[ehdr.e_shstrndx].sh_offset); - if (0 > rv) { - LOGD(OBFUSCATE("read\n")); - goto out; - } - if (rv != size) { - LOGD(OBFUSCATE("elf error 4 %d %d\n"), rv, size); - goto out; - } - - /* symbol table headers */ - symh = dynsymh = NULL; - strh = dynstrh = NULL; - for (i = 0, p = shdr; i < ehdr.e_shnum; i++, p++) - if (SHT_SYMTAB == p->sh_type) { - if (symh) { - LOGD(OBFUSCATE("too many symbol tables\n")); - goto out; - } - symh = p; - } else if (SHT_DYNSYM == p->sh_type) { - if (dynsymh) { - LOGD(OBFUSCATE("too many symbol tables\n")); - goto out; - } - dynsymh = p; - } else if (SHT_STRTAB == p->sh_type - && !strncmp(shstrtab + p->sh_name, OBFUSCATE(".strtab"), 7)) { - if (strh) { - LOGD(OBFUSCATE("too many string tables\n")); - goto out; - } - strh = p; - } else if (SHT_STRTAB == p->sh_type - && !strncmp(shstrtab + p->sh_name, OBFUSCATE(".dynstr"), 7)) { - if (dynstrh) { - LOGD(OBFUSCATE("too many string tables\n")); - goto out; - } - dynstrh = p; - } - /* sanity checks */ - if ((!dynsymh && dynstrh) || (dynsymh && !dynstrh)) { - LOGD(OBFUSCATE("bad dynamic symbol table\n")); - goto out; - } - if ((!symh && strh) || (symh && !strh)) { - LOGD(OBFUSCATE("bad symbol table\n")); - goto out; - } - if (!dynsymh && !symh) { - LOGD(OBFUSCATE("no symbol table\n")); - goto out; - } - - /* symbol tables */ - if (dynsymh) - symtab->dyn = get_syms(fd, dynsymh, dynstrh); - if (symh) - symtab->st = get_syms(fd, symh, strh); - ret = 0; - out: - free(shstrtab); - free(shdr); - return ret; -} - -static symtab_t load_symtab(char *filename) { - int fd; - symtab_t symtab; - - symtab = (symtab_t) xmalloc(sizeof(*symtab)); - memset(symtab, 0, sizeof(*symtab)); - - fd = open(filename, O_RDONLY); - if (0 > fd) { - LOGE(OBFUSCATE("%s open\n"), __func__); - return NULL; - } - if (0 > do_load(fd, symtab)) { - LOGE(OBFUSCATE("Error ELF parsing %s\n"), filename); - free(symtab); - symtab = NULL; - } - close(fd); - return symtab; -} - -static int load_memmap(pid_t pid, struct mm *mm, int *nmmp) { - size_t buf_size = 0x40000; - char *p_buf = (char *) malloc(buf_size); // increase this if needed for larger "maps" - char name[MAX_NAME_LEN] = {0}; - char *p; - unsigned long start, end; - struct mm *m; - int nmm = 0; - int fd, rv; - int i; - - sprintf(p_buf, OBFUSCATE("/proc/%d/maps"), pid); - fd = open(p_buf, O_RDONLY); - if (0 > fd) { - LOGE(OBFUSCATE("Can't open %s for reading\n"), p_buf); - free(p_buf); - return -1; - } - - /* Zero to ensure data is null terminated */ - memset(p_buf, 0, buf_size); - - p = p_buf; - while (1) { - rv = read(fd, p, buf_size - (p - p_buf)); - if (0 > rv) { - LOGE(OBFUSCATE("%s read"), __FUNCTION__); - free(p_buf); - return -1; - } - if (0 == rv) - break; - p += rv; - if (p - p_buf >= buf_size) { - LOGE(OBFUSCATE("Too many memory mapping\n")); - free(p_buf); - return -1; - } - } - close(fd); - - p = strtok(p_buf, "\n"); - m = mm; - while (p) { - /* parse current map line */ - rv = sscanf(p, OBFUSCATE("%08lx-%08lx %*s %*s %*s %*s %s\n"), &start, &end, name); - - p = strtok(NULL, "\n"); - - if (rv == 2) { - m = &mm[nmm++]; - m->start = start; - m->end = end; - memcpy(m->name, MEMORY_ONLY, sizeof(MEMORY_ONLY)); - continue; - } - - /* search backward for other mapping with same name */ - for (i = nmm - 1; i >= 0; i--) { - m = &mm[i]; - if (!strcmp(m->name, name)) - break; - } - - if (i >= 0) { - if (start < m->start) - m->start = start; - if (end > m->end) - m->end = end; - } else { - /* new entry */ - m = &mm[nmm++]; - m->start = start; - m->end = end; - memcpy(m->name, name, strlen(name)); - } - } - - *nmmp = nmm; - free(p_buf); - return 0; -} - -/* Find libc in MM, storing no more than LEN-1 chars of - its name in NAME and set START to its starting - address. If libc cannot be found return -1 and - leave NAME and START untouched. Otherwise return 0 - and null-terminated NAME. */ -static int find_libname(const char *libn, char *name, int len, unsigned long *start, - struct mm *mm, int nmm) { - int i; - struct mm *m; - char *p; - for (i = 0, m = mm; i < nmm; i++, m++) { - if (!strcmp(m->name, MEMORY_ONLY)) - continue; - p = strrchr(m->name, '/'); - if (!p) - continue; - p++; - if (strncmp(libn, p, strlen(libn))) - continue; - p += strlen(libn); - - /* here comes our crude test -> 'libc.so' or 'libc-[0-9]' */ - if (!strncmp(OBFUSCATE("so"), p, 2) || 1) // || (p[0] == '-' && isdigit(p[1]))) - break; - } - if (i >= nmm) - /* not found */ - return -1; - - *start = m->start; - strncpy(name, m->name, len); - if (strlen(m->name) >= len) - name[len - 1] = '\0'; - - mprotect((void *) m->start, m->end - m->start, - PROT_READ | PROT_WRITE | PROT_EXEC); - return 0; -} - -static int lookup2(struct symlist *sl, unsigned char type, char *name, - unsigned long *val) { - Elf32_Sym *p; - int len; - int i; - - len = strlen(name); - for (i = 0, p = sl->sym; i < sl->num; i++, p++) { - //LOGD("name: %s %x\n", sl->str+p->st_name, p->st_value) - if (!strncmp(sl->str + p->st_name, name, len) - && *(sl->str + p->st_name + len) == 0 - && ELF32_ST_TYPE(p->st_info) == type) { - //if (p->st_value != 0) { - *val = p->st_value; - return 0; - //} - } - } - return -1; -} - -static int lookup_sym(symtab_t s, unsigned char type, char *name, - unsigned long *val) { - if (s->dyn && !lookup2(s->dyn, type, name, val)) - return 0; - if (s->st && !lookup2(s->st, type, name, val)) - return 0; - return -1; -} - -static int lookup_func_sym(symtab_t s, char *name, unsigned long *val) { - return lookup_sym(s, STT_FUNC, name, val); -} - -int find_name(pid_t pid, const char *name, const char *libn, - unsigned long *addr) { - struct mm mm[1000] = {0}; - unsigned long libcaddr; - int nmm; - char libc[1024] = {0}; - symtab_t s; - - if (0 > load_memmap(pid, mm, &nmm)) { - LOGD(OBFUSCATE("cannot read memory map\n")); - return -1; - } - if (0 - > find_libname((char *) libn, (char *) libc, sizeof(libc), - &libcaddr, mm, nmm)) { - LOGD(OBFUSCATE("cannot find lib: %s\n"), libn); - return -1; - } - //LOGD("lib: >%s<\n", libc) - s = load_symtab(libc); - if (!s) { - LOGD(OBFUSCATE("cannot read symbol table\n")); - return -1; - } - if (0 > lookup_func_sym(s, (char *) name, addr)) { - LOGD(OBFUSCATE("cannot find function: %s\n"), name); - return -1; - } - *addr += libcaddr; - return 0; -} - -int find_libbase(pid_t pid, const char *libn, unsigned long *addr) { - struct mm mm[1000] = {0}; - unsigned long libcaddr; - int nmm; - char libc[1024] = {0}; - symtab_t s; - - if (0 > load_memmap(pid, mm, &nmm)) { - LOGD(OBFUSCATE("cannot read memory map\n")); - return -1; - } - if (0 > find_libname(libn, libc, sizeof(libc), &libcaddr, mm, nmm)) { - LOGD(OBFUSCATE("cannot find lib\n")); - return -1; - } - *addr = libcaddr; - return 0; -} diff --git a/app/src/main/jni/Substrate/SymbolFinder.h b/app/src/main/jni/Substrate/SymbolFinder.h deleted file mode 100644 index 7b99910..0000000 --- a/app/src/main/jni/Substrate/SymbolFinder.h +++ /dev/null @@ -1,8 +0,0 @@ -#ifndef SYMBOL_FINDER -#define SYMBOL_FINDER - -#include - -extern int find_name(pid_t pid, const char *name,const char *libn, unsigned long *addr); -extern int find_libbase(pid_t pid, const char *libn, unsigned long *addr); -#endif \ No newline at end of file diff --git a/app/src/main/jni/Substrate/hde64.c b/app/src/main/jni/Substrate/hde64.c deleted file mode 100644 index d69f0c6..0000000 --- a/app/src/main/jni/Substrate/hde64.c +++ /dev/null @@ -1,332 +0,0 @@ -/* - * Hacker Disassembler Engine 64 C - * Copyright (c) 2008-2009, Vyacheslav Patkov. - * All rights reserved. - * - */ - -#include -#include - -#include "hde64.h" -#include "table64.h" - -unsigned int hde64_disasm(const void *code, hde64s *hs) -{ - uint8_t x, c, *p = (uint8_t *)code, cflags, opcode, pref = 0; - uint8_t *ht = hde64_table, m_mod, m_reg, m_rm, disp_size = 0; - uint8_t op64 = 0; - - memset(hs,0,sizeof(hde64s)); - char *tmp=(char*)hs; - - for (x = 16; x; x--) - switch (c = *p++) { - case 0xf3: - hs->p_rep = c; - pref |= PRE_F3; - break; - case 0xf2: - hs->p_rep = c; - pref |= PRE_F2; - break; - case 0xf0: - hs->p_lock = c; - pref |= PRE_LOCK; - break; - case 0x26: case 0x2e: case 0x36: - case 0x3e: case 0x64: case 0x65: - hs->p_seg = c; - pref |= PRE_SEG; - break; - case 0x66: - hs->p_66 = c; - pref |= PRE_66; - break; - case 0x67: - hs->p_67 = c; - pref |= PRE_67; - break; - default: - goto pref_done; - } - pref_done: - - hs->flags = (uint32_t)pref << 23; - - if (!pref) - pref |= PRE_NONE; - - if ((c & 0xf0) == 0x40) { - hs->flags |= F_PREFIX_REX; - if ((hs->rex_w = (c & 0xf) >> 3) && (*p & 0xf8) == 0xb8) - op64++; - hs->rex_r = (c & 7) >> 2; - hs->rex_x = (c & 3) >> 1; - hs->rex_b = c & 1; - if (((c = *p++) & 0xf0) == 0x40) { - opcode = c; - goto error_opcode; - } - } - - if ((hs->opcode = c) == 0x0f) { - hs->opcode2 = c = *p++; - ht += DELTA_OPCODES; - } else if (c >= 0xa0 && c <= 0xa3) { - op64++; - if (pref & PRE_67) - pref |= PRE_66; - else - pref &= ~PRE_66; - } - - opcode = c; - cflags = ht[ht[opcode / 4] + (opcode % 4)]; - - if (cflags == C_ERROR) { - error_opcode: - hs->flags |= F_ERROR | F_ERROR_OPCODE; - cflags = 0; - if ((opcode & -3) == 0x24) - cflags++; - } - - x = 0; - if (cflags & C_GROUP) { - uint16_t t; - t = *(uint16_t *)(ht + (cflags & 0x7f)); - cflags = (uint8_t)t; - x = (uint8_t)(t >> 8); - } - - if (hs->opcode2) { - ht = hde64_table + DELTA_PREFIXES; - if (ht[ht[opcode / 4] + (opcode % 4)] & pref) - hs->flags |= F_ERROR | F_ERROR_OPCODE; - } - - if (cflags & C_MODRM) { - hs->flags |= F_MODRM; - hs->modrm = c = *p++; - hs->modrm_mod = m_mod = c >> 6; - hs->modrm_rm = m_rm = c & 7; - hs->modrm_reg = m_reg = (c & 0x3f) >> 3; - - if (x && ((x << m_reg) & 0x80)) - hs->flags |= F_ERROR | F_ERROR_OPCODE; - - if (!hs->opcode2 && opcode >= 0xd9 && opcode <= 0xdf) { - uint8_t t = opcode - 0xd9; - if (m_mod == 3) { - ht = hde64_table + DELTA_FPU_MODRM + t*8; - t = ht[m_reg] << m_rm; - } else { - ht = hde64_table + DELTA_FPU_REG; - t = ht[t] << m_reg; - } - if (t & 0x80) - hs->flags |= F_ERROR | F_ERROR_OPCODE; - } - - if (pref & PRE_LOCK) { - if (m_mod == 3) { - hs->flags |= F_ERROR | F_ERROR_LOCK; - } else { - uint8_t *table_end, op = opcode; - if (hs->opcode2) { - ht = hde64_table + DELTA_OP2_LOCK_OK; - table_end = ht + DELTA_OP_ONLY_MEM - DELTA_OP2_LOCK_OK; - } else { - ht = hde64_table + DELTA_OP_LOCK_OK; - table_end = ht + DELTA_OP2_LOCK_OK - DELTA_OP_LOCK_OK; - op &= -2; - } - for (; ht != table_end; ht++) - if (*ht++ == op) { - if (!((*ht << m_reg) & 0x80)) - goto no_lock_error; - else - break; - } - hs->flags |= F_ERROR | F_ERROR_LOCK; - no_lock_error: - ; - } - } - - if (hs->opcode2) { - switch (opcode) { - case 0x20: case 0x22: - m_mod = 3; - if (m_reg > 4 || m_reg == 1) - goto error_operand; - else - goto no_error_operand; - case 0x21: case 0x23: - m_mod = 3; - if (m_reg == 4 || m_reg == 5) - goto error_operand; - else - goto no_error_operand; - } - } else { - switch (opcode) { - case 0x8c: - if (m_reg > 5) - goto error_operand; - else - goto no_error_operand; - case 0x8e: - if (m_reg == 1 || m_reg > 5) - goto error_operand; - else - goto no_error_operand; - } - } - - if (m_mod == 3) { - uint8_t *table_end; - if (hs->opcode2) { - ht = hde64_table + DELTA_OP2_ONLY_MEM; - table_end = ht + sizeof(hde64_table) - DELTA_OP2_ONLY_MEM; - } else { - ht = hde64_table + DELTA_OP_ONLY_MEM; - table_end = ht + DELTA_OP2_ONLY_MEM - DELTA_OP_ONLY_MEM; - } - for (; ht != table_end; ht += 2) - if (*ht++ == opcode) { - if (*ht++ & pref && !((*ht << m_reg) & 0x80)) - goto error_operand; - else - break; - } - goto no_error_operand; - } else if (hs->opcode2) { - switch (opcode) { - case 0x50: case 0xd7: case 0xf7: - if (pref & (PRE_NONE | PRE_66)) - goto error_operand; - break; - case 0xd6: - if (pref & (PRE_F2 | PRE_F3)) - goto error_operand; - break; - case 0xc5: - goto error_operand; - } - goto no_error_operand; - } else - goto no_error_operand; - - error_operand: - hs->flags |= F_ERROR | F_ERROR_OPERAND; - no_error_operand: - - c = *p++; - if (m_reg <= 1) { - if (opcode == 0xf6) - cflags |= C_IMM8; - else if (opcode == 0xf7) - cflags |= C_IMM_P66; - } - - switch (m_mod) { - case 0: - if (pref & PRE_67) { - if (m_rm == 6) - disp_size = 2; - } else - if (m_rm == 5) - disp_size = 4; - break; - case 1: - disp_size = 1; - break; - case 2: - disp_size = 2; - if (!(pref & PRE_67)) - disp_size <<= 1; - } - - if (m_mod != 3 && m_rm == 4) { - hs->flags |= F_SIB; - p++; - hs->sib = c; - hs->sib_scale = c >> 6; - hs->sib_index = (c & 0x3f) >> 3; - if ((hs->sib_base = c & 7) == 5 && !(m_mod & 1)) - disp_size = 4; - } - - p--; - switch (disp_size) { - case 1: - hs->flags |= F_DISP8; - hs->disp.disp8 = *p; - break; - case 2: - hs->flags |= F_DISP16; - hs->disp.disp16 = *(uint16_t *)p; - break; - case 4: - hs->flags |= F_DISP32; - hs->disp.disp32 = *(uint32_t *)p; - } - p += disp_size; - } else if (pref & PRE_LOCK) - hs->flags |= F_ERROR | F_ERROR_LOCK; - - if (cflags & C_IMM_P66) { - if (cflags & C_REL32) { - if (pref & PRE_66) { - hs->flags |= F_IMM16 | F_RELATIVE; - hs->imm.imm16 = *(uint16_t *)p; - p += 2; - goto disasm_done; - } - goto rel32_ok; - } - if (op64) { - hs->flags |= F_IMM64; - hs->imm.imm64 = *(uint64_t *)p; - p += 8; - } else if (!(pref & PRE_66)) { - hs->flags |= F_IMM32; - hs->imm.imm32 = *(uint32_t *)p; - p += 4; - } else - goto imm16_ok; - } - - - if (cflags & C_IMM16) { - imm16_ok: - hs->flags |= F_IMM16; - hs->imm.imm16 = *(uint16_t *)p; - p += 2; - } - if (cflags & C_IMM8) { - hs->flags |= F_IMM8; - hs->imm.imm8 = *p++; - } - - if (cflags & C_REL32) { - rel32_ok: - hs->flags |= F_IMM32 | F_RELATIVE; - hs->imm.imm32 = *(uint32_t *)p; - p += 4; - } else if (cflags & C_REL8) { - hs->flags |= F_IMM8 | F_RELATIVE; - hs->imm.imm8 = *p++; - } - - disasm_done: - - if ((hs->len = (uint8_t)(p-(uint8_t *)code)) > 15) { - hs->flags |= F_ERROR | F_ERROR_LENGTH; - hs->len = 15; - } - - return (unsigned int)hs->len; -} diff --git a/app/src/main/jni/Substrate/hde64.h b/app/src/main/jni/Substrate/hde64.h deleted file mode 100644 index 2fcc4cb..0000000 --- a/app/src/main/jni/Substrate/hde64.h +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Hacker Disassembler Engine 64 - * Copyright (c) 2008-2009, Vyacheslav Patkov. - * All rights reserved. - * - * hde64.h: C/C++ header file - * - */ - -#ifndef _HDE64_H_ -#define _HDE64_H_ - -/* stdint.h - C99 standard header - * http://en.wikipedia.org/wiki/stdint.h - * - * if your compiler doesn't contain "stdint.h" header (for - * example, Microsoft Visual C++), you can download file: - * http://www.azillionmonkeys.com/qed/pstdint.h - * and change next line to: - * #include "pstdint.h" - */ -#include - -#define F_MODRM 0x00000001 -#define F_SIB 0x00000002 -#define F_IMM8 0x00000004 -#define F_IMM16 0x00000008 -#define F_IMM32 0x00000010 -#define F_IMM64 0x00000020 -#define F_DISP8 0x00000040 -#define F_DISP16 0x00000080 -#define F_DISP32 0x00000100 -#define F_RELATIVE 0x00000200 -#define F_ERROR 0x00001000 -#define F_ERROR_OPCODE 0x00002000 -#define F_ERROR_LENGTH 0x00004000 -#define F_ERROR_LOCK 0x00008000 -#define F_ERROR_OPERAND 0x00010000 -#define F_PREFIX_REPNZ 0x01000000 -#define F_PREFIX_REPX 0x02000000 -#define F_PREFIX_REP 0x03000000 -#define F_PREFIX_66 0x04000000 -#define F_PREFIX_67 0x08000000 -#define F_PREFIX_LOCK 0x10000000 -#define F_PREFIX_SEG 0x20000000 -#define F_PREFIX_REX 0x40000000 -#define F_PREFIX_ANY 0x7f000000 - -#define PREFIX_SEGMENT_CS 0x2e -#define PREFIX_SEGMENT_SS 0x36 -#define PREFIX_SEGMENT_DS 0x3e -#define PREFIX_SEGMENT_ES 0x26 -#define PREFIX_SEGMENT_FS 0x64 -#define PREFIX_SEGMENT_GS 0x65 -#define PREFIX_LOCK 0xf0 -#define PREFIX_REPNZ 0xf2 -#define PREFIX_REPX 0xf3 -#define PREFIX_OPERAND_SIZE 0x66 -#define PREFIX_ADDRESS_SIZE 0x67 - -#pragma pack(push,1) - -typedef struct { - uint8_t len; - uint8_t p_rep; - uint8_t p_lock; - uint8_t p_seg; - uint8_t p_66; - uint8_t p_67; - uint8_t rex; - uint8_t rex_w; - uint8_t rex_r; - uint8_t rex_x; - uint8_t rex_b; - uint8_t opcode; - uint8_t opcode2; - uint8_t modrm; - uint8_t modrm_mod; - uint8_t modrm_reg; - uint8_t modrm_rm; - uint8_t sib; - uint8_t sib_scale; - uint8_t sib_index; - uint8_t sib_base; - union { - uint8_t imm8; - uint16_t imm16; - uint32_t imm32; - uint64_t imm64; - } imm; - union { - uint8_t disp8; - uint16_t disp16; - uint32_t disp32; - } disp; - uint32_t flags; -} hde64s; - -#pragma pack(pop) - -#ifdef __cplusplus -extern "C" { -#endif - -/* __cdecl */ -unsigned int hde64_disasm(const void *code, hde64s *hs); - -#ifdef __cplusplus -} -#endif - -#endif /* _HDE64_H_ */ diff --git a/app/src/main/jni/Substrate/table64.h b/app/src/main/jni/Substrate/table64.h deleted file mode 100644 index 144f290..0000000 --- a/app/src/main/jni/Substrate/table64.h +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Hacker Disassembler Engine 64 C - * Copyright (c) 2008-2009, Vyacheslav Patkov. - * All rights reserved. - * - */ - -#define C_NONE 0x00 -#define C_MODRM 0x01 -#define C_IMM8 0x02 -#define C_IMM16 0x04 -#define C_IMM_P66 0x10 -#define C_REL8 0x20 -#define C_REL32 0x40 -#define C_GROUP 0x80 -#define C_ERROR 0xff - -#define PRE_ANY 0x00 -#define PRE_NONE 0x01 -#define PRE_F2 0x02 -#define PRE_F3 0x04 -#define PRE_66 0x08 -#define PRE_67 0x10 -#define PRE_LOCK 0x20 -#define PRE_SEG 0x40 -#define PRE_ALL 0xff - -#define DELTA_OPCODES 0x4a -#define DELTA_FPU_REG 0xfd -#define DELTA_FPU_MODRM 0x104 -#define DELTA_PREFIXES 0x13c -#define DELTA_OP_LOCK_OK 0x1ae -#define DELTA_OP2_LOCK_OK 0x1c6 -#define DELTA_OP_ONLY_MEM 0x1d8 -#define DELTA_OP2_ONLY_MEM 0x1e7 - -unsigned char hde64_table[] = { - 0xa5,0xaa,0xa5,0xb8,0xa5,0xaa,0xa5,0xaa,0xa5,0xb8,0xa5,0xb8,0xa5,0xb8,0xa5, - 0xb8,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xac,0xc0,0xcc,0xc0,0xa1,0xa1, - 0xa1,0xa1,0xb1,0xa5,0xa5,0xa6,0xc0,0xc0,0xd7,0xda,0xe0,0xc0,0xe4,0xc0,0xea, - 0xea,0xe0,0xe0,0x98,0xc8,0xee,0xf1,0xa5,0xd3,0xa5,0xa5,0xa1,0xea,0x9e,0xc0, - 0xc0,0xc2,0xc0,0xe6,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0xab, - 0x8b,0x90,0x64,0x5b,0x5b,0x5b,0x5b,0x5b,0x92,0x5b,0x5b,0x76,0x90,0x92,0x92, - 0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x6a,0x73,0x90, - 0x5b,0x52,0x52,0x52,0x52,0x5b,0x5b,0x5b,0x5b,0x77,0x7c,0x77,0x85,0x5b,0x5b, - 0x70,0x5b,0x7a,0xaf,0x76,0x76,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b, - 0x5b,0x5b,0x86,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xd5,0x03,0xcc,0x01,0xbc, - 0x03,0xf0,0x03,0x03,0x04,0x00,0x50,0x50,0x50,0x50,0xff,0x20,0x20,0x20,0x20, - 0x01,0x01,0x01,0x01,0xc4,0x02,0x10,0xff,0xff,0xff,0x01,0x00,0x03,0x11,0xff, - 0x03,0xc4,0xc6,0xc8,0x02,0x10,0x00,0xff,0xcc,0x01,0x01,0x01,0x00,0x00,0x00, - 0x00,0x01,0x01,0x03,0x01,0xff,0xff,0xc0,0xc2,0x10,0x11,0x02,0x03,0x01,0x01, - 0x01,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x10, - 0x10,0x10,0x10,0x02,0x10,0x00,0x00,0xc6,0xc8,0x02,0x02,0x02,0x02,0x06,0x00, - 0x04,0x00,0x02,0xff,0x00,0xc0,0xc2,0x01,0x01,0x03,0x03,0x03,0xca,0x40,0x00, - 0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00,0x00,0x00,0x00, - 0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0xff,0x00, - 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff, - 0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0x00,0x00, - 0xff,0x40,0x40,0x40,0x40,0x41,0x49,0x40,0x40,0x40,0x40,0x4c,0x42,0x40,0x40, - 0x40,0x40,0x40,0x40,0x40,0x40,0x4f,0x44,0x53,0x40,0x40,0x40,0x44,0x57,0x43, - 0x5c,0x40,0x60,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40, - 0x40,0x40,0x64,0x66,0x6e,0x6b,0x40,0x40,0x6a,0x46,0x40,0x40,0x44,0x46,0x40, - 0x40,0x5b,0x44,0x40,0x40,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x01,0x06, - 0x06,0x02,0x06,0x06,0x00,0x06,0x00,0x0a,0x0a,0x00,0x00,0x00,0x02,0x07,0x07, - 0x06,0x02,0x0d,0x06,0x06,0x06,0x0e,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04, - 0x04,0x04,0x05,0x06,0x06,0x06,0x00,0x00,0x00,0x0e,0x00,0x00,0x08,0x00,0x10, - 0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01,0x86,0x00, - 0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba,0xf8,0xbb, - 0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00,0xc4,0xff, - 0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00,0x13,0x09, - 0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07,0xb2,0xff, - 0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf,0xe7,0x08, - 0x00,0xf0,0x02,0x00 -}; diff --git a/app/src/main/jni/xDL/xdl.c b/app/src/main/jni/xDL/xdl.c new file mode 100644 index 0000000..b467645 --- /dev/null +++ b/app/src/main/jni/xDL/xdl.c @@ -0,0 +1,1014 @@ +// Copyright (c) 2020-2025 HexHacking Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2020-10-04. + +#include "xdl.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xdl_iterate.h" +#include "xdl_linker.h" +#include "xdl_lzma.h" +#include "xdl_util.h" + +#ifndef STT_GNU_IFUNC +#define STT_GNU_IFUNC 10 +#endif + +#ifndef __LP64__ +#define XDL_LIB_PATH "/system/lib" +#else +#define XDL_LIB_PATH "/system/lib64" +#endif + +#define XDL_DYNSYM_IS_EXPORT_SYM(shndx) (SHN_UNDEF != (shndx)) +#define XDL_SYMTAB_IS_EXPORT_SYM(shndx) \ + (SHN_UNDEF != (shndx) && !((shndx) >= SHN_LORESERVE && (shndx) <= SHN_HIRESERVE)) + +extern __attribute((weak)) unsigned long int getauxval(unsigned long int); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" + +typedef struct xdl { + char *pathname; + uintptr_t load_bias; + const ElfW(Phdr) *dlpi_phdr; + ElfW(Half) dlpi_phnum; + + struct xdl *next; // to next xdl obj for cache in xdl_addr() + void *linker_handle; // hold handle returned by xdl_linker_force_dlopen() + + // + // (1) for searching symbols from .dynsym + // + + bool dynsym_try_load; + ElfW(Sym) *dynsym; // .dynsym + const char *dynstr; // .dynstr + + // .hash (SYSV hash for .dynstr) + struct { + const uint32_t *buckets; + uint32_t buckets_cnt; + const uint32_t *chains; + uint32_t chains_cnt; + } sysv_hash; + + // .gnu.hash (GNU hash for .dynstr) + struct { + const uint32_t *buckets; + uint32_t buckets_cnt; + const uint32_t *chains; + uint32_t symoffset; + const ElfW(Addr) *bloom; + uint32_t bloom_cnt; + uint32_t bloom_shift; + } gnu_hash; + + // + // (2) for searching symbols from .symtab + // + + bool symtab_try_load; + uintptr_t base; + + ElfW(Sym) *symtab; // .symtab + size_t symtab_cnt; + char *strtab; // .strtab + size_t strtab_sz; +} xdl_t; + +#pragma clang diagnostic pop + +static ElfW(Sym) *xdl_sym_by_addr(void *handle, void *addr); +static ElfW(Sym) *xdl_dsym_by_addr(void *handle, void *addr); + +// load from memory +static int xdl_dynsym_load(xdl_t *self) { + // find the dynamic segment + ElfW(Dyn) *dynamic = NULL; + for (size_t i = 0; i < self->dlpi_phnum; i++) { + const ElfW(Phdr) *phdr = &(self->dlpi_phdr[i]); + if (PT_DYNAMIC == phdr->p_type) { + dynamic = (ElfW(Dyn) *)(self->load_bias + phdr->p_vaddr); + break; + } + } + if (NULL == dynamic) return -1; + + // iterate the dynamic segment + for (ElfW(Dyn) *entry = dynamic; entry && entry->d_tag != DT_NULL; entry++) { + switch (entry->d_tag) { + case DT_SYMTAB: //.dynsym + self->dynsym = (ElfW(Sym) *)(self->load_bias + entry->d_un.d_ptr); + break; + case DT_STRTAB: //.dynstr + self->dynstr = (const char *)(self->load_bias + entry->d_un.d_ptr); + break; + case DT_HASH: //.hash + self->sysv_hash.buckets_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[0]; + self->sysv_hash.chains_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[1]; + self->sysv_hash.buckets = &(((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[2]); + self->sysv_hash.chains = &(self->sysv_hash.buckets[self->sysv_hash.buckets_cnt]); + break; + case DT_GNU_HASH: //.gnu.hash + self->gnu_hash.buckets_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[0]; + self->gnu_hash.symoffset = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[1]; + self->gnu_hash.bloom_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[2]; + self->gnu_hash.bloom_shift = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[3]; + self->gnu_hash.bloom = (const ElfW(Addr) *)(self->load_bias + entry->d_un.d_ptr + 16); + self->gnu_hash.buckets = (const uint32_t *)(&(self->gnu_hash.bloom[self->gnu_hash.bloom_cnt])); + self->gnu_hash.chains = (const uint32_t *)(&(self->gnu_hash.buckets[self->gnu_hash.buckets_cnt])); + break; + default: + break; + } + } + + if (NULL == self->dynsym || NULL == self->dynstr || + (0 == self->sysv_hash.buckets_cnt && 0 == self->gnu_hash.buckets_cnt)) { + self->dynsym = NULL; + self->dynstr = NULL; + self->sysv_hash.buckets_cnt = 0; + self->gnu_hash.buckets_cnt = 0; + return -1; + } + + return 0; +} + +static void *xdl_read_file_to_heap(int file_fd, size_t file_sz, size_t data_offset, size_t data_len) { + if (0 == data_len) return NULL; + if (data_offset >= file_sz) return NULL; + if (data_offset + data_len > file_sz) return NULL; + + if (data_offset != (size_t)lseek(file_fd, (off_t)data_offset, SEEK_SET)) return NULL; + + void *data = malloc(data_len); + if (NULL == data) return NULL; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wgnu-statement-expression" + if ((ssize_t)data_len != XDL_UTIL_TEMP_FAILURE_RETRY(read(file_fd, data, data_len))) +#pragma clang diagnostic pop + { + free(data); + return NULL; + } + + return data; +} + +static void *xdl_read_file_to_heap_by_section(int file_fd, size_t file_sz, ElfW(Shdr) *shdr) { + return xdl_read_file_to_heap(file_fd, file_sz, (size_t)shdr->sh_offset, shdr->sh_size); +} + +static void *xdl_read_memory_to_heap(void *mem, size_t mem_sz, size_t data_offset, size_t data_len) { + if (0 == data_len) return NULL; + if (data_offset >= mem_sz) return NULL; + if (data_offset + data_len > mem_sz) return NULL; + + void *data = malloc(data_len); + if (NULL == data) return NULL; + + memcpy(data, (void *)((uintptr_t)mem + data_offset), data_len); + return data; +} + +static void *xdl_read_memory_to_heap_by_section(void *mem, size_t mem_sz, ElfW(Shdr) *shdr) { + return xdl_read_memory_to_heap(mem, mem_sz, (size_t)shdr->sh_offset, shdr->sh_size); +} + +static void *xdl_get_memory(void *mem, size_t mem_sz, size_t data_offset, size_t data_len) { + if (0 == data_len) return NULL; + if (data_offset >= mem_sz) return NULL; + if (data_offset + data_len > mem_sz) return NULL; + + return (void *)((uintptr_t)mem + data_offset); +} + +static void *xdl_get_memory_by_section(void *mem, size_t mem_sz, ElfW(Shdr) *shdr) { + return xdl_get_memory(mem, mem_sz, (size_t)shdr->sh_offset, shdr->sh_size); +} + +// load from disk and memory +static int xdl_symtab_load_from_debugdata(xdl_t *self, int file_fd, size_t file_sz, + ElfW(Shdr) *shdr_debugdata) { + void *debugdata = NULL; + ElfW(Shdr) *shdrs = NULL; + int r = -1; + + // get zipped .gnu_debugdata + uint8_t *debugdata_zip = (uint8_t *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdr_debugdata); + if (NULL == debugdata_zip) return -1; + + // get unzipped .gnu_debugdata + size_t debugdata_sz; + if (0 != xdl_lzma_decompress(debugdata_zip, shdr_debugdata->sh_size, (uint8_t **)&debugdata, &debugdata_sz)) + goto end; + + // get ELF header + ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)debugdata; + if (0 == ehdr->e_shnum || ehdr->e_shentsize != sizeof(ElfW(Shdr))) goto end; + + // get section headers + shdrs = (ElfW(Shdr) *)xdl_read_memory_to_heap(debugdata, debugdata_sz, (size_t)ehdr->e_shoff, + ehdr->e_shentsize * ehdr->e_shnum); + if (NULL == shdrs) goto end; + + // get .shstrtab + if (SHN_UNDEF == ehdr->e_shstrndx || ehdr->e_shstrndx >= ehdr->e_shnum) goto end; + char *shstrtab = (char *)xdl_get_memory_by_section(debugdata, debugdata_sz, shdrs + ehdr->e_shstrndx); + if (NULL == shstrtab) goto end; + + // find .symtab & .strtab + for (ElfW(Shdr) *shdr = shdrs; shdr < shdrs + ehdr->e_shnum; shdr++) { + char *shdr_name = shstrtab + shdr->sh_name; + + if (SHT_SYMTAB == shdr->sh_type && 0 == strcmp(".symtab", shdr_name)) { + // get & check associated .strtab section + if (shdr->sh_link >= ehdr->e_shnum) continue; + ElfW(Shdr) *shdr_strtab = shdrs + shdr->sh_link; + if (SHT_STRTAB != shdr_strtab->sh_type) continue; + + // get .symtab & .strtab + ElfW(Sym) *symtab = (ElfW(Sym) *)xdl_read_memory_to_heap_by_section(debugdata, debugdata_sz, shdr); + if (NULL == symtab) continue; + char *strtab = (char *)xdl_read_memory_to_heap_by_section(debugdata, debugdata_sz, shdr_strtab); + if (NULL == strtab) { + free(symtab); + continue; + } + + // OK + self->symtab = symtab; + self->symtab_cnt = shdr->sh_size / shdr->sh_entsize; + self->strtab = strtab; + self->strtab_sz = shdr_strtab->sh_size; + r = 0; + break; + } + } + +end: + free(debugdata_zip); + if (NULL != debugdata) free(debugdata); + if (NULL != shdrs) free(shdrs); + return r; +} + +// load from disk and memory +static int xdl_symtab_load(xdl_t *self) { + if ('[' == self->pathname[0]) return -1; + + int r = -1; + ElfW(Shdr) *shdrs = NULL; + char *shstrtab = NULL; + + // get base address + uintptr_t vaddr_min = UINTPTR_MAX; + for (size_t i = 0; i < self->dlpi_phnum; i++) { + const ElfW(Phdr) *phdr = &(self->dlpi_phdr[i]); + if (PT_LOAD == phdr->p_type) { + if (vaddr_min > phdr->p_vaddr) vaddr_min = phdr->p_vaddr; + } + } + if (UINTPTR_MAX == vaddr_min) return -1; + self->base = self->load_bias + vaddr_min; + + // open file + int flags = O_RDONLY | O_CLOEXEC; + int file_fd; + if ('/' == self->pathname[0]) { + file_fd = open(self->pathname, flags); + } else { + char full_pathname[1024]; + // try the fast method + snprintf(full_pathname, sizeof(full_pathname), "%s/%s", XDL_LIB_PATH, self->pathname); + file_fd = open(full_pathname, flags); + if (file_fd < 0) { + // try the slow method + if (0 != xdl_iterate_get_full_pathname(self->base, full_pathname, sizeof(full_pathname))) return -1; + file_fd = open(full_pathname, flags); + } + } + if (file_fd < 0) return -1; + struct stat st; + if (0 != fstat(file_fd, &st)) goto end; + size_t file_sz = (size_t)st.st_size; + + // get ELF header + ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)self->base; + if (0 == ehdr->e_shnum || ehdr->e_shentsize != sizeof(ElfW(Shdr))) goto end; + + // get section headers + shdrs = (ElfW(Shdr) *)xdl_read_file_to_heap(file_fd, file_sz, (size_t)ehdr->e_shoff, + ehdr->e_shentsize * ehdr->e_shnum); + if (NULL == shdrs) goto end; + + // get .shstrtab + if (SHN_UNDEF == ehdr->e_shstrndx || ehdr->e_shstrndx >= ehdr->e_shnum) goto end; + shstrtab = (char *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdrs + ehdr->e_shstrndx); + if (NULL == shstrtab) goto end; + + // find .symtab & .strtab + for (ElfW(Shdr) *shdr = shdrs; shdr < shdrs + ehdr->e_shnum; shdr++) { + char *shdr_name = shstrtab + shdr->sh_name; + + if (SHT_SYMTAB == shdr->sh_type && 0 == strcmp(".symtab", shdr_name)) { + // get & check associated .strtab section + if (shdr->sh_link >= ehdr->e_shnum) continue; + ElfW(Shdr) *shdr_strtab = shdrs + shdr->sh_link; + if (SHT_STRTAB != shdr_strtab->sh_type) continue; + + // get .symtab & .strtab + ElfW(Sym) *symtab = (ElfW(Sym) *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdr); + if (NULL == symtab) continue; + char *strtab = (char *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdr_strtab); + if (NULL == strtab) { + free(symtab); + continue; + } + + // OK + self->symtab = symtab; + self->symtab_cnt = shdr->sh_size / shdr->sh_entsize; + self->strtab = strtab; + self->strtab_sz = shdr_strtab->sh_size; + r = 0; + break; + } else if (SHT_PROGBITS == shdr->sh_type && 0 == strcmp(".gnu_debugdata", shdr_name)) { + if (0 == xdl_symtab_load_from_debugdata(self, file_fd, file_sz, shdr)) { + // OK + r = 0; + break; + } + } + } + +end: + close(file_fd); + if (NULL != shdrs) free(shdrs); + if (NULL != shstrtab) free(shstrtab); + return r; +} + +static xdl_t *xdl_find_from_auxv(unsigned long type, const char *pathname) { + if (NULL == getauxval) return NULL; // API level < 18 + + uintptr_t val = (uintptr_t)getauxval(type); + if (0 == val) return NULL; + + // get base + uintptr_t base = (AT_PHDR == type ? (val & (~0xffful)) : val); + if (0 != memcmp((void *)base, ELFMAG, SELFMAG)) return NULL; + + // ELF info + ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base; + const ElfW(Phdr) *dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff); + ElfW(Half) dlpi_phnum = ehdr->e_phnum; + + // get bias + uintptr_t min_vaddr = UINTPTR_MAX; + for (size_t i = 0; i < dlpi_phnum; i++) { + const ElfW(Phdr) *phdr = &(dlpi_phdr[i]); + if (PT_LOAD == phdr->p_type) { + if (min_vaddr > phdr->p_vaddr) min_vaddr = phdr->p_vaddr; + } + } + if (UINTPTR_MAX == min_vaddr || base < min_vaddr) return NULL; + uintptr_t load_bias = base - min_vaddr; + + // create xDL object + xdl_t *self; + if (NULL == (self = calloc(1, sizeof(xdl_t)))) return NULL; + if (NULL == (self->pathname = strdup(pathname))) { + free(self); + return NULL; + } + self->load_bias = load_bias; + self->dlpi_phdr = dlpi_phdr; + self->dlpi_phnum = dlpi_phnum; + self->dynsym_try_load = false; + self->symtab_try_load = false; + return self; +} + +static int xdl_find_iterate_cb(struct dl_phdr_info *info, size_t size, void *arg) { + (void)size; + + uintptr_t *pkg = (uintptr_t *)arg; + xdl_t **self = (xdl_t **)*pkg++; + const char *filename = (const char *)*pkg; + + // check load_bias + if (0 == info->dlpi_addr || NULL == info->dlpi_name) return 0; + + // check pathname + if ('[' == filename[0]) { + if (0 != strcmp(info->dlpi_name, filename)) return 0; + } else if ('/' == filename[0]) { + if ('/' == info->dlpi_name[0]) { + if (0 != strcmp(info->dlpi_name, filename)) return 0; + } else { + if (!xdl_util_ends_with(filename, info->dlpi_name)) return 0; + } + } else { + if ('/' == info->dlpi_name[0]) { + if (!xdl_util_ends_with(info->dlpi_name, filename)) return 0; + } else { + if (0 != strcmp(info->dlpi_name, filename)) return 0; + } + } + + // found the target ELF + if (NULL == ((*self) = calloc(1, sizeof(xdl_t)))) return 1; // return failed + if (NULL == ((*self)->pathname = strdup((const char *)info->dlpi_name))) { + free(*self); + *self = NULL; + return 1; // return failed + } + (*self)->load_bias = info->dlpi_addr; + (*self)->dlpi_phdr = info->dlpi_phdr; + (*self)->dlpi_phnum = info->dlpi_phnum; + (*self)->dynsym_try_load = false; + (*self)->symtab_try_load = false; + return 1; // return OK +} + +static xdl_t *xdl_find(const char *filename) { + // from auxv (linker, vDSO) + xdl_t *self = NULL; + if (xdl_util_ends_with(filename, XDL_UTIL_LINKER_BASENAME)) + self = xdl_find_from_auxv(AT_BASE, XDL_UTIL_LINKER_PATHNAME); + else if (xdl_util_ends_with(filename, XDL_UTIL_VDSO_BASENAME)) + self = xdl_find_from_auxv(AT_SYSINFO_EHDR, XDL_UTIL_VDSO_BASENAME); + + // from auxv (app_process) + const char *basename, *pathname; +#if (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < __ANDROID_API_L__ + if (xdl_util_get_api_level() < __ANDROID_API_L__) { + basename = XDL_UTIL_APP_PROCESS_BASENAME_K; + pathname = XDL_UTIL_APP_PROCESS_PATHNAME_K; + } else +#endif + { + basename = XDL_UTIL_APP_PROCESS_BASENAME; + pathname = XDL_UTIL_APP_PROCESS_PATHNAME; + } + if (xdl_util_ends_with(filename, basename)) self = xdl_find_from_auxv(AT_PHDR, pathname); + + if (NULL != self) return self; + + // from dl_iterate_phdr + uintptr_t pkg[2] = {(uintptr_t)&self, (uintptr_t)filename}; + xdl_iterate_phdr(xdl_find_iterate_cb, pkg, XDL_DEFAULT); + return self; +} + +static void *xdl_open_always_force(const char *filename) { + // always force dlopen() + void *linker_handle = xdl_linker_force_dlopen(filename); + if (NULL == linker_handle) return NULL; + + // find + xdl_t *self = xdl_find(filename); + if (NULL == self) + dlclose(linker_handle); + else + self->linker_handle = linker_handle; + + return (void *)self; +} + +static void *xdl_open_try_force(const char *filename) { + // find + xdl_t *self = xdl_find(filename); + if (NULL != self) return (void *)self; + + // try force dlopen() + void *linker_handle = xdl_linker_force_dlopen(filename); + if (NULL == linker_handle) return NULL; + + // find again + self = xdl_find(filename); + if (NULL == self) + dlclose(linker_handle); + else + self->linker_handle = linker_handle; + + return (void *)self; +} + +void *xdl_open(const char *filename, int flags) { + if (NULL == filename) return NULL; + + if (flags & XDL_ALWAYS_FORCE_LOAD) + return xdl_open_always_force(filename); + else if (flags & XDL_TRY_FORCE_LOAD) + return xdl_open_try_force(filename); + else + return xdl_find(filename); +} + +void *xdl_open2(struct dl_phdr_info *info) { + xdl_t *self = calloc(1, sizeof(xdl_t)); + if (NULL == self) return NULL; + if (NULL == (self->pathname = strdup((const char *)info->dlpi_name))) { + free(self); + return NULL; + } + self->load_bias = info->dlpi_addr; + self->dlpi_phdr = info->dlpi_phdr; + self->dlpi_phnum = info->dlpi_phnum; + self->dynsym_try_load = false; + self->symtab_try_load = false; + return self; +} + +void *xdl_close(void *handle) { + if (NULL == handle) return NULL; + + xdl_t *self = (xdl_t *)handle; + if (NULL != self->pathname) free(self->pathname); + if (NULL != self->symtab) free(self->symtab); + if (NULL != self->strtab) free(self->strtab); + + void *linker_handle = self->linker_handle; + free(self); + return linker_handle; +} + +static uint32_t xdl_sysv_hash(const uint8_t *name) { + uint32_t h = 0, g; + + while (*name) { + h = (h << 4) + *name++; + g = h & 0xf0000000; + h ^= g; + h ^= g >> 24; + } + return h; +} + +static uint32_t xdl_gnu_hash(const uint8_t *name) { + uint32_t h = 5381; + + while (*name) { + h += (h << 5) + *name++; + } + return h; +} + +static ElfW(Sym) *xdl_dynsym_find_symbol_use_sysv_hash(xdl_t *self, const char *sym_name) { + uint32_t hash = xdl_sysv_hash((const uint8_t *)sym_name); + + for (uint32_t i = self->sysv_hash.buckets[hash % self->sysv_hash.buckets_cnt]; 0 != i; + i = self->sysv_hash.chains[i]) { + ElfW(Sym) *sym = self->dynsym + i; + if (0 != strcmp(self->dynstr + sym->st_name, sym_name)) continue; + return sym; + } + + return NULL; +} + +static ElfW(Sym) *xdl_dynsym_find_symbol_use_gnu_hash(xdl_t *self, const char *sym_name) { + uint32_t hash = xdl_gnu_hash((const uint8_t *)sym_name); + + static uint32_t elfclass_bits = sizeof(ElfW(Addr)) * 8; + size_t word = self->gnu_hash.bloom[(hash / elfclass_bits) % self->gnu_hash.bloom_cnt]; + size_t mask = 0 | (size_t)1 << (hash % elfclass_bits) | + (size_t)1 << ((hash >> self->gnu_hash.bloom_shift) % elfclass_bits); + + // if at least one bit is not set, this symbol is surely missing + if ((word & mask) != mask) return NULL; + + // ignore STN_UNDEF + uint32_t i = self->gnu_hash.buckets[hash % self->gnu_hash.buckets_cnt]; + if (i < self->gnu_hash.symoffset) return NULL; + + // loop through the chain + while (1) { + ElfW(Sym) *sym = self->dynsym + i; + uint32_t sym_hash = self->gnu_hash.chains[i - self->gnu_hash.symoffset]; + + if ((hash | (uint32_t)1) == (sym_hash | (uint32_t)1)) { + if (0 == strcmp(self->dynstr + sym->st_name, sym_name)) { + return sym; + } + } + + // chain ends with an element with the lowest bit set to 1 + if (sym_hash & (uint32_t)1) break; + + i++; + } + + return NULL; +} + +typedef struct { + unsigned long size; /** Set to sizeof(__ifunc_arg_t). */ + unsigned long hwcap; /** Set to getauxval(AT_HWCAP). */ + unsigned long hwcap2; /** Set to getauxval(AT_HWCAP2). */ +} xdl_ifunc_arg_t; + +#if defined(__aarch64__) +#define XDL_IFUNC_ARG_HWCAP (1ULL << 62) +#endif + +static void *xdl_resolve_symbol_address(xdl_t *self, ElfW(Sym) *sym, size_t *symbol_size) { + if (ELF_ST_TYPE(sym->st_info) == STT_TLS) { + return NULL; + } else if (ELF_ST_TYPE(sym->st_info) == STT_GNU_IFUNC) { + void *sym_addr = (void *)(self->load_bias + sym->st_value); + void *real_sym_addr = NULL; + if (xdl_util_get_api_level() < __ANDROID_API_R__) { + // Android [4.x, 10] + typedef void *(*ifunc_resolver_t)(void); + real_sym_addr = ((ifunc_resolver_t)sym_addr)(); + } else { + // Android [11, ...) +#if defined(__aarch64__) + if (NULL == getauxval) return NULL; + typedef void *(*ifunc_resolver_t)(uint64_t, xdl_ifunc_arg_t *); + static xdl_ifunc_arg_t arg; + static bool initialized = false; + if (!initialized) { + arg.size = sizeof(xdl_ifunc_arg_t); + arg.hwcap = getauxval(AT_HWCAP); + arg.hwcap2 = getauxval(AT_HWCAP2); + initialized = true; + } + real_sym_addr = ((ifunc_resolver_t)sym_addr)(arg.hwcap | XDL_IFUNC_ARG_HWCAP, &arg); +#elif defined(__arm__) + if (NULL == getauxval) return NULL; + typedef void *(*ifunc_resolver_t)(unsigned long); + static unsigned long hwcap; + static bool initialized = false; + if (!initialized) { + hwcap = getauxval(AT_HWCAP); + initialized = true; + } + real_sym_addr = ((ifunc_resolver_t)sym_addr)(hwcap); +#else + typedef void *(*ifunc_resolver_t)(void); + real_sym_addr = ((ifunc_resolver_t)sym_addr)(); +#endif + } + if (NULL != symbol_size && NULL != real_sym_addr) { + ElfW(Sym) *real_sym = xdl_sym_by_addr(self, real_sym_addr); + if (NULL == real_sym) real_sym = xdl_dsym_by_addr(self, real_sym_addr); + if (NULL != real_sym) *symbol_size = real_sym->st_size; + } + return real_sym_addr; + } else { + if (NULL != symbol_size) *symbol_size = sym->st_size; + return (void *)(self->load_bias + sym->st_value); + } +} + +void *xdl_sym(void *handle, const char *symbol, size_t *symbol_size) { + if (NULL == handle || NULL == symbol) return NULL; + if (NULL != symbol_size) *symbol_size = 0; + + xdl_t *self = (xdl_t *)handle; + + // load .dynsym only once + if (!self->dynsym_try_load) { + self->dynsym_try_load = true; + if (0 != xdl_dynsym_load(self)) return NULL; + } + + // find symbol + if (NULL == self->dynsym) return NULL; + ElfW(Sym) *sym = NULL; + if (self->gnu_hash.buckets_cnt > 0) { + // use GNU hash (.gnu.hash -> .dynsym -> .dynstr), O(x) + O(1) + O(1) + sym = xdl_dynsym_find_symbol_use_gnu_hash(self, symbol); + } + if (NULL == sym && self->sysv_hash.buckets_cnt > 0) { + // use SYSV hash (.hash -> .dynsym -> .dynstr), O(x) + O(1) + O(1) + sym = xdl_dynsym_find_symbol_use_sysv_hash(self, symbol); + } + if (NULL == sym || !XDL_DYNSYM_IS_EXPORT_SYM(sym->st_shndx)) return NULL; + + return xdl_resolve_symbol_address(self, sym, symbol_size); +} + +// clang-format off +/* + * For internal symbols in .symtab, LLVM may add some suffixes (for example for thinLTO). + * The format of the suffix is: ".xxxx.[hash]". LLVM may add multiple suffixes at once. + * The symbol name after removing these all suffixes is called canonical name. + * + * Because the hash part in the suffix may change when recompiled, so here we only match + * the canonical name. + * + * IN ADDITION: According to C/C++ syntax, it is illegal for a function name to contain + * dot character('.'), either in the middle or at the end. + * + * samples: + * + * symbol name in .symtab lookup is match + * ---------------------- ---------------- -------- + * abcd abc N + * abcd abcde N + * abcd abcd Y + * abcd.llvm.10190306339727611508 abc N + * abcd.llvm.10190306339727611508 abcd Y + * abcd.llvm.10190306339727611508 abcd. N + * abcd.llvm.10190306339727611508 abcd.llvm Y + * abcd.llvm.10190306339727611508 abcd.llvm. N + * abcd.__uniq.513291356003753 abcd.__uniq.51329 N + * abcd.__uniq.513291356003753 abcd.__uniq.513291356003753 Y + */ +// clang-format on +static inline bool xdl_dsym_is_match(const char *str, const char *sym, size_t sym_len) { + size_t str_len = strlen(str); + if (0 == str_len) return false; + + if (str_len < sym_len) { + return false; + } else { + bool sym_len_match = (0 == memcmp(str, sym, sym_len)); + if (str_len == sym_len) + return sym_len_match; + else // str_len > sym_len + return sym_len_match && (str[sym_len] == '.' || str[sym_len] == '$'); + } +} + +void *xdl_dsym(void *handle, const char *symbol, size_t *symbol_size) { + if (NULL == handle || NULL == symbol) return NULL; + if (NULL != symbol_size) *symbol_size = 0; + + xdl_t *self = (xdl_t *)handle; + + // load .symtab only once + if (!self->symtab_try_load) { + self->symtab_try_load = true; + if (0 != xdl_symtab_load(self)) return NULL; + } + + // find symbol + if (NULL == self->symtab) return NULL; + size_t symbol_len = strlen(symbol); + for (size_t i = 0; i < self->symtab_cnt; i++) { + ElfW(Sym) *sym = self->symtab + i; + + if (!XDL_SYMTAB_IS_EXPORT_SYM(sym->st_shndx)) continue; + // if (0 != strncmp(self->strtab + sym->st_name, symbol, self->strtab_sz - sym->st_name)) continue; + if (!xdl_dsym_is_match(self->strtab + sym->st_name, symbol, symbol_len)) continue; + + if (NULL != symbol_size) *symbol_size = sym->st_size; + return (void *)(self->load_bias + sym->st_value); + } + + return NULL; +} + +static bool xdl_elf_is_match(uintptr_t load_bias, const ElfW(Phdr) *dlpi_phdr, ElfW(Half) dlpi_phnum, + uintptr_t addr) { + if (addr < load_bias) return false; + + uintptr_t vaddr = addr - load_bias; + for (size_t i = 0; i < dlpi_phnum; i++) { + const ElfW(Phdr) *phdr = &(dlpi_phdr[i]); + if (PT_LOAD != phdr->p_type) continue; + + if (phdr->p_vaddr <= vaddr && vaddr < phdr->p_vaddr + phdr->p_memsz) return true; + } + + return false; +} + +static int xdl_open_by_addr_iterate_cb(struct dl_phdr_info *info, size_t size, void *arg) { + (void)size; + + uintptr_t *pkg = (uintptr_t *)arg; + xdl_t **self = (xdl_t **)*pkg++; + uintptr_t addr = *pkg; + + if (0 == info->dlpi_addr || NULL == info->dlpi_name) return 0; // continue + + if (xdl_elf_is_match(info->dlpi_addr, info->dlpi_phdr, info->dlpi_phnum, addr)) { + // found the target ELF + if (NULL == ((*self) = calloc(1, sizeof(xdl_t)))) return 1; // failed + if (NULL == ((*self)->pathname = strdup((const char *)info->dlpi_name))) { + free(*self); + *self = NULL; + return 1; // failed + } + (*self)->load_bias = info->dlpi_addr; + (*self)->dlpi_phdr = info->dlpi_phdr; + (*self)->dlpi_phnum = info->dlpi_phnum; + (*self)->dynsym_try_load = false; + (*self)->symtab_try_load = false; + return 1; // OK + } + + return 0; // continue +} + +static void *xdl_open_by_addr(void *addr) { + if (NULL == addr) return NULL; + + xdl_t *self = NULL; + uintptr_t pkg[2] = {(uintptr_t)&self, (uintptr_t)addr}; + xdl_iterate_phdr(xdl_open_by_addr_iterate_cb, pkg, XDL_DEFAULT); + + return (void *)self; +} + +static bool xdl_sym_is_match(ElfW(Sym) *sym, uintptr_t offset, bool is_symtab) { + if (is_symtab) { + if (!XDL_SYMTAB_IS_EXPORT_SYM(sym->st_shndx)) return false; + } else { + if (!XDL_DYNSYM_IS_EXPORT_SYM(sym->st_shndx)) return false; + } + if (ELF_ST_TYPE(sym->st_info) == STT_TLS) return false; + + // For thumb instructions, "st_value" is an odd number, and the instructions are stored + // in the range: [st_value - 1, st_value - 1 + st_size). + // NOTE: The dladdr() implementation in the Android bionic linker does NOT fix this for + // thumb and is therefore incorrect. + uintptr_t sym_st_value_fixed = sym->st_value; +#if defined(__arm__) && defined(__thumb__) +#define CLEAR_BIT0(addr) ((addr) & 0xFFFFFFFE) + sym_st_value_fixed = CLEAR_BIT0(sym->st_value); +#endif + + return offset >= sym_st_value_fixed && offset < sym_st_value_fixed + sym->st_size; +} + +static ElfW(Sym) *xdl_sym_by_addr(void *handle, void *addr) { + xdl_t *self = (xdl_t *)handle; + + // load .dynsym only once + if (!self->dynsym_try_load) { + self->dynsym_try_load = true; + if (0 != xdl_dynsym_load(self)) return NULL; + } + + // find symbol + if (NULL == self->dynsym) return NULL; + uintptr_t offset = (uintptr_t)addr - self->load_bias; + if (self->gnu_hash.buckets_cnt > 0) { + const uint32_t *chains_all = self->gnu_hash.chains - self->gnu_hash.symoffset; + for (size_t i = 0; i < self->gnu_hash.buckets_cnt; i++) { + uint32_t n = self->gnu_hash.buckets[i]; + if (n < self->gnu_hash.symoffset) continue; + do { + ElfW(Sym) *sym = self->dynsym + n; + if (xdl_sym_is_match(sym, offset, false)) return sym; + } while ((chains_all[n++] & 1) == 0); + } + } else if (self->sysv_hash.chains_cnt > 0) { + for (size_t i = 0; i < self->sysv_hash.chains_cnt; i++) { + ElfW(Sym) *sym = self->dynsym + i; + if (xdl_sym_is_match(sym, offset, false)) return sym; + } + } + + return NULL; +} + +static ElfW(Sym) *xdl_dsym_by_addr(void *handle, void *addr) { + xdl_t *self = (xdl_t *)handle; + + // load .symtab only once + if (!self->symtab_try_load) { + self->symtab_try_load = true; + if (0 != xdl_symtab_load(self)) return NULL; + } + + // find symbol + if (NULL == self->symtab) return NULL; + uintptr_t offset = (uintptr_t)addr - self->load_bias; + for (size_t i = 0; i < self->symtab_cnt; i++) { + ElfW(Sym) *sym = self->symtab + i; + if (xdl_sym_is_match(sym, offset, true)) return sym; + } + + return NULL; +} + +int xdl_addr(void *addr, xdl_info_t *info, void **cache) { + return xdl_addr4(addr, info, cache, XDL_DEFAULT); +} + +int xdl_addr4(void *addr, xdl_info_t *info, void **cache, int flags) { + if (NULL == addr || NULL == info || NULL == cache) return 0; + + memset(info, 0, sizeof(Dl_info)); + + // find handle from cache + xdl_t *handle = NULL; + for (handle = *((xdl_t **)cache); NULL != handle; handle = handle->next) + if (xdl_elf_is_match(handle->load_bias, handle->dlpi_phdr, handle->dlpi_phnum, (uintptr_t)addr)) break; + + // create new handle, save handle to cache + if (NULL == handle) { + handle = (xdl_t *)xdl_open_by_addr(addr); + if (NULL == handle) return 0; + handle->next = *(xdl_t **)cache; + *(xdl_t **)cache = handle; + } + + // we have at least: load_bias, pathname, dlpi_phdr, dlpi_phnum + info->dli_fbase = (void *)handle->load_bias; + info->dli_fname = handle->pathname; + info->dli_sname = NULL; + info->dli_saddr = 0; + info->dli_ssize = 0; + info->dlpi_phdr = handle->dlpi_phdr; + info->dlpi_phnum = (size_t)handle->dlpi_phnum; + + // keep looking for: symbol name, symbol offset, symbol size + if (!(flags & XDL_NON_SYM)) { + ElfW(Sym) *sym; + if (NULL != (sym = xdl_sym_by_addr((void *)handle, addr))) { + info->dli_sname = handle->dynstr + sym->st_name; + info->dli_saddr = (void *)(handle->load_bias + sym->st_value); + info->dli_ssize = sym->st_size; + } else if (NULL != (sym = xdl_dsym_by_addr((void *)handle, addr))) { + info->dli_sname = handle->strtab + sym->st_name; + info->dli_saddr = (void *)(handle->load_bias + sym->st_value); + info->dli_ssize = sym->st_size; + } + } + + return 1; +} + +void xdl_addr_clean(void **cache) { + if (NULL == cache) return; + + xdl_t *handle = *((xdl_t **)cache); + while (NULL != handle) { + xdl_t *tmp = handle; + handle = handle->next; + xdl_close(tmp); + } + *cache = NULL; +} + +int xdl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data, int flags) { + if (NULL == callback) return 0; + + return xdl_iterate_phdr_impl(callback, data, flags); +} + +int xdl_info(void *handle, int request, void *info) { + if (NULL == handle || XDL_DI_DLINFO != request || NULL == info) return -1; + + xdl_t *self = (xdl_t *)handle; + xdl_info_t *dlinfo = (xdl_info_t *)info; + + dlinfo->dli_fbase = (void *)self->load_bias; + dlinfo->dli_fname = self->pathname; + dlinfo->dli_sname = NULL; + dlinfo->dli_saddr = 0; + dlinfo->dli_ssize = 0; + dlinfo->dlpi_phdr = self->dlpi_phdr; + dlinfo->dlpi_phnum = (size_t)self->dlpi_phnum; + return 0; +} diff --git a/app/src/main/jni/xDL/xdl.h b/app/src/main/jni/xDL/xdl.h new file mode 100644 index 0000000..e057991 --- /dev/null +++ b/app/src/main/jni/xDL/xdl.h @@ -0,0 +1,95 @@ +// Copyright (c) 2020-2025 HexHacking Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2020-10-04. + +// +// xDL version: 2.3.0 +// +// xDL is an enhanced implementation of the Android DL series functions. +// For more information, documentation, and the latest version please check: +// https://github.com/hexhacking/xDL +// + +#ifndef IO_GITHUB_HEXHACKING_XDL +#define IO_GITHUB_HEXHACKING_XDL + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + // same as Dl_info: + const char *dli_fname; // Pathname of shared object that contains address. + void *dli_fbase; // Address at which shared object is loaded. + const char *dli_sname; // Name of nearest symbol with address lower than addr. + void *dli_saddr; // Exact address of symbol named in dli_sname. + // added by xDL: + size_t dli_ssize; // Symbol size of nearest symbol with address lower than addr. + const ElfW(Phdr) *dlpi_phdr; // Pointer to array of ELF program headers for this object. + size_t dlpi_phnum; // Number of items in dlpi_phdr. +} xdl_info_t; + +// +// Default value for flags in xdl_open(), xdl_addr4(), and xdl_iterate_phdr(). +// +#define XDL_DEFAULT 0x00 + +// +// Enhanced dlopen() / dlclose() / dlsym(). +// +#define XDL_TRY_FORCE_LOAD 0x01 +#define XDL_ALWAYS_FORCE_LOAD 0x02 +void *xdl_open(const char *filename, int flags); +void *xdl_open2(struct dl_phdr_info *info); +void *xdl_close(void *handle); +void *xdl_sym(void *handle, const char *symbol, size_t *symbol_size); +void *xdl_dsym(void *handle, const char *symbol, size_t *symbol_size); + +// +// Enhanced dladdr(). +// +#define XDL_NON_SYM 0x01 +int xdl_addr(void *addr, xdl_info_t *info, void **cache); +int xdl_addr4(void *addr, xdl_info_t *info, void **cache, int flags); +void xdl_addr_clean(void **cache); + +// +// Enhanced dl_iterate_phdr(). +// +#define XDL_FULL_PATHNAME 0x01 +int xdl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data, int flags); + +// +// Custom dlinfo(). +// +#define XDL_DI_DLINFO 1 // type of info: xdl_info_t +int xdl_info(void *handle, int request, void *info); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/jni/xDL/xdl.map.txt b/app/src/main/jni/xDL/xdl.map.txt new file mode 100644 index 0000000..02558da --- /dev/null +++ b/app/src/main/jni/xDL/xdl.map.txt @@ -0,0 +1,16 @@ +{ + global: + xdl_open; + xdl_open2; + xdl_close; + xdl_sym; + xdl_dsym; + xdl_addr; + xdl_addr4; + xdl_addr_clean; + xdl_iterate_phdr; + xdl_info; + + local: + *; +}; diff --git a/app/src/main/jni/xDL/xdl_iterate.c b/app/src/main/jni/xDL/xdl_iterate.c new file mode 100644 index 0000000..f650705 --- /dev/null +++ b/app/src/main/jni/xDL/xdl_iterate.c @@ -0,0 +1,297 @@ +// Copyright (c) 2020-2025 HexHacking Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2020-10-04. + +#include "xdl_iterate.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xdl.h" +#include "xdl_linker.h" +#include "xdl_util.h" + +/* + * ========================================================================================================= + * API-LEVEL ANDROID-VERSION SOLUTION + * ========================================================================================================= + * 16 4.1 /proc/self/maps + * 17 4.2 /proc/self/maps + * 18 4.3 /proc/self/maps + * 19 4.4 /proc/self/maps + * 20 4.4W /proc/self/maps + * --------------------------------------------------------------------------------------------------------- + * 21 5.0 dl_iterate_phdr() + __dl__ZL10g_dl_mutex + linker/linker64 from getauxval(3) + * 22 5.1 dl_iterate_phdr() + __dl__ZL10g_dl_mutex + linker/linker64 from getauxval(3) + * --------------------------------------------------------------------------------------------------------- + * 23 >= 6.0 dl_iterate_phdr() + linker/linker64 from getauxval(3) + * ========================================================================================================= + */ + +extern __attribute((weak)) int dl_iterate_phdr(int (*)(struct dl_phdr_info *, size_t, void *), void *); +extern __attribute((weak)) unsigned long int getauxval(unsigned long int); + +static uintptr_t xdl_iterate_get_min_vaddr(struct dl_phdr_info *info) { + uintptr_t min_vaddr = UINTPTR_MAX; + for (size_t i = 0; i < info->dlpi_phnum; i++) { + const ElfW(Phdr) *phdr = &(info->dlpi_phdr[i]); + if (PT_LOAD == phdr->p_type) { + if (min_vaddr > phdr->p_vaddr) min_vaddr = phdr->p_vaddr; + } + } + return min_vaddr; +} + +static int xdl_iterate_open_or_rewind_maps(FILE **maps) { + if (NULL == *maps) { + *maps = fopen("/proc/self/maps", "r"); + if (NULL == *maps) return -1; + } else + rewind(*maps); + + return 0; +} + +static int xdl_iterate_get_pathname_from_maps(uintptr_t base, char *buf, size_t buf_len, FILE **maps) { + // open or rewind maps-file + if (0 != xdl_iterate_open_or_rewind_maps(maps)) return -1; // failed + + char line[1024]; + while (fgets(line, sizeof(line), *maps)) { + // check base address + uintptr_t start, end; + if (2 != sscanf(line, "%" SCNxPTR "-%" SCNxPTR " r", &start, &end)) continue; + if (base < start) break; // failed + if (base >= end) continue; + + // get pathname + char *pathname = strchr(line, '/'); + if (NULL == pathname) break; // failed + xdl_util_trim_ending(pathname); + + // found it + strlcpy(buf, pathname, buf_len); + return 0; // OK + } + + return -1; // failed +} + +static int xdl_iterate_by_linker_cb(struct dl_phdr_info *info, size_t size, void *arg) { + uintptr_t *pkg = (uintptr_t *)arg; + xdl_iterate_phdr_cb_t cb = (xdl_iterate_phdr_cb_t)*pkg++; + void *cb_arg = (void *)*pkg++; + FILE **maps = (FILE **)*pkg++; + uintptr_t linker_load_bias = *pkg++; + int flags = (int)*pkg; + + // ignore invalid ELF + if (0 == info->dlpi_addr || NULL == info->dlpi_name || '\0' == info->dlpi_name[0]) return 0; + + // ignore linker if we have returned it already + if (linker_load_bias == info->dlpi_addr) return 0; + + struct dl_phdr_info info_fixed; + info_fixed.dlpi_addr = info->dlpi_addr; + info_fixed.dlpi_name = info->dlpi_name; + info_fixed.dlpi_phdr = info->dlpi_phdr; + info_fixed.dlpi_phnum = info->dlpi_phnum; + info = &info_fixed; + + // fix dlpi_phdr & dlpi_phnum (from memory) + if (NULL == info->dlpi_phdr || 0 == info->dlpi_phnum) { + ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)info->dlpi_addr; + info->dlpi_phdr = (ElfW(Phdr) *)(info->dlpi_addr + ehdr->e_phoff); + info->dlpi_phnum = ehdr->e_phnum; + } + + // fix dlpi_name (from /proc/self/maps) + if ('/' != info->dlpi_name[0] && '[' != info->dlpi_name[0] && (0 != (flags & XDL_FULL_PATHNAME))) { + // get base address + uintptr_t min_vaddr = xdl_iterate_get_min_vaddr(info); + if (UINTPTR_MAX == min_vaddr) return 0; // ignore this ELF + uintptr_t base = (uintptr_t)(info->dlpi_addr + min_vaddr); + + char buf[1024]; + if (0 != xdl_iterate_get_pathname_from_maps(base, buf, sizeof(buf), maps)) return 0; // ignore this ELF + + info->dlpi_name = (const char *)buf; + } + + // callback + return cb(info, size, cb_arg); +} + +static uintptr_t xdl_iterate_get_linker_base(void) { + if (NULL == getauxval) return 0; + + uintptr_t base = (uintptr_t)getauxval(AT_BASE); + if (0 == base) return 0; + if (0 != memcmp((void *)base, ELFMAG, SELFMAG)) return 0; + + return base; +} + +static int xdl_iterate_do_callback(xdl_iterate_phdr_cb_t cb, void *cb_arg, uintptr_t base, + const char *pathname, uintptr_t *load_bias) { + ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base; + + struct dl_phdr_info info; + info.dlpi_name = pathname; + info.dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff); + info.dlpi_phnum = ehdr->e_phnum; + + // get load bias + uintptr_t min_vaddr = xdl_iterate_get_min_vaddr(&info); + if (UINTPTR_MAX == min_vaddr) return 0; // ignore invalid ELF + info.dlpi_addr = (ElfW(Addr))(base - min_vaddr); + if (NULL != load_bias) *load_bias = info.dlpi_addr; + + return cb(&info, sizeof(struct dl_phdr_info), cb_arg); +} + +static int xdl_iterate_by_linker(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags) { + if (NULL == dl_iterate_phdr) return 0; + + int api_level = xdl_util_get_api_level(); + FILE *maps = NULL; + int r; + + // dl_iterate_phdr(3) does NOT contain linker/linker64 when Android version < 8.1 (API level 27). + // Here we always try to get linker base address from auxv. + uintptr_t linker_load_bias = 0; + uintptr_t linker_base = xdl_iterate_get_linker_base(); + if (0 != linker_base) { + if (0 != + (r = xdl_iterate_do_callback(cb, cb_arg, linker_base, XDL_UTIL_LINKER_PATHNAME, &linker_load_bias))) + return r; + } + + // for other ELF + uintptr_t pkg[5] = {(uintptr_t)cb, (uintptr_t)cb_arg, (uintptr_t)&maps, linker_load_bias, (uintptr_t)flags}; + if (__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) xdl_linker_lock(); + r = dl_iterate_phdr(xdl_iterate_by_linker_cb, pkg); + if (__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) xdl_linker_unlock(); + + if (NULL != maps) fclose(maps); + return r; +} + +#if (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < __ANDROID_API_L__ +static int xdl_iterate_by_maps(xdl_iterate_phdr_cb_t cb, void *cb_arg) { + FILE *maps = fopen("/proc/self/maps", "r"); + if (NULL == maps) return 0; + + int r = 0; + char buf1[1024], buf2[1024]; + char *line = buf1; + uintptr_t prev_base = 0; + bool try_next_line = false; + + while (fgets(line, sizeof(buf1), maps)) { + // Try to find an ELF which loaded by linker. + uintptr_t base, offset; + char exec; + if (3 != sscanf(line, "%" SCNxPTR "-%*" SCNxPTR " r%*c%cp %" SCNxPTR " ", &base, &exec, &offset)) + goto clean; + + if ('-' == exec && 0 == offset) { + // r--p + prev_base = base; + line = (line == buf1 ? buf2 : buf1); + try_next_line = true; + continue; + } else if (exec == 'x') { + // r-xp + char *pathname = NULL; + if (try_next_line && 0 != offset) { + char *prev = (line == buf1 ? buf2 : buf1); + char *prev_pathname = strchr(prev, '/'); + if (NULL == prev_pathname) goto clean; + + pathname = strchr(line, '/'); + if (NULL == pathname) goto clean; + + xdl_util_trim_ending(prev_pathname); + xdl_util_trim_ending(pathname); + if (0 != strcmp(prev_pathname, pathname)) goto clean; + + // we found the line with r-xp in the next line + base = prev_base; + offset = 0; + } + + if (0 != offset) goto clean; + + // get pathname + if (NULL == pathname) { + pathname = strchr(line, '/'); + if (NULL == pathname) goto clean; + xdl_util_trim_ending(pathname); + } + + if (0 != memcmp((void *)base, ELFMAG, SELFMAG)) goto clean; + ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base; + struct dl_phdr_info info; + info.dlpi_name = pathname; + info.dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff); + info.dlpi_phnum = ehdr->e_phnum; + + // callback + if (0 != (r = xdl_iterate_do_callback(cb, cb_arg, base, pathname, NULL))) break; + } + + clean: + try_next_line = false; + } + + fclose(maps); + return r; +} +#endif + +int xdl_iterate_phdr_impl(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags) { + // iterate by /proc/self/maps in Android 4.x (Android 4.x only supports arm32 and x86) +#if (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < __ANDROID_API_L__ + if (xdl_util_get_api_level() < __ANDROID_API_L__) return xdl_iterate_by_maps(cb, cb_arg); +#endif + + // iterate by dl_iterate_phdr() + return xdl_iterate_by_linker(cb, cb_arg, flags); +} + +int xdl_iterate_get_full_pathname(uintptr_t base, char *buf, size_t buf_len) { + FILE *maps = NULL; + int r = xdl_iterate_get_pathname_from_maps(base, buf, buf_len, &maps); + if (NULL != maps) fclose(maps); + return r; +} diff --git a/app/src/main/jni/xDL/xdl_iterate.h b/app/src/main/jni/xDL/xdl_iterate.h new file mode 100644 index 0000000..a4f5bf7 --- /dev/null +++ b/app/src/main/jni/xDL/xdl_iterate.h @@ -0,0 +1,43 @@ +// Copyright (c) 2020-2025 HexHacking Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2020-10-04. + +#ifndef IO_GITHUB_HEXHACKING_XDL_ITERATE +#define IO_GITHUB_HEXHACKING_XDL_ITERATE + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef int (*xdl_iterate_phdr_cb_t)(struct dl_phdr_info *info, size_t size, void *arg); +int xdl_iterate_phdr_impl(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags); + +int xdl_iterate_get_full_pathname(uintptr_t base, char *buf, size_t buf_len); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/jni/xDL/xdl_linker.c b/app/src/main/jni/xDL/xdl_linker.c new file mode 100644 index 0000000..e0519b3 --- /dev/null +++ b/app/src/main/jni/xDL/xdl_linker.c @@ -0,0 +1,231 @@ +// Copyright (c) 2020-2025 HexHacking Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2021-02-21. + +#include "xdl_linker.h" + +#include +#include +#include +#include + +#include "xdl.h" +#include "xdl_iterate.h" +#include "xdl_util.h" + +#define XDL_LINKER_SYM_MUTEX "__dl__ZL10g_dl_mutex" +#define XDL_LINKER_SYM_DLOPEN_EXT_N "__dl__ZL10dlopen_extPKciPK17android_dlextinfoPv" +#define XDL_LINKER_SYM_DO_DLOPEN_N "__dl__Z9do_dlopenPKciPK17android_dlextinfoPv" +#define XDL_LINKER_SYM_DLOPEN_O "__dl__Z8__dlopenPKciPKv" +#define XDL_LINKER_SYM_LOADER_DLOPEN_P "__loader_dlopen" + +#ifndef __LP64__ +#define LIB "lib" +#else +#define LIB "lib64" +#endif + +typedef void *(*xdl_linker_dlopen_n_t)(const char *, int, const void *, void *); +typedef void *(*xdl_linker_dlopen_o_t)(const char *, int, const void *); + +static pthread_mutex_t *xdl_linker_mutex = NULL; +static void *xdl_linker_dlopen = NULL; + +typedef enum { MATCH_PREFIX, MATCH_SUFFIX } xdl_linker_match_type_t; + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpadded" +typedef struct { + xdl_linker_match_type_t type; + const char *value; +} xdl_linker_match_t; +#pragma clang diagnostic pop + +typedef struct { + void *addr; + xdl_linker_match_t *matches; + size_t matches_cursor; +} xdl_linker_caller_t; + +// https://source.android.com/docs/core/architecture/vndk/linker-namespace +// The following rules are loose and incomplete, you can add more according to your needs. +static xdl_linker_match_t xdl_linker_match_default[] = {{MATCH_SUFFIX, "/libc.so"}}; +static xdl_linker_match_t xdl_linker_match_art[] = {{MATCH_SUFFIX, "/libart.so"}}; +static xdl_linker_match_t xdl_linker_match_sphal[] = {{MATCH_PREFIX, "/vendor/" LIB "/egl/"}, + {MATCH_PREFIX, "/vendor/" LIB "/hw/"}, + {MATCH_PREFIX, "/vendor/" LIB "/"}, + {MATCH_PREFIX, "/odm/" LIB "/"}}; +static xdl_linker_match_t xdl_linker_match_vndk[] = {{MATCH_PREFIX, "/apex/com.android.vndk.v"}, + {MATCH_PREFIX, "/vendor/" LIB "/vndk-sp/"}, + {MATCH_PREFIX, "/odm/" LIB "/vndk-sp/"}}; +static xdl_linker_caller_t xdl_linker_callers[] = { + {NULL, xdl_linker_match_default, sizeof(xdl_linker_match_default) / sizeof(xdl_linker_match_t)}, + {NULL, xdl_linker_match_art, sizeof(xdl_linker_match_art) / sizeof(xdl_linker_match_t)}, + {NULL, xdl_linker_match_sphal, sizeof(xdl_linker_match_sphal) / sizeof(xdl_linker_match_t)}, + {NULL, xdl_linker_match_vndk, sizeof(xdl_linker_match_vndk) / sizeof(xdl_linker_match_t)}}; + +static void xdl_linker_init_symbols_impl(void) { + // find linker from: /proc/self/maps (API level < 18) or getauxval (API level >= 18) + void *handle = xdl_open(XDL_UTIL_LINKER_BASENAME, XDL_DEFAULT); + if (NULL == handle) return; + + int api_level = xdl_util_get_api_level(); + if (__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) { + // == Android 5.x + xdl_linker_mutex = (pthread_mutex_t *)xdl_dsym(handle, XDL_LINKER_SYM_MUTEX, NULL); + } else if (__ANDROID_API_N__ == api_level || __ANDROID_API_N_MR1__ == api_level) { + // == Android 7.x + xdl_linker_dlopen = xdl_dsym(handle, XDL_LINKER_SYM_DLOPEN_EXT_N, NULL); + if (NULL == xdl_linker_dlopen) { + xdl_linker_dlopen = xdl_dsym(handle, XDL_LINKER_SYM_DO_DLOPEN_N, NULL); + xdl_linker_mutex = (pthread_mutex_t *)xdl_dsym(handle, XDL_LINKER_SYM_MUTEX, NULL); + } + } else if (__ANDROID_API_O__ == api_level || __ANDROID_API_O_MR1__ == api_level) { + // == Android 8.x + xdl_linker_dlopen = xdl_dsym(handle, XDL_LINKER_SYM_DLOPEN_O, NULL); + } else if (api_level >= __ANDROID_API_P__) { + // >= Android 9.0 + xdl_linker_dlopen = xdl_sym(handle, XDL_LINKER_SYM_LOADER_DLOPEN_P, NULL); + } + + xdl_close(handle); +} + +static void xdl_linker_init_symbols(void) { + static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + static bool inited = false; + if (!inited) { + pthread_mutex_lock(&lock); + if (!inited) { + xdl_linker_init_symbols_impl(); + inited = true; + } + pthread_mutex_unlock(&lock); + } +} + +void xdl_linker_lock(void) { + xdl_linker_init_symbols(); + + if (NULL != xdl_linker_mutex) pthread_mutex_lock(xdl_linker_mutex); +} + +void xdl_linker_unlock(void) { + if (NULL != xdl_linker_mutex) pthread_mutex_unlock(xdl_linker_mutex); +} + +static void *xdl_linker_get_caller_addr(struct dl_phdr_info *info) { + for (size_t i = 0; i < info->dlpi_phnum; i++) { + const ElfW(Phdr) *phdr = &(info->dlpi_phdr[i]); + if (PT_LOAD == phdr->p_type) { + return (void *)(info->dlpi_addr + phdr->p_vaddr); + } + } + return NULL; +} + +static void xdl_linker_save_caller_addr(struct dl_phdr_info *info, xdl_linker_caller_t *caller, + size_t cursor) { + void *addr = xdl_linker_get_caller_addr(info); + if (NULL != addr) { + caller->addr = addr; + caller->matches_cursor = cursor; + } +} + +static int xdl_linker_get_caller_addr_cb(struct dl_phdr_info *info, size_t size, void *arg) { + (void)size, (void)arg; + if (0 == info->dlpi_addr || NULL == info->dlpi_name) return 0; // continue + + int ret = 1; // OK + for (size_t i = 0; i < sizeof(xdl_linker_callers) / sizeof(xdl_linker_callers[0]); i++) { + xdl_linker_caller_t *caller = &xdl_linker_callers[i]; + for (size_t j = 0; j < caller->matches_cursor; j++) { + xdl_linker_match_t *match = &caller->matches[j]; + if (MATCH_PREFIX == match->type) { + if (xdl_util_starts_with(info->dlpi_name, match->value)) { + xdl_linker_save_caller_addr(info, caller, j); + } + } else if (MATCH_SUFFIX == match->type) { + if (xdl_util_ends_with(info->dlpi_name, match->value)) { + xdl_linker_save_caller_addr(info, caller, j); + } + } + } + if (NULL == caller->addr || 0 != caller->matches_cursor) ret = 0; // continue + } + return ret; +} + +static void xdl_linker_init_caller_addr_impl(void) { + xdl_iterate_phdr_impl(xdl_linker_get_caller_addr_cb, NULL, XDL_DEFAULT); +} + +static void xdl_linker_init_caller_addr(void) { + static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + static bool inited = false; + if (!inited) { + pthread_mutex_lock(&lock); + if (!inited) { + xdl_linker_init_caller_addr_impl(); + inited = true; + } + pthread_mutex_unlock(&lock); + } +} + +void *xdl_linker_force_dlopen(const char *filename) { + int api_level = xdl_util_get_api_level(); + + if (api_level <= __ANDROID_API_M__) { + // <= Android 6.0 + return dlopen(filename, RTLD_NOW); + } else { + xdl_linker_init_symbols(); + if (NULL == xdl_linker_dlopen) return NULL; + xdl_linker_init_caller_addr(); + + void *handle = NULL; + if (__ANDROID_API_N__ == api_level || __ANDROID_API_N_MR1__ == api_level) { + // == Android 7.x + xdl_linker_lock(); + for (size_t i = 0; i < sizeof(xdl_linker_callers) / sizeof(xdl_linker_callers[0]); i++) { + xdl_linker_caller_t *caller = &xdl_linker_callers[i]; + if (NULL != caller->addr) { + handle = ((xdl_linker_dlopen_n_t)xdl_linker_dlopen)(filename, RTLD_NOW, NULL, caller->addr); + if (NULL != handle) break; + } + } + xdl_linker_unlock(); + } else { + // >= Android 8.0 + for (size_t i = 0; i < sizeof(xdl_linker_callers) / sizeof(xdl_linker_callers[0]); i++) { + xdl_linker_caller_t *caller = &xdl_linker_callers[i]; + if (NULL != caller->addr) { + handle = ((xdl_linker_dlopen_o_t)xdl_linker_dlopen)(filename, RTLD_NOW, caller->addr); + if (NULL != handle) break; + } + } + } + return handle; + } +} diff --git a/app/src/main/jni/xDL/xdl_linker.h b/app/src/main/jni/xDL/xdl_linker.h new file mode 100644 index 0000000..ce2c8a8 --- /dev/null +++ b/app/src/main/jni/xDL/xdl_linker.h @@ -0,0 +1,40 @@ +// Copyright (c) 2020-2025 HexHacking Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2021-02-21. + +#ifndef IO_GITHUB_HEXHACKING_XDL_LINKER +#define IO_GITHUB_HEXHACKING_XDL_LINKER + +#ifdef __cplusplus +extern "C" { +#endif + +void xdl_linker_lock(void); +void xdl_linker_unlock(void); + +void *xdl_linker_force_dlopen(const char *filename); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/jni/xDL/xdl_lzma.c b/app/src/main/jni/xDL/xdl_lzma.c new file mode 100644 index 0000000..9bde151 --- /dev/null +++ b/app/src/main/jni/xDL/xdl_lzma.c @@ -0,0 +1,187 @@ +// Copyright (c) 2020-2025 HexHacking Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2020-11-08. + +#include "xdl_lzma.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xdl.h" +#include "xdl_util.h" + +// LZMA library pathname & symbol names +#ifndef __LP64__ +#define XDL_LZMA_PATHNAME "/system/lib/liblzma.so" +#else +#define XDL_LZMA_PATHNAME "/system/lib64/liblzma.so" +#endif +#define XDL_LZMA_SYM_CRCGEN "CrcGenerateTable" +#define XDL_LZMA_SYM_CRC64GEN "Crc64GenerateTable" +#define XDL_LZMA_SYM_CONSTRUCT "XzUnpacker_Construct" +#define XDL_LZMA_SYM_ISFINISHED "XzUnpacker_IsStreamWasFinished" +#define XDL_LZMA_SYM_FREE "XzUnpacker_Free" +#define XDL_LZMA_SYM_CODE "XzUnpacker_Code" + +// LZMA data type definition +#define SZ_OK 0 +typedef struct ISzAlloc ISzAlloc; +typedef const ISzAlloc *ISzAllocPtr; +struct ISzAlloc { + void *(*Alloc)(ISzAllocPtr p, size_t size); + void (*Free)(ISzAllocPtr p, void *address); /* address can be 0 */ +}; +typedef enum { + CODER_STATUS_NOT_SPECIFIED, /* use main error code instead */ + CODER_STATUS_FINISHED_WITH_MARK, /* stream was finished with end mark. */ + CODER_STATUS_NOT_FINISHED, /* stream was not finished */ + CODER_STATUS_NEEDS_MORE_INPUT /* you must provide more input bytes */ +} ECoderStatus; +typedef enum { + CODER_FINISH_ANY, /* finish at any point */ + CODER_FINISH_END /* block must be finished at the end */ +} ECoderFinishMode; + +// LZMA function type definition +typedef void (*xdl_lzma_crcgen_t)(void); +typedef void (*xdl_lzma_crc64gen_t)(void); +typedef void (*xdl_lzma_construct_t)(void *, ISzAllocPtr); +typedef int (*xdl_lzma_isfinished_t)(const void *); +typedef void (*xdl_lzma_free_t)(void *); +typedef int (*xdl_lzma_code_t)(void *, uint8_t *, size_t *, const uint8_t *, size_t *, ECoderFinishMode, + ECoderStatus *); +typedef int (*xdl_lzma_code_q_t)(void *, uint8_t *, size_t *, const uint8_t *, size_t *, int, + ECoderFinishMode, ECoderStatus *); + +// LZMA function pointor +static xdl_lzma_construct_t xdl_lzma_construct = NULL; +static xdl_lzma_isfinished_t xdl_lzma_isfinished = NULL; +static xdl_lzma_free_t xdl_lzma_free = NULL; +static void *xdl_lzma_code = NULL; + +// LZMA init +static void xdl_lzma_init(void) { + void *lzma = xdl_open(XDL_LZMA_PATHNAME, XDL_TRY_FORCE_LOAD); + if (NULL == lzma) return; + + xdl_lzma_crcgen_t crcgen = NULL; + xdl_lzma_crc64gen_t crc64gen = NULL; + if (NULL == (crcgen = (xdl_lzma_crcgen_t)xdl_sym(lzma, XDL_LZMA_SYM_CRCGEN, NULL))) goto end; + if (NULL == (crc64gen = (xdl_lzma_crc64gen_t)xdl_sym(lzma, XDL_LZMA_SYM_CRC64GEN, NULL))) goto end; + if (NULL == (xdl_lzma_construct = (xdl_lzma_construct_t)xdl_sym(lzma, XDL_LZMA_SYM_CONSTRUCT, NULL))) + goto end; + if (NULL == (xdl_lzma_isfinished = (xdl_lzma_isfinished_t)xdl_sym(lzma, XDL_LZMA_SYM_ISFINISHED, NULL))) + goto end; + if (NULL == (xdl_lzma_free = (xdl_lzma_free_t)xdl_sym(lzma, XDL_LZMA_SYM_FREE, NULL))) goto end; + if (NULL == (xdl_lzma_code = xdl_sym(lzma, XDL_LZMA_SYM_CODE, NULL))) goto end; + crcgen(); + crc64gen(); + +end: + xdl_close(lzma); +} + +// LZMA internal alloc / free +static void *xdl_lzma_internal_alloc(ISzAllocPtr p, size_t size) { + (void)p; + return malloc(size); +} +static void xdl_lzma_internal_free(ISzAllocPtr p, void *address) { + (void)p; + free(address); +} + +int xdl_lzma_decompress(uint8_t *src, size_t src_size, uint8_t **dst, size_t *dst_size) { + size_t src_offset = 0; + size_t dst_offset = 0; + size_t src_remaining; + size_t dst_remaining; + ISzAlloc alloc = {.Alloc = xdl_lzma_internal_alloc, .Free = xdl_lzma_internal_free}; + long long state[4096 / sizeof(long long)]; // must be enough, 8-bit aligned + ECoderStatus status; + int api_level = xdl_util_get_api_level(); + + // init and check + static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; + static bool inited = false; + if (!inited) { + pthread_mutex_lock(&lock); + if (!inited) { + xdl_lzma_init(); + inited = true; + } + pthread_mutex_unlock(&lock); + } + if (NULL == xdl_lzma_code) return -1; + + xdl_lzma_construct(&state, &alloc); + + *dst_size = 2 * src_size; + *dst = NULL; + do { + *dst_size *= 2; + if (NULL == (*dst = realloc(*dst, *dst_size))) { + xdl_lzma_free(&state); + return -1; + } + + src_remaining = src_size - src_offset; + dst_remaining = *dst_size - dst_offset; + + int result; + if (api_level >= __ANDROID_API_Q__) { + xdl_lzma_code_q_t lzma_code_q = (xdl_lzma_code_q_t)xdl_lzma_code; + result = lzma_code_q(&state, *dst + dst_offset, &dst_remaining, src + src_offset, &src_remaining, 1, + CODER_FINISH_ANY, &status); + } else { + xdl_lzma_code_t lzma_code = (xdl_lzma_code_t)xdl_lzma_code; + result = lzma_code(&state, *dst + dst_offset, &dst_remaining, src + src_offset, &src_remaining, + CODER_FINISH_ANY, &status); + } + if (SZ_OK != result) { + free(*dst); + xdl_lzma_free(&state); + return -1; + } + + src_offset += src_remaining; + dst_offset += dst_remaining; + } while (status == CODER_STATUS_NOT_FINISHED); + + xdl_lzma_free(&state); + + if (!xdl_lzma_isfinished(&state)) { + free(*dst); + return -1; + } + + *dst_size = dst_offset; + *dst = realloc(*dst, *dst_size); + return 0; +} diff --git a/app/src/main/jni/xDL/xdl_lzma.h b/app/src/main/jni/xDL/xdl_lzma.h new file mode 100644 index 0000000..d282de1 --- /dev/null +++ b/app/src/main/jni/xDL/xdl_lzma.h @@ -0,0 +1,40 @@ +// Copyright (c) 2020-2025 HexHacking Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2020-11-08. + +#ifndef IO_GITHUB_HEXHACKING_XDL_LZMA +#define IO_GITHUB_HEXHACKING_XDL_LZMA + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +int xdl_lzma_decompress(uint8_t *src, size_t src_size, uint8_t **dst, size_t *dst_size); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/app/src/main/jni/xDL/xdl_util.c b/app/src/main/jni/xDL/xdl_util.c new file mode 100644 index 0000000..e379ccd --- /dev/null +++ b/app/src/main/jni/xDL/xdl_util.c @@ -0,0 +1,95 @@ +// Copyright (c) 2020-2025 HexHacking Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2020-10-04. + +#include "xdl_util.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool xdl_util_starts_with(const char *str, const char *start) { + while (*str && *str == *start) { + str++; + start++; + } + + return '\0' == *start; +} + +bool xdl_util_ends_with(const char *str, const char *ending) { + size_t str_len = strlen(str); + size_t ending_len = strlen(ending); + + if (ending_len > str_len) return false; + + return 0 == strcmp(str + (str_len - ending_len), ending); +} + +size_t xdl_util_trim_ending(char *start) { + char *end = start + strlen(start); + while (start < end && isspace((int)(*(end - 1)))) { + end--; + *end = '\0'; + } + return (size_t)(end - start); +} + +static int xdl_util_get_api_level_from_build_prop(void) { + char buf[128]; + int api_level = -1; + + FILE *fp = fopen("/system/build.prop", "r"); + if (NULL == fp) goto end; + + while (fgets(buf, sizeof(buf), fp)) { + if (xdl_util_starts_with(buf, "ro.build.version.sdk=")) { + api_level = atoi(buf + 21); + break; + } + } + fclose(fp); + +end: + return (api_level > 0) ? api_level : -1; +} + +int xdl_util_get_api_level(void) { + static int xdl_util_api_level = -1; + + if (xdl_util_api_level < 0) { + int api_level = android_get_device_api_level(); + if (api_level < 0) + api_level = xdl_util_get_api_level_from_build_prop(); // compatible with unusual models + if (api_level < __ANDROID_API_J__) api_level = __ANDROID_API_J__; + + __atomic_store_n(&xdl_util_api_level, api_level, __ATOMIC_SEQ_CST); + } + + return xdl_util_api_level; +} diff --git a/app/src/main/jni/xDL/xdl_util.h b/app/src/main/jni/xDL/xdl_util.h new file mode 100644 index 0000000..159275e --- /dev/null +++ b/app/src/main/jni/xDL/xdl_util.h @@ -0,0 +1,71 @@ +// Copyright (c) 2020-2025 HexHacking Team +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +// Created by caikelun on 2020-10-04. + +#ifndef IO_GITHUB_HEXHACKING_XDL_UTIL +#define IO_GITHUB_HEXHACKING_XDL_UTIL + +#include +#include +#include + +#ifndef __LP64__ +#define XDL_UTIL_LINKER_BASENAME "linker" +#define XDL_UTIL_LINKER_PATHNAME "/system/bin/linker" +#define XDL_UTIL_APP_PROCESS_BASENAME "app_process32" +#define XDL_UTIL_APP_PROCESS_PATHNAME "/system/bin/app_process32" +#define XDL_UTIL_APP_PROCESS_BASENAME_K "app_process" +#define XDL_UTIL_APP_PROCESS_PATHNAME_K "/system/bin/app_process" +#else +#define XDL_UTIL_LINKER_BASENAME "linker64" +#define XDL_UTIL_LINKER_PATHNAME "/system/bin/linker64" +#define XDL_UTIL_APP_PROCESS_BASENAME "app_process64" +#define XDL_UTIL_APP_PROCESS_PATHNAME "/system/bin/app_process64" +#endif +#define XDL_UTIL_VDSO_BASENAME "[vdso]" + +#define XDL_UTIL_TEMP_FAILURE_RETRY(exp) \ + ({ \ + __typeof__(exp) _rc; \ + do { \ + errno = 0; \ + _rc = (exp); \ + } while (_rc == -1 && errno == EINTR); \ + _rc; \ + }) + +#ifdef __cplusplus +extern "C" { +#endif + +bool xdl_util_starts_with(const char *str, const char *start); +bool xdl_util_ends_with(const char *str, const char *ending); + +size_t xdl_util_trim_ending(char *start); + +int xdl_util_get_api_level(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/build.gradle b/build.gradle index d572fec..b7df6d6 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.6.1' + classpath 'com.android.tools.build:gradle:8.11.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -21,5 +21,5 @@ allprojects { } tasks.register('clean', Delete) { - delete rootProject.buildDir -} + delete getLayout().getBuildDirectory() +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 3dd11dd..6c12845 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,6 @@ # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. -android.defaults.buildfeatures.buildconfig=true android.nonFinalResIds=false android.nonTransitiveRClass=false org.gradle.jvmargs=-Xmx1536m diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a0a32d2..203b481 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip From a4c982059ff391dbe3fe9b57dc24d60ece4b1d38 Mon Sep 17 00:00:00 2001 From: ABS Date: Thu, 18 Dec 2025 20:53:39 +0000 Subject: [PATCH 02/15] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b035c51..ce9631a 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ This is just my vision of what version 5 should be like. - Init() from Setup migrated to Menu for convenience - And64InlineHook and Substrate libs removed (replaced by Dobby) - get_device_api_level_inlines.h removed (replaced by JNI get_api_sdk) - +- - Java: - CrashHandler.java updated: - - more debug info @@ -23,7 +23,7 @@ This is just my vision of what version 5 should be like. - - clear old crash logs (keep last 5) - Main.java minor changes - Menu.java minor changes - +- - Project settings: - Updated to SDK 36 - Upgraded gradle From 5e83067b138068cbd0820c698f977edce97f0a0e Mon Sep 17 00:00:00 2001 From: ABS Date: Thu, 18 Dec 2025 20:54:33 +0000 Subject: [PATCH 03/15] Update README.md --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ce9631a..ca7945b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ This is just my vision of what version 5 should be like. # 5.0 what's new: -- C++: +## C++: - Added xDL lib - KittyMemory lib updated - Macros.h & Utils.cpp: @@ -15,16 +15,16 @@ This is just my vision of what version 5 should be like. - Init() from Setup migrated to Menu for convenience - And64InlineHook and Substrate libs removed (replaced by Dobby) - get_device_api_level_inlines.h removed (replaced by JNI get_api_sdk) -- -- Java: + +## Java: - CrashHandler.java updated: - - more debug info - - save path has been changed to: android/media/PACKAGE/files/LOG_DIR for all devices - - clear old crash logs (keep last 5) - Main.java minor changes - Menu.java minor changes -- -- Project settings: + +## Project settings: - Updated to SDK 36 - Upgraded gradle - Minor gradle settings changes From de849e1eba324c9dfcae653d397cd832c7226906 Mon Sep 17 00:00:00 2001 From: ABS Date: Thu, 18 Dec 2025 20:58:46 +0000 Subject: [PATCH 04/15] Update README.md --- README.md | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index ca7945b..bb9e636 100644 --- a/README.md +++ b/README.md @@ -1,28 +1,26 @@ -# Fork Android-Mod-Menu by ABS -This is just my vision of what version 5 should be like. +**No official! This is just my vision of what version 5 should be like** # 5.0 what's new: ## C++: - Added xDL lib - KittyMemory lib updated - Macros.h & Utils.cpp: -- - xDL sym resolver added -- - HOOKs reworked with Dobby usage -- - PATCH_SWITCH, RESTORE has been returned and reworked -- - INST added (Dobby) -- - asm support added (KittyMemory::Keystone assembler) -- - Macro defines have been optimized +- - _xDL sym resolver added_ +- - _HOOKs reworked with Dobby usage_ +- - _PATCH_SWITCH, RESTORE has been returned and reworked_ +- - _INST added (Dobby)_ +- - _asm support added (KittyMemory::Keystone assembler)_ +- - _Macro defines have been optimized_ - Init() from Setup migrated to Menu for convenience - And64InlineHook and Substrate libs removed (replaced by Dobby) - get_device_api_level_inlines.h removed (replaced by JNI get_api_sdk) ## Java: - CrashHandler.java updated: -- - more debug info -- - save path has been changed to: android/media/PACKAGE/files/LOG_DIR for all devices -- - clear old crash logs (keep last 5) -- Main.java minor changes -- Menu.java minor changes +- - _more debug info_ +- - _save path has been changed to: android/media/PACKAGE/files/LOG_DIR for all devices_ +- - _clear old crash logs (keep last 5)_ +- Other minor changes ## Project settings: - Updated to SDK 36 From ae9c3aee3a4347df117acf5b873fa38941d59d96 Mon Sep 17 00:00:00 2001 From: ABS Date: Thu, 18 Dec 2025 21:13:22 +0000 Subject: [PATCH 05/15] restore readme --- README.md | 40 ++++++---------------------------------- 1 file changed, 6 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index bb9e636..704352b 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,10 @@ -**No official! This is just my vision of what version 5 should be like** - -# 5.0 what's new: -## C++: -- Added xDL lib -- KittyMemory lib updated -- Macros.h & Utils.cpp: -- - _xDL sym resolver added_ -- - _HOOKs reworked with Dobby usage_ -- - _PATCH_SWITCH, RESTORE has been returned and reworked_ -- - _INST added (Dobby)_ -- - _asm support added (KittyMemory::Keystone assembler)_ -- - _Macro defines have been optimized_ -- Init() from Setup migrated to Menu for convenience -- And64InlineHook and Substrate libs removed (replaced by Dobby) -- get_device_api_level_inlines.h removed (replaced by JNI get_api_sdk) - -## Java: -- CrashHandler.java updated: -- - _more debug info_ -- - _save path has been changed to: android/media/PACKAGE/files/LOG_DIR for all devices_ -- - _clear old crash logs (keep last 5)_ -- Other minor changes - -## Project settings: -- Updated to SDK 36 -- Upgraded gradle -- Minor gradle settings changes # Introduction ![GitHub](https://img.shields.io/github/license/LGLTeam/Android-Mod-Menu?style=flat-square) Floating mod menu for il2cpp and other native android games. KittyMemory, MSHook, And64InlineHook and basic string obfuscator (AY obfuscator) included. Assets are stored as base64 in cpp and does not need to be stored under assets folder. -Support Android 4.4.x up to Android 16. ARMv7 and ARM64 are supported. +Support Android 4.4.x up to Android 15. ARMv7 and ARM64 are supported. ![](Intro.gif) @@ -51,13 +23,11 @@ If you have an issue with Hooking and game crashes, you should go to the **suppo # Credits Thanks to the following individuals whose code helped me develop this mod menu -* LGLTeam - (parent of fork) Mod menu - https://github.com/LGLTeam/Android-Mod-Menu -* Octowolve/Escanor - Mod menu: https://github.com/z3r0Sec/Substrate-Template-With-Mod-Menu +* Octowolve/Escanor - Mod menu: https://github.com/z3r0Sec/Substrate-Template-With-Mod-Menu and Hooking: https://github.com/z3r0Sec/Substrate-Hooking-Example * VanHoevenTR - Mod menu - https://github.com/LGLTeam/VanHoevenTR_Android_Mod_Menu * MrIkso - First mod menu template https://github.com/MrIkso/FloatingModMenu * MJx0 A.K.A Ruit - https://github.com/MJx0/KittyMemory -* jmpews(AKA.zz) - https://github.com/jmpews/Dobby -* Kelun Cai & ruki - https://github.com/hexhacking/xDL +* Rprop - https://github.com/Rprop/And64InlineHook * And everyone else who provided input and contributions to this project! # License @@ -66,4 +36,6 @@ Thanks to the following individuals whose code helped me develop this mod menu # Disclaimer This project is for Educational Use only. We do not condone this project being used to gain an advantage against other people. This project was made for fun. If you are using this project for modding/hacking PU*G, C*DM, and any other Tencent games, we ask you to STOP immediately! -Do not buy any source codes on Telegram even if the author can be trusted, there is always a risk getting scammed. We will not be responsible for that. This project is always FREE to use \ No newline at end of file +Do not buy any source codes on Telegram even if the author can be trusted, there is always a risk getting scammed. We will not be responsible for that. This project is always FREE to use + +Telegram: https://t.me/LGLTeams From 2e20cca6436b301ed4c45a267d0332607c31edd4 Mon Sep 17 00:00:00 2001 From: ABS Date: Sat, 20 Dec 2025 18:19:52 +0000 Subject: [PATCH 06/15] v5.1 --- app/build.gradle | 2 +- app/src/main/jni/Includes/Macros.h | 102 +++++++++++++++++++++++++---- 2 files changed, 91 insertions(+), 13 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3664126..2c9fd48 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,7 +7,7 @@ android { minSdkVersion 21 targetSdkVersion 36 versionCode 1 - versionName "5.0" + versionName "5.1" ndk { //noinspection ChromeOsAbiSupport abiFilters 'armeabi-v7a', 'arm64-v8a' diff --git a/app/src/main/jni/Includes/Macros.h b/app/src/main/jni/Includes/Macros.h index 03196ad..d87054b 100644 --- a/app/src/main/jni/Includes/Macros.h +++ b/app/src/main/jni/Includes/Macros.h @@ -4,6 +4,7 @@ #include "KittyMemory/MemoryPatch.hpp" #include "KittyMemory/KittyInclude.hpp" +#include "KittyMemory/Deps/Keystone/includes/keystone.h" #include "Dobby/dobby.h" #if defined(__aarch64__) @@ -29,6 +30,9 @@ void DobbyHookWrapper(const char *lib, const char *relative, void* hook_function } } + + + /// (offset || sym) you can use instrument for logging, counting function calls, executing side code before the function is executed #define INST(lib, off_sym, name) DobbyInstrumentWrapper(lib, off_sym, name) @@ -47,18 +51,91 @@ void DobbyInstrumentWrapper(const char *lib, const char *relative, const char *n DobbyInstrument(abs, (dobby_instrument_callback_t)(Detector)); } + + + +/// Dobby-Kitty patch implementation +std::map, size_t>> pExpress; +void DobbyPatchWrapper(const char *libName, const char *relative, std::string data, bool apply) { + std::string key = relative; + auto it = pExpress.find(key); + void* abs = nullptr; + std::vector patch_code; + size_t patch_size = 0; + + if(it != pExpress.end()) { + abs = std::get<0>(it->second); + patch_code = std::get<1>(it->second); + patch_size = std::get<2>(it->second); + // LOGI(OBFUSCATE("%s <- expressed"), relative); + } else { + abs = getAbsoluteAddress(libName, relative); + pExpress[key] = std::make_tuple(abs, patch_code, patch_size); + // LOGI(OBFUSCATE("expressing %s -> new abs: 0x%llx"), relative, abs); + } + + if(apply) { + if(patch_code.empty()) { + std::string asm_data = data; + if (KittyUtils::String::ValidateHex(data)) { + patch_size = data.length() / 2; + patch_code.resize(patch_size); + KittyUtils::dataFromHex(data, patch_code.data()); + // LOGI(OBFUSCATE("expressing %s -> new hex patch: %llx, %zu"), relative, patch_code.data(), patch_size); + } else { + ks_engine *ks = nullptr; + ks_err err = (MP_ASM == 1) ? ks_open(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN, &ks) + : ks_open(KS_ARCH_ARM, KS_MODE_LITTLE_ENDIAN, &ks); + + if (err != KS_ERR_OK) { + KITTY_LOGE(OBFUSCATE("ks_open failed: %s"), ks_strerror(err)); + return; + } + + unsigned char *insn_bytes = nullptr; + size_t insn_size = 0, insn_count = 0; + + if (ks_asm(ks, asm_data.c_str(), 0, &insn_bytes, &insn_size, &insn_count) == 0 && + insn_bytes != nullptr && insn_size > 0) { + patch_size = insn_size; + patch_code.resize(patch_size); + memcpy(patch_code.data(), insn_bytes, patch_size); + } + + if (insn_bytes) ks_free(insn_bytes); + ks_close(ks); + + // LOGI(OBFUSCATE("expressing %s -> new asm patch: %llx, %zu"), relative, patch_code.data(), patch_size); + } + pExpress[key] = std::make_tuple(abs, patch_code, patch_size); + } + + if(!patch_code.empty()) { + DobbyCodePatch(abs, patch_code.data(), patch_size); + // LOGI(OBFUSCATE("New patch created: %s"), relative); + } else { + LOGE(OBFUSCATE("Failed to create patch: %s"), relative); + } + } else { + DobbyDestroy(abs); + // LOGI(OBFUSCATE("Patch removed: %s"), relative); + } +} + +/* use this if for some reason Dobby doesn't suit you: +/// KittyMemory patch implementation std::map memoryPatches; -void patchOffsetWrapper(const char *libName, const char *relative, std::string data, bool change) { +void KittyPatchWrapper(const char *libName, const char *relative, std::string data, bool apply) { auto it = memoryPatches.find(relative); - if(change) { + if(apply) { if(it != memoryPatches.end()) { MemoryPatch& existingPatch = it->second; if(!existingPatch.Modify()) { - LOGE(OBFUSCATE("Failed to modify existing patch at: %s"), relative); + LOGE(OBFUSCATE("Failed to modify existing patch: %s"), relative); return; } - // LOGI(OBFUSCATE("Existing patch modified at: %s"), relative); + // LOGI(OBFUSCATE("Existing patch modified: %s"), relative); } else { MemoryPatch patch; auto address = (uintptr_t) getAbsoluteAddress(libName, relative); @@ -72,33 +149,34 @@ void patchOffsetWrapper(const char *libName, const char *relative, std::string d } if(!patch.isValid()) { - LOGE(OBFUSCATE("Failed to create patch at: 0x%llx"), address); + LOGE(OBFUSCATE("Failed to create patch at: %s"), relative); return; } if(!patch.Modify()) { - LOGE(OBFUSCATE("Failed to apply patch at: 0x%llx"), address); + LOGE(OBFUSCATE("Failed to apply patch at: %s"), relative); return; } memoryPatches[relative] = patch; - // LOGI(OBFUSCATE("New patch applied at: %s"), relative); + // LOGI(OBFUSCATE("New patch applied: %s"), relative); } } else { if(it != memoryPatches.end()) { if(!it->second.Restore()) { - LOGE(OBFUSCATE("Failed to restore patch at: %s"), relative); + LOGE(OBFUSCATE("Failed to remove patch: %s"), relative); return; } - // LOGI(OBFUSCATE("Patch restored at: %s"), relative); + // LOGI(OBFUSCATE("Patch removed: %s"), relative); } } } +*/ /// classic patch (offset || sym) (hex || asm) -#define PATCH(lib, off_sym, hex_asm) patchOffsetWrapper(lib, off_sym, hex_asm, true) +#define PATCH(lib, off_sym, hex_asm) DobbyPatchWrapper(lib, off_sym, hex_asm, true) /// patch original restore (offset || sym) (hex || asm) -#define RESTORE(lib, off_sym) patchOffsetWrapper(lib, off_sym, "", false) +#define RESTORE(lib, off_sym) DobbyPatchWrapper(lib, off_sym, "", false) /// patch switch (offset || sym) (hex || asm) -#define PATCH_SWITCH(lib, off_sym, hex_asm, boolean) patchOffsetWrapper(lib, off_sym, hex_asm, boolean) +#define PATCH_SWITCH(lib, off_sym, hex_asm, boolean) DobbyPatchWrapper(lib, off_sym, hex_asm, boolean) #endif //ANDROID_MOD_MENU_MACROS_H \ No newline at end of file From 82b888b5a12b2a48ee6d9fd21a41d6b6802199d4 Mon Sep 17 00:00:00 2001 From: ABS Date: Sat, 20 Dec 2025 18:51:25 +0000 Subject: [PATCH 07/15] v5.1-pre --- app/src/main/jni/Includes/Macros.h | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app/src/main/jni/Includes/Macros.h b/app/src/main/jni/Includes/Macros.h index d87054b..94c4169 100644 --- a/app/src/main/jni/Includes/Macros.h +++ b/app/src/main/jni/Includes/Macros.h @@ -56,6 +56,8 @@ void DobbyInstrumentWrapper(const char *lib, const char *relative, const char *n /// Dobby-Kitty patch implementation std::map, size_t>> pExpress; +// For some reason, the Dobby doesn't work correctly with destroy, making restore memory impossible... +// Need time to fix the Dobby itself, so now using Kitty implementation. void DobbyPatchWrapper(const char *libName, const char *relative, std::string data, bool apply) { std::string key = relative; auto it = pExpress.find(key); @@ -67,11 +69,11 @@ void DobbyPatchWrapper(const char *libName, const char *relative, std::string da abs = std::get<0>(it->second); patch_code = std::get<1>(it->second); patch_size = std::get<2>(it->second); - // LOGI(OBFUSCATE("%s <- expressed"), relative); + LOGI(OBFUSCATE("%s <- expressed"), relative); } else { abs = getAbsoluteAddress(libName, relative); pExpress[key] = std::make_tuple(abs, patch_code, patch_size); - // LOGI(OBFUSCATE("expressing %s -> new abs: 0x%llx"), relative, abs); + LOGI(OBFUSCATE("expressing %s -> new abs: 0x%llx"), relative, abs); } if(apply) { @@ -81,7 +83,7 @@ void DobbyPatchWrapper(const char *libName, const char *relative, std::string da patch_size = data.length() / 2; patch_code.resize(patch_size); KittyUtils::dataFromHex(data, patch_code.data()); - // LOGI(OBFUSCATE("expressing %s -> new hex patch: %llx, %zu"), relative, patch_code.data(), patch_size); + LOGI(OBFUSCATE("expressing %s -> new hex patch: %llx, %zu"), relative, patch_code.data(), patch_size); } else { ks_engine *ks = nullptr; ks_err err = (MP_ASM == 1) ? ks_open(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN, &ks) @@ -105,24 +107,23 @@ void DobbyPatchWrapper(const char *libName, const char *relative, std::string da if (insn_bytes) ks_free(insn_bytes); ks_close(ks); - // LOGI(OBFUSCATE("expressing %s -> new asm patch: %llx, %zu"), relative, patch_code.data(), patch_size); + LOGI(OBFUSCATE("expressing %s -> new asm patch: %llx, %zu"), relative, patch_code.data(), patch_size); } pExpress[key] = std::make_tuple(abs, patch_code, patch_size); } if(!patch_code.empty()) { DobbyCodePatch(abs, patch_code.data(), patch_size); - // LOGI(OBFUSCATE("New patch created: %s"), relative); + LOGI(OBFUSCATE("New patch created: %s"), relative); } else { LOGE(OBFUSCATE("Failed to create patch: %s"), relative); } } else { DobbyDestroy(abs); - // LOGI(OBFUSCATE("Patch removed: %s"), relative); + LOGI(OBFUSCATE("Patch removed: %s"), relative); } } -/* use this if for some reason Dobby doesn't suit you: /// KittyMemory patch implementation std::map memoryPatches; void KittyPatchWrapper(const char *libName, const char *relative, std::string data, bool apply) { @@ -169,14 +170,13 @@ void KittyPatchWrapper(const char *libName, const char *relative, std::string da } } } -*/ /// classic patch (offset || sym) (hex || asm) -#define PATCH(lib, off_sym, hex_asm) DobbyPatchWrapper(lib, off_sym, hex_asm, true) +#define PATCH(lib, off_sym, hex_asm) KittyPatchWrapper(lib, off_sym, hex_asm, true) /// patch original restore (offset || sym) (hex || asm) -#define RESTORE(lib, off_sym) DobbyPatchWrapper(lib, off_sym, "", false) +#define RESTORE(lib, off_sym) KittyPatchWrapper(lib, off_sym, "", false) /// patch switch (offset || sym) (hex || asm) -#define PATCH_SWITCH(lib, off_sym, hex_asm, boolean) DobbyPatchWrapper(lib, off_sym, hex_asm, boolean) +#define PATCH_SWITCH(lib, off_sym, hex_asm, boolean) KittyPatchWrapper(lib, off_sym, hex_asm, boolean) #endif //ANDROID_MOD_MENU_MACROS_H \ No newline at end of file From cc50eff566ee8517b93b5df0e61cacad98491ed5 Mon Sep 17 00:00:00 2001 From: ABS Date: Sat, 20 Dec 2025 22:32:00 +0000 Subject: [PATCH 08/15] v5.1 --- README.md | 9 +- app/src/main/jni/Includes/Macros.h | 181 +++++++++++++++++++---------- app/src/main/jni/Main.cpp | 33 ++++-- 3 files changed, 148 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 704352b..34e4f50 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ # Introduction ![GitHub](https://img.shields.io/github/license/LGLTeam/Android-Mod-Menu?style=flat-square) -Floating mod menu for il2cpp and other native android games. KittyMemory, MSHook, And64InlineHook and basic string obfuscator (AY obfuscator) included. Assets are stored as base64 in cpp and does not need to be stored under assets folder. +Floating mod menu for il2cpp and other native android games. KittyMemory, Dobby, xDL and basic string obfuscator (AY obfuscator) included. Assets are stored as base64 in cpp and does not need to be stored under assets folder. -Support Android 4.4.x up to Android 15. ARMv7 and ARM64 are supported. +Support Android 4.4.x up to Android 16. ARMv7 and ARM64 are supported. ![](Intro.gif) @@ -23,11 +23,12 @@ If you have an issue with Hooking and game crashes, you should go to the **suppo # Credits Thanks to the following individuals whose code helped me develop this mod menu -* Octowolve/Escanor - Mod menu: https://github.com/z3r0Sec/Substrate-Template-With-Mod-Menu and Hooking: https://github.com/z3r0Sec/Substrate-Hooking-Example +* Octowolve/Escanor - Mod menu: https://github.com/z3r0Sec/Substrate-Template-With-Mod-Menu * VanHoevenTR - Mod menu - https://github.com/LGLTeam/VanHoevenTR_Android_Mod_Menu * MrIkso - First mod menu template https://github.com/MrIkso/FloatingModMenu * MJx0 A.K.A Ruit - https://github.com/MJx0/KittyMemory -* Rprop - https://github.com/Rprop/And64InlineHook +* hexhacking - https://github.com/hexhacking/xDL +* jmpews - https://github.com/jmpews/Dobby * And everyone else who provided input and contributions to this project! # License diff --git a/app/src/main/jni/Includes/Macros.h b/app/src/main/jni/Includes/Macros.h index 94c4169..ce283b4 100644 --- a/app/src/main/jni/Includes/Macros.h +++ b/app/src/main/jni/Includes/Macros.h @@ -13,28 +13,30 @@ int MP_ASM = 1; int MP_ASM = 0; #endif -/// classic hook (offset || sym) +/// dobby hook (offset || sym) #define HOOK(lib, off_sym, ptr, orig) DobbyHookWrapper(lib, off_sym, (void*)(ptr), (void**)&(orig)) -/// hook (offset || sym) without original +/// dobby hook (offset || sym) without original #define HOOK_NO_ORIG(lib, off_sym, ptr) DobbyHookWrapper(lib, off_sym, (void*)(ptr), nullptr) void DobbyHookWrapper(const char *lib, const char *relative, void* hook_function, void** original_function) { void *abs = getAbsoluteAddress(lib, relative); - // LOGI(OBFUSCATE("Off: 0x%llx, Addr: 0x%llx"), offset, (uintptr_t) abs); + int res = -1; if (original_function != nullptr) { - DobbyHook(abs, (dobby_dummy_func_t)hook_function, (dobby_dummy_func_t*)original_function); + res = DobbyHook(abs, (dobby_dummy_func_t)hook_function, (dobby_dummy_func_t*)original_function); + if (res < 0) LOGE(OBFUSCATE("HOOK FAILED: %s"), relative); } else { - DobbyHook(abs, (dobby_dummy_func_t)hook_function, nullptr); + res = DobbyHook(abs, (dobby_dummy_func_t)hook_function, nullptr); + if (res < 0) LOGE(OBFUSCATE("HOOK_NO_ORIG FAILED: %s"), relative); } } -/// (offset || sym) you can use instrument for logging, counting function calls, executing side code before the function is executed -#define INST(lib, off_sym, name) DobbyInstrumentWrapper(lib, off_sym, name) +/// (offset || sym) you can use dobby instrument for logging, counting function calls, executing side code before the function is executed +#define INST(lib, off_sym, name, boolean) DobbyInstrumentWrapper(lib, off_sym, name, boolean) std::map detecting_functions; void Detector(void *address, DobbyRegisterContext *ctx) { @@ -42,90 +44,149 @@ void Detector(void *address, DobbyRegisterContext *ctx) { } /// an example of a wrapper with a function for detecting execution -void DobbyInstrumentWrapper(const char *lib, const char *relative, const char *name) { +void DobbyInstrumentWrapper(const char *lib, const char *relative, const char *name, bool apply) { void *abs = getAbsoluteAddress(lib, relative); - detecting_functions[abs] = name; - - // not access to the arguments "directly," as in a hook - // accessing the arguments requires low-level register reading - DobbyInstrument(abs, (dobby_instrument_callback_t)(Detector)); + if(detecting_functions.count(abs)) { + if(!apply) { + int res = DobbyDestroy(abs); + if(res == 0) { + LOGI(OBFUSCATE("()x_x) %s >>>>>>>>>>>>> INST killed"), detecting_functions[abs]); + detecting_functions.erase(abs); + } else { + LOGE(OBFUSCATE("()4_4) %s >>>>>>>>>>>>> INST kill failed"), detecting_functions[abs]); + } + } + } else { + if(apply) { + // not access to the arguments "directly," as in a hook + // accessing the arguments requires low-level register reading + int res = DobbyInstrument(abs, (dobby_instrument_callback_t) (Detector)); + if (res == 0) { + LOGI(OBFUSCATE("()-_-) %s >>>>>>>>>>>>> INST run"), name); + detecting_functions[abs] = name; + } else { + LOGE(OBFUSCATE("()7_7) %s >>>>>>>>>>>>> INST run failed"), detecting_functions[abs]); + } + } + } } +struct DobbyPatchInfo { + void* address{}; + std::vector original_bytes; + std::vector patch_bytes; + bool applied{}; +}; + +std::map pExpress; /// Dobby-Kitty patch implementation -std::map, size_t>> pExpress; -// For some reason, the Dobby doesn't work correctly with destroy, making restore memory impossible... -// Need time to fix the Dobby itself, so now using Kitty implementation. void DobbyPatchWrapper(const char *libName, const char *relative, std::string data, bool apply) { std::string key = relative; auto it = pExpress.find(key); void* abs = nullptr; - std::vector patch_code; - size_t patch_size = 0; if(it != pExpress.end()) { - abs = std::get<0>(it->second); - patch_code = std::get<1>(it->second); - patch_size = std::get<2>(it->second); - LOGI(OBFUSCATE("%s <- expressed"), relative); + abs = it->second.address; + // LOGI(OBFUSCATE("%s <- expressed"), relative); } else { abs = getAbsoluteAddress(libName, relative); - pExpress[key] = std::make_tuple(abs, patch_code, patch_size); - LOGI(OBFUSCATE("expressing %s -> new abs: 0x%llx"), relative, abs); - } + if (!abs) { + LOGE(OBFUSCATE("Failed to get absolute address for %s"), relative); + return; + } - if(apply) { - if(patch_code.empty()) { - std::string asm_data = data; - if (KittyUtils::String::ValidateHex(data)) { - patch_size = data.length() / 2; - patch_code.resize(patch_size); - KittyUtils::dataFromHex(data, patch_code.data()); - LOGI(OBFUSCATE("expressing %s -> new hex patch: %llx, %zu"), relative, patch_code.data(), patch_size); - } else { - ks_engine *ks = nullptr; - ks_err err = (MP_ASM == 1) ? ks_open(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN, &ks) - : ks_open(KS_ARCH_ARM, KS_MODE_LITTLE_ENDIAN, &ks); + DobbyPatchInfo info; + info.address = abs; + info.applied = false; - if (err != KS_ERR_OK) { - KITTY_LOGE(OBFUSCATE("ks_open failed: %s"), ks_strerror(err)); - return; - } + std::string asm_data = data; + if (KittyUtils::String::ValidateHex(data)) { + size_t patch_size = data.length() / 2; + info.patch_bytes.resize(patch_size); + KittyUtils::dataFromHex(data, info.patch_bytes.data()); + } else { + ks_engine *ks = nullptr; + ks_err err = (MP_ASM == 1) ? ks_open(KS_ARCH_ARM64, KS_MODE_LITTLE_ENDIAN, &ks) + : ks_open(KS_ARCH_ARM, KS_MODE_LITTLE_ENDIAN, &ks); - unsigned char *insn_bytes = nullptr; - size_t insn_size = 0, insn_count = 0; + if (err != KS_ERR_OK) { + KITTY_LOGE(OBFUSCATE("ks_open failed: %s"), ks_strerror(err)); + return; + } - if (ks_asm(ks, asm_data.c_str(), 0, &insn_bytes, &insn_size, &insn_count) == 0 && - insn_bytes != nullptr && insn_size > 0) { - patch_size = insn_size; - patch_code.resize(patch_size); - memcpy(patch_code.data(), insn_bytes, patch_size); - } + unsigned char *insn_bytes = nullptr; + size_t insn_size = 0, insn_count = 0; + if (ks_asm(ks, asm_data.c_str(), 0, &insn_bytes, &insn_size, &insn_count) == 0 && + insn_bytes != nullptr && insn_size > 0) { + info.patch_bytes.resize(insn_size); + memcpy(info.patch_bytes.data(), insn_bytes, insn_size); + } else { + KITTY_LOGE(OBFUSCATE("ks_asm failed for %s"), relative); if (insn_bytes) ks_free(insn_bytes); ks_close(ks); - - LOGI(OBFUSCATE("expressing %s -> new asm patch: %llx, %zu"), relative, patch_code.data(), patch_size); + return; } - pExpress[key] = std::make_tuple(abs, patch_code, patch_size); + + if (insn_bytes) ks_free(insn_bytes); + ks_close(ks); } - if(!patch_code.empty()) { - DobbyCodePatch(abs, patch_code.data(), patch_size); - LOGI(OBFUSCATE("New patch created: %s"), relative); + info.original_bytes.resize(info.patch_bytes.size()); + memcpy(info.original_bytes.data(), info.address, info.patch_bytes.size()); + + pExpress[key] = info; + // LOGI(OBFUSCATE("expressing %s -> new abs: 0x%llx, patch size: %zu"), relative, info.address, info.patch_bytes.size()); + } + + DobbyPatchInfo& info = pExpress[key]; + + if(apply) { + if(!info.applied) { + // LOGI(OBFUSCATE("Applying patch to %s at 0x%llx, size: %zu"), relative, info.address, info.patch_bytes.size()); + + int res = DobbyCodePatch(info.address, info.patch_bytes.data(), info.patch_bytes.size()); + if(res == 0) { + info.applied = true; + // LOGI(OBFUSCATE("New patch created: %s"), relative); + } else { + LOGE(OBFUSCATE("Failed to create patch: %s, error: %d"), relative, res); + } } else { - LOGE(OBFUSCATE("Failed to create patch: %s"), relative); + // LOGI(OBFUSCATE("Patch already applied: %s"), relative); } } else { - DobbyDestroy(abs); - LOGI(OBFUSCATE("Patch removed: %s"), relative); + if(info.applied) { + // LOGI(OBFUSCATE("Restoring original bytes for %s at 0x%llx, size: %zu"), relative, info.address, info.original_bytes.size()); + + int res = DobbyCodePatch(info.address, info.original_bytes.data(), info.original_bytes.size()); + if(res == 0) { + info.applied = false; + // LOGI(OBFUSCATE("Patch removed: %s"), relative); + } else { + LOGE(OBFUSCATE("Failed to restore patch: %s, error: %d"), relative, res); + } + } else { + // LOGI(OBFUSCATE("No need restore: %s"), relative); + } } } -/// KittyMemory patch implementation +/// dobby patch (offset || sym) (hex || asm) +#define PATCH(lib, off_sym, hex_asm) DobbyPatchWrapper(lib, off_sym, hex_asm, true) +/// dobby patch remove (offset || sym) (hex || asm) +#define RESTORE(lib, off_sym) DobbyPatchWrapper(lib, off_sym, "", false) +/// dobby patch switch (offset || sym) (hex || asm) +#define PATCH_SWITCH(lib, off_sym, hex_asm, boolean) DobbyPatchWrapper(lib, off_sym, hex_asm, boolean) + + + +/* use this option if for some reason the Dobby doesn't suit you std::map memoryPatches; +/// KittyMemory patch implementation void KittyPatchWrapper(const char *libName, const char *relative, std::string data, bool apply) { auto it = memoryPatches.find(relative); @@ -175,8 +236,8 @@ void KittyPatchWrapper(const char *libName, const char *relative, std::string da #define PATCH(lib, off_sym, hex_asm) KittyPatchWrapper(lib, off_sym, hex_asm, true) /// patch original restore (offset || sym) (hex || asm) #define RESTORE(lib, off_sym) KittyPatchWrapper(lib, off_sym, "", false) - /// patch switch (offset || sym) (hex || asm) #define PATCH_SWITCH(lib, off_sym, hex_asm, boolean) KittyPatchWrapper(lib, off_sym, hex_asm, boolean) +*/ #endif //ANDROID_MOD_MENU_MACROS_H \ No newline at end of file diff --git a/app/src/main/jni/Main.cpp b/app/src/main/jni/Main.cpp index e7ceca9..2f3be28 100644 --- a/app/src/main/jni/Main.cpp +++ b/app/src/main/jni/Main.cpp @@ -100,12 +100,16 @@ void Changes(JNIEnv *env, jclass clazz, jobject obj, jint featNum, jstring featN case 0: // offset, hex PATCH_SWITCH(targetLibName, "0x1079728", "C0 03 5F D6", boolean); - // The patch switch has been returned and reworked... so it should work stably + // The patch switch has been returned and reworked: + // - (active) Dobby-Kitty implementation + // - reworked KittyMemory implementation + // // if you encounter any problems: - // - uncommiting logging for detailed checking of your functions; + // - switch to Kitty implementation (uncomment code in Macros.h) + // - uncommiting logging for detailed debug // - special attention to the preferences -> this is the only source of this problem in the past that I have noticed: // -- try rename the preferences file; - // -- for a stable and more flexible save settings, recommend using own system with XML/JSON files. + // -- for a maximum stable and flexible save settings, recommend using own system with XML/JSON files. // alt possibles usage variants: // symbol, hex @@ -150,6 +154,17 @@ void Changes(JNIEnv *env, jclass clazz, jobject obj, jint featNum, jstring featN case 3: coinsMul = value; break; + case 5: + // you can use this for things as detect log, counting function calls, executing side code before the function is executed + // now instrument wrapper implemented for detecting execution in logcat + INST(targetLibName, OBFUSCATE("0x235630"), OBFUSCATE("AnyNameForDetect2"), boolean); + + if(boolean) { + INST(targetLibName, OBFUSCATE("_example__sym"), OBFUSCATE("AnyNameForDetect3"), true); + } else { + INST(targetLibName, OBFUSCATE("_example__sym"), OBFUSCATE("AnyNameForDetect3"), false); + } + break; default: break; } @@ -176,7 +191,7 @@ void Update(void *instance) { return old_AddScore(instance, score * scoreMul); } */ -// === This function was completely replaced with super-macro `install_hook_name` from dobby.h === +// === This function was completely replaced with `install_hook_name` from dobby.h === // (base name, return type, ... args) install_hook_name(AddScore, void *, void *instance, int score) { // default any actions @@ -204,13 +219,13 @@ void hack_thread() { #if defined(__aarch64__) //Il2Cpp: Use RVA offset StartInvcibility = (void (*)(void *, float)) getAbsoluteAddress(targetLibName, OBFUSCATE("0x107A3BC")); - // StartInvcibility = (void (*)(void *, float)) getAbsoluteAddress(targetLibName, "_characterPlayer_Update")); + StartInvcibility = (void (*)(void *, float)) getAbsoluteAddress(targetLibName, OBFUSCATE("_characterPlayer_Update")); HOOK(targetLibName, OBFUSCATE("0x107A2FC"), AddCoins, old_AddCoins); // HOOK(targetLibName, OBFUSCATE("0x107A2E0"), AddScore, old_AddScore); // === This function was completely replaced with super-macro `install_hook_name` from dobby.h === - // don't forget set address for hook super-macro: + // don't forget set address for install_hook: install_hook_AddScore(getAbsoluteAddress(targetLibName,OBFUSCATE("0x107A2E0"))); HOOK(targetLibName, OBFUSCATE("0x1078C44"), Update, old_Update); @@ -221,11 +236,7 @@ void hack_thread() { //PATCH(targetLibName, OBFUSCATE("0x10709AC"), "E05F40B2C0035FD6"); - // you can use this for things as detect log, counting function calls, executing side code before the function is executed - // now instrument wrapper implemented for detecting execution - INST(targetLibName, OBFUSCATE("0x23558C"), OBFUSCATE("AnyNameForDetect")); - INST(targetLibName, OBFUSCATE("0x235630"), OBFUSCATE("AnyNameForDetect2")); - INST(targetLibName, OBFUSCATE("_example__sym"), OBFUSCATE("AnyNameForDetect3")); + INST(targetLibName, OBFUSCATE("0x23558C"), OBFUSCATE("AnyNameForDetect"), true); // LOGI("Test SYM: 0x%llx", (uintptr_t)getAbsoluteAddress(targetLibName, "il2cpp_init")); #elif defined(__arm__) From 0038d24186101c1ff7d172063e209031652afdf1 Mon Sep 17 00:00:00 2001 From: ABS Date: Sun, 21 Dec 2025 09:15:47 +0000 Subject: [PATCH 09/15] v5.2 --- app/src/main/jni/Includes/Macros.h | 35 +++++++++++------ app/src/main/jni/Includes/Utils.cpp | 20 ++++++++-- app/src/main/jni/Includes/Utils.hpp | 2 + app/src/main/jni/Main.cpp | 61 +++++++++++++++++++---------- 4 files changed, 84 insertions(+), 34 deletions(-) diff --git a/app/src/main/jni/Includes/Macros.h b/app/src/main/jni/Includes/Macros.h index ce283b4..ac4447d 100644 --- a/app/src/main/jni/Includes/Macros.h +++ b/app/src/main/jni/Includes/Macros.h @@ -14,9 +14,9 @@ int MP_ASM = 0; #endif /// dobby hook (offset || sym) -#define HOOK(lib, off_sym, ptr, orig) DobbyHookWrapper(lib, off_sym, (void*)(ptr), (void**)&(orig)) +#define HOOK(lib, off_sym, ptr, orig) DobbyHookWrapper(lib, OBFUSCATE(off_sym), (void*)(ptr), (void**)&(orig)) /// dobby hook (offset || sym) without original -#define HOOK_NO_ORIG(lib, off_sym, ptr) DobbyHookWrapper(lib, off_sym, (void*)(ptr), nullptr) +#define HOOK_NO_ORIG(lib, off_sym, ptr) DobbyHookWrapper(lib, OBFUSCATE(off_sym), (void*)(ptr), nullptr) void DobbyHookWrapper(const char *lib, const char *relative, void* hook_function, void** original_function) { void *abs = getAbsoluteAddress(lib, relative); @@ -36,7 +36,7 @@ void DobbyHookWrapper(const char *lib, const char *relative, void* hook_function /// (offset || sym) you can use dobby instrument for logging, counting function calls, executing side code before the function is executed -#define INST(lib, off_sym, name, boolean) DobbyInstrumentWrapper(lib, off_sym, name, boolean) +#define INST(lib, off_sym, name, boolean) DobbyInstrumentWrapper(lib, OBFUSCATE(off_sym), OBFUSCATE(name), boolean) std::map detecting_functions; void Detector(void *address, DobbyRegisterContext *ctx) { @@ -176,11 +176,11 @@ void DobbyPatchWrapper(const char *libName, const char *relative, std::string da } /// dobby patch (offset || sym) (hex || asm) -#define PATCH(lib, off_sym, hex_asm) DobbyPatchWrapper(lib, off_sym, hex_asm, true) -/// dobby patch remove (offset || sym) (hex || asm) -#define RESTORE(lib, off_sym) DobbyPatchWrapper(lib, off_sym, "", false) +#define PATCH(lib, off_sym, hex_asm) DobbyPatchWrapper(lib, OBFUSCATE(off_sym), OBFUSCATE(hex_asm), true) +/// dobby patch remove (offset || sym) +#define RESTORE(lib, off_sym) DobbyPatchWrapper(lib, OBFUSCATE(off_sym), "", false) /// dobby patch switch (offset || sym) (hex || asm) -#define PATCH_SWITCH(lib, off_sym, hex_asm, boolean) DobbyPatchWrapper(lib, off_sym, hex_asm, boolean) +#define PATCH_SWITCH(lib, off_sym, hex_asm, boolean) DobbyPatchWrapper(lib, OBFUSCATE(off_sym), OBFUSCATE(hex_asm), boolean) @@ -233,11 +233,24 @@ void KittyPatchWrapper(const char *libName, const char *relative, std::string da } /// classic patch (offset || sym) (hex || asm) -#define PATCH(lib, off_sym, hex_asm) KittyPatchWrapper(lib, off_sym, hex_asm, true) -/// patch original restore (offset || sym) (hex || asm) -#define RESTORE(lib, off_sym) KittyPatchWrapper(lib, off_sym, "", false) +#define PATCH(lib, off_sym, hex_asm) KittyPatchWrapper(lib, OBFUSCATE(off_sym), OBFUSCATE(hex_asm), true) +/// patch original restore (offset || sym) +#define RESTORE(lib, off_sym) KittyPatchWrapper(lib, OBFUSCATE(off_sym), "", false) /// patch switch (offset || sym) (hex || asm) -#define PATCH_SWITCH(lib, off_sym, hex_asm, boolean) KittyPatchWrapper(lib, off_sym, hex_asm, boolean) +#define PATCH_SWITCH(lib, off_sym, hex_asm, boolean) KittyPatchWrapper(lib, OBFUSCATE(off_sym), OBFUSCATE(hex_asm), boolean) */ +/// Relative patches allow you to speed up patch creation if you are sure that the offsets within methods rarely change +void PatchRelativeOffset(const char *libName, const char *rootOffset, const char *addOffset, std::string data, bool apply) { + DobbyPatchWrapper(libName, (char *) getRelativeAddress(libName, rootOffset, addOffset), std::move(data), apply); + // KittyPatchWrapper(libName, (char *) getRelativeAddress(libName, rootOffset, addOffset), std::move(data), apply); +} + +/// relative patch (offset || sym) (offset) (hex || asm) +#define rPATCH(lib, root_off_sym, add_off, hex_asm) PatchRelativeOffset(lib, OBFUSCATE(root_off_sym), OBFUSCATE(add_off), OBFUSCATE(hex_asm), true) +/// relative patch remove (offset || sym) +#define rRESTORE(lib, root_off_sym, add_off) PatchRelativeOffset(lib, OBFUSCATE(root_off_sym), OBFUSCATE(add_off), "", false) +/// relative patch switch (offset || sym) (offset) (hex || asm) +#define rPATCH_SWITCH(lib, root_off_sym, add_off, hex_asm, boolean) PatchRelativeOffset(lib, OBFUSCATE(root_off_sym), OBFUSCATE(add_off), OBFUSCATE(hex_asm), boolean) + #endif //ANDROID_MOD_MENU_MACROS_H \ No newline at end of file diff --git a/app/src/main/jni/Includes/Utils.cpp b/app/src/main/jni/Includes/Utils.cpp index 5c3f709..fcbf4ef 100644 --- a/app/src/main/jni/Includes/Utils.cpp +++ b/app/src/main/jni/Includes/Utils.cpp @@ -21,7 +21,7 @@ uintptr_t getLibraryAddress(const char *libraryName) { return lib_links[libraryName]; } -void* getSymAddress(const char *libraryName, const char *SymName) { +void* getSymAddress(const char *libraryName, const char *SymName, bool relative) { xdl_info_t info; void *handle = xdl_open(libraryName, XDL_DEFAULT); if (handle == nullptr) { @@ -42,7 +42,10 @@ void* getSymAddress(const char *libraryName, const char *SymName) { } xdl_close(handle); - return symbol_addr; + + if (relative) { + return (void*)((uintptr_t) symbol_addr - (uintptr_t) info.dli_fbase); + } else return symbol_addr; } void* getAbsAddress(const char *libraryName, uintptr_t relativeAddr) { @@ -53,13 +56,24 @@ void* getAbsAddress(const char *libraryName, uintptr_t relativeAddr) { return (void*)(lib_links[libraryName] + relativeAddr); } +void* getRelativeAddress(const char *libraryName, const char *rootOffset, const char *addOffset) { + uintptr_t offset = str2offset(rootOffset); + uintptr_t offset2 = str2offset(addOffset); + + if(offset != 0) { + return (void*)(offset + offset2); + } else { + return (void*)((uintptr_t) getSymAddress(libraryName, rootOffset, true)); + } +} + void* getAbsoluteAddress(const char *libraryName, const char *relative) { uintptr_t offset = str2offset(relative); if(offset != 0) { return getAbsAddress(libraryName, offset); } else { - return getSymAddress(libraryName, relative); + return getSymAddress(libraryName, relative, false); // ElfScanner is still available... you can use it for advanced searches } } diff --git a/app/src/main/jni/Includes/Utils.hpp b/app/src/main/jni/Includes/Utils.hpp index fc10c52..a3457c6 100644 --- a/app/src/main/jni/Includes/Utils.hpp +++ b/app/src/main/jni/Includes/Utils.hpp @@ -15,6 +15,8 @@ uintptr_t getLibraryAddress(const char *library); void* getAbsoluteAddress(const char *libraryName, const char *relative); +void* getRelativeAddress(const char *libraryName, const char *rootOffset, const char *addOffset); + jboolean isGameLibLoaded(JNIEnv *env, jobject thiz); bool isLibraryLoaded(const char *libraryName); diff --git a/app/src/main/jni/Main.cpp b/app/src/main/jni/Main.cpp index 2f3be28..5fd2629 100644 --- a/app/src/main/jni/Main.cpp +++ b/app/src/main/jni/Main.cpp @@ -117,7 +117,7 @@ void Changes(JNIEnv *env, jclass clazz, jobject obj, jint featNum, jstring featN // offset, asm PATCH_SWITCH(targetLibName, "0x1079728", "ret", boolean); // symbol, asm - PATCH_SWITCH(targetLibName, OBFUSCATE("_example__sym"), OBFUSCATE("ret"), boolean); + PATCH_SWITCH(targetLibName, "_example__sym", "ret", boolean); // asm allows you to avoid using hex code, as it is generated automatically from the instructions. // - this is the awesome option if you know what you're doing @@ -126,23 +126,43 @@ void Changes(JNIEnv *env, jclass clazz, jobject obj, jint featNum, jstring featN // recommended insert ';' to separate statements, example: "mov x0, #1; ret" // recommended to test your instructions on https://armconverter.com or // https://shell-storm.org/online/Online-Assembler-and-Disassembler/ + + + // Relative patches allow you to speed up patch creation if you are sure that the offsets within methods rarely change + // So, you only need to update the offset instruction for the function + // https://www.rapidtables.com/calc/math/hex-calculator.html <- use hex calculator to calculate the offset relative to the method + // ! This is an extremely unstable due to the hard offsets... don't forget to check the logs to identify outdated offsets + // offset, offset, hex + rPATCH_SWITCH(targetLibName, "0x1079728", "0x204", "C0 03 5F D6", boolean); + // sym, offset, hex + rPATCH_SWITCH(targetLibName, "_example__sym", "0xAC", "C0 03 5F D6", boolean); + // offset, offset, asm + rPATCH_SWITCH(targetLibName, "0x1079728", "0x204", "mov x0, #0xffffff; ret", boolean); + // sym, offset, asm + rPATCH_SWITCH(targetLibName, "_example__sym", "0xAC", "mov x0, #0xffffff; ret", boolean); break; case 4: if(boolean) { // offset, hex - PATCH(targetLibName, OBFUSCATE("0x10709AC"), OBFUSCATE("E05F40B2 C0035FD6")); + PATCH(targetLibName, "0x10709AC", "E05F40B2 C0035FD6"); + rPATCH(targetLibName, "0x107094D", "0x5F", "E05F40B2 C0035FD6"); // alt possibles usage variants: // symbol, hex - PATCH(targetLibName, OBFUSCATE("_example__sym"), OBFUSCATE("E0 5F 40 B2 C0 03 5F D6")); + PATCH(targetLibName, "_example__sym", "E0 5F 40 B2 C0 03 5F D6"); + rPATCH(targetLibName, "_example__sym", "0x5F", "E0 5F 40 B2 C0 03 5F D6"); // offset, asm - PATCH(targetLibName, OBFUSCATE("0x10709AC"), OBFUSCATE("mov x0, #0xffffff; ret")); + PATCH(targetLibName, "0x10709AC", "mov x0, #0xffffff; ret"); + rPATCH(targetLibName, "0x107094D", "0x5F", "mov x0, #0xffffff; ret"); // symbol, asm - PATCH(targetLibName, OBFUSCATE("_example__sym"), OBFUSCATE("mov x0, #0xffffff; ret")); + PATCH(targetLibName, "_example__sym", "mov x0, #0xffffff; ret"); + rPATCH(targetLibName, "_example__sym", "0x5F", "mov x0, #0xffffff; ret"); } else { - RESTORE(targetLibName, OBFUSCATE("0x10709AC")); + RESTORE(targetLibName, "0x10709AC"); + rRESTORE(targetLibName, "0x10709AC", "0x5F"); // or - RESTORE(targetLibName, OBFUSCATE("_example__sym")); + RESTORE(targetLibName, "_example__sym"); + rRESTORE(targetLibName, "_example__sym", "0x5F"); } break; case 1: @@ -157,12 +177,12 @@ void Changes(JNIEnv *env, jclass clazz, jobject obj, jint featNum, jstring featN case 5: // you can use this for things as detect log, counting function calls, executing side code before the function is executed // now instrument wrapper implemented for detecting execution in logcat - INST(targetLibName, OBFUSCATE("0x235630"), OBFUSCATE("AnyNameForDetect2"), boolean); + INST(targetLibName, "0x235630", "AnyNameForDetect2", boolean); if(boolean) { - INST(targetLibName, OBFUSCATE("_example__sym"), OBFUSCATE("AnyNameForDetect3"), true); + INST(targetLibName, "_example__sym", "AnyNameForDetect3", true); } else { - INST(targetLibName, OBFUSCATE("_example__sym"), OBFUSCATE("AnyNameForDetect3"), false); + INST(targetLibName, "_example__sym", "AnyNameForDetect3", false); } break; default: @@ -221,24 +241,25 @@ void hack_thread() { StartInvcibility = (void (*)(void *, float)) getAbsoluteAddress(targetLibName, OBFUSCATE("0x107A3BC")); StartInvcibility = (void (*)(void *, float)) getAbsoluteAddress(targetLibName, OBFUSCATE("_characterPlayer_Update")); - HOOK(targetLibName, OBFUSCATE("0x107A2FC"), AddCoins, old_AddCoins); + HOOK(targetLibName, "0x107A2FC", AddCoins, old_AddCoins); - // HOOK(targetLibName, OBFUSCATE("0x107A2E0"), AddScore, old_AddScore); + // HOOK(targetLibName, "0x107A2E0", AddScore, old_AddScore); // === This function was completely replaced with super-macro `install_hook_name` from dobby.h === // don't forget set address for install_hook: + // ! getAbsoluteAddress not have OBFUSCATE, so don't forget use his here install_hook_AddScore(getAbsoluteAddress(targetLibName,OBFUSCATE("0x107A2E0"))); - HOOK(targetLibName, OBFUSCATE("0x1078C44"), Update, old_Update); - //HOOK(targetLibName, OBFUSCATE("0x1079728"), Kill, old_Kill); - //HOOK(targetLibName, OBFUSCATE("_example__sym"), Kill, old_Kill); - //HOOK_NO_ORIG("libFileC.so", OBFUSCATE("0x123456"), FunctionExample); - //HOOK_NO_ORIG("libFileC.so", OBFUSCATE("_example__sym"), FunctionExample); + HOOK(targetLibName, "0x1078C44", Update, old_Update); + //HOOK(targetLibName, "0x1079728", Kill, old_Kill); + //HOOK(targetLibName, "_example__sym", Kill, old_Kill); + //HOOK_NO_ORIG("libFileC.so", "0x123456", FunctionExample); + //HOOK_NO_ORIG("libFileC.so", "_example__sym", FunctionExample); - //PATCH(targetLibName, OBFUSCATE("0x10709AC"), "E05F40B2C0035FD6"); + //PATCH(targetLibName, "0x10709AC", "E05F40B2C0035FD6"); - INST(targetLibName, OBFUSCATE("0x23558C"), OBFUSCATE("AnyNameForDetect"), true); + INST(targetLibName, "0x23558C", "AnyNameForDetect", true); - // LOGI("Test SYM: 0x%llx", (uintptr_t)getAbsoluteAddress(targetLibName, "il2cpp_init")); + // LOGI(OBFUSCATE("Test SYM: 0x%llx"), (uintptr_t)getAbsoluteAddress(OBFUSCATE("libil2cpp.so"), OBFUSCATE("il2cpp_init"))); #elif defined(__arm__) //Put your code here if you want the code to be compiled for armv7 only #endif From 41ef74eb77f6ed088f810676f3a6a9b04414f9b3 Mon Sep 17 00:00:00 2001 From: ABS Date: Sun, 21 Dec 2025 09:29:50 +0000 Subject: [PATCH 10/15] v5.2 --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 2c9fd48..8841b62 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,7 +7,7 @@ android { minSdkVersion 21 targetSdkVersion 36 versionCode 1 - versionName "5.1" + versionName "5.2" ndk { //noinspection ChromeOsAbiSupport abiFilters 'armeabi-v7a', 'arm64-v8a' From 25b70e3a21309444ee2758a09985b8967c11843e Mon Sep 17 00:00:00 2001 From: ABS Date: Sun, 21 Dec 2025 12:34:40 +0000 Subject: [PATCH 11/15] v5.3 --- app/build.gradle | 2 +- app/src/main/jni/Includes/Macros.h | 12 ++++++++++++ app/src/main/jni/Main.cpp | 7 +++++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 8841b62..ce148eb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,7 +7,7 @@ android { minSdkVersion 21 targetSdkVersion 36 versionCode 1 - versionName "5.2" + versionName "5.3" ndk { //noinspection ChromeOsAbiSupport abiFilters 'armeabi-v7a', 'arm64-v8a' diff --git a/app/src/main/jni/Includes/Macros.h b/app/src/main/jni/Includes/Macros.h index ac4447d..99208a3 100644 --- a/app/src/main/jni/Includes/Macros.h +++ b/app/src/main/jni/Includes/Macros.h @@ -253,4 +253,16 @@ void PatchRelativeOffset(const char *libName, const char *rootOffset, const char /// relative patch switch (offset || sym) (offset) (hex || asm) #define rPATCH_SWITCH(lib, root_off_sym, add_off, hex_asm, boolean) PatchRelativeOffset(lib, OBFUSCATE(root_off_sym), OBFUSCATE(add_off), OBFUSCATE(hex_asm), boolean) + + +/// Dynamic asm patches allow for real-time assembly of structures: + +/// dynamic asm patch (offset || sym) (asm pattern) (asm args) +#define dPATCH(lib, off_sym, asms) DobbyPatchWrapper(lib, OBFUSCATE(off_sym), KittyUtils::String::Fmt(OBFUSCATE(asms), __VA_ARGS__), true) +/// dynamic asm patch remove (offset || sym) +#define dRESTORE(lib, off_sym) DobbyPatchWrapper(lib, OBFUSCATE(off_sym), "", false) +/// dynamic asm patch (offset || sym) (asm pattern) (asm args) +#define dPATCH_SWITCH(boolean, lib, off_sym, asms, ...) DobbyPatchWrapper(lib, OBFUSCATE(off_sym), KittyUtils::String::Fmt(OBFUSCATE(asms), __VA_ARGS__), boolean) +/// dynamic asm relative patch (offset || sym) (offset) (asm pattern) (asm args) +#define drPATCH_SWITCH(boolean, lib, root_off_sym, add_off, asms, ...) PatchRelativeOffset(lib, OBFUSCATE(root_off_sym), OBFUSCATE(add_off), KittyUtils::String::Fmt(OBFUSCATE(asms), __VA_ARGS__), boolean) #endif //ANDROID_MOD_MENU_MACROS_H \ No newline at end of file diff --git a/app/src/main/jni/Main.cpp b/app/src/main/jni/Main.cpp index 5fd2629..abe0343 100644 --- a/app/src/main/jni/Main.cpp +++ b/app/src/main/jni/Main.cpp @@ -121,12 +121,15 @@ void Changes(JNIEnv *env, jclass clazz, jobject obj, jint featNum, jstring featN // asm allows you to avoid using hex code, as it is generated automatically from the instructions. // - this is the awesome option if you know what you're doing - // - this is probably especially useful with creating dynamic deep patches - // recommended insert ';' to separate statements, example: "mov x0, #1; ret" // recommended to test your instructions on https://armconverter.com or // https://shell-storm.org/online/Online-Assembler-and-Disassembler/ + // - this is probably especially useful with creating dynamic deep patches + dPATCH_SWITCH(true, targetLibName, "0x1079728", "mov w%d, #%d", 0, 222); + dPATCH_SWITCH(true, targetLibName, "_example__sym", "mov w%d, #%d", 0, 222); + // standard formatting specifiers are supported (%d, %i, %x, %s, etc.) + // Relative patches allow you to speed up patch creation if you are sure that the offsets within methods rarely change // So, you only need to update the offset instruction for the function From cf04afcb493bb6eeeee1410423d77c3635d4b40f Mon Sep 17 00:00:00 2001 From: ABS Date: Tue, 23 Dec 2025 05:46:58 +0000 Subject: [PATCH 12/15] v5.3 dPATCH fix --- app/src/main/jni/Includes/Macros.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/jni/Includes/Macros.h b/app/src/main/jni/Includes/Macros.h index 99208a3..44bd10e 100644 --- a/app/src/main/jni/Includes/Macros.h +++ b/app/src/main/jni/Includes/Macros.h @@ -258,7 +258,7 @@ void PatchRelativeOffset(const char *libName, const char *rootOffset, const char /// Dynamic asm patches allow for real-time assembly of structures: /// dynamic asm patch (offset || sym) (asm pattern) (asm args) -#define dPATCH(lib, off_sym, asms) DobbyPatchWrapper(lib, OBFUSCATE(off_sym), KittyUtils::String::Fmt(OBFUSCATE(asms), __VA_ARGS__), true) +#define dPATCH(lib, off_sym, asms, ...) DobbyPatchWrapper(lib, OBFUSCATE(off_sym), KittyUtils::String::Fmt(OBFUSCATE(asms), __VA_ARGS__), true) /// dynamic asm patch remove (offset || sym) #define dRESTORE(lib, off_sym) DobbyPatchWrapper(lib, OBFUSCATE(off_sym), "", false) /// dynamic asm patch (offset || sym) (asm pattern) (asm args) From 7e864ccc69966dcc70e308b814db5302c4935409 Mon Sep 17 00:00:00 2001 From: ABS Date: Tue, 23 Dec 2025 10:56:26 +0000 Subject: [PATCH 13/15] v5.3 new dialog --- .../com/android/support/DialogHelper.java | 99 +++++++++++++++++++ app/src/main/jni/Menu/Jni.cpp | 62 +++++++----- app/src/main/jni/Menu/Jni.hpp | 2 +- app/src/main/jni/Menu/Menu.cpp | 26 ++--- 4 files changed, 144 insertions(+), 45 deletions(-) create mode 100644 app/src/main/java/com/android/support/DialogHelper.java diff --git a/app/src/main/java/com/android/support/DialogHelper.java b/app/src/main/java/com/android/support/DialogHelper.java new file mode 100644 index 0000000..9ed0caa --- /dev/null +++ b/app/src/main/java/com/android/support/DialogHelper.java @@ -0,0 +1,99 @@ +package com.android.support; + +import android.annotation.SuppressLint; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.widget.Toast; + +public class DialogHelper { + public static void showDialogWithLink(Context context, String title, String message, String LinkBtnTitle, String CloseBtnTitle, int sec, final String url) { + AlertDialog.Builder builder = new AlertDialog.Builder(context) + .setTitle(title) + .setMessage(message); + + if (url != null) { + builder.setPositiveButton(LinkBtnTitle, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + openUrlSafely(context, url); + } + }); + } + + final AlertDialog dialog = builder.create(); + + String closeInitTitle = CloseBtnTitle; + if(sec > 0) { + closeInitTitle = closeInitTitle + "(" + sec + ")"; + } + + dialog.setButton(DialogInterface.BUTTON_NEGATIVE, closeInitTitle, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + + dialog.show(); + + if (sec > 0) startCountdown(dialog, CloseBtnTitle, sec); + } + + private static void startCountdown(final AlertDialog dialog, String CloseBtnTitle, final int seconds) { + final Handler handler = new Handler(); + final Runnable countdownRunnable = new Runnable() { + int remainingTime = seconds; + + @SuppressLint("SetTextI18n") + @Override + public void run() { + if (remainingTime > 0 && dialog != null && dialog.isShowing()) { + dialog.getButton(AlertDialog.BUTTON_NEGATIVE) + .setText(CloseBtnTitle + "(" + remainingTime + ")"); + remainingTime--; + handler.postDelayed(this, 1000); + } else if (dialog != null && dialog.isShowing()) { + dialog.getButton(AlertDialog.BUTTON_NEGATIVE).performClick(); + } + } + }; + + handler.post(countdownRunnable); + } + + @SuppressLint("QueryPermissionsNeeded") + public static void openUrlSafely(Context context, String url) { + if (url == null || url.trim().isEmpty()) { + return; + } + + if (!url.startsWith("http://") && !url.startsWith("https://")) { + url = "http://" + url; + } + + try { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + + if (intent.resolveActivity(context.getPackageManager()) != null) { + context.startActivity(intent); + } else { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + browserIntent.setPackage("com.android.chrome"); + + if (browserIntent.resolveActivity(context.getPackageManager()) != null) { + context.startActivity(browserIntent); + } else { + browserIntent.setPackage(null); + context.startActivity(browserIntent); + } + } + } catch (Exception e) { + Toast.makeText(context, e.toString(), Toast.LENGTH_SHORT).show(); + } + } +} \ No newline at end of file diff --git a/app/src/main/jni/Menu/Jni.cpp b/app/src/main/jni/Menu/Jni.cpp index c5feeb7..9a48f6d 100644 --- a/app/src/main/jni/Menu/Jni.cpp +++ b/app/src/main/jni/Menu/Jni.cpp @@ -14,32 +14,25 @@ #include "Menu/Jni.hpp" #include "Includes/Logger.h" -//Jni stuff from MrDarkRX https://github.com/MrDarkRXx/DarkMod-Floating -void setDialog(jobject ctx, JNIEnv *env, const char *title, const char *msg){ - jclass Alert = env->FindClass(OBFUSCATE("android/app/AlertDialog$Builder")); - jmethodID AlertCons = env->GetMethodID(Alert, OBFUSCATE(""), OBFUSCATE("(Landroid/content/Context;)V")); - - jobject MainAlert = env->NewObject(Alert, AlertCons, ctx); - - jmethodID setTitle = env->GetMethodID(Alert, OBFUSCATE("setTitle"), OBFUSCATE("(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;")); - env->CallObjectMethod(MainAlert, setTitle, env->NewStringUTF(title)); - - jmethodID setMsg = env->GetMethodID(Alert, OBFUSCATE("setMessage"), OBFUSCATE("(Ljava/lang/CharSequence;)Landroid/app/AlertDialog$Builder;")); - env->CallObjectMethod(MainAlert, setMsg, env->NewStringUTF(msg)); - - jmethodID setCa = env->GetMethodID(Alert, OBFUSCATE("setCancelable"), OBFUSCATE("(Z)Landroid/app/AlertDialog$Builder;")); - env->CallObjectMethod(MainAlert, setCa, false); - - jmethodID setPB = env->GetMethodID(Alert, OBFUSCATE("setPositiveButton"), OBFUSCATE("(Ljava/lang/CharSequence;Landroid/content/DialogInterface$OnClickListener;)Landroid/app/AlertDialog$Builder;")); - env->CallObjectMethod(MainAlert, setPB, env->NewStringUTF("Ok"), static_cast(NULL)); - - jmethodID create = env->GetMethodID(Alert, OBFUSCATE("create"), OBFUSCATE("()Landroid/app/AlertDialog;")); - jobject creaetob = env->CallObjectMethod(MainAlert, create); - - jclass AlertN = env->FindClass(OBFUSCATE("android/app/AlertDialog")); - - jmethodID show = env->GetMethodID(AlertN, OBFUSCATE("show"), OBFUSCATE("()V")); - env->CallVoidMethod(creaetob, show); +void Dialog(JNIEnv *env, jobject context, const char *title, const char *message, const char *openBtn, const char *closeBtn, int sec, const char *url) { + jclass dialogHelperClass = env->FindClass(OBFUSCATE("com/android/support/DialogHelper")); + jmethodID showMethod = env->GetStaticMethodID(dialogHelperClass, OBFUSCATE("showDialogWithLink"), + OBFUSCATE("(Landroid/content/Context;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/String;)V")); + + jstring jTitle = env->NewStringUTF(title); + jstring jMessage = env->NewStringUTF(message); + jstring jOpen = env->NewStringUTF(openBtn); + jstring jClose = env->NewStringUTF(closeBtn); + jint jSec = sec; + jstring jUrl = env->NewStringUTF(url); + + env->CallStaticVoidMethod(dialogHelperClass, showMethod, context, jTitle, jMessage, jOpen, jClose, jSec, jUrl); + + env->DeleteLocalRef(jTitle); + env->DeleteLocalRef(jMessage); + env->DeleteLocalRef(jOpen); + env->DeleteLocalRef(jClose); + env->DeleteLocalRef(jUrl); } void Toast(JNIEnv *env, jobject thiz, const char *text, int length) { @@ -51,6 +44,23 @@ void Toast(JNIEnv *env, jobject thiz, const char *text, int length) { env->CallVoidMethod(toastobj, methodShow); } +//Big letter cause crash +void setText(JNIEnv *env, jobject obj, const char* text){ + //https://stackoverflow.com/a/33627640/3763113 + //A little JNI calls here. You really really need a great knowledge if you want to play with JNI stuff + //Html.fromHtml(""); + jclass html = (*env).FindClass(OBFUSCATE("android/text/Html")); + jmethodID fromHtml = (*env).GetStaticMethodID(html, OBFUSCATE("fromHtml"), OBFUSCATE("(Ljava/lang/String;)Landroid/text/Spanned;")); + + //setText(""); + jclass textView = (*env).FindClass(OBFUSCATE("android/widget/TextView")); + jmethodID setText = (*env).GetMethodID(textView, OBFUSCATE("setText"), OBFUSCATE("(Ljava/lang/CharSequence;)V")); + + //Java string + jstring jstr = (*env).NewStringUTF(text); + (*env).CallVoidMethod(obj, setText, (*env).CallStaticObjectMethod(html, fromHtml, jstr)); +} + void startService(JNIEnv *env, jobject ctx){ jclass native_context = env->GetObjectClass(ctx); jclass intentClass = env->FindClass(OBFUSCATE("android/content/Intent")); diff --git a/app/src/main/jni/Menu/Jni.hpp b/app/src/main/jni/Menu/Jni.hpp index cf90b1c..10c4e60 100644 --- a/app/src/main/jni/Menu/Jni.hpp +++ b/app/src/main/jni/Menu/Jni.hpp @@ -9,7 +9,7 @@ namespace ToastLength { inline const int LENGTH_SHORT = 0; } -void setDialog(jobject ctx, JNIEnv *env, const char *title, const char *msg); +void Dialog(JNIEnv *env, jobject context, const char *title, const char *message, const char *openBtn, const char *closeBtn, int sec, const char *url); void Toast(JNIEnv *env, jobject thiz, const char *text, int length); diff --git a/app/src/main/jni/Menu/Menu.cpp b/app/src/main/jni/Menu/Menu.cpp index 0ed06f6..d8b74eb 100644 --- a/app/src/main/jni/Menu/Menu.cpp +++ b/app/src/main/jni/Menu/Menu.cpp @@ -12,29 +12,19 @@ void Init(JNIEnv *env, jobject thiz, jobject ctx, jobject title, jobject subtitl "")); //Dialog Example - //setDialog(ctx,env,OBFUSCATE("Title"),OBFUSCATE("Message Example")); + Dialog(env, + ctx, + OBFUSCATE("Welcome to your mod menu"), + OBFUSCATE("Thanks for the installation and call to action"), + OBFUSCATE("Visit"), + OBFUSCATE("Close"), + 3, // set 0 if auto-close is not needed + OBFUSCATE("https://github.com/LGLTeam")); //Toast Example Toast(env, ctx, OBFUSCATE("Modded by YOU"), ToastLength::LENGTH_LONG); } -//Big letter cause crash -void setText(JNIEnv *env, jobject obj, const char* text){ - //https://stackoverflow.com/a/33627640/3763113 - //A little JNI calls here. You really really need a great knowledge if you want to play with JNI stuff - //Html.fromHtml(""); - jclass html = (*env).FindClass(OBFUSCATE("android/text/Html")); - jmethodID fromHtml = (*env).GetStaticMethodID(html, OBFUSCATE("fromHtml"), OBFUSCATE("(Ljava/lang/String;)Landroid/text/Spanned;")); - - //setText(""); - jclass textView = (*env).FindClass(OBFUSCATE("android/widget/TextView")); - jmethodID setText = (*env).GetMethodID(textView, OBFUSCATE("setText"), OBFUSCATE("(Ljava/lang/CharSequence;)V")); - - //Java string - jstring jstr = (*env).NewStringUTF(text); - (*env).CallVoidMethod(obj, setText, (*env).CallStaticObjectMethod(html, fromHtml, jstr)); -} - jstring Icon(JNIEnv *env, jobject thiz) { //Use https://www.base64encode.org/ to encode your image to base64 return env->NewStringUTF( From a9cbbaa13b5efab76b6506b23a25463683b1e86b Mon Sep 17 00:00:00 2001 From: ABS Date: Tue, 23 Dec 2025 11:07:04 +0000 Subject: [PATCH 14/15] v5.3 dialog comments usage update --- app/src/main/jni/Menu/Menu.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/jni/Menu/Menu.cpp b/app/src/main/jni/Menu/Menu.cpp index d8b74eb..48d23ca 100644 --- a/app/src/main/jni/Menu/Menu.cpp +++ b/app/src/main/jni/Menu/Menu.cpp @@ -16,10 +16,10 @@ void Init(JNIEnv *env, jobject thiz, jobject ctx, jobject title, jobject subtitl ctx, OBFUSCATE("Welcome to your mod menu"), OBFUSCATE("Thanks for the installation and call to action"), - OBFUSCATE("Visit"), + OBFUSCATE("Visit"), // set nullptr if btn-link is not needed OBFUSCATE("Close"), 3, // set 0 if auto-close is not needed - OBFUSCATE("https://github.com/LGLTeam")); + OBFUSCATE("https://github.com/LGLTeam")); // set nullptr if btn-link is not needed //Toast Example Toast(env, ctx, OBFUSCATE("Modded by YOU"), ToastLength::LENGTH_LONG); From 2af31f65668d646c742143ea8bc870d90e3d5b37 Mon Sep 17 00:00:00 2001 From: ABS Date: Sat, 27 Dec 2025 06:28:03 +0000 Subject: [PATCH 15/15] fix getRelativeAddress --- app/src/main/jni/Includes/Utils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/jni/Includes/Utils.cpp b/app/src/main/jni/Includes/Utils.cpp index fcbf4ef..a325f60 100644 --- a/app/src/main/jni/Includes/Utils.cpp +++ b/app/src/main/jni/Includes/Utils.cpp @@ -61,9 +61,9 @@ void* getRelativeAddress(const char *libraryName, const char *rootOffset, const uintptr_t offset2 = str2offset(addOffset); if(offset != 0) { - return (void*)(offset + offset2); + return getAbsAddress(libraryName, offset + offset2); } else { - return (void*)((uintptr_t) getSymAddress(libraryName, rootOffset, true)); + return getSymAddress(libraryName, rootOffset, true); } }