Old zip magic
This commit is contained in:
parent
0c51939e6d
commit
9fa0c56231
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue