Fix Android 9 compatibility

This commit is contained in:
Nullptr 2021-09-14 00:41:40 +08:00
parent dbc9d3aa61
commit de06c1cf7c
9 changed files with 131 additions and 156 deletions

View File

@ -6,8 +6,8 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:name="org.lsposed.lspatch.appstub.LSPApplicationStub"
android:allowBackup="true"
android:appComponentFactory="org.lsposed.lspatch.appstub.LSPAppComponentFactoryStub"
android:icon="@mipmap/ic_launcher"
android:label="@string/sample_app_title"
android:roundIcon="@mipmap/ic_launcher_round"

View File

@ -2,9 +2,12 @@ package org.lsposed.lspatch.loader;
import static android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE;
import static org.lsposed.lspatch.share.Constants.ORIGINAL_APK_ASSET_PATH;
import static org.lsposed.lspatch.share.Constants.ORIGINAL_APP_COMPONENT_FACTORY_ASSET_PATH;
import static org.lsposed.lspd.service.ConfigFileManager.loadModule;
import android.annotation.SuppressLint;
import android.app.ActivityThread;
import android.app.AppComponentFactory;
import android.app.LoadedApk;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@ -43,6 +46,7 @@ import java.util.List;
import java.util.Objects;
import java.util.zip.ZipFile;
import dalvik.system.DelegateLastClassLoader;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.XposedInit;
@ -61,6 +65,7 @@ public class LSPApplication extends ApplicationServiceClient {
private static String originalSignature = null;
private static ManagerResolver managerResolver = null;
private static Object activityThread;
private static LoadedApk loadedApkObj;
final static public int FIRST_APP_ZYGOTE_ISOLATED_UID = 90000;
final static public int PER_USER_RANGE = 100000;
@ -86,6 +91,8 @@ public class LSPApplication extends ApplicationServiceClient {
return;
}
initAppComponentFactory(context);
useManager = Boolean.parseBoolean(Objects.requireNonNull(FileUtils.readTextFromAssets(context, USE_MANAGER_CONTROL_PATH)));
originalSignature = FileUtils.readTextFromAssets(context, ORIGINAL_SIGNATURE_ASSET_PATH);
@ -114,6 +121,49 @@ public class LSPApplication extends ApplicationServiceClient {
}
}
@SuppressLint("DiscouragedPrivateApi")
private static void initAppComponentFactory(Context context) {
try {
ApplicationInfo aInfo = context.getApplicationInfo();
ClassLoader baseClassLoader = context.getClassLoader();
Class<?> stubClass = Class.forName("org.lsposed.lspatch.appstub.LSPAppComponentFactoryStub", false, baseClassLoader);
String originPath = aInfo.dataDir + "/cache/lspatch/origin/";
String originalAppComponentFactoryClass = FileUtils.readTextFromInputStream(baseClassLoader.getResourceAsStream(ORIGINAL_APP_COMPONENT_FACTORY_ASSET_PATH));
String cacheApkPath;
try (ZipFile sourceFile = new ZipFile(aInfo.sourceDir)) {
cacheApkPath = originPath + sourceFile.getEntry(ORIGINAL_APK_ASSET_PATH).getCrc();
}
if (!Files.exists(Paths.get(cacheApkPath))) {
Log.i(TAG, "extract original apk");
FileUtils.deleteFolderIfExists(Paths.get(originPath));
Files.createDirectories(Paths.get(originPath));
try (InputStream is = baseClassLoader.getResourceAsStream(ORIGINAL_APK_ASSET_PATH)) {
Files.copy(is, Paths.get(cacheApkPath));
}
}
var appClassLoader = new DelegateLastClassLoader(cacheApkPath, aInfo.nativeLibraryDir, baseClassLoader.getParent());
AppComponentFactory originalAppComponentFactory;
try {
originalAppComponentFactory = (AppComponentFactory) appClassLoader.loadClass(originalAppComponentFactoryClass).newInstance();
} catch (ClassNotFoundException | NullPointerException ignored) {
if (originalAppComponentFactoryClass != null && !originalAppComponentFactoryClass.isEmpty())
Log.w(TAG, "original AppComponentFactory not found");
originalAppComponentFactory = new AppComponentFactory();
}
Field mClassLoaderField = LoadedApk.class.getDeclaredField("mClassLoader");
mClassLoaderField.setAccessible(true);
mClassLoaderField.set(loadedApkObj, appClassLoader);
stubClass.getDeclaredField("appClassLoader").set(null, appClassLoader);
stubClass.getDeclaredField("originalAppComponentFactory").set(null, originalAppComponentFactory);
Log.d(TAG, "set up original AppComponentFactory");
} catch (Throwable e) {
Log.e(TAG, "initAppComponentFactory", e);
}
}
public static void disableProfile(Context context) {
final ArrayList<String> codePaths = new ArrayList<>();
var appInfo = context.getApplicationInfo();
@ -182,7 +232,7 @@ public class LSPApplication extends ApplicationServiceClient {
if (!Files.exists(Paths.get(cacheApkPath))) {
Log.i(TAG, "extract module apk: " + packageName);
org.lsposed.lspatch.share.FileUtils.deleteFolderIfExists(Paths.get(modulePath));
FileUtils.deleteFolderIfExists(Paths.get(modulePath));
Files.createDirectories(Paths.get(modulePath));
try (var is = context.getAssets().open("modules/" + name)) {
Files.copy(is, Paths.get(cacheApkPath));
@ -331,7 +381,7 @@ public class LSPApplication extends ApplicationServiceClient {
}
Field infoField = mBoundApplication.getClass().getDeclaredField("info"); // info
infoField.setAccessible(true);
LoadedApk loadedApkObj = (LoadedApk) infoField.get(mBoundApplication); // LoadedApk
loadedApkObj = (LoadedApk) infoField.get(mBoundApplication); // LoadedApk
if (loadedApkObj == null) {
Log.e(TAG, "loadedApkObj null");
return null;

View File

@ -2,16 +2,60 @@ package org.lsposed.lspatch.loader.util;
import android.content.Context;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
public class FileUtils {
public static String readTextFromInputStream(InputStream is) {
try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8); BufferedReader bufferedReader = new BufferedReader(reader)) {
StringBuilder builder = new StringBuilder();
String str;
while ((str = bufferedReader.readLine()) != null) {
builder.append(str);
}
return builder.toString();
} catch (Throwable ignored) {
}
return null;
}
public static void deleteFolderIfExists(Path target) throws IOException {
if (Files.notExists(target)) return;
Files.walkFileTree(target, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException e)
throws IOException {
if (e == null) {
Files.delete(dir);
return FileVisitResult.CONTINUE;
} else {
throw e;
}
}
});
}
public static String readTextFromAssets(Context context, String assetsFileName) {
if (context == null) {
throw new IllegalStateException("context null");
}
try (InputStream is = context.getAssets().open(assetsFileName)) {
return org.lsposed.lspatch.share.FileUtils.readTextFromInputStream(is);
return readTextFromInputStream(is);
} catch (Throwable ignored) {
}
return null;

View File

@ -50,7 +50,6 @@ android {
}
dependencies {
implementation project(':share')
implementation 'de.upb.cs.swt:axml:2.1.1'
compileOnly project(":hiddenapi-stubs")
}

View File

@ -1,8 +1,5 @@
package org.lsposed.lspatch.appstub;
import static org.lsposed.lspatch.share.Constants.ORIGINAL_APK_ASSET_PATH;
import static org.lsposed.lspatch.share.Constants.ORIGINAL_APP_COMPONENT_FACTORY_ASSET_PATH;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AppComponentFactory;
@ -11,78 +8,54 @@ import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.ContentProvider;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.util.Log;
import org.lsposed.lspatch.share.FileUtils;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Method;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.zip.ZipFile;
import dalvik.system.DelegateLastClassLoader;
@SuppressLint("NewApi")
@SuppressLint({"UnsafeDynamicallyLoadedCode", "DiscouragedPrivateApi"})
public class LSPAppComponentFactoryStub extends AppComponentFactory {
private static final String TAG = "LSPatch";
private static final String PROXY_APPLICATION = "org.lsposed.lspatch.appstub.LSPApplicationStub";
private ClassLoader appClassLoader = null;
private ClassLoader baseClassLoader = null;
private AppComponentFactory originalAppComponentFactory = null;
public static byte[] dex = null;
public static ClassLoader appClassLoader = null;
public static ClassLoader baseClassLoader = null;
public static AppComponentFactory originalAppComponentFactory = null;
/**
* Instantiate original AppComponentFactory<br/>
* This method will be called at <b>instantiateClassLoader</b> by <b>createOrUpdateClassLoaderLocked</b>
**/
private void initOriginalAppComponentFactory(ApplicationInfo aInfo) {
final String originPath = aInfo.dataDir + "/cache/lspatch/origin/";
final String originalAppComponentFactoryClass = FileUtils.readTextFromInputStream(baseClassLoader.getResourceAsStream(ORIGINAL_APP_COMPONENT_FACTORY_ASSET_PATH));
public LSPAppComponentFactoryStub() {
baseClassLoader = getClass().getClassLoader();
try (var is = baseClassLoader.getResourceAsStream("assets/lsp");
var os = new ByteArrayOutputStream()) {
byte[] buffer = new byte[8192];
int n;
while (-1 != (n = is.read(buffer))) {
os.write(buffer, 0, n);
}
dex = os.toByteArray();
} catch (Throwable e) {
Log.e("LSPatch", "load dex error", e);
}
try {
String cacheApkPath;
try (ZipFile sourceFile = new ZipFile(aInfo.sourceDir)) {
cacheApkPath = originPath + sourceFile.getEntry(ORIGINAL_APK_ASSET_PATH).getCrc();
}
Class<?> VMRuntime = Class.forName("dalvik.system.VMRuntime");
Method getRuntime = VMRuntime.getDeclaredMethod("getRuntime");
getRuntime.setAccessible(true);
Method vmInstructionSet = VMRuntime.getDeclaredMethod("vmInstructionSet");
vmInstructionSet.setAccessible(true);
if (!Files.exists(Paths.get(cacheApkPath))) {
Log.i(TAG, "extract original apk");
FileUtils.deleteFolderIfExists(Paths.get(originPath));
Files.createDirectories(Paths.get(originPath));
try (InputStream is = baseClassLoader.getResourceAsStream(ORIGINAL_APK_ASSET_PATH)) {
Files.copy(is, Paths.get(cacheApkPath));
}
}
appClassLoader = new DelegateLastClassLoader(cacheApkPath, aInfo.nativeLibraryDir, baseClassLoader);
try {
originalAppComponentFactory = (AppComponentFactory) appClassLoader.loadClass(originalAppComponentFactoryClass).newInstance();
} catch (ClassNotFoundException | NullPointerException ignored) {
if (originalAppComponentFactoryClass != null && !originalAppComponentFactoryClass.isEmpty())
Log.w(TAG, "original AppComponentFactory not found");
originalAppComponentFactory = new AppComponentFactory();
}
Log.d(TAG, "instantiate original AppComponentFactory: " + originalAppComponentFactory);
String arch = (String) vmInstructionSet.invoke(getRuntime.invoke(null));
String path = baseClassLoader.getResource("assets/lib/lspd/" + arch + "/liblspd.so").getPath().substring(5);
System.load(path);
} catch (Throwable e) {
Log.e(TAG, "initOriginalAppComponentFactory", e);
Log.e("LSPatch", "load lspd error", e);
}
}
@Override
public ClassLoader instantiateClassLoader(ClassLoader cl, ApplicationInfo aInfo) {
baseClassLoader = cl;
initOriginalAppComponentFactory(aInfo);
public Application instantiateApplication(ClassLoader cl, String className) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
Log.d(TAG, "baseClassLoader is " + baseClassLoader);
Log.d(TAG, "appClassLoader is " + appClassLoader);
return originalAppComponentFactory.instantiateClassLoader(appClassLoader, aInfo);
}
@Override
public Application instantiateApplication(ClassLoader cl, String className) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
baseClassLoader.loadClass(PROXY_APPLICATION).newInstance();
Log.d(TAG, "originalAppComponentFactory is " + originalAppComponentFactory);
Log.i(TAG, "lspd initialized, instantiate original application");
return originalAppComponentFactory.instantiateApplication(cl, className);
}

View File

@ -1,41 +0,0 @@
package org.lsposed.lspatch.appstub;
import android.annotation.SuppressLint;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Method;
@SuppressLint("UnsafeDynamicallyLoadedCode")
public class LSPApplicationStub {
private static byte[] dex = null;
static {
try (var is = LSPApplicationStub.class.getClassLoader().getResourceAsStream("assets/lsp");
var os = new ByteArrayOutputStream()) {
byte[] buffer = new byte[8192];
int n;
while (-1 != (n = is.read(buffer))) {
os.write(buffer, 0, n);
}
dex = os.toByteArray();
} catch (Throwable e) {
Log.e("LSPatch", "load dex error", e);
}
try {
Class<?> VMRuntime = Class.forName("dalvik.system.VMRuntime");
Method getRuntime = VMRuntime.getDeclaredMethod("getRuntime");
getRuntime.setAccessible(true);
Method vmInstructionSet = VMRuntime.getDeclaredMethod("vmInstructionSet");
vmInstructionSet.setAccessible(true);
String arch = (String) vmInstructionSet.invoke(getRuntime.invoke(null));
String path = LSPApplicationStub.class.getClassLoader().getResource("assets/lib/lspd/" + arch + "/liblspd.so").getPath().substring(5);
System.load(path);
} catch (Throwable e) {
Log.e("LSPatch", "load lspd error", e);
}
}
}

View File

@ -16,7 +16,7 @@ ext {
androidCompileSdkVersion = 31
androidCompileNdkVersion = "23.0.7599858"
androidBuildToolsVersion = "31.0.0"
androidMinSdkVersion = 27
androidMinSdkVersion = 28
androidTargetSdkVersion = 30
verCode = 1
verName = "lspatch"

2
core

@ -1 +1 @@
Subproject commit 89eedddbd94af1240c14e6172e98c19bd4c7cafa
Subproject commit a6be0018e02bbd0d581c8a5f7ee9310bb8a6a711

View File

@ -1,50 +0,0 @@
package org.lsposed.lspatch.share;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
public class FileUtils {
public static String readTextFromInputStream(InputStream is) {
try (InputStreamReader reader = new InputStreamReader(is, StandardCharsets.UTF_8); BufferedReader bufferedReader = new BufferedReader(reader)) {
StringBuilder builder = new StringBuilder();
String str;
while ((str = bufferedReader.readLine()) != null) {
builder.append(str);
}
return builder.toString();
} catch (Throwable ignored) {
}
return null;
}
public static void deleteFolderIfExists(Path target) throws IOException {
if (Files.notExists(target)) return;
Files.walkFileTree(target, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException e)
throws IOException {
if (e == null) {
Files.delete(dir);
return FileVisitResult.CONTINUE;
} else {
throw e;
}
}
});
}
}