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.base.Function; 35 import com.google.common.collect.ImmutableList; 36 import com.google.common.collect.Iterators; 37 import com.google.common.io.ByteStreams; 38 import org.jf.dexlib2.Opcodes; 39 import org.jf.dexlib2.dexbacked.OatFile.OatDexFile; 40 import org.jf.dexlib2.dexbacked.OatFile.SymbolTable.Symbol; 41 import org.jf.dexlib2.dexbacked.raw.HeaderItem; 42 import org.jf.dexlib2.iface.MultiDexContainer; 43 import org.jf.util.AbstractForwardSequentialList; 44 45 import javax.annotation.Nonnull; 46 import javax.annotation.Nullable; 47 import java.io.EOFException; 48 import java.io.IOException; 49 import java.io.InputStream; 50 import java.nio.charset.Charset; 51 import java.util.AbstractList; 52 import java.util.Arrays; 53 import java.util.Iterator; 54 import java.util.List; 55 56 public class OatFile extends BaseDexBuffer implements MultiDexContainer<OatDexFile> { 57 private static final byte[] ELF_MAGIC = new byte[] { 0x7f, 'E', 'L', 'F' }; 58 private static final byte[] OAT_MAGIC = new byte[] { 'o', 'a', 't', '\n' }; 59 private static final int MIN_ELF_HEADER_SIZE = 52; 60 61 // These are the "known working" versions that I have manually inspected the source for. 62 // Later version may or may not work, depending on what changed. 63 private static final int MIN_OAT_VERSION = 56; 64 private static final int MAX_OAT_VERSION = 86; 65 66 public static final int UNSUPPORTED = 0; 67 public static final int SUPPORTED = 1; 68 public static final int UNKNOWN = 2; 69 70 private final boolean is64bit; 71 @Nonnull private final OatHeader oatHeader; 72 @Nonnull private final Opcodes opcodes; 73 @Nullable private final VdexProvider vdexProvider; 74 75 public OatFile(@Nonnull byte[] buf) { 76 this(buf, null); 77 } 78 79 public OatFile(@Nonnull byte[] buf, @Nullable VdexProvider vdexProvider) { 80 super(buf); 81 82 if (buf.length < MIN_ELF_HEADER_SIZE) { 83 throw new NotAnOatFileException(); 84 } 85 86 verifyMagic(buf); 87 88 if (buf[4] == 1) { 89 is64bit = false; 90 } else if (buf[4] == 2) { 91 is64bit = true; 92 } else { 93 throw new InvalidOatFileException(String.format("Invalid word-size value: %x", buf[5])); 94 } 95 96 OatHeader oatHeader = null; 97 SymbolTable symbolTable = getSymbolTable(); 98 for (Symbol symbol: symbolTable.getSymbols()) { 99 if (symbol.getName().equals("oatdata")) { 100 oatHeader = new OatHeader(symbol.getFileOffset()); 101 break; 102 } 103 } 104 105 if (oatHeader == null) { 106 throw new InvalidOatFileException("Oat file has no oatdata symbol"); 107 } 108 this.oatHeader = oatHeader; 109 110 if (!oatHeader.isValid()) { 111 throw new InvalidOatFileException("Invalid oat magic value"); 112 } 113 114 this.opcodes = Opcodes.forArtVersion(oatHeader.getVersion()); 115 this.vdexProvider = vdexProvider; 116 } 117 118 private static void verifyMagic(byte[] buf) { 119 for (int i = 0; i < ELF_MAGIC.length; i++) { 120 if (buf[i] != ELF_MAGIC[i]) { 121 throw new NotAnOatFileException(); 122 } 123 } 124 } 125 126 public static OatFile fromInputStream(@Nonnull InputStream is) throws IOException { 127 return fromInputStream(is, null); 128 } 129 130 public static OatFile fromInputStream(@Nonnull InputStream is, @Nullable VdexProvider vdexProvider) 131 throws IOException { 132 if (!is.markSupported()) { 133 throw new IllegalArgumentException("InputStream must support mark"); 134 } 135 is.mark(4); 136 byte[] partialHeader = new byte[4]; 137 try { 138 ByteStreams.readFully(is, partialHeader); 139 } catch (EOFException ex) { 140 throw new NotAnOatFileException(); 141 } finally { 142 is.reset(); 143 } 144 145 verifyMagic(partialHeader); 146 147 is.reset(); 148 149 byte[] buf = ByteStreams.toByteArray(is); 150 return new OatFile(buf, vdexProvider); 151 } 152 153 public int getOatVersion() { 154 return oatHeader.getVersion(); 155 } 156 157 public int isSupportedVersion() { 158 int version = getOatVersion(); 159 if (version < MIN_OAT_VERSION) { 160 return UNSUPPORTED; 161 } 162 if (version <= MAX_OAT_VERSION) { 163 return SUPPORTED; 164 } 165 return UNKNOWN; 166 } 167 168 @Nonnull 169 public List<String> getBootClassPath() { 170 if (getOatVersion() < 75) { 171 return ImmutableList.of(); 172 } 173 String bcp = oatHeader.getKeyValue("bootclasspath"); 174 if (bcp == null) { 175 return ImmutableList.of(); 176 } 177 return Arrays.asList(bcp.split(":")); 178 } 179 180 @Nonnull @Override public Opcodes getOpcodes() { 181 return opcodes; 182 } 183 184 @Nonnull 185 public List<OatDexFile> getDexFiles() { 186 return new AbstractForwardSequentialList<OatDexFile>() { 187 @Override public int size() { 188 return oatHeader.getDexFileCount(); 189 } 190 191 @Nonnull @Override public Iterator<OatDexFile> iterator() { 192 return Iterators.transform(new DexEntryIterator(), new Function<DexEntry, OatDexFile>() { 193 @Nullable @Override public OatDexFile apply(DexEntry dexEntry) { 194 return dexEntry.getDexFile(); 195 } 196 }); 197 } 198 }; 199 } 200 201 @Nonnull @Override public List<String> getDexEntryNames() throws IOException { 202 return new AbstractForwardSequentialList<String>() { 203 @Override public int size() { 204 return oatHeader.getDexFileCount(); 205 } 206 207 @Nonnull @Override public Iterator<String> iterator() { 208 return Iterators.transform(new DexEntryIterator(), new Function<DexEntry, String>() { 209 @Nullable @Override public String apply(DexEntry dexEntry) { 210 return dexEntry.entryName; 211 } 212 }); 213 } 214 }; 215 } 216 217 @Nullable @Override public OatDexFile getEntry(@Nonnull String entryName) throws IOException { 218 DexEntryIterator iterator = new DexEntryIterator(); 219 while (iterator.hasNext()) { 220 DexEntry entry = iterator.next(); 221 222 if (entry.entryName.equals(entryName)) { 223 return entry.getDexFile(); 224 } 225 } 226 return null; 227 } 228 229 public class OatDexFile extends DexBackedDexFile implements MultiDexContainer.MultiDexFile { 230 @Nonnull public final String filename; 231 232 public OatDexFile(byte[] buf, int offset, @Nonnull String filename) { 233 super(opcodes, buf, offset); 234 this.filename = filename; 235 } 236 237 @Nonnull @Override public String getEntryName() { 238 return filename; 239 } 240 241 @Nonnull @Override public OatFile getContainer() { 242 return OatFile.this; 243 } 244 245 @Override public boolean hasOdexOpcodes() { 246 return true; 247 } 248 } 249 250 private class OatHeader { 251 private final int headerOffset; 252 253 public OatHeader(int offset) { 254 this.headerOffset = offset; 255 } 256 257 public boolean isValid() { 258 for (int i=0; i<OAT_MAGIC.length; i++) { 259 if (buf[headerOffset + i] != OAT_MAGIC[i]) { 260 return false; 261 } 262 } 263 264 for (int i=4; i<7; i++) { 265 if (buf[headerOffset + i] < '0' || buf[headerOffset + i] > '9') { 266 return false; 267 } 268 } 269 270 return buf[headerOffset + 7] == 0; 271 } 272 273 public int getVersion() { 274 return Integer.valueOf(new String(buf, headerOffset + 4, 3)); 275 } 276 277 public int getDexFileCount() { 278 return readSmallUint(headerOffset + 20); 279 } 280 281 public int getKeyValueStoreSize() { 282 if (getVersion() < MIN_OAT_VERSION) { 283 throw new IllegalStateException("Unsupported oat version"); 284 } 285 int fieldOffset = 17 * 4; 286 return readSmallUint(headerOffset + fieldOffset); 287 } 288 289 public int getHeaderSize() { 290 if (getVersion() < MIN_OAT_VERSION) { 291 throw new IllegalStateException("Unsupported oat version"); 292 } 293 return 18*4 + getKeyValueStoreSize(); 294 } 295 296 @Nullable 297 public String getKeyValue(@Nonnull String key) { 298 int size = getKeyValueStoreSize(); 299 300 int offset = headerOffset + 18 * 4; 301 int endOffset = offset + size; 302 303 while (offset < endOffset) { 304 int keyStartOffset = offset; 305 while (offset < endOffset && buf[offset] != '\0') { 306 offset++; 307 } 308 if (offset >= endOffset) { 309 throw new InvalidOatFileException("Oat file contains truncated key value store"); 310 } 311 int keyEndOffset = offset; 312 313 String k = new String(buf, keyStartOffset, keyEndOffset - keyStartOffset); 314 if (k.equals(key)) { 315 int valueStartOffset = ++offset; 316 while (offset < endOffset && buf[offset] != '\0') { 317 offset++; 318 } 319 if (offset >= endOffset) { 320 throw new InvalidOatFileException("Oat file contains truncated key value store"); 321 } 322 int valueEndOffset = offset; 323 return new String(buf, valueStartOffset, valueEndOffset - valueStartOffset); 324 } 325 offset++; 326 } 327 return null; 328 } 329 330 public int getDexListStart() { 331 return headerOffset + getHeaderSize(); 332 } 333 } 334 335 @Nonnull 336 private List<SectionHeader> getSections() { 337 final int offset; 338 final int entrySize; 339 final int entryCount; 340 if (is64bit) { 341 offset = readLongAsSmallUint(40); 342 entrySize = readUshort(58); 343 entryCount = readUshort(60); 344 } else { 345 offset = readSmallUint(32); 346 entrySize = readUshort(46); 347 entryCount = readUshort(48); 348 } 349 350 if (offset + (entrySize * entryCount) > buf.length) { 351 throw new InvalidOatFileException("The ELF section headers extend past the end of the file"); 352 } 353 354 return new AbstractList<SectionHeader>() { 355 @Override public SectionHeader get(int index) { 356 if (index < 0 || index >= entryCount) { 357 throw new IndexOutOfBoundsException(); 358 } 359 if (is64bit) { 360 return new SectionHeader64Bit(offset + (index * entrySize)); 361 } else { 362 return new SectionHeader32Bit(offset + (index * entrySize)); 363 } 364 } 365 366 @Override public int size() { 367 return entryCount; 368 } 369 }; 370 } 371 372 @Nonnull 373 private SymbolTable getSymbolTable() { 374 for (SectionHeader header: getSections()) { 375 if (header.getType() == SectionHeader.TYPE_DYNAMIC_SYMBOL_TABLE) { 376 return new SymbolTable(header); 377 } 378 } 379 throw new InvalidOatFileException("Oat file has no symbol table"); 380 } 381 382 @Nonnull 383 private StringTable getSectionNameStringTable() { 384 int index = readUshort(50); 385 if (index == 0) { 386 throw new InvalidOatFileException("There is no section name string table"); 387 } 388 389 try { 390 return new StringTable(getSections().get(index)); 391 } catch (IndexOutOfBoundsException ex) { 392 throw new InvalidOatFileException("The section index for the section name string table is invalid"); 393 } 394 } 395 396 private abstract class SectionHeader { 397 protected final int offset; 398 public static final int TYPE_DYNAMIC_SYMBOL_TABLE = 11; 399 public SectionHeader(int offset) { this.offset = offset; } 400 @Nonnull public String getName() { return getSectionNameStringTable().getString(readSmallUint(offset)); } 401 public int getType() { return readInt(offset + 4); } 402 public abstract long getAddress(); 403 public abstract int getOffset(); 404 public abstract int getSize(); 405 public abstract int getLink(); 406 public abstract int getEntrySize(); 407 } 408 409 private class SectionHeader32Bit extends SectionHeader { 410 public SectionHeader32Bit(int offset) { super(offset); } 411 @Override public long getAddress() { return readInt(offset + 12) & 0xFFFFFFFFL; } 412 @Override public int getOffset() { return readSmallUint(offset + 16); } 413 @Override public int getSize() { return readSmallUint(offset + 20); } 414 @Override public int getLink() { return readSmallUint(offset + 24); } 415 @Override public int getEntrySize() { return readSmallUint(offset + 36); } 416 } 417 418 private class SectionHeader64Bit extends SectionHeader { 419 public SectionHeader64Bit(int offset) { super(offset); } 420 @Override public long getAddress() { return readLong(offset + 16); } 421 @Override public int getOffset() { return readLongAsSmallUint(offset + 24); } 422 @Override public int getSize() { return readLongAsSmallUint(offset + 32); } 423 @Override public int getLink() { return readSmallUint(offset + 40); } 424 @Override public int getEntrySize() { return readLongAsSmallUint(offset + 56); } 425 } 426 427 class SymbolTable { 428 @Nonnull private final StringTable stringTable; 429 private final int offset; 430 private final int entryCount; 431 private final int entrySize; 432 433 public SymbolTable(@Nonnull SectionHeader header) { 434 try { 435 this.stringTable = new StringTable(getSections().get(header.getLink())); 436 } catch (IndexOutOfBoundsException ex) { 437 throw new InvalidOatFileException("String table section index is invalid"); 438 } 439 this.offset = header.getOffset(); 440 this.entrySize = header.getEntrySize(); 441 this.entryCount = header.getSize() / entrySize; 442 443 if (offset + entryCount * entrySize > buf.length) { 444 throw new InvalidOatFileException("Symbol table extends past end of file"); 445 } 446 } 447 448 @Nonnull 449 public List<Symbol> getSymbols() { 450 return new AbstractList<Symbol>() { 451 @Override public Symbol get(int index) { 452 if (index < 0 || index >= entryCount) { 453 throw new IndexOutOfBoundsException(); 454 } 455 if (is64bit) { 456 return new Symbol64(offset + index * entrySize); 457 } else { 458 return new Symbol32(offset + index * entrySize); 459 } 460 } 461 462 @Override public int size() { 463 return entryCount; 464 } 465 }; 466 } 467 468 public abstract class Symbol { 469 protected final int offset; 470 public Symbol(int offset) { this.offset = offset; } 471 @Nonnull public abstract String getName(); 472 public abstract long getValue(); 473 public abstract int getSize(); 474 public abstract int getSectionIndex(); 475 476 public int getFileOffset() { 477 SectionHeader sectionHeader; 478 try { 479 sectionHeader = getSections().get(getSectionIndex()); 480 } catch (IndexOutOfBoundsException ex) { 481 throw new InvalidOatFileException("Section index for symbol is out of bounds"); 482 } 483 484 long sectionAddress = sectionHeader.getAddress(); 485 int sectionOffset = sectionHeader.getOffset(); 486 int sectionSize = sectionHeader.getSize(); 487 488 long symbolAddress = getValue(); 489 490 if (symbolAddress < sectionAddress || symbolAddress >= sectionAddress + sectionSize) { 491 throw new InvalidOatFileException("symbol address lies outside it's associated section"); 492 } 493 494 long fileOffset = (sectionOffset + (getValue() - sectionAddress)); 495 assert fileOffset <= Integer.MAX_VALUE; 496 return (int)fileOffset; 497 } 498 } 499 500 public class Symbol32 extends Symbol { 501 public Symbol32(int offset) { super(offset); } 502 503 @Nonnull 504 public String getName() { return stringTable.getString(readSmallUint(offset)); } 505 public long getValue() { return readSmallUint(offset + 4); } 506 public int getSize() { return readSmallUint(offset + 8); } 507 public int getSectionIndex() { return readUshort(offset + 14); } 508 } 509 510 public class Symbol64 extends Symbol { 511 public Symbol64(int offset) { super(offset); } 512 513 @Nonnull 514 public String getName() { return stringTable.getString(readSmallUint(offset)); } 515 public long getValue() { return readLong(offset + 8); } 516 public int getSize() { return readLongAsSmallUint(offset + 16); } 517 public int getSectionIndex() { return readUshort(offset + 6); } 518 } 519 } 520 521 private class StringTable { 522 private final int offset; 523 private final int size; 524 525 public StringTable(@Nonnull SectionHeader header) { 526 this.offset = header.getOffset(); 527 this.size = header.getSize(); 528 529 if (offset + size > buf.length) { 530 throw new InvalidOatFileException("String table extends past end of file"); 531 } 532 } 533 534 @Nonnull 535 public String getString(int index) { 536 if (index >= size) { 537 throw new InvalidOatFileException("String index is out of bounds"); 538 } 539 540 int start = offset + index; 541 int end = start; 542 while (buf[end] != 0) { 543 end++; 544 if (end >= offset + size) { 545 throw new InvalidOatFileException("String extends past end of string table"); 546 } 547 } 548 549 return new String(buf, start, end-start, Charset.forName("US-ASCII")); 550 } 551 } 552 553 private class DexEntry { 554 public final String entryName; 555 public final byte[] buf; 556 public final int dexOffset; 557 558 559 public DexEntry(String entryName, byte[] buf, int dexOffset) { 560 this.entryName = entryName; 561 this.buf = buf; 562 this.dexOffset = dexOffset; 563 } 564 565 public OatDexFile getDexFile() { 566 return new OatDexFile(buf, dexOffset, entryName); 567 } 568 } 569 570 private class DexEntryIterator implements Iterator<DexEntry> { 571 int index = 0; 572 int offset = oatHeader.getDexListStart(); 573 574 @Override public boolean hasNext() { 575 return index < oatHeader.getDexFileCount(); 576 } 577 578 @Override public DexEntry next() { 579 int filenameLength = readSmallUint(offset); 580 offset += 4; 581 582 // TODO: what is the correct character encoding? 583 String filename = new String(buf, offset, filenameLength, Charset.forName("US-ASCII")); 584 offset += filenameLength; 585 586 offset += 4; // checksum 587 588 int dexOffset = readSmallUint(offset); 589 offset += 4; 590 591 byte[] buf; 592 if (getOatVersion() >= 87 && vdexProvider != null && vdexProvider.getVdex() != null) { 593 buf = vdexProvider.getVdex(); 594 } else { 595 buf = OatFile.this.buf; 596 dexOffset += oatHeader.headerOffset; 597 } 598 599 if (getOatVersion() >= 75) { 600 offset += 4; // offset to class offsets table 601 } 602 if (getOatVersion() >= 73) { 603 offset += 4; // lookup table offset 604 } 605 if (getOatVersion() < 75) { 606 // prior to 75, the class offsets are included here directly 607 int classCount = readSmallUint(dexOffset + HeaderItem.CLASS_COUNT_OFFSET); 608 offset += 4 * classCount; 609 } 610 611 index++; 612 613 return new DexEntry(filename, buf, dexOffset); 614 } 615 616 @Override public void remove() { 617 throw new UnsupportedOperationException(); 618 } 619 } 620 621 public static class InvalidOatFileException extends RuntimeException { 622 public InvalidOatFileException(String message) { 623 super(message); 624 } 625 } 626 627 public static class NotAnOatFileException extends RuntimeException { 628 public NotAnOatFileException() {} 629 } 630 631 public interface VdexProvider { 632 @Nullable 633 byte[] getVdex(); 634 } 635 }