diff --git a/app/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java b/app/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java index f0baa0b..27e837c 100644 --- a/app/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java +++ b/app/src/main/java/org/lsposed/lspatch/loader/LSPApplication.java @@ -191,14 +191,15 @@ public class LSPApplication extends ApplicationServiceClient { HashSet disabled_modules = new HashSet<>(); try { for (var name : context.getAssets().list("modules")) { - String modulePath = context.getCacheDir() + "/lspatch/" + name + "/"; + String packageName = name.substring(0, name.length() - 4); + String modulePath = context.getCacheDir() + "/lspatch/" + packageName + "/"; String cacheApkPath; try (ZipFile sourceFile = new ZipFile(context.getApplicationInfo().sourceDir)) { cacheApkPath = modulePath + sourceFile.getEntry("assets/modules/" + name).getCrc(); } if (!Files.exists(Paths.get(cacheApkPath))) { - Log.i(TAG, "extract module apk: " + name); + 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)) { @@ -206,10 +207,10 @@ public class LSPApplication extends ApplicationServiceClient { } } - embedded_modules.add(name); + embedded_modules.add(packageName); var module = new Module(); module.apkPath = cacheApkPath; - module.packageName = name; + module.packageName = packageName; LSPApplication.modules.add(module); } } catch (Throwable ignored) { diff --git a/appstub/src/main/java/org/lsposed/lspatch/appstub/LSPApplicationStub.java b/appstub/src/main/java/org/lsposed/lspatch/appstub/LSPApplicationStub.java index 00fb8f0..e36b806 100644 --- a/appstub/src/main/java/org/lsposed/lspatch/appstub/LSPApplicationStub.java +++ b/appstub/src/main/java/org/lsposed/lspatch/appstub/LSPApplicationStub.java @@ -34,7 +34,7 @@ public class LSPApplicationStub extends Application { vmInstructionSet.setAccessible(true); String arch = (String) vmInstructionSet.invoke(getRuntime.invoke(null)); - String path = LSPApplicationStub.class.getClassLoader().getResource("assets/lib/" + arch + "/liblspd.so").getPath().substring(5); + 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); diff --git a/patch/src/main/java/org/lsposed/patch/LSPatch.java b/patch/src/main/java/org/lsposed/patch/LSPatch.java index 72c48e6..f10f0a5 100644 --- a/patch/src/main/java/org/lsposed/patch/LSPatch.java +++ b/patch/src/main/java/org/lsposed/patch/LSPatch.java @@ -1,5 +1,6 @@ package org.lsposed.patch; +import com.android.apksig.internal.util.Pair; import com.android.tools.build.apkzlib.sign.SigningExtension; import com.android.tools.build.apkzlib.sign.SigningOptions; import com.android.tools.build.apkzlib.zip.AlignmentRules; @@ -99,7 +100,7 @@ public class LSPatch { private static final ZFileOptions Z_FILE_OPTIONS = new ZFileOptions().setAlignmentRule(AlignmentRules.compose( AlignmentRules.constantForSuffix(".so", 4096), - AlignmentRules.constantForSuffix(ORIGINAL_APK_ASSET_PATH, 4096) + AlignmentRules.constantForSuffix(".bin", 4096) )); private static JCommander jCommander; @@ -223,7 +224,7 @@ public class LSPatch { // 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/" + arch + "/liblspd.so"; + String entryName = "assets/lib/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) { @@ -256,7 +257,7 @@ public class LSPatch { throw new PatchError("Error when saving signature bypass level: " + e); } - embedModules(dstZFile); + var embedded = embedModules(dstZFile); dstZFile.realign(); @@ -274,9 +275,22 @@ public class LSPatch { if (name.equals("AndroidManifest.xml")) continue; if (name.startsWith("META-INF/CERT")) continue; if (name.equals("META-INF/MANIFEST.MF")) continue; - nestedZip.addFileLink(name); + nestedZip.addFileLink(name, name); } nestedZipLink.nestedZips.add(nestedZip); + + for (var pair : embedded) { + ZFile moduleZFile = ZFile.openReadOnly(pair.getFirst()); + StoredEntry moduleEntry = dstZFile.get("assets/modules/" + pair.getSecond() + ".bin"); + nestedZip = new NestedZip(moduleZFile, moduleEntry); + for (var nestedEntry : moduleZFile.entries()) { + var name = nestedEntry.getCentralDirectoryHeader().getName(); + if (name.startsWith("lib/")) + nestedZip.addFileLink(name, "assets/lib/" + pair.getSecond() + name.substring(3)); + } + nestedZipLink.nestedZips.add(nestedZip); + } + dstZFile.addZFileExtension(nestedZipLink); // sign apk @@ -308,8 +322,9 @@ public class LSPatch { } } - private void embedModules(ZFile zFile) { + private List> embedModules(ZFile zFile) { System.out.println("Embedding modules..."); + List> list = new ArrayList<>(); for (var module : modules) { var file = new File(module); if (!file.exists()) { @@ -339,13 +354,15 @@ public class LSPatch { } if (triple != null) { try (var is = new FileInputStream(file)) { - zFile.add("assets/modules/" + triple.packageName, is); + var entryName = "assets/modules/" + triple.packageName + ".bin"; + zFile.add(entryName, is, false); + list.add(Pair.of(file, triple.packageName)); } catch (Throwable e) { System.err.println("Embed " + triple.packageName + " with error: " + e.getMessage()); } } } - + return list; } private byte[] modifyManifestFile(InputStream is) throws IOException { diff --git a/patch/src/main/java/org/lsposed/patch/util/NestedZipLink.java b/patch/src/main/java/org/lsposed/patch/util/NestedZipLink.java index f7ad57f..278db88 100644 --- a/patch/src/main/java/org/lsposed/patch/util/NestedZipLink.java +++ b/patch/src/main/java/org/lsposed/patch/util/NestedZipLink.java @@ -1,7 +1,9 @@ package org.lsposed.patch.util; +import com.android.apksig.internal.util.Pair; import com.android.tools.build.apkzlib.utils.IOExceptionRunnable; import com.android.tools.build.apkzlib.zip.CentralDirectoryHeader; +import com.android.tools.build.apkzlib.zip.EncodeUtils; import com.android.tools.build.apkzlib.zip.StoredEntry; import com.android.tools.build.apkzlib.zip.ZFile; import com.android.tools.build.apkzlib.zip.ZFileExtension; @@ -15,7 +17,7 @@ import java.util.Set; public class NestedZipLink extends ZFileExtension { public static class NestedZip { - final Set links; + final Set> links; final ZFile zip; final StoredEntry entry; @@ -25,8 +27,8 @@ public class NestedZipLink extends ZFileExtension { this.links = new HashSet<>(); } - public void addFileLink(String name) { - links.add(name); + public void addFileLink(String srcName, String dstName) { + links.add(Pair.of(srcName, dstName)); } } @@ -71,9 +73,10 @@ public class NestedZipLink extends ZFileExtension { Field field_cdh_file = CentralDirectoryHeader.class.getDeclaredField("file"); field_cdh_file.setAccessible(true); - - Field field_offset = CentralDirectoryHeader.class.getDeclaredField("offset"); - field_offset.setAccessible(true); + Field field_cdh_encodedFileName = CentralDirectoryHeader.class.getDeclaredField("encodedFileName"); + field_cdh_encodedFileName.setAccessible(true); + Field field_cdh_offset = CentralDirectoryHeader.class.getDeclaredField("offset"); + field_cdh_offset.setAccessible(true); Method makeFree = Class.forName("com.android.tools.build.apkzlib.zip.FileUseMapEntry") .getDeclaredMethod("makeUsed", long.class, long.class, Object.class); @@ -89,17 +92,29 @@ public class NestedZipLink extends ZFileExtension { for (var nestedZip : nestedZips) { long nestedZipOffset = nestedZip.entry.getCentralDirectoryHeader().getOffset(); for (var link : nestedZip.links) { - var entry = nestedZip.zip.get(link); + var entry = nestedZip.zip.get(link.getFirst()); if (entry == null) throw new IOException("Entry " + link + " does not exist in nested zip"); CentralDirectoryHeader cdh = entry.getCentralDirectoryHeader(); field_entry_file.set(entry, zFile); field_cdh_file.set(cdh, zFile); - field_offset.set(cdh, nestedZipOffset + cdh.getOffset() + nestedZip.entry.getLocalHeaderSize()); + field_cdh_encodedFileName.set(cdh, encodeFileName(link.getSecond())); + field_cdh_offset.set(cdh, nestedZipOffset + cdh.getOffset() + nestedZip.entry.getLocalHeaderSize()); var newFileUseMapEntry = makeFree.invoke(null, 0, 1, entry); - entries.put(link, newFileUseMapEntry); + entries.put(link.getSecond(), newFileUseMapEntry); } } computeCentralDirectory.invoke(zFile); computeEocd.invoke(zFile); } + + private byte[] encodeFileName(String name) throws Exception { + Class GPFlags = Class.forName("com.android.tools.build.apkzlib.zip.GPFlags"); + Method make = GPFlags.getDeclaredMethod("make", boolean.class); + make.setAccessible(true); + Method encode = EncodeUtils.class.getDeclaredMethod("encode", String.class, GPFlags); + + boolean encodeWithUtf8 = !EncodeUtils.canAsciiEncode(name); + var flags = make.invoke(null, encodeWithUtf8); + return (byte[]) encode.invoke(null, name, flags); + } } \ No newline at end of file