diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java index 8e7127a8..fe260c5f 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedDexFile.java @@ -42,11 +42,11 @@ import org.jf.dexlib2.dexbacked.reference.DexBackedTypeReference; import org.jf.dexlib2.dexbacked.util.FixedSizeSet; import org.jf.dexlib2.iface.DexFile; import org.jf.dexlib2.iface.reference.Reference; +import org.jf.dexlib2.util.DexUtil; import org.jf.util.ExceptionWithContext; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.util.AbstractList; @@ -75,7 +75,7 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile { this.opcodes = opcodes; if (verifyMagic) { - verifyMagicAndByteOrder(buf, offset); + DexUtil.verifyDexHeader(buf, offset); } stringCount = readSmallUint(HeaderItem.STRING_COUNT_OFFSET); @@ -107,20 +107,7 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile { @Nonnull public static DexBackedDexFile fromInputStream(@Nonnull Opcodes opcodes, @Nonnull InputStream is) throws IOException { - if (!is.markSupported()) { - throw new IllegalArgumentException("InputStream must support mark"); - } - is.mark(44); - byte[] partialHeader = new byte[44]; - try { - ByteStreams.readFully(is, partialHeader); - } catch (EOFException ex) { - throw new NotADexFile("File is too short"); - } finally { - is.reset(); - } - - verifyMagicAndByteOrder(partialHeader, 0); + DexUtil.verifyDexHeader(is); byte[] buf = ByteStreams.toByteArray(is); return new DexBackedDexFile(opcodes, buf, 0, false); @@ -157,25 +144,6 @@ public class DexBackedDexFile extends BaseDexBuffer implements DexFile { }; } - protected static void verifyMagicAndByteOrder(@Nonnull byte[] buf, int offset) { - if (!HeaderItem.verifyMagic(buf, offset)) { - StringBuilder sb = new StringBuilder("Invalid magic value:"); - for (int i=0; i<8; i++) { - sb.append(String.format(" %02x", buf[i])); - } - throw new NotADexFile(sb.toString()); - } - - int endian = HeaderItem.getEndian(buf, offset); - if (endian == HeaderItem.BIG_ENDIAN_TAG) { - throw new ExceptionWithContext("Big endian dex files are not currently supported"); - } - - if (endian != HeaderItem.LITTLE_ENDIAN_TAG) { - throw new ExceptionWithContext("Invalid endian tag: 0x%x", endian); - } - } - public int getStringIdItemOffset(int stringIndex) { if (stringIndex < 0 || stringIndex >= stringCount) { throw new InvalidItemIndex(stringIndex, "String index out of bounds: %d", stringIndex); diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedOdexFile.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedOdexFile.java index 12f19db0..379ecaac 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedOdexFile.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/DexBackedOdexFile.java @@ -35,9 +35,9 @@ import com.google.common.io.ByteStreams; import org.jf.dexlib2.Opcodes; import org.jf.dexlib2.dexbacked.raw.OdexHeaderItem; import org.jf.dexlib2.dexbacked.util.VariableSizeList; +import org.jf.dexlib2.util.DexUtil; import javax.annotation.Nonnull; -import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; @@ -49,7 +49,6 @@ public class DexBackedOdexFile extends DexBackedDexFile { private final byte[] odexBuf; - public DexBackedOdexFile(@Nonnull Opcodes opcodes, @Nonnull byte[] odexBuf, byte[] dexBuf) { super(opcodes, dexBuf); @@ -64,7 +63,7 @@ public class DexBackedOdexFile extends DexBackedDexFile { return true; } - public List getDependencies() { + @Nonnull public List getDependencies() { final int dexOffset = OdexHeaderItem.getDexOffset(odexBuf); final int dependencyOffset = OdexHeaderItem.getDependenciesOffset(odexBuf) - dexOffset; @@ -85,22 +84,9 @@ public class DexBackedOdexFile extends DexBackedDexFile { }; } - public static DexBackedOdexFile fromInputStream(@Nonnull Opcodes opcodes, @Nonnull InputStream is) + @Nonnull public static DexBackedOdexFile fromInputStream(@Nonnull Opcodes opcodes, @Nonnull InputStream is) throws IOException { - if (!is.markSupported()) { - throw new IllegalArgumentException("InputStream must support mark"); - } - is.mark(8); - byte[] partialHeader = new byte[8]; - try { - ByteStreams.readFully(is, partialHeader); - } catch (EOFException ex) { - throw new NotADexFile("File is too short"); - } finally { - is.reset(); - } - - verifyMagic(partialHeader); + DexUtil.verifyOdexHeader(is); is.reset(); byte[] odexBuf = new byte[OdexHeaderItem.ITEM_SIZE]; @@ -115,18 +101,8 @@ public class DexBackedOdexFile extends DexBackedDexFile { return new DexBackedOdexFile(opcodes, odexBuf, dexBuf); } - private static void verifyMagic(byte[] buf) { - if (!OdexHeaderItem.verifyMagic(buf)) { - StringBuilder sb = new StringBuilder("Invalid magic value:"); - for (int i=0; i<8; i++) { - sb.append(String.format(" %02x", buf[i])); - } - throw new NotAnOdexFile(sb.toString()); - } - } - public int getOdexVersion() { - return OdexHeaderItem.getVersion(odexBuf); + return OdexHeaderItem.getVersion(odexBuf, 0); } public static class NotAnOdexFile extends RuntimeException { diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/ZipDexContainer.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/ZipDexContainer.java index 695866a2..97810e59 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/ZipDexContainer.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/ZipDexContainer.java @@ -37,10 +37,12 @@ import org.jf.dexlib2.Opcodes; import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile; import org.jf.dexlib2.dexbacked.ZipDexContainer.ZipDexFile; import org.jf.dexlib2.iface.MultiDexContainer; +import org.jf.dexlib2.util.DexUtil; +import org.jf.dexlib2.util.DexUtil.InvalidFile; +import org.jf.dexlib2.util.DexUtil.UnsupportedFile; import javax.annotation.Nonnull; import javax.annotation.Nullable; -import java.io.EOFException; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -49,8 +51,6 @@ import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; -import static org.jf.dexlib2.dexbacked.DexBackedDexFile.verifyMagicAndByteOrder; - /** * Represents a zip file that contains dex files (i.e. an apk or jar file) */ @@ -154,23 +154,17 @@ public class ZipDexContainer implements MultiDexContainer { protected boolean isDex(@Nonnull ZipFile zipFile, @Nonnull ZipEntry zipEntry) throws IOException { InputStream inputStream = zipFile.getInputStream(zipEntry); try { - inputStream.mark(44); - byte[] partialHeader = new byte[44]; - try { - ByteStreams.readFully(inputStream, partialHeader); - } catch (EOFException ex) { - return false; - } - - try { - verifyMagicAndByteOrder(partialHeader, 0); - } catch (NotADexFile ex) { - return false; - } - return true; + DexUtil.verifyDexHeader(inputStream); + } catch (NotADexFile ex) { + return false; + } catch (InvalidFile ex) { + return false; + } catch (UnsupportedFile ex) { + return false; } finally { inputStream.close(); } + return true; } protected ZipFile getZipFile() throws IOException { diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/HeaderItem.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/HeaderItem.java index 531ff461..a88f509e 100644 --- a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/HeaderItem.java +++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/raw/HeaderItem.java @@ -42,14 +42,8 @@ import javax.annotation.Nullable; public class HeaderItem { public static final int ITEM_SIZE = 0x70; - /** - * The magic numbers for dex files. - * - * They are: "dex\n035\0" and "dex\n037\0". - */ - public static final byte[][] MAGIC_VALUES= new byte[][] { - new byte[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x35, 0x00}, - new byte[]{0x64, 0x65, 0x78, 0x0a, 0x30, 0x33, 0x37, 0x00}}; + private static final byte[] MAGIC_VALUE = new byte[] { 0x64, 0x65, 0x78, 0x0a, 0x00, 0x00, 0x00, 0x00 }; + private static final int[] SUPPORTED_DEX_VERSIONS = new int[] { 35, 37 }; public static final int LITTLE_ENDIAN_TAG = 0x12345678; public static final int BIG_ENDIAN_TAG = 0x78563412; @@ -230,48 +224,97 @@ public class HeaderItem { return "Invalid"; } - /** - * Get the higest magic number supported by Android for this api level. + * Get the highest magic number supported by Android for this api level. * @return The dex file magic number */ public static byte[] getMagicForApi(int api) { if (api < 24) { // Prior to Android N we only support dex version 035. - return HeaderItem.MAGIC_VALUES[0]; + return getMagicForDexVersion(35); } else { // On android N and later we support dex version 037. - return HeaderItem.MAGIC_VALUES[1]; + return getMagicForDexVersion(37); } } - private static int getVersion(byte[] buf, int offset) { - if (buf.length - offset < 8) { - return 0; + public static byte[] getMagicForDexVersion(int dexVersion) { + byte[] magic = MAGIC_VALUE.clone(); + + if (dexVersion < 0 || dexVersion > 999) { + throw new IllegalArgumentException("dexVersion must be within [0, 999]"); } - boolean matches = true; - for (int i=0; i=4; i--) { + int digit = dexVersion % 10; + magic[i] = (byte)('0' + digit); + dexVersion /= 10; } - return 0; + + return magic; } + /** + * Verifies the magic value at the beginning of a dex file + * + * @param buf A byte array containing at least the first 8 bytes of a dex file + * @param offset The offset within the buffer to the beginning of the dex header + * @return True if the magic value is valid + */ public static boolean verifyMagic(byte[] buf, int offset) { - // verifies the magic value - return getVersion(buf, offset) != 0; + if (buf.length - offset < 8) { + return false; + } + + for (int i=0; i<4; i++) { + if (buf[offset + i] != MAGIC_VALUE[i]) { + return false; + } + } + for (int i=4; i<7; i++) { + if (buf[offset + i] < '0' || + buf[offset + i] > '9') { + return false; + } + } + if (buf[offset + 7] != MAGIC_VALUE[7]) { + return false; + } + + return true; } + /** + * Gets the dex version from a dex header + * + * @param buf A byte array containing at least the first 7 bytes of a dex file + * @param offset The offset within the buffer to the beginning of the dex header + * @return The dex version if the header is valid or -1 if the header is invalid + */ + public static int getVersion(byte[] buf, int offset) { + if (!verifyMagic(buf, offset)) { + return -1; + } + + return getVersionUnchecked(buf, offset); + } + + private static int getVersionUnchecked(byte[] buf, int offset) { + int version = (buf[offset + 4] - '0') * 100; + version += (buf[offset + 5] - '0') * 10; + version += buf[offset + 6] - '0'; + + return version; + } + + public static boolean isSupportedDexVersion(int version) { + for (int i=0; i '9') { + return false; + } + } + if (buf[offset + 7] != MAGIC_VALUE[7]) { + return false; + } + + return true; } - public static boolean verifyMagic(byte[] buf) { - // verifies the magic value - return getVersion(buf) != 0; + /** + * Gets the dex version from an odex header + * + * @param buf A byte array containing at least the first 7 bytes of an odex file + * @param offset The offset within the buffer to the beginning of the odex header + * @return The odex version if the header is valid or -1 if the header is invalid + */ + public static int getVersion(byte[] buf, int offset) { + if (!verifyMagic(buf, offset)) { + return -1; + } + + return getVersionUnchecked(buf, offset); + } + + private static int getVersionUnchecked(byte[] buf, int offset) { + int version = (buf[offset + 4] - '0') * 100; + version += (buf[offset + 5] - '0') * 10; + version += buf[offset + 6] - '0'; + + return version; + } + + public static boolean isSupportedOdexVersion(int version) { + for (int i=0; i