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:
Nullptr 2021-10-21 12:48:17 +08:00
parent 826f9abec3
commit 99c7f72665
10 changed files with 125 additions and 145 deletions

View File

@ -85,4 +85,6 @@ dependencies {
compileOnly project(":hiddenapi-stubs") compileOnly project(":hiddenapi-stubs")
implementation project(':share') implementation project(':share')
implementation project(':imanager') implementation project(':imanager')
implementation 'com.google.code.gson:gson:2.8.8'
} }

View File

@ -1,8 +1,8 @@
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.share.Constants.CONFIG_ASSET_PATH;
import static org.lsposed.lspatch.share.Constants.ORIGINAL_APK_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 static org.lsposed.lspd.service.ConfigFileManager.loadModule;
import android.app.ActivityThread; import android.app.ActivityThread;
@ -22,19 +22,25 @@ import android.system.Os;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.Log; import android.util.Log;
import com.google.gson.Gson;
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.share.Constants; import org.lsposed.lspatch.share.Constants;
import org.lsposed.lspatch.share.PatchConfig;
import org.lsposed.lspd.config.ApplicationServiceClient; import org.lsposed.lspd.config.ApplicationServiceClient;
import org.lsposed.lspd.core.Main; import org.lsposed.lspd.core.Main;
import org.lsposed.lspd.models.Module; import org.lsposed.lspd.models.Module;
import org.lsposed.lspd.nativebridge.SigBypass; import org.lsposed.lspd.nativebridge.SigBypass;
import java.io.BufferedReader;
import java.io.File; import java.io.File;
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.io.InputStreamReader;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermissions; import java.nio.file.attribute.PosixFilePermissions;
@ -53,14 +59,12 @@ import hidden.HiddenApiBridge;
*/ */
@SuppressWarnings("unused") @SuppressWarnings("unused")
public class LSPApplication extends ApplicationServiceClient { 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 final String TAG = "LSPatch";
private static ActivityThread activityThread; private static ActivityThread activityThread;
private static LoadedApk appLoadedApk; private static LoadedApk appLoadedApk;
private static boolean useManager;
private static String originalSignature = null; private static PatchConfig config;
private static ManagerResolver managerResolver = null; private static ManagerResolver managerResolver = null;
final static public int FIRST_APP_ZYGOTE_ISOLATED_UID = 90000; final static public int FIRST_APP_ZYGOTE_ISOLATED_UID = 90000;
@ -75,30 +79,23 @@ public class LSPApplication extends ApplicationServiceClient {
} }
public static void onLoad() { public static void onLoad() {
cacheSigbypassLv = -1;
if (isIsolated()) { if (isIsolated()) {
XLog.d(TAG, "skip isolated process"); XLog.d(TAG, "Skip isolated process");
return; return;
} }
activityThread = ActivityThread.currentActivityThread(); activityThread = ActivityThread.currentActivityThread();
var context = createLoadedApkWithContext(); var context = createLoadedApkWithContext();
if (context == null) { if (context == null) {
XLog.e(TAG, "create context err"); XLog.e(TAG, "Error when creating context");
return; return;
} }
useManager = Boolean.parseBoolean(FileUtils.readTextFromAssets(context, USE_MANAGER_CONTROL_PATH)); if (config.useManager) try {
originalSignature = FileUtils.readTextFromAssets(context, ORIGINAL_SIGNATURE_ASSET_PATH);
if (useManager) try {
managerResolver = new ManagerResolver(context); managerResolver = new ManagerResolver(context);
} catch (RemoteException e) { } catch (RemoteException e) {
Log.e(TAG, "Failed to instantiate manager resolver", e); Log.e(TAG, "Failed to instantiate manager resolver", e);
} }
XLog.d(TAG, "original signature info " + originalSignature);
instance = new LSPApplication(); instance = new LSPApplication();
serviceClient = instance; serviceClient = instance;
try { try {
@ -111,6 +108,7 @@ public class LSPApplication extends ApplicationServiceClient {
// WARN: Since it uses `XResource`, the following class should not be initialized // WARN: Since it uses `XResource`, the following class should not be initialized
// before forkPostCommon is invoke. Otherwise, you will get failure of XResources // before forkPostCommon is invoke. Otherwise, you will get failure of XResources
LSPLoader.initModules(appLoadedApk); LSPLoader.initModules(appLoadedApk);
Log.i(TAG, "Modules initialized");
} catch (Throwable e) { } catch (Throwable e) {
Log.e(TAG, "Do hook", e); Log.e(TAG, "Do hook", e);
} }
@ -118,12 +116,21 @@ public class LSPApplication extends ApplicationServiceClient {
private static Context createLoadedApkWithContext() { private static Context createLoadedApkWithContext() {
try { try {
var baseClassLoader = LSPApplication.class.getClassLoader().getParent();
var mBoundApplication = XposedHelpers.getObjectField(activityThread, "mBoundApplication"); var mBoundApplication = XposedHelpers.getObjectField(activityThread, "mBoundApplication");
var stubLoadedApk = (LoadedApk) XposedHelpers.getObjectField(mBoundApplication, "info"); var stubLoadedApk = (LoadedApk) XposedHelpers.getObjectField(mBoundApplication, "info");
var appInfo = (ApplicationInfo) XposedHelpers.getObjectField(mBoundApplication, "appInfo"); var appInfo = (ApplicationInfo) XposedHelpers.getObjectField(mBoundApplication, "appInfo");
var compatInfo = (CompatibilityInfo) XposedHelpers.getObjectField(mBoundApplication, "compatInfo"); 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 originPath = appInfo.dataDir + "/cache/lspatch/origin/";
String cacheApkPath; String cacheApkPath;
@ -133,10 +140,10 @@ public class LSPApplication extends ApplicationServiceClient {
appInfo.sourceDir = cacheApkPath; appInfo.sourceDir = cacheApkPath;
appInfo.publicSourceDir = 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))) { if (!Files.exists(Paths.get(cacheApkPath))) {
Log.i(TAG, "extract original apk"); Log.i(TAG, "Extract original apk");
FileUtils.deleteFolderIfExists(Paths.get(originPath)); FileUtils.deleteFolderIfExists(Paths.get(originPath));
Files.createDirectories(Paths.get(originPath)); Files.createDirectories(Paths.get(originPath));
try (InputStream is = baseClassLoader.getResourceAsStream(ORIGINAL_APK_ASSET_PATH)) { 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--) { for (int i = codePaths.size() - 1; i >= 0; i--) {
String splitName = i == 0 ? null : appInfo.splitNames[i - 1]; String splitName = i == 0 ? null : appInfo.splitNames[i - 1];
File curProfileFile = new File(profileDir, splitName == null ? "primary.prof" : splitName + ".split.prof").getAbsoluteFile(); 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 { try {
if (!curProfileFile.canWrite() && Files.size(curProfileFile.toPath()) == 0) { if (!curProfileFile.canWrite() && Files.size(curProfileFile.toPath()) == 0) {
Log.d(TAG, "skip profile " + curProfileFile.getAbsolutePath()); Log.d(TAG, "Skip profile " + curProfileFile.getAbsolutePath());
continue; continue;
} }
if (curProfileFile.exists() && !curProfileFile.delete()) { if (curProfileFile.exists() && !curProfileFile.delete()) {
try (var writer = new FileOutputStream(curProfileFile)) { 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) { } 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); Os.chmod(curProfileFile.getAbsolutePath(), 00400);
} else { } else {
Files.createFile(curProfileFile.toPath(), attrs); Files.createFile(curProfileFile.toPath(), attrs);
} }
} catch (Throwable e) { } 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) { public static void loadModules(Context context) {
if (useManager) { if (config.useManager) {
try { try {
modules.addAll(managerResolver.getModules()); modules.addAll(managerResolver.getModules());
modules.forEach(m -> Log.i(TAG, "load module from manager: " + m.packageName)); modules.forEach(m -> Log.i(TAG, "load module from manager: " + m.packageName));
@ -215,19 +222,19 @@ public class LSPApplication extends ApplicationServiceClient {
} }
} else { } else {
try { 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 packageName = name.substring(0, name.length() - 4);
String modulePath = context.getCacheDir() + "/lspatch/" + packageName + "/"; String modulePath = context.getCacheDir() + "/lspatch/" + packageName + "/";
String cacheApkPath; String cacheApkPath;
try (ZipFile sourceFile = new ZipFile(context.getPackageResourcePath())) { 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))) { 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)); FileUtils.deleteFolderIfExists(Paths.get(modulePath));
Files.createDirectories(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)); Files.copy(is, Paths.get(cacheApkPath));
} }
} }
@ -236,7 +243,6 @@ public class LSPApplication extends ApplicationServiceClient {
module.apkPath = cacheApkPath; module.apkPath = cacheApkPath;
module.packageName = packageName; module.packageName = packageName;
module.file = loadModule(cacheApkPath); module.file = loadModule(cacheApkPath);
if (module.file != null) module.file.hostApk = context.getPackageResourcePath();
modules.add(module); modules.add(module);
} }
} catch (Throwable ignored) { } catch (Throwable ignored) {
@ -286,16 +292,16 @@ public class LSPApplication extends ApplicationServiceClient {
PackageInfo packageInfo = PackageInfo.CREATOR.createFromParcel(out); PackageInfo packageInfo = PackageInfo.CREATOR.createFromParcel(out);
if (packageInfo.packageName.equals(context.getApplicationInfo().packageName)) { if (packageInfo.packageName.equals(context.getApplicationInfo().packageName)) {
if (packageInfo.signatures != null && packageInfo.signatures.length > 0) { if (packageInfo.signatures != null && packageInfo.signatures.length > 0) {
XLog.d(TAG, "replace signature info [0]"); XLog.d(TAG, "Replace signature info (method 1)");
packageInfo.signatures[0] = new Signature(originalSignature); packageInfo.signatures[0] = new Signature(config.originalSignature);
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (packageInfo.signingInfo != null) { if (packageInfo.signingInfo != null) {
XLog.d(TAG, "replace signature info [1]"); XLog.d(TAG, "Replace signature info (method 2)");
Signature[] signaturesArray = packageInfo.signingInfo.getApkContentsSigners(); Signature[] signaturesArray = packageInfo.signingInfo.getApkContentsSigners();
if (signaturesArray != null && signaturesArray.length > 0) { 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 { private static void doHook(Context context) throws IllegalAccessException, ClassNotFoundException, IOException, NoSuchFieldException {
int bypassLv = fetchSigbypassLv(context); if (config.sigBypassLevel >= Constants.SIGBYPASS_LV_PM) {
if (bypassLv >= Constants.SIGBYPASS_LV_PM) { XLog.d(TAG, "Original signature: " + config.originalSignature.substring(0, 16) + "...");
byPassSignature(context); byPassSignature(context);
} }
if (bypassLv >= Constants.SIGBYPASS_LV_PM_OPENAT) { if (config.sigBypassLevel >= Constants.SIGBYPASS_LV_PM_OPENAT) {
String cacheApkPath; String cacheApkPath;
try (ZipFile sourceFile = new ZipFile(context.getPackageResourcePath())) { try (ZipFile sourceFile = new ZipFile(context.getPackageResourcePath())) {
cacheApkPath = context.getCacheDir() + "/lspatch/origin/" + sourceFile.getEntry(ORIGINAL_APK_ASSET_PATH).getCrc(); 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 @Override
public IBinder requestModuleBinder(String name) { public IBinder requestModuleBinder(String name) {
return null; return null;

View File

@ -1,12 +1,6 @@
package org.lsposed.lspatch.loader.util; package org.lsposed.lspatch.loader.util;
import android.content.Context;
import java.io.BufferedReader;
import java.io.IOException; 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.FileVisitResult;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
@ -14,18 +8,6 @@ import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
public class FileUtils { 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 { public static void deleteFolderIfExists(Path target) throws IOException {
if (Files.notExists(target)) return; 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;
}
} }

View File

@ -14,7 +14,7 @@ public class LSPAppComponentFactoryStub extends AppComponentFactory {
static { static {
var cl = Objects.requireNonNull(LSPAppComponentFactoryStub.class.getClassLoader()); 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()) { var os = new ByteArrayOutputStream()) {
byte[] buffer = new byte[8192]; byte[] buffer = new byte[8192];
int n; int n;
@ -34,7 +34,7 @@ public class LSPAppComponentFactoryStub extends AppComponentFactory {
vmInstructionSet.setAccessible(true); vmInstructionSet.setAccessible(true);
String arch = (String) vmInstructionSet.invoke(getRuntime.invoke(null)); 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); System.load(path);
} catch (Throwable e) { } catch (Throwable e) {
Log.e("LSPatch", "load lspd error", e); Log.e("LSPatch", "load lspd error", e);

View File

@ -6,8 +6,8 @@ buildscript {
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.1.0-alpha11' classpath 'com.android.tools.build:gradle:7.1.0-alpha13'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.30' classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.31'
} }
} }

2
core

@ -1 +1 @@
Subproject commit a6be0018e02bbd0d581c8a5f7ee9310bb8a6a711 Subproject commit c205f8a2c9d2f207b2ce6de14d0054f5220a0b10

View File

@ -16,6 +16,7 @@ dependencies {
implementation project(':apkzlib') implementation project(':apkzlib')
implementation 'commons-io:commons-io:2.11.0' implementation 'commons-io:commons-io:2.11.0'
implementation 'com.beust:jcommander:1.81' implementation 'com.beust:jcommander:1.81'
implementation 'com.google.code.gson:gson:2.8.8'
} }
jar { jar {

View File

@ -1,7 +1,8 @@
package org.lsposed.patch; 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_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 static org.lsposed.lspatch.share.Constants.PROXY_APP_COMPONENT_FACTORY;
import com.android.tools.build.apkzlib.sign.SigningExtension; 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.android.tools.build.apkzlib.zip.ZFileOptions;
import com.beust.jcommander.JCommander; import com.beust.jcommander.JCommander;
import com.beust.jcommander.Parameter; import com.beust.jcommander.Parameter;
import com.google.gson.Gson;
import com.wind.meditor.core.ManifestEditor; import com.wind.meditor.core.ManifestEditor;
import com.wind.meditor.property.AttributeItem; import com.wind.meditor.property.AttributeItem;
import com.wind.meditor.property.ModificationProperty; 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.FileUtils;
import org.apache.commons.io.FilenameUtils; 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.ApkSignatureHelper;
import org.lsposed.patch.util.ManifestParser; import org.lsposed.patch.util.ManifestParser;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -36,6 +39,7 @@ 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.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
public class LSPatch { public class LSPatch {
@ -89,8 +93,6 @@ public class LSPatch {
@Parameter(names = {"-m", "--embed"}, description = "Embed provided modules to apk") @Parameter(names = {"-m", "--embed"}, description = "Embed provided modules to apk")
private List<String> modules = new ArrayList<>(); 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 String ANDROID_MANIFEST_XML = "AndroidManifest.xml";
private static final HashSet<String> ARCHES = new HashSet<>(Arrays.asList( private static final HashSet<String> ARCHES = new HashSet<>(Arrays.asList(
"armeabi-v7a", "armeabi-v7a",
@ -107,7 +109,7 @@ public class LSPatch {
private static final ZFileOptions Z_FILE_OPTIONS = new ZFileOptions().setAlignmentRule(AlignmentRules.compose( private static final ZFileOptions Z_FILE_OPTIONS = new ZFileOptions().setAlignmentRule(AlignmentRules.compose(
AlignmentRules.constantForSuffix(".so", 4096), AlignmentRules.constantForSuffix(".so", 4096),
AlignmentRules.constantForSuffix(".bin", 4096) AlignmentRules.constantForSuffix(ORIGINAL_APK_ASSET_PATH, 4096)
)); ));
private final JCommander jCommander; private final JCommander jCommander;
@ -173,7 +175,8 @@ public class LSPatch {
System.out.println("Parsing original apk..."); 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 // sign apk
System.out.println("Register apk signer..."); System.out.println("Register apk signer...");
@ -193,19 +196,17 @@ public class LSPatch {
} catch (Exception e) { } catch (Exception e) {
throw new PatchError("Failed to register signer", e); throw new PatchError("Failed to register signer", e);
} }
String originalSignature = null;
if (sigbypassLevel > 0) { if (sigbypassLevel > 0) {
// save the apk original signature info, to support crack signature. // 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()) { if (originalSignature == null || originalSignature.isEmpty()) {
throw new PatchError("get original signature failed"); throw new PatchError("get original signature failed");
} }
if (verbose) if (verbose)
System.out.println("Original signature\n" + originalSignature); 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 // copy out manifest file from zlib
@ -213,14 +214,17 @@ public class LSPatch {
if (manifestEntry == null) if (manifestEntry == null)
throw new PatchError("Provided file is not a valid apk"); throw new PatchError("Provided file is not a valid apk");
// parse the app main application full name from the manifest file // parse the app appComponentFactory full name from the manifest file
ManifestParser.Pair pair = ManifestParser.parseManifestFile(manifestEntry.open()); String appComponentFactory;
try (var is = manifestEntry.open()) {
var pair =ManifestParser.parseManifestFile(is);
if (pair == null) if (pair == null)
throw new PatchError("Failed to parse AndroidManifest.xml"); throw new PatchError("Failed to parse AndroidManifest.xml");
String appComponentFactory = pair.appComponentFactory == null ? "" : pair.appComponentFactory; appComponentFactory = pair.appComponentFactory == null ? "" : pair.appComponentFactory;
if (verbose) if (verbose)
System.out.println("original appComponentFactory class: " + appComponentFactory); System.out.println("original appComponentFactory class: " + appComponentFactory);
}
System.out.println("Patching apk..."); System.out.println("Patching apk...");
// modify manifest // modify manifest
@ -230,18 +234,13 @@ public class LSPatch {
throw new PatchError("Error when modifying manifest", e); 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) if (verbose)
System.out.println("Adding native lib.."); System.out.println("Adding native lib..");
// copy so and dex files into the unzipped apk // copy so and dex files into the unzipped apk
// do not put liblspd.so into apk!lib because x86 native bridge causes crash // do not put liblspd.so into apk!lib because x86 native bridge causes crash
for (String arch : APK_LIB_PATH_ARRAY) { 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")) { 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 dstZFile.add(entryName, is, false); // no compress for so
} catch (Throwable e) { } catch (Throwable e) {
@ -258,21 +257,23 @@ public class LSPatch {
try (var is = getClass().getClassLoader().getResourceAsStream("assets/dex/loader.dex")) { try (var is = getClass().getClassLoader().getResourceAsStream("assets/dex/loader.dex")) {
dstZFile.add("classes.dex", is); dstZFile.add("classes.dex", is);
} catch (Throwable e) { } 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")) { try (var is = getClass().getClassLoader().getResourceAsStream("assets/dex/lsp.dex")) {
dstZFile.add("assets/lsp", is); dstZFile.add(DEX_ASSET_PATH, is);
} catch (Throwable e) { } catch (Throwable e) {
throw new PatchError("Error when add assets", e); throw new PatchError("Error when adding assets", e);
} }
// save lspatch config to asset.. // save lspatch config to asset..
try (var is = new ByteArrayInputStream("42".getBytes(StandardCharsets.UTF_8))) { var config = new PatchConfig(useManager, sigbypassLevel, originalSignature, appComponentFactory);
dstZFile.add("assets/" + Constants.CONFIG_NAME_SIGBYPASSLV + sigbypassLevel, is); var configBytes = new Gson().toJson(config).getBytes(StandardCharsets.UTF_8);
}
try (var is = new ByteArrayInputStream(Boolean.toString(useManager).getBytes(StandardCharsets.UTF_8))) { try (var is = new ByteArrayInputStream(configBytes)) {
dstZFile.add(USE_MANAGER_CONTROL_PATH, is); dstZFile.add(CONFIG_ASSET_PATH, is);
} catch (Throwable e) {
throw new PatchError("Error when saving config");
} }
Set<String> apkArchs = new HashSet<>(); Set<String> apkArchs = new HashSet<>();
@ -296,30 +297,12 @@ public class LSPatch {
return false; return false;
}); });
embedModules(dstZFile);
// create zip link // create zip link
if (verbose) if (verbose)
System.out.println("Creating nested apk link..."); 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()) { for (StoredEntry entry : srcZFile.entries()) {
String name = entry.getCentralDirectoryHeader().getName(); String name = entry.getCentralDirectoryHeader().getName();
if (name.startsWith("classes") && name.endsWith(".dex")) continue; 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 { private byte[] modifyManifestFile(InputStream is) throws IOException {
ModificationProperty property = new ModificationProperty(); ModificationProperty property = new ModificationProperty();
if (!modules.isEmpty())
property.addApplicationAttribute(new AttributeItem("extractNativeLibs", true));
if (overrideVersionCode) if (overrideVersionCode)
property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_CODE, 1)); property.addManifestAttribute(new AttributeItem(NodeValue.Manifest.VERSION_CODE, 1));
property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggableFlag)); property.addApplicationAttribute(new AttributeItem(NodeValue.Application.DEBUGGABLE, debuggableFlag));

View File

@ -1,12 +1,13 @@
package org.lsposed.lspatch.share; package org.lsposed.lspatch.share;
public class Constants { 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 PROXY_APP_COMPONENT_FACTORY = "org.lsposed.lspatch.appstub.LSPAppComponentFactoryStub";
final static public String MANAGER_PACKAGE_NAME = "org.lsposed.lspatch"; 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_DISABLE = 0;
final static public int SIGBYPASS_LV_PM = 1; final static public int SIGBYPASS_LV_PM = 1;
final static public int SIGBYPASS_LV_PM_OPENAT = 2; final static public int SIGBYPASS_LV_PM_OPENAT = 2;

View File

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