From 0c51939e6d50b6e7430d7c0a33a9fbfee06bf16e Mon Sep 17 00:00:00 2001 From: LoveSy Date: Sun, 29 Aug 2021 04:04:12 +0800 Subject: [PATCH] [2/2] New zip magic --- .../apkzlib/zip/CentralDirectoryHeader.java | 4 +- .../tools/build/apkzlib/zip/NestedZip.java | 46 +++++++++++++++++++ .../tools/build/apkzlib/zip/StoredEntry.java | 24 ++++++---- .../tools/build/apkzlib/zip/ZFile.java | 21 +++++---- .../main/java/org/lsposed/patch/LSPatch.java | 39 +++++++--------- .../org/lsposed/patch/util/NestedZipLink.java | 38 ++++----------- 6 files changed, 102 insertions(+), 70 deletions(-) create mode 100644 apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/NestedZip.java diff --git a/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/CentralDirectoryHeader.java b/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/CentralDirectoryHeader.java index 80826db..6dabda8 100644 --- a/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/CentralDirectoryHeader.java +++ b/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/CentralDirectoryHeader.java @@ -152,7 +152,7 @@ public class CentralDirectoryHeader implements Cloneable { file = zFile; } - public CentralDirectoryHeader link(String name, byte[] encodedFileName, GPFlags flags) { + public CentralDirectoryHeader link(String name, byte[] encodedFileName, GPFlags flags, ZFile file) { var newData = new CentralDirectoryHeader(name, encodedFileName, uncompressedSize, @@ -162,7 +162,7 @@ public class CentralDirectoryHeader implements Cloneable { lastModTime, lastModDate); newData.extraField = extraField; - newData.offset = offset; + newData.offset = -1; newData.internalAttributes = internalAttributes; newData.externalAttributes = externalAttributes; newData.comment = comment; diff --git a/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/NestedZip.java b/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/NestedZip.java new file mode 100644 index 0000000..a622568 --- /dev/null +++ b/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/NestedZip.java @@ -0,0 +1,46 @@ +package com.android.tools.build.apkzlib.zip; + +import com.google.common.collect.Maps; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +public class NestedZip extends ZFile { + final Map links; + final ZFile target; + final StoredEntry entry; + + public NestedZip(String name, ZFile target, File src, boolean mayCompress) throws IOException { + super(src, new ZFileOptions(), true); + this.target = target; + this.entry = target.add(name, directOpen(0, directSize()), mayCompress); + this.links = Maps.newHashMap(); + } + + public boolean addFileLink(String srcName, String dstName) throws IOException { + var srcEntry = get(srcName); + if (entry == null) + throw new IOException("Entry " + srcEntry + " does not exist in nested zip"); + var offset = srcEntry.getCentralDirectoryHeader().getOffset() + srcEntry.getLocalHeaderSize(); + if (offset < MAX_LOCAL_EXTRA_FIELD_CONTENTS_SIZE) { + target.addNestedLink(srcName, entry, srcEntry, (int) offset); + return true; + } else { + links.put(srcEntry, dstName); + return false; + } + } + + public Map getLinks() { + return links; + } + + public ZFile getTarget() { + return target; + } + + public StoredEntry getEntry() { + return entry; + } +} diff --git a/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/StoredEntry.java b/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/StoredEntry.java index 77167d2..84b7fec 100644 --- a/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/StoredEntry.java +++ b/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/StoredEntry.java @@ -25,7 +25,6 @@ import com.google.common.base.Preconditions; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.base.Verify; -import com.google.common.collect.ImmutableList; import com.google.common.io.ByteStreams; import com.google.common.primitives.Ints; import java.io.BufferedInputStream; @@ -159,7 +158,7 @@ public class StoredEntry { private final StoredEntry linkedEntry; /** Offset of the nested link */ - private final int nestedLinkOffset = 0; + private final int nestedOffset; /** * Creates a new stored entry. @@ -177,30 +176,34 @@ public class StoredEntry { @Nullable ProcessedAndRawByteSources source, ByteStorage storage) throws IOException { - this(header, file, source, storage, null); + this(header, file, source, storage, null, 0); } StoredEntry( String name, ZFile file, ByteStorage storage, - StoredEntry linkedEntry) + StoredEntry linkedEntry, + StoredEntry nestedEntry, + int nestedOffset) throws IOException { - this(linkedEntry.linkingCentralDirectoryHeader(name), file, linkedEntry.getSource(), storage, linkedEntry); + this((nestedEntry == null ? linkedEntry: nestedEntry).linkingCentralDirectoryHeader(name, file), + file, linkedEntry.getSource(), storage, linkedEntry, nestedOffset); } - private CentralDirectoryHeader linkingCentralDirectoryHeader(String name) { + private CentralDirectoryHeader linkingCentralDirectoryHeader(String name, ZFile file) { boolean encodeWithUtf8 = !EncodeUtils.canAsciiEncode(name); GPFlags flags = GPFlags.make(encodeWithUtf8); - return cdh.link(name, EncodeUtils.encode(name, flags), flags); + return cdh.link(name, EncodeUtils.encode(name, flags), flags, file); } - StoredEntry( + private StoredEntry( CentralDirectoryHeader header, ZFile file, @Nullable ProcessedAndRawByteSources source, ByteStorage storage, - StoredEntry linkedEntry) + StoredEntry linkedEntry, + int nestedOffset) throws IOException { cdh = header; this.file = file; @@ -208,6 +211,7 @@ public class StoredEntry { verifyLog = file.makeVerifyLog(); this.storage = storage; this.linkedEntry = linkedEntry; + this.nestedOffset = nestedOffset; if (header.getOffset() >= 0) { readLocalHeader(); @@ -725,7 +729,7 @@ public class StoredEntry { F_COMPRESSED_SIZE.write(out, compressInfo.getCompressedSize()); F_UNCOMPRESSED_SIZE.write(out, cdh.getUncompressedSize()); F_FILE_NAME_LENGTH.write(out, cdh.getEncodedFileName().length); - F_EXTRA_LENGTH.write(out, localExtra.size() + extraOffset); + F_EXTRA_LENGTH.write(out, localExtra.size() + extraOffset + nestedOffset); out.put(cdh.getEncodedFileName()); localExtra.write(out); diff --git a/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/ZFile.java b/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/ZFile.java index bcc23fc..d980c6a 100644 --- a/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/ZFile.java +++ b/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/ZFile.java @@ -223,7 +223,7 @@ public class ZFile implements Closeable { * Minimum size for the extra field when we have to add one. We rely on the alignment segment to * do that so the minimum size for the extra field is the minimum size of an alignment segment. */ - private static final int MINIMUM_EXTRA_FIELD_SIZE = ExtraField.AlignmentSegment.MINIMUM_SIZE; + protected static final int MINIMUM_EXTRA_FIELD_SIZE = ExtraField.AlignmentSegment.MINIMUM_SIZE; /** * Maximum size of the extra field. @@ -231,10 +231,10 @@ public class ZFile implements Closeable { *

Theoretically, this is (1 << 16) - 1 = 65535 and not (1 < 15) -1 = 32767. However, due to * http://b.android.com/221703, we need to keep this limited. */ - private static final int MAX_LOCAL_EXTRA_FIELD_CONTENTS_SIZE = (1 << 15) - 1; + protected static final int MAX_LOCAL_EXTRA_FIELD_CONTENTS_SIZE = (1 << 15) - 1; /** File zip file. */ - private final File file; + protected final File file; /** * The random access file used to access the zip file. This will be {@code null} if and only if @@ -1768,18 +1768,23 @@ public class ZFile implements Closeable { } public void addLink(String name, StoredEntry linkedEntry) - throws CloneNotSupportedException,IOException { + throws IOException { + addNestedLink(name, linkedEntry, null, 0); + } + + void addNestedLink(String name, StoredEntry linkedEntry, StoredEntry nestedEntry, int nestedOffset) + throws IOException { Preconditions.checkArgument(linkedEntry != null, "linkedEntry is null"); Preconditions.checkArgument(linkedEntry.getCentralDirectoryHeader().getOffset() < 0, "linkedEntry is not new file"); Preconditions.checkArgument(!linkedEntry.isLinkingEntry(), "linkedEntry is a linking entry"); - var linkingEntry = new StoredEntry(name, this, storage, linkedEntry); + var linkingEntry = new StoredEntry(name, this, storage, linkedEntry, nestedEntry, nestedOffset); linkingEntries.add(linkingEntry); linkedEntry.setLocalExtraNoNotify(new ExtraField(ImmutableList.builder().add(linkedEntry.getLocalExtra().getSegments().toArray(new ExtraField.Segment[0])).add(new ExtraField.LinkingEntrySegment(linkingEntry)).build())); + reAdd(linkedEntry, PositionHint.LOWEST_OFFSET); } - public void addNestedZipWithLinks(Map nameMapping) - throws CloneNotSupportedException,IOException { - // TODO: nested link + public NestedZip addNestedZip(String name, File src, boolean mayCompress) throws IOException { + return new NestedZip(name, this, src, mayCompress); } diff --git a/patch/src/main/java/org/lsposed/patch/LSPatch.java b/patch/src/main/java/org/lsposed/patch/LSPatch.java index 23d4c2e..c7af0d5 100644 --- a/patch/src/main/java/org/lsposed/patch/LSPatch.java +++ b/patch/src/main/java/org/lsposed/patch/LSPatch.java @@ -4,6 +4,7 @@ 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; +import com.android.tools.build.apkzlib.zip.NestedZip; import com.android.tools.build.apkzlib.zip.StoredEntry; import com.android.tools.build.apkzlib.zip.ZFile; import com.android.tools.build.apkzlib.zip.ZFileOptions; @@ -20,7 +21,6 @@ import org.lsposed.lspatch.share.Constants; import org.lsposed.patch.util.ApkSignatureHelper; import org.lsposed.patch.util.ManifestParser; import org.lsposed.patch.util.NestedZipLink; -import org.lsposed.patch.util.NestedZipLink.NestedZip; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -159,10 +159,7 @@ public class LSPatch { System.out.println("Parsing original apk..."); - try (ZFile srcZFile = ZFile.openReadOnly(srcApkFile); ZFile dstZFile = ZFile.openReadWrite(tmpApk, Z_FILE_OPTIONS)) { - // copy origin apk to assets - dstZFile.add(ORIGINAL_APK_ASSET_PATH, new FileInputStream(srcApkFile), false); - + try (ZFile dstZFile = ZFile.openReadWrite(tmpApk, Z_FILE_OPTIONS); var srcZFile = dstZFile.addNestedZip(ORIGINAL_APK_ASSET_PATH, srcApkFile, false)) { if (sigbypassLevel > 0) { // save the apk original signature info, to support crack signature. String originalSignature = ApkSignatureHelper.getApkSignInfo(srcApkFile.getAbsolutePath()); @@ -259,8 +256,6 @@ public class LSPatch { var embedded = embedModules(dstZFile); - dstZFile.realign(); - // create zip link if (verbose) System.out.println("Creating nested apk link..."); @@ -286,8 +281,6 @@ public class LSPatch { } NestedZipLink nestedZipLink = new NestedZipLink(dstZFile, signingExtension); - StoredEntry originalZipEntry = dstZFile.get(ORIGINAL_APK_ASSET_PATH); - NestedZip nestedZip = new NestedZip(srcZFile, originalZipEntry); for (StoredEntry entry : srcZFile.entries()) { String name = entry.getCentralDirectoryHeader().getName(); if (name.startsWith("classes") && name.endsWith(".dex")) continue; @@ -295,21 +288,21 @@ 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, name); + srcZFile.addFileLink(name, name); } - nestedZipLink.nestedZips.add(nestedZip); + nestedZipLink.nestedZips.add(srcZFile); - 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); - } +// for (var pair : embedded) { +// ZFile moduleZFile = ZFile.openReadOnly(pair.getFirst()); +// StoredEntry moduleEntry = dstZFile.get("assets/modules/" + pair.getSecond() + ".bin"); +// nestedZip = new NestedZip(dstZFile, 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); +// } try { nestedZipLink.register(); @@ -317,6 +310,8 @@ public class LSPatch { throw new PatchError("Failed to create link", e); } + dstZFile.realign(); + System.out.println("Done. Output APK: " + outputFile.getAbsolutePath()); } finally { try { 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 82cf572..7538c14 100644 --- a/patch/src/main/java/org/lsposed/patch/util/NestedZipLink.java +++ b/patch/src/main/java/org/lsposed/patch/util/NestedZipLink.java @@ -6,6 +6,7 @@ import com.android.tools.build.apkzlib.sign.SigningExtension; 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.NestedZip; import com.android.tools.build.apkzlib.zip.StoredEntry; import com.android.tools.build.apkzlib.zip.ZFile; import com.android.tools.build.apkzlib.zip.ZFileExtension; @@ -21,21 +22,6 @@ import java.util.Map; import java.util.Set; public class NestedZipLink extends ZFileExtension { - public static class NestedZip { - final Set> links; - final ZFile zip; - final StoredEntry entry; - - public NestedZip(ZFile zip, StoredEntry entry) { - this.zip = zip; - this.entry = entry; - this.links = new HashSet<>(); - } - - public void addFileLink(String srcName, String dstName) { - links.add(Pair.of(srcName, dstName)); - } - } public final ZFile zFile; @@ -61,11 +47,9 @@ public class NestedZipLink extends ZFileExtension { var signer = (ApkSignerEngine) signerField.get(signingExtension); for (var nestedZip : nestedZips) { - for (var link : nestedZip.links) { - var entry = nestedZip.zip.get(link.getFirst()); - if (entry == null) - throw new IOException("Entry " + link + " does not exist in nested zip"); - notifySigner(signer, link.getFirst(), entry); + for (var link : nestedZip.getLinks().entrySet()) { + var entry = link.getKey(); + notifySigner(signer, link.getValue(), entry); } } @@ -119,18 +103,16 @@ public class NestedZipLink extends ZFileExtension { var entries = (Map) field_entries.get(zFile); for (var nestedZip : nestedZips) { - long nestedZipOffset = nestedZip.entry.getCentralDirectoryHeader().getOffset(); - for (var link : nestedZip.links) { - var entry = nestedZip.zip.get(link.getFirst()); - if (entry == null) - throw new IOException("Entry " + link + " does not exist in nested zip"); + long nestedZipOffset = nestedZip.getEntry().getCentralDirectoryHeader().getOffset(); + for (var link : nestedZip.getLinks().entrySet()) { + var entry = link.getKey(); CentralDirectoryHeader cdh = entry.getCentralDirectoryHeader(); field_entry_file.set(entry, zFile); field_cdh_file.set(cdh, zFile); - field_cdh_encodedFileName.set(cdh, encodeFileName(link.getSecond())); - field_cdh_offset.set(cdh, nestedZipOffset + cdh.getOffset() + nestedZip.entry.getLocalHeaderSize()); + field_cdh_encodedFileName.set(cdh, encodeFileName(link.getValue())); + field_cdh_offset.set(cdh, nestedZipOffset + cdh.getOffset() + nestedZip.getEntry().getLocalHeaderSize()); var newFileUseMapEntry = makeFree.invoke(null, 0, 1, entry); - entries.put(link.getSecond(), newFileUseMapEntry); + entries.put(link.getValue(), newFileUseMapEntry); } } computeCentralDirectory.invoke(zFile);