Compress module; Move all lspatch files to one directory; Use a single config json instead of a lot of inis
This commit is contained in:
parent
826f9abec3
commit
99c7f72665
|
|
@ -85,4 +85,6 @@ dependencies {
|
|||
compileOnly project(":hiddenapi-stubs")
|
||||
implementation project(':share')
|
||||
implementation project(':imanager')
|
||||
|
||||
implementation 'com.google.code.gson:gson:2.8.8'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package org.lsposed.lspatch.loader;
|
||||
|
||||
import static android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE;
|
||||
import static org.lsposed.lspatch.share.Constants.CONFIG_ASSET_PATH;
|
||||
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.app.ActivityThread;
|
||||
|
|
@ -22,19 +22,25 @@ import android.system.Os;
|
|||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import org.lsposed.lspatch.loader.util.FileUtils;
|
||||
import org.lsposed.lspatch.loader.util.XLog;
|
||||
import org.lsposed.lspatch.share.Constants;
|
||||
import org.lsposed.lspatch.share.PatchConfig;
|
||||
import org.lsposed.lspd.config.ApplicationServiceClient;
|
||||
import org.lsposed.lspd.core.Main;
|
||||
import org.lsposed.lspd.models.Module;
|
||||
import org.lsposed.lspd.nativebridge.SigBypass;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.attribute.PosixFilePermissions;
|
||||
|
|
@ -53,14 +59,12 @@ import hidden.HiddenApiBridge;
|
|||
*/
|
||||
@SuppressWarnings("unused")
|
||||
public class LSPApplication extends ApplicationServiceClient {
|
||||
private static final String ORIGINAL_SIGNATURE_ASSET_PATH = "original_signature_info.ini";
|
||||
private static final String USE_MANAGER_CONTROL_PATH = "use_manager.ini";
|
||||
private static final String TAG = "LSPatch";
|
||||
|
||||
private static ActivityThread activityThread;
|
||||
private static LoadedApk appLoadedApk;
|
||||
private static boolean useManager;
|
||||
private static String originalSignature = null;
|
||||
|
||||
private static PatchConfig config;
|
||||
private static ManagerResolver managerResolver = null;
|
||||
|
||||
final static public int FIRST_APP_ZYGOTE_ISOLATED_UID = 90000;
|
||||
|
|
@ -75,30 +79,23 @@ public class LSPApplication extends ApplicationServiceClient {
|
|||
}
|
||||
|
||||
public static void onLoad() {
|
||||
cacheSigbypassLv = -1;
|
||||
|
||||
if (isIsolated()) {
|
||||
XLog.d(TAG, "skip isolated process");
|
||||
XLog.d(TAG, "Skip isolated process");
|
||||
return;
|
||||
}
|
||||
activityThread = ActivityThread.currentActivityThread();
|
||||
var context = createLoadedApkWithContext();
|
||||
if (context == null) {
|
||||
XLog.e(TAG, "create context err");
|
||||
XLog.e(TAG, "Error when creating context");
|
||||
return;
|
||||
}
|
||||
|
||||
useManager = Boolean.parseBoolean(FileUtils.readTextFromAssets(context, USE_MANAGER_CONTROL_PATH));
|
||||
originalSignature = FileUtils.readTextFromAssets(context, ORIGINAL_SIGNATURE_ASSET_PATH);
|
||||
|
||||
if (useManager) try {
|
||||
if (config.useManager) try {
|
||||
managerResolver = new ManagerResolver(context);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to instantiate manager resolver", e);
|
||||
}
|
||||
|
||||
XLog.d(TAG, "original signature info " + originalSignature);
|
||||
|
||||
instance = new LSPApplication();
|
||||
serviceClient = instance;
|
||||
try {
|
||||
|
|
@ -111,6 +108,7 @@ public class LSPApplication extends ApplicationServiceClient {
|
|||
// 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(appLoadedApk);
|
||||
Log.i(TAG, "Modules initialized");
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "Do hook", e);
|
||||
}
|
||||
|
|
@ -118,12 +116,21 @@ public class LSPApplication extends ApplicationServiceClient {
|
|||
|
||||
private static Context createLoadedApkWithContext() {
|
||||
try {
|
||||
var baseClassLoader = LSPApplication.class.getClassLoader().getParent();
|
||||
|
||||
var mBoundApplication = XposedHelpers.getObjectField(activityThread, "mBoundApplication");
|
||||
var stubLoadedApk = (LoadedApk) XposedHelpers.getObjectField(mBoundApplication, "info");
|
||||
var appInfo = (ApplicationInfo) XposedHelpers.getObjectField(mBoundApplication, "appInfo");
|
||||
var compatInfo = (CompatibilityInfo) XposedHelpers.getObjectField(mBoundApplication, "compatInfo");
|
||||
var baseClassLoader = stubLoadedApk.getClassLoader();
|
||||
|
||||
try (var is = baseClassLoader.getResourceAsStream(CONFIG_ASSET_PATH)) {
|
||||
BufferedReader streamReader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
|
||||
config = new Gson().fromJson(streamReader, PatchConfig.class);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to load config file");
|
||||
return null;
|
||||
}
|
||||
Log.i(TAG, "Use manager: " + config.useManager);
|
||||
Log.i(TAG, "Signature bypass level: " + config.sigBypassLevel);
|
||||
|
||||
String originPath = appInfo.dataDir + "/cache/lspatch/origin/";
|
||||
String cacheApkPath;
|
||||
|
|
@ -133,10 +140,10 @@ public class LSPApplication extends ApplicationServiceClient {
|
|||
|
||||
appInfo.sourceDir = cacheApkPath;
|
||||
appInfo.publicSourceDir = cacheApkPath;
|
||||
appInfo.appComponentFactory = FileUtils.readTextFromInputStream(baseClassLoader.getResourceAsStream(ORIGINAL_APP_COMPONENT_FACTORY_ASSET_PATH));
|
||||
appInfo.appComponentFactory = config.appComponentFactory;
|
||||
|
||||
if (!Files.exists(Paths.get(cacheApkPath))) {
|
||||
Log.i(TAG, "extract original apk");
|
||||
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)) {
|
||||
|
|
@ -182,31 +189,31 @@ public class LSPApplication extends ApplicationServiceClient {
|
|||
for (int i = codePaths.size() - 1; i >= 0; i--) {
|
||||
String splitName = i == 0 ? null : appInfo.splitNames[i - 1];
|
||||
File curProfileFile = new File(profileDir, splitName == null ? "primary.prof" : splitName + ".split.prof").getAbsoluteFile();
|
||||
Log.d(TAG, "processing " + curProfileFile.getAbsolutePath());
|
||||
Log.d(TAG, "Processing " + curProfileFile.getAbsolutePath());
|
||||
try {
|
||||
if (!curProfileFile.canWrite() && Files.size(curProfileFile.toPath()) == 0) {
|
||||
Log.d(TAG, "skip profile " + curProfileFile.getAbsolutePath());
|
||||
Log.d(TAG, "Skip profile " + curProfileFile.getAbsolutePath());
|
||||
continue;
|
||||
}
|
||||
if (curProfileFile.exists() && !curProfileFile.delete()) {
|
||||
try (var writer = new FileOutputStream(curProfileFile)) {
|
||||
Log.d(TAG, "failed to delete, try to clear content " + curProfileFile.getAbsolutePath());
|
||||
Log.d(TAG, "Failed to delete, try to clear content " + curProfileFile.getAbsolutePath());
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "failed to delete and clear profile file " + curProfileFile.getAbsolutePath(), e);
|
||||
Log.e(TAG, "Failed to delete and clear profile file " + curProfileFile.getAbsolutePath(), e);
|
||||
}
|
||||
Os.chmod(curProfileFile.getAbsolutePath(), 00400);
|
||||
} else {
|
||||
Files.createFile(curProfileFile.toPath(), attrs);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "failed to disable profile file " + curProfileFile.getAbsolutePath(), e);
|
||||
Log.e(TAG, "Failed to disable profile file " + curProfileFile.getAbsolutePath(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static void loadModules(Context context) {
|
||||
if (useManager) {
|
||||
if (config.useManager) {
|
||||
try {
|
||||
modules.addAll(managerResolver.getModules());
|
||||
modules.forEach(m -> Log.i(TAG, "load module from manager: " + m.packageName));
|
||||
|
|
@ -215,19 +222,19 @@ public class LSPApplication extends ApplicationServiceClient {
|
|||
}
|
||||
} else {
|
||||
try {
|
||||
for (var name : context.getAssets().list("modules")) {
|
||||
for (var name : context.getAssets().list("lspatch/modules")) {
|
||||
String packageName = name.substring(0, name.length() - 4);
|
||||
String modulePath = context.getCacheDir() + "/lspatch/" + packageName + "/";
|
||||
String cacheApkPath;
|
||||
try (ZipFile sourceFile = new ZipFile(context.getPackageResourcePath())) {
|
||||
cacheApkPath = modulePath + sourceFile.getEntry("assets/modules/" + name).getCrc();
|
||||
cacheApkPath = modulePath + sourceFile.getEntry("assets/lspatch/modules/" + name).getCrc();
|
||||
}
|
||||
|
||||
if (!Files.exists(Paths.get(cacheApkPath))) {
|
||||
Log.i(TAG, "extract module apk: " + packageName);
|
||||
Log.i(TAG, "Extract module apk: " + packageName);
|
||||
FileUtils.deleteFolderIfExists(Paths.get(modulePath));
|
||||
Files.createDirectories(Paths.get(modulePath));
|
||||
try (var is = context.getAssets().open("modules/" + name)) {
|
||||
try (var is = context.getAssets().open("lspatch/modules/" + name)) {
|
||||
Files.copy(is, Paths.get(cacheApkPath));
|
||||
}
|
||||
}
|
||||
|
|
@ -236,7 +243,6 @@ public class LSPApplication extends ApplicationServiceClient {
|
|||
module.apkPath = cacheApkPath;
|
||||
module.packageName = packageName;
|
||||
module.file = loadModule(cacheApkPath);
|
||||
if (module.file != null) module.file.hostApk = context.getPackageResourcePath();
|
||||
modules.add(module);
|
||||
}
|
||||
} catch (Throwable ignored) {
|
||||
|
|
@ -286,16 +292,16 @@ public class LSPApplication extends ApplicationServiceClient {
|
|||
PackageInfo packageInfo = PackageInfo.CREATOR.createFromParcel(out);
|
||||
if (packageInfo.packageName.equals(context.getApplicationInfo().packageName)) {
|
||||
if (packageInfo.signatures != null && packageInfo.signatures.length > 0) {
|
||||
XLog.d(TAG, "replace signature info [0]");
|
||||
packageInfo.signatures[0] = new Signature(originalSignature);
|
||||
XLog.d(TAG, "Replace signature info (method 1)");
|
||||
packageInfo.signatures[0] = new Signature(config.originalSignature);
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
if (packageInfo.signingInfo != null) {
|
||||
XLog.d(TAG, "replace signature info [1]");
|
||||
XLog.d(TAG, "Replace signature info (method 2)");
|
||||
Signature[] signaturesArray = packageInfo.signingInfo.getApkContentsSigners();
|
||||
if (signaturesArray != null && signaturesArray.length > 0) {
|
||||
signaturesArray[0] = new Signature(originalSignature);
|
||||
signaturesArray[0] = new Signature(config.originalSignature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -320,11 +326,11 @@ public class LSPApplication extends ApplicationServiceClient {
|
|||
}
|
||||
|
||||
private static void doHook(Context context) throws IllegalAccessException, ClassNotFoundException, IOException, NoSuchFieldException {
|
||||
int bypassLv = fetchSigbypassLv(context);
|
||||
if (bypassLv >= Constants.SIGBYPASS_LV_PM) {
|
||||
if (config.sigBypassLevel >= Constants.SIGBYPASS_LV_PM) {
|
||||
XLog.d(TAG, "Original signature: " + config.originalSignature.substring(0, 16) + "...");
|
||||
byPassSignature(context);
|
||||
}
|
||||
if (bypassLv >= Constants.SIGBYPASS_LV_PM_OPENAT) {
|
||||
if (config.sigBypassLevel >= Constants.SIGBYPASS_LV_PM_OPENAT) {
|
||||
String cacheApkPath;
|
||||
try (ZipFile sourceFile = new ZipFile(context.getPackageResourcePath())) {
|
||||
cacheApkPath = context.getCacheDir() + "/lspatch/origin/" + sourceFile.getEntry(ORIGINAL_APK_ASSET_PATH).getCrc();
|
||||
|
|
@ -333,22 +339,6 @@ public class LSPApplication extends ApplicationServiceClient {
|
|||
}
|
||||
}
|
||||
|
||||
private static int cacheSigbypassLv;
|
||||
|
||||
private static int fetchSigbypassLv(Context context) {
|
||||
if (cacheSigbypassLv != -1) {
|
||||
return cacheSigbypassLv;
|
||||
}
|
||||
for (int i = Constants.SIGBYPASS_LV_DISABLE; i < Constants.SIGBYPASS_LV_MAX; i++) {
|
||||
try (InputStream inputStream = context.getAssets().open(Constants.CONFIG_NAME_SIGBYPASSLV + i)) {
|
||||
cacheSigbypassLv = i;
|
||||
return i;
|
||||
} catch (IOException ignore) {
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder requestModuleBinder(String name) {
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,6 @@
|
|||
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;
|
||||
|
|
@ -14,18 +8,6 @@ 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;
|
||||
|
|
@ -49,15 +31,4 @@ public class FileUtils {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static String readTextFromAssets(Context context, String assetsFileName) {
|
||||
if (context == null) {
|
||||
throw new IllegalStateException("context null");
|
||||
}
|
||||
try (InputStream is = context.getAssets().open(assetsFileName)) {
|
||||
return readTextFromInputStream(is);
|
||||
} catch (Throwable ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public class LSPAppComponentFactoryStub extends AppComponentFactory {
|
|||
|
||||
static {
|
||||
var cl = Objects.requireNonNull(LSPAppComponentFactoryStub.class.getClassLoader());
|
||||
try (var is = cl.getResourceAsStream("assets/lsp");
|
||||
try (var is = cl.getResourceAsStream("assets/lspatch/lsp.dex");
|
||||
var os = new ByteArrayOutputStream()) {
|
||||
byte[] buffer = new byte[8192];
|
||||
int n;
|
||||
|
|
@ -34,7 +34,7 @@ public class LSPAppComponentFactoryStub extends AppComponentFactory {
|
|||
vmInstructionSet.setAccessible(true);
|
||||
|
||||
String arch = (String) vmInstructionSet.invoke(getRuntime.invoke(null));
|
||||
String path = cl.getResource("assets/lib/lspd/" + arch + "/liblspd.so").getPath().substring(5);
|
||||
String path = cl.getResource("assets/lspatch/lspd/" + arch + "/liblspd.so").getPath().substring(5);
|
||||
System.load(path);
|
||||
} catch (Throwable e) {
|
||||
Log.e("LSPatch", "load lspd error", e);
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.1.0-alpha11'
|
||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.30'
|
||||
classpath 'com.android.tools.build:gradle:7.1.0-alpha13'
|
||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
2
core
2
core
|
|
@ -1 +1 @@
|
|||
Subproject commit a6be0018e02bbd0d581c8a5f7ee9310bb8a6a711
|
||||
Subproject commit c205f8a2c9d2f207b2ce6de14d0054f5220a0b10
|
||||
|
|
@ -16,6 +16,7 @@ dependencies {
|
|||
implementation project(':apkzlib')
|
||||
implementation 'commons-io:commons-io:2.11.0'
|
||||
implementation 'com.beust:jcommander:1.81'
|
||||
implementation 'com.google.code.gson:gson:2.8.8'
|
||||
}
|
||||
|
||||
jar {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
package org.lsposed.patch;
|
||||
|
||||
import static org.lsposed.lspatch.share.Constants.CONFIG_ASSET_PATH;
|
||||
import static org.lsposed.lspatch.share.Constants.DEX_ASSET_PATH;
|
||||
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.lspatch.share.Constants.PROXY_APP_COMPONENT_FACTORY;
|
||||
|
||||
import com.android.tools.build.apkzlib.sign.SigningExtension;
|
||||
|
|
@ -12,6 +13,7 @@ import com.android.tools.build.apkzlib.zip.ZFile;
|
|||
import com.android.tools.build.apkzlib.zip.ZFileOptions;
|
||||
import com.beust.jcommander.JCommander;
|
||||
import com.beust.jcommander.Parameter;
|
||||
import com.google.gson.Gson;
|
||||
import com.wind.meditor.core.ManifestEditor;
|
||||
import com.wind.meditor.property.AttributeItem;
|
||||
import com.wind.meditor.property.ModificationProperty;
|
||||
|
|
@ -19,13 +21,14 @@ import com.wind.meditor.utils.NodeValue;
|
|||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.lsposed.lspatch.share.Constants;
|
||||
import org.lsposed.lspatch.share.PatchConfig;
|
||||
import org.lsposed.patch.util.ApkSignatureHelper;
|
||||
import org.lsposed.patch.util.ManifestParser;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
|
@ -36,6 +39,7 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
public class LSPatch {
|
||||
|
|
@ -89,8 +93,6 @@ public class LSPatch {
|
|||
@Parameter(names = {"-m", "--embed"}, description = "Embed provided modules to apk")
|
||||
private List<String> modules = new ArrayList<>();
|
||||
|
||||
private static final String SIGNATURE_INFO_ASSET_PATH = "assets/original_signature_info.ini";
|
||||
private static final String USE_MANAGER_CONTROL_PATH = "assets/use_manager.ini";
|
||||
private static final String ANDROID_MANIFEST_XML = "AndroidManifest.xml";
|
||||
private static final HashSet<String> ARCHES = new HashSet<>(Arrays.asList(
|
||||
"armeabi-v7a",
|
||||
|
|
@ -107,7 +109,7 @@ public class LSPatch {
|
|||
|
||||
private static final ZFileOptions Z_FILE_OPTIONS = new ZFileOptions().setAlignmentRule(AlignmentRules.compose(
|
||||
AlignmentRules.constantForSuffix(".so", 4096),
|
||||
AlignmentRules.constantForSuffix(".bin", 4096)
|
||||
AlignmentRules.constantForSuffix(ORIGINAL_APK_ASSET_PATH, 4096)
|
||||
));
|
||||
|
||||
private final JCommander jCommander;
|
||||
|
|
@ -173,7 +175,8 @@ public class LSPatch {
|
|||
|
||||
System.out.println("Parsing original apk...");
|
||||
|
||||
try (ZFile dstZFile = ZFile.openReadWrite(tmpApk, Z_FILE_OPTIONS); var srcZFile = dstZFile.addNestedZip((ignore) -> ORIGINAL_APK_ASSET_PATH, srcApkFile, false)) {
|
||||
try (var dstZFile = ZFile.openReadWrite(tmpApk, Z_FILE_OPTIONS);
|
||||
var srcZFile = dstZFile.addNestedZip((ignore) -> ORIGINAL_APK_ASSET_PATH, srcApkFile, false)) {
|
||||
|
||||
// sign apk
|
||||
System.out.println("Register apk signer...");
|
||||
|
|
@ -193,19 +196,17 @@ public class LSPatch {
|
|||
} catch (Exception e) {
|
||||
throw new PatchError("Failed to register signer", e);
|
||||
}
|
||||
|
||||
String originalSignature = null;
|
||||
if (sigbypassLevel > 0) {
|
||||
// save the apk original signature info, to support crack signature.
|
||||
String originalSignature = ApkSignatureHelper.getApkSignInfo(srcApkFile.getAbsolutePath());
|
||||
originalSignature = ApkSignatureHelper.getApkSignInfo(srcApkFile.getAbsolutePath());
|
||||
if (originalSignature == null || originalSignature.isEmpty()) {
|
||||
throw new PatchError("get original signature failed");
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
System.out.println("Original signature\n" + originalSignature);
|
||||
try (var is = new ByteArrayInputStream(originalSignature.getBytes(StandardCharsets.UTF_8))) {
|
||||
dstZFile.add(SIGNATURE_INFO_ASSET_PATH, is);
|
||||
} catch (Throwable e) {
|
||||
throw new PatchError("Error when saving signature", e);
|
||||
}
|
||||
}
|
||||
|
||||
// copy out manifest file from zlib
|
||||
|
|
@ -213,14 +214,17 @@ public class LSPatch {
|
|||
if (manifestEntry == null)
|
||||
throw new PatchError("Provided file is not a valid apk");
|
||||
|
||||
// parse the app main application full name from the manifest file
|
||||
ManifestParser.Pair pair = ManifestParser.parseManifestFile(manifestEntry.open());
|
||||
if (pair == null)
|
||||
throw new PatchError("Failed to parse AndroidManifest.xml");
|
||||
String appComponentFactory = pair.appComponentFactory == null ? "" : pair.appComponentFactory;
|
||||
// parse the app appComponentFactory full name from the manifest file
|
||||
String appComponentFactory;
|
||||
try (var is = manifestEntry.open()) {
|
||||
var pair =ManifestParser.parseManifestFile(is);
|
||||
if (pair == null)
|
||||
throw new PatchError("Failed to parse AndroidManifest.xml");
|
||||
appComponentFactory = pair.appComponentFactory == null ? "" : pair.appComponentFactory;
|
||||
|
||||
if (verbose)
|
||||
System.out.println("original appComponentFactory class: " + appComponentFactory);
|
||||
if (verbose)
|
||||
System.out.println("original appComponentFactory class: " + appComponentFactory);
|
||||
}
|
||||
|
||||
System.out.println("Patching apk...");
|
||||
// modify manifest
|
||||
|
|
@ -230,18 +234,13 @@ public class LSPatch {
|
|||
throw new PatchError("Error when modifying manifest", e);
|
||||
}
|
||||
|
||||
// save original appComponentFactory name to asset file even its empty
|
||||
try (var is = new ByteArrayInputStream(appComponentFactory.getBytes(StandardCharsets.UTF_8))) {
|
||||
dstZFile.add(ORIGINAL_APP_COMPONENT_FACTORY_ASSET_PATH, is);
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
System.out.println("Adding native lib..");
|
||||
|
||||
// copy so and dex files into the unzipped apk
|
||||
// do not put liblspd.so into apk!lib because x86 native bridge causes crash
|
||||
for (String arch : APK_LIB_PATH_ARRAY) {
|
||||
String entryName = "assets/lib/lspd/" + arch + "/liblspd.so";
|
||||
String entryName = "assets/lspatch/lspd/" + arch + "/liblspd.so";
|
||||
try (var is = getClass().getClassLoader().getResourceAsStream("assets/so/" + (arch.equals("arm") ? "armeabi-v7a" : (arch.equals("arm64") ? "arm64-v8a" : arch)) + "/liblspd.so")) {
|
||||
dstZFile.add(entryName, is, false); // no compress for so
|
||||
} catch (Throwable e) {
|
||||
|
|
@ -258,21 +257,23 @@ public class LSPatch {
|
|||
try (var is = getClass().getClassLoader().getResourceAsStream("assets/dex/loader.dex")) {
|
||||
dstZFile.add("classes.dex", is);
|
||||
} catch (Throwable e) {
|
||||
throw new PatchError("Error when add dex", e);
|
||||
throw new PatchError("Error when adding dex", e);
|
||||
}
|
||||
|
||||
try (var is = getClass().getClassLoader().getResourceAsStream("assets/dex/lsp.dex")) {
|
||||
dstZFile.add("assets/lsp", is);
|
||||
dstZFile.add(DEX_ASSET_PATH, is);
|
||||
} catch (Throwable e) {
|
||||
throw new PatchError("Error when add assets", e);
|
||||
throw new PatchError("Error when adding assets", e);
|
||||
}
|
||||
|
||||
// save lspatch config to asset..
|
||||
try (var is = new ByteArrayInputStream("42".getBytes(StandardCharsets.UTF_8))) {
|
||||
dstZFile.add("assets/" + Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel, is);
|
||||
}
|
||||
try (var is = new ByteArrayInputStream(Boolean.toString(useManager).getBytes(StandardCharsets.UTF_8))) {
|
||||
dstZFile.add(USE_MANAGER_CONTROL_PATH, is);
|
||||
var config = new PatchConfig(useManager, sigbypassLevel, originalSignature, appComponentFactory);
|
||||
var configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
try (var is = new ByteArrayInputStream(configBytes)) {
|
||||
dstZFile.add(CONFIG_ASSET_PATH, is);
|
||||
} catch (Throwable e) {
|
||||
throw new PatchError("Error when saving config");
|
||||
}
|
||||
|
||||
Set<String> apkArchs = new HashSet<>();
|
||||
|
|
@ -296,30 +297,12 @@ public class LSPatch {
|
|||
return false;
|
||||
});
|
||||
|
||||
embedModules(dstZFile);
|
||||
|
||||
// create zip link
|
||||
if (verbose)
|
||||
System.out.println("Creating nested apk link...");
|
||||
|
||||
for (var moduleFile : modules) {
|
||||
final var moduleManifest = new ManifestParser.Pair[]{null};
|
||||
try (var nested = dstZFile.addNestedZip((module) -> {
|
||||
var manifest = module.get(ANDROID_MANIFEST_XML);
|
||||
if (manifest == null) {
|
||||
throw new PatchError(moduleFile + " is not a valid apk file.");
|
||||
}
|
||||
moduleManifest[0] = ManifestParser.parseManifestFile(manifest.open());
|
||||
if (moduleManifest[0] == null) {
|
||||
throw new PatchError(moduleFile + " is not a valid apk file.");
|
||||
}
|
||||
return "assets/modules/" + moduleManifest[0].packageName + ".bin";
|
||||
}, new File(moduleFile), false)) {
|
||||
var packageName = moduleManifest[0].packageName;
|
||||
for (var arch : apkArchs) {
|
||||
dstZFile.addLink(nested.getEntry(), "lib/" + arch + "/" + packageName + ".so");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (StoredEntry entry : srcZFile.entries()) {
|
||||
String name = entry.getCentralDirectoryHeader().getName();
|
||||
if (name.startsWith("classes") && name.endsWith(".dex")) continue;
|
||||
|
|
@ -343,11 +326,27 @@ public class LSPatch {
|
|||
}
|
||||
}
|
||||
|
||||
private void embedModules(ZFile zFile) {
|
||||
System.out.println("Embedding modules...");
|
||||
for (var module : modules) {
|
||||
File file = new File(module);
|
||||
try (var apk = ZFile.openReadOnly(new File(module));
|
||||
var fileIs = new FileInputStream(file);
|
||||
var xmlIs = Objects.requireNonNull(apk.get(ANDROID_MANIFEST_XML)).open()
|
||||
) {
|
||||
var manifest = Objects.requireNonNull(ManifestParser.parseManifestFile(xmlIs));
|
||||
var packageName = manifest.packageName;
|
||||
System.out.println(" - " + packageName);
|
||||
zFile.add("assets/lspatch/modules/" + packageName + ".bin", fileIs);
|
||||
} catch (NullPointerException | IOException e) {
|
||||
System.err.println(module + " does not exist or is not a valid apk file.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private byte[] modifyManifestFile(InputStream is) throws IOException {
|
||||
ModificationProperty property = new ModificationProperty();
|
||||
|
||||
if (!modules.isEmpty())
|
||||
property.addApplicationAttribute(new AttributeItem("extractNativeLibs", true));
|
||||
if (overrideVersionCode)
|
||||
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_CODE, 1));
|
||||
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggableFlag));
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
package org.lsposed.lspatch.share;
|
||||
|
||||
public class Constants {
|
||||
final static public String ORIGINAL_APK_ASSET_PATH = "assets/origin_apk.bin";
|
||||
final static public String ORIGINAL_APP_COMPONENT_FACTORY_ASSET_PATH = "assets/original_app_component_factory.ini";
|
||||
|
||||
final static public String DEX_ASSET_PATH = "assets/lspatch/lsp.dex";
|
||||
final static public String CONFIG_ASSET_PATH = "assets/lspatch/config.json";
|
||||
final static public String ORIGINAL_APK_ASSET_PATH = "assets/lspatch/origin_apk.bin";
|
||||
|
||||
final static public String PROXY_APP_COMPONENT_FACTORY = "org.lsposed.lspatch.appstub.LSPAppComponentFactoryStub";
|
||||
final static public String MANAGER_PACKAGE_NAME = "org.lsposed.lspatch";
|
||||
final static public String CONFIG_NAME_SIGBYPASSLV = "lspatch_sigbypasslv";
|
||||
final static public int SIGBYPASS_LV_DISABLE = 0;
|
||||
final static public int SIGBYPASS_LV_PM = 1;
|
||||
final static public int SIGBYPASS_LV_PM_OPENAT = 2;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
package org.lsposed.lspatch.share;
|
||||
|
||||
public class PatchConfig {
|
||||
|
||||
public final boolean useManager;
|
||||
public final int sigBypassLevel;
|
||||
public final String originalSignature;
|
||||
public final String appComponentFactory;
|
||||
|
||||
public PatchConfig(boolean useManager, int sigBypassLevel, String originalSignature, String appComponentFactory) {
|
||||
this.useManager = useManager;
|
||||
this.sigBypassLevel = sigBypassLevel;
|
||||
this.originalSignature = originalSignature;
|
||||
this.appComponentFactory = appComponentFactory;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue