From 9fa0c56231e576acfa787edd7624b7566d69eaf2 Mon Sep 17 00:00:00 2001 From: LoveSy Date: Sun, 29 Aug 2021 13:54:20 +0800 Subject: [PATCH] Old zip magic --- .../tools/build/apkzlib/zip/ExtraField.java | 16 +- .../tools/build/apkzlib/zip/NestedZip.java | 14 +- .../tools/build/apkzlib/zip/StoredEntry.java | 26 ++- .../tools/build/apkzlib/zip/ZFile.java | 6 +- .../main/java/org/lsposed/patch/LSPatch.java | 57 +++---- .../org/lsposed/patch/util/NestedZipLink.java | 152 ------------------ 6 files changed, 66 insertions(+), 205 deletions(-) delete mode 100644 patch/src/main/java/org/lsposed/patch/util/NestedZipLink.java diff --git a/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/ExtraField.java b/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/ExtraField.java index 0dfca1e..99aaa58 100644 --- a/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/ExtraField.java +++ b/apkzlib/src/main/java/com/android/tools/build/apkzlib/zip/ExtraField.java @@ -404,7 +404,7 @@ public class ExtraField { @Override public int size() { - return linkingEntry.getLocalHeaderSize() + 4; + return linkingEntry.isDummyEntry() ? 0 : linkingEntry.getLocalHeaderSize() + 4; } public void setOffset(int dataOffset, long zipOffset) { @@ -417,11 +417,15 @@ public class ExtraField { if (dataOffset == 0 || zipOffset == 0) { throw new IOException("linking entry has 0 offset"); } - LittleEndianUtils.writeUnsigned2Le(out, LINKING_ENTRY_EXTRA_DATA_FIELD_HEADER_ID); - LittleEndianUtils.writeUnsigned2Le(out, linkingEntry.getLocalHeaderSize()); - var offset = out.position(); - linkingEntry.writeData(out, dataOffset - linkingEntry.getLocalHeaderSize() - offset); - linkingEntry.replaceSourceFromZip(offset + zipOffset); + if (!linkingEntry.isDummyEntry()) { + LittleEndianUtils.writeUnsigned2Le(out, LINKING_ENTRY_EXTRA_DATA_FIELD_HEADER_ID); + LittleEndianUtils.writeUnsigned2Le(out, linkingEntry.getLocalHeaderSize()); + var offset = out.position(); + linkingEntry.writeData(out, dataOffset - linkingEntry.getLocalHeaderSize() - offset); + linkingEntry.replaceSourceFromZip(offset + zipOffset); + } else { + linkingEntry.replaceSourceFromZip(zipOffset + dataOffset + linkingEntry.getNestedOffset()); + } } } } 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 index a622568..3e7bd28 100644 --- 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 @@ -18,18 +18,22 @@ public class NestedZip extends ZFile { this.links = Maps.newHashMap(); } + /** + * @return true if lfh is consistent with cdh otherwise inconsistent + */ 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); + if (srcName.equals(dstName)) { + target.addNestedLink(srcName, entry, srcEntry, srcEntry.getCentralDirectoryHeader().getOffset(), true); + return true; + } else if (offset < MAX_LOCAL_EXTRA_FIELD_CONTENTS_SIZE) { + target.addNestedLink(srcName, entry, srcEntry, offset, false); return true; - } else { - links.put(srcEntry, dstName); - return false; } + return false; } public Map getLinks() { 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 84b7fec..e8627cf 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 @@ -157,8 +157,11 @@ public class StoredEntry { /** Entry it is linking to. */ private final StoredEntry linkedEntry; - /** Offset of the nested link */ - private final int nestedOffset; + /** Offset of the nested link. */ + private final long nestedOffset; + + /** Dummy entry won't be written to file. */ + private final boolean dummy; /** * Creates a new stored entry. @@ -176,7 +179,7 @@ public class StoredEntry { @Nullable ProcessedAndRawByteSources source, ByteStorage storage) throws IOException { - this(header, file, source, storage, null, 0); + this(header, file, source, storage, null, 0, false); } StoredEntry( @@ -185,10 +188,11 @@ public class StoredEntry { ByteStorage storage, StoredEntry linkedEntry, StoredEntry nestedEntry, - int nestedOffset) + long nestedOffset, + boolean dummy) throws IOException { this((nestedEntry == null ? linkedEntry: nestedEntry).linkingCentralDirectoryHeader(name, file), - file, linkedEntry.getSource(), storage, linkedEntry, nestedOffset); + file, linkedEntry.getSource(), storage, linkedEntry, nestedOffset, dummy); } private CentralDirectoryHeader linkingCentralDirectoryHeader(String name, ZFile file) { @@ -203,7 +207,8 @@ public class StoredEntry { @Nullable ProcessedAndRawByteSources source, ByteStorage storage, StoredEntry linkedEntry, - int nestedOffset) + long nestedOffset, + boolean dummy) throws IOException { cdh = header; this.file = file; @@ -212,6 +217,7 @@ public class StoredEntry { this.storage = storage; this.linkedEntry = linkedEntry; this.nestedOffset = nestedOffset; + this.dummy = dummy; if (header.getOffset() >= 0) { readLocalHeader(); @@ -758,6 +764,14 @@ public class StoredEntry { return linkedEntry != null; } + public boolean isDummyEntry() { + return dummy; + } + + public long getNestedOffset() { + return nestedOffset; + } + /** * Obtains the contents of the local extra field. * 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 d980c6a..64b854b 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 @@ -1769,15 +1769,15 @@ public class ZFile implements Closeable { public void addLink(String name, StoredEntry linkedEntry) throws IOException { - addNestedLink(name, linkedEntry, null, 0); + addNestedLink(name, linkedEntry, null, 0L, false); } - void addNestedLink(String name, StoredEntry linkedEntry, StoredEntry nestedEntry, int nestedOffset) + void addNestedLink(String name, StoredEntry linkedEntry, StoredEntry nestedEntry, long nestedOffset, boolean dummy) 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, nestedEntry, nestedOffset); + var linkingEntry = new StoredEntry(name, this, storage, linkedEntry, nestedEntry, nestedOffset, dummy); 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); diff --git a/patch/src/main/java/org/lsposed/patch/LSPatch.java b/patch/src/main/java/org/lsposed/patch/LSPatch.java index c7af0d5..64a0333 100644 --- a/patch/src/main/java/org/lsposed/patch/LSPatch.java +++ b/patch/src/main/java/org/lsposed/patch/LSPatch.java @@ -4,7 +4,6 @@ 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 +19,6 @@ import org.apache.commons.io.FilenameUtils; 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 java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -115,7 +113,7 @@ public class LSPatch { try { lsPatch.doCommandLine(); } catch (PatchError e) { - System.err.println(e.getMessage()); + e.printStackTrace(System.err); } } @@ -160,6 +158,25 @@ public class LSPatch { System.out.println("Parsing original apk..."); try (ZFile dstZFile = ZFile.openReadWrite(tmpApk, Z_FILE_OPTIONS); var srcZFile = dstZFile.addNestedZip(ORIGINAL_APK_ASSET_PATH, srcApkFile, false)) { + + // sign apk + System.out.println("Register apk signer..."); + try { + var keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + try (var is = getClass().getClassLoader().getResourceAsStream("assets/keystore")) { + keyStore.load(is, "123456".toCharArray()); + } + var entry = (KeyStore.PrivateKeyEntry) keyStore.getEntry("key0", new KeyStore.PasswordProtection("123456".toCharArray())); + new SigningExtension(SigningOptions.builder() + .setMinSdkVersion(27) + .setV1SigningEnabled(v1) + .setV2SigningEnabled(v2) + .setCertificates((X509Certificate[]) entry.getCertificateChain()) + .setKey(entry.getPrivateKey()) + .build()).register(dstZFile); + } catch (Exception e) { + throw new PatchError("Failed to register signer", e); + } if (sigbypassLevel > 0) { // save the apk original signature info, to support crack signature. String originalSignature = ApkSignatureHelper.getApkSignInfo(srcApkFile.getAbsolutePath()); @@ -260,27 +277,6 @@ public class LSPatch { if (verbose) System.out.println("Creating nested apk link..."); - SigningExtension signingExtension = null; - // sign apk - System.out.println("Signing apk..."); - try { - var keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); - try (var is = getClass().getClassLoader().getResourceAsStream("assets/keystore")) { - keyStore.load(is, "123456".toCharArray()); - } - var entry = (KeyStore.PrivateKeyEntry) keyStore.getEntry("key0", new KeyStore.PasswordProtection("123456".toCharArray())); - signingExtension = new SigningExtension(SigningOptions.builder() - .setMinSdkVersion(27) - .setV1SigningEnabled(v1) - .setV2SigningEnabled(v2) - .setCertificates((X509Certificate[]) entry.getCertificateChain()) - .setKey(entry.getPrivateKey()) - .build()); - } catch (Exception e) { - throw new PatchError("Failed to create signer", e); - } - - NestedZipLink nestedZipLink = new NestedZipLink(dstZFile, signingExtension); for (StoredEntry entry : srcZFile.entries()) { String name = entry.getCentralDirectoryHeader().getName(); if (name.startsWith("classes") && name.endsWith(".dex")) continue; @@ -290,7 +286,6 @@ public class LSPatch { if (name.equals("META-INF/MANIFEST.MF")) continue; srcZFile.addFileLink(name, name); } - nestedZipLink.nestedZips.add(srcZFile); // for (var pair : embedded) { // ZFile moduleZFile = ZFile.openReadOnly(pair.getFirst()); @@ -304,20 +299,16 @@ public class LSPatch { // nestedZipLink.nestedZips.add(nestedZip); // } - try { - nestedZipLink.register(); - } catch (NoSuchAlgorithmException e) { - throw new PatchError("Failed to create link", e); - } - dstZFile.realign(); - System.out.println("Done. Output APK: " + outputFile.getAbsolutePath()); + System.out.println("Writing apk..."); } finally { try { outputFile.delete(); FileUtils.moveFile(tmpApk, outputFile); - } catch (Throwable ignored) { + System.out.println("Done. Output APK: " + outputFile.getAbsolutePath()); + } catch (Throwable e) { + throw new PatchError("Error writing apk", e); } } } diff --git a/patch/src/main/java/org/lsposed/patch/util/NestedZipLink.java b/patch/src/main/java/org/lsposed/patch/util/NestedZipLink.java deleted file mode 100644 index 7538c14..0000000 --- a/patch/src/main/java/org/lsposed/patch/util/NestedZipLink.java +++ /dev/null @@ -1,152 +0,0 @@ -package org.lsposed.patch.util; - -import com.android.apksig.ApkSignerEngine; -import com.android.apksig.internal.util.Pair; -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; - -import java.io.BufferedInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.security.NoSuchAlgorithmException; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; - -public class NestedZipLink extends ZFileExtension { - - public final ZFile zFile; - - public final Set nestedZips = new HashSet<>(); - - private boolean written; - - public final SigningExtension signingExtension; - - public NestedZipLink(ZFile zFile, SigningExtension signingExtension) { - this.zFile = zFile; - this.signingExtension = signingExtension; - } - - @Override - public IOExceptionRunnable beforeUpdate() { - return () -> { - written = false; - try { - - var signerField = SigningExtension.class.getDeclaredField("signer"); - signerField.setAccessible(true); - var signer = (ApkSignerEngine) signerField.get(signingExtension); - - for (var nestedZip : nestedZips) { - for (var link : nestedZip.getLinks().entrySet()) { - var entry = link.getKey(); - notifySigner(signer, link.getValue(), entry); - } - } - - } catch (Exception e) { - var ex = new IOException("Error when writing link entries"); - ex.addSuppressed(e); - throw ex; - } - }; - } - - @Override - public void entriesWritten() throws IOException { - if (written) return; - try { - Method deleteDirectoryAndEocd = ZFile.class.getDeclaredMethod("deleteDirectoryAndEocd"); - deleteDirectoryAndEocd.setAccessible(true); - deleteDirectoryAndEocd.invoke(zFile); - appendEntries(); - } catch (Exception e) { - var ex = new IOException("Error when writing link entries"); - ex.addSuppressed(e); - throw ex; - } - written = true; - } - - private void appendEntries() throws Exception { - Field field_entry_file = StoredEntry.class.getDeclaredField("file"); - field_entry_file.setAccessible(true); - - Field field_entries = ZFile.class.getDeclaredField("entries"); - field_entries.setAccessible(true); - - Field field_cdh_file = CentralDirectoryHeader.class.getDeclaredField("file"); - field_cdh_file.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); - makeFree.setAccessible(true); - - Method computeCentralDirectory = ZFile.class.getDeclaredMethod("computeCentralDirectory"); - computeCentralDirectory.setAccessible(true); - - Method computeEocd = ZFile.class.getDeclaredMethod("computeEocd"); - computeEocd.setAccessible(true); - - var entries = (Map) field_entries.get(zFile); - for (var nestedZip : nestedZips) { - 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.getValue())); - field_cdh_offset.set(cdh, nestedZipOffset + cdh.getOffset() + nestedZip.getEntry().getLocalHeaderSize()); - var newFileUseMapEntry = makeFree.invoke(null, 0, 1, entry); - entries.put(link.getValue(), newFileUseMapEntry); - } - } - computeCentralDirectory.invoke(zFile); - computeEocd.invoke(zFile); - } - - private void notifySigner(ApkSignerEngine signer, String entryName, StoredEntry entry) throws IOException { - ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest = signer.outputJarEntry(entryName); - if (inspectEntryRequest != null) { - try (InputStream inputStream = new BufferedInputStream(entry.open())) { - int bytesRead; - byte[] buffer = new byte[65536]; - var dataSink = inspectEntryRequest.getDataSink(); - while ((bytesRead = inputStream.read(buffer)) > 0) { - dataSink.consume(buffer, 0, bytesRead); - } - } - inspectEntryRequest.done(); - } - } - - 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); - } - - public void register() throws NoSuchAlgorithmException, IOException { - zFile.addZFileExtension(this); - signingExtension.register(zFile); - } -}