├── .gitignore ├── create-eclipse-project-linux.sh ├── create-eclipse-project-windows.cmd ├── out ├── JebOatPlugin-1.0.12.jar └── JebOatPlugin-1.0.13.jar ├── src └── com │ └── pnf │ └── plugin │ └── oat │ ├── KeyValueStoreDocumentPart.java │ ├── internal │ ├── OAT.java │ ├── DexFile.java │ ├── StreamReader.java │ └── OATFile.java │ ├── OATPlugin.java │ ├── KeyValueStoreDocument.java │ └── OATUnit.java ├── README.md └── scripts ├── generateEclipseProjectFilesForPlugin.py └── build.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .classpath 3 | bin/ -------------------------------------------------------------------------------- /create-eclipse-project-linux.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | python scripts/generateEclipseProjectFilesForPlugin.py $1 -------------------------------------------------------------------------------- /create-eclipse-project-windows.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | python scripts\generateEclipseProjectFilesForPlugin.py %1 3 | -------------------------------------------------------------------------------- /out/JebOatPlugin-1.0.12.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pnfsoftware/jeb-plugin-oat/HEAD/out/JebOatPlugin-1.0.12.jar -------------------------------------------------------------------------------- /out/JebOatPlugin-1.0.13.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pnfsoftware/jeb-plugin-oat/HEAD/out/JebOatPlugin-1.0.13.jar -------------------------------------------------------------------------------- /src/com/pnf/plugin/oat/KeyValueStoreDocumentPart.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JEB Copyright PNF Software, Inc. 3 | * 4 | * https://www.pnfsoftware.com 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.pnf.plugin.oat; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | import com.pnfsoftware.jeb.core.output.table.ITableDocumentPart; 25 | import com.pnfsoftware.jeb.core.output.table.impl.TableRow; 26 | 27 | public class KeyValueStoreDocumentPart implements ITableDocumentPart { 28 | private ArrayList rows; 29 | private int rowIndex; 30 | 31 | public KeyValueStoreDocumentPart(int rowIndex, List rows) { 32 | this.rowIndex = rowIndex; 33 | this.rows = new ArrayList<>(rows); 34 | } 35 | 36 | @Override 37 | public int getFirstRowIndex() { 38 | return rowIndex; 39 | } 40 | 41 | @Override 42 | public List getRows() { 43 | return rows; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/com/pnf/plugin/oat/internal/OAT.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JEB Copyright PNF Software, Inc. 3 | * 4 | * https://www.pnfsoftware.com 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.pnf.plugin.oat.internal; 20 | 21 | /** 22 | * Static constants. 23 | * 24 | */ 25 | public class OAT { 26 | 27 | // Oat magic numbers 28 | public static final byte[] magic = new byte[]{'o', 'a', 't', '\n'}; 29 | 30 | // ISA constants - incomplete list but still useful 31 | public static final int kNone = 0; 32 | public static final int kArm = 1; 33 | public static final int kArm64 = 2; 34 | public static final int kThumb2 = 3; 35 | public static final int kX86 = 4; 36 | public static final int X86_64 = 5; 37 | public static final int kMips = 6; 38 | public static final int kMips64 = 7; 39 | 40 | // For the class headers - not used 41 | public static final int kOatClassAllCompiled = 0; 42 | public static final int kOatClassSomeCompiled = 1; 43 | public static final int kOatClassNoneCompiled = 2; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android OAT Plugin for JEB 2 | 3 | This plugin extracts DEX files from compiled OAT files (ELF) that are used by the Android Runtime. 4 | 5 | Supports OAT versions 39 to 214 (Nov 2021). 6 | 7 | Building from source: adjust the version number and run the `build-xxx` script. 8 | 9 | ## File Format 10 | ``` 11 | OAT HEADER FORMAT: base is version 39, exceptions start in version 45+ 12 | 13 | (all entries are 32-bit words) 14 | magic number ('oat\n') 15 | OAT version ('NNN\0') 16 | checksum of header 17 | ISA 18 | ISA features bitmask 19 | Dex file count 20 | OAT Dex Files Offset // ADDED in v127+ 21 | offset of executable code section 22 | interpreter to interpreter bridge offset // REMOVED in v170+ 23 | interpreter to compile code bridge offset // REMOVED in v170+ 24 | jni dlsym lookup (trampoline) offset 25 | jni dlsym lookup critical trampoline offset // ADDED in v180+ 26 | portable imt conflict trampoline offset // REMOVED in v45+ 27 | portable resolution trampoline offset // REMOVED in v45+ 28 | portable to interpreter bridge offset // REMOVED in v45+ 29 | quick generic jni trampoline offset 30 | quick imt conflict trampoline offset 31 | quick resolution trampoline offset 32 | quick to interpreter bridge offset 33 | nterp trampoline offset // ADDED in v190+ 34 | image patch delta // REMOVED in v162+ 35 | image file location oat checksum // BECOMES "boot image checksum" in v164+ / REMOVED in v166+ 36 | image file location oat data begin // REMOVED in v162+ 37 | key value store length 38 | * key value store - hold some info about compilation 39 | (start of dex headers) 40 | dex file location size 41 | * dex file location path string 42 | dex file location checksum 43 | dex file pointer from start of oatdata 44 | ``` -------------------------------------------------------------------------------- /scripts/generateEclipseProjectFilesForPlugin.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | tplProject = ''' 5 | 6 | %s 7 | 8 | 9 | 10 | 11 | 12 | org.eclipse.jdt.core.javabuilder 13 | 14 | 15 | 16 | 17 | 18 | org.eclipse.jdt.core.javanature 19 | 20 | 21 | ''' 22 | 23 | tplClasspath = ''' 24 | 25 | 26 | 27 | %s 28 | 29 | 30 | ''' 31 | 32 | if __name__ == '__main__': 33 | prjname = os.path.split(os.path.abspath(os.path.dirname(sys.argv[0]) + '/..'))[-1] 34 | print('Project name: %s' % prjname) 35 | 36 | internal = len(sys.argv) > 1 and sys.argv[1] == '-i' 37 | 38 | if 'JEB_HOME' not in os.environ: 39 | print('Set an environment variable JEB_HOME pointing to your JEB folder') 40 | sys.exit(-1) 41 | 42 | jebhome = os.environ['JEB_HOME'] 43 | jebcorepath = os.path.join(jebhome, 'bin/app/jeb.jar') 44 | if not os.path.isfile(jebcorepath): 45 | print('Based on your value of JEB_HOME, jeb.jar was expected at this location, but it was not found: %s' % jebcorepath) 46 | sys.exit(-1) 47 | jebdocpath = os.path.join(jebhome, 'doc/apidoc.zip') 48 | 49 | _Project = tplProject % prjname 50 | with open('.project', 'w') as f: 51 | f.write(_Project) 52 | print('Generated: Eclipse .project file') 53 | 54 | jeblibentry = ''' 55 | 56 | ''' % (jebcorepath, jebdocpath) 57 | if internal: 58 | # FOR INTERNAL USE 59 | jeblibentry = '' 60 | _Classpath = tplClasspath % jeblibentry 61 | with open('.classpath', 'w') as f: 62 | f.write(_Classpath) 63 | print('Generated: Eclipse .classpath file') -------------------------------------------------------------------------------- /src/com/pnf/plugin/oat/internal/DexFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JEB Copyright PNF Software, Inc. 3 | * 4 | * https://www.pnfsoftware.com 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.pnf.plugin.oat.internal; 20 | 21 | import com.pnfsoftware.jeb.util.io.EndianUtil; 22 | import com.pnfsoftware.jeb.util.serialization.annotations.SerConstructor; 23 | import com.pnfsoftware.jeb.util.serialization.annotations.SerId; 24 | 25 | /** 26 | * Wrapper for the bytes in a dexfile pulled from the oatfile 27 | * 28 | */ 29 | public class DexFile extends StreamReader { 30 | @SerId(1) 31 | private byte[] data; 32 | @SerId(2) 33 | private int offset; 34 | @SerId(3) 35 | private int maxSize; 36 | @SerId(4) 37 | private String location; 38 | 39 | @SerConstructor 40 | DexFile() { 41 | } 42 | 43 | public DexFile(byte[] data, int offset, int maxSize, String location) { 44 | this.data = data; 45 | this.offset = offset; 46 | this.maxSize = maxSize; 47 | this.location = location; 48 | } 49 | 50 | // Returns all of the bytes within its bounds 51 | public byte[] getBytes(boolean provideAllBytes) { 52 | int size = maxSize; 53 | if(!provideAllBytes && maxSize >= 0x24) { 54 | // read the DEX header's file offset (may be <0 on corruption) 55 | int expectedFileSize = EndianUtil.littleEndianBytesToInt(data, offset + 0x20); 56 | if(expectedFileSize > 0) { 57 | size = Math.min(maxSize, expectedFileSize); 58 | } 59 | } 60 | byte[] output = new byte[size]; 61 | System.arraycopy(data, offset, output, 0, size); 62 | return output; 63 | } 64 | 65 | public byte[] getBytes() { 66 | return getBytes(true); 67 | } 68 | 69 | // Returns the string location pulled from the oat file 70 | public String getLocation() { 71 | return location; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/com/pnf/plugin/oat/OATPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JEB Copyright PNF Software, Inc. 3 | * 4 | * https://www.pnfsoftware.com 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.pnf.plugin.oat; 20 | 21 | import java.nio.ByteBuffer; 22 | 23 | import com.pnfsoftware.jeb.core.IUnitCreator; 24 | import com.pnfsoftware.jeb.core.PluginInformation; 25 | import com.pnfsoftware.jeb.core.Version; 26 | import com.pnfsoftware.jeb.core.input.IInput; 27 | import com.pnfsoftware.jeb.core.properties.IPropertyDefinitionManager; 28 | import com.pnfsoftware.jeb.core.units.AbstractUnitIdentifier; 29 | import com.pnfsoftware.jeb.core.units.IUnit; 30 | import com.pnfsoftware.jeb.core.units.IUnitProcessor; 31 | 32 | /** 33 | * Android OAT 34 | * 35 | * @author PNF Software 36 | * 37 | */ 38 | public class OATPlugin extends AbstractUnitIdentifier { 39 | static final String TYPE = "OAT"; 40 | 41 | public OATPlugin() { 42 | super(TYPE, 0); 43 | } 44 | 45 | @Override 46 | public PluginInformation getPluginInformation() { 47 | return new PluginInformation("OAT File Unit", 48 | "Plugin to extract Dex files embedded in compiled OAT files that are generated by the ART", 49 | "PNF Software", Version.create(1, 0, 13)); 50 | } 51 | 52 | @Override 53 | public void initialize(IPropertyDefinitionManager parent) { 54 | super.initialize(parent); 55 | } 56 | 57 | @Override 58 | public boolean canIdentify(IInput input, IUnitCreator parent) { 59 | ByteBuffer hdr = input.getHeader(); 60 | if(hdr == null) { 61 | return false; 62 | } 63 | if(input.getCurrentSize() < 0x20) { 64 | return false; 65 | } 66 | if(!checkBytes(input, 0, (byte)'o', (byte)'a', (byte)'t', (byte)'\n')) { 67 | return false; 68 | } 69 | if(hdr.get(7) != 0) { 70 | return false; 71 | } 72 | int v1 = hdr.get(4); 73 | int v2 = hdr.get(5); 74 | int v3 = hdr.get(6); 75 | return v1 >= '0' && v1 <= '9' && v2 >= '0' && v2 <= '9' && v3 >= '0' && v3 <= '9'; 76 | } 77 | 78 | @Override 79 | public IUnit prepare(String name, IInput input, IUnitProcessor unitProcessor, IUnitCreator parent) { 80 | OATUnit unit = new OATUnit(name, input, unitProcessor, parent, pdm); 81 | return unit; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /scripts/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /src/com/pnf/plugin/oat/KeyValueStoreDocument.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JEB Copyright PNF Software, Inc. 3 | * 4 | * https://www.pnfsoftware.com 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.pnf.plugin.oat; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | import com.pnf.plugin.oat.internal.OATFile; 25 | import com.pnfsoftware.jeb.core.events.JebEventSource; 26 | import com.pnfsoftware.jeb.core.output.table.ICellCoordinates; 27 | import com.pnfsoftware.jeb.core.output.table.ITableDocument; 28 | import com.pnfsoftware.jeb.core.output.table.ITableDocumentPart; 29 | import com.pnfsoftware.jeb.core.output.table.impl.Cell; 30 | import com.pnfsoftware.jeb.core.output.table.impl.TableRow; 31 | import com.pnfsoftware.jeb.core.properties.IPropertyManager; 32 | 33 | /** 34 | * View of the keyvalue store in the OAT's header. 35 | * 36 | */ 37 | public class KeyValueStoreDocument extends JebEventSource implements ITableDocument { 38 | List rows; 39 | OATFile oat; 40 | 41 | public KeyValueStoreDocument(OATFile oat) { 42 | this.oat = oat; 43 | rows = new ArrayList<>(); 44 | 45 | String[] keyValueStore = oat.getKeyValueStore().split("\0"); 46 | String key; 47 | String value; 48 | List cells = new ArrayList<>(); 49 | for(int index = 0; index < keyValueStore.length / 2; index++) { 50 | cells = new ArrayList<>(); 51 | key = keyValueStore[index * 2]; 52 | value = keyValueStore[index * 2 + 1]; 53 | // Create column for key and column for value 54 | cells.add(new Cell(key)); 55 | cells.add(new Cell(value)); 56 | rows.add(new TableRow(cells)); 57 | } 58 | } 59 | 60 | @Override 61 | public List getColumnLabels() { 62 | ArrayList output = new ArrayList<>(); 63 | output.add("Key"); 64 | output.add("Value"); 65 | return output; 66 | } 67 | 68 | @Override 69 | public int getRowCount() { 70 | return rows.size(); 71 | } 72 | 73 | @Override 74 | public ITableDocumentPart getTable() { 75 | // Get all rows 76 | return getTablePart(0, rows.size()); 77 | } 78 | 79 | @Override 80 | public ITableDocumentPart getTablePart(int start, int count) { 81 | return new KeyValueStoreDocumentPart(start, rows.subList(start, start + count)); 82 | } 83 | 84 | @Override 85 | public ICellCoordinates addressToCoordinates(String address) { 86 | return null; 87 | } 88 | 89 | @Override 90 | public String coordinatesToAddress(ICellCoordinates coordinates) { 91 | return null; 92 | } 93 | 94 | @Override 95 | public void dispose() { 96 | } 97 | 98 | @Override 99 | public IPropertyManager getPropertyManager() { 100 | return null; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/com/pnf/plugin/oat/internal/StreamReader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JEB Copyright PNF Software, Inc. 3 | * 4 | * https://www.pnfsoftware.com 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.pnf.plugin.oat.internal; 20 | 21 | import java.io.ByteArrayInputStream; 22 | import java.nio.ByteBuffer; 23 | import java.nio.ByteOrder; 24 | 25 | import com.pnfsoftware.jeb.util.logging.GlobalLog; 26 | import com.pnfsoftware.jeb.util.logging.ILogger; 27 | 28 | public class StreamReader { 29 | // Give a logger to all subclasses 30 | protected static final ILogger logger = GlobalLog.getLogger(StreamReader.class); 31 | 32 | // Read an int from the stream at an offset from the current position 33 | // Leaves a mark 34 | protected static int readInt(ByteArrayInputStream stream, int offset) { 35 | stream.mark(0); 36 | stream.skip(offset); 37 | int output = readInt(stream); 38 | stream.reset(); 39 | return output; 40 | } 41 | 42 | // Read an short from the stream at an offset from the current position 43 | // Leaves a mark 44 | protected static short readShort(ByteArrayInputStream stream, int offset) { 45 | stream.mark(0); 46 | stream.skip(offset); 47 | short output = readShort(stream); 48 | stream.reset(); 49 | return output; 50 | } 51 | 52 | // Read an int from the stream 53 | // No mark 54 | protected static int readInt(ByteArrayInputStream stream) { 55 | byte[] temp = new byte[4]; 56 | stream.read(temp, 0, 4); 57 | return ByteBuffer.wrap(temp).order(ByteOrder.LITTLE_ENDIAN).getInt(); 58 | } 59 | 60 | // Read an short from the stream 61 | // No mark 62 | protected static short readShort(ByteArrayInputStream stream) { 63 | byte[] temp = new byte[2]; 64 | stream.read(temp, 0, 2); 65 | return ByteBuffer.wrap(temp).order(ByteOrder.LITTLE_ENDIAN).getShort(); 66 | } 67 | 68 | // Read a string from the stream. Goes until sees null character '\0' 69 | protected static String readString(ByteArrayInputStream stream) { 70 | String output = ""; 71 | char character; 72 | while(stream.available() > 0) { 73 | character = (char)stream.read(); 74 | if(character == 0) 75 | break; 76 | output = output + character; 77 | } 78 | return output; 79 | } 80 | 81 | // Reads a string of given length, ignoring null char 82 | protected static String readString(ByteArrayInputStream stream, int length) { 83 | String output = ""; 84 | char character; 85 | for(int index = 0; index < length; index++) { 86 | character = (char)stream.read(); 87 | output = output + character; 88 | } 89 | return output; 90 | } 91 | 92 | // Extra implementation of checkbytes. Does byte by byte comparison 93 | protected static boolean checkBytes(byte[] data, int offset, byte... checkBytes) { 94 | for(int index = 0; index < checkBytes.length; index++) { 95 | if(data[offset + index] != checkBytes[index]) 96 | return false; 97 | } 98 | return true; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/com/pnf/plugin/oat/OATUnit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JEB Copyright PNF Software, Inc. 3 | * 4 | * https://www.pnfsoftware.com 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.pnf.plugin.oat; 20 | 21 | import java.io.InputStream; 22 | 23 | import com.pnf.plugin.oat.internal.DexFile; 24 | import com.pnf.plugin.oat.internal.OATFile; 25 | import com.pnfsoftware.jeb.core.IUnitCreator; 26 | import com.pnfsoftware.jeb.core.input.BytesInput; 27 | import com.pnfsoftware.jeb.core.input.IInput; 28 | import com.pnfsoftware.jeb.core.output.AbstractTransientUnitRepresentation; 29 | import com.pnfsoftware.jeb.core.output.IGenericDocument; 30 | import com.pnfsoftware.jeb.core.output.IUnitFormatter; 31 | import com.pnfsoftware.jeb.core.output.UnitFormatterUtil; 32 | import com.pnfsoftware.jeb.core.properties.IPropertyDefinitionManager; 33 | import com.pnfsoftware.jeb.core.units.AbstractInteractiveBinaryUnit; 34 | import com.pnfsoftware.jeb.core.units.IUnitProcessor; 35 | import com.pnfsoftware.jeb.util.format.Strings; 36 | import com.pnfsoftware.jeb.util.io.IO; 37 | import com.pnfsoftware.jeb.util.logging.GlobalLog; 38 | import com.pnfsoftware.jeb.util.logging.ILogger; 39 | import com.pnfsoftware.jeb.util.serialization.annotations.Ser; 40 | import com.pnfsoftware.jeb.util.serialization.annotations.SerId; 41 | 42 | @Ser 43 | public class OATUnit extends AbstractInteractiveBinaryUnit { 44 | private static final ILogger logger = GlobalLog.getLogger(OATUnit.class); 45 | 46 | @SerId(1) 47 | private OATFile oat; 48 | 49 | public OATUnit(String name, IInput input, IUnitProcessor unitProcessor, IUnitCreator parent, 50 | IPropertyDefinitionManager pdm) { 51 | super(null, input, OATPlugin.TYPE, name, unitProcessor, parent, pdm); 52 | } 53 | 54 | @Override 55 | public boolean isProcessed() { 56 | return oat != null; 57 | } 58 | 59 | @Override 60 | public boolean process() { 61 | if(isProcessed()) { 62 | return true; 63 | } 64 | 65 | try(InputStream stream = getInput().getStream()) { 66 | byte[] data = IO.readInputStream(stream); 67 | 68 | oat = new OATFile(data); 69 | 70 | for(DexFile dex: oat.getDexFiles()) { 71 | String loc = dex.getLocation(); 72 | if(Strings.isBlank(loc)) { 73 | //loc = "unnamed"; 74 | continue; 75 | } 76 | 77 | addChild(getUnitProcessor().process(loc, new BytesInput(dex.getBytes(false)), this)); 78 | } 79 | } 80 | catch(Exception e) { 81 | logger.catching(e); 82 | return false; 83 | } 84 | 85 | setProcessed(true); 86 | return true; 87 | } 88 | 89 | @Override 90 | public String getDescription() { 91 | String output = super.getDescription(); 92 | output += "\nOAT information:\n"; 93 | output += "- Version: " + oat.getVersion() + "\n"; 94 | output += "- Target ISA: " + oat.getISAString() + "\n"; 95 | output += "- Dex file count: " + oat.getDexFileCount() + "\n"; 96 | output += "- Dex paths:\n"; 97 | for(DexFile dex: oat.getDexFiles()) { 98 | output += " - " + dex.getLocation() + "\n"; 99 | } 100 | return output; 101 | } 102 | 103 | @Override 104 | public IUnitFormatter getFormatter() { 105 | IUnitFormatter formatter = super.getFormatter(); 106 | if(UnitFormatterUtil.getPresentationByIdentifier(formatter, 1) == null) { 107 | formatter.addPresentation(new AbstractTransientUnitRepresentation(1, "OAT KV-Store", true) { 108 | @Override 109 | public IGenericDocument createDocument() { 110 | return new KeyValueStoreDocument(oat); 111 | } 112 | }, false); 113 | } 114 | return formatter; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/com/pnf/plugin/oat/internal/OATFile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JEB Copyright PNF Software, Inc. 3 | * 4 | * https://www.pnfsoftware.com 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | package com.pnf.plugin.oat.internal; 20 | 21 | import java.io.ByteArrayInputStream; 22 | import java.io.IOException; 23 | import java.io.OutputStream; 24 | import java.util.ArrayList; 25 | import java.util.List; 26 | 27 | import com.pnfsoftware.jeb.util.serialization.annotations.Ser; 28 | import com.pnfsoftware.jeb.util.serialization.annotations.SerConstructor; 29 | import com.pnfsoftware.jeb.util.serialization.annotations.SerId; 30 | 31 | /** 32 | * Description of an OAT file. 33 | *

34 | * Description of the OAT header: refer to [art]/runtime/oat.h 35 | *

36 | * Valid until version 214 (2021/11/22). 37 | * 38 | */ 39 | @SuppressWarnings("unused") 40 | @Ser 41 | public class OATFile extends StreamReader { 42 | @SerId(1) 43 | private byte[] magic = new byte[4]; 44 | @SerId(2) 45 | private int version; 46 | @SerId(3) 47 | private int checksum; 48 | @SerId(4) 49 | private int instructionSet; 50 | @SerId(5) 51 | private int instructionSetFeatures; 52 | @SerId(6) 53 | private int dexFileCount; 54 | @SerId(7) 55 | private int executableOffset; 56 | @SerId(8) 57 | private int interpreterToInterpreterBridgeOffset; 58 | @SerId(9) 59 | private int interpreterToCompiledCodeBridgeOffset; 60 | @SerId(10) 61 | private int jniDlsymLookupOffset; 62 | @SerId(11) 63 | private int portableImtConflictTrampolineOffset; 64 | @SerId(12) 65 | private int portableResolutionTrampolineOffset; 66 | @SerId(13) 67 | private int portableToInterpreterBridgeOffset; 68 | @SerId(14) 69 | private int quickGenericJniTrampolineOffset; 70 | @SerId(15) 71 | private int quickImtConflictTrampolineOffset; 72 | @SerId(16) 73 | private int quickResolutionTrampolineOffset; 74 | @SerId(17) 75 | private int quickToInterpreterBridgeOffset; 76 | @SerId(18) 77 | private int imagePatchDelta; 78 | @SerId(19) 79 | private int imageFileLocationOatChecksum; 80 | @SerId(20) 81 | private int imageFileLocationOatDataBegin; 82 | @SerId(21) 83 | private int keyValueStoreSize; 84 | @SerId(22) 85 | private byte[] keyValueStore; 86 | @SerId(23) 87 | private List dexFiles = new ArrayList<>(); 88 | @SerId(24) 89 | private int oatDexFilesOffset; 90 | @SerId(25) 91 | private int jniDlsymLookupCriticalTrampolineOffset; 92 | @SerId(26) 93 | private int nterpTrampolineOffset; 94 | 95 | @SerConstructor 96 | protected OATFile() { 97 | } 98 | 99 | @SuppressWarnings("resource") 100 | public OATFile(byte[] data) { 101 | ByteArrayInputStream stream = new ByteArrayInputStream(data); 102 | int offset = 0; 103 | 104 | // Compare the magic numbers at start of oat 105 | stream.read(magic, 0, 4); 106 | if(!checkBytes(data, 0, OAT.magic)) { 107 | throw new IllegalArgumentException("Magic number does not match"); 108 | } 109 | 110 | // Get oat format version. Parser designed on version 39, which is 111 | // compatible 112 | // at least until 45 (current at time of writing) 113 | version = Integer.parseInt(new String(readString(stream)).replaceFirst("^0+(?!$)", "").trim()); 114 | 115 | // Not useful. Does not represent the header's data alone 116 | checksum = readInt(stream); 117 | 118 | if(version < 39) { 119 | throw new IllegalArgumentException("Unsupported OAT version " + version); 120 | } 121 | 122 | if(version > 214) { 123 | logger.warn("OAT version %d not officially unsupported, unexpected behavior may happen", version); 124 | } 125 | 126 | // OAT HEADER FORMAT: base is version 39, exceptions start in version 45+ 127 | // 128 | // (all entries are 32-bit words) 129 | // magic number ('oat\n') 130 | // OAT version ('NNN\0') 131 | // checksum of header 132 | // ISA 133 | // ISA features bitmask 134 | // Dex file count 135 | // OAT Dex Files Offset // ADDED in v127+ 136 | // offset of executable code section 137 | // interpreter to interpreter bridge offset // REMOVED in v170+ 138 | // interpreter to compile code bridge offset // REMOVED in v170+ 139 | // jni dlsym lookup (trampoline) offset 140 | // jni dlsym lookup critical trampoline offset // ADDED in v180+ 141 | // portable imt conflict trampoline offset // REMOVED in v45+ 142 | // portable resolution trampoline offset // REMOVED in v45+ 143 | // portable to interpreter bridge offset // REMOVED in v45+ 144 | // quick generic jni trampoline offset 145 | // quick imt conflict trampoline offset 146 | // quick resolution trampoline offset 147 | // quick to interpreter bridge offset 148 | // nterp trampoline offset // ADDED in v190+ 149 | // image patch delta // REMOVED in v162+ 150 | // image file location oat checksum // BECOMES "boot image checksum" in v164+ / REMOVED in v166+ 151 | // image file location oat data begin // REMOVED in v162+ 152 | // key value store length 153 | // * key value store - hold some info about compilation 154 | // (start of dex headers) 155 | // dex file location size 156 | // * dex file location path string 157 | // dex file location checksum 158 | // dex file pointer from start of oatdata 159 | 160 | // ISA - see OAT.java 161 | instructionSet = readInt(stream); 162 | instructionSetFeatures = readInt(stream); 163 | dexFileCount = readInt(stream); 164 | if(version >= 127) { 165 | oatDexFilesOffset = readInt(stream); 166 | } 167 | executableOffset = readInt(stream); 168 | if(version < 170) { 169 | interpreterToInterpreterBridgeOffset = readInt(stream); 170 | interpreterToCompiledCodeBridgeOffset = readInt(stream); 171 | } 172 | jniDlsymLookupOffset = readInt(stream); 173 | if(version >= 180) { 174 | jniDlsymLookupCriticalTrampolineOffset = readInt(stream); 175 | } 176 | if(version < 45) { 177 | portableImtConflictTrampolineOffset = readInt(stream); 178 | portableResolutionTrampolineOffset = readInt(stream); 179 | portableToInterpreterBridgeOffset = readInt(stream); 180 | } 181 | quickGenericJniTrampolineOffset = readInt(stream); 182 | quickImtConflictTrampolineOffset = readInt(stream); 183 | quickResolutionTrampolineOffset = readInt(stream); 184 | quickToInterpreterBridgeOffset = readInt(stream); 185 | if(version >= 190) { 186 | nterpTrampolineOffset = readInt(stream); 187 | } 188 | if(version < 162) { 189 | imagePatchDelta = readInt(stream); 190 | } 191 | if(version < 166) { 192 | imageFileLocationOatChecksum = readInt(stream); 193 | } 194 | if(version < 162) { 195 | imageFileLocationOatDataBegin = readInt(stream); 196 | } 197 | 198 | keyValueStoreSize = readInt(stream); 199 | if(keyValueStoreSize >= 200*1024*1024) { 200 | // safety, most likely would be the result of an unsupported oat version 201 | throw new IllegalArgumentException("KeyValue store is too large, parsing failed!"); 202 | } 203 | keyValueStore = new byte[keyValueStoreSize]; 204 | try { 205 | if(stream.read(keyValueStore) != keyValueStoreSize) { 206 | logger.warn("The KeyValue store was not fully read, the input file may be truncated"); 207 | } 208 | } 209 | catch(IOException ex) { 210 | throw new RuntimeException(ex); 211 | } 212 | 213 | // After the key value store, there is a list of dex file headers 214 | // that give information about each dex file 215 | byte[] headerBytes = new byte[data.length - stream.available()]; 216 | int dexFileLocationSize; 217 | String dexFileLocation; 218 | int dexFileLocationChecksum; 219 | int dexFilePointer; 220 | int dexFileOffset; 221 | int classes_offsets_size; 222 | int current = 0; 223 | 224 | // Loop through the dex file headers. Will be dexFileCount of them 225 | for(int idex = 0; idex < dexFileCount; idex++) { 226 | // Loop through dex file headers 227 | 228 | // Number of characters in dex files location data 229 | dexFileLocationSize = readInt(stream); 230 | if(dexFileLocationSize > 0x10000) { 231 | logger.warning("OAT File entry format appears to be unsupported"); 232 | break; 233 | } 234 | 235 | // Location of dex file to compile from on disk 236 | dexFileLocation = readString(stream, dexFileLocationSize); 237 | // Checksum of the location string 238 | dexFileLocationChecksum = readInt(stream); 239 | // Pointer to location of dex file within the oat file 240 | dexFilePointer = readInt(stream); 241 | 242 | // Create a dex file out of the bytes starting from 243 | // dexFilePointer -> end of the oatfile. (Can't trust dex files size numbers) 244 | dexFiles.add(new DexFile(data, dexFilePointer, data.length - dexFilePointer, dexFileLocation)); 245 | } 246 | 247 | // heuristically search for 2+ entries, in order to handle OAT header format changes 248 | if(dexFiles.size() < dexFileCount) { 249 | logger.info("Searching for %d additional DEX files heuristically...", dexFileCount - dexFiles.size()); 250 | int idex = 2; 251 | byte[] b = new byte[0x1000]; 252 | int n = stream.read(b, 0, 0x1000); 253 | for(int i = 0; i <= n - 4; i++) { 254 | int v = littleEndianBytesToInt(b, i); 255 | 256 | if(v >= 0 && (v + 100) <= data.length) { 257 | int val = littleEndianBytesToInt(data, v); 258 | if(val == 0x0A786564) { // 'dex\n' 259 | String name = String.format("Unknown DEX #%d", dexFiles.size() + 1); 260 | dexFiles.add(new DexFile(data, v, data.length - v, name)); 261 | if(dexFiles.size() >= dexFileCount) { 262 | break; 263 | } 264 | } 265 | } 266 | } 267 | } 268 | } 269 | 270 | public static int littleEndianBytesToInt(byte[] array, int offset) { 271 | //@formatter:off 272 | return (array[offset] & 0xFF) 273 | | ((array[offset + 1] << 8) & 0xFF00) 274 | | ((array[offset + 2] << 16) & 0xFF0000) 275 | | ((array[offset + 3] << 24) & 0xFF000000); 276 | //@formatter:on 277 | } 278 | 279 | public int getVersion() { 280 | return version; 281 | } 282 | 283 | public int getDexFileCount() { 284 | return dexFileCount; 285 | } 286 | 287 | public List getDexFiles() { 288 | return dexFiles; 289 | } 290 | 291 | public String getISAString() { 292 | // Might use this in a description 293 | switch(instructionSet) { 294 | case OAT.kArm: 295 | return "ARM"; 296 | case OAT.kArm64: 297 | return "ARM64"; 298 | case OAT.kThumb2: 299 | return "ARM_Thumb2"; 300 | case OAT.kX86: 301 | return "X86"; 302 | case OAT.X86_64: 303 | return "X86_64"; 304 | case OAT.kMips: 305 | return "MIPS"; 306 | case OAT.kMips64: 307 | return "MIPS64"; 308 | default: 309 | return "Unknown"; 310 | } 311 | } 312 | 313 | public String getKeyValueStore() { 314 | // Get the info from key value store 315 | // The returned string alternates 316 | // between key and value separated by 317 | // the null character '\0' 318 | return new String(keyValueStore); 319 | } 320 | } 321 | --------------------------------------------------------------------------------