1 /* 2 * Copyright 2016, Google Inc. 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * Redistributions in binary form must reproduce the above 12 * copyright notice, this list of conditions and the following disclaimer 13 * in the documentation and/or other materials provided with the 14 * distribution. 15 * Neither the name of Google Inc. nor the names of its 16 * contributors may be used to endorse or promote products derived from 17 * this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 package org.jf.dexlib2.util; 33 34 import com.google.common.io.ByteStreams; 35 import org.jf.dexlib2.dexbacked.DexBackedDexFile.NotADexFile; 36 import org.jf.dexlib2.dexbacked.DexBackedOdexFile.NotAnOdexFile; 37 import org.jf.dexlib2.dexbacked.raw.HeaderItem; 38 import org.jf.dexlib2.dexbacked.raw.OdexHeaderItem; 39 40 import javax.annotation.Nonnull; 41 import java.io.EOFException; 42 import java.io.IOException; 43 import java.io.InputStream; 44 45 public class DexUtil { 46 47 /** 48 * Reads in the dex header from the given input stream and verifies that it is valid and a supported version 49 * 50 * The inputStream must support mark(), and will be reset to initial position upon exiting the method 51 * 52 * @param inputStream An input stream that is positioned at a dex header 53 * @throws NotADexFile If the file is not a dex file 54 * @throws InvalidFile If the header appears to be a dex file, but is not valid for some reason 55 * @throws UnsupportedFile If the dex header is valid, but uses unsupported functionality 56 */ verifyDexHeader(@onnull InputStream inputStream)57 public static void verifyDexHeader(@Nonnull InputStream inputStream) throws IOException { 58 if (!inputStream.markSupported()) { 59 throw new IllegalArgumentException("InputStream must support mark"); 60 } 61 inputStream.mark(44); 62 byte[] partialHeader = new byte[44]; 63 try { 64 ByteStreams.readFully(inputStream, partialHeader); 65 } catch (EOFException ex) { 66 throw new NotADexFile("File is too short"); 67 } finally { 68 inputStream.reset(); 69 } 70 71 verifyDexHeader(partialHeader, 0); 72 } 73 74 /** 75 * Verifies that the dex header is valid and a supported version 76 * 77 * @param buf A byte array containing at least the first 44 bytes of a dex file 78 * @param offset The offset within the array to the dex header 79 * @throws NotADexFile If the file is not a dex file 80 * @throws InvalidFile If the header appears to be a dex file, but is not valid for some reason 81 * @throws UnsupportedFile If the dex header is valid, but uses unsupported functionality 82 */ verifyDexHeader(@onnull byte[] buf, int offset)83 public static void verifyDexHeader(@Nonnull byte[] buf, int offset) { 84 int dexVersion = HeaderItem.getVersion(buf, offset); 85 if (dexVersion == -1) { 86 StringBuilder sb = new StringBuilder("Not a valid dex magic value:"); 87 for (int i=0; i<8; i++) { 88 sb.append(String.format(" %02x", buf[i])); 89 } 90 throw new NotADexFile(sb.toString()); 91 } 92 93 if (!HeaderItem.isSupportedDexVersion(dexVersion)) { 94 throw new UnsupportedFile(String.format("Dex version %03d is not supported", dexVersion)); 95 } 96 97 int endian = HeaderItem.getEndian(buf, offset); 98 if (endian == HeaderItem.BIG_ENDIAN_TAG) { 99 throw new UnsupportedFile("Big endian dex files are not supported"); 100 } 101 102 if (endian != HeaderItem.LITTLE_ENDIAN_TAG) { 103 throw new InvalidFile(String.format("Invalid endian tag: 0x%x", endian)); 104 } 105 } 106 107 /** 108 * Reads in the odex header from the given input stream and verifies that it is valid and a supported version 109 * 110 * The inputStream must support mark(), and will be reset to initial position upon exiting the method 111 * 112 * @param inputStream An input stream that is positioned at an odex header 113 * @throws NotAnOdexFile If the file is not an odex file 114 * @throws UnsupportedFile If the odex header is valid, but is an unsupported version 115 */ verifyOdexHeader(@onnull InputStream inputStream)116 public static void verifyOdexHeader(@Nonnull InputStream inputStream) throws IOException { 117 if (!inputStream.markSupported()) { 118 throw new IllegalArgumentException("InputStream must support mark"); 119 } 120 inputStream.mark(8); 121 byte[] partialHeader = new byte[8]; 122 try { 123 ByteStreams.readFully(inputStream, partialHeader); 124 } catch (EOFException ex) { 125 throw new NotAnOdexFile("File is too short"); 126 } finally { 127 inputStream.reset(); 128 } 129 130 verifyOdexHeader(partialHeader, 0); 131 } 132 133 /** 134 * Verifies that the odex header is valid and a supported version 135 * 136 * @param buf A byte array containing at least the first 8 bytes of an odex file 137 * @param offset The offset within the array to the odex header 138 * @throws NotAnOdexFile If the file is not an odex file 139 * @throws UnsupportedFile If the odex header is valid, but uses unsupported functionality 140 */ verifyOdexHeader(@onnull byte[] buf, int offset)141 public static void verifyOdexHeader(@Nonnull byte[] buf, int offset) { 142 int odexVersion = OdexHeaderItem.getVersion(buf, offset); 143 if (odexVersion == -1) { 144 StringBuilder sb = new StringBuilder("Not a valid odex magic value:"); 145 for (int i=0; i<8; i++) { 146 sb.append(String.format(" %02x", buf[i])); 147 } 148 throw new NotAnOdexFile(sb.toString()); 149 } 150 151 if (!OdexHeaderItem.isSupportedOdexVersion(odexVersion)) { 152 throw new UnsupportedFile(String.format("Odex version %03d is not supported", odexVersion)); 153 } 154 } 155 156 public static class InvalidFile extends RuntimeException { InvalidFile()157 public InvalidFile() { 158 } 159 InvalidFile(String message)160 public InvalidFile(String message) { 161 super(message); 162 } 163 InvalidFile(String message, Throwable cause)164 public InvalidFile(String message, Throwable cause) { 165 super(message, cause); 166 } 167 InvalidFile(Throwable cause)168 public InvalidFile(Throwable cause) { 169 super(cause); 170 } 171 } 172 173 public static class UnsupportedFile extends RuntimeException { UnsupportedFile()174 public UnsupportedFile() { 175 } 176 UnsupportedFile(String message)177 public UnsupportedFile(String message) { 178 super(message); 179 } 180 UnsupportedFile(String message, Throwable cause)181 public UnsupportedFile(String message, Throwable cause) { 182 super(message, cause); 183 } 184 UnsupportedFile(Throwable cause)185 public UnsupportedFile(Throwable cause) { 186 super(cause); 187 } 188 } 189 } 190