From ce39256f3ccb86e971819aa4a4b5d7fad9d1c8d1 Mon Sep 17 00:00:00 2001 From: LoveSy Date: Tue, 22 Jun 2021 01:31:22 +0800 Subject: [PATCH] Support configuration --- .../lspatch/loader/LSPApplication.java | 212 +++++++++++- .../org/lsposed/lspatch/loader/LSPLoader.java | 326 +----------------- .../lspatch/loader/util/FileUtils.java | 16 - .../lspatch/loader/util/XpatchUtils.java | 62 ---- core | 2 +- .../main/java/org/lsposed/patch/LSPatch.java | 54 ++- 6 files changed, 259 insertions(+), 413 deletions(-) delete mode 100644 app/src/main/java/org/lsposed/lspatch/loader/util/XpatchUtils.java diff --git a/app/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java b/app/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java index baa648c..e5fdf91 100644 --- a/app/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java +++ b/app/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java @@ -1,44 +1,58 @@ package org.lsposed.lspatch.loader; import static android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE; -import static org.lsposed.lspatch.loader.LSPLoader.initAndLoadModules; -import android.annotation.SuppressLint; +import android.app.ActivityThread; import android.app.Application; +import android.app.LoadedApk; import android.content.Context; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.content.pm.Signature; import android.os.Build; +import android.os.Environment; import android.os.IBinder; import android.os.Parcel; +import android.os.ParcelFileDescriptor; import android.util.Log; +import org.json.JSONArray; +import org.json.JSONObject; import org.lsposed.lspatch.loader.util.FileUtils; import org.lsposed.lspatch.loader.util.XLog; -import org.lsposed.lspatch.loader.util.XpatchUtils; import org.lsposed.lspatch.share.Constants; -import org.lsposed.lspd.deopt.PrebuiltMethodsDeopter; +import org.lsposed.lspd.config.ApplicationServiceClient; +import org.lsposed.lspd.core.Main; import org.lsposed.lspd.nativebridge.SigBypass; -import org.lsposed.lspd.yahfa.hooker.YahfaHooker; +import java.io.ByteArrayInputStream; import java.io.File; +import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; -import de.robv.android.xposed.XposedInit; /** * Created by Windysha */ -@SuppressLint("UnsafeDynamicallyLoadedCode") -public class LSPApplication { +@SuppressWarnings("unused") +public class LSPApplication extends ApplicationServiceClient { private static final String ORIGINAL_APPLICATION_NAME_ASSET_PATH = "original_application_name.ini"; private static final String ORIGINAL_SIGNATURE_ASSET_PATH = "original_signature_info.ini"; private static final String TAG = "LSPatch"; @@ -56,26 +70,24 @@ public class LSPApplication { static private LSPApplication instance = null; + static private final Map modules = new HashMap<>(); + static public boolean isIsolated() { return (android.os.Process.myUid() % PER_USER_RANGE) >= FIRST_APP_ZYGOTE_ISOLATED_UID; } - static public void onLoad() { + public static void onLoad() { cacheSigbypassLv = -1; if (isIsolated()) { XLog.d(TAG, "skip isolated process"); return; } - Context context = XpatchUtils.createAppContext(); + Context context = createAppContext(); if (context == null) { XLog.e(TAG, "create context err"); return; } - YahfaHooker.init(); - // XposedBridge.initXResources(); - // PrebuiltMethodsDeopter.deoptBootMethods(); // do it once for secondary zygote - XposedInit.startsSystemServer = false; originalApplicationName = FileUtils.readTextFromAssets(context, ORIGINAL_APPLICATION_NAME_ASSET_PATH); originalSignature = FileUtils.readTextFromAssets(context, ORIGINAL_SIGNATURE_ASSET_PATH); @@ -84,14 +96,107 @@ public class LSPApplication { XLog.d(TAG, "original signature info " + originalSignature); instance = new LSPApplication(); + serviceClient = instance; + try { + loadModules(context); + Main.forkPostCommon(false, context.getDataDir().toString(), ActivityThread.currentProcessName()); doHook(context); - initAndLoadModules(context); + // WARN: Since it uses `XResource`, the following class should not be initialized + // before forkPostCommon is invoke. Otherwise, you will get failure of XResources + LSPLoader.initModules(context); } catch (Throwable e) { Log.e(TAG, "Do hook", e); } } + public static void loadModules(Context context) { + var configFile = new File(context.getExternalFilesDir(null), "lspatch.json"); + var cacheDir = new File(context.getExternalCacheDir(), "modules"); + cacheDir.mkdirs(); + JSONObject moduleConfigs = new JSONObject(); + try (var is = new FileInputStream(configFile)) { + moduleConfigs = new JSONObject(FileUtils.readTextFromInputStream(is)); + } catch (Throwable ignored) { + } + var modules = moduleConfigs.optJSONArray("modules"); + if (modules == null) { + modules = new JSONArray(); + try { + moduleConfigs.put("modules", modules); + } catch (Throwable ignored) { + + } + } + HashSet embedded_modules = new HashSet<>(); + HashSet disabled_modules = new HashSet<>(); + try { + for (var name : context.getAssets().list("modules")) { + var target = new File(cacheDir, name + ".apk"); + if (target.exists()) { + embedded_modules.add(name); + LSPApplication.modules.put(name, target.getAbsolutePath()); + continue; + } + try (var is = context.getAssets().open("modules/" + name)) { + Files.copy(is, target.toPath()); + embedded_modules.add(name); + LSPApplication.modules.put(name, target.getAbsolutePath()); + } catch (IOException ignored) { + + } + } + } catch (Throwable ignored) { + + } + for (int i = 0; i < modules.length(); ++i) { + var module = modules.optJSONObject(i); + var name = module.optString("name"); + var enabled = module.optBoolean("enabled", true); + var useEmbed = module.optBoolean("use_embed", false); + var apk = module.optString("path"); + if (!enabled) disabled_modules.add(name); + if (embedded_modules.contains(name) && useEmbed) continue; + if (apk.isEmpty() || name.isEmpty()) continue; + if (!new File(apk).exists()) continue; + LSPApplication.modules.put(name, apk); + } + + for (PackageInfo pkg : context.getPackageManager().getInstalledPackages(PackageManager.GET_META_DATA)) { + ApplicationInfo app = pkg.applicationInfo; + if (!app.enabled) { + continue; + } + if (app.metaData != null && app.metaData.containsKey("xposedminversion")) { + LSPApplication.modules.computeIfAbsent(app.packageName, k -> app.publicSourceDir); + } + } + final var new_modules = new JSONArray(); + LSPApplication.modules.forEach((k, v) -> { + try { + var module = new JSONObject(); + module.put("name", k); + module.put("enabled", !disabled_modules.contains(k)); + module.put("use_embed", embedded_modules.contains(k)); + module.put("path", v); + new_modules.put(module); + } catch (Throwable ignored) { + } + }); + try { + moduleConfigs.put("modules", new_modules); + } catch (Throwable ignored) { + } + try (var is = new ByteArrayInputStream(moduleConfigs.toString(4).getBytes(StandardCharsets.UTF_8))) { + Files.copy(is, configFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (Throwable ignored) { + } + for (var module : disabled_modules) { + LSPApplication.modules.remove(module); + } + Log.e(TAG, "DONE LOAD"); + } + public LSPApplication() { super(); @@ -331,8 +436,7 @@ public class LSPApplication { private static Object getActivityThread() { if (activityThread == null) { try { - Class activityThreadClass = Class.forName("android.app.ActivityThread"); - activityThread = XposedHelpers.callStaticMethod(activityThreadClass, "currentActivityThread"); + activityThread = ActivityThread.currentActivityThread(); } catch (Throwable e) { Log.e(TAG, "getActivityThread", e); } @@ -422,4 +526,78 @@ public class LSPApplication { Log.e(TAG, "modifyApplicationInfoClassName", e); } } + + public static Context createAppContext() { + try { + + ActivityThread activityThreadObj = ActivityThread.currentActivityThread(); + + Field boundApplicationField = ActivityThread.class.getDeclaredField("mBoundApplication"); + boundApplicationField.setAccessible(true); + Object mBoundApplication = boundApplicationField.get(activityThreadObj); // AppBindData + if (mBoundApplication == null) { + Log.e(TAG, "mBoundApplication null"); + return null; + } + Field infoField = mBoundApplication.getClass().getDeclaredField("info"); // info + infoField.setAccessible(true); + LoadedApk loadedApkObj = (LoadedApk) infoField.get(mBoundApplication); // LoadedApk + if (loadedApkObj == null) { + Log.e(TAG, "loadedApkObj null"); + return null; + } + Class contextImplClass = Class.forName("android.app.ContextImpl"); + Method createAppContextMethod = contextImplClass.getDeclaredMethod("createAppContext", ActivityThread.class, LoadedApk.class); + createAppContextMethod.setAccessible(true); + + Object context = createAppContextMethod.invoke(null, activityThreadObj, loadedApkObj); + + if (context instanceof Context) { + return (Context) context; + } + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) { + Log.e(TAG, "Fail to create app context", e); + } + return null; + } + + @Override + public IBinder requestModuleBinder() { + return null; + } + + @Override + public IBinder requestManagerBinder(String packageName) { + return null; + } + + @Override + public boolean isResourcesHookEnabled() { + return false; + } + + @Override + public Map getModulesList(String processName) { + return getModulesList(); + } + + @Override + public Map getModulesList() { + return modules; + } + + @Override + public String getPrefsPath(String packageName) { + return new File(Environment.getDataDirectory(), "data/" + packageName + "/shared_prefs/").getAbsolutePath(); + } + + @Override + public ParcelFileDescriptor getModuleLogger() { + return null; + } + + @Override + public IBinder asBinder() { + return null; + } } diff --git a/app/src/main/java/org/lsposed/lspatch/loader/LSPLoader.java b/app/src/main/java/org/lsposed/lspatch/loader/LSPLoader.java index 4996239..c7e4055 100644 --- a/app/src/main/java/org/lsposed/lspatch/loader/LSPLoader.java +++ b/app/src/main/java/org/lsposed/lspatch/loader/LSPLoader.java @@ -1,324 +1,24 @@ package org.lsposed.lspatch.loader; -import android.annotation.SuppressLint; +import android.app.ActivityThread; import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.Build; -import android.os.Environment; -import android.text.TextUtils; -import android.util.Log; -import android.util.Pair; +import android.content.res.XResources; -import org.lsposed.lspatch.loader.util.FileUtils; -import org.lsposed.lspatch.loader.util.XLog; -import org.lsposed.lspatch.loader.util.XpatchUtils; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; - -import dalvik.system.DelegateLastClassLoader; -import de.robv.android.xposed.IXposedHookInitPackageResources; -import de.robv.android.xposed.IXposedHookLoadPackage; -import de.robv.android.xposed.IXposedHookZygoteInit; import de.robv.android.xposed.XposedBridge; -import de.robv.android.xposed.XposedHelper; import de.robv.android.xposed.XposedInit; import de.robv.android.xposed.callbacks.XC_LoadPackage; public class LSPLoader { - - private static final String TAG = LSPLoader.class.getSimpleName(); - private static final String DIR_BASE = Environment.getExternalStorageDirectory().getAbsolutePath(); - private static final String XPOSED_MODULE_FILE_PATH = "xpmodules.list"; - private static AtomicBoolean hasInited = new AtomicBoolean(false); - private static Context appContext; - - - @SuppressLint("DiscouragedPrivateApi") - public static boolean loadModule(final String moduleApkPath, String moduleLibPath, final ApplicationInfo currentApplicationInfo, ClassLoader appClassLoader) { - - XLog.i(TAG, "Loading modules from " + moduleApkPath); - - if (!new File(moduleApkPath).exists()) { - XLog.e(TAG, moduleApkPath + " does not exist"); - return false; - } - - // module can load it's own so - StringBuilder nativePath = new StringBuilder(); - for (String i : Build.SUPPORTED_ABIS) { - nativePath.append(moduleApkPath).append("!/lib/").append(i).append(File.pathSeparator); - } - ClassLoader initLoader = XposedInit.class.getClassLoader(); - ClassLoader mcl = new DelegateLastClassLoader(moduleApkPath, nativePath.toString(), initLoader); - - try { - if (mcl.loadClass(XposedBridge.class.getName()).getClassLoader() != initLoader) { - Log.e(TAG, "Cannot load module:"); - Log.e(TAG, "The Xposed API classes are compiled into the module's APK."); - Log.e(TAG, "This may cause strange issues and must be fixed by the module developer."); - Log.e(TAG, "For details, see: http://api.xposed.info/using.html"); - return false; - } - } - catch (ClassNotFoundException ignored) { - } - - try (InputStream is = mcl.getResourceAsStream("assets/xposed_init")) { - if (is == null) { - XLog.e(TAG, "assets/xposed_init not found in the APK"); - return false; - } - - BufferedReader moduleClassesReader = new BufferedReader(new InputStreamReader(is)); - String moduleClassName; - while ((moduleClassName = moduleClassesReader.readLine()) != null) { - moduleClassName = moduleClassName.trim(); - if (moduleClassName.isEmpty() || moduleClassName.startsWith("#")) { - continue; - } - - try { - XLog.i(TAG, "Loading class " + moduleClassName); - Class moduleClass = mcl.loadClass(moduleClassName); - - if (!XposedHelper.isIXposedMod(moduleClass)) { - Log.w(TAG, "This class doesn't implement any sub-interface of IXposedMod, skipping it"); - continue; - } - else if (IXposedHookInitPackageResources.class.isAssignableFrom(moduleClass)) { - Log.w(TAG, "This class requires resource-related hooks (which are disabled), skipping it."); - continue; - } - - final Object moduleInstance = moduleClass.newInstance(); - if (moduleInstance instanceof IXposedHookZygoteInit) { - XposedHelper.callInitZygote(moduleApkPath, moduleInstance); - } - - if (moduleInstance instanceof IXposedHookLoadPackage) { - // hookLoadPackage(new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance)); - IXposedHookLoadPackage.Wrapper wrapper = new IXposedHookLoadPackage.Wrapper((IXposedHookLoadPackage) moduleInstance, moduleApkPath); - XposedBridge.CopyOnWriteSortedSet xc_loadPackageCopyOnWriteSortedSet = new XposedBridge.CopyOnWriteSortedSet<>(); - xc_loadPackageCopyOnWriteSortedSet.add(wrapper); - XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(xc_loadPackageCopyOnWriteSortedSet); - lpparam.packageName = currentApplicationInfo.packageName; - lpparam.processName = (String) Class.forName("android.app.ActivityThread").getDeclaredMethod("currentProcessName").invoke(null); - lpparam.classLoader = appClassLoader; - lpparam.appInfo = currentApplicationInfo; - lpparam.isFirstApplication = true; - XC_LoadPackage.callAll(lpparam); - } - - if (moduleInstance instanceof IXposedHookInitPackageResources) { - XLog.e(TAG, "Unsupport resource hook"); - } - } - catch (Throwable t) { - XLog.e(TAG, "", t); - } - } - } - catch (IOException e) { - XLog.e(TAG, "", e); - } - return true; - } - - public static void initAndLoadModules() { - Context context = XpatchUtils.createAppContext(); - initAndLoadModules(context); - } - - public static void initAndLoadModules(Context context) { - if (!hasInited.compareAndSet(false, true)) { - XLog.w(TAG, "Has been init"); - return; - } - - if (context == null) { - XLog.e(TAG, "Try to init with context null"); - return; - } - - appContext = context; - - initSELinux(context); - - ClassLoader originClassLoader = context.getClassLoader(); - List modulePathList = loadAllInstalledModule(context); - - for (String modulePath : modulePathList) { - if (!TextUtils.isEmpty(modulePath)) { - LSPLoader.loadModule(modulePath, null, context.getApplicationInfo(), originClassLoader); - } - } - } - - private static void initSELinux(Context context) { - XposedHelper.initSeLinux(context.getApplicationInfo().processName); - } - - private static List loadAllInstalledModule(Context context) { - PackageManager pm = context.getPackageManager(); - List modulePathList = new ArrayList<>(); - - List packageNameList = loadPackageNameListFromFile(true); - List> installedModuleList = new ArrayList<>(); - - boolean configFileExist = configFileExist(); - - // todo: Android 11 - for (PackageInfo pkg : pm.getInstalledPackages(PackageManager.GET_META_DATA)) { - ApplicationInfo app = pkg.applicationInfo; - if (!app.enabled) { - continue; - } - if (app.metaData != null && app.metaData.containsKey("xposedminversion")) { - String apkPath = pkg.applicationInfo.publicSourceDir; - String apkName = context.getPackageManager().getApplicationLabel(pkg.applicationInfo).toString(); - if (TextUtils.isEmpty(apkPath)) { - apkPath = pkg.applicationInfo.sourceDir; - } - if (!TextUtils.isEmpty(apkPath) && (!configFileExist || packageNameList == null || packageNameList.contains(app.packageName))) { - XLog.d(TAG, "query installed module path " + apkPath); - modulePathList.add(apkPath); - } - installedModuleList.add(Pair.create(pkg.applicationInfo.packageName, apkName)); - } - } - - final List> installedModuleListFinal = installedModuleList; - - new Thread(() -> { - List savedPackageNameList = loadPackageNameListFromFile(false); - if (savedPackageNameList == null) { - savedPackageNameList = new ArrayList<>(); - } - List> addPackageList = new ArrayList<>(); - for (Pair packgagePair : installedModuleListFinal) { - if (!savedPackageNameList.contains(packgagePair.first)) { - XLog.d(TAG, "append " + packgagePair + " to " + XPOSED_MODULE_FILE_PATH); - addPackageList.add(packgagePair); - } - } - try { - appendPackageNameToFile(addPackageList); - } - catch (IOException e) { - e.printStackTrace(); - } - }).start(); - return modulePathList; - } - - // 从 sd 卡中加载指定文件,以加载指定的 xposed module - private static List loadPackageNameListFromFile(boolean loadActivedPackages) { - File moduleFile = new File(DIR_BASE, XPOSED_MODULE_FILE_PATH); - if (!moduleFile.exists()) { - return null; - } - List modulePackageList = new ArrayList<>(); - - try (FileInputStream fileInputStream = new FileInputStream(moduleFile); - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream))) { - String modulePackageName; - while ((modulePackageName = bufferedReader.readLine()) != null) { - modulePackageName = modulePackageName.trim(); - if (modulePackageName.isEmpty() || (modulePackageName.startsWith("#") && loadActivedPackages)) { - continue; - } - - if (modulePackageName.startsWith("#")) { - modulePackageName = modulePackageName.substring(1); - } - int index = modulePackageName.indexOf("#"); - if (index > 0) { - modulePackageName = modulePackageName.substring(0, index); - } - XLog.d(TAG, "load " + XPOSED_MODULE_FILE_PATH + " file result, modulePackageName " + modulePackageName); - modulePackageList.add(modulePackageName); - } - } - catch (FileNotFoundException ignore) { - return null; - } - catch (IOException e) { - e.printStackTrace(); - return null; - } - return modulePackageList; - } - - private static void appendPackageNameToFile(List> packageNameList) throws IOException { - - if (isEmpty(packageNameList)) { - return; - } - - File moduleFile = new File(DIR_BASE, XPOSED_MODULE_FILE_PATH); - if (!moduleFile.exists()) { - if (!moduleFile.createNewFile()) { - throw new IllegalStateException("create " + XPOSED_MODULE_FILE_PATH + " err"); - } - } - try (FileOutputStream outputStream = new FileOutputStream(moduleFile, true); - BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream))) { - for (Pair packageInfo : packageNameList) { - String packageName = packageInfo.first; - String appName = packageInfo.second; - writer.write(packageName + "#" + appName); - writer.write("\n"); - XLog.d(TAG, "append new pkg to " + XPOSED_MODULE_FILE_PATH); - } - writer.flush(); - } - catch (FileNotFoundException ignore) { - } - catch (Exception e) { - e.printStackTrace(); - } - } - - private static boolean configFileExist() { - File moduleConfigFile = new File(DIR_BASE, XPOSED_MODULE_FILE_PATH); - return moduleConfigFile.exists(); - } - - private static void closeStream(Closeable closeable) { - if (closeable != null) { - try { - closeable.close(); - } - catch (IOException e) { - e.printStackTrace(); - } - } - } - - private static boolean isEmpty(Collection collection) { - if (collection == null || collection.size() == 0) { - return true; - } - return false; - } - - public static Context getAppContext() { - return appContext; + public static void initModules(Context context) { + XposedInit.loadedPackagesInProcess.add(context.getPackageName()); + XResources.setPackageNameForResDir(context.getPackageName(), context.getPackageResourcePath()); + XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam( + XposedBridge.sLoadedPackageCallbacks); + lpparam.packageName = context.getPackageName(); + lpparam.processName = ActivityThread.currentProcessName(); + lpparam.classLoader = context.getClassLoader(); + lpparam.appInfo = context.getApplicationInfo(); + lpparam.isFirstApplication = true; + XC_LoadPackage.callAll(lpparam); } } diff --git a/app/src/main/java/org/lsposed/lspatch/loader/util/FileUtils.java b/app/src/main/java/org/lsposed/lspatch/loader/util/FileUtils.java index 5f8864c..f6dc78d 100644 --- a/app/src/main/java/org/lsposed/lspatch/loader/util/FileUtils.java +++ b/app/src/main/java/org/lsposed/lspatch/loader/util/FileUtils.java @@ -1,9 +1,6 @@ package org.lsposed.lspatch.loader.util; -import android.Manifest; import android.content.Context; -import android.content.pm.PackageManager; -import android.os.Process; import java.io.BufferedReader; import java.io.InputStream; @@ -12,19 +9,6 @@ import java.nio.charset.StandardCharsets; public class FileUtils { - //读写权限 - private static String[] PERMISSIONS_STORAGE = { - Manifest.permission.READ_EXTERNAL_STORAGE, - Manifest.permission.WRITE_EXTERNAL_STORAGE - }; - - public static boolean isSdcardPermissionGranted(Context context) { - int pid = android.os.Process.myPid(); - int uid = Process.myUid(); - return context.checkPermission(PERMISSIONS_STORAGE[0], pid, uid) == PackageManager.PERMISSION_GRANTED && context.checkPermission(PERMISSIONS_STORAGE[1], pid, - uid) == PackageManager.PERMISSION_GRANTED; - } - public static String readTextFromAssets(Context context, String assetsFileName) { if (context == null) { throw new IllegalStateException("context null"); diff --git a/app/src/main/java/org/lsposed/lspatch/loader/util/XpatchUtils.java b/app/src/main/java/org/lsposed/lspatch/loader/util/XpatchUtils.java deleted file mode 100644 index 31e475f..0000000 --- a/app/src/main/java/org/lsposed/lspatch/loader/util/XpatchUtils.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.lsposed.lspatch.loader.util; - -import android.app.ActivityThread; -import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.util.Log; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; - -public class XpatchUtils { - final static String TAG = "LSP" + XpatchUtils.class.getSimpleName(); - - public static Context createAppContext() { - try { - Class activityThreadClass = Class.forName("android.app.ActivityThread"); - Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread"); - currentActivityThreadMethod.setAccessible(true); - - Object activityThreadObj = currentActivityThreadMethod.invoke(null); - - Field boundApplicationField = activityThreadClass.getDeclaredField("mBoundApplication"); - boundApplicationField.setAccessible(true); - Object mBoundApplication = boundApplicationField.get(activityThreadObj); // AppBindData - if (mBoundApplication == null) { - Log.e(TAG, "mBoundApplication null"); - return null; - } - Field infoField = mBoundApplication.getClass().getDeclaredField("info"); // info - infoField.setAccessible(true); - Object loadedApkObj = infoField.get(mBoundApplication); // LoadedApk - if (loadedApkObj == null) { - Log.e(TAG, "loadedApkObj null"); - return null; - } - Class contextImplClass = Class.forName("android.app.ContextImpl"); - Method createAppContextMethod = contextImplClass.getDeclaredMethod("createAppContext", activityThreadClass, loadedApkObj.getClass()); - createAppContextMethod.setAccessible(true); - - Object context = createAppContextMethod.invoke(null, (ActivityThread) activityThreadObj, loadedApkObj); - - if (context instanceof Context) { - return (Context) context; - } - } - catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) { - e.printStackTrace(); - } - return null; - } - - public static boolean isApkDebugable(Context context) { - try { - ApplicationInfo info = context.getApplicationInfo(); - return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; - } - catch (Exception ignore) { - } - return false; - } -} diff --git a/core b/core index 9e47e90..9891fc4 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit 9e47e9027c4244317085cce2a95ddcbb7b579ee0 +Subproject commit 9891fc4760e3ef806e909ca7aed83c7248991bf7 diff --git a/patch/src/main/java/org/lsposed/patch/LSPatch.java b/patch/src/main/java/org/lsposed/patch/LSPatch.java index 736117d..e137815 100644 --- a/patch/src/main/java/org/lsposed/patch/LSPatch.java +++ b/patch/src/main/java/org/lsposed/patch/LSPatch.java @@ -26,11 +26,12 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; +import java.util.jar.JarFile; public class LSPatch { @@ -41,7 +42,7 @@ public class LSPatch { } @Parameter(description = "apks") - private ArrayList apkPaths = new ArrayList<>(); + private List apkPaths = new ArrayList<>(); @Parameter(names = {"-h", "--help"}, help = true, order = 0, description = "Print this message") private boolean help = false; @@ -72,6 +73,9 @@ public class LSPatch { @Parameter(names = {"-v", "--verbose"}, description = "Verbose output") private boolean verbose = false; + @Parameter(names = {"-m", "--embed"}, description = "Embed provided modules to apk") + private List modules = new ArrayList<>(); + private int dexFileCount = 0; private static final String APPLICATION_NAME_ASSET_PATH = "assets/original_application_name.ini"; @@ -195,7 +199,7 @@ public class LSPatch { try (var is = new ByteArrayInputStream(applicationName.getBytes(StandardCharsets.UTF_8))) { zFile.add(APPLICATION_NAME_ASSET_PATH, is); } catch (Throwable e) { - throw new PatchError("Error when saving signature: " + e); + throw new PatchError("Error when saving application name: " + e); } // copy so and dex files into the unzipped apk @@ -264,9 +268,11 @@ public class LSPatch { try (var is = new ByteArrayInputStream("42".getBytes(StandardCharsets.UTF_8))) { zFile.add("assets/" + Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel, is); } catch (Throwable e) { - throw new PatchError("Error when saving signature: " + e); + throw new PatchError("Error when saving signature bypass level: " + e); } + embedModules(zFile); + System.out.println("Signing apk..."); var sign = zFile.get("META-INF/MANIFEST.MF"); if (sign != null) @@ -286,6 +292,46 @@ public class LSPatch { } } + private void embedModules(ZFile zFile) { + System.out.println("Embedding modules..."); + for (var module : modules) { + var file = new File(module); + if (!file.exists()) { + System.err.println(file.getAbsolutePath() + " does not exist."); + } + + System.out.print("Embedding module "); + + ManifestParser.Pair pair = null; + try (JarFile jar = new JarFile(file)) { + var manifest = jar.getEntry(ANDROID_MANIFEST_XML); + if (manifest == null) { + System.out.println(); + System.err.println(file.getAbsolutePath() + " is not a valid apk file."); + continue; + } + pair = ManifestParser.parseManifestFile(jar.getInputStream(manifest)); + if (pair == null) { + System.out.println(); + System.err.println(file.getAbsolutePath() + " is not a valid apk file."); + continue; + } + System.out.println(pair.packageName); + } catch (Throwable e) { + System.out.println(); + System.err.println(e.getMessage()); + } + if (pair != null) { + try (var is = new FileInputStream(file)) { + zFile.add("assets/modules/" + pair.packageName, is); + } catch (Throwable e) { + System.err.println("Embed " + pair.packageName + " with error: " + e.getMessage()); + } + } + } + + } + private byte[] modifyManifestFile(InputStream is) throws IOException { ModificationProperty property = new ModificationProperty();