diff --git a/src/main/java/com/reandroid/apk/ApkModule.java b/src/main/java/com/reandroid/apk/ApkModule.java index 5181403..c409e19 100644 --- a/src/main/java/com/reandroid/apk/ApkModule.java +++ b/src/main/java/com/reandroid/apk/ApkModule.java @@ -166,12 +166,12 @@ public class ApkModule implements ApkFile { return null; } AndroidManifestBlock manifestBlock = getAndroidManifestBlock(); - Integer version = manifestBlock.getCompileSdkVersion(); + Integer version = manifestBlock.getTargetSdkVersion(); if(version == null){ version = manifestBlock.getPlatformBuildVersionCode(); } if(version == null){ - version = manifestBlock.getTargetSdkVersion(); + version = manifestBlock.getCompileSdkVersion(); } return version; } diff --git a/src/main/java/com/reandroid/archive2/Archive.java b/src/main/java/com/reandroid/archive2/Archive.java index 966cfc4..a00e02a 100644 --- a/src/main/java/com/reandroid/archive2/Archive.java +++ b/src/main/java/com/reandroid/archive2/Archive.java @@ -21,6 +21,7 @@ import com.reandroid.archive2.block.LocalFileHeader; import com.reandroid.archive2.io.ArchiveFile; import com.reandroid.archive2.io.ArchiveUtil; import com.reandroid.archive2.io.ZipSource; +import com.reandroid.archive2.model.ApkSigBlock; import com.reandroid.archive2.model.LocalFileDirectory; import java.io.File; @@ -37,6 +38,7 @@ public class Archive { private final ZipSource zipSource; private final List entryList; private final EndRecord endRecord; + private final ApkSigBlock apkSigBlock; public Archive(ZipSource zipSource) throws IOException { this.zipSource = zipSource; LocalFileDirectory lfd = new LocalFileDirectory(); @@ -52,6 +54,7 @@ public class Archive { } this.entryList = entryList; this.endRecord = lfd.getCentralFileDirectory().getEndRecord(); + this.apkSigBlock = lfd.getApkSigBlock(); } public Archive(File file) throws IOException { this(new ArchiveFile(file)); @@ -71,6 +74,13 @@ public class Archive { return entryList; } + public ApkSigBlock getApkSigBlock() { + return apkSigBlock; + } + public EndRecord getEndRecord() { + return endRecord; + } + // for test public void extract(File dir) throws IOException { for(ArchiveEntry archiveEntry:getEntryList()){ diff --git a/src/main/java/com/reandroid/archive2/block/ApkSignature.java b/src/main/java/com/reandroid/archive2/block/ApkSignature.java new file mode 100644 index 0000000..13d0861 --- /dev/null +++ b/src/main/java/com/reandroid/archive2/block/ApkSignature.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.block; + +import com.reandroid.arsc.decoder.ValueDecoder; + +import java.io.*; +import java.util.Objects; + +public class ApkSignature extends ZipBlock{ + public ApkSignature() { + super(MIN_SIZE); + } + public boolean isValid(){ + return getSigSize() > MIN_SIZE && getId() != null; + } + @Override + public int readBytes(InputStream inputStream) throws IOException { + setBytesLength(8, false); + byte[] bytes = getBytesInternal(); + int read = inputStream.read(bytes, 0, bytes.length); + if(read != bytes.length){ + return read; + } + int offset = bytes.length; + int length = (int) getSigSize(); + setSigSize(length); + + bytes = getBytesInternal(); + int dataRead = inputStream.read(bytes, offset, length); + if(dataRead != length){ + setSigSize(0); + } + return read + dataRead; + } + public long getSigSize(){ + return getLong(OFFSET_size); + } + public void setSigSize(long size){ + int length = (int) (OFFSET_id + size); + setBytesLength(length, false); + putLong(OFFSET_size, size); + } + public int getIdValue(){ + return getInteger(OFFSET_id); + } + public Id getId(){ + return Id.valueOf(getIdValue()); + } + public void setId(int id){ + putInteger(OFFSET_id, id); + } + public void setId(Id signatureId){ + setId(signatureId == null? 0 : signatureId.getId()); + } + public byte[] getData(){ + return getBytes(OFFSET_data, (int) getSigSize() - 4, true); + } + public void setData(byte[] data){ + if(data == null){ + data = new byte[0]; + } + setData(data, 0, data.length); + } + public void setData(byte[] data, int offset, int length){ + setSigSize(length); + putBytes(data, offset, OFFSET_data, data.length); + } + public void writeData(OutputStream outputStream) throws IOException{ + byte[] bytes = getBytesInternal(); + outputStream.write(bytes, OFFSET_data, (int) (getSigSize() - 4)); + } + public void writeData(File file) throws IOException{ + File dir = file.getParentFile(); + if(dir != null && !dir.exists()){ + dir.mkdirs(); + } + FileOutputStream outputStream = new FileOutputStream(file); + writeData(outputStream); + outputStream.close(); + } + public File writeToDir(File dir) throws IOException{ + File file = new File(dir, getId().toFileName()); + writeData(file); + return file; + } + @Override + public String toString() { + return getId() + ", size=" + getSigSize(); + } + + + public static class Id { + private final String name; + private final int id; + + private Id(String name, int id) { + this.name = name; + this.id = id; + } + public String name() { + return name; + } + public int getId() { + return id; + } + public String toFileName(){ + if(this.name != null){ + return name + FILE_EXTENSION; + } + return String.format("0x%08x", id) + FILE_EXTENSION; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + Id that = (Id) obj; + return id == that.id; + } + @Override + public int hashCode() { + return Objects.hash(id); + } + @Override + public String toString(){ + String name = this.name; + if(name != null){ + return name; + } + return "UNKNOWN("+String.format("0x%08x", id)+")"; + } + public static Id valueOf(String name){ + if(name == null){ + return null; + } + String ext = FILE_EXTENSION; + if(name.endsWith(ext)){ + name = name.substring(0, name.length()-ext.length()); + } + for(Id signatureId:VALUES){ + if(name.equalsIgnoreCase(signatureId.name())){ + return signatureId; + } + } + if(ValueDecoder.isHex(name)){ + return new Id(null, ValueDecoder.parseHex(name)); + } + return null; + } + public static Id valueOf(int id){ + if(id == 0){ + return null; + } + for(Id signatureId:VALUES){ + if(id == signatureId.getId()){ + return signatureId; + } + } + return new Id(null, id); + } + public static Id[] values() { + return VALUES.clone(); + } + public static final Id V2 = new Id("V2", 0x7109871A); + public static final Id V3 = new Id("V3",0xF05368C0); + public static final Id V31 = new Id("V31",0x1B93AD61); + public static final Id STAMP_V1 = new Id("STAMP_V1", 0x2B09189E); + public static final Id STAMP_V2 = new Id("STAMP_V2", 0x6DFF800D); + public static final Id PADDING = new Id("PADDING", 0x42726577); + + + private static final Id[] VALUES = new Id[]{ + V2, V3, V31, STAMP_V1, STAMP_V2, PADDING + }; + + private static final String FILE_EXTENSION = ".bin"; + } + + public static final int MIN_SIZE = 12; + + private static final int OFFSET_size = 0; + private static final int OFFSET_id = 8; + private static final int OFFSET_data = 12; +} diff --git a/src/main/java/com/reandroid/archive2/block/CommonHeader.java b/src/main/java/com/reandroid/archive2/block/CommonHeader.java index 6129bce..1142e4d 100644 --- a/src/main/java/com/reandroid/archive2/block/CommonHeader.java +++ b/src/main/java/com/reandroid/archive2/block/CommonHeader.java @@ -115,7 +115,7 @@ public abstract class CommonHeader extends ZipHeader { putShort(offsetGeneralPurpose + 2, value); } public long getDosTime(){ - return getUnsignedLong(offsetGeneralPurpose + 4); + return getIntegerUnsigned(offsetGeneralPurpose + 4); } public void setDosTime(long value){ putInteger(offsetGeneralPurpose + 4, value); @@ -130,19 +130,19 @@ public abstract class CommonHeader extends ZipHeader { setDosTime(javaToDosTime(date)); } public long getCrc(){ - return getUnsignedLong(offsetGeneralPurpose + 8); + return getIntegerUnsigned(offsetGeneralPurpose + 8); } public void setCrc(long value){ putInteger(offsetGeneralPurpose + 8, value); } public long getCompressedSize(){ - return getUnsignedLong(offsetGeneralPurpose + 12); + return getIntegerUnsigned(offsetGeneralPurpose + 12); } public void setCompressedSize(long value){ putInteger(offsetGeneralPurpose + 12, value); } public long getSize(){ - return getUnsignedLong(offsetGeneralPurpose + 16); + return getIntegerUnsigned(offsetGeneralPurpose + 16); } public void setSize(long value){ putInteger(offsetGeneralPurpose + 16, value); diff --git a/src/main/java/com/reandroid/archive2/block/DataDescriptor.java b/src/main/java/com/reandroid/archive2/block/DataDescriptor.java index 92cf38a..5b578cc 100644 --- a/src/main/java/com/reandroid/archive2/block/DataDescriptor.java +++ b/src/main/java/com/reandroid/archive2/block/DataDescriptor.java @@ -22,19 +22,19 @@ public class DataDescriptor extends ZipHeader{ super(MIN_LENGTH, ZipSignature.DATA_DESCRIPTOR); } public long getCrc(){ - return getUnsignedLong(OFFSET_crc); + return getIntegerUnsigned(OFFSET_crc); } public void setCrc(long value){ putInteger(OFFSET_crc, value); } public long getCompressedSize(){ - return getUnsignedLong(OFFSET_compressed_size); + return getIntegerUnsigned(OFFSET_compressed_size); } public void setCompressedSize(long value){ putInteger(OFFSET_compressed_size, value); } public long getSize(){ - return getUnsignedLong(OFFSET_size); + return getIntegerUnsigned(OFFSET_size); } public void setSize(long value){ putInteger(OFFSET_size, value); diff --git a/src/main/java/com/reandroid/archive2/block/EndRecord.java b/src/main/java/com/reandroid/archive2/block/EndRecord.java index 618e539..da3ba3b 100644 --- a/src/main/java/com/reandroid/archive2/block/EndRecord.java +++ b/src/main/java/com/reandroid/archive2/block/EndRecord.java @@ -46,13 +46,13 @@ public class EndRecord extends ZipHeader{ putShort(OFFSET_totalNumberOfDirectories, value); } public long getLengthOfCentralDirectory(){ - return getUnsignedLong(OFFSET_lengthOfCentralDirectory); + return getIntegerUnsigned(OFFSET_lengthOfCentralDirectory); } public void setLengthOfCentralDirectory(long value){ putInteger(OFFSET_lengthOfCentralDirectory, value); } public long getOffsetOfCentralDirectory(){ - return getUnsignedLong(OFFSET_offsetOfCentralDirectory); + return getIntegerUnsigned(OFFSET_offsetOfCentralDirectory); } public void setOffsetOfCentralDirectory(int value){ putInteger(OFFSET_offsetOfCentralDirectory, value); diff --git a/src/main/java/com/reandroid/archive2/block/LongBlock.java b/src/main/java/com/reandroid/archive2/block/LongBlock.java new file mode 100644 index 0000000..f1f3097 --- /dev/null +++ b/src/main/java/com/reandroid/archive2/block/LongBlock.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.block; + +import java.io.IOException; +import java.io.InputStream; + +public class LongBlock extends ZipBlock{ + public LongBlock() { + super(8); + } + @Override + public int readBytes(InputStream inputStream) throws IOException { + byte[] bytes = getBytesInternal(); + return inputStream.read(bytes, 0, bytes.length); + } + public long get(){ + return getLong(0); + } + public void set(long value){ + putLong(0, value); + } + @Override + public String toString(){ + return String.valueOf(get()); + } +} diff --git a/src/main/java/com/reandroid/archive2/block/SignatureFooter.java b/src/main/java/com/reandroid/archive2/block/SignatureFooter.java new file mode 100644 index 0000000..25c94f3 --- /dev/null +++ b/src/main/java/com/reandroid/archive2/block/SignatureFooter.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2022 github.com/REAndroid + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.reandroid.archive2.block; + +import com.reandroid.arsc.item.ByteArray; + +import java.io.IOException; +import java.io.InputStream; + +public class SignatureFooter extends ZipBlock{ + public SignatureFooter() { + super(MIN_SIZE); + setMagic(APK_SIG_BLOCK_MAGIC); + } + @Override + public int readBytes(InputStream inputStream) throws IOException { + setBytesLength(MIN_SIZE, false); + byte[] bytes = getBytesInternal(); + return inputStream.read(bytes, 0, bytes.length); + } + public long getSigBlockSizeInFooter(){ + return getLong(OFFSET_size); + } + public void setSigBlockSizeInFooter(long size){ + putLong(OFFSET_size, size); + } + public byte[] getMagic() { + return getBytes(OFFSET_magic, APK_SIG_BLOCK_MAGIC.length, false); + } + public void setMagic(byte[] magic){ + putBytes(magic, 0, OFFSET_magic, magic.length); + } + public boolean isValid(){ + return getSigBlockSizeInFooter() > MIN_SIZE + && ByteArray.equals(APK_SIG_BLOCK_MAGIC, getMagic()); + } + @Override + public String toString(){ + return getSigBlockSizeInFooter() + " ["+new String(getMagic())+"]"; + } + + public static final int MIN_SIZE = 24; + + private static final int OFFSET_size = 0; + private static final int OFFSET_magic = 8; + + private static final byte[] APK_SIG_BLOCK_MAGIC = + new byte[]{'A', 'P', 'K', ' ', 'S', 'i', 'g', ' ', 'B', 'l', 'o', 'c', 'k', ' ', '4', '2'}; +} diff --git a/src/main/java/com/reandroid/archive2/block/ZipBlock.java b/src/main/java/com/reandroid/archive2/block/ZipBlock.java index fc6116b..33e4ddb 100644 --- a/src/main/java/com/reandroid/archive2/block/ZipBlock.java +++ b/src/main/java/com/reandroid/archive2/block/ZipBlock.java @@ -43,7 +43,34 @@ public abstract class ZipBlock extends BlockItem { this.readBytes((InputStream) blockReader); } - long getUnsignedLong(int offset){ + byte[] getBytes(int offset, int length, boolean strict){ + byte[] bytes = getBytesInternal(); + if(strict){ + if(offset<0 || offset>=bytes.length || (offset + length)>bytes.length){ + return null; + } + } + if(offset < 0){ + offset = 0; + } + int available = bytes.length - offset; + if(length<=0 || available <=0){ + return new byte[0]; + } + if(length > available){ + length = available; + } + byte[] result = new byte[length]; + System.arraycopy(getBytesInternal(), offset, result, 0, length); + return result; + } + long getLong(int offset){ + return getLong(getBytesInternal(), offset); + } + void putLong(int offset, long value){ + putLong(getBytesInternal(), offset, value); + } + long getIntegerUnsigned(int offset){ return getInteger(offset) & 0x00000000ffffffffL; } void putBit(int offset, int bitIndex, boolean bit){ @@ -71,4 +98,29 @@ public abstract class ZipBlock extends BlockItem { putShort(getBytesInternal(), offset, (short) value); } + public static long getLong(byte[] bytes, int offset){ + if((offset + 8)>bytes.length){ + return 0; + } + long result = 0; + int index = offset + 7; + while (index>=offset){ + result = result << 8; + result |= (bytes[index] & 0xff); + index --; + } + return result; + } + public static void putLong(byte[] bytes, int offset, long value){ + if((offset + 8) > bytes.length){ + return; + } + int index = offset; + offset = index + 8; + while (index>> 8; + index++; + } + } } diff --git a/src/main/java/com/reandroid/archive2/model/ApkSigBlock.java b/src/main/java/com/reandroid/archive2/model/ApkSigBlock.java index 8d1b660..7a429bd 100644 --- a/src/main/java/com/reandroid/archive2/model/ApkSigBlock.java +++ b/src/main/java/com/reandroid/archive2/model/ApkSigBlock.java @@ -15,45 +15,56 @@ */ package com.reandroid.archive2.model; +import com.reandroid.archive2.block.ApkSignature; +import com.reandroid.archive2.block.LongBlock; +import com.reandroid.archive2.block.SignatureFooter; + +import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; public class ApkSigBlock { - public ApkSigBlock(){ + private final SignatureFooter signatureFooter; + private final LongBlock mSize; + private final List mSignatures; + public ApkSigBlock(SignatureFooter signatureFooter){ + this.signatureFooter = signatureFooter; + this.mSize = new LongBlock(); + this.mSignatures = new ArrayList<>(); } - //TODO : implement all - public void parse(byte[] bytes) throws IOException { - int offset = findSignature(bytes); - if(offset<0){ - return; - } - offset += APK_SIGNING_BLOCK_MAGIC.length; - int length = bytes.length - offset; + public LongBlock getTotalSize(){ + return mSize; } - private int findSignature(byte[] bytes){ - for(int i=0;i getSignatures() { + return mSignatures; } - private boolean matchesSignature(byte[] bytes, int offset){ - byte[] sig = APK_SIGNING_BLOCK_MAGIC; - int length = bytes.length - offset; - if (length