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 OatFile(@onnull byte[] buf)75 public OatFile(@Nonnull byte[] buf) { 76 this(buf, null); 77 } 78 OatFile(@onnull byte[] buf, @Nullable VdexProvider vdexProvider)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 verifyMagic(byte[] buf)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 fromInputStream(@onnull InputStream is)126 public static OatFile fromInputStream(@Nonnull InputStream is) throws IOException { 127 return fromInputStream(is, null); 128 } 129 fromInputStream(@onnull InputStream is, @Nullable VdexProvider vdexProvider)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 getOatVersion()153 public int getOatVersion() { 154 return oatHeader.getVersion(); 155 } 156 isSupportedVersion()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 getBootClassPath()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 getDexFiles()181 public List<OatDexFile> getDexFiles() { 182 return new AbstractForwardSequentialList<OatDexFile>() { 183 @Override public int size() { 184 return oatHeader.getDexFileCount(); 185 } 186 187 @Nonnull @Override public Iterator<OatDexFile> iterator() { 188 return Iterators.transform(new DexEntryIterator(), new Function<DexEntry, OatDexFile>() { 189 @Nullable @Override public OatDexFile apply(DexEntry dexEntry) { 190 return dexEntry.getDexFile(); 191 } 192 }); 193 } 194 }; 195 } 196 197 @Nonnull @Override public List<String> getDexEntryNames() throws IOException { 198 return new AbstractForwardSequentialList<String>() { 199 @Override public int size() { 200 return oatHeader.getDexFileCount(); 201 } 202 203 @Nonnull @Override public Iterator<String> iterator() { 204 return Iterators.transform(new DexEntryIterator(), new Function<DexEntry, String>() { 205 @Nullable @Override public String apply(DexEntry dexEntry) { 206 return dexEntry.entryName; 207 } 208 }); 209 } 210 }; 211 } 212 213 @Nullable @Override public OatDexFile getEntry(@Nonnull String entryName) throws IOException { 214 DexEntryIterator iterator = new DexEntryIterator(); 215 while (iterator.hasNext()) { 216 DexEntry entry = iterator.next(); 217 218 if (entry.entryName.equals(entryName)) { 219 return entry.getDexFile(); 220 } 221 } 222 return null; 223 } 224 225 public class OatDexFile extends DexBackedDexFile implements MultiDexContainer.MultiDexFile { 226 @Nonnull public final String filename; 227 228 public OatDexFile(byte[] buf, int offset, @Nonnull String filename) { 229 super(opcodes, buf, offset); 230 this.filename = filename; 231 } 232 233 @Nonnull @Override public String getEntryName() { 234 return filename; 235 } 236 237 @Nonnull @Override public OatFile getContainer() { 238 return OatFile.this; 239 } 240 241 @Override public boolean hasOdexOpcodes() { 242 return true; 243 } 244 } 245 246 private class OatHeader { 247 private final int headerOffset; 248 private final int keyValueStoreOffset; 249 250 public OatHeader(int offset) { 251 this.headerOffset = offset; 252 if (getVersion() >= 127) { 253 this.keyValueStoreOffset = 19 * 4; 254 } else { 255 this.keyValueStoreOffset = 18 * 4; 256 } 257 } 258 259 public boolean isValid() { 260 for (int i=0; i<OAT_MAGIC.length; i++) { 261 if (buf[headerOffset + i] != OAT_MAGIC[i]) { 262 return false; 263 } 264 } 265 266 for (int i=4; i<7; i++) { 267 if (buf[headerOffset + i] < '0' || buf[headerOffset + i] > '9') { 268 return false; 269 } 270 } 271 272 return buf[headerOffset + 7] == 0; 273 } 274 275 public int getVersion() { 276 return Integer.valueOf(new String(buf, headerOffset + 4, 3)); 277 } 278 279 public int getDexFileCount() { 280 return readSmallUint(headerOffset + 20); 281 } 282 283 public int getKeyValueStoreSize() { 284 if (getVersion() < MIN_OAT_VERSION) { 285 throw new IllegalStateException("Unsupported oat version"); 286 } 287 int fieldOffset = keyValueStoreOffset - 4; 288 return readSmallUint(headerOffset + fieldOffset); 289 } 290 291 public int getHeaderSize() { 292 if (getVersion() < MIN_OAT_VERSION) { 293 throw new IllegalStateException("Unsupported oat version"); 294 } 295 return keyValueStoreOffset + getKeyValueStoreSize(); 296 } 297 298 @Nullable 299 public String getKeyValue(@Nonnull String key) { 300 int size = getKeyValueStoreSize(); 301 302 int offset = headerOffset + keyValueStoreOffset; 303 int endOffset = offset + size; 304 305 while (offset < endOffset) { 306 int keyStartOffset = offset; 307 while (offset < endOffset && buf[offset] != '\0') { 308 offset++; 309 } 310 if (offset >= endOffset) { 311 throw new InvalidOatFileException("Oat file contains truncated key value store"); 312 } 313 int keyEndOffset = offset; 314 315 String k = new String(buf, keyStartOffset, keyEndOffset - keyStartOffset); 316 if (k.equals(key)) { 317 int valueStartOffset = ++offset; 318 while (offset < endOffset && buf[offset] != '\0') { 319 offset++; 320 } 321 if (offset >= endOffset) { 322 throw new InvalidOatFileException("Oat file contains truncated key value store"); 323 } 324 int valueEndOffset = offset; 325 return new String(buf, valueStartOffset, valueEndOffset - valueStartOffset); 326 } 327 offset++; 328 } 329 return null; 330 } 331 332 public int getDexListStart() { 333 if (getVersion() >= 127) { 334 return headerOffset + readSmallUint(headerOffset + (6 * 4)); 335 } else { 336 return headerOffset + getHeaderSize(); 337 } 338 } 339 } 340 341 @Nonnull 342 private List<SectionHeader> getSections() { 343 final int offset; 344 final int entrySize; 345 final int entryCount; 346 if (is64bit) { 347 offset = readLongAsSmallUint(40); 348 entrySize = readUshort(58); 349 entryCount = readUshort(60); 350 } else { 351 offset = readSmallUint(32); 352 entrySize = readUshort(46); 353 entryCount = readUshort(48); 354 } 355 356 if (offset + (entrySize * entryCount) > buf.length) { 357 throw new InvalidOatFileException("The ELF section headers extend past the end of the file"); 358 } 359 360 return new AbstractList<SectionHeader>() { 361 @Override public SectionHeader get(int index) { 362 if (index < 0 || index >= entryCount) { 363 throw new IndexOutOfBoundsException(); 364 } 365 if (is64bit) { 366 return new SectionHeader64Bit(offset + (index * entrySize)); 367 } else { 368 return new SectionHeader32Bit(offset + (index * entrySize)); 369 } 370 } 371 372 @Override public int size() { 373 return entryCount; 374 } 375 }; 376 } 377 378 @Nonnull 379 private SymbolTable getSymbolTable() { 380 for (SectionHeader header: getSections()) { 381 if (header.getType() == SectionHeader.TYPE_DYNAMIC_SYMBOL_TABLE) { 382 return new SymbolTable(header); 383 } 384 } 385 throw new InvalidOatFileException("Oat file has no symbol table"); 386 } 387 388 @Nonnull 389 private StringTable getSectionNameStringTable() { 390 int index = readUshort(50); 391 if (index == 0) { 392 throw new InvalidOatFileException("There is no section name string table"); 393 } 394 395 try { 396 return new StringTable(getSections().get(index)); 397 } catch (IndexOutOfBoundsException ex) { 398 throw new InvalidOatFileException("The section index for the section name string table is invalid"); 399 } 400 } 401 402 private abstract class SectionHeader { 403 protected final int offset; 404 public static final int TYPE_DYNAMIC_SYMBOL_TABLE = 11; 405 public SectionHeader(int offset) { this.offset = offset; } 406 @Nonnull public String getName() { return getSectionNameStringTable().getString(readSmallUint(offset)); } 407 public int getType() { return readInt(offset + 4); } 408 public abstract long getAddress(); 409 public abstract int getOffset(); 410 public abstract int getSize(); 411 public abstract int getLink(); 412 public abstract int getEntrySize(); 413 } 414 415 private class SectionHeader32Bit extends SectionHeader { 416 public SectionHeader32Bit(int offset) { super(offset); } 417 @Override public long getAddress() { return readInt(offset + 12) & 0xFFFFFFFFL; } 418 @Override public int getOffset() { return readSmallUint(offset + 16); } 419 @Override public int getSize() { return readSmallUint(offset + 20); } 420 @Override public int getLink() { return readSmallUint(offset + 24); } 421 @Override public int getEntrySize() { return readSmallUint(offset + 36); } 422 } 423 424 private class SectionHeader64Bit extends SectionHeader { 425 public SectionHeader64Bit(int offset) { super(offset); } 426 @Override public long getAddress() { return readLong(offset + 16); } 427 @Override public int getOffset() { return readLongAsSmallUint(offset + 24); } 428 @Override public int getSize() { return readLongAsSmallUint(offset + 32); } 429 @Override public int getLink() { return readSmallUint(offset + 40); } 430 @Override public int getEntrySize() { return readLongAsSmallUint(offset + 56); } 431 } 432 433 class SymbolTable { 434 @Nonnull private final StringTable stringTable; 435 private final int offset; 436 private final int entryCount; 437 private final int entrySize; 438 439 public SymbolTable(@Nonnull SectionHeader header) { 440 try { 441 this.stringTable = new StringTable(getSections().get(header.getLink())); 442 } catch (IndexOutOfBoundsException ex) { 443 throw new InvalidOatFileException("String table section index is invalid"); 444 } 445 this.offset = header.getOffset(); 446 this.entrySize = header.getEntrySize(); 447 this.entryCount = header.getSize() / entrySize; 448 449 if (offset + entryCount * entrySize > buf.length) { 450 throw new InvalidOatFileException("Symbol table extends past end of file"); 451 } 452 } 453 454 @Nonnull 455 public List<Symbol> getSymbols() { 456 return new AbstractList<Symbol>() { 457 @Override public Symbol get(int index) { 458 if (index < 0 || index >= entryCount) { 459 throw new IndexOutOfBoundsException(); 460 } 461 if (is64bit) { 462 return new Symbol64(offset + index * entrySize); 463 } else { 464 return new Symbol32(offset + index * entrySize); 465 } 466 } 467 468 @Override public int size() { 469 return entryCount; 470 } 471 }; 472 } 473 474 public abstract class Symbol { 475 protected final int offset; 476 public Symbol(int offset) { this.offset = offset; } 477 @Nonnull public abstract String getName(); 478 public abstract long getValue(); 479 public abstract int getSize(); 480 public abstract int getSectionIndex(); 481 482 public int getFileOffset() { 483 SectionHeader sectionHeader; 484 try { 485 sectionHeader = getSections().get(getSectionIndex()); 486 } catch (IndexOutOfBoundsException ex) { 487 throw new InvalidOatFileException("Section index for symbol is out of bounds"); 488 } 489 490 long sectionAddress = sectionHeader.getAddress(); 491 int sectionOffset = sectionHeader.getOffset(); 492 int sectionSize = sectionHeader.getSize(); 493 494 long symbolAddress = getValue(); 495 496 if (symbolAddress < sectionAddress || symbolAddress >= sectionAddress + sectionSize) { 497 throw new InvalidOatFileException("symbol address lies outside it's associated section"); 498 } 499 500 long fileOffset = (sectionOffset + (getValue() - sectionAddress)); 501 assert fileOffset <= Integer.MAX_VALUE; 502 return (int)fileOffset; 503 } 504 } 505 506 public class Symbol32 extends Symbol { 507 public Symbol32(int offset) { super(offset); } 508 509 @Nonnull 510 public String getName() { return stringTable.getString(readSmallUint(offset)); } 511 public long getValue() { return readSmallUint(offset + 4); } 512 public int getSize() { return readSmallUint(offset + 8); } 513 public int getSectionIndex() { return readUshort(offset + 14); } 514 } 515 516 public class Symbol64 extends Symbol { 517 public Symbol64(int offset) { super(offset); } 518 519 @Nonnull 520 public String getName() { return stringTable.getString(readSmallUint(offset)); } 521 public long getValue() { return readLong(offset + 8); } 522 public int getSize() { return readLongAsSmallUint(offset + 16); } 523 public int getSectionIndex() { return readUshort(offset + 6); } 524 } 525 } 526 527 private class StringTable { 528 private final int offset; 529 private final int size; 530 531 public StringTable(@Nonnull SectionHeader header) { 532 this.offset = header.getOffset(); 533 this.size = header.getSize(); 534 535 if (offset + size > buf.length) { 536 throw new InvalidOatFileException("String table extends past end of file"); 537 } 538 } 539 540 @Nonnull 541 public String getString(int index) { 542 if (index >= size) { 543 throw new InvalidOatFileException("String index is out of bounds"); 544 } 545 546 int start = offset + index; 547 int end = start; 548 while (buf[end] != 0) { 549 end++; 550 if (end >= offset + size) { 551 throw new InvalidOatFileException("String extends past end of string table"); 552 } 553 } 554 555 return new String(buf, start, end-start, Charset.forName("US-ASCII")); 556 } 557 } 558 559 private class DexEntry { 560 public final String entryName; 561 public final byte[] buf; 562 public final int dexOffset; 563 564 565 public DexEntry(String entryName, byte[] buf, int dexOffset) { 566 this.entryName = entryName; 567 this.buf = buf; 568 this.dexOffset = dexOffset; 569 } 570 571 public OatDexFile getDexFile() { 572 return new OatDexFile(buf, dexOffset, entryName); 573 } 574 } 575 576 private class DexEntryIterator implements Iterator<DexEntry> { 577 int index = 0; 578 int offset = oatHeader.getDexListStart(); 579 580 @Override public boolean hasNext() { 581 return index < oatHeader.getDexFileCount(); 582 } 583 584 @Override public DexEntry next() { 585 int filenameLength = readSmallUint(offset); 586 offset += 4; 587 588 // TODO: what is the correct character encoding? 589 String filename = new String(buf, offset, filenameLength, Charset.forName("US-ASCII")); 590 offset += filenameLength; 591 592 offset += 4; // checksum 593 594 int dexOffset = readSmallUint(offset); 595 offset += 4; 596 597 byte[] buf; 598 if (getOatVersion() >= 87 && vdexProvider != null && vdexProvider.getVdex() != null) { 599 buf = vdexProvider.getVdex(); 600 } else { 601 buf = OatFile.this.buf; 602 dexOffset += oatHeader.headerOffset; 603 } 604 605 if (getOatVersion() >= 75) { 606 offset += 4; // offset to class offsets table 607 } 608 if (getOatVersion() >= 73) { 609 offset += 4; // lookup table offset 610 } 611 if (getOatVersion() >= 131) { 612 offset += 4; // dex sections layout offset 613 } 614 if (getOatVersion() >= 127) { 615 offset += 4; // method bss mapping offset 616 } 617 if (getOatVersion() < 75) { 618 // prior to 75, the class offsets are included here directly 619 int classCount = readSmallUint(dexOffset + HeaderItem.CLASS_COUNT_OFFSET); 620 offset += 4 * classCount; 621 } 622 623 index++; 624 625 return new DexEntry(filename, buf, dexOffset); 626 } 627 628 @Override public void remove() { 629 throw new UnsupportedOperationException(); 630 } 631 } 632 633 public static class InvalidOatFileException extends RuntimeException { 634 public InvalidOatFileException(String message) { 635 super(message); 636 } 637 } 638 639 public static class NotAnOatFileException extends RuntimeException { 640 public NotAnOatFileException() {} 641 } 642 643 public interface VdexProvider { 644 @Nullable 645 byte[] getVdex(); 646 } 647 }