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
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");
}
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());
}
}
}
}

View File

@ -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<StoredEntry, String> getLinks() {

View File

@ -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.
*

View File

@ -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.<ExtraField.Segment>builder().add(linkedEntry.getLocalExtra().getSegments().toArray(new ExtraField.Segment[0])).add(new ExtraField.LinkingEntrySegment(linkingEntry)).build()));
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.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);
}
}
}

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