Support configuration

This commit is contained in:
LoveSy 2021-06-22 01:31:22 +08:00
parent 5cd3f73ed1
commit ce39256f3c
6 changed files with 259 additions and 413 deletions

View File

@ -1,44 +1,58 @@
package org.lsposed.lspatch.loader; package org.lsposed.lspatch.loader;
import static android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE; 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.Application;
import android.app.LoadedApk;
import android.content.Context; import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature; import android.content.pm.Signature;
import android.os.Build; import android.os.Build;
import android.os.Environment;
import android.os.IBinder; import android.os.IBinder;
import android.os.Parcel; import android.os.Parcel;
import android.os.ParcelFileDescriptor;
import android.util.Log; 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.FileUtils;
import org.lsposed.lspatch.loader.util.XLog; import org.lsposed.lspatch.loader.util.XLog;
import org.lsposed.lspatch.loader.util.XpatchUtils;
import org.lsposed.lspatch.share.Constants; 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.nativebridge.SigBypass;
import org.lsposed.lspd.yahfa.hooker.YahfaHooker;
import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier; 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.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.XC_MethodHook;
import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.XposedInit;
/** /**
* Created by Windysha * Created by Windysha
*/ */
@SuppressLint("UnsafeDynamicallyLoadedCode") @SuppressWarnings("unused")
public class LSPApplication { public class LSPApplication extends ApplicationServiceClient {
private static final String ORIGINAL_APPLICATION_NAME_ASSET_PATH = "original_application_name.ini"; 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 ORIGINAL_SIGNATURE_ASSET_PATH = "original_signature_info.ini";
private static final String TAG = "LSPatch"; private static final String TAG = "LSPatch";
@ -56,26 +70,24 @@ public class LSPApplication {
static private LSPApplication instance = null; static private LSPApplication instance = null;
static private final Map<String, String> modules = new HashMap<>();
static public boolean isIsolated() { static public boolean isIsolated() {
return (android.os.Process.myUid() % PER_USER_RANGE) >= FIRST_APP_ZYGOTE_ISOLATED_UID; return (android.os.Process.myUid() % PER_USER_RANGE) >= FIRST_APP_ZYGOTE_ISOLATED_UID;
} }
static public void onLoad() { public static void onLoad() {
cacheSigbypassLv = -1; cacheSigbypassLv = -1;
if (isIsolated()) { if (isIsolated()) {
XLog.d(TAG, "skip isolated process"); XLog.d(TAG, "skip isolated process");
return; return;
} }
Context context = XpatchUtils.createAppContext(); Context context = createAppContext();
if (context == null) { if (context == null) {
XLog.e(TAG, "create context err"); XLog.e(TAG, "create context err");
return; 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); originalApplicationName = FileUtils.readTextFromAssets(context, ORIGINAL_APPLICATION_NAME_ASSET_PATH);
originalSignature = FileUtils.readTextFromAssets(context, ORIGINAL_SIGNATURE_ASSET_PATH); originalSignature = FileUtils.readTextFromAssets(context, ORIGINAL_SIGNATURE_ASSET_PATH);
@ -84,14 +96,107 @@ public class LSPApplication {
XLog.d(TAG, "original signature info " + originalSignature); XLog.d(TAG, "original signature info " + originalSignature);
instance = new LSPApplication(); instance = new LSPApplication();
serviceClient = instance;
try { try {
loadModules(context);
Main.forkPostCommon(false, context.getDataDir().toString(), ActivityThread.currentProcessName());
doHook(context); 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) { } catch (Throwable e) {
Log.e(TAG, "Do hook", 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<String> embedded_modules = new HashSet<>();
HashSet<String> 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() { public LSPApplication() {
super(); super();
@ -331,8 +436,7 @@ public class LSPApplication {
private static Object getActivityThread() { private static Object getActivityThread() {
if (activityThread == null) { if (activityThread == null) {
try { try {
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread"); activityThread = ActivityThread.currentActivityThread();
activityThread = XposedHelpers.callStaticMethod(activityThreadClass, "currentActivityThread");
} catch (Throwable e) { } catch (Throwable e) {
Log.e(TAG, "getActivityThread", e); Log.e(TAG, "getActivityThread", e);
} }
@ -422,4 +526,78 @@ public class LSPApplication {
Log.e(TAG, "modifyApplicationInfoClassName", e); 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<String, String> 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;
}
} }

View File

@ -1,324 +1,24 @@
package org.lsposed.lspatch.loader; package org.lsposed.lspatch.loader;
import android.annotation.SuppressLint; import android.app.ActivityThread;
import android.content.Context; import android.content.Context;
import android.content.pm.ApplicationInfo; import android.content.res.XResources;
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 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.XposedBridge;
import de.robv.android.xposed.XposedHelper;
import de.robv.android.xposed.XposedInit; import de.robv.android.xposed.XposedInit;
import de.robv.android.xposed.callbacks.XC_LoadPackage; import de.robv.android.xposed.callbacks.XC_LoadPackage;
public class LSPLoader { public class LSPLoader {
public static void initModules(Context context) {
private static final String TAG = LSPLoader.class.getSimpleName(); XposedInit.loadedPackagesInProcess.add(context.getPackageName());
private static final String DIR_BASE = Environment.getExternalStorageDirectory().getAbsolutePath(); XResources.setPackageNameForResDir(context.getPackageName(), context.getPackageResourcePath());
private static final String XPOSED_MODULE_FILE_PATH = "xpmodules.list"; XC_LoadPackage.LoadPackageParam lpparam = new XC_LoadPackage.LoadPackageParam(
private static AtomicBoolean hasInited = new AtomicBoolean(false); XposedBridge.sLoadedPackageCallbacks);
private static Context appContext; lpparam.packageName = context.getPackageName();
lpparam.processName = ActivityThread.currentProcessName();
lpparam.classLoader = context.getClassLoader();
@SuppressLint("DiscouragedPrivateApi") lpparam.appInfo = context.getApplicationInfo();
public static boolean loadModule(final String moduleApkPath, String moduleLibPath, final ApplicationInfo currentApplicationInfo, ClassLoader appClassLoader) { lpparam.isFirstApplication = true;
XC_LoadPackage.callAll(lpparam);
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_LoadPackage> 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<String> 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<String> loadAllInstalledModule(Context context) {
PackageManager pm = context.getPackageManager();
List<String> modulePathList = new ArrayList<>();
List<String> packageNameList = loadPackageNameListFromFile(true);
List<Pair<String, String>> 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<Pair<String, String>> installedModuleListFinal = installedModuleList;
new Thread(() -> {
List<String> savedPackageNameList = loadPackageNameListFromFile(false);
if (savedPackageNameList == null) {
savedPackageNameList = new ArrayList<>();
}
List<Pair<String, String>> addPackageList = new ArrayList<>();
for (Pair<String, String> 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<String> loadPackageNameListFromFile(boolean loadActivedPackages) {
File moduleFile = new File(DIR_BASE, XPOSED_MODULE_FILE_PATH);
if (!moduleFile.exists()) {
return null;
}
List<String> 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<Pair<String, String>> 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<String, String> 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;
} }
} }

View File

@ -1,9 +1,6 @@
package org.lsposed.lspatch.loader.util; package org.lsposed.lspatch.loader.util;
import android.Manifest;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Process;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.InputStream; import java.io.InputStream;
@ -12,19 +9,6 @@ import java.nio.charset.StandardCharsets;
public class FileUtils { 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) { public static String readTextFromAssets(Context context, String assetsFileName) {
if (context == null) { if (context == null) {
throw new IllegalStateException("context null"); throw new IllegalStateException("context null");

View File

@ -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;
}
}

2
core

@ -1 +1 @@
Subproject commit 9e47e9027c4244317085cce2a95ddcbb7b579ee0 Subproject commit 9891fc4760e3ef806e909ca7aed83c7248991bf7

View File

@ -26,11 +26,12 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.jar.JarFile;
public class LSPatch { public class LSPatch {
@ -41,7 +42,7 @@ public class LSPatch {
} }
@Parameter(description = "apks") @Parameter(description = "apks")
private ArrayList<String> apkPaths = new ArrayList<>(); private List<String> apkPaths = new ArrayList<>();
@Parameter(names = {"-h", "--help"}, help = true, order = 0, description = "Print this message") @Parameter(names = {"-h", "--help"}, help = true, order = 0, description = "Print this message")
private boolean help = false; private boolean help = false;
@ -72,6 +73,9 @@ public class LSPatch {
@Parameter(names = {"-v", "--verbose"}, description = "Verbose output") @Parameter(names = {"-v", "--verbose"}, description = "Verbose output")
private boolean verbose = false; private boolean verbose = false;
@Parameter(names = {"-m", "--embed"}, description = "Embed provided modules to apk")
private List<String> modules = new ArrayList<>();
private int dexFileCount = 0; private int dexFileCount = 0;
private static final String APPLICATION_NAME_ASSET_PATH = "assets/original_application_name.ini"; 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))) { try (var is = new ByteArrayInputStream(applicationName.getBytes(StandardCharsets.UTF_8))) {
zFile.add(APPLICATION_NAME_ASSET_PATH, is); zFile.add(APPLICATION_NAME_ASSET_PATH, is);
} catch (Throwable e) { } 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 // 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))) { try (var is = new ByteArrayInputStream("42".getBytes(StandardCharsets.UTF_8))) {
zFile.add("assets/" + Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel, is); zFile.add("assets/" + Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel, is);
} catch (Throwable e) { } 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..."); System.out.println("Signing apk...");
var sign = zFile.get("META-INF/MANIFEST.MF"); var sign = zFile.get("META-INF/MANIFEST.MF");
if (sign != null) 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 { private byte[] modifyManifestFile(InputStream is) throws IOException {
ModificationProperty property = new ModificationProperty(); ModificationProperty property = new ModificationProperty();