Old zip magic

This commit is contained in:
LoveSy 2021-08-29 13:54:20 +08:00
parent 0c51939e6d
commit 9fa0c56231
6 changed files with 66 additions and 205 deletions

View File

@ -404,7 +404,7 @@ public class ExtraField {
@Override @Override
public int size() { public int size() {
return linkingEntry.getLocalHeaderSize() + 4; return linkingEntry.isDummyEntry() ? 0 : linkingEntry.getLocalHeaderSize() + 4;
} }
public void setOffset(int dataOffset, long zipOffset) { public void setOffset(int dataOffset, long zipOffset) {
@ -417,11 +417,15 @@ public class ExtraField {
if (dataOffset == 0 || zipOffset == 0) { if (dataOffset == 0 || zipOffset == 0) {
throw new IOException("linking entry has 0 offset"); throw new IOException("linking entry has 0 offset");
} }
LittleEndianUtils.writeUnsigned2Le(out, LINKING_ENTRY_EXTRA_DATA_FIELD_HEADER_ID); if (!linkingEntry.isDummyEntry()) {
LittleEndianUtils.writeUnsigned2Le(out, linkingEntry.getLocalHeaderSize()); LittleEndianUtils.writeUnsigned2Le(out, LINKING_ENTRY_EXTRA_DATA_FIELD_HEADER_ID);
var offset = out.position(); LittleEndianUtils.writeUnsigned2Le(out, linkingEntry.getLocalHeaderSize());
linkingEntry.writeData(out, dataOffset - linkingEntry.getLocalHeaderSize() - offset); var offset = out.position();
linkingEntry.replaceSourceFromZip(offset + zipOffset); linkingEntry.writeData(out, dataOffset - linkingEntry.getLocalHeaderSize() - offset);
linkingEntry.replaceSourceFromZip(offset + zipOffset);
} else {
linkingEntry.replaceSourceFromZip(zipOffset + dataOffset + linkingEntry.getNestedOffset());
}
} }
} }
} }

View File

@ -18,18 +18,22 @@ public class NestedZip extends ZFile {
this.links = Maps.newHashMap(); this.links = Maps.newHashMap();
} }
/**
* @return true if lfh is consistent with cdh otherwise inconsistent
*/
public boolean addFileLink(String srcName, String dstName) throws IOException { public boolean addFileLink(String srcName, String dstName) throws IOException {
var srcEntry = get(srcName); var srcEntry = get(srcName);
if (entry == null) if (entry == null)
throw new IOException("Entry " + srcEntry + " does not exist in nested zip"); throw new IOException("Entry " + srcEntry + " does not exist in nested zip");
var offset = srcEntry.getCentralDirectoryHeader().getOffset() + srcEntry.getLocalHeaderSize(); var offset = srcEntry.getCentralDirectoryHeader().getOffset() + srcEntry.getLocalHeaderSize();
if (offset < MAX_LOCAL_EXTRA_FIELD_CONTENTS_SIZE) { if (srcName.equals(dstName)) {
target.addNestedLink(srcName, entry, srcEntry, (int) offset); 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; return true;
} else {
links.put(srcEntry, dstName);
return false;
} }
return false;
} }
public Map<StoredEntry, String> getLinks() { public Map<StoredEntry, String> getLinks() {

View File

@ -157,8 +157,11 @@ public class StoredEntry {
/** Entry it is linking to. */ /** Entry it is linking to. */
private final StoredEntry linkedEntry; private final StoredEntry linkedEntry;
/** Offset of the nested link */ /** Offset of the nested link. */
private final int nestedOffset; private final long nestedOffset;
/** Dummy entry won't be written to file. */
private final boolean dummy;
/** /**
* Creates a new stored entry. * Creates a new stored entry.
@ -176,7 +179,7 @@ public class StoredEntry {
@Nullable ProcessedAndRawByteSources source, @Nullable ProcessedAndRawByteSources source,
ByteStorage storage) ByteStorage storage)
throws IOException { throws IOException {
this(header, file, source, storage, null, 0); this(header, file, source, storage, null, 0, false);
} }
StoredEntry( StoredEntry(
@ -185,10 +188,11 @@ public class StoredEntry {
ByteStorage storage, ByteStorage storage,
StoredEntry linkedEntry, StoredEntry linkedEntry,
StoredEntry nestedEntry, StoredEntry nestedEntry,
int nestedOffset) long nestedOffset,
boolean dummy)
throws IOException { throws IOException {
this((nestedEntry == null ? linkedEntry: nestedEntry).linkingCentralDirectoryHeader(name, file), 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) { private CentralDirectoryHeader linkingCentralDirectoryHeader(String name, ZFile file) {
@ -203,7 +207,8 @@ public class StoredEntry {
@Nullable ProcessedAndRawByteSources source, @Nullable ProcessedAndRawByteSources source,
ByteStorage storage, ByteStorage storage,
StoredEntry linkedEntry, StoredEntry linkedEntry,
int nestedOffset) long nestedOffset,
boolean dummy)
throws IOException { throws IOException {
cdh = header; cdh = header;
this.file = file; this.file = file;
@ -212,6 +217,7 @@ public class StoredEntry {
this.storage = storage; this.storage = storage;
this.linkedEntry = linkedEntry; this.linkedEntry = linkedEntry;
this.nestedOffset = nestedOffset; this.nestedOffset = nestedOffset;
this.dummy = dummy;
if (header.getOffset() >= 0) { if (header.getOffset() >= 0) {
readLocalHeader(); readLocalHeader();
@ -758,6 +764,14 @@ public class StoredEntry {
return linkedEntry != null; return linkedEntry != null;
} }
public boolean isDummyEntry() {
return dummy;
}
public long getNestedOffset() {
return nestedOffset;
}
/** /**
* Obtains the contents of the local extra field. * Obtains the contents of the local extra field.
* *

View File

@ -1769,15 +1769,15 @@ public class ZFile implements Closeable {
public void addLink(String name, StoredEntry linkedEntry) public void addLink(String name, StoredEntry linkedEntry)
throws IOException { 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 { throws IOException {
Preconditions.checkArgument(linkedEntry != null, "linkedEntry is null"); Preconditions.checkArgument(linkedEntry != null, "linkedEntry is null");
Preconditions.checkArgument(linkedEntry.getCentralDirectoryHeader().getOffset() < 0, "linkedEntry is not new file"); Preconditions.checkArgument(linkedEntry.getCentralDirectoryHeader().getOffset() < 0, "linkedEntry is not new file");
Preconditions.checkArgument(!linkedEntry.isLinkingEntry(), "linkedEntry is a linking entry"); 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); linkingEntries.add(linkingEntry);
linkedEntry.setLocalExtraNoNotify(new ExtraField(ImmutableList.<ExtraField.Segment>builder().add(linkedEntry.getLocalExtra().getSegments().toArray(new ExtraField.Segment[0])).add(new ExtraField.LinkingEntrySegment(linkingEntry)).build())); linkedEntry.setLocalExtraNoNotify(new ExtraField(ImmutableList.<ExtraField.Segment>builder().add(linkedEntry.getLocalExtra().getSegments().toArray(new ExtraField.Segment[0])).add(new ExtraField.LinkingEntrySegment(linkingEntry)).build()));
reAdd(linkedEntry, PositionHint.LOWEST_OFFSET); reAdd(linkedEntry, PositionHint.LOWEST_OFFSET);

View File

@ -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.SigningExtension;
import com.android.tools.build.apkzlib.sign.SigningOptions; import com.android.tools.build.apkzlib.sign.SigningOptions;
import com.android.tools.build.apkzlib.zip.AlignmentRules; 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.StoredEntry;
import com.android.tools.build.apkzlib.zip.ZFile; import com.android.tools.build.apkzlib.zip.ZFile;
import com.android.tools.build.apkzlib.zip.ZFileOptions; 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.lspatch.share.Constants;
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 org.lsposed.patch.util.NestedZipLink;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@ -115,7 +113,7 @@ public class LSPatch {
try { try {
lsPatch.doCommandLine(); lsPatch.doCommandLine();
} catch (PatchError e) { } 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..."); 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)) { 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) { 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()); String originalSignature = ApkSignatureHelper.getApkSignInfo(srcApkFile.getAbsolutePath());
@ -260,27 +277,6 @@ public class LSPatch {
if (verbose) if (verbose)
System.out.println("Creating nested apk link..."); 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()) { 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;
@ -290,7 +286,6 @@ public class LSPatch {
if (name.equals("META-INF/MANIFEST.MF")) continue; if (name.equals("META-INF/MANIFEST.MF")) continue;
srcZFile.addFileLink(name, name); srcZFile.addFileLink(name, name);
} }
nestedZipLink.nestedZips.add(srcZFile);
// for (var pair : embedded) { // for (var pair : embedded) {
// ZFile moduleZFile = ZFile.openReadOnly(pair.getFirst()); // ZFile moduleZFile = ZFile.openReadOnly(pair.getFirst());
@ -304,20 +299,16 @@ public class LSPatch {
// nestedZipLink.nestedZips.add(nestedZip); // nestedZipLink.nestedZips.add(nestedZip);
// } // }
try {
nestedZipLink.register();
} catch (NoSuchAlgorithmException e) {
throw new PatchError("Failed to create link", e);
}
dstZFile.realign(); dstZFile.realign();
System.out.println("Done. Output APK: " + outputFile.getAbsolutePath()); System.out.println("Writing apk...");
} finally { } finally {
try { try {
outputFile.delete(); outputFile.delete();
FileUtils.moveFile(tmpApk, outputFile); 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);
} }
} }
} }

View File

@ -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<NestedZip> 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<String, Object>) 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);
}
}