diff options
author | Ben Gruver <bgruv@google.com> | 2015-07-11 21:38:09 -0700 |
---|---|---|
committer | Ben Gruver <bgruv@google.com> | 2015-09-29 23:40:52 -0700 |
commit | e5266afb14817bd3bc3d780157a41b8785fbacce (patch) | |
tree | 6cb4f3fce1af61f937b551cc68bcd104a4f04ff2 | |
parent | 613c493e9698812c0531acf073bc7ca9e4538eac (diff) | |
download | smali-e5266afb14817bd3bc3d780157a41b8785fbacce.tar.gz |
Add a minimal parser for oat files
-rw-r--r-- | dexlib2/OatVersions.txt | 15 | ||||
-rw-r--r-- | dexlib2/src/main/java/org/jf/dexlib2/dexbacked/OatFile.java | 463 |
2 files changed, 478 insertions, 0 deletions
diff --git a/dexlib2/OatVersions.txt b/dexlib2/OatVersions.txt new file mode 100644 index 00000000..e64eccf6 --- /dev/null +++ b/dexlib2/OatVersions.txt @@ -0,0 +1,15 @@ +7642cfc90fc9c3ebfd8e3b5041915705c93b5cf0 - 56 + - first version with all stability fixes needed for deodexing +14691c5e786e8c2c5734f687e4c96217340771be - 57 +1558b577907b613864e98f05862543557263e864 - 58 +f3251d12dfa387493dbde4c4148a633802f5f7e3 - 59 +706cae36209932f258b2fe2e396f31d2dd7d585e - 58 (revert of f3251d12) +d7cbf8a6629942e7bd315ffae7e1c77b082f3e11 - 60 + - return-void-barrier -> return-void-no-barrier +1412dfa4adcd511902e510fa0c948b168ab5840c - 61 (re-commit of f3251d12) +9d6bf69ad3012a9d843268fdd5325b6719b6d5f2 - 62 +0de1133ba600f299b3d67938f650720d9f859eb2 - 63 +07785bb98dc8bbe192970e0f4c2cafd338a8dc68 - 64 +fa2c054b28d4b540c1b3651401a7a091282a015f - 65 +7070ccd8b6439477eafeea7ed3736645d78e003f - 64 (revert of fa2c054b) +7bf2b4f1d08050f80782217febac55c8cfc5e4ef - 65 (re-commit of fa2c054b)
\ No newline at end of file diff --git a/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/OatFile.java b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/OatFile.java new file mode 100644 index 00000000..157b1668 --- /dev/null +++ b/dexlib2/src/main/java/org/jf/dexlib2/dexbacked/OatFile.java @@ -0,0 +1,463 @@ +/* + * Copyright 2014, Google Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google Inc. nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.jf.dexlib2.dexbacked; + +import com.google.common.io.ByteStreams; +import org.jf.dexlib2.Opcodes; +import org.jf.dexlib2.dexbacked.OatFile.SymbolTable.Symbol; +import org.jf.dexlib2.dexbacked.raw.HeaderItem; +import org.jf.util.AbstractForwardSequentialList; + +import javax.annotation.Nonnull; +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charset; +import java.util.AbstractList; +import java.util.Iterator; +import java.util.List; + +public class OatFile extends BaseDexBuffer { + private static final byte[] ELF_MAGIC = new byte[] { 0x7f, 'E', 'L', 'F' }; + private static final byte[] OAT_MAGIC = new byte[] { 'o', 'a', 't', '\n' }; + private static final int ELF_HEADER_SIZE = 52; + + // These are the "known working" versions that I have manually inspected the source for. + // Later version may or may not work, depending on what changed. + private static final int MIN_OAT_VERSION = 56; + private static final int MAX_OAT_VERSION = 65; + + public static final int UNSUPPORTED = 0; + public static final int SUPPORTED = 1; + public static final int UNKNOWN = 2; + + @Nonnull private final OatHeader oatHeader; + @Nonnull private final Opcodes opcodes; + + public OatFile(@Nonnull Opcodes opcodes, @Nonnull byte[] buf) { + super(buf); + + if (buf.length < ELF_HEADER_SIZE) { + throw new NotAnOatFileException(); + } + + verifyMagic(buf); + + this.opcodes = opcodes; + + OatHeader oatHeader = null; + SymbolTable symbolTable = getSymbolTable(); + for (Symbol symbol: symbolTable.getSymbols()) { + if (symbol.getName().equals("oatdata")) { + oatHeader = new OatHeader(symbol.getFileOffset()); + break; + } + } + + if (oatHeader == null) { + throw new InvalidOatFileException("Oat file has no oatdata symbol"); + } + this.oatHeader = oatHeader; + + if (!oatHeader.isValid()) { + throw new InvalidOatFileException("Invalid oat magic value"); + } + } + + private static void verifyMagic(byte[] buf) { + for (int i = 0; i < ELF_MAGIC.length; i++) { + if (buf[i] != ELF_MAGIC[i]) { + throw new NotAnOatFileException(); + } + } + } + + @Nonnull + public static OatFile fromInputStream(@Nonnull InputStream is) + throws IOException { + if (!is.markSupported()) { + throw new IllegalArgumentException("InputStream must support mark"); + } + is.mark(4); + byte[] partialHeader = new byte[4]; + try { + ByteStreams.readFully(is, partialHeader); + } catch (EOFException ex) { + throw new NotAnOatFileException(); + } finally { + is.reset(); + } + + verifyMagic(partialHeader); + + is.reset(); + + byte[] buf = ByteStreams.toByteArray(is); + return new OatFile(new Opcodes(21), buf); + } + + public int isSupportedVersion() { + int version = oatHeader.getVersion(); + if (version < MIN_OAT_VERSION) { + return UNSUPPORTED; + } + if (version <= MAX_OAT_VERSION) { + return SUPPORTED; + } + return UNKNOWN; + } + + @Nonnull + public List<OatDexFile> getDexFiles() { + return new AbstractForwardSequentialList<OatDexFile>() { + @Override public int size() { + return oatHeader.getDexFileCount(); + } + + @Nonnull @Override public Iterator<OatDexFile> iterator() { + return new Iterator<OatDexFile>() { + int index = 0; + int offset = oatHeader.getDexListStart(); + + @Override public boolean hasNext() { + return index < size(); + } + + @Override public OatDexFile next() { + int filenameLength = readSmallUint(offset); + offset += 4; + + // TODO: what is the correct character encoding? + String filename = new String(buf, offset, filenameLength, Charset.forName("US-ASCII")); + offset += filenameLength; + + offset += 4; // checksum + + int dexOffset = readSmallUint(offset) + oatHeader.offset; + offset += 4; + + int classCount = readSmallUint(dexOffset + HeaderItem.CLASS_COUNT_OFFSET); + offset += 4 * classCount; + + index++; + + return new OatDexFile(dexOffset, filename); + } + + @Override public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + + public class OatDexFile extends DexBackedDexFile { + @Nonnull public final String filename; + + public OatDexFile(int offset, @Nonnull String filename) { + super(opcodes, OatFile.this.buf, offset); + this.filename = filename; + } + } + + private class OatHeader { + private final int offset; + + public OatHeader(int offset) { + this.offset = offset; + } + + public boolean isValid() { + for (int i=0; i<OAT_MAGIC.length; i++) { + if (buf[offset + i] != OAT_MAGIC[i]) { + return false; + } + } + + for (int i=4; i<7; i++) { + if (buf[offset + i] < '0' || buf[offset + i] > '9') { + return false; + } + } + + return buf[offset + 7] == 0; + } + + public int getVersion() { + return Integer.valueOf(new String(buf, offset + 4, 3)); + } + + public int getDexFileCount() { + return readSmallUint(offset + 20); + } + + public int getKeyValueStoreSize() { + int version = getVersion(); + if (version < 56) { + throw new IllegalStateException("Unsupported oat version"); + } + int fieldOffset = 17 * 4; + return readSmallUint(offset + fieldOffset); + } + + public int getHeaderSize() { + int version = getVersion(); + if (version >= 56) { + return 18*4 + getKeyValueStoreSize(); + } else { + throw new IllegalStateException("Unsupported oat version"); + } + + } + + public int getDexListStart() { + return offset + getHeaderSize(); + } + } + + @Nonnull + private List<SectionHeader> getSections() { + final int offset = readSmallUint(32); + final int entrySize = readUshort(46); + final int entryCount = readUshort(48); + + if (offset + (entrySize * entryCount) > buf.length) { + throw new InvalidOatFileException("The ELF section headers extend past the end of the file"); + } + + return new AbstractList<SectionHeader>() { + @Override public SectionHeader get(int index) { + if (index < 0 || index >= entryCount) { + throw new IndexOutOfBoundsException(); + } + return new SectionHeader(offset + (index * entrySize)); + } + + @Override public int size() { + return entryCount; + } + }; + } + + @Nonnull + private SymbolTable getSymbolTable() { + for (SectionHeader header: getSections()) { + if (header.getType() == SectionHeader.TYPE_DYNAMIC_SYMBOL_TABLE) { + return new SymbolTable(header); + } + } + throw new InvalidOatFileException("Oat file has no symbol table"); + } + + @Nonnull + private StringTable getSectionNameStringTable() { + int index = readUshort(50); + if (index == 0) { + throw new InvalidOatFileException("There is no section name string table"); + } + + try { + return new StringTable(getSections().get(index)); + } catch (IndexOutOfBoundsException ex) { + throw new InvalidOatFileException("The section index for the section name string table is invalid"); + } + } + + private class SectionHeader { + private final int offset; + + public static final int TYPE_DYNAMIC_SYMBOL_TABLE = 11; + + public SectionHeader(int offset) { + this.offset = offset; + } + + @Nonnull + public String getName() { + return getSectionNameStringTable().getString(readSmallUint(offset)); + } + + public int getType() { + return readInt(offset + 4); + } + + public long getAddress() { + return readInt(offset + 12) & 0xFFFFFFFFL; + } + + public int getOffset() { + return readSmallUint(offset + 16); + } + + public int getSize() { + return readSmallUint(offset + 20); + } + + public int getLink() { + return readSmallUint(offset + 24); + } + + public int getEntrySize() { + return readSmallUint(offset + 36); + } + } + + class SymbolTable { + @Nonnull private final StringTable stringTable; + private final int offset; + private final int entryCount; + private final int entrySize; + + public SymbolTable(@Nonnull SectionHeader header) { + try { + this.stringTable = new StringTable(getSections().get(header.getLink())); + } catch (IndexOutOfBoundsException ex) { + throw new InvalidOatFileException("String table section index is invalid"); + } + this.offset = header.getOffset(); + this.entrySize = header.getEntrySize(); + this.entryCount = header.getSize() / entrySize; + + if (offset + entryCount * entrySize > buf.length) { + throw new InvalidOatFileException("Symbol table extends past end of file"); + } + } + + @Nonnull + public List<Symbol> getSymbols() { + return new AbstractList<Symbol>() { + @Override public Symbol get(int index) { + if (index < 0 || index >= entryCount) { + throw new IndexOutOfBoundsException(); + } + return new Symbol(offset + index * entrySize); + } + + @Override public int size() { + return entryCount; + } + }; + } + + public class Symbol { + private final int offset; + + public Symbol(int offset) { + this.offset = offset; + } + + @Nonnull + public String getName() { + return stringTable.getString(readSmallUint(offset)); + } + + public int getValue() { + return readSmallUint(offset + 4); + } + + public int getSize() { + return readSmallUint(offset + 8); + } + + public int getSectionIndex() { + return readUshort(offset + 14); + } + + public int getFileOffset() { + SectionHeader sectionHeader; + try { + sectionHeader = getSections().get(getSectionIndex()); + } catch (IndexOutOfBoundsException ex) { + throw new InvalidOatFileException("Section index for symbol is out of bounds"); + } + + long sectionAddress = sectionHeader.getAddress(); + int sectionOffset = sectionHeader.getOffset(); + int sectionSize = sectionHeader.getSize(); + + long symbolAddress = getValue(); + + if (symbolAddress < sectionAddress || symbolAddress >= sectionAddress + sectionSize) { + throw new InvalidOatFileException("symbol address lies outside it's associated section"); + } + + long fileOffset = (sectionOffset + (getValue() - sectionAddress)); + assert fileOffset <= Integer.MAX_VALUE; + return (int)fileOffset; + } + } + } + + private class StringTable { + private final int offset; + private final int size; + + public StringTable(@Nonnull SectionHeader header) { + this.offset = header.getOffset(); + this.size = header.getSize(); + + if (offset + size > buf.length) { + throw new InvalidOatFileException("String table extends past end of file"); + } + } + + @Nonnull + public String getString(int index) { + if (index >= size) { + throw new InvalidOatFileException("String index is out of bounds"); + } + + int start = offset + index; + int end = start; + while (buf[end] != 0) { + end++; + if (end >= offset + size) { + throw new InvalidOatFileException("String extends past end of string table"); + } + } + + return new String(buf, start, end-start, Charset.forName("US-ASCII")); + } + + } + + public static class InvalidOatFileException extends RuntimeException { + public InvalidOatFileException(String message) { + super(message); + } + } + + public static class NotAnOatFileException extends RuntimeException { + public NotAnOatFileException() {} + } +}
\ No newline at end of file |