1 /* 2 * Copyright 2014, 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.dexbacked; 33 34 import com.google.common.io.ByteStreams; 35 import org.jf.dexlib2.Opcodes; 36 import org.jf.dexlib2.dexbacked.OatFile.SymbolTable.Symbol; 37 import org.jf.dexlib2.dexbacked.raw.HeaderItem; 38 import org.jf.util.AbstractForwardSequentialList; 39 40 import javax.annotation.Nonnull; 41 import java.io.EOFException; 42 import java.io.IOException; 43 import java.io.InputStream; 44 import java.nio.charset.Charset; 45 import java.util.AbstractList; 46 import java.util.Iterator; 47 import java.util.List; 48 49 public class OatFile extends BaseDexBuffer { 50 private static final byte[] ELF_MAGIC = new byte[] { 0x7f, 'E', 'L', 'F' }; 51 private static final byte[] OAT_MAGIC = new byte[] { 'o', 'a', 't', '\n' }; 52 private static final int MIN_ELF_HEADER_SIZE = 52; 53 54 // These are the "known working" versions that I have manually inspected the source for. 55 // Later version may or may not work, depending on what changed. 56 private static final int MIN_OAT_VERSION = 56; 57 private static final int MAX_OAT_VERSION = 71; 58 59 public static final int UNSUPPORTED = 0; 60 public static final int SUPPORTED = 1; 61 public static final int UNKNOWN = 2; 62 63 private final boolean is64bit; 64 @Nonnull private final OatHeader oatHeader; 65 @Nonnull private final Opcodes opcodes; 66 67 public OatFile(@Nonnull byte[] buf) { 68 super(buf); 69 70 if (buf.length < MIN_ELF_HEADER_SIZE) { 71 throw new NotAnOatFileException(); 72 } 73 74 verifyMagic(buf); 75 76 if (buf[4] == 1) { 77 is64bit = false; 78 } else if (buf[4] == 2) { 79 is64bit = true; 80 } else { 81 throw new InvalidOatFileException(String.format("Invalid word-size value: %x", buf[5])); 82 } 83 84 OatHeader oatHeader = null; 85 SymbolTable symbolTable = getSymbolTable(); 86 for (Symbol symbol: symbolTable.getSymbols()) { 87 if (symbol.getName().equals("oatdata")) { 88 oatHeader = new OatHeader(symbol.getFileOffset()); 89 break; 90 } 91 } 92 93 if (oatHeader == null) { 94 throw new InvalidOatFileException("Oat file has no oatdata symbol"); 95 } 96 this.oatHeader = oatHeader; 97 98 if (!oatHeader.isValid()) { 99 throw new InvalidOatFileException("Invalid oat magic value"); 100 } 101 102 this.opcodes = Opcodes.forArtVersion(oatHeader.getVersion()); 103 } 104 105 private static void verifyMagic(byte[] buf) { 106 for (int i = 0; i < ELF_MAGIC.length; i++) { 107 if (buf[i] != ELF_MAGIC[i]) { 108 throw new NotAnOatFileException(); 109 } 110 } 111 } 112 113 public static OatFile fromInputStream(@Nonnull InputStream is) 114 throws IOException { 115 if (!is.markSupported()) { 116 throw new IllegalArgumentException("InputStream must support mark"); 117 } 118 is.mark(4); 119 byte[] partialHeader = new byte[4]; 120 try { 121 ByteStreams.readFully(is, partialHeader); 122 } catch (EOFException ex) { 123 throw new NotAnOatFileException(); 124 } finally { 125 is.reset(); 126 } 127 128 verifyMagic(partialHeader); 129 130 is.reset(); 131 132 byte[] buf = ByteStreams.toByteArray(is); 133 return new OatFile(buf); 134 } 135 136 public int getOatVersion() { 137 return oatHeader.getVersion(); 138 } 139 140 public int isSupportedVersion() { 141 int version = getOatVersion(); 142 if (version < MIN_OAT_VERSION) { 143 return UNSUPPORTED; 144 } 145 if (version <= MAX_OAT_VERSION) { 146 return SUPPORTED; 147 } 148 return UNKNOWN; 149 } 150 151 @Nonnull 152 public List<OatDexFile> getDexFiles() { 153 return new AbstractForwardSequentialList<OatDexFile>() { 154 @Override public int size() { 155 return oatHeader.getDexFileCount(); 156 } 157 158 @Nonnull @Override public Iterator<OatDexFile> iterator() { 159 return new Iterator<OatDexFile>() { 160 int index = 0; 161 int offset = oatHeader.getDexListStart(); 162 163 @Override public boolean hasNext() { 164 return index < size(); 165 } 166 167 @Override public OatDexFile next() { 168 int filenameLength = readSmallUint(offset); 169 offset += 4; 170 171 // TODO: what is the correct character encoding? 172 String filename = new String(buf, offset, filenameLength, Charset.forName("US-ASCII")); 173 offset += filenameLength; 174 175 offset += 4; // checksum 176 177 int dexOffset = readSmallUint(offset) + oatHeader.offset; 178 offset += 4; 179 180 int classCount = readSmallUint(dexOffset + HeaderItem.CLASS_COUNT_OFFSET); 181 offset += 4 * classCount; 182 183 index++; 184 185 return new OatDexFile(dexOffset, filename); 186 } 187 188 @Override public void remove() { 189 throw new UnsupportedOperationException(); 190 } 191 }; 192 } 193 }; 194 } 195 196 public class OatDexFile extends DexBackedDexFile { 197 @Nonnull public final String filename; 198 199 public OatDexFile(int offset, @Nonnull String filename) { 200 super(opcodes, OatFile.this.buf, offset); 201 this.filename = filename; 202 } 203 204 public int getOatVersion() { 205 return OatFile.this.getOatVersion(); 206 } 207 208 @Override public boolean hasOdexOpcodes() { 209 return true; 210 } 211 } 212 213 private class OatHeader { 214 private final int offset; 215 216 public OatHeader(int offset) { 217 this.offset = offset; 218 } 219 220 public boolean isValid() { 221 for (int i=0; i<OAT_MAGIC.length; i++) { 222 if (buf[offset + i] != OAT_MAGIC[i]) { 223 return false; 224 } 225 } 226 227 for (int i=4; i<7; i++) { 228 if (buf[offset + i] < '0' || buf[offset + i] > '9') { 229 return false; 230 } 231 } 232 233 return buf[offset + 7] == 0; 234 } 235 236 public int getVersion() { 237 return Integer.valueOf(new String(buf, offset + 4, 3)); 238 } 239 240 public int getDexFileCount() { 241 return readSmallUint(offset + 20); 242 } 243 244 public int getKeyValueStoreSize() { 245 int version = getVersion(); 246 if (version < 56) { 247 throw new IllegalStateException("Unsupported oat version"); 248 } 249 int fieldOffset = 17 * 4; 250 return readSmallUint(offset + fieldOffset); 251 } 252 253 public int getHeaderSize() { 254 int version = getVersion(); 255 if (version >= 56) { 256 return 18*4 + getKeyValueStoreSize(); 257 } else { 258 throw new IllegalStateException("Unsupported oat version"); 259 } 260 261 } 262 263 public int getDexListStart() { 264 return offset + getHeaderSize(); 265 } 266 } 267 268 @Nonnull 269 private List<SectionHeader> getSections() { 270 final int offset; 271 final int entrySize; 272 final int entryCount; 273 if (is64bit) { 274 offset = readLongAsSmallUint(40); 275 entrySize = readUshort(58); 276 entryCount = readUshort(60); 277 } else { 278 offset = readSmallUint(32); 279 entrySize = readUshort(46); 280 entryCount = readUshort(48); 281 } 282 283 if (offset + (entrySize * entryCount) > buf.length) { 284 throw new InvalidOatFileException("The ELF section headers extend past the end of the file"); 285 } 286 287 return new AbstractList<SectionHeader>() { 288 @Override public SectionHeader get(int index) { 289 if (index < 0 || index >= entryCount) { 290 throw new IndexOutOfBoundsException(); 291 } 292 if (is64bit) { 293 return new SectionHeader64Bit(offset + (index * entrySize)); 294 } else { 295 return new SectionHeader32Bit(offset + (index * entrySize)); 296 } 297 } 298 299 @Override public int size() { 300 return entryCount; 301 } 302 }; 303 } 304 305 @Nonnull 306 private SymbolTable getSymbolTable() { 307 for (SectionHeader header: getSections()) { 308 if (header.getType() == SectionHeader.TYPE_DYNAMIC_SYMBOL_TABLE) { 309 return new SymbolTable(header); 310 } 311 } 312 throw new InvalidOatFileException("Oat file has no symbol table"); 313 } 314 315 @Nonnull 316 private StringTable getSectionNameStringTable() { 317 int index = readUshort(50); 318 if (index == 0) { 319 throw new InvalidOatFileException("There is no section name string table"); 320 } 321 322 try { 323 return new StringTable(getSections().get(index)); 324 } catch (IndexOutOfBoundsException ex) { 325 throw new InvalidOatFileException("The section index for the section name string table is invalid"); 326 } 327 } 328 329 private abstract class SectionHeader { 330 protected final int offset; 331 public static final int TYPE_DYNAMIC_SYMBOL_TABLE = 11; 332 public SectionHeader(int offset) { this.offset = offset; } 333 @Nonnull public String getName() { return getSectionNameStringTable().getString(readSmallUint(offset)); } 334 public int getType() { return readInt(offset + 4); } 335 public abstract long getAddress(); 336 public abstract int getOffset(); 337 public abstract int getSize(); 338 public abstract int getLink(); 339 public abstract int getEntrySize(); 340 } 341 342 private class SectionHeader32Bit extends SectionHeader { 343 public SectionHeader32Bit(int offset) { super(offset); } 344 @Override public long getAddress() { return readInt(offset + 12) & 0xFFFFFFFFL; } 345 @Override public int getOffset() { return readSmallUint(offset + 16); } 346 @Override public int getSize() { return readSmallUint(offset + 20); } 347 @Override public int getLink() { return readSmallUint(offset + 24); } 348 @Override public int getEntrySize() { return readSmallUint(offset + 36); } 349 } 350 351 private class SectionHeader64Bit extends SectionHeader { 352 public SectionHeader64Bit(int offset) { super(offset); } 353 @Override public long getAddress() { return readLong(offset + 16); } 354 @Override public int getOffset() { return readLongAsSmallUint(offset + 24); } 355 @Override public int getSize() { return readLongAsSmallUint(offset + 32); } 356 @Override public int getLink() { return readSmallUint(offset + 40); } 357 @Override public int getEntrySize() { return readLongAsSmallUint(offset + 56); } 358 } 359 360 class SymbolTable { 361 @Nonnull private final StringTable stringTable; 362 private final int offset; 363 private final int entryCount; 364 private final int entrySize; 365 366 public SymbolTable(@Nonnull SectionHeader header) { 367 try { 368 this.stringTable = new StringTable(getSections().get(header.getLink())); 369 } catch (IndexOutOfBoundsException ex) { 370 throw new InvalidOatFileException("String table section index is invalid"); 371 } 372 this.offset = header.getOffset(); 373 this.entrySize = header.getEntrySize(); 374 this.entryCount = header.getSize() / entrySize; 375 376 if (offset + entryCount * entrySize > buf.length) { 377 throw new InvalidOatFileException("Symbol table extends past end of file"); 378 } 379 } 380 381 @Nonnull 382 public List<Symbol> getSymbols() { 383 return new AbstractList<Symbol>() { 384 @Override public Symbol get(int index) { 385 if (index < 0 || index >= entryCount) { 386 throw new IndexOutOfBoundsException(); 387 } 388 if (is64bit) { 389 return new Symbol64(offset + index * entrySize); 390 } else { 391 return new Symbol32(offset + index * entrySize); 392 } 393 } 394 395 @Override public int size() { 396 return entryCount; 397 } 398 }; 399 } 400 401 public abstract class Symbol { 402 protected final int offset; 403 public Symbol(int offset) { this.offset = offset; } 404 @Nonnull public abstract String getName(); 405 public abstract long getValue(); 406 public abstract int getSize(); 407 public abstract int getSectionIndex(); 408 409 public int getFileOffset() { 410 SectionHeader sectionHeader; 411 try { 412 sectionHeader = getSections().get(getSectionIndex()); 413 } catch (IndexOutOfBoundsException ex) { 414 throw new InvalidOatFileException("Section index for symbol is out of bounds"); 415 } 416 417 long sectionAddress = sectionHeader.getAddress(); 418 int sectionOffset = sectionHeader.getOffset(); 419 int sectionSize = sectionHeader.getSize(); 420 421 long symbolAddress = getValue(); 422 423 if (symbolAddress < sectionAddress || symbolAddress >= sectionAddress + sectionSize) { 424 throw new InvalidOatFileException("symbol address lies outside it's associated section"); 425 } 426 427 long fileOffset = (sectionOffset + (getValue() - sectionAddress)); 428 assert fileOffset <= Integer.MAX_VALUE; 429 return (int)fileOffset; 430 } 431 } 432 433 public class Symbol32 extends Symbol { 434 public Symbol32(int offset) { super(offset); } 435 436 @Nonnull 437 public String getName() { return stringTable.getString(readSmallUint(offset)); } 438 public long getValue() { return readSmallUint(offset + 4); } 439 public int getSize() { return readSmallUint(offset + 8); } 440 public int getSectionIndex() { return readUshort(offset + 14); } 441 } 442 443 public class Symbol64 extends Symbol { 444 public Symbol64(int offset) { super(offset); } 445 446 @Nonnull 447 public String getName() { return stringTable.getString(readSmallUint(offset)); } 448 public long getValue() { return readLong(offset + 8); } 449 public int getSize() { return readLongAsSmallUint(offset + 16); } 450 public int getSectionIndex() { return readUshort(offset + 6); } 451 } 452 } 453 454 private class StringTable { 455 private final int offset; 456 private final int size; 457 458 public StringTable(@Nonnull SectionHeader header) { 459 this.offset = header.getOffset(); 460 this.size = header.getSize(); 461 462 if (offset + size > buf.length) { 463 throw new InvalidOatFileException("String table extends past end of file"); 464 } 465 } 466 467 @Nonnull 468 public String getString(int index) { 469 if (index >= size) { 470 throw new InvalidOatFileException("String index is out of bounds"); 471 } 472 473 int start = offset + index; 474 int end = start; 475 while (buf[end] != 0) { 476 end++; 477 if (end >= offset + size) { 478 throw new InvalidOatFileException("String extends past end of string table"); 479 } 480 } 481 482 return new String(buf, start, end-start, Charset.forName("US-ASCII")); 483 } 484 485 } 486 487 public static class InvalidOatFileException extends RuntimeException { 488 public InvalidOatFileException(String message) { 489 super(message); 490 } 491 } 492 493 public static class NotAnOatFileException extends RuntimeException { 494 public NotAnOatFileException() {} 495 } 496 }