diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/app/util/opinion/DexLoader.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/app/util/opinion/DexLoader.java index 988726d24c..e999a606d3 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/app/util/opinion/DexLoader.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/app/util/opinion/DexLoader.java @@ -23,6 +23,7 @@ import ghidra.app.util.Option; import ghidra.app.util.bin.BinaryReader; import ghidra.app.util.bin.ByteProvider; import ghidra.app.util.importer.MessageLog; +import ghidra.file.formats.android.dex.DexHeaderFactory; import ghidra.file.formats.android.dex.format.*; import ghidra.file.formats.android.dex.util.DexUtil; import ghidra.program.model.address.Address; @@ -46,7 +47,7 @@ public class DexLoader extends AbstractLibrarySupportLoader { BinaryReader reader = new BinaryReader(provider, true); try { - DexHeader header = new DexHeader(reader); + DexHeader header = DexHeaderFactory.getDexHeader(reader); if (DexConstants.DEX_MAGIC_BASE.equals(new String(header.getMagic()))) { List queries = QueryOpinionService.query(getName(), DexConstants.MACHINE, null); @@ -80,7 +81,7 @@ public class DexLoader extends AbstractLibrarySupportLoader { } BinaryReader reader = new BinaryReader( provider, true ); - DexHeader header = new DexHeader( reader ); + DexHeader header = DexHeaderFactory.getDexHeader( reader ); monitor.setMessage( "DEX Loader: creating method byte code" ); diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/apex/ApexContants.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/apex/ApexContants.java new file mode 100644 index 0000000000..2971213f20 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/apex/ApexContants.java @@ -0,0 +1,27 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.apex; + +/** + * Android Pony EXpress (ApexContants) + * + * https://source.android.com/devices/tech/ota/apex + * + * https://android.googlesource.com/platform/system/apex/+/refs/heads/master/apexd/apex_constants.h + */ +public class ApexContants { + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtAnalyzer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtAnalyzer.java new file mode 100644 index 0000000000..1ecfb7a5ec --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtAnalyzer.java @@ -0,0 +1,102 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art; + +import ghidra.app.util.bin.*; +import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.opinion.BinaryLoader; +import ghidra.file.analyzers.FileFormatAnalyzer; +import ghidra.file.formats.android.oat.OatConstants; +import ghidra.program.model.address.Address; +import ghidra.program.model.address.AddressSetView; +import ghidra.program.model.data.DataType; +import ghidra.program.model.listing.Program; +import ghidra.util.task.TaskMonitor; + +public class ArtAnalyzer extends FileFormatAnalyzer { + + @Override + public String getName() { + return "Android ART Header Format"; + } + + @Override + public boolean getDefaultEnablement(Program program) { + return true; + } + + @Override + public String getDescription() { + return "Analyzes the Android ART information in this program."; + } + + @Override + public boolean canAnalyze(Program program) { + return ArtConstants.isART(program) + //HACK: + //Make analyzer appear after ART is merged with OAT program + //Currently, analyzers will not recognize the new ART block being added + || OatConstants.isOAT(program); + } + + @Override + public boolean isPrototype() { + return true; + } + + @Override + public boolean analyze(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) + throws Exception { + Address address = ArtConstants.findART(program); + + if (address == null) {//ART does not exist so quit, could be OAT + return false; + } + + ByteProvider provider = new MemoryByteProvider(program.getMemory(), address); + BinaryReader reader = new BinaryReader(provider, !program.getLanguage().isBigEndian()); + + try { + ArtHeader header = ArtFactory.newArtHeader(reader); + + DataType headerDataType = header.toDataType(); + + //only set "image base" when ART header not defined at "image begin" + //---this really only opens when ART is "added to" OAT program + Address imageBase = toAddr(program, header.getImageBegin()); + + if (BinaryLoader.BINARY_NAME.equals(program.getExecutableFormat())) { + program.setImageBase(imageBase, true); + createData(program, imageBase, headerDataType); + } + else { + createData(program, address, headerDataType); + } + + header.markup(program, monitor); + + return true; + } + catch (UnsupportedArtVersionException e) { + log.appendException(e); + } + catch (Exception e) { + throw e; + } + return false; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtBlock.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtBlock.java new file mode 100644 index 0000000000..b324b04617 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtBlock.java @@ -0,0 +1,99 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art; + +import java.io.IOException; + +import ghidra.app.util.bin.*; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/android10-release/runtime/image.h + * + * + */ +public class ArtBlock implements StructConverter, ArtCompression { + + private ArtStorageMode storage_mode_ = ArtStorageMode.kDefaultStorageMode; + private int data_offset_; + private int data_size_; + private int image_offset_; + private int image_size_; + + public ArtBlock(BinaryReader reader) throws IOException { + storage_mode_ = ArtStorageMode.get(reader.readNextInt()); + data_offset_ = reader.readNextInt(); + data_size_ = reader.readNextInt(); + image_offset_ = reader.readNextInt(); + image_size_ = reader.readNextInt(); + } + + @Override + public ArtStorageMode getStorageMode() { + return storage_mode_; + } + + @Override + public long getCompressedOffset() { + return Integer.toUnsignedLong(data_offset_); + } + + @Override + public int getCompressedSize() { + return data_size_; + } + + @Override + public long getDecompressedOffset() { + return Integer.toUnsignedLong(image_offset_); + } + + @Override + public int getDecompressedSize() { + return image_size_; + } + + public int getDataOffset() { + return data_offset_; + } + + public int getDataSize() { + return data_size_; + } + + public int getImageOffset() { + return image_offset_; + } + + public int getImageSize() { + return image_size_; + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + String name = StructConverterUtil.parseName(ArtBlock.class); + Structure structure = new StructureDataType(name, 0); + structure.setCategoryPath(new CategoryPath("/art")); + structure.add(DWORD, "storage_mode_", storage_mode_.name()); + structure.add(DWORD, "data_offset_", null); + structure.add(DWORD, "data_size_", null); + structure.add(DWORD, "image_offset_", null); + structure.add(DWORD, "image_size_", null); + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtCompression.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtCompression.java new file mode 100644 index 0000000000..2679a5130b --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtCompression.java @@ -0,0 +1,52 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art; + +public interface ArtCompression { + + /** + * Storage method for the image, the image may be compressed. + * @return the storage method + * @throws UnknownArtStorageModeException when an unknown storage mode is encountered + */ + public ArtStorageMode getStorageMode() throws UnknownArtStorageModeException; + + /** + * Data size for the image data excluding the bitmap and the header. + * For compressed images, this is the compressed size in the file. + * @return the compressed size + */ + public int getCompressedSize(); + + /** + * Offset to the start of the compressed bytes. + * Also, offset of where to place the decompressed bytes. + * @return the offset to the compressed bytes + */ + public long getCompressedOffset(); + + /** + * Expected size of the decompressed bytes. + * @return the expected decompressed size + */ + public int getDecompressedSize(); + + /** + * Offset to the start of the decompressed bytes. + * @return the offset to the dcompressed bytes + */ + public long getDecompressedOffset(); +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtConstants.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtConstants.java new file mode 100644 index 0000000000..499ddf6923 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtConstants.java @@ -0,0 +1,127 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art; + +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemoryBlock; + +/** + * https://android.googlesource.com/platform/art/+/master/runtime/image.cc + */ +public final class ArtConstants { + + public final static String ART_NAME = "Android Runtime (ART)"; + + public final static String MAGIC = "art\n"; + + public final static int VERSION_LENGTH = 4; + + public final static String VERSION_KITKAT_RELEASE = "005"; + public final static String VERSION_LOLLIPOP_RELEASE = "009"; + public final static String VERSION_LOLLIPOP_MR1_WFC_RELEASE = "012"; + public final static String VERSION_MARSHMALLOW_RELEASE = "017"; + public final static String VERSION_NOUGAT_RELEASE = "029"; + public final static String VERSION_NOUGAT_MR2_PIXEL_RELEASE = "030"; + public final static String VERSION_OREO_RELEASE = "043"; + public final static String VERSION_OREO_DR1_RELEASE = "044"; + public final static String VERSION_OREO_MR1_RELEASE = "046"; + public final static String VERSION_PIE_RELEASE = "056"; + public final static String VERSION_10_RELEASE = "074";//Q + public final static String VERSION_11_RELEASE = "085";//R + + // "005",// kitkat-release + // "009",// lollipop-release + // "012",// lollipop-mr1-wfc-release + // "017",// marshmallow-release + // "029",// nougat-release + // "030",// nougat-mr2-pixel-release + // "043",// oreo-release + // "044",// taimen-op1 + // "046",// oreo-mr1-release + // "051",// + // "056",// pie-release + // "059",// android-o-mr1-iot-release-1.0.0 + // "060",// android-o-mr1-iot-release-1.0.1 + // "061",// android-n-iot-release-polk-at1 + + /** + * NOTE: only going to support RELEASE versions + */ + public final static String[] SUPPORTED_VERSIONS = new String[] { + //@formatter:off + VERSION_KITKAT_RELEASE, + VERSION_LOLLIPOP_RELEASE, + VERSION_LOLLIPOP_MR1_WFC_RELEASE, + VERSION_MARSHMALLOW_RELEASE, + VERSION_NOUGAT_RELEASE, + VERSION_NOUGAT_MR2_PIXEL_RELEASE, + VERSION_OREO_RELEASE, + VERSION_OREO_DR1_RELEASE, + VERSION_OREO_MR1_RELEASE, + VERSION_PIE_RELEASE, + VERSION_10_RELEASE, + VERSION_11_RELEASE, + //@formatter:on + }; + + public final static boolean isSupportedVersion(String version) { + for (String supportedVersion : SUPPORTED_VERSIONS) { + if (supportedVersion.equals(version)) { + return true; + } + } + return false; + } + + public final static boolean isART(Program program) { + if (program != null) { + for (MemoryBlock block : program.getMemory().getBlocks()) { + try { + byte[] bytes = new byte[ArtConstants.MAGIC.length()]; + block.getBytes(block.getStart(), bytes); + String magic = new String(bytes); + if (ArtConstants.MAGIC.equals(magic)) { + return true; + } + } + catch (Exception e) { + //ignore + } + } + } + return false; + } + + public final static Address findART(Program program) { + if (program != null) { + for (MemoryBlock block : program.getMemory().getBlocks()) { + try { + byte[] bytes = new byte[ArtConstants.MAGIC.length()]; + block.getBytes(block.getStart(), bytes); + String magic = new String(bytes); + if (ArtConstants.MAGIC.equals(magic)) { + return block.getStart(); + } + } + catch (Exception e) { + //ignore + } + } + } + return null; + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtFactory.java new file mode 100644 index 0000000000..27e70bce18 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtFactory.java @@ -0,0 +1,79 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.file.formats.android.art.android10.ArtHeader_10; +import ghidra.file.formats.android.art.android11.ArtHeader_11; +import ghidra.file.formats.android.art.kitkat.ArtHeader_KitKat; +import ghidra.file.formats.android.art.lollipop.ArtHeader_Lollipop; +import ghidra.file.formats.android.art.lollipop.ArtHeader_LollipopMR1WFC; +import ghidra.file.formats.android.art.marshmallow.ArtHeader_Marshmallow; +import ghidra.file.formats.android.art.nougat.ArtHeader_Nougat; +import ghidra.file.formats.android.art.nougat.ArtHeader_NougatMR2Pixel; +import ghidra.file.formats.android.art.oreo.ArtHeader_Oreo; +import ghidra.file.formats.android.art.oreo.ArtHeader_OreoMR1; +import ghidra.file.formats.android.art.pie.ArtHeader_Pie; + +public final class ArtFactory { + + /** + * Returns an ArtHeader of the correct version. + * @param reader the BinaryReader to the ART header + * @return the specific version of the ART header + * @throws IOException should an error occur during reading or parsing + * @throws UnsupportedArtVersionException when the provided version is invalid or not yet implemented. + */ + public final static ArtHeader newArtHeader(BinaryReader reader) + throws IOException, UnsupportedArtVersionException { + String magic = new String(reader.readByteArray(0, ArtConstants.MAGIC.length())); + String version = reader.readAsciiString(4, 4); + if (magic.equals(ArtConstants.MAGIC)) { + if (ArtConstants.isSupportedVersion(version)) { + switch (version ) { + case ArtConstants.VERSION_KITKAT_RELEASE: + return new ArtHeader_KitKat(reader); + case ArtConstants.VERSION_LOLLIPOP_RELEASE: + return new ArtHeader_Lollipop(reader); + case ArtConstants.VERSION_LOLLIPOP_MR1_WFC_RELEASE: + return new ArtHeader_LollipopMR1WFC(reader); + case ArtConstants.VERSION_MARSHMALLOW_RELEASE: + return new ArtHeader_Marshmallow(reader); + case ArtConstants.VERSION_NOUGAT_RELEASE: + return new ArtHeader_Nougat(reader); + case ArtConstants.VERSION_NOUGAT_MR2_PIXEL_RELEASE: + return new ArtHeader_NougatMR2Pixel(reader); + case ArtConstants.VERSION_OREO_RELEASE: + return new ArtHeader_Oreo(reader); + case ArtConstants.VERSION_OREO_DR1_RELEASE: + return new ArtHeader_Oreo(reader);//v043 and v044 are same format + case ArtConstants.VERSION_OREO_MR1_RELEASE: + return new ArtHeader_OreoMR1(reader); + case ArtConstants.VERSION_PIE_RELEASE: + return new ArtHeader_Pie(reader); + case ArtConstants.VERSION_10_RELEASE: + return new ArtHeader_10(reader); + case ArtConstants.VERSION_11_RELEASE: + return new ArtHeader_11(reader); + } + } + } + throw new UnsupportedArtVersionException(magic, version); + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtField.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtField.java new file mode 100644 index 0000000000..81bea98836 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtField.java @@ -0,0 +1,71 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art; + +import java.io.IOException; + +import ghidra.app.util.bin.*; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/android10-release/runtime/art_field.h + * + * + */ +public class ArtField implements StructConverter { + + private int declaring_class_; + private int access_flags_; + private int field_dex_idx_; + private int offset_; + + public ArtField(BinaryReader reader) throws IOException { + declaring_class_ = reader.readNextInt(); + access_flags_ = reader.readNextInt(); + field_dex_idx_ = reader.readNextInt(); + offset_ = reader.readNextInt(); + } + + public int getDeclaringClass() { + return declaring_class_; + } + + public int getAccessFlags() { + return access_flags_; + } + + public int getFieldDexIndex() { + return field_dex_idx_; + } + + public int getOffset() { + return offset_; + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + String name = StructConverterUtil.parseName(ArtField.class); + Structure structure = new StructureDataType(name, 0); + structure.setCategoryPath(new CategoryPath("/art")); + structure.add(new Pointer32DataType(), "declaring_class_", null); + structure.add(DWORD, "access_flags_", null); + structure.add(DWORD, "field_dex_idx_", null); + structure.add(DWORD, "offset_", null); + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtFieldGroup.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtFieldGroup.java new file mode 100644 index 0000000000..32840aa03d --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtFieldGroup.java @@ -0,0 +1,66 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import ghidra.app.util.bin.*; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/android10-release/runtime/art_field.h + * + * NOTE: this class does not exist, was created to make field reading easier. + */ +public class ArtFieldGroup implements StructConverter { + + private int fieldCount; + private List fieldList = new ArrayList<>(); + + public ArtFieldGroup(BinaryReader reader) throws IOException { + fieldCount = reader.readNextInt(); + if (fieldCount > 0xffff) {//sanity check... + throw new IOException("Too many ART fields: " + fieldCount); + } + for (int i = 0; i < fieldCount; ++i) { + fieldList.add(new ArtField(reader)); + } + } + + public int getFieldCount() { + return fieldCount; + } + + public List getFieldList() { + return fieldList; + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + String name = StructConverterUtil.parseName(ArtFieldGroup.class); + Structure structure = new StructureDataType(name + "_" + fieldCount, 0); + structure.setCategoryPath(new CategoryPath("/art")); + structure.add(DWORD, "fieldCount", null); + for (int i = 0; i < fieldCount; ++i) { + structure.add(fieldList.get(i).toDataType(), "field_" + i, null); + } + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtHeader.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtHeader.java new file mode 100644 index 0000000000..13943c9af9 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtHeader.java @@ -0,0 +1,148 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import ghidra.app.util.bin.*; +import ghidra.program.model.data.*; +import ghidra.program.model.listing.Program; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.task.TaskMonitor; + +/** + * https://android.googlesource.com/platform/art/+/marshmallow-release/runtime/image.h + */ +public abstract class ArtHeader implements StructConverter { + + protected String magic_; + protected String version_; + + protected List imageMethodsList = new ArrayList<>(); + + protected ArtHeader(BinaryReader reader) throws IOException { + magic_ = new String(reader.readNextByteArray(ArtConstants.MAGIC.length())); + version_ = reader.readNextAsciiString(ArtConstants.VERSION_LENGTH); + } + + /** + * Returns the magic string: "art\n". + * @return the magic string + */ + public final String getMagic() { + return magic_; + } + + /** + * Returns the version string: eg, "001", "017" + * @return the version + */ + public final String getVersion() { + return version_; + } + + /** + * Required base address for mapping the image. + * + * Base address of the ART file. + * -1 indicates unsupported + * @return image base address + */ + abstract public int getImageBegin(); + + /** + * Required base size for mapping the image. + * -1 indicates unsupported + * @return image size + */ + abstract public int getImageSize(); + + /** + * Returns the checksum of the matching OAT file. + * The checksum is stored in the OAT header and is generated using Adler32. + * -1 indicates unsupported + * @return oat checksum + */ + abstract public int getOatChecksum(); + + /** + * -1 indicates unsupported + * @return the oat file begin address + */ + abstract public int getOatFileBegin(); + + /** + * -1 indicates unsupported + * @return the oat file end address + */ + abstract public int getOatFileEnd(); + + /** + * Returns the offset to the start of the .oatdata section, + * usually defined within the ".rodata" section. + * -1 indicates unsupported + * @return the oat data begin address + */ + abstract public int getOatDataBegin(); + + /** + * -1 indicates unsupported + * @return the oat data end address + */ + abstract public int getOatDataEnd(); + + /** + * Pointer size (in bytes). + * @return the pointer size + */ + abstract public int getPointerSize(); + + abstract public int getArtMethodCountForVersion(); + + /** + * Parses the ART header data. + * @param reader the binary reader + * @throws IOException if an error occurs parsing the header + */ + abstract protected void parse(BinaryReader reader) throws IOException; + + protected final void parseImageMethods(BinaryReader reader) throws IOException { + for (int i = 0; i < getArtMethodCountForVersion(); ++i) { + imageMethodsList.add(reader.readNextLong()); + } + } + + /** + * Allows each specific version to mark-up the specified program. + * @param program the program to markup + * @param monitor the task monitor + * @throws Exception if an error occurs while marking up the program + */ + abstract public void markup(Program program, TaskMonitor monitor) throws Exception; + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + String className = StructConverterUtil.parseName(ArtHeader.class); + Structure structure = new StructureDataType(className, 0); + structure.add(STRING, 4, "magic_", null); + structure.add(STRING, 4, "version_", null); + structure.setCategoryPath(new CategoryPath("/art")); + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtImageSection.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtImageSection.java new file mode 100644 index 0000000000..5bffccc7f5 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtImageSection.java @@ -0,0 +1,53 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art; + +import java.io.IOException; + +import ghidra.app.util.bin.*; +import ghidra.program.model.data.CategoryPath; +import ghidra.program.model.data.DataType; +import ghidra.util.exception.DuplicateNameException; + +public class ArtImageSection implements StructConverter { + private int offset_; + private int size_; + + public ArtImageSection(BinaryReader reader) throws IOException { + offset_ = reader.readNextInt(); + size_ = reader.readNextInt(); + } + + public int getOffset() { + return offset_; + } + + public int getSize() { + return size_; + } + + public int getEnd() { + return offset_ + size_; + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + DataType dataType = StructConverterUtil.toDataType(ArtImageSection.class); + dataType.setCategoryPath(new CategoryPath("/art")); + return dataType; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtImageSections.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtImageSections.java new file mode 100644 index 0000000000..34afc91c56 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtImageSections.java @@ -0,0 +1,522 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.program.model.address.Address; +import ghidra.program.model.data.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.SourceType; +import ghidra.util.task.TaskMonitor; + +public abstract class ArtImageSections { + + public final static int UNSUPPORTED_SECTION = -1; + + protected ArtHeader header; + + protected List sectionList = new ArrayList<>(); + protected List fieldGroupList = new ArrayList<>(); + protected List methodGroupList = new ArrayList<>(); + + protected List fieldList = new ArrayList<>(); + protected List methodList = new ArrayList<>(); + + protected ArtImageSections(BinaryReader reader, ArtHeader header) { + this.header = header; + } + + /** + * Returns the section name for the given ordinal. + * For example, if sectionOrdinal is 0 then return "kSectionObjects". + * @param sectionOrdinal the original of the ART section + * @return the ART section name + */ + protected String getSectionName(int sectionOrdinal) { + for (Field field : getClass().getDeclaredFields()) { + if (field.getName().startsWith("kSection")) { + try { + Object value = field.get(null); + if ((int) value == sectionOrdinal) { + return field.getName(); + } + } + catch (Exception e) { + //ignore + } + } + } + return "unknown_section_0x" + Integer.toHexString(sectionOrdinal); + } + + public abstract int get_kSectionObjects(); + + public abstract int get_kSectionArtFields(); + + public abstract int get_kSectionArtMethods(); + + public abstract int get_kSectionRuntimeMethods(); + + public abstract int get_kSectionImTables(); + + public abstract int get_kSectionIMTConflictTables(); + + public abstract int get_kSectionDexCacheArrays(); + + public abstract int get_kSectionInternedStrings(); + + public abstract int get_kSectionClassTable(); + + public abstract int get_kSectionStringReferenceOffsets(); + + public abstract int get_kSectionMetadata(); + + public abstract int get_kSectionImageBitmap(); + + public abstract int get_kSectionCount(); // Number of elements in enum. + + public final List getSectionList() { + return sectionList; + } + + public final void parseSections(BinaryReader reader) throws IOException { + for (int i = 0; i < get_kSectionCount(); ++i) { + sectionList.add(new ArtImageSection(reader)); + } + } + + public final void parse(BinaryReader reader) throws IOException { + parseArtFields(reader); + parseArtMethods(reader); + } + + private void parseArtFields(BinaryReader reader) throws IOException { + ArtImageSection kSectionArtFields = sectionList.get(get_kSectionArtFields()); + if (kSectionArtFields.getSize() > 0) { + if (reader.length() > kSectionArtFields.getOffset()) {//out of bounds + reader.setPointerIndex(kSectionArtFields.getOffset()); + while (reader.getPointerIndex() < Integer + .toUnsignedLong(kSectionArtFields.getEnd())) { + if (ArtConstants.VERSION_MARSHMALLOW_RELEASE.equals(header.getVersion())) { + ArtField field = new ArtField(reader); + fieldList.add(field); + } + else { + ArtFieldGroup group = new ArtFieldGroup(reader); + fieldGroupList.add(group); + } + } + } + } + } + + private void parseArtMethods(BinaryReader reader) throws IOException { + ArtImageSection kSectionArtMethods = sectionList.get(get_kSectionArtMethods()); + if (kSectionArtMethods.getSize() > 0) { + if (reader.length() > kSectionArtMethods.getOffset()) {//out of bounds + reader.setPointerIndex(kSectionArtMethods.getOffset()); + while (reader.getPointerIndex() < Integer + .toUnsignedLong(kSectionArtMethods.getEnd())) { + if (ArtConstants.VERSION_MARSHMALLOW_RELEASE.equals(header.getVersion())) { + ArtMethod method = + new ArtMethod(reader, header.getPointerSize(), header.getVersion()); + methodList.add(method); + } + else { + ArtMethodGroup group = new ArtMethodGroup(reader, header.getPointerSize(), + header.getVersion()); + methodGroupList.add(group); + } + } + } + } + } + + public void markup(Program program, TaskMonitor monitor) throws Exception { + markupSections(program, monitor); + markupFields(program, monitor); + markupMethods(program, monitor); + markupImTables(program, monitor); + markupIMTConflictTables(program, monitor); + markupRuntimeMethods(program, monitor); + markupDexCacheArrays(program, monitor); + markupInternedStrings(program, monitor); + markupClassTables(program, monitor); + } + + private void markupSections(Program program, TaskMonitor monitor) throws Exception { + monitor.setMessage("ART - markup sections..."); + monitor.setProgress(0); + monitor.setMaximum(sectionList.size()); + + for (int i = 0; i < sectionList.size(); ++i) { + monitor.checkCanceled(); + monitor.incrementProgress(1); + + ArtImageSection section = sectionList.get(i); + + if (section.getSize() == 0) { + continue; + } + String name = getSectionName(i); + Address address = + program.getMinAddress().getNewAddress(header.getImageBegin() + section.getOffset()); + program.getSymbolTable().createLabel(address, name, SourceType.ANALYSIS); + program.getListing() + .setComment(address, CodeUnit.PLATE_COMMENT, "Size: " + section.getSize()); + + createFragment(program, address, section, name, monitor); + } + } + + private void markupFields(Program program, TaskMonitor monitor) throws Exception { + if (get_kSectionArtFields() == UNSUPPORTED_SECTION) { + return; + } + + monitor.setMessage("ART - markup fields..."); + monitor.setProgress(0); + monitor.setMaximum(fieldList.size()); + + ArtImageSection section = sectionList.get(get_kSectionArtFields()); + + if (section.getSize() > 0) { + Address address = + program.getMinAddress().getNewAddress(header.getImageBegin() + section.getOffset()); + + for (int i = 0; i < fieldList.size(); ++i) { + monitor.checkCanceled(); + ArtField field = fieldList.get(i); + DataType dataType = field.toDataType(); + program.getListing().createData(address, dataType); + + String comment = + "Declaring Class: 0x" + Integer.toHexString(field.getDeclaringClass()); + program.getListing().setComment(address, CodeUnit.PLATE_COMMENT, comment); + + address = address.add(dataType.getLength()); + + monitor.incrementProgress(1); + } + } + + if (section.getSize() > 0) { + Address address = + program.getMinAddress().getNewAddress(header.getImageBegin() + section.getOffset()); + for (int i = 0; i < fieldGroupList.size(); ++i) { + monitor.checkCanceled(); + ArtFieldGroup fieldGroup = fieldGroupList.get(i); + DataType dataType = fieldGroup.toDataType(); + program.getListing().createData(address, dataType); + if (fieldGroup.getFieldCount() > 0) { + ArtField artField = fieldGroup.getFieldList().get(0); + String comment = + "Declaring Class: 0x" + Integer.toHexString(artField.getDeclaringClass()); + program.getListing().setComment(address, CodeUnit.PLATE_COMMENT, comment); + } + address = address.add(dataType.getLength()); + + monitor.incrementProgress(1); + } + } + } + + private void markupMethods(Program program, TaskMonitor monitor) throws Exception { + if (get_kSectionArtMethods() == UNSUPPORTED_SECTION) { + return; + } + + monitor.setMessage("ART - markup methods..."); + monitor.setProgress(0); + monitor.setMaximum(methodGroupList.size()); + + ArtImageSection section = sectionList.get(get_kSectionArtMethods()); + + if (section.getSize() > 0) { + Address address = + program.getMinAddress().getNewAddress(header.getImageBegin() + section.getOffset()); + for (int i = 0; i < methodList.size(); ++i) { + monitor.checkCanceled(); + + ArtMethod method = methodList.get(i); + DataType dataType = method.toDataType(); + program.getListing().createData(address, dataType); + String comment = + "Declaring Class: 0x" + Integer.toHexString(method.getDeclaringClass()); + program.getListing().setComment(address, CodeUnit.PLATE_COMMENT, comment); + + address = address.add(dataType.getLength()); + + monitor.incrementProgress(1); + } + } + + if (section.getSize() > 0) { + Address address = + program.getMinAddress().getNewAddress(header.getImageBegin() + section.getOffset()); + for (int i = 0; i < methodGroupList.size(); ++i) { + monitor.checkCanceled(); + + ArtMethodGroup methodGroup = methodGroupList.get(i); + DataType dataType = methodGroup.toDataType(); + program.getListing().createData(address, dataType); + if (methodGroup.getMethodCount() > 0) { + ArtMethod artMethod = methodGroup.getMethodList().get(0); + String comment = + "Declaring Class: 0x" + Integer.toHexString(artMethod.getDeclaringClass()); + program.getListing().setComment(address, CodeUnit.PLATE_COMMENT, comment); + } + address = address.add(dataType.getLength()); + + monitor.incrementProgress(1); + } + } + } + + /** + * Interface Methods Tables + */ + private void markupImTables(Program program, TaskMonitor monitor) throws Exception { + if (get_kSectionImTables() == UNSUPPORTED_SECTION) { + return; + } + ArtImageSection section = sectionList.get(get_kSectionImTables()); + + monitor.setMessage("ART - markup IM tables..."); + monitor.setProgress(0); + monitor.setMaximum(section.getSize()); + + int pointerSize = header.getPointerSize(); + + if (section.getSize() > 0) { + Address address = + program.getMinAddress().getNewAddress(header.getImageBegin() + section.getOffset()); + + if (!program.getMemory().contains(address)) {//outside of ART file + return; + } + + Address endAddress = address.add(section.getSize()); + while (address.compareTo(endAddress) < 0) { + monitor.checkCanceled(); + monitor.incrementProgress(pointerSize); + + createDataAt(program, address, pointerSize); + address = address.add(pointerSize); + } + } + } + + /** + * IMT Conflict Tables + */ + private void markupIMTConflictTables(Program program, TaskMonitor monitor) throws Exception { + if (get_kSectionIMTConflictTables() == UNSUPPORTED_SECTION) { + return; + } + + ArtImageSection section = sectionList.get(get_kSectionIMTConflictTables()); + + monitor.setMessage("ART - markup IMT conflict tables..."); + monitor.setProgress(0); + monitor.setMaximum(section.getSize()); + + int pointerSize = header.getPointerSize(); + + if (section.getSize() > 0) { + Address address = + program.getMinAddress().getNewAddress(header.getImageBegin() + section.getOffset()); + + if (!program.getMemory().contains(address)) {//outside of ART file + return; + } + + Address endAddress = address.add(section.getSize()); + while (address.compareTo(endAddress) < 0) { + monitor.checkCanceled(); + monitor.incrementProgress(pointerSize); + + createDataAt(program, address, pointerSize); + address = address.add(pointerSize); + } + } + } + + private void markupRuntimeMethods(Program program, TaskMonitor monitor) throws Exception { + if (get_kSectionRuntimeMethods() == UNSUPPORTED_SECTION) { + return; + } + + ArtImageSection section = sectionList.get(get_kSectionRuntimeMethods()); + + monitor.setMessage("ART - markup runtime methods..."); + monitor.setProgress(0); + monitor.setMaximum(section.getSize()); + + int pointerSize = header.getPointerSize(); + if (section.getSize() > 0) { + Address address = + program.getMinAddress().getNewAddress(header.getImageBegin() + section.getOffset()); + + if (!program.getMemory().contains(address)) {//outside of ART file + return; + } + + Address endAddress = address.add(section.getSize()); + while (address.compareTo(endAddress) < 0) { + monitor.checkCanceled(); + monitor.incrementProgress(pointerSize); + + createDataAt(program, address, pointerSize); + address = address.add(pointerSize); + } + } + } + + private void markupDexCacheArrays(Program program, TaskMonitor monitor) throws Exception { + if (get_kSectionDexCacheArrays() == UNSUPPORTED_SECTION) { + return; + } + + ArtImageSection section = sectionList.get(get_kSectionDexCacheArrays()); + + monitor.setMessage("ART - markup dex cache arrays..."); + monitor.setProgress(0); + monitor.setMaximum(section.getSize()); + + if (section.getSize() > 0) { + Address address = + program.getMinAddress().getNewAddress(header.getImageBegin() + section.getOffset()); + + if (!program.getMemory().contains(address)) {//outside of ART file + return; + } + + Address endAddress = address.add(section.getSize()); + while (address.compareTo(endAddress) < 0) { + monitor.checkCanceled(); + monitor.incrementProgress(4); + + createDataAt(program, address, 4); + address = address.add(4); + } + } + } + + private void markupInternedStrings(Program program, TaskMonitor monitor) throws Exception { + if (get_kSectionInternedStrings() == UNSUPPORTED_SECTION) { + return; + } + + ArtImageSection section = sectionList.get(get_kSectionInternedStrings()); + + monitor.setMessage("ART - markup interned strings..."); + monitor.setProgress(0); + monitor.setMaximum(section.getSize()); + + if (section.getSize() > 0) { + Address address = + program.getMinAddress().getNewAddress(header.getImageBegin() + section.getOffset()); + + if (!program.getMemory().contains(address)) {//outside of ART file + return; + } + + Address endAddress = address.add(section.getSize()); + while (address.compareTo(endAddress) < 0) { + monitor.checkCanceled(); + monitor.incrementProgress(4); + + createDataAt(program, address, 4); + address = address.add(4); + } + } + } + + private void markupClassTables(Program program, TaskMonitor monitor) throws Exception { + if (get_kSectionClassTable() == UNSUPPORTED_SECTION) { + return; + } + + ArtImageSection section = sectionList.get(get_kSectionClassTable()); + + monitor.setMessage("ART - markup class tables..."); + monitor.setProgress(0); + monitor.setMaximum(section.getSize()); + + if (section.getSize() > 0) { + Address address = + program.getMinAddress().getNewAddress(header.getImageBegin() + section.getOffset()); + + if (!program.getMemory().contains(address)) {//outside of ART file + return; + } + + Address endAddress = address.add(section.getSize()); + while (address.compareTo(endAddress) < 0) { + monitor.checkCanceled(); + monitor.incrementProgress(4); + + address = address.add(4); + } + } + } + + private void createDataAt(Program program, Address address, int pointerSize) throws Exception { + if (pointerSize == 4) { + program.getListing().createData(address, new DWordDataType()); + } + else if (pointerSize == 8) { + program.getListing().createData(address, new QWordDataType()); + } + else { + throw new RuntimeException("invalid pointer size"); + } + } + + private void createFragment(Program program, Address address, ArtImageSection section, + String sectionName, TaskMonitor monitor) { + try { + ProgramModule rootModule = program.getListing().getDefaultRootModule(); + + ProgramFragment fragment = null; + for (Group group : rootModule.getChildren()) { + if (group.getName().equals(sectionName)) { + fragment = (ProgramFragment) group; + } + } + if (fragment == null) { + fragment = rootModule.createFragment(sectionName); + } + + Address endAddress = address.add((Integer.toUnsignedLong(section.getSize()) - 1)); + if (sectionList.indexOf(section) == sectionList.size() - 1) {//last section might extend past the end of the program + if (endAddress.compareTo(program.getMaxAddress()) > 0) { + endAddress = program.getMaxAddress(); + } + } + fragment.move(address, endAddress); + } + catch (Exception e) { + //ignore... + } + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtMethod.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtMethod.java new file mode 100644 index 0000000000..dd34ff2582 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtMethod.java @@ -0,0 +1,402 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art; + +import java.io.IOException; + +import ghidra.app.util.bin.*; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/android10-release/runtime/art_method.h + * + * + */ +public class ArtMethod implements StructConverter { + private final int pointerSize; + private final String artVersion; + + private int declaring_class_; + private int access_flags_; + private int dex_code_item_offset_; + private int dex_method_index_; + private short method_index_; + private short hotness_count_; + private short imt_index_; + private short padding_; + + private long dex_cache_resolved_methods_; + private long dex_cache_resolved_types_; + private long entry_point_from_interpreter_; + private long entry_point_from_jni_; + private long data_; + private long unknown1_; + private long entry_point_from_quick_compiled_code_; + private int unknown2_; + + public ArtMethod(BinaryReader reader, int pointerSize, String artVersion) throws IOException { + this.pointerSize = pointerSize; + this.artVersion = artVersion; + + if (ArtConstants.VERSION_MARSHMALLOW_RELEASE.equals(artVersion)) { + if (pointerSize == 4) { + declaring_class_ = reader.readNextInt(); + dex_cache_resolved_methods_ = Integer.toUnsignedLong(reader.readNextInt()); + dex_cache_resolved_types_ = Integer.toUnsignedLong(reader.readNextInt()); + access_flags_ = reader.readNextInt(); + dex_code_item_offset_ = reader.readNextInt(); + dex_method_index_ = reader.readNextInt(); + method_index_ = reader.readNextShort(); + padding_ = reader.readNextShort(); + entry_point_from_interpreter_ = Integer.toUnsignedLong(reader.readNextInt()); + entry_point_from_jni_ = Integer.toUnsignedLong(reader.readNextInt()); + entry_point_from_quick_compiled_code_ = + Integer.toUnsignedLong(reader.readNextInt()); + } + else if (pointerSize == 8) { + throw new IOException("Unsupported 64-bit ART method format: " + artVersion); + } + } + else if (ArtConstants.VERSION_NOUGAT_RELEASE.equals(artVersion) || + ArtConstants.VERSION_NOUGAT_MR2_PIXEL_RELEASE.equals(artVersion)) { + + if (pointerSize == 4) { + declaring_class_ = reader.readNextInt(); + access_flags_ = reader.readNextInt(); + dex_code_item_offset_ = reader.readNextInt(); + dex_method_index_ = reader.readNextInt(); + method_index_ = reader.readNextShort(); + hotness_count_ = reader.readNextShort(); + dex_cache_resolved_methods_ = Integer.toUnsignedLong(reader.readNextInt()); + dex_cache_resolved_types_ = Integer.toUnsignedLong(reader.readNextInt()); + entry_point_from_jni_ = Integer.toUnsignedLong(reader.readNextInt()); + entry_point_from_quick_compiled_code_ = + Integer.toUnsignedLong(reader.readNextInt()); + } + else if (pointerSize == 8) { + declaring_class_ = reader.readNextInt(); + access_flags_ = reader.readNextInt(); + dex_code_item_offset_ = reader.readNextInt(); + dex_method_index_ = reader.readNextInt(); + method_index_ = reader.readNextShort(); + hotness_count_ = reader.readNextShort(); + imt_index_ = reader.readNextShort(); + padding_ = reader.readNextShort(); + dex_cache_resolved_methods_ = reader.readNextLong(); + dex_cache_resolved_types_ = reader.readNextLong(); + entry_point_from_jni_ = reader.readNextLong(); + entry_point_from_quick_compiled_code_ = reader.readNextLong(); + } + } + else if (ArtConstants.VERSION_OREO_RELEASE.equals(artVersion) || + ArtConstants.VERSION_OREO_DR1_RELEASE.equals(artVersion) || + ArtConstants.VERSION_OREO_MR1_RELEASE.equals(artVersion)) { + + if (pointerSize == 4) { + declaring_class_ = reader.readNextInt(); + access_flags_ = reader.readNextInt(); + dex_code_item_offset_ = reader.readNextInt(); + dex_method_index_ = reader.readNextInt(); + method_index_ = reader.readNextShort(); + hotness_count_ = reader.readNextShort(); + data_ = reader.readNextLong(); + entry_point_from_quick_compiled_code_ = + Integer.toUnsignedLong(reader.readNextInt()); + } + else if (pointerSize == 8) { + declaring_class_ = reader.readNextInt(); + access_flags_ = reader.readNextInt(); + dex_code_item_offset_ = reader.readNextInt(); + dex_method_index_ = reader.readNextInt(); + method_index_ = reader.readNextShort(); + hotness_count_ = reader.readNextShort(); + imt_index_ = reader.readNextShort(); + padding_ = reader.readNextShort(); + data_ = reader.readNextLong(); + unknown1_ = reader.readNextLong(); + entry_point_from_quick_compiled_code_ = reader.readNextLong(); + } + } + else if (ArtConstants.VERSION_PIE_RELEASE.equals(artVersion)) { + declaring_class_ = reader.readNextInt(); + access_flags_ = reader.readNextInt(); + dex_code_item_offset_ = reader.readNextInt(); + dex_method_index_ = reader.readNextInt(); + method_index_ = reader.readNextShort(); + hotness_count_ = reader.readNextShort(); + imt_index_ = reader.readNextShort(); + padding_ = reader.readNextShort(); + + if (pointerSize == 4) { + data_ = Integer.toUnsignedLong(reader.readNextInt()); + } + else if (pointerSize == 8) { + data_ = reader.readNextLong(); + entry_point_from_quick_compiled_code_ = reader.readNextLong(); + } + } + else if (ArtConstants.VERSION_10_RELEASE.equals(artVersion)) { + declaring_class_ = reader.readNextInt(); + access_flags_ = reader.readNextInt(); + dex_code_item_offset_ = reader.readNextInt(); + dex_method_index_ = reader.readNextInt(); + method_index_ = reader.readNextShort(); + hotness_count_ = reader.readNextShort(); + imt_index_ = reader.readNextShort(); + padding_ = reader.readNextShort(); + + if (pointerSize == 4) { + data_ = Integer.toUnsignedLong(reader.readNextInt()); + } + else if (pointerSize == 8) { + data_ = reader.readNextLong(); + entry_point_from_quick_compiled_code_ = reader.readNextLong(); + } + } + else if (ArtConstants.VERSION_11_RELEASE.equals(artVersion)) { + declaring_class_ = reader.readNextInt(); + access_flags_ = reader.readNextInt(); + dex_code_item_offset_ = reader.readNextInt(); + dex_method_index_ = reader.readNextInt(); + method_index_ = reader.readNextShort(); + hotness_count_ = reader.readNextShort(); + imt_index_ = reader.readNextShort(); + padding_ = reader.readNextShort(); + + if (pointerSize == 4) { + data_ = Integer.toUnsignedLong(reader.readNextInt()); + } + else if (pointerSize == 8) { + data_ = reader.readNextLong(); + entry_point_from_quick_compiled_code_ = reader.readNextLong(); + } + } + else { + throw new IOException("Unsupported ART method format: " + artVersion); + } + } + + public int getDeclaringClass() { + return declaring_class_; + } + + public int getAccessFlags() { + return access_flags_; + } + + public int getDexCodeItemOffset() { + return dex_code_item_offset_; + } + + public int getDexMethodIndex() { + return dex_method_index_; + } + + public short getMethodIndex() { + return method_index_; + } + + public short getHotnessCount() { + return hotness_count_; + } + + public short getImtIndex() { + return imt_index_; + } + + public short getPadding() { + return padding_; + } + + public long getData() { + return data_; + } + + public long getEntryPointFromInterpreter() { + return entry_point_from_interpreter_; + } + + public long getEntryPointFromQuickCompiledCode() { + return entry_point_from_quick_compiled_code_; + } + + public long getDexCacheResolvedMethods() { + return dex_cache_resolved_methods_; + } + + public long getDexCacheResolvedTypes() { + return dex_cache_resolved_types_; + } + + public long getEntryPointFromJNI() { + return entry_point_from_jni_; + } + + public long getUnknown1() { + return unknown1_; + } + + public int getUnknown2() { + return unknown2_; + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + DataType ptr32 = new Pointer32DataType(); + DataType ptr64 = new Pointer64DataType(); + + String name = StructConverterUtil.parseName(ArtMethod.class); + Structure struct = new StructureDataType(name, 0); + struct.setCategoryPath(new CategoryPath("/art")); + + if (ArtConstants.VERSION_MARSHMALLOW_RELEASE.equals(artVersion)) { + if (pointerSize == 4) { + struct.add(DWORD, "declaring_class_", null); + struct.add(DWORD, "dex_cache_resolved_methods_", null); + struct.add(DWORD, "dex_cache_resolved_types_", null); + struct.add(DWORD, "access_flags_", null); + struct.add(DWORD, "dex_code_item_offset_", null); + struct.add(DWORD, "dex_method_index_", null); + struct.add(WORD, "method_index_", null); + struct.add(WORD, "padding_", null); + struct.add(DWORD, "entry_point_from_interpreter_", null); + struct.add(DWORD, "entry_point_from_jni_", null); + struct.add(DWORD, "entry_point_from_quick_compiled_code_", null); + } + else if (pointerSize == 8) { + throw new IOException("Unsupported 64-bit ART method format: " + artVersion); + } + } + else if (ArtConstants.VERSION_NOUGAT_RELEASE.equals(artVersion) || + ArtConstants.VERSION_NOUGAT_MR2_PIXEL_RELEASE.equals(artVersion)) { + + if (pointerSize == 4) { + struct.add(ptr32, "declaring_class_", null); + struct.add(DWORD, "access_flags_", null); + struct.add(DWORD, "dex_code_item_offset_", null); + struct.add(DWORD, "dex_method_index_", null); + struct.add(WORD, "method_index_", null); + struct.add(WORD, "hotness_count_", null); + struct.add(DWORD, "dex_cache_resolved_methods_", null); + struct.add(DWORD, "dex_cache_resolved_types_", null); + struct.add(ptr32, "entry_point_from_jni_", null); + struct.add(ptr32, "entry_point_from_quick_compiled_code_", null); + } + else if (pointerSize == 8) { + struct.add(ptr32, "declaring_class_", null); + struct.add(DWORD, "access_flags_", null); + struct.add(DWORD, "dex_code_item_offset_", null); + struct.add(DWORD, "dex_method_index_", null); + struct.add(WORD, "method_index_", null); + struct.add(WORD, "hotness_count_", null); + struct.add(WORD, "imt_index_", null); + struct.add(WORD, "padding", null); + struct.add(QWORD, "dex_cache_resolved_methods_", null); + struct.add(QWORD, "dex_cache_resolved_types_", null); + struct.add(ptr64, "entry_point_from_jni_", null); + struct.add(ptr64, "entry_point_from_quick_compiled_code_", null); + } + } + else if (ArtConstants.VERSION_OREO_RELEASE.equals(artVersion) || + ArtConstants.VERSION_OREO_DR1_RELEASE.equals(artVersion) || + ArtConstants.VERSION_OREO_MR1_RELEASE.equals(artVersion)) { + + if (pointerSize == 4) { + struct.add(ptr32, "declaring_class_", null); + struct.add(DWORD, "access_flags_", null); + struct.add(DWORD, "dex_code_item_offset_", null); + struct.add(DWORD, "dex_method_index_", null); + struct.add(WORD, "method_index_", null); + struct.add(WORD, "hotness_count_", null); + struct.add(QWORD, "data", null); + struct.add(ptr32, "entry_point_from_quick_compiled_code_", null); + } + else if (pointerSize == 8) { + struct.add(ptr32, "declaring_class_", null); + struct.add(DWORD, "access_flags_", null); + struct.add(DWORD, "dex_code_item_offset_", null); + struct.add(DWORD, "dex_method_index_", null); + struct.add(WORD, "method_index_", null); + struct.add(WORD, "hotness_count_", null); + struct.add(WORD, "imt_index_", null); + struct.add(WORD, "padding", null); + struct.add(QWORD, "data", null); + struct.add(QWORD, "unknown1_", null); + struct.add(ptr64, "entry_point_from_quick_compiled_code_", null); + } + } + else if (ArtConstants.VERSION_PIE_RELEASE.equals(artVersion)) { + struct.add(ptr32, "declaring_class_", null); + struct.add(DWORD, "access_flags_", null); + struct.add(DWORD, "dex_code_item_offset_", null); + struct.add(DWORD, "dex_method_index_", null); + struct.add(WORD, "method_index_", null); + struct.add(WORD, "hotness_count_", null); + struct.add(WORD, "imt_index_", null); + struct.add(WORD, "padding", null); + + if (pointerSize == 4) { + struct.add(DWORD, "data", null); + } + else if (pointerSize == 8) { + struct.add(QWORD, "data", null); + struct.add(QWORD, "entry_point_from_quick_compiled_code_", null); + } + } + else if (ArtConstants.VERSION_10_RELEASE.equals(artVersion)) { + struct.add(ptr32, "declaring_class_", null); + struct.add(DWORD, "access_flags_", null); + struct.add(DWORD, "dex_code_item_offset_", null); + struct.add(DWORD, "dex_method_index_", null); + struct.add(WORD, "method_index_", null); + struct.add(WORD, "hotness_count_", null); + struct.add(WORD, "imt_index_", null); + struct.add(WORD, "padding", null); + + if (pointerSize == 4) { + struct.add(DWORD, "data", null); + } + else if (pointerSize == 8) { + struct.add(QWORD, "data", null); + struct.add(ptr64, "entry_point_from_quick_compiled_code_", null); + } + } + else if (ArtConstants.VERSION_11_RELEASE.equals(artVersion)) { + struct.add(ptr32, "declaring_class_", null); + struct.add(DWORD, "access_flags_", null); + struct.add(DWORD, "dex_code_item_offset_", null); + struct.add(DWORD, "dex_method_index_", null); + struct.add(WORD, "method_index_", null); + struct.add(WORD, "hotness_count_", null); + struct.add(WORD, "imt_index_", null); + struct.add(WORD, "padding", null); + + if (pointerSize == 4) { + struct.add(DWORD, "data", null); + } + else if (pointerSize == 8) { + struct.add(QWORD, "data", null); + struct.add(QWORD, "entry_point_from_quick_compiled_code_", null); + } + } + else { + throw new IOException("Unsupported ART method format: " + artVersion); + } + return struct; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtMethodGroup.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtMethodGroup.java new file mode 100644 index 0000000000..7b7f98e603 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtMethodGroup.java @@ -0,0 +1,83 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import ghidra.app.util.bin.*; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/android10-release/runtime/art_method.h + * + * NOTE: this class does not exist, was created to make method reading easier. + */ +public class ArtMethodGroup implements StructConverter { + + private int pointerSize; + + private long methodCount; + private List methodList = new ArrayList<>(); + + public ArtMethodGroup(BinaryReader reader, int pointerSize, String artVersion) + throws IOException { + this.pointerSize = pointerSize; + + if (pointerSize == 8) { + methodCount = reader.readNextLong(); + } + else if (pointerSize == 4) { + methodCount = Integer.toUnsignedLong(reader.readNextInt()); + } + + if (methodCount > 0xffff) {//sanity check... + throw new IOException("Too many ART methods: " + methodCount); + } + + for (int i = 0; i < methodCount; ++i) { + methodList.add(new ArtMethod(reader, pointerSize, artVersion)); + } + } + + public long getMethodCount() { + return methodCount; + } + + public List getMethodList() { + return methodList; + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + String name = StructConverterUtil.parseName(ArtMethodGroup.class); + Structure structure = new StructureDataType(name + "_" + methodCount, 0); + structure.setCategoryPath(new CategoryPath("/art")); + if (pointerSize == 8) { + structure.add(QWORD, "methodCount", null); + } + else if (pointerSize == 4) { + structure.add(DWORD, "methodCount", null); + } + for (int i = 0; i < methodCount; ++i) { + structure.add(methodList.get(i).toDataType(), "method_" + i, null); + } + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtStorageMode.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtStorageMode.java new file mode 100644 index 0000000000..fcc32ac0e0 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtStorageMode.java @@ -0,0 +1,36 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/pie-release/runtime/image.h + */ +public enum ArtStorageMode { + kStorageModeUncompressed, kStorageModeLZ4, kStorageModeLZ4HC, kStorageModeCount; // Number of elements in enum. + + public final static ArtStorageMode kDefaultStorageMode = kStorageModeUncompressed; + + public final static int SIZE = 32;//bits + + public static ArtStorageMode get(int value) throws UnknownArtStorageModeException { + for (ArtStorageMode mode : values()) { + if (mode.ordinal() == value) { + return mode; + } + } + throw new UnknownArtStorageModeException(value); + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtUtilities.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtUtilities.java new file mode 100644 index 0000000000..e8372e131c --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/ArtUtilities.java @@ -0,0 +1,68 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art; + +import java.math.BigInteger; + +import ghidra.program.model.address.Address; +import ghidra.program.model.lang.*; +import ghidra.program.model.listing.*; + +public final class ArtUtilities { + + static void createFragment(Program program, String fragmentName, Address start, + Address end) throws Exception { + ProgramModule module = program.getListing().getRootModule(0); + ProgramFragment fragment = getFragment(module, fragmentName); + if (fragment == null) { + fragment = module.createFragment(fragmentName); + } + fragment.move(start, end.subtract(1)); + } + + static ProgramFragment getFragment(ProgramModule module, String fragmentName) { + Group[] groups = module.getChildren(); + for (Group group : groups) { + if (group.getName().equals(fragmentName)) { + return (ProgramFragment) group; + } + } + return null; + } + + public static Address adjustForThumbAsNeeded(ArtHeader artHeader, Program program, + Address address) { + long displacement = address.getOffset(); + if (program.getLanguage() + .getProcessor() + .equals(Processor.findOrPossiblyCreateProcessor("ARM"))) { + if ((displacement & 0x1) == 0x1) {//thumb code? + address = address.subtract(1); + + Register register = program.getLanguage().getRegister("TMode"); + RegisterValue value = new RegisterValue(register, BigInteger.valueOf(1)); + try { + program.getProgramContext().setRegisterValue(address, address, value); + } + catch (ContextChangeException e) { + //log.appendException( e ); + //ignore... + } + } + } + return address; + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/UnknownArtStorageModeException.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/UnknownArtStorageModeException.java new file mode 100644 index 0000000000..61384ec0c8 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/UnknownArtStorageModeException.java @@ -0,0 +1,26 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art; + +import java.io.IOException; + +public class UnknownArtStorageModeException extends IOException { + + public UnknownArtStorageModeException(int storageMode) { + super("Unrecognized storage mode: 0x" + Integer.toHexString(storageMode)); + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/UnsupportedArtVersionException.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/UnsupportedArtVersionException.java new file mode 100644 index 0000000000..2c1975aaaf --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/UnsupportedArtVersionException.java @@ -0,0 +1,23 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art; + +final class UnsupportedArtVersionException extends Exception { + + UnsupportedArtVersionException(String magic, String version) { + super("Unsupported ART version: " + version); + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android10/ArtHeader_10.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android10/ArtHeader_10.java new file mode 100644 index 0000000000..b49e180ce2 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android10/ArtHeader_10.java @@ -0,0 +1,234 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.android10; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverterUtil; +import ghidra.file.formats.android.art.*; +import ghidra.file.formats.android.util.DecompressionManager; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.program.model.listing.Program; +import ghidra.util.InvalidNameException; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.task.TaskMonitor; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/android10-release/runtime/image.h + */ +public class ArtHeader_10 extends ArtHeader { + + private int image_reservation_size_; + private int component_count_; + private int image_begin_; + private int image_size_; + private int image_checksum_; + private int oat_checksum_; + private int oat_file_begin_; + private int oat_data_begin_; + private int oat_data_end_; + private int oat_file_end_; + private int boot_image_begin_; + private int boot_image_size_; + private int image_roots_; + private int pointer_size_; + private long[] image_methods_ = new long[ImageMethod_10.kImageMethodsCount.ordinal()]; + private int data_size_; + private int blocks_offset_; + private int blocks_count_; + private List blocks = new ArrayList<>(); + + private ArtImageSections sections; + + public ArtHeader_10(BinaryReader reader) throws IOException { + super(reader); + parse(reader); + } + + @Override + protected void parse(BinaryReader reader) throws IOException { + image_reservation_size_ = reader.readNextInt(); + component_count_ = reader.readNextInt(); + image_begin_ = reader.readNextInt(); + image_size_ = reader.readNextInt(); + image_checksum_ = reader.readNextInt(); + oat_checksum_ = reader.readNextInt(); + oat_file_begin_ = reader.readNextInt(); + oat_data_begin_ = reader.readNextInt(); + oat_data_end_ = reader.readNextInt(); + oat_file_end_ = reader.readNextInt(); + boot_image_begin_ = reader.readNextInt(); + boot_image_size_ = reader.readNextInt(); + image_roots_ = reader.readNextInt(); + pointer_size_ = reader.readNextInt(); + + sections = new ImageSections_10(reader, this); + sections.parseSections(reader); + + parseImageMethods(reader); + + data_size_ = reader.readNextInt(); + blocks_offset_ = reader.readNextInt(); + blocks_count_ = reader.readNextInt(); + + if (blocks_offset_ > 0 && blocks_count_ > 0) { + reader.setPointerIndex(blocks_offset_); + for (int i = 0; i < blocks_count_; ++i) { + blocks.add(new ArtBlock(reader)); + } + } + + reader = DecompressionManager.decompress(reader, blocks, TaskMonitor.DUMMY); + + // NOTE: + // cannot parse the sections until after the blocks are decompressed! + + sections.parse(reader); + } + + @Override + public int getArtMethodCountForVersion() { + return ImageMethod_10.kImageMethodsCount.ordinal(); + } + + @Override + public int getImageBegin() { + return image_begin_; + } + + @Override + public int getImageSize() { + return image_size_; + } + + public int getImageChecksum_() { + return image_checksum_; + } + + @Override + public int getOatChecksum() { + return oat_checksum_; + } + + @Override + public int getOatDataBegin() { + return oat_data_begin_; + } + + @Override + public int getOatDataEnd() { + return oat_data_end_; + } + + @Override + public int getOatFileBegin() { + return oat_file_begin_; + } + + @Override + public int getOatFileEnd() { + return oat_file_end_; + } + + @Override + public int getPointerSize() { + return pointer_size_; + } + + public int getBootImageBegin() { + return boot_image_begin_; + } + + /** + * App images currently require a boot image, + * if the size is non zero then it is an app image header. + * @return true if this header represents an app image + */ + public boolean isAppImage() { + return boot_image_size_ != 0x0; + } + + public int getImageReservationSize() { + return image_reservation_size_; + } + + public int getComponentCount() { + return component_count_; + } + + public int getImageRoots() { + return image_roots_; + } + + public int getDataSize() { + return data_size_; + } + + public List getBlocks() { + return blocks; + } + + @Override + public void markup(Program program, TaskMonitor monitor) throws Exception { + DecompressionManager.decompressOverMemory(program, blocks, monitor); + + sections.markup(program, monitor); + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = (Structure) super.toDataType(); + + String className = StructConverterUtil.parseName(ArtHeader_10.class); + try { + structure.setName(className); + } + catch (InvalidNameException e) { + //ignore, just use original name should this fail + } + + structure.add(DWORD, "image_reservation_size_", null); + structure.add(DWORD, "component_count_", null); + structure.add(DWORD, "image_begin_", null); + structure.add(DWORD, "image_size_", null); + structure.add(DWORD, "image_checksum_", null); + structure.add(DWORD, "oat_checksum_", null); + structure.add(DWORD, "oat_file_begin_", null); + structure.add(DWORD, "oat_data_begin_", null); + structure.add(DWORD, "oat_data_end_", null); + structure.add(DWORD, "oat_file_end_", null); + structure.add(DWORD, "boot_image_begin_", null); + structure.add(DWORD, "boot_image_size_", null); + structure.add(DWORD, "image_roots_", null); + structure.add(DWORD, "pointer_size_", null); + + for (int i = 0; i < sections.getSectionList().size(); ++i) { + structure.add(sections.getSectionList().get(i).toDataType(), "section_" + i, null); + } + for (int i = 0; i < image_methods_.length; ++i) { + structure.add(QWORD, "image_method_" + i, null); + } + + structure.add(DWORD, "data_size_", null); + structure.add(DWORD, "blocks_offset_", null); + structure.add(DWORD, "blocks_count_", null); + return structure; + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android10/ImageMethod_10.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android10/ImageMethod_10.java new file mode 100644 index 0000000000..793c3ffb59 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android10/ImageMethod_10.java @@ -0,0 +1,32 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.android10; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/android10-release/runtime/image.h + */ +public enum ImageMethod_10 { + kResolutionMethod, + kImtConflictMethod, + kImtUnimplementedMethod, + kSaveAllCalleeSavesMethod, + kSaveRefsOnlyMethod, + kSaveRefsAndArgsMethod, + kSaveEverythingMethod, + kSaveEverythingMethodForClinit, + kSaveEverythingMethodForSuspendCheck, + kImageMethodsCount, // Number of elements in enum. +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android10/ImageRoot_10.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android10/ImageRoot_10.java new file mode 100644 index 0000000000..315fa68b4e --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android10/ImageRoot_10.java @@ -0,0 +1,43 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.android10; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/android10-release/runtime/image.h + */ +public enum ImageRoot_10 { + kDexCaches, + kClassRoots, + /** Pre-allocated OOME when throwing exception.*/ + kOomeWhenThrowingException, + /** Pre-allocated OOME when throwing OOME. */ + kOomeWhenThrowingOome, + /** Pre-allocated OOME when handling StackOverflowError. */ + kOomeWhenHandlingStackOverflow, + /** Pre-allocated NoClassDefFoundError. */ + kNoClassDefFoundError, + /** Different for boot image and app image, see aliases below. */ + kSpecialRoots, + kImageRootsMax; + + //Aliases + + /** The class loader used to build the app image.*/ + public final static ImageRoot_10 kAppImageClassLoader = kSpecialRoots; + + /** Array of boot image objects that must be kept live. */ + public final static ImageRoot_10 kBootImageLiveObjects = kSpecialRoots; +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android10/ImageSections_10.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android10/ImageSections_10.java new file mode 100644 index 0000000000..b61d930d14 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android10/ImageSections_10.java @@ -0,0 +1,109 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.android10; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.file.formats.android.art.ArtHeader; +import ghidra.file.formats.android.art.ArtImageSections; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/android10-release/runtime/image.h + */ +public class ImageSections_10 extends ArtImageSections { + public final static int kSectionObjects = 0; + public final static int kSectionArtFields = 1; + public final static int kSectionArtMethods = 2; + public final static int kSectionRuntimeMethods = 3; + public final static int kSectionImTables = 4; + public final static int kSectionIMTConflictTables = 5; + public final static int kSectionDexCacheArrays = 6; + public final static int kSectionInternedStrings = 7; + public final static int kSectionClassTable = 8; + public final static int kSectionStringReferenceOffsets = 9; + public final static int kSectionMetadata = 10; + public final static int kSectionImageBitmap = 11; + public final static int kSectionCount = 12; // Number of elements in enum. + + public ImageSections_10(BinaryReader reader, ArtHeader header) { + super(reader, header); + } + + @Override + public int get_kSectionObjects() { + return kSectionObjects; + } + + @Override + public int get_kSectionArtFields() { + return kSectionArtFields; + } + + @Override + public int get_kSectionArtMethods() { + return kSectionArtMethods; + } + + @Override + public int get_kSectionRuntimeMethods() { + return kSectionRuntimeMethods; + } + + @Override + public int get_kSectionImTables() { + return kSectionImTables; + } + + @Override + public int get_kSectionIMTConflictTables() { + return kSectionIMTConflictTables; + } + + @Override + public int get_kSectionDexCacheArrays() { + return kSectionDexCacheArrays; + } + + @Override + public int get_kSectionInternedStrings() { + return kSectionInternedStrings; + } + + @Override + public int get_kSectionClassTable() { + return kSectionClassTable; + } + + @Override + public int get_kSectionStringReferenceOffsets() { + return kSectionStringReferenceOffsets; + } + + @Override + public int get_kSectionMetadata() { + return kSectionMetadata; + } + + @Override + public int get_kSectionImageBitmap() { + return kSectionImageBitmap; + } + + @Override + public int get_kSectionCount() { + return kSectionCount; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android11/ArtHeader_11.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android11/ArtHeader_11.java new file mode 100644 index 0000000000..2f625b404a --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android11/ArtHeader_11.java @@ -0,0 +1,250 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.android11; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverterUtil; +import ghidra.file.formats.android.art.*; +import ghidra.file.formats.android.art.android10.ImageMethod_10; +import ghidra.file.formats.android.art.android10.ImageSections_10; +import ghidra.file.formats.android.util.DecompressionManager; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.program.model.listing.Program; +import ghidra.util.InvalidNameException; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.task.TaskMonitor; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/android10-release/runtime/image.h + */ +public class ArtHeader_11 extends ArtHeader { + + private int image_reservation_size_; + private int component_count_; + private int image_begin_; + private int image_size_; + private int image_checksum_; + private int oat_checksum_; + private int oat_file_begin_; + private int oat_data_begin_; + private int oat_data_end_; + private int oat_file_end_; + private int boot_image_begin_; + private int boot_image_size_; + private int boot_image_component_count_; + private int boot_image_checksum_; + private int image_roots_; + private int pointer_size_; + private long[] image_methods_ = new long[ImageMethod_10.kImageMethodsCount.ordinal()]; + private int data_size_; + private int blocks_offset_; + private int blocks_count_; + private List blocks = new ArrayList<>(); + + private ArtImageSections sections; + + public ArtHeader_11(BinaryReader reader) throws IOException { + super(reader); + parse(reader); + } + + @Override + protected void parse(BinaryReader reader) throws IOException { + image_reservation_size_ = reader.readNextInt(); + component_count_ = reader.readNextInt(); + image_begin_ = reader.readNextInt(); + image_size_ = reader.readNextInt(); + image_checksum_ = reader.readNextInt(); + oat_checksum_ = reader.readNextInt(); + oat_file_begin_ = reader.readNextInt(); + oat_data_begin_ = reader.readNextInt(); + oat_data_end_ = reader.readNextInt(); + oat_file_end_ = reader.readNextInt(); + boot_image_begin_ = reader.readNextInt(); + boot_image_size_ = reader.readNextInt(); + boot_image_component_count_ = reader.readNextInt(); + boot_image_checksum_ = reader.readNextInt(); + image_roots_ = reader.readNextInt(); + pointer_size_ = reader.readNextInt(); + + sections = new ImageSections_10(reader, this); + sections.parseSections(reader); + + parseImageMethods(reader); + + data_size_ = reader.readNextInt(); + blocks_offset_ = reader.readNextInt(); + blocks_count_ = reader.readNextInt(); + + if (blocks_offset_ > 0 && blocks_count_ > 0) { + reader.setPointerIndex(blocks_offset_); + for (int i = 0; i < blocks_count_; ++i) { + blocks.add(new ArtBlock(reader)); + } + } + + reader = DecompressionManager.decompress(reader, blocks, TaskMonitor.DUMMY); + + // NOTE: + // cannot parse the sections until after the blocks are decompressed! + + sections.parse(reader); + } + + @Override + public int getArtMethodCountForVersion() { + return ImageMethod_10.kImageMethodsCount.ordinal(); + } + + @Override + public int getImageBegin() { + return image_begin_; + } + + @Override + public int getImageSize() { + return image_size_; + } + + public int getImageChecksum_() { + return image_checksum_; + } + + @Override + public int getOatChecksum() { + return oat_checksum_; + } + + @Override + public int getOatDataBegin() { + return oat_data_begin_; + } + + @Override + public int getOatDataEnd() { + return oat_data_end_; + } + + @Override + public int getOatFileBegin() { + return oat_file_begin_; + } + + @Override + public int getOatFileEnd() { + return oat_file_end_; + } + + @Override + public int getPointerSize() { + return pointer_size_; + } + + public int getBootImageBegin() { + return boot_image_begin_; + } + + public int getBootImageComponentCount() { + return boot_image_component_count_; + } + + public int getBootImageChecksum() { + return boot_image_checksum_; + } + + /** + * App images currently require a boot image, + * if the size is non zero then it is an app image header. + * @return true if app image + */ + public boolean isAppImage() { + return boot_image_size_ != 0x0; + } + + public int getImageReservationSize() { + return image_reservation_size_; + } + + public int getComponentCount() { + return component_count_; + } + + public int getImageRoots() { + return image_roots_; + } + + public int getDataSize() { + return data_size_; + } + + public List getBlocks() { + return blocks; + } + + @Override + public void markup(Program program, TaskMonitor monitor) throws Exception { + DecompressionManager.decompressOverMemory(program, blocks, monitor); + + sections.markup(program, monitor); + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = (Structure) super.toDataType(); + + String className = StructConverterUtil.parseName(ArtHeader_11.class); + try { + structure.setName(className); + } + catch (InvalidNameException e) { + //ignore + } + + structure.add(DWORD, "image_reservation_size_", null); + structure.add(DWORD, "component_count_", null); + structure.add(DWORD, "image_begin_", null); + structure.add(DWORD, "image_size_", null); + structure.add(DWORD, "image_checksum_", null); + structure.add(DWORD, "oat_checksum_", null); + structure.add(DWORD, "oat_file_begin_", null); + structure.add(DWORD, "oat_data_begin_", null); + structure.add(DWORD, "oat_data_end_", null); + structure.add(DWORD, "oat_file_end_", null); + structure.add(DWORD, "boot_image_begin_", null); + structure.add(DWORD, "boot_image_size_", null); + structure.add(DWORD, "boot_image_component_count_", null); + structure.add(DWORD, "boot_image_checksum_", null); + structure.add(DWORD, "image_roots_", null); + structure.add(DWORD, "pointer_size_", null); + + for (int i = 0; i < sections.getSectionList().size(); ++i) { + structure.add(sections.getSectionList().get(i).toDataType(), "section_" + i, null); + } + for (int i = 0; i < image_methods_.length; ++i) { + structure.add(QWORD, "image_method_" + i, null); + } + + structure.add(DWORD, "data_size_", null); + structure.add(DWORD, "blocks_offset_", null); + structure.add(DWORD, "blocks_count_", null); + return structure; + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android11/BootImageLiveObjects.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android11/BootImageLiveObjects.java new file mode 100644 index 0000000000..525912411b --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android11/BootImageLiveObjects.java @@ -0,0 +1,34 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.android11; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/android11-release/runtime/image.h + */ +public enum BootImageLiveObjects { + + /** Pre-allocated OOME when throwing exception. */ + kOomeWhenThrowingException, + /** Pre-allocated OOME when throwing OOME. */ + kOomeWhenThrowingOome, + /** Pre-allocated OOME when handling StackOverflowError. */ + kOomeWhenHandlingStackOverflow, + /** Pre-allocated NoClassDefFoundError. */ + kNoClassDefFoundError, + /** Pre-allocated sentinel for cleared weak JNI references. */ + kClearedJniWeakSentinel, + kIntrinsicObjectsStart; +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android11/ImageRoot_11.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android11/ImageRoot_11.java new file mode 100644 index 0000000000..c91c821b59 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/android11/ImageRoot_11.java @@ -0,0 +1,31 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.android11; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/android11-release/runtime/image.h + */ +public enum ImageRoot_11 { + kDexCaches, kClassRoots, kSpecialRoots, kImageRootsMax; + + //Aliases + + /** The class loader used to build the app image.*/ + public final static ImageRoot_11 kAppImageClassLoader = kSpecialRoots; + + /** Array of boot image objects that must be kept live. */ + public final static ImageRoot_11 kBootImageLiveObjects = kSpecialRoots; +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/kitkat/ArtHeader_KitKat.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/kitkat/ArtHeader_KitKat.java new file mode 100644 index 0000000000..45106f2bec --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/kitkat/ArtHeader_KitKat.java @@ -0,0 +1,176 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.kitkat; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverterUtil; +import ghidra.file.formats.android.art.ArtHeader; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.program.model.listing.Program; +import ghidra.util.InvalidNameException; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.task.TaskMonitor; + +/** + * @see https://android.googlesource.com/platform/art/+/refs/heads/kitkat-release/runtime/image.cc + */ +public class ArtHeader_KitKat extends ArtHeader { + + protected int image_begin_; + protected int image_size_; + protected int image_bitmap_offset_; + protected int image_bitmap_size_; + protected int oat_checksum_; + protected int oat_file_begin_; + protected int oat_data_begin_; + protected int oat_data_end_; + protected int oat_file_end_; + protected int image_roots_; + + public ArtHeader_KitKat(BinaryReader reader) throws IOException { + super(reader); + parse(reader); + } + + @Override + protected void parse(BinaryReader reader) throws IOException { + image_begin_ = reader.readNextInt(); + image_size_ = reader.readNextInt(); + image_bitmap_offset_ = reader.readNextInt(); + image_bitmap_size_ = reader.readNextInt(); + oat_checksum_ = reader.readNextInt(); + oat_file_begin_ = reader.readNextInt(); + oat_data_begin_ = reader.readNextInt(); + oat_data_end_ = reader.readNextInt(); + oat_file_end_ = reader.readNextInt(); + image_roots_ = reader.readNextInt(); + } + + @Override + public int getImageBegin() { + return image_begin_; + } + + @Override + public int getImageSize() { + return image_size_; + } + + /** + * Image bitmap offset in the file. + */ + public int getImageBitmapOffset() { + return image_bitmap_offset_; + } + + /** + * Size of the image bitmap. + */ + public int getImageBitmapSize() { + return image_bitmap_size_; + } + + /** + * Checksum of the oat file we link to for load time sanity check. + */ + public int getOatChecksum() { + return oat_checksum_; + } + + /** + * Start address for oat file. Will be before oat_data_begin_ for .so files. + */ + @Override + public int getOatFileBegin() { + return oat_file_begin_; + } + + /** + * Required oat address expected by image Method::GetCode() pointers. + */ + @Override + public int getOatDataBegin() { + return oat_data_begin_; + } + + /** + * End of oat data address range for this image file. + */ + @Override + public int getOatDataEnd() { + return oat_data_end_; + } + + /** + * End of oat file address range. will be after oat_data_end_ for + * .so files. Used for positioning a following alloc spaces. + */ + @Override + public int getOatFileEnd() { + return oat_file_end_; + } + + /** + * Absolute address of an Object[] of objects needed to reinitialize from an image. + */ + public int getImageRoots() { + return image_roots_; + } + + @Override + public int getPointerSize() { + return -1; //unsupported + } + + @Override + public void markup(Program program, TaskMonitor monitor) throws Exception { + //do nothing for now + } + + @Override + public int getArtMethodCountForVersion() { + throw new UnsupportedOperationException(); + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = (Structure) super.toDataType(); + + String className = StructConverterUtil.parseName(ArtHeader_KitKat.class); + try { + structure.setName(className); + } + catch (InvalidNameException e) { + } + + structure.add(DWORD, "image_begin_", null); + structure.add(DWORD, "image_size_", null); + structure.add(DWORD, "image_bitmap_offset_", null); + structure.add(DWORD, "image_bitmap_size_", null); + structure.add(DWORD, "oat_checksum_", null); + structure.add(DWORD, "oat_file_begin_", null); + structure.add(DWORD, "oat_data_begin_", null); + structure.add(DWORD, "oat_data_end_", null); + structure.add(DWORD, "oat_file_end_", null); + structure.add(DWORD, "image_roots_", null); + + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/kitkat/ImageRoot_KitKat.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/kitkat/ImageRoot_KitKat.java new file mode 100644 index 0000000000..cf871d2ae1 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/kitkat/ImageRoot_KitKat.java @@ -0,0 +1,30 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.kitkat; + +/** + * @see https://android.googlesource.com/platform/art/+/refs/heads/kitkat-release/runtime/image.h + */ +public enum ImageRoot_KitKat { + kResolutionMethod, + kCalleeSaveMethod, + kRefsOnlySaveMethod, + kRefsAndArgsSaveMethod, + kOatLocation, + kDexCaches, + kClassRoots, + kImageRootsMax, +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/lollipop/ArtHeader_Lollipop.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/lollipop/ArtHeader_Lollipop.java new file mode 100644 index 0000000000..73861ccb85 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/lollipop/ArtHeader_Lollipop.java @@ -0,0 +1,153 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.lollipop; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverterUtil; +import ghidra.file.formats.android.art.ArtHeader; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.program.model.listing.Program; +import ghidra.util.InvalidNameException; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.task.TaskMonitor; + +/** + * @see https://android.googlesource.com/platform/art/+/refs/heads/lollipop-release/runtime/image.cc + */ +public class ArtHeader_Lollipop extends ArtHeader { + + protected int image_begin_; + protected int image_size_; + protected int image_bitmap_offset_; + protected int image_bitmap_size_; + protected int oat_checksum_; + protected int oat_file_begin_; + protected int oat_data_begin_; + protected int oat_data_end_; + protected int oat_file_end_; + protected int patch_delta_; + protected int image_roots_; + + public ArtHeader_Lollipop(BinaryReader reader) throws IOException { + super(reader); + parse(reader); + } + + @Override + protected void parse(BinaryReader reader) throws IOException { + image_begin_ = reader.readNextInt(); + image_size_ = reader.readNextInt(); + image_bitmap_offset_ = reader.readNextInt(); + image_bitmap_size_ = reader.readNextInt(); + oat_checksum_ = reader.readNextInt(); + oat_file_begin_ = reader.readNextInt(); + oat_data_begin_ = reader.readNextInt(); + oat_data_end_ = reader.readNextInt(); + oat_file_end_ = reader.readNextInt(); + patch_delta_ = reader.readNextInt(); + image_roots_ = reader.readNextInt(); + } + + @Override + public int getImageBegin() { + return image_begin_; + } + + @Override + public int getImageSize() { + return image_size_; + } + + /** + * The total delta that this image has been patched. + */ + public int getPatchDelta() { + return patch_delta_; + } + + /** + * Checksum of the oat file we link to for load time sanity check. + */ + @Override + public int getOatChecksum() { + return oat_checksum_; + } + + @Override + public int getPointerSize() { + return -1; //unsupported + } + + @Override + public int getOatFileBegin() { + return oat_file_begin_; + } + + @Override + public int getOatFileEnd() { + return oat_file_end_; + } + + @Override + public int getOatDataBegin() { + return oat_data_begin_; + } + + @Override + public int getOatDataEnd() { + return oat_data_end_; + } + + @Override + public void markup(Program program, TaskMonitor monitor) throws Exception { + //do nothing for now + } + + @Override + public int getArtMethodCountForVersion() { + throw new UnsupportedOperationException(); + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = (Structure) super.toDataType(); + + String className = StructConverterUtil.parseName(ArtHeader_Lollipop.class); + try { + structure.setName(className); + } + catch (InvalidNameException e) { + } + + structure.add(DWORD, "image_begin_", null); + structure.add(DWORD, "image_size_", null); + structure.add(DWORD, "image_bitmap_offset_", null); + structure.add(DWORD, "image_bitmap_size_", null); + structure.add(DWORD, "oat_checksum_", null); + structure.add(DWORD, "oat_file_begin_", null); + structure.add(DWORD, "oat_data_begin_", null); + structure.add(DWORD, "oat_data_end_", null); + structure.add(DWORD, "oat_file_end_", null); + structure.add(DWORD, "patch_delta_", null); + structure.add(DWORD, "image_roots_", null); + + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/lollipop/ArtHeader_LollipopMR1WFC.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/lollipop/ArtHeader_LollipopMR1WFC.java new file mode 100644 index 0000000000..03d597e387 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/lollipop/ArtHeader_LollipopMR1WFC.java @@ -0,0 +1,153 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.lollipop; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverterUtil; +import ghidra.file.formats.android.art.ArtHeader; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.program.model.listing.Program; +import ghidra.util.InvalidNameException; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.task.TaskMonitor; + +/** + * @see https://android.googlesource.com/platform/art/+/lollipop-mr1-wfc-release/runtime/image.h + */ +public class ArtHeader_LollipopMR1WFC extends ArtHeader { + + protected int image_begin_; + protected int image_size_; + protected int image_bitmap_offset_; + protected int image_bitmap_size_; + protected int oat_checksum_; + protected int oat_file_begin_; + protected int oat_data_begin_; + protected int oat_data_end_; + protected int oat_file_end_; + protected int patch_delta_; + protected int image_roots_; + protected int compile_pic_; + + public ArtHeader_LollipopMR1WFC(BinaryReader reader) throws IOException { + super(reader); + parse(reader); + } + + @Override + protected void parse(BinaryReader reader) throws IOException { + image_begin_ = reader.readNextInt(); + image_size_ = reader.readNextInt(); + image_bitmap_offset_ = reader.readNextInt(); + image_bitmap_size_ = reader.readNextInt(); + oat_checksum_ = reader.readNextInt(); + oat_file_begin_ = reader.readNextInt(); + oat_data_begin_ = reader.readNextInt(); + oat_data_end_ = reader.readNextInt(); + oat_file_end_ = reader.readNextInt(); + patch_delta_ = reader.readNextInt(); + image_roots_ = reader.readNextInt(); + compile_pic_ = reader.readNextInt(); + } + + @Override + public int getImageBegin() { + return image_begin_; + } + + @Override + public int getImageSize() { + return image_size_; + } + + /** + * Boolean (0 or 1) to denote if the image was compiled with --compile-pic option. + */ + public int getCompilePic() { + return compile_pic_; + } + + @Override + public int getOatChecksum() { + return oat_checksum_; + } + + @Override + public int getPointerSize() { + return -1; //unsupported + } + + @Override + public int getOatFileBegin() { + return oat_file_begin_; + } + + @Override + public int getOatFileEnd() { + return oat_file_end_; + } + + @Override + public int getOatDataBegin() { + return oat_data_begin_; + } + + @Override + public int getOatDataEnd() { + return oat_data_end_; + } + + @Override + public void markup(Program program, TaskMonitor monitor) throws Exception { + + } + + @Override + public int getArtMethodCountForVersion() { + throw new UnsupportedOperationException(); + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = (Structure) super.toDataType(); + + String className = StructConverterUtil.parseName(ArtHeader_LollipopMR1WFC.class); + try { + structure.setName(className); + } + catch (InvalidNameException e) { + } + + structure.add(DWORD, "image_begin_", null); + structure.add(DWORD, "image_size_", null); + structure.add(DWORD, "image_bitmap_offset_", null); + structure.add(DWORD, "image_bitmap_size_", null); + structure.add(DWORD, "oat_checksum_", null); + structure.add(DWORD, "oat_file_begin_", null); + structure.add(DWORD, "oat_data_begin_", null); + structure.add(DWORD, "oat_data_end_", null); + structure.add(DWORD, "oat_file_end_", null); + structure.add(DWORD, "patch_delta_", null); + structure.add(DWORD, "image_roots_", null); + structure.add(DWORD, "compile_pic_", null); + + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/lollipop/ImageRoot_Lollipop.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/lollipop/ImageRoot_Lollipop.java new file mode 100644 index 0000000000..c979bf59c1 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/lollipop/ImageRoot_Lollipop.java @@ -0,0 +1,31 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.lollipop; + +/** + * @see https://android.googlesource.com/platform/art/+/refs/heads/lollipop-release/runtime/image.h + */ +public enum ImageRoot_Lollipop { + kResolutionMethod, + kImtConflictMethod, + kDefaultImt, + kCalleeSaveMethod, + kRefsOnlySaveMethod, + kRefsAndArgsSaveMethod, + kDexCaches, + kClassRoots, + kImageRootsMax, +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/lollipop/ImageRoot_LollipopMR1WRC.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/lollipop/ImageRoot_LollipopMR1WRC.java new file mode 100644 index 0000000000..28d713e5ad --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/lollipop/ImageRoot_LollipopMR1WRC.java @@ -0,0 +1,32 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.lollipop; + +/** + * @see https://android.googlesource.com/platform/art/+/lollipop-mr1-wfc-release/runtime/image.h + */ +public enum ImageRoot_LollipopMR1WRC { + kResolutionMethod, + kImtConflictMethod, + kImtUnimplementedMethod, + kDefaultImt, + kCalleeSaveMethod, + kRefsOnlySaveMethod, + kRefsAndArgsSaveMethod, + kDexCaches, + kClassRoots, + kImageRootsMax, +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/marshmallow/ArtHeader_Marshmallow.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/marshmallow/ArtHeader_Marshmallow.java new file mode 100644 index 0000000000..24e2a337b1 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/marshmallow/ArtHeader_Marshmallow.java @@ -0,0 +1,175 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.marshmallow; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverterUtil; +import ghidra.file.formats.android.art.ArtHeader; +import ghidra.file.formats.android.art.ArtImageSections; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.program.model.listing.Program; +import ghidra.util.InvalidNameException; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.task.TaskMonitor; + +/** + * @see https://android.googlesource.com/platform/art/+/marshmallow-release/runtime/image.h + */ +public class ArtHeader_Marshmallow extends ArtHeader { + + protected int image_begin_; + protected int image_size_; + protected int oat_checksum_; + protected int oat_file_begin_; + protected int oat_data_begin_; + protected int oat_data_end_; + protected int oat_file_end_; + protected int patch_delta_; + protected int image_roots_; + protected int pointer_size_; + protected int compile_pic_; + + protected long[] image_methods_ = + new long[ImageMethod_Marshmallow.kImageMethodsCount.ordinal()]; + + protected ArtImageSections sections; + + public ArtHeader_Marshmallow(BinaryReader reader) throws IOException { + super(reader); + parse(reader); + } + + @Override + protected void parse(BinaryReader reader) throws IOException { + image_begin_ = reader.readNextInt(); + image_size_ = reader.readNextInt(); + oat_checksum_ = reader.readNextInt(); + oat_file_begin_ = reader.readNextInt(); + oat_data_begin_ = reader.readNextInt(); + oat_data_end_ = reader.readNextInt(); + oat_file_end_ = reader.readNextInt(); + patch_delta_ = reader.readNextInt(); + image_roots_ = reader.readNextInt(); + pointer_size_ = reader.readNextInt(); + compile_pic_ = reader.readNextInt(); + + sections = new ImageSections_Marshmallow(reader, this); + sections.parseSections(reader); + + for (int i = 0; i < image_methods_.length; ++i) { + image_methods_[i] = reader.readNextLong(); + } + + sections.parse(reader); + } + + @Override + public int getImageBegin() { + return image_begin_; + } + + @Override + public int getImageSize() { + return image_size_; + } + + @Override + public int getPointerSize() { + return pointer_size_; + } + + /** + * Image methods. + */ + public long[] getImageMethods() { + return image_methods_; + } + + /** + * Checksum of the oat file we link to for load time sanity check. + */ + @Override + public int getOatChecksum() { + return oat_checksum_; + } + + @Override + public int getOatFileBegin() { + return oat_file_begin_; + } + + @Override + public int getOatFileEnd() { + return oat_file_end_; + } + + @Override + public int getOatDataBegin() { + return oat_data_begin_; + } + + @Override + public int getOatDataEnd() { + return oat_data_end_; + } + + @Override + public void markup(Program program, TaskMonitor monitor) throws Exception { + + sections.markup(program, monitor); + } + + @Override + public int getArtMethodCountForVersion() { + throw new UnsupportedOperationException(); + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = (Structure) super.toDataType(); + + String className = StructConverterUtil.parseName(ArtHeader_Marshmallow.class); + try { + structure.setName(className); + } + catch (InvalidNameException e) { + } + + structure.add(DWORD, "image_begin_", null); + structure.add(DWORD, "image_size_", null); + structure.add(DWORD, "oat_checksum_", null); + structure.add(DWORD, "oat_file_begin_", null); + structure.add(DWORD, "oat_data_begin_", null); + structure.add(DWORD, "oat_data_end_", null); + structure.add(DWORD, "oat_file_end_", null); + structure.add(DWORD, "patch_delta_", null); + structure.add(DWORD, "image_roots_", null); + structure.add(DWORD, "pointer_size_", null); + structure.add(DWORD, "compile_pic_", null); + + for (int i = 0; i < sections.getSectionList().size(); ++i) { + structure.add(sections.getSectionList().get(i).toDataType(), "section_" + i, null); + } + for (int i = 0; i < image_methods_.length; ++i) { + structure.add(QWORD, "image_method_" + i, null); + } + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/marshmallow/ImageMethod_Marshmallow.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/marshmallow/ImageMethod_Marshmallow.java new file mode 100644 index 0000000000..698e0c8908 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/marshmallow/ImageMethod_Marshmallow.java @@ -0,0 +1,29 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.marshmallow; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/pie-release/runtime/image.h + */ +public enum ImageMethod_Marshmallow { + kResolutionMethod, + kImtConflictMethod, + kImtUnimplementedMethod, + kCalleeSaveMethod, + kRefsOnlySaveMethod, + kRefsAndArgsSaveMethod, + kImageMethodsCount, // Number of elements in enum. +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/marshmallow/ImageRoot_Marshmallow.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/marshmallow/ImageRoot_Marshmallow.java new file mode 100644 index 0000000000..4cc6b89304 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/marshmallow/ImageRoot_Marshmallow.java @@ -0,0 +1,31 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.marshmallow; + +/** + * @see https://android.googlesource.com/platform/art/+/marshmallow-release/runtime/image.h + */ +public enum ImageRoot_Marshmallow { + kResolutionMethod, + kImtConflictMethod, + kDefaultImt, + kCalleeSaveMethod, + kRefsOnlySaveMethod, + kRefsAndArgsSaveMethod, + kDexCaches, + kClassRoots, + kImageRootsMax, +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/marshmallow/ImageSections_Marshmallow.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/marshmallow/ImageSections_Marshmallow.java new file mode 100644 index 0000000000..3bf5606ff0 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/marshmallow/ImageSections_Marshmallow.java @@ -0,0 +1,98 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.marshmallow; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.file.formats.android.art.ArtHeader; +import ghidra.file.formats.android.art.ArtImageSections; + +public class ImageSections_Marshmallow extends ArtImageSections { + public final static int kSectionObjects = 0; + public final static int kSectionArtFields = 1; + public final static int kSectionArtMethods = 2; + public final static int kSectionInternedStrings = 3; + public final static int kSectionImageBitmap = 4; + public final static int kSectionCount = 5; // Number of elements in enum. + + public ImageSections_Marshmallow(BinaryReader reader, ArtHeader header) { + super(reader, header); + } + + @Override + public int get_kSectionObjects() { + return kSectionObjects; + } + + @Override + public int get_kSectionArtFields() { + return kSectionArtFields; + } + + @Override + public int get_kSectionArtMethods() { + return kSectionArtMethods; + } + + @Override + public int get_kSectionRuntimeMethods() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionImTables() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionIMTConflictTables() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionDexCacheArrays() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionInternedStrings() { + return kSectionInternedStrings; + } + + @Override + public int get_kSectionClassTable() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionStringReferenceOffsets() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionMetadata() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionImageBitmap() { + return kSectionImageBitmap; + } + + @Override + public int get_kSectionCount() { + return kSectionCount; + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/nougat/ArtHeader_Nougat.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/nougat/ArtHeader_Nougat.java new file mode 100644 index 0000000000..6c8c64d0f4 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/nougat/ArtHeader_Nougat.java @@ -0,0 +1,251 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.nougat; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverterUtil; +import ghidra.file.formats.android.art.*; +import ghidra.file.formats.android.util.DecompressionManager; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.program.model.listing.Program; +import ghidra.util.InvalidNameException; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.task.TaskMonitor; + +/** + * https://android.googlesource.com/platform/art/+/nougat-release/runtime/image.h + * https://android.googlesource.com/platform/art/+/nougat-release/runtime/image.cc + */ +public class ArtHeader_Nougat extends ArtHeader implements ArtCompression { + + protected int image_begin_; + protected int image_size_; + protected int oat_checksum_; + protected int oat_file_begin_; + protected int oat_data_begin_; + protected int oat_data_end_; + protected int oat_file_end_; + protected int boot_image_begin_; + protected int boot_image_size_; + protected int boot_oat_begin_; + protected int boot_oat_size_; + protected int patch_delta_; + protected int image_roots_; + protected int pointer_size_; + protected int compile_pic_; + protected int is_pic_; + protected int storage_mode_; + protected int data_size_; + + protected ArtImageSections sections; + + private long _compressedOffset; + + public ArtHeader_Nougat(BinaryReader reader) throws IOException { + super(reader); + parse(reader); + } + + protected ArtImageSections getImageSections(BinaryReader reader) { + return new ImageSections_Nougat(reader, this); + } + + @Override + protected void parse(BinaryReader reader) throws IOException { + image_begin_ = reader.readNextInt(); + image_size_ = reader.readNextInt(); + oat_checksum_ = reader.readNextInt(); + oat_file_begin_ = reader.readNextInt(); + oat_data_begin_ = reader.readNextInt(); + oat_data_end_ = reader.readNextInt(); + oat_file_end_ = reader.readNextInt(); + boot_image_begin_ = reader.readNextInt(); + boot_image_size_ = reader.readNextInt(); + boot_oat_begin_ = reader.readNextInt(); + boot_oat_size_ = reader.readNextInt(); + patch_delta_ = reader.readNextInt(); + image_roots_ = reader.readNextInt(); + pointer_size_ = reader.readNextInt(); + compile_pic_ = reader.readNextInt(); + is_pic_ = reader.readNextInt(); + + sections = getImageSections(reader); + + sections.parseSections(reader); + parseImageMethods(reader); + + storage_mode_ = reader.readNextInt(); + data_size_ = reader.readNextInt(); + + //The compressed offset is immediately following this header. + _compressedOffset = reader.getPointerIndex(); + + reader = DecompressionManager.decompress(reader, this, TaskMonitor.DUMMY); + + sections.parse(reader); + } + + @Override + public int getArtMethodCountForVersion() { + return ImageMethod_Nougat.kImageMethodsCount.ordinal(); + } + + @Override + public int getImageBegin() { + return image_begin_; + } + + @Override + public int getImageSize() { + return image_size_; + } + + /** + * Boot oat begin and end (app image headers only). + * @return the boot oat begin address + */ + public int getBootOatBegin() { + return boot_oat_begin_; + } + + /** + * Boot oat begin and end (app image headers only). + * @return the boot oat size + */ + public int getBootOatSize() { + return boot_oat_size_; + } + + @Override + public int getOatFileBegin() { + return oat_file_begin_; + } + + @Override + public int getOatFileEnd() { + return oat_file_end_; + } + + @Override + public int getOatDataBegin() { + return oat_data_begin_; + } + + @Override + public int getOatDataEnd() { + return oat_data_end_; + } + + /** + * Boolean (0 or 1) to denote if the image can be mapped at a random address, + * this only refers to the .art file. + * Currently, app oat files do not depend on their app image. + * There are no pointers from the app oat code to the app image. + * @return true of position independent code, or false if dependent. + */ + public int getIsPic() { + return is_pic_; + } + + @Override + public ArtStorageMode getStorageMode() throws UnknownArtStorageModeException { + return ArtStorageMode.get(storage_mode_); + } + + @Override + public long getCompressedOffset() { + return _compressedOffset; + } + + @Override + public int getCompressedSize() { + return data_size_; + } + + @Override + public long getDecompressedOffset() { + return getCompressedOffset(); + } + + @Override + public int getDecompressedSize() { + return image_size_; + } + + /** + * Checksum of the oat file we link to for load time sanity check. + */ + @Override + public int getOatChecksum() { + return oat_checksum_; + } + + @Override + public int getPointerSize() { + return pointer_size_; + } + + @Override + public void markup(Program program, TaskMonitor monitor) throws Exception { + DecompressionManager.decompressOverMemory(program, this, monitor); + + sections.markup(program, monitor); + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = (Structure) super.toDataType(); + + String className = StructConverterUtil.parseName(ArtHeader_Nougat.class); + try { + structure.setName(className); + } + catch (InvalidNameException e) { + //ignore, just use original name should this fail + } + + structure.add(DWORD, "image_begin_", null); + structure.add(DWORD, "image_size_", null); + structure.add(DWORD, "oat_checksum_", null); + structure.add(DWORD, "oat_file_begin_", null); + structure.add(DWORD, "oat_data_begin_", null); + structure.add(DWORD, "oat_data_end_", null); + structure.add(DWORD, "oat_file_end_", null); + structure.add(DWORD, "boot_image_begin_", null); + structure.add(DWORD, "boot_image_size_", null); + structure.add(DWORD, "boot_oat_begin_", null); + structure.add(DWORD, "boot_oat_size_", null); + structure.add(DWORD, "patch_delta_", null); + structure.add(DWORD, "image_roots_", null); + structure.add(DWORD, "pointer_size_", null); + structure.add(DWORD, "compile_pic_", null); + structure.add(DWORD, "is_pic_", null); + + for (int i = 0; i < sections.getSectionList().size(); ++i) { + structure.add(sections.getSectionList().get(i).toDataType(), "section_" + i, null); + } + for (int i = 0; i < imageMethodsList.size(); ++i) { + structure.add(QWORD, "image_method_" + i, null); + } + structure.add(DWORD, "storage_mode_", null); + structure.add(DWORD, "data_size_", null); + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/nougat/ArtHeader_NougatMR2Pixel.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/nougat/ArtHeader_NougatMR2Pixel.java new file mode 100644 index 0000000000..31bbf6bf37 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/nougat/ArtHeader_NougatMR2Pixel.java @@ -0,0 +1,59 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.nougat; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverterUtil; +import ghidra.file.formats.android.art.ArtImageSections; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.util.InvalidNameException; +import ghidra.util.exception.DuplicateNameException; + +/** + * https://android.googlesource.com/platform/art/+/nougat-mr2-pixel-release/runtime/image.h + */ +public class ArtHeader_NougatMR2Pixel extends ArtHeader_Nougat { + + public ArtHeader_NougatMR2Pixel(BinaryReader reader) throws IOException { + super(reader); + } + + protected ArtImageSections getImageSections(BinaryReader reader) { + return new ImageSections_NougatMR2Pixel(reader, this); + } + + @Override + public int getArtMethodCountForVersion() { + return ImageMethod_Nougat.kImageMethodsCount.ordinal(); + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = (Structure) super.toDataType(); + String className = StructConverterUtil.parseName(ArtHeader_NougatMR2Pixel.class); + try { + structure.setName(className); + } + catch (InvalidNameException e) { + //ignore + } + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/nougat/ImageMethod_Nougat.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/nougat/ImageMethod_Nougat.java new file mode 100644 index 0000000000..fa635f90ef --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/nougat/ImageMethod_Nougat.java @@ -0,0 +1,29 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.nougat; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/pie-release/runtime/image.h + */ +public enum ImageMethod_Nougat { + kResolutionMethod, + kImtConflictMethod, + kImtUnimplementedMethod, + kCalleeSaveMethod, + kRefsOnlySaveMethod, + kRefsAndArgsSaveMethod, + kImageMethodsCount, // Number of elements in enum. +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/nougat/ImageRoot_Nougat.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/nougat/ImageRoot_Nougat.java new file mode 100644 index 0000000000..0a362685c0 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/nougat/ImageRoot_Nougat.java @@ -0,0 +1,23 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.nougat; + +/** + * @see https://android.googlesource.com/platform/art/+/nougat-release/runtime/image.h + */ +public enum ImageRoot_Nougat { + kDexCaches, kClassRoots, kImageRootsMax, +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/nougat/ImageSections_Nougat.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/nougat/ImageSections_Nougat.java new file mode 100644 index 0000000000..3452f4ff8a --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/nougat/ImageSections_Nougat.java @@ -0,0 +1,102 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.nougat; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.file.formats.android.art.ArtHeader; +import ghidra.file.formats.android.art.ArtImageSections; + +public class ImageSections_Nougat extends ArtImageSections { + public final static int kSectionObjects = 0; + public final static int kSectionArtFields = 1; + public final static int kSectionArtMethods = 2; + public final static int kSectionRuntimeMethods = 3; + public final static int kSectionIMTConflictTables = 4; + public final static int kSectionDexCacheArrays = 5; + public final static int kSectionInternedStrings = 6; + public final static int kSectionClassTable = 7; + public final static int kSectionImageBitmap = 8; + public final static int kSectionCount = 9; // Number of elements in enum. + + public ImageSections_Nougat(BinaryReader reader, ArtHeader header) { + super(reader, header); + } + + @Override + public int get_kSectionObjects() { + return kSectionObjects; + } + + @Override + public int get_kSectionArtFields() { + return kSectionArtFields; + } + + @Override + public int get_kSectionArtMethods() { + return kSectionArtMethods; + } + + @Override + public int get_kSectionRuntimeMethods() { + return kSectionRuntimeMethods; + } + + @Override + public int get_kSectionImTables() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionIMTConflictTables() { + return kSectionIMTConflictTables; + } + + @Override + public int get_kSectionDexCacheArrays() { + return kSectionDexCacheArrays; + } + + @Override + public int get_kSectionInternedStrings() { + return kSectionInternedStrings; + } + + @Override + public int get_kSectionClassTable() { + return kSectionClassTable; + } + + @Override + public int get_kSectionStringReferenceOffsets() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionMetadata() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionImageBitmap() { + return kSectionImageBitmap; + } + + @Override + public int get_kSectionCount() { + return kSectionCount; + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/nougat/ImageSections_NougatMR2Pixel.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/nougat/ImageSections_NougatMR2Pixel.java new file mode 100644 index 0000000000..8886244da8 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/nougat/ImageSections_NougatMR2Pixel.java @@ -0,0 +1,103 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.nougat; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.file.formats.android.art.ArtHeader; +import ghidra.file.formats.android.art.ArtImageSections; + +public class ImageSections_NougatMR2Pixel extends ArtImageSections { + public final static int kSectionObjects = 0; + public final static int kSectionArtFields = 1; + public final static int kSectionArtMethods = 2; + public final static int kSectionRuntimeMethods = 3; + public final static int kSectionImTables = 4; + public final static int kSectionIMTConflictTables = 5; + public final static int kSectionDexCacheArrays = 6; + public final static int kSectionInternedStrings = 7; + public final static int kSectionClassTable = 8; + public final static int kSectionImageBitmap = 9; + public final static int kSectionCount = 10; // Number of elements in enum. + + public ImageSections_NougatMR2Pixel(BinaryReader reader, ArtHeader header) { + super(reader, header); + } + + @Override + public int get_kSectionObjects() { + return kSectionObjects; + } + + @Override + public int get_kSectionArtFields() { + return kSectionArtFields; + } + + @Override + public int get_kSectionArtMethods() { + return kSectionArtMethods; + } + + @Override + public int get_kSectionRuntimeMethods() { + return kSectionRuntimeMethods; + } + + @Override + public int get_kSectionImTables() { + return kSectionImTables; + } + + @Override + public int get_kSectionIMTConflictTables() { + return kSectionIMTConflictTables; + } + + @Override + public int get_kSectionDexCacheArrays() { + return kSectionDexCacheArrays; + } + + @Override + public int get_kSectionInternedStrings() { + return kSectionInternedStrings; + } + + @Override + public int get_kSectionClassTable() { + return kSectionClassTable; + } + + @Override + public int get_kSectionStringReferenceOffsets() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionMetadata() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionImageBitmap() { + return kSectionImageBitmap; + } + + @Override + public int get_kSectionCount() { + return kSectionCount; + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/oreo/ArtHeader_Oreo.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/oreo/ArtHeader_Oreo.java new file mode 100644 index 0000000000..d3837fcce8 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/oreo/ArtHeader_Oreo.java @@ -0,0 +1,60 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.oreo; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverterUtil; +import ghidra.file.formats.android.art.ArtImageSections; +import ghidra.file.formats.android.art.nougat.ArtHeader_NougatMR2Pixel; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.util.InvalidNameException; +import ghidra.util.exception.DuplicateNameException; + +/** + * https://android.googlesource.com/platform/art/+/oreo-release/runtime/image.h + */ +public class ArtHeader_Oreo extends ArtHeader_NougatMR2Pixel { + + public ArtHeader_Oreo(BinaryReader reader) throws IOException { + super(reader); + } + + protected ArtImageSections getImageSections(BinaryReader reader) { + return new ImageSections_Oreo(reader, this); + } + + @Override + public int getArtMethodCountForVersion() { + return ImageMethod_Oreo.kImageMethodsCount.ordinal(); + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = (Structure) super.toDataType(); + String className = StructConverterUtil.parseName(ArtHeader_Oreo.class); + try { + structure.setName(className); + } + catch (InvalidNameException e) { + //ignore + } + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/oreo/ArtHeader_OreoMR1.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/oreo/ArtHeader_OreoMR1.java new file mode 100644 index 0000000000..e61493ad68 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/oreo/ArtHeader_OreoMR1.java @@ -0,0 +1,59 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.oreo; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverterUtil; +import ghidra.file.formats.android.art.ArtImageSections; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.util.InvalidNameException; +import ghidra.util.exception.DuplicateNameException; + +/** + * https://android.googlesource.com/platform/art/+/oreo-mr1-release/runtime/image.h + */ +public class ArtHeader_OreoMR1 extends ArtHeader_Oreo { + + public ArtHeader_OreoMR1(BinaryReader reader) throws IOException { + super(reader); + } + + protected ArtImageSections getImageSections(BinaryReader reader) { + return new ImageSections_OreoMR1(reader, this); + } + + @Override + public int getArtMethodCountForVersion() { + return ImageMethod_Oreo.kImageMethodsCount.ordinal(); + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = (Structure) super.toDataType(); + String className = StructConverterUtil.parseName(ArtHeader_OreoMR1.class); + try { + structure.setName(className); + } + catch (InvalidNameException e) { + //ignore + } + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/oreo/ImageMethod_Oreo.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/oreo/ImageMethod_Oreo.java new file mode 100644 index 0000000000..34b84ca9de --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/oreo/ImageMethod_Oreo.java @@ -0,0 +1,30 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.oreo; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/oreo-release/runtime/image.h + */ +public enum ImageMethod_Oreo { + kResolutionMethod, + kImtConflictMethod, + kImtUnimplementedMethod, + kSaveAllCalleeSavesMethod, + kSaveRefsOnlyMethod, + kSaveRefsAndArgsMethod, + kSaveEverythingMethod, + kImageMethodsCount, // Number of elements in enum. +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/oreo/ImageRoot_Oreo.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/oreo/ImageRoot_Oreo.java new file mode 100644 index 0000000000..fee0334063 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/oreo/ImageRoot_Oreo.java @@ -0,0 +1,26 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.oreo; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/oreo-release/runtime/image.h + */ +public enum ImageRoot_Oreo { + kDexCaches, + kClassRoots, + kClassLoader, // App image only. + kImageRootsMax, +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/oreo/ImageSections_Oreo.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/oreo/ImageSections_Oreo.java new file mode 100644 index 0000000000..c046b0050e --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/oreo/ImageSections_Oreo.java @@ -0,0 +1,117 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.oreo; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.file.formats.android.art.ArtHeader; +import ghidra.file.formats.android.art.ArtImageSections; + +public class ImageSections_Oreo extends ArtImageSections { + public final static int kSectionObjects = 0; + public final static int kSectionArtFields = 1; + public final static int kSectionArtMethods = 2; + public final static int kSectionRuntimeMethods = 3; + public final static int kSectionImTables = 4; + public final static int kSectionIMTConflictTables = 5; + public final static int kSectionDexCacheArrays = 6; + public final static int kSectionInternedStrings = 7; + public final static int kSectionClassTable = 8; + public final static int kSectionImageBitmap = 9; + public final static int kSectionCount = 10; // Number of elements in enum. + + enum ImageSections { + kSectionObjects, + kSectionArtFields, + kSectionArtMethods, + kSectionRuntimeMethods, + kSectionImTables, + kSectionIMTConflictTables, + kSectionDexCacheArrays, + kSectionInternedStrings, + kSectionClassTable, + kSectionImageBitmap, + kSectionCount, // Number of elements in enum. + }; + + public ImageSections_Oreo(BinaryReader reader, ArtHeader header) { + super(reader, header); + } + + @Override + public int get_kSectionObjects() { + return kSectionObjects; + } + + @Override + public int get_kSectionArtFields() { + return kSectionArtFields; + } + + @Override + public int get_kSectionArtMethods() { + return kSectionArtMethods; + } + + @Override + public int get_kSectionRuntimeMethods() { + return kSectionRuntimeMethods; + } + + @Override + public int get_kSectionImTables() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionIMTConflictTables() { + return kSectionIMTConflictTables; + } + + @Override + public int get_kSectionDexCacheArrays() { + return kSectionDexCacheArrays; + } + + @Override + public int get_kSectionInternedStrings() { + return kSectionInternedStrings; + } + + @Override + public int get_kSectionClassTable() { + return kSectionClassTable; + } + + @Override + public int get_kSectionStringReferenceOffsets() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionMetadata() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionImageBitmap() { + return kSectionImageBitmap; + } + + @Override + public int get_kSectionCount() { + return kSectionCount; + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/oreo/ImageSections_OreoMR1.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/oreo/ImageSections_OreoMR1.java new file mode 100644 index 0000000000..657de24612 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/oreo/ImageSections_OreoMR1.java @@ -0,0 +1,103 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.oreo; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.file.formats.android.art.ArtHeader; +import ghidra.file.formats.android.art.ArtImageSections; + +public class ImageSections_OreoMR1 extends ArtImageSections { + public final static int kSectionObjects = 0; + public final static int kSectionArtFields = 1; + public final static int kSectionArtMethods = 2; + public final static int kSectionRuntimeMethods = 3; + public final static int kSectionImTables = 4; + public final static int kSectionIMTConflictTables = 5; + public final static int kSectionDexCacheArrays = 6; + public final static int kSectionInternedStrings = 7; + public final static int kSectionClassTable = 8; + public final static int kSectionImageBitmap = 9; + public final static int kSectionCount = 10; // Number of elements in enum. + + public ImageSections_OreoMR1(BinaryReader reader, ArtHeader header) { + super(reader, header); + } + + @Override + public int get_kSectionObjects() { + return kSectionObjects; + } + + @Override + public int get_kSectionArtFields() { + return kSectionArtFields; + } + + @Override + public int get_kSectionArtMethods() { + return kSectionArtMethods; + } + + @Override + public int get_kSectionRuntimeMethods() { + return kSectionRuntimeMethods; + } + + @Override + public int get_kSectionImTables() { + return kSectionImTables; + } + + @Override + public int get_kSectionIMTConflictTables() { + return kSectionIMTConflictTables; + } + + @Override + public int get_kSectionDexCacheArrays() { + return kSectionDexCacheArrays; + } + + @Override + public int get_kSectionInternedStrings() { + return kSectionInternedStrings; + } + + @Override + public int get_kSectionClassTable() { + return kSectionClassTable; + } + + @Override + public int get_kSectionStringReferenceOffsets() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionMetadata() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionImageBitmap() { + return kSectionImageBitmap; + } + + @Override + public int get_kSectionCount() { + return kSectionCount; + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/pie/ArtHeader_Pie.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/pie/ArtHeader_Pie.java new file mode 100644 index 0000000000..ea3c5bff2f --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/pie/ArtHeader_Pie.java @@ -0,0 +1,244 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.pie; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverterUtil; +import ghidra.file.formats.android.art.*; +import ghidra.file.formats.android.util.DecompressionManager; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.program.model.listing.Program; +import ghidra.util.InvalidNameException; +import ghidra.util.exception.DuplicateNameException; +import ghidra.util.task.TaskMonitor; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/pie-release/runtime/image.h + */ +public class ArtHeader_Pie extends ArtHeader implements ArtCompression { + + protected int image_begin_; + protected int image_size_; + protected int oat_checksum_; + protected int oat_file_begin_; + protected int oat_data_begin_; + protected int oat_data_end_; + protected int oat_file_end_; + private int boot_image_begin_; + private int boot_image_size_; + private int boot_oat_begin_; + private int boot_oat_size_; + protected int patch_delta_; + protected int image_roots_; + protected int pointer_size_; + protected int compile_pic_; + private int is_pic_; + + protected long[] image_methods_ = new long[ImageMethod_Pie.kImageMethodsCount.ordinal()]; + private int storage_mode_; + private int data_size_; + + private ArtImageSections sections; + + private long _compressedOffset; + + public ArtHeader_Pie(BinaryReader reader) throws IOException { + super(reader); + parse(reader); + } + + @Override + protected void parse(BinaryReader reader) throws IOException { + image_begin_ = reader.readNextInt(); + image_size_ = reader.readNextInt(); + oat_checksum_ = reader.readNextInt(); + oat_file_begin_ = reader.readNextInt(); + oat_data_begin_ = reader.readNextInt(); + oat_data_end_ = reader.readNextInt(); + oat_file_end_ = reader.readNextInt(); + boot_image_begin_ = reader.readNextInt(); + boot_image_size_ = reader.readNextInt(); + boot_oat_begin_ = reader.readNextInt(); + boot_oat_size_ = reader.readNextInt(); + patch_delta_ = reader.readNextInt(); + image_roots_ = reader.readNextInt(); + pointer_size_ = reader.readNextInt(); + compile_pic_ = reader.readNextInt(); + is_pic_ = reader.readNextInt(); + + sections = new ImageSections_Pie(reader, this); + sections.parseSections(reader); + parseImageMethods(reader); + + storage_mode_ = reader.readNextInt(); + data_size_ = reader.readNextInt(); + + _compressedOffset = reader.getPointerIndex(); + + reader = DecompressionManager.decompress(reader, this, TaskMonitor.DUMMY); + + // NOTE: + // cannot parse the sections until after the blocks are decompressed! + + sections.parse(reader); + + } + + @Override + public int getImageBegin() { + return image_begin_; + } + + @Override + public int getImageSize() { + return image_size_; + } + + @Override + public int getOatChecksum() { + return oat_checksum_; + } + + @Override + public int getOatFileBegin() { + return oat_file_begin_; + } + + @Override + public int getOatFileEnd() { + return oat_file_end_; + } + + @Override + public int getOatDataBegin() { + return oat_data_begin_; + } + + @Override + public int getOatDataEnd() { + return oat_data_end_; + } + + public int getBootOatBegin() { + return boot_oat_begin_; + } + + public int getBootOatSize() { + return boot_oat_size_; + } + + public int getBootImageBegin() { + return boot_image_begin_; + } + + public int getBootImageSize() { + return boot_image_size_; + } + + public int isPIC() { + return is_pic_; + } + + public int getCompilePIC() { + return compile_pic_; + } + + @Override + public ArtStorageMode getStorageMode() throws UnknownArtStorageModeException { + return ArtStorageMode.get(storage_mode_); + } + + @Override + public long getCompressedOffset() { + return _compressedOffset; + } + + @Override + public int getCompressedSize() { + return data_size_; + } + + @Override + public long getDecompressedOffset() { + return getCompressedOffset(); + } + + @Override + public int getDecompressedSize() { + return image_size_; + } + + @Override + public int getPointerSize() { + return pointer_size_; + } + + @Override + public void markup(Program program, TaskMonitor monitor) throws Exception { + DecompressionManager.decompressOverMemory(program, this, monitor); + + sections.markup(program, monitor); + } + + @Override + public int getArtMethodCountForVersion() { + return ImageMethod_Pie.kImageMethodsCount.ordinal(); + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = (Structure) super.toDataType(); + + String className = StructConverterUtil.parseName(ArtHeader_Pie.class); + try { + structure.setName(className); + } + catch (InvalidNameException e) { + //ignore + } + + structure.add(DWORD, "image_begin_", null); + structure.add(DWORD, "image_size_", null); + structure.add(DWORD, "oat_checksum_", null); + structure.add(DWORD, "oat_file_begin_", null); + structure.add(DWORD, "oat_data_begin_", null); + structure.add(DWORD, "oat_data_end_", null); + structure.add(DWORD, "oat_file_end_", null); + structure.add(DWORD, "boot_image_begin_", null); + structure.add(DWORD, "boot_image_size_", null); + structure.add(DWORD, "boot_oat_begin_", null); + structure.add(DWORD, "boot_oat_size_", null); + structure.add(DWORD, "patch_delta_", null); + structure.add(DWORD, "image_roots_", null); + structure.add(DWORD, "pointer_size_", null); + structure.add(DWORD, "compile_pic_", null); + structure.add(DWORD, "is_pic_", null); + + for (int i = 0; i < sections.getSectionList().size(); ++i) { + structure.add(sections.getSectionList().get(i).toDataType(), "section_" + i, null); + } + for (int i = 0; i < image_methods_.length; ++i) { + structure.add(QWORD, "image_method_" + i, null); + } + structure.add(DWORD, "storage_mode_", null); + structure.add(DWORD, "data_size_", null); + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/pie/ImageMethod_Pie.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/pie/ImageMethod_Pie.java new file mode 100644 index 0000000000..dc6103b4c7 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/pie/ImageMethod_Pie.java @@ -0,0 +1,32 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.pie; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/pie-release/runtime/image.h + */ +public enum ImageMethod_Pie { + kResolutionMethod, + kImtConflictMethod, + kImtUnimplementedMethod, + kSaveAllCalleeSavesMethod, + kSaveRefsOnlyMethod, + kSaveRefsAndArgsMethod, + kSaveEverythingMethod, + kSaveEverythingMethodForClinit, + kSaveEverythingMethodForSuspendCheck, + kImageMethodsCount, // Number of elements in enum. +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/pie/ImageRoot_Pie.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/pie/ImageRoot_Pie.java new file mode 100644 index 0000000000..9cfca767e5 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/pie/ImageRoot_Pie.java @@ -0,0 +1,26 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.pie; + +/** + * https://android.googlesource.com/platform/art/+/nougat-release/runtime/image.h + */ +public enum ImageRoot_Pie { + kDexCaches, + kClassRoots, + kClassLoader, // App image only. + kImageRootsMax, +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/pie/ImageSections_Pie.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/pie/ImageSections_Pie.java new file mode 100644 index 0000000000..3cf9f8beb0 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/art/pie/ImageSections_Pie.java @@ -0,0 +1,103 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.art.pie; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.file.formats.android.art.ArtHeader; +import ghidra.file.formats.android.art.ArtImageSections; + +public class ImageSections_Pie extends ArtImageSections { + public final static int kSectionObjects = 0; + public final static int kSectionArtFields = 1; + public final static int kSectionArtMethods = 2; + public final static int kSectionRuntimeMethods = 3; + public final static int kSectionImTables = 4; + public final static int kSectionIMTConflictTables = 5; + public final static int kSectionDexCacheArrays = 6; + public final static int kSectionInternedStrings = 7; + public final static int kSectionClassTable = 8; + public final static int kSectionImageBitmap = 9; + public final static int kSectionCount = 10; // Number of elements in enum. + + public ImageSections_Pie(BinaryReader reader, ArtHeader header) { + super(reader, header); + } + + @Override + public int get_kSectionObjects() { + return kSectionObjects; + } + + @Override + public int get_kSectionArtFields() { + return kSectionArtFields; + } + + @Override + public int get_kSectionArtMethods() { + return kSectionArtMethods; + } + + @Override + public int get_kSectionRuntimeMethods() { + return kSectionRuntimeMethods; + } + + @Override + public int get_kSectionImTables() { + return kSectionImTables; + } + + @Override + public int get_kSectionIMTConflictTables() { + return kSectionIMTConflictTables; + } + + @Override + public int get_kSectionDexCacheArrays() { + return kSectionDexCacheArrays; + } + + @Override + public int get_kSectionInternedStrings() { + return kSectionInternedStrings; + } + + @Override + public int get_kSectionClassTable() { + return kSectionClassTable; + } + + @Override + public int get_kSectionStringReferenceOffsets() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionMetadata() { + return UNSUPPORTED_SECTION; + } + + @Override + public int get_kSectionImageBitmap() { + return kSectionImageBitmap; + } + + @Override + public int get_kSectionCount() { + return kSectionCount; + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImage.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImage.java index dad9f25ee0..7899efc8f7 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImage.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImage.java @@ -32,29 +32,29 @@ public class BootImage implements StructConverter { private int secondStageAddress; private int tagsAddress; private int pageSize; - private int [] unused; + private int[] unused; private String name; private String commandLine; - private int [] id; + private int[] id; public BootImage(ByteProvider provider) throws IOException { - this( new BinaryReader( provider, true ) ); + this(new BinaryReader(provider, true)); } public BootImage(BinaryReader reader) throws IOException { - magic = reader.readNextAsciiString( BootImageConstants.BOOT_IMAGE_MAGIC_SIZE ); - kernelSize = reader.readNextInt(); - kernelAddress = reader.readNextInt(); - ramDiskSize = reader.readNextInt(); - ramDiskAddress = reader.readNextInt(); - secondStageSize = reader.readNextInt(); - secondStageAddress = reader.readNextInt(); - tagsAddress = reader.readNextInt(); - pageSize = reader.readNextInt(); - unused = reader.readNextIntArray( 2 ); - name = reader.readNextAsciiString( BootImageConstants.BOOT_NAME_SIZE ); - commandLine = reader.readNextAsciiString( BootImageConstants.BOOT_ARGS_SIZE ); - id = reader.readNextIntArray( 8 ); + magic = reader.readNextAsciiString(BootImageConstants.BOOT_MAGIC_SIZE); + kernelSize = reader.readNextInt(); + kernelAddress = reader.readNextInt(); + ramDiskSize = reader.readNextInt(); + ramDiskAddress = reader.readNextInt(); + secondStageSize = reader.readNextInt(); + secondStageAddress = reader.readNextInt(); + tagsAddress = reader.readNextInt(); + pageSize = reader.readNextInt(); + unused = reader.readNextIntArray(2); + name = reader.readNextAsciiString(BootImageConstants.BOOT_NAME_SIZE); + commandLine = reader.readNextAsciiString(BootImageConstants.BOOT_ARGS_SIZE); + id = reader.readNextIntArray(8); } public String getMagic() { @@ -74,7 +74,7 @@ public class BootImage implements StructConverter { } public int getKernelSizePageAligned() { - int remainder = getPageSize() - ( getKernelSize() % getPageSize() ); + int remainder = getPageSize() - (getKernelSize() % getPageSize()); return getKernelSize() + remainder; } @@ -91,7 +91,7 @@ public class BootImage implements StructConverter { } public int getRamDiskSizePageAligned() { - int remainder = getPageSize() - ( getRamDiskSize() % getPageSize() ); + int remainder = getPageSize() - (getRamDiskSize() % getPageSize()); return getRamDiskSize() + remainder; } @@ -104,11 +104,11 @@ public class BootImage implements StructConverter { } public int getSecondStageOffset() { - return getRamDiskOffset() + ( getRamDiskSizePageAligned() * getPageSize() ); + return getRamDiskOffset() + (getRamDiskSizePageAligned() * getPageSize()); } public int getSecondStageSizePageAligned() { - int remainder = getPageSize() - ( getSecondStageSize() % getPageSize() ); + int remainder = getPageSize() - (getSecondStageSize() % getPageSize()); return getSecondStageSize() + remainder; } @@ -120,7 +120,7 @@ public class BootImage implements StructConverter { return pageSize; } - public int [] getUnused() { + public int[] getUnused() { return unused; } @@ -132,25 +132,25 @@ public class BootImage implements StructConverter { return commandLine; } - public int [] getId() { + public int[] getId() { return id; } @Override public DataType toDataType() throws DuplicateNameException, IOException { - Structure structure = new StructureDataType( "boot_img_hdr", 0 ); - structure.add( UTF8, BootImageConstants.BOOT_IMAGE_MAGIC_SIZE, "magic", null ); - structure.add( DWORD, "kernelSize", null ); - structure.add( DWORD, "kernelAddress", null ); - structure.add( DWORD, "ramDiskSize", null ); - structure.add( DWORD, "ramDiskAddress", null ); - structure.add( DWORD, "secondStageSize", null ); - structure.add( DWORD, "secondStageAddress", null ); - structure.add( DWORD, "tagsAddress", null ); - structure.add( DWORD, "pageSize", null ); + Structure structure = new StructureDataType("boot_img_hdr", 0); + structure.add(UTF8, BootImageConstants.BOOT_MAGIC_SIZE, "magic", null); + structure.add(DWORD, "kernelSize", null); + structure.add(DWORD, "kernelAddress", null); + structure.add(DWORD, "ramDiskSize", null); + structure.add(DWORD, "ramDiskAddress", null); + structure.add(DWORD, "secondStageSize", null); + structure.add(DWORD, "secondStageAddress", null); + structure.add(DWORD, "tagsAddress", null); + structure.add(DWORD, "pageSize", null); structure.add(new ArrayDataType(DWORD, 2, DWORD.getLength()), "unused", null); - structure.add( UTF8, BootImageConstants.BOOT_NAME_SIZE, "name", null ); - structure.add( UTF8, BootImageConstants.BOOT_ARGS_SIZE, "commandLine", null ); + structure.add(UTF8, BootImageConstants.BOOT_NAME_SIZE, "name", null); + structure.add(UTF8, BootImageConstants.BOOT_ARGS_SIZE, "commandLine", null); structure.add(new ArrayDataType(DWORD, 8, DWORD.getLength()), "id", null); return structure; } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageAnalyzer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageAnalyzer.java index d1dd456b6a..ebff5bb123 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageAnalyzer.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageAnalyzer.java @@ -17,7 +17,9 @@ package ghidra.file.formats.android.bootimg; import ghidra.app.plugin.core.analysis.AnalysisWorker; import ghidra.app.plugin.core.analysis.AutoAnalysisManager; -import ghidra.app.util.bin.*; +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.bin.MemoryByteProvider; import ghidra.app.util.importer.MessageLog; import ghidra.file.analyzers.FileFormatAnalyzer; import ghidra.program.model.address.Address; @@ -32,7 +34,7 @@ public class BootImageAnalyzer extends FileFormatAnalyzer implements AnalysisWor @Override public String getName() { - return "Android Boot or Recovery Image Annotation"; + return "Android Boot, Recovery, or Vendor Image Annotation"; } @Override @@ -42,13 +44,13 @@ public class BootImageAnalyzer extends FileFormatAnalyzer implements AnalysisWor @Override public String getDescription() { - return "Annotates Android Boot and Recovery Image files."; + return "Annotates Android Boot, Recovery, or Vendor Image files."; } @Override public boolean canAnalyze(Program program) { try { - return BootImageUtil.isBootImage(program); + return BootImageUtil.isBootImage(program) || BootImageUtil.isVendorBootImage(program); } catch (Exception e) { // not a boot image @@ -61,63 +63,98 @@ public class BootImageAnalyzer extends FileFormatAnalyzer implements AnalysisWor return true; } - private MessageLog log; + private MessageLog messageLog; @Override public boolean analyze(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) throws Exception { - this.log = log; + this.messageLog = log; AutoAnalysisManager manager = AutoAnalysisManager.getAnalysisManager(program); return manager.scheduleWorker(this, null, false, monitor); } @Override - public boolean analysisWorkerCallback(Program program, Object workerContext, TaskMonitor monitor) - throws Exception, CancelledException { + public boolean analysisWorkerCallback(Program program, Object workerContext, + TaskMonitor monitor) throws Exception, CancelledException { Address address = program.getMinAddress(); ByteProvider provider = new MemoryByteProvider(program.getMemory(), address); BinaryReader reader = new BinaryReader(provider, true); - BootImage header = new BootImage(reader); + if (BootImageUtil.isBootImage(program)) { - if (!header.getMagic().equals(BootImageConstants.BOOT_IMAGE_MAGIC)) { - return false; + BootImageHeader header = BootImageHeaderFactory.getBootImageHeader(reader); + + if (!BootImageConstants.BOOT_MAGIC.equals(header.getMagic())) { + return false; + } + + DataType headerDataType = header.toDataType(); + + Data headerData = createData(program, address, headerDataType); + + if (headerData == null) { + messageLog.appendMsg("Unable to create header data."); + return false; + } + + createFragment(program, headerDataType.getName(), toAddr(program, 0), + toAddr(program, header.getPageSize())); + + if (header.getKernelSize() > 0) { + Address start = toAddr(program, header.getKernelOffset()); + Address end = toAddr(program, header.getKernelOffset() + header.getKernelSize()); + createFragment(program, BootImageConstants.KERNEL, start, end); + } + + if (header.getRamdiskSize() > 0) { + Address start = toAddr(program, header.getRamdiskOffset()); + Address end = toAddr(program, header.getRamdiskOffset() + header.getRamdiskSize()); + createFragment(program, BootImageConstants.RAMDISK, start, end); + } + + if (header.getSecondSize() > 0) { + Address start = toAddr(program, header.getSecondOffset()); + Address end = toAddr(program, header.getSecondOffset() + header.getSecondSize()); + createFragment(program, BootImageConstants.SECOND_STAGE, start, end); + } + + changeDataSettings(program, monitor); } + else if (BootImageUtil.isVendorBootImage(program)) { + VendorBootImageHeader header = + VendorBootImageHeaderFactory.getVendorBootImageHeader(reader); - DataType headerDataType = header.toDataType(); + if (!header.getMagic().equals(BootImageConstants.VENDOR_BOOT_MAGIC)) { + return false; + } - Data headerData = createData(program, address, headerDataType); + DataType headerDataType = header.toDataType(); - if (headerData == null) { - log.appendMsg("Unable to create header data."); + Data headerData = createData(program, address, headerDataType); + + if (headerData == null) { + messageLog.appendMsg("Unable to create header data."); + } + + createFragment(program, headerDataType.getName(), toAddr(program, 0), + toAddr(program, headerData.getLength())); + + if (header.getVendorRamdiskSize() > 0) { + Address start = toAddr(program, header.getVendorRamdiskOffset()); + Address end = toAddr(program, + header.getVendorRamdiskOffset() + header.getVendorRamdiskSize()); + createFragment(program, BootImageConstants.RAMDISK, start, end); + } + + if (header.getDtbSize() > 0) { + Address start = toAddr(program, header.getDtbOffset()); + Address end = toAddr(program, header.getDtbOffset() + header.getDtbSize()); + createFragment(program, BootImageConstants.DTB, start, end); + } } - createFragment(program, headerDataType.getName(), toAddr(program, 0), - toAddr(program, header.getPageSize())); - - if (header.getKernelSize() > 0) { - Address start = toAddr(program, header.getKernelOffset()); - Address end = toAddr(program, header.getKernelOffset() + header.getKernelSize()); - createFragment(program, BootImageConstants.KERNEL, start, end); - } - - if (header.getRamDiskSize() > 0) { - Address start = toAddr(program, header.getRamDiskOffset()); - Address end = toAddr(program, header.getRamDiskOffset() + header.getRamDiskSize()); - createFragment(program, BootImageConstants.RAMDISK, start, end); - } - - if (header.getSecondStageSize() > 0) { - Address start = toAddr(program, header.getSecondStageOffset()); - Address end = - toAddr(program, header.getSecondStageOffset() + header.getSecondStageSize()); - createFragment(program, BootImageConstants.SECOND_STAGE, start, end); - } - - changeDataSettings(program, monitor); - removeEmptyFragments(program); return true; diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageConstants.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageConstants.java index 892471a5a6..4ecf6695db 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageConstants.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageConstants.java @@ -15,22 +15,42 @@ */ package ghidra.file.formats.android.bootimg; +/** + * + * https://android.googlesource.com/platform/system/tools/mkbootimg/+/refs/heads/master/include/bootimg/bootimg.h + * + */ public final class BootImageConstants { - public final static String BOOT_IMAGE_MAGIC = "ANDROID!"; + public final static String BOOT_MAGIC = "ANDROID!"; + public final static int BOOT_MAGIC_SIZE = 8; + public final static int BOOT_NAME_SIZE = 16; + public final static int BOOT_ARGS_SIZE = 512; + public final static int BOOT_EXTRA_ARGS_SIZE = 1024; - public final static byte [] BOOT_IMAGE_MAGIC_BYTES = BOOT_IMAGE_MAGIC.getBytes(); + public final static int ID_SIZE = 8; - public final static int BOOT_IMAGE_MAGIC_SIZE = BOOT_IMAGE_MAGIC.length(); + public final static int V3_HEADER_SIZE = 4096; + public final static int V3_PAGE_SIZE = 4096; + public final static int V4_HEADER_SIZE = V3_HEADER_SIZE; + public final static int V4_PAGE_SIZE = V3_PAGE_SIZE; - public final static int BOOT_NAME_SIZE = 16; - - public final static int BOOT_ARGS_SIZE = 512; + public final static String VENDOR_BOOT_MAGIC = "VNDRBOOT"; + public final static int VENDOR_BOOT_MAGIC_SIZE = 8; + public final static int VENDOR_BOOT_ARGS_SIZE = 2048; + public final static int VENDOR_BOOT_NAME_SIZE = 16; - public final static String SECOND_STAGE = "second stage"; + public final static int VENDOR_RAMDISK_TYPE_NONE = 0; + public final static int VENDOR_RAMDISK_TYPE_PLATFORM = 1; + public final static int VENDOR_RAMDISK_TYPE_RECOVERY = 2; + public final static int VENDOR_RAMDISK_TYPE_DLKM = 3; + public final static int VENDOR_RAMDISK_NAME_SIZE = 32; + public final static int VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE = 16; - public final static String RAMDISK = "ramdisk"; - - public final static String KERNEL = "kernel"; + public final static String SECOND_STAGE = "second stage"; + public final static String RAMDISK = "ramdisk"; + public final static String KERNEL = "kernel"; + public final static String DTB = "dtb"; + public final static int HEADER_VERSION_OFFSET = 0x28; } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageFileSystem.java index c774026204..ede551438d 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageFileSystem.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageFileSystem.java @@ -15,11 +15,18 @@ */ package ghidra.file.formats.android.bootimg; -import java.io.*; -import java.util.*; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import ghidra.app.util.bin.ByteProvider; -import ghidra.formats.gfilesystem.*; +import ghidra.formats.gfilesystem.GFile; +import ghidra.formats.gfilesystem.GFileImpl; +import ghidra.formats.gfilesystem.GFileSystemBase; import ghidra.formats.gfilesystem.annotations.FileSystemInfo; import ghidra.formats.gfilesystem.factory.GFileSystemBaseFactory; import ghidra.util.exception.CancelledException; @@ -29,7 +36,7 @@ import ghidra.util.task.TaskMonitor; @FileSystemInfo(type = "androidbootimg", description = "Android Boot and Recovery Images", factory = GFileSystemBaseFactory.class) public class BootImageFileSystem extends GFileSystemBase { - private BootImage header; + private BootImageHeader header; private GFileImpl kernelFile; private GFileImpl ramdiskFile; private GFileImpl secondStageFile; @@ -41,15 +48,16 @@ public class BootImageFileSystem extends GFileSystemBase { @Override public boolean isValid(TaskMonitor monitor) throws IOException { - byte[] bytes = provider.readBytes(0, BootImageConstants.BOOT_IMAGE_MAGIC_SIZE); - return Arrays.equals(bytes, BootImageConstants.BOOT_IMAGE_MAGIC_BYTES); + byte[] bytes = provider.readBytes(0, BootImageConstants.BOOT_MAGIC_SIZE); + return Arrays.equals(bytes, BootImageConstants.BOOT_MAGIC.getBytes()); } @Override public void open(TaskMonitor monitor) throws IOException, CryptoException, CancelledException { - this.header = new BootImage(provider); - if (!header.getMagic().equals(BootImageConstants.BOOT_IMAGE_MAGIC)) { + this.header = BootImageHeaderFactory.getBootImageHeader(provider, true); + + if (!header.getMagic().equals(BootImageConstants.BOOT_MAGIC)) { throw new IOException("Invalid Android boot image file!"); } @@ -58,14 +66,15 @@ public class BootImageFileSystem extends GFileSystemBase { header.getKernelSize(), null); fileList.add(kernelFile); } - if (header.getRamDiskSize() > 0) { + + if (header.getRamdiskSize() > 0) { ramdiskFile = GFileImpl.fromFilename(this, root, BootImageConstants.RAMDISK, false, - header.getKernelSize(), null); + header.getRamdiskSize(), null); fileList.add(ramdiskFile); } - if (header.getSecondStageSize() > 0) { + if (header.getSecondSize() > 0) { secondStageFile = GFileImpl.fromFilename(this, root, BootImageConstants.SECOND_STAGE, - false, header.getKernelSize(), null); + false, header.getSecondSize(), null); fileList.add(secondStageFile); } } @@ -109,12 +118,12 @@ public class BootImageFileSystem extends GFileSystemBase { } else if (file == ramdiskFile) { byte[] ramDiskBytes = - provider.readBytes(header.getRamDiskOffset(), header.getRamDiskSize()); + provider.readBytes(header.getRamdiskOffset(), header.getRamdiskSize()); return new ByteArrayInputStream(ramDiskBytes); } else if (file == secondStageFile) { byte[] secondStageBytes = - provider.readBytes(header.getSecondStageOffset(), header.getSecondStageSize()); + provider.readBytes(header.getSecondOffset(), header.getSecondSize()); return new ByteArrayInputStream(secondStageBytes); } return null; diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeader.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeader.java new file mode 100644 index 0000000000..3920da986b --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeader.java @@ -0,0 +1,93 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.bootimg; + +import ghidra.app.util.bin.StructConverter; + +public abstract class BootImageHeader implements StructConverter { + /** + * Returns the Boot Image MAGIC value. + * @see BootImageConstants + * @return the Boot Image MAGIC value + */ + public abstract String getMagic(); + + /** + * Returns the page size, as defined in the header. + * @return the page size, as defined in the header + */ + public abstract int getPageSize(); + + /** + * Returns the kernel size, as defined in the header. + * @return the kernel size, as defined in the header + */ + public abstract int getKernelSize(); + + /** + * Returns the number of pages used to store the kernel. + * @return the number of pages used to store the kernel + */ + public abstract int getKernelPageCount(); + + /** + * Returns the kernel file offset + * @return the kernel file offset + */ + public abstract long getKernelOffset(); + + /** + * Returns the ramdisk size, as defined in the header. + * @return the ramdisk size, as defined in the header + */ + public abstract int getRamdiskSize(); + + /** + * Returns the number of pages used to store the ramdisk. + * @return the number of pages used to store the ramdisk + */ + public abstract int getRamdiskPageCount(); + + /** + * Returns the ramdisk file offset. + * @return the ramdisk file offset + */ + public abstract int getRamdiskOffset(); + + /** + * Returns the second stage size, as defined in the header. + * @return the second stage size, as defined in the header + */ + public abstract int getSecondSize(); + + /** + * Returns the number of pages used to store the second stage. + * @return the number of pages used to store the second stage + */ + public abstract int getSecondPageCount(); + + /** + * Returns the second stage file offset. + * @return the second stage file offset + */ + public abstract long getSecondOffset(); + + /** + * Returns the kernel commandline. + * @return the kernel commandline + */ + public abstract String getCommandLine(); +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeaderFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeaderFactory.java new file mode 100644 index 0000000000..f86260a17d --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeaderFactory.java @@ -0,0 +1,54 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.bootimg; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.ByteProvider; + +public final class BootImageHeaderFactory { + + public final static BootImageHeader getBootImageHeader(ByteProvider provider, + boolean littleEndian) throws IOException { + return getBootImageHeader(new BinaryReader(provider, littleEndian)); + } + + public final static BootImageHeader getBootImageHeader(BinaryReader reader) throws IOException { + + if (!BootImageUtil.isBootImage(reader)) { + throw new IOException("BootImageHeader magic not found."); + } + + int version = reader.readInt(BootImageConstants.HEADER_VERSION_OFFSET); + + switch (version) { + case 0: + return new BootImageHeaderV0(reader); + case 1: + return new BootImageHeaderV1(reader); + case 2: + return new BootImageHeaderV2(reader); + case 3: + return new BootImageHeaderV3(reader); + case 4: + return new BootImageHeaderV4(reader); + default: + throw new IOException("BootImageHeader unsupported version found: " + version); + } + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeaderV0.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeaderV0.java new file mode 100644 index 0000000000..b93ace0256 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeaderV0.java @@ -0,0 +1,207 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.bootimg; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.program.model.data.*; +import ghidra.util.NumericUtilities; +import ghidra.util.exception.DuplicateNameException; + +/** + * https://android.googlesource.com/platform/system/tools/mkbootimg/+/refs/heads/master/include/bootimg/bootimg.h#67 + */ +public class BootImageHeaderV0 extends BootImageHeader { + + private String magic; + private int kernel_size; + private int kernel_addr; + private int ramdisk_size; + private int ramdisk_addr; + private int second_size; + private int second_addr; + private int tags_addr; + private int page_size; + private int header_version; + private int os_version; + private String name; + private String cmdline; + private int[] id; + private String extra_cmdline; + + public BootImageHeaderV0(BinaryReader reader) throws IOException { + magic = reader.readNextAsciiString(BootImageConstants.BOOT_MAGIC_SIZE); + kernel_size = reader.readNextInt(); + kernel_addr = reader.readNextInt(); + ramdisk_size = reader.readNextInt(); + ramdisk_addr = reader.readNextInt(); + second_size = reader.readNextInt(); + second_addr = reader.readNextInt(); + tags_addr = reader.readNextInt(); + page_size = reader.readNextInt(); + header_version = reader.readNextInt(); + os_version = reader.readNextInt(); + name = reader.readNextAsciiString(BootImageConstants.BOOT_NAME_SIZE); + cmdline = reader.readNextAsciiString(BootImageConstants.BOOT_ARGS_SIZE); + id = reader.readNextIntArray(BootImageConstants.ID_SIZE); + extra_cmdline = reader.readNextAsciiString(BootImageConstants.BOOT_EXTRA_ARGS_SIZE); + } + + @Override + public String getMagic() { + return magic; + } + + @Override + public int getKernelSize() { + return kernel_size; + } + + @Override + public long getKernelOffset() { + return page_size;//see header comment... + } + + public int getKernelAddress() { + return kernel_addr; + } + + /** + * n = (kernel_size + page_size - 1) / page_size + */ + public int getKernelPageCount() { + return (int) NumericUtilities.getUnsignedAlignedValue(Integer.toUnsignedLong(kernel_size), + Integer.toUnsignedLong(page_size)); + } + + @Override + public int getRamdiskSize() { + return ramdisk_size; + } + + @Override + public int getRamdiskOffset() { + return page_size + getKernelPageCount() * page_size;//see header comment... + } + + public int getRamdiskAddress() { + return ramdisk_addr; + } + + /** + * m = (ramdisk_size + page_size - 1) / page_size + */ + public int getRamdiskPageCount() { + return (int) NumericUtilities.getUnsignedAlignedValue(Integer.toUnsignedLong(ramdisk_size), + Integer.toUnsignedLong(page_size)); + } + + @Override + public int getSecondSize() { + return second_size; + } + + @Override + public long getSecondOffset() { + return page_size + (getKernelPageCount() + getRamdiskPageCount()) * page_size;//see header comment... + } + + public int getSecondAddress() { + return second_addr; + } + + /** + * o = (second_size + page_size - 1) / page_size + */ + public int getSecondPageCount() { + return (int) NumericUtilities.getUnsignedAlignedValue(Integer.toUnsignedLong(second_size), + Integer.toUnsignedLong(page_size)); + } + + /** + * Physical address for kernel tags (if required) + * @return physical address for kernel tags + */ + public int getTagsAddress() { + return tags_addr; + } + + @Override + public int getPageSize() { + return page_size; + } + + /** + * Version of the boot image header. + * @return version of the boot image header + */ + public int getHeaderVersion() { + return header_version; + } + + /** + * Operating system version and security patch level. + * For version "A.B.C" and patch level "Y-M-D": + * (7 bits for each of A, B, C; 7 bits for (Y-2000), 4 bits for M) + * os_version = A[31:25] B[24:18] C[17:11] (Y-2000)[10:4] M[3:0] + * @return OS version + */ + public int getOSVersion() { + return os_version; + } + + public String getName() { + return name; + } + + @Override + public String getCommandLine() { + return cmdline; + } + + public int[] getId() { + return id; + } + + public String getExtraCommandLine() { + return extra_cmdline; + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = new StructureDataType("boot_img_hdr_v0", 0); + structure.add(UTF8, BootImageConstants.BOOT_MAGIC_SIZE, "magic", null); + structure.add(DWORD, "kernel_size", null); + structure.add(DWORD, "kernel_addr", null); + structure.add(DWORD, "ramdisk_size", null); + structure.add(DWORD, "ramdisk_addr", null); + structure.add(DWORD, "second_size", null); + structure.add(DWORD, "second_addr", null); + structure.add(DWORD, "tags_addr", null); + structure.add(DWORD, "page_size", null); + structure.add(DWORD, "header_version", null); + structure.add(DWORD, "os_version", BootImageUtil.getOSVersionString(os_version)); + structure.add(UTF8, BootImageConstants.BOOT_NAME_SIZE, "name", null); + structure.add(UTF8, BootImageConstants.BOOT_ARGS_SIZE, "cmdline", null); + ArrayDataType array = + new ArrayDataType(DWORD, BootImageConstants.ID_SIZE, DWORD.getLength()); + structure.add(array, "id", null); + structure.add(UTF8, BootImageConstants.BOOT_EXTRA_ARGS_SIZE, "extra_cmdline", null); + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeaderV1.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeaderV1.java new file mode 100644 index 0000000000..2fa1d2cb4e --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeaderV1.java @@ -0,0 +1,75 @@ +/* ### + * IP: LICENSE + */ +package ghidra.file.formats.android.bootimg; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.util.InvalidNameException; +import ghidra.util.NumericUtilities; +import ghidra.util.exception.DuplicateNameException; + +/** + * https://android.googlesource.com/platform/system/tools/mkbootimg/+/refs/heads/master/include/bootimg/bootimg.h#156 + */ +public class BootImageHeaderV1 extends BootImageHeaderV0 { + + private int recovery_dtbo_size; + private long recovery_dtbo_offset; + private int header_size; + + public BootImageHeaderV1(BinaryReader reader) throws IOException { + super(reader); + recovery_dtbo_size = reader.readNextInt(); + recovery_dtbo_offset = reader.readNextLong(); + header_size = reader.readNextInt(); + } + + /** + * Size in bytes for recovery DTBO/ACPIO image + * @return recovery DTBO/ACPIO image byte size + */ + public int getRecoveryDtboSize() { + return recovery_dtbo_size; + } + + /** + * p = (recovery_dtbo_size + page_size - 1) / page_size + * @return the recovery DTBO adjusted size + */ + public int getRecoveryDtboSizeAdjusted() { + return (int) NumericUtilities.getUnsignedAlignedValue( + Integer.toUnsignedLong(recovery_dtbo_size), Integer.toUnsignedLong(getPageSize())); + } + + /** + * Offset to recovery dtbo/acpio in boot image + * @return the recover DTBO offset + */ + public long getRecoveryDtboOffset() { + return recovery_dtbo_offset; + } + + public int getHeaderSize() { + return header_size; + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = (Structure) super.toDataType(); + try { + structure.setName("boot_img_hdr_v1"); + } + catch (InvalidNameException e) { + //ignore + } + structure.add(DWORD, "recovery_dtbo_size", null); + structure.add(QWORD, "recovery_dtbo_offset", null); + structure.add(DWORD, "header_size", null); + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeaderV2.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeaderV2.java new file mode 100644 index 0000000000..0f0075f238 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeaderV2.java @@ -0,0 +1,80 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.bootimg; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.util.InvalidNameException; +import ghidra.util.NumericUtilities; +import ghidra.util.exception.DuplicateNameException; + +/** + * https://android.googlesource.com/platform/system/tools/mkbootimg/+/refs/heads/master/include/bootimg/bootimg.h#199 + */ +public class BootImageHeaderV2 extends BootImageHeaderV1 { + + private int dtb_size; + private long dtb_addr; + + public BootImageHeaderV2(BinaryReader reader) throws IOException { + super(reader); + dtb_size = reader.readNextInt(); + dtb_addr = reader.readNextLong(); + } + + /** + * Size in bytes for DTB image + * @return size in bytes for DTB image + */ + public int getDtbSize() { + return dtb_size; + } + + /** + * q = (dtb_size + page_size - 1) / page_size + * @return the DTB adjusted size + */ + public int getDtbSizeAdjusted() { + return (int) NumericUtilities.getUnsignedAlignedValue(Integer.toUnsignedLong(dtb_size), + Integer.toUnsignedLong(getPageSize())); + } + + /** + * Physical load address for DTB image + * @return physical load address for DTB image + */ + public long getDtbAddress() { + return dtb_addr; + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = (Structure) super.toDataType(); + try { + structure.setName("boot_img_hdr_v2"); + } + catch (InvalidNameException e) { + //ignore + } + structure.add(DWORD, "dtb_size", null); + structure.add(QWORD, "dtb_addr", null); + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeaderV3.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeaderV3.java new file mode 100644 index 0000000000..550d56dd5d --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeaderV3.java @@ -0,0 +1,158 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.bootimg; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.program.model.data.ArrayDataType; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.program.model.data.StructureDataType; +import ghidra.util.NumericUtilities; +import ghidra.util.exception.DuplicateNameException; + +/** + * https://android.googlesource.com/platform/system/tools/mkbootimg/+/refs/heads/master/include/bootimg/bootimg.h#250 + */ +public class BootImageHeaderV3 extends BootImageHeader { + + private String magic; + private int kernel_size; + private int ramdisk_size; + private int os_version; + private int header_size; + private int[] reserved;// 4 elements + private int header_version; + private String cmdline; + + public BootImageHeaderV3(BinaryReader reader) throws IOException { + magic = reader.readNextAsciiString(BootImageConstants.BOOT_MAGIC_SIZE); + kernel_size = reader.readNextInt(); + ramdisk_size = reader.readNextInt(); + os_version = reader.readNextInt(); + header_size = reader.readNextInt(); + reserved = reader.readNextIntArray(4); + header_version = reader.readNextInt(); + cmdline = reader.readNextAsciiString( + BootImageConstants.BOOT_ARGS_SIZE + BootImageConstants.BOOT_EXTRA_ARGS_SIZE); + } + + @Override + public String getMagic() { + return magic; + } + + @Override + public int getPageSize() { + return BootImageConstants.V3_PAGE_SIZE; + } + + @Override + public int getKernelSize() { + return kernel_size; + } + + /** + * n = (kernel_size + page_size - 1) / page_size + */ + @Override + public int getKernelPageCount() { + return (int) NumericUtilities.getUnsignedAlignedValue(Integer.toUnsignedLong(kernel_size), + Integer.toUnsignedLong(BootImageConstants.V3_PAGE_SIZE)); + } + + @Override + public long getKernelOffset() { + return BootImageConstants.V3_PAGE_SIZE;//see header comment... + } + + @Override + public int getRamdiskSize() { + return ramdisk_size; + } + + /** + * m = (ramdisk_size + page_size - 1) / page_size + */ + @Override + public int getRamdiskPageCount() { + return (int) NumericUtilities.getUnsignedAlignedValue(Integer.toUnsignedLong(ramdisk_size), + Integer.toUnsignedLong(BootImageConstants.V3_PAGE_SIZE)); + } + + @Override + public int getRamdiskOffset() { + return BootImageConstants.V3_PAGE_SIZE + + (getKernelPageCount() * BootImageConstants.V3_PAGE_SIZE); + } + + @Override + public long getSecondOffset() { + // v3 does not contain 2nd stage, just return 0 + return 0; + } + + @Override + public int getSecondSize() { + // v3 does not contain 2nd stage, just return 0 + return 0; + } + + @Override + public int getSecondPageCount() { + // v3 does not contain 2nd stage, just return 0 + return 0; + } + + public int getOSVersion() { + return os_version; + } + + public int getHeaderSize() { + return header_size; + } + + public int[] getReserved() { + return reserved; + } + + public int getHeaderVersion() { + return header_version; + } + + public String getCommandLine() { + return cmdline; + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = new StructureDataType("boot_img_hdr_v3", 0); + structure.add(UTF8, BootImageConstants.BOOT_MAGIC_SIZE, "magic", null); + structure.add(DWORD, "kernel_size", null); + structure.add(DWORD, "ramdisk_size", null); + structure.add(DWORD, "os_version", BootImageUtil.getOSVersionString(os_version)); + structure.add(DWORD, "header_size", null); + ArrayDataType array = new ArrayDataType(DWORD, 4, DWORD.getLength()); + structure.add(array, "reserved", null); + structure.add(DWORD, "header_version", null); + structure.add(UTF8, + BootImageConstants.BOOT_ARGS_SIZE + BootImageConstants.BOOT_EXTRA_ARGS_SIZE, "cmdline", + null); + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeaderV4.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeaderV4.java new file mode 100644 index 0000000000..8ae1ef951d --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageHeaderV4.java @@ -0,0 +1,59 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.bootimg; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.util.InvalidNameException; +import ghidra.util.exception.DuplicateNameException; + +/** + * https://android.googlesource.com/platform/system/tools/mkbootimg/+/refs/heads/master/include/bootimg/bootimg.h#397 + */ +public class BootImageHeaderV4 extends BootImageHeaderV3 { + + private int signature_size; + + public BootImageHeaderV4(BinaryReader reader) throws IOException { + super(reader); + } + + public int getSignatureSize() { + return signature_size; + } + + @Override + public int getPageSize() { + return BootImageConstants.V4_PAGE_SIZE; + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = (Structure) super.toDataType(); + try { + structure.setName("boot_img_hdr_v4"); + } + catch (InvalidNameException e) { + //ignore + } + structure.add(DWORD, "signature_size", null); + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageUtil.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageUtil.java index fa5fe6a3c1..23465a9ac4 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageUtil.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/BootImageUtil.java @@ -15,21 +15,67 @@ */ package ghidra.file.formats.android.bootimg; +import java.util.Arrays; + +import ghidra.app.util.bin.BinaryReader; import ghidra.program.model.address.Address; import ghidra.program.model.listing.Program; -import java.util.Arrays; +public final class BootImageUtil { -public class BootImageUtil { - - public final static boolean isBootImage( Program program ) { - byte [] bytes = new byte[ 8 ]; + public final static boolean isBootImage(Program program) { + byte[] bytes = new byte[BootImageConstants.BOOT_MAGIC_SIZE]; try { Address address = program.getMinAddress(); - program.getMemory().getBytes( address, bytes ); + program.getMemory().getBytes(address, bytes); } - catch (Exception e) {} - return Arrays.equals( bytes, BootImageConstants.BOOT_IMAGE_MAGIC_BYTES ); + catch (Exception e) { + //ignore + } + return Arrays.equals(bytes, BootImageConstants.BOOT_MAGIC.getBytes()); + } + + public final static boolean isBootImage(BinaryReader reader) { + try { + String magic = reader.readAsciiString(0, BootImageConstants.BOOT_MAGIC_SIZE); + return BootImageConstants.BOOT_MAGIC.equals(magic); + } + catch (Exception e) { + //ignore + } + return false; + } + + public final static boolean isVendorBootImage(Program program) { + byte[] bytes = new byte[BootImageConstants.VENDOR_BOOT_MAGIC_SIZE]; + try { + Address address = program.getMinAddress(); + program.getMemory().getBytes(address, bytes); + } + catch (Exception e) { + //ignore + } + return Arrays.equals(bytes, BootImageConstants.VENDOR_BOOT_MAGIC.getBytes()); + } + + public final static boolean isVendorBootImage(BinaryReader reader) { + try { + String magic = reader.readAsciiString(0, BootImageConstants.VENDOR_BOOT_MAGIC_SIZE); + return BootImageConstants.VENDOR_BOOT_MAGIC.equals(magic); + } + catch (Exception e) { + //ignore + } + return false; + } + + public final static String getOSVersionString(int os_version) { + int a = (os_version & 0xfe000000) >>> 25; + int b = (os_version & 0x01fc0000) >>> 18; + int c = (os_version & 0x0003f800) >>> 11; + int y = (os_version & 0x000007f0) >>> 4; + int m = (os_version & 0x0000000f); + return a + "." + b + "." + c + "_" + y + "_" + m; } } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/PartitionTableEntry.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/PartitionTableEntry.java index d7f213194d..36784c2580 100644 --- a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/PartitionTableEntry.java +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/PartitionTableEntry.java @@ -22,31 +22,34 @@ import java.io.IOException; public class PartitionTableEntry { - private byte [] name; + private byte[] name; private int start; private int length; private int flags; public PartitionTableEntry(ByteProvider provider) throws IOException { - this( new BinaryReader( provider, true ) ); + this(new BinaryReader(provider, true)); } public PartitionTableEntry(BinaryReader reader) throws IOException { - name = reader.readNextByteArray( 16 ); - start = reader.readNextInt(); - length = reader.readNextInt(); - flags = reader.readNextInt(); + name = reader.readNextByteArray(16); + start = reader.readNextInt(); + length = reader.readNextInt(); + flags = reader.readNextInt(); } public String getName() { - return new String( name ); + return new String(name); } + public int getStart() { return start; } + public int getLength() { return length; } + public int getFlags() { return flags; } diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageFileSystem.java new file mode 100644 index 0000000000..33d2f0a840 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageFileSystem.java @@ -0,0 +1,116 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.bootimg; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import ghidra.app.util.bin.ByteProvider; +import ghidra.formats.gfilesystem.GFile; +import ghidra.formats.gfilesystem.GFileImpl; +import ghidra.formats.gfilesystem.GFileSystemBase; +import ghidra.formats.gfilesystem.annotations.FileSystemInfo; +import ghidra.formats.gfilesystem.factory.GFileSystemBaseFactory; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.CryptoException; +import ghidra.util.task.TaskMonitor; + +@FileSystemInfo(type = "androidvendorbootimg", description = "Android Vendor Boot Images", factory = GFileSystemBaseFactory.class) +public class VendorBootImageFileSystem extends GFileSystemBase { + + private VendorBootImageHeader header; + private GFileImpl ramdiskFile; + private GFileImpl dtbFile; + private List fileList = new ArrayList<>(); + + public VendorBootImageFileSystem(String fileSystemName, ByteProvider provider) { + super(fileSystemName, provider); + } + + @Override + public boolean isValid(TaskMonitor monitor) throws IOException { + byte[] bytes = provider.readBytes(0, BootImageConstants.VENDOR_BOOT_MAGIC_SIZE); + return Arrays.equals(bytes, BootImageConstants.VENDOR_BOOT_MAGIC.getBytes()); + } + + @Override + public void open(TaskMonitor monitor) throws IOException, CryptoException, CancelledException { + + this.header = VendorBootImageHeaderFactory.getVendorBootImageHeader(provider, true); + + if (!header.getMagic().equals(BootImageConstants.VENDOR_BOOT_MAGIC)) { + throw new IOException("Invalid Android boot image file!"); + } + + if (header.getVendorRamdiskSize() > 0) { + ramdiskFile = GFileImpl.fromFilename(this, root, BootImageConstants.RAMDISK, false, + header.getVendorRamdiskSize(), null); + fileList.add(ramdiskFile); + } + if (header.getDtbSize() > 0) { + dtbFile = GFileImpl.fromFilename(this, root, BootImageConstants.DTB, false, + header.getDtbSize(), null); + fileList.add(dtbFile); + } + } + + @Override + public void close() throws IOException { + ramdiskFile = null; + dtbFile = null; + header = null; + super.close(); + } + + @Override + public List getListing(GFile directory) throws IOException { + return (directory == null || directory.equals(root)) ? new ArrayList<>(fileList) + : Collections.emptyList(); + } + + @Override + public String getInfo(GFile file, TaskMonitor monitor) { + if (file == ramdiskFile) { + return "This is a ramdisk, it is a GZIP file containing a CPIO archive."; + } + else if (file == dtbFile) { + return "This is a DTB file. It appears unused at this time."; + } + return null; + } + + @Override + protected InputStream getData(GFile file, TaskMonitor monitor) + throws IOException, CancelledException, CryptoException { + + if (file == ramdiskFile) { + byte[] ramDiskBytes = + provider.readBytes(header.getVendorRamdiskOffset(), header.getVendorRamdiskSize()); + return new ByteArrayInputStream(ramDiskBytes); + } + else if (file == dtbFile) { + byte[] dtbBytes = provider.readBytes(header.getDtbOffset(), header.getDtbSize()); + return new ByteArrayInputStream(dtbBytes); + } + return null; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeader.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeader.java new file mode 100644 index 0000000000..120f1df42f --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeader.java @@ -0,0 +1,34 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.bootimg; + +import ghidra.app.util.bin.StructConverter; + +/** + * + */ +public abstract class VendorBootImageHeader implements StructConverter { + + public abstract String getMagic(); + + public abstract long getVendorRamdiskOffset(); + + public abstract int getVendorRamdiskSize(); + + public abstract long getDtbOffset(); + + public abstract int getDtbSize(); +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeaderFactory.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeaderFactory.java new file mode 100644 index 0000000000..11e9acadef --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeaderFactory.java @@ -0,0 +1,50 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.bootimg; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.ByteProvider; + +public final class VendorBootImageHeaderFactory { + + public final static VendorBootImageHeader getVendorBootImageHeader(ByteProvider provider, + boolean littleEndian) throws IOException { + return getVendorBootImageHeader(new BinaryReader(provider, littleEndian)); + } + + public final static VendorBootImageHeader getVendorBootImageHeader(BinaryReader reader) + throws IOException { + + if (!BootImageUtil.isVendorBootImage(reader)) { + throw new IOException("VendorBootImageHeader magic not found."); + } + + int version = reader.readInt(BootImageConstants.VENDOR_BOOT_MAGIC_SIZE); + + switch (version) { + case 3: + return new VendorBootImageHeaderV3(reader); + case 4: + return new VendorBootImageHeaderV4(reader); + default: + throw new IOException( + "VendorBootImageHeader unsupported version found: " + version); + } + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeaderV3.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeaderV3.java new file mode 100644 index 0000000000..235e572dae --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeaderV3.java @@ -0,0 +1,166 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.bootimg; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.program.model.data.StructureDataType; +import ghidra.util.exception.DuplicateNameException; + +/** + * https://android.googlesource.com/platform/system/tools/mkbootimg/+/refs/heads/master/include/bootimg/bootimg.h#286 + *
+ * The structure of the vendor boot image (introduced with version 3 and
+ * required to be present when a v3 boot image is used) is as follows:
+ *
+ * +---------------------+
+ * | vendor boot header  | o pages
+ * +---------------------+
+ * | vendor ramdisk      | p pages
+ * +---------------------+
+ * | dtb                 | q pages
+ * +---------------------+
+ * o = (2112 + page_size - 1) / page_size
+ * p = (vendor_ramdisk_size + page_size - 1) / page_size
+ * q = (dtb_size + page_size - 1) / page_size
+ *
+ * 0. all entities in the boot image are 4096-byte aligned in flash, all
+ *    entities in the vendor boot image are page_size (determined by the vendor
+ *    and specified in the vendor boot image header) aligned in flash
+ * 1. kernel, ramdisk, vendor ramdisk, and DTB are required (size != 0)
+ * 2. load the kernel and DTB at the specified physical address (kernel_addr,
+ *    dtb_addr)
+ * 3. load the vendor ramdisk at ramdisk_addr
+ * 4. load the generic ramdisk immediately following the vendor ramdisk in
+ *    memory
+ * 5. set up registers for kernel entry as required by your architecture
+ * 6. if the platform has a second stage bootloader jump to it (must be
+ *    contained outside boot and vendor boot partitions), otherwise
+ *    jump to kernel_addr
+ * 
+ * + */ +public class VendorBootImageHeaderV3 extends VendorBootImageHeader { + + private String magic; + private int header_version; + private int page_size; + private int kernel_addr; + private int ramdisk_addr; + private int vendor_ramdisk_size; + private String cmdline; + private int tags_addr; + private String name; + private int header_size; + private int dtb_size; + private long dtb_addr; + + public VendorBootImageHeaderV3(BinaryReader reader) throws IOException { + magic = reader.readNextAsciiString(BootImageConstants.VENDOR_BOOT_MAGIC_SIZE); + header_version = reader.readNextInt(); + page_size = reader.readNextInt(); + kernel_addr = reader.readNextInt(); + ramdisk_addr = reader.readNextInt(); + vendor_ramdisk_size = reader.readNextInt(); + cmdline = reader.readNextAsciiString(BootImageConstants.VENDOR_BOOT_ARGS_SIZE); + tags_addr = reader.readNextInt(); + name = reader.readNextAsciiString(BootImageConstants.VENDOR_BOOT_NAME_SIZE); + header_size = reader.readNextInt(); + dtb_size = reader.readNextInt(); + dtb_addr = reader.readNextLong(); + } + + public String getMagic() { + return magic; + } + + public int getHeaderVersion() { + return header_version; + } + + public int getPageSize() { + return page_size; + } + + public int getKernelAddress() { + return kernel_addr; + } + + public int getRamdiskAddress() { + return ramdisk_addr; + } + + public int getVendorRamdiskSize() { + return vendor_ramdisk_size; + } + + @Override + public long getVendorRamdiskOffset() { + return page_size; + } + + public String getCmdline() { + return cmdline; + } + + public int getTagsAddress() { + return tags_addr; + } + + public String getName() { + return name; + } + + public int getHeaderSize() { + return header_size; + } + + public int getDtbSize() { + return dtb_size; + } + + public long getDtbAddress() { + return dtb_addr; + } + + @Override + public long getDtbOffset() { + int o = ((2112 + page_size - 1) / page_size); + int p = ((vendor_ramdisk_size + page_size - 1) / page_size); + return (o + p) * page_size; + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = new StructureDataType("vendor_boot_img_hdr_v3", 0); + structure.add(UTF8, BootImageConstants.VENDOR_BOOT_MAGIC_SIZE, "magic", null); + structure.add(DWORD, "header_version", null); + structure.add(DWORD, "page_size", null); + structure.add(DWORD, "kernel_addr", null); + structure.add(DWORD, "ramdisk_addr", null); + structure.add(DWORD, "vendor_ramdisk_size", null); + structure.add(UTF8, BootImageConstants.VENDOR_BOOT_ARGS_SIZE, "cmdline", null); + structure.add(DWORD, "tags_addr", null); + structure.add(UTF8, BootImageConstants.VENDOR_BOOT_NAME_SIZE, "name", null); + structure.add(DWORD, "header_size", null); + structure.add(DWORD, "dtb_size", null); + structure.add(QWORD, "dtb_addr", null); + return structure; + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeaderV4.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeaderV4.java new file mode 100644 index 0000000000..d82d162abd --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorBootImageHeaderV4.java @@ -0,0 +1,159 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.bootimg; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.util.InvalidNameException; +import ghidra.util.exception.DuplicateNameException; + +/** + * https://android.googlesource.com/platform/system/tools/mkbootimg/+/refs/heads/master/include/bootimg/bootimg.h#401 + *
+ * The structure of the vendor boot image version 4, which is required to be
+ * present when a version 4 boot image is used, is as follows:
+ *
+ * +------------------------+
+ * | vendor boot header     | o pages
+ * +------------------------+
+ * | vendor ramdisk section | p pages
+ * +------------------------+
+ * | dtb                    | q pages
+ * +------------------------+
+ * | vendor ramdisk table   | r pages
+ * +------------------------+
+ * | bootconfig             | s pages
+ * +------------------------+
+ *
+ * o = (2128 + page_size - 1) / page_size
+ * p = (vendor_ramdisk_size + page_size - 1) / page_size
+ * q = (dtb_size + page_size - 1) / page_size
+ * r = (vendor_ramdisk_table_size + page_size - 1) / page_size
+ * s = (vendor_bootconfig_size + page_size - 1) / page_size
+ *
+ * Note that in version 4 of the vendor boot image, multiple vendor ramdisks can
+ * be included in the vendor boot image. The bootloader can select a subset of
+ * ramdisks to load at runtime. To help the bootloader select the ramdisks, each
+ * ramdisk is tagged with a type tag and a set of hardware identifiers
+ * describing the board, soc or platform that this ramdisk is intended for.
+ *
+ * The vendor ramdisk section is consist of multiple ramdisk images concatenated
+ * one after another, and vendor_ramdisk_size is the size of the section, which
+ * is the total size of all the ramdisks included in the vendor boot image.
+ *
+ * The vendor ramdisk table holds the size, offset, type, name and hardware
+ * identifiers of each ramdisk. The type field denotes the type of its content.
+ * The hardware identifiers are specified in the board_id field in each table
+ * entry. The board_id field is consist of a vector of unsigned integer words,
+ * and the encoding scheme is defined by the hardware vendor.
+ *
+ * For the different type of ramdisks, there are:
+ *    - VENDOR_RAMDISK_TYPE_NONE indicates the value is unspecified.
+ *    - VENDOR_RAMDISK_TYPE_PLATFORM ramdisk contains platform specific bits.
+ *    - VENDOR_RAMDISK_TYPE_RECOVERY ramdisk contains recovery resources.
+ *    - VENDOR_RAMDISK_TYPE_DLKM ramdisk contains dynamic loadable kernel
+ *      modules.
+ *
+ * Version 4 of the vendor boot image also adds a bootconfig section to the end
+ * of the image. This section contains Boot Configuration parameters known at
+ * build time. The bootloader is responsible for placing this section directly
+ * after the generic ramdisk, followed by the bootconfig trailer, before
+ * entering the kernel.
+ *
+ * 0. all entities in the boot image are 4096-byte aligned in flash, all
+ *    entities in the vendor boot image are page_size (determined by the vendor
+ *    and specified in the vendor boot image header) aligned in flash
+ * 1. kernel, ramdisk, and DTB are required (size != 0)
+ * 2. load the kernel and DTB at the specified physical address (kernel_addr,
+ *    dtb_addr)
+ * 3. load the vendor ramdisks at ramdisk_addr
+ * 4. load the generic ramdisk immediately following the vendor ramdisk in
+ *    memory
+ * 5. load the bootconfig immediately following the generic ramdisk. Add
+ *    additional bootconfig parameters followed by the bootconfig trailer.
+ * 6. set up registers for kernel entry as required by your architecture
+ * 7. if the platform has a second stage bootloader jump to it (must be
+ *    contained outside boot and vendor boot partitions), otherwise
+ *    jump to kernel_addr
+ * 
+ * + */ +public class VendorBootImageHeaderV4 extends VendorBootImageHeaderV3 { + + private int vendor_ramdisk_table_size; + private int vendor_ramdisk_table_entry_num; + private int vendor_ramdisk_table_entry_size; + private int bootconfig_size; + + public VendorBootImageHeaderV4(BinaryReader reader) throws IOException { + super(reader); + vendor_ramdisk_table_size = reader.readNextInt(); + vendor_ramdisk_table_entry_num = reader.readNextInt(); + vendor_ramdisk_table_entry_size = reader.readNextInt(); + bootconfig_size = reader.readNextInt(); + } + + /** + * Size in bytes for the vendor ramdisk table + * @return size in bytes for the vendor ramdisk table + */ + public int getVendorRamdiskTableSize() { + return vendor_ramdisk_table_size; + } + + /** + * Number of entries in the vendor ramdisk table + * @return number of entries in the vendor ramdisk table + */ + public int getVendorRamdiskTableEntryNum() { + return vendor_ramdisk_table_entry_num; + } + + /** + * Size in bytes for a vendor ramdisk table entry + * @return size in bytes for a vendor ramdisk table entry + */ + public int getVendorRamdiskTableEntrySize() { + return vendor_ramdisk_table_entry_size; + } + + /** + * Size in bytes for the bootconfig section + * @return size in bytes for the bootconfig section + */ + public int getBootConfigSize() { + return bootconfig_size; + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = (Structure) super.toDataType(); + try { + structure.setName("vendor_boot_img_hdr_v4"); + } + catch (InvalidNameException e) { + //ignore + } + structure.add(DWORD, "vendor_ramdisk_table_size", null); + structure.add(DWORD, "vendor_ramdisk_table_entry_num", null); + structure.add(DWORD, "vendor_ramdisk_table_entry_size", null); + structure.add(DWORD, "bootconfig_size", null); + return structure; + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorRamdiskTableEntryV4.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorRamdiskTableEntryV4.java new file mode 100644 index 0000000000..096a34f00b --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootimg/VendorRamdiskTableEntryV4.java @@ -0,0 +1,97 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.bootimg; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverter; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; + +public class VendorRamdiskTableEntryV4 implements StructConverter { + + private int ramdisk_size; + private int ramdisk_offset; + private int ramdisk_type; + private String ramdisk_name; + + private int[] board_id; + + public VendorRamdiskTableEntryV4(BinaryReader reader) throws IOException { + ramdisk_size = reader.readNextInt(); + ramdisk_offset = reader.readNextInt(); + ramdisk_type = reader.readNextInt(); + ramdisk_name = reader.readNextAsciiString(BootImageConstants.VENDOR_RAMDISK_NAME_SIZE); + board_id = + reader.readNextIntArray(BootImageConstants.VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE); + } + + /** + * Size in bytes for the ramdisk image + * @return ramdisk size + */ + public int getRamdiskSize() { + return ramdisk_size; + } + + /** + * Offset to the ramdisk image in vendor ramdisk section + * @return ramdisk offset + */ + public int getRamdiskOffset() { + return ramdisk_offset; + } + + /** + * Type of the ramdisk + * @return ramdisk type + */ + public int getRamdiskType() { + return ramdisk_type; + } + + /** + * Ascii ramdisk name + * @return the ascii ramdisk name + */ + public String getRamdiskName() { + return ramdisk_name; + } + + /** + * Hardware identifiers describing the board, soc or platform + * which this ramdisk is intended to be loaded on. + * @return the board ID + */ + public int[] getBoardID() { + return board_id; + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = new StructureDataType("vendor_ramdisk_table_entry_v4", 0); + structure.add(DWORD, "ramdisk_size", null); + structure.add(DWORD, "ramdisk_offset", null); + structure.add(DWORD, "ramdisk_type", null); + structure.add(UTF8, BootImageConstants.VENDOR_RAMDISK_NAME_SIZE, "ramdisk_name", null); + for (int i = 0; i < BootImageConstants.VENDOR_RAMDISK_TABLE_ENTRY_BOARD_ID_SIZE; ++i) { + structure.add(DWORD, "board_id_" + i, null); + } + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootldr/AndroidBootLoaderAnalyzer.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootldr/AndroidBootLoaderAnalyzer.java new file mode 100644 index 0000000000..4bff86449c --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootldr/AndroidBootLoaderAnalyzer.java @@ -0,0 +1,94 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.bootldr; + +import ghidra.app.services.AbstractAnalyzer; +import ghidra.app.services.AnalyzerType; +import ghidra.app.util.bin.*; +import ghidra.app.util.importer.MessageLog; +import ghidra.framework.options.Options; +import ghidra.program.model.address.*; +import ghidra.program.model.data.DataType; +import ghidra.program.model.listing.*; +import ghidra.program.model.symbol.*; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +/** + * Annotates the structures contained in an Android Boot Loader image. + */ +public class AndroidBootLoaderAnalyzer extends AbstractAnalyzer { + + public AndroidBootLoaderAnalyzer() { + super("Android Boot Loader", "Annotates the Android Boot Loader header components", + AnalyzerType.BYTE_ANALYZER); + } + + @Override + public boolean getDefaultEnablement(Program program) { + return AndroidBootLoaderConstants.isBootLoader(program); + } + + @Override + public boolean canAnalyze(Program program) { + return AndroidBootLoaderConstants.isBootLoader(program); + } + + @Override + public void registerOptions(Options options, Program program) { + } + + @Override + public boolean added(Program program, AddressSetView set, TaskMonitor monitor, MessageLog log) + throws CancelledException { + + AddressSpace addressSpace = program.getAddressFactory().getDefaultAddressSpace(); + + Address headerAddress = program.getMinAddress(); + ByteProvider provider = new MemoryByteProvider(program.getMemory(), headerAddress); + BinaryReader reader = new BinaryReader(provider, !program.getLanguage().isBigEndian()); + try { + AndroidBootLoaderHeader header = new AndroidBootLoaderHeader(reader); + DataType headerDataType = header.toDataType(); + Data headerData = program.getListing().createData(headerAddress, headerDataType); + if (headerData == null) { + log.appendMsg("Unable to apply header data, stopping."); + return false; + } + SymbolTable symbolTable = program.getSymbolTable(); + Symbol headerSymbol = symbolTable.getPrimarySymbol(headerAddress); + if (headerSymbol == null) { + symbolTable.createLabel(headerAddress, header.getMagic(), SourceType.ANALYSIS); + } + else { + headerSymbol.setName(header.getMagic(), SourceType.ANALYSIS); + } + int runningOffset = header.getStartOffset(); + for (AndroidBootLoaderImageInfo imageInfo : header.getImageInfoList()) { + Address address = addressSpace.getAddress(runningOffset); + symbolTable.createLabel(address, imageInfo.getName(), SourceType.ANALYSIS); + program.getBookmarkManager() + .setBookmark(address, BookmarkType.ANALYSIS, "boot", imageInfo.getName()); + runningOffset += imageInfo.getSize(); + } + return true; + } + catch (Exception e) { + log.appendException(e); + } + return false; + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootldr/AndroidBootLoaderConstants.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootldr/AndroidBootLoaderConstants.java new file mode 100644 index 0000000000..c9168581b0 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootldr/AndroidBootLoaderConstants.java @@ -0,0 +1,63 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.bootldr; + +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.Memory; + +/** + * Source: https://android.googlesource.com/device/lge/mako/+/android-4.2.2_r1/releasetools.py + * # + * # #define BOOTLDR_MAGIC "BOOTLDR!" + * # #define BOOTLDR_MAGIC_SIZE 8 + * # + * # struct bootloader_images_header { + * # char magic[BOOTLDR_MAGIC_SIZE]; + * # unsigned int num_images; + * # unsigned int start_offset; + * # unsigned int bootldr_size; + * # struct { + * # char name[64]; + * # unsigned int size; + * # } img_info[]; + * # }; + */ +public final class AndroidBootLoaderConstants { + + public static final String BOOTLDR_NAME = "bootloader_images_header"; + + public static final String BOOTLDR_MAGIC = "BOOTLDR!"; + + public static final int BOOTLDR_MAGIC_SIZE = BOOTLDR_MAGIC.length(); + + public static final String IMG_INFO_NAME = "img_info"; + + public static final int IMG_INFO_NAME_LENGTH = 64; + + public static boolean isBootLoader(Program program) { + try { + Memory memory = program.getMemory(); + byte[] bytes = new byte[AndroidBootLoaderConstants.BOOTLDR_MAGIC_SIZE]; + memory.getBytes(program.getMinAddress(), bytes); + String magic = new String(bytes).trim(); + return AndroidBootLoaderConstants.BOOTLDR_MAGIC.equals(magic); + } + catch (Exception e) { + //ignore + } + return false; + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootldr/AndroidBootLoaderFileSystem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootldr/AndroidBootLoaderFileSystem.java new file mode 100644 index 0000000000..f3b5996569 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootldr/AndroidBootLoaderFileSystem.java @@ -0,0 +1,88 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.bootldr; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.ByteProvider; +import ghidra.formats.gfilesystem.GFile; +import ghidra.formats.gfilesystem.GFileImpl; +import ghidra.formats.gfilesystem.GFileSystemBase; +import ghidra.formats.gfilesystem.annotations.FileSystemInfo; +import ghidra.formats.gfilesystem.factory.GFileSystemBaseFactory; +import ghidra.util.exception.CancelledException; +import ghidra.util.exception.CryptoException; +import ghidra.util.task.TaskMonitor; + +@FileSystemInfo(type = "androidbootloader", // ([a-z0-9]+ only) + description = "Android Boot Loader Image", factory = GFileSystemBaseFactory.class) + +public class AndroidBootLoaderFileSystem extends GFileSystemBase { + private List fileList = new ArrayList<>(); + private List offsetList = new ArrayList<>(); + + public AndroidBootLoaderFileSystem(String fileSystemName, ByteProvider provider) { + super(fileSystemName, provider); + } + + @Override + public boolean isValid(TaskMonitor monitor) throws IOException { + byte[] bytes = provider.readBytes(0, AndroidBootLoaderConstants.BOOTLDR_MAGIC_SIZE); + return AndroidBootLoaderConstants.BOOTLDR_MAGIC.equals(new String(bytes).trim()); + } + + @Override + public void open(TaskMonitor monitor) throws IOException, CryptoException, CancelledException { + BinaryReader reader = new BinaryReader(provider, true /*might not always be LE*/ ); + AndroidBootLoaderHeader header = new AndroidBootLoaderHeader(reader); + int runningOffset = header.getStartOffset(); + for (AndroidBootLoaderImageInfo imageInfo : header.getImageInfoList()) { + + GFileImpl file = GFileImpl.fromFilename(this, root, imageInfo.getName(), false, + imageInfo.getSize(), null); + + fileList.add(file); + offsetList.add(runningOffset); + + runningOffset += imageInfo.getSize(); + } + } + + @Override + public List getListing(GFile directory) throws IOException { + return new ArrayList<>(fileList); + } + + @Override + public String getInfo(GFile file, TaskMonitor monitor) { + return null; + } + + @Override + protected InputStream getData(GFile file, TaskMonitor monitor) + throws IOException, CancelledException, CryptoException { + + int index = fileList.indexOf(file); + int offset = offsetList.get(index); + + return provider.getInputStream(offset); + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootldr/AndroidBootLoaderHeader.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootldr/AndroidBootLoaderHeader.java new file mode 100644 index 0000000000..bfed825e5e --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootldr/AndroidBootLoaderHeader.java @@ -0,0 +1,86 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.bootldr; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverter; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.program.model.data.StructureDataType; +import ghidra.util.exception.DuplicateNameException; + +/** + * Class to represent the Android boot loader header. + * + */ +public class AndroidBootLoaderHeader implements StructConverter { + + private String magic; + private int numberOfImages; + private int startOffset; + private int bootLoaderSize; + private List imageInfoList = + new ArrayList(); + + public AndroidBootLoaderHeader(BinaryReader reader) throws IOException { + magic = reader.readNextAsciiString(AndroidBootLoaderConstants.BOOTLDR_MAGIC_SIZE); + numberOfImages = reader.readNextInt(); + startOffset = reader.readNextInt(); + bootLoaderSize = reader.readNextInt(); + for (int i = 0; i < numberOfImages; ++i) { + imageInfoList.add(new AndroidBootLoaderImageInfo(reader)); + } + } + + public String getMagic() { + return magic; + } + + public int getNumberOfImages() { + return numberOfImages; + } + + public int getStartOffset() { + return startOffset; + } + + public int getBootLoaderSize() { + return bootLoaderSize; + } + + public List getImageInfoList() { + return new ArrayList(imageInfoList); + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure struct = new StructureDataType( + AndroidBootLoaderConstants.BOOTLDR_NAME + "_" + numberOfImages, 0); + struct.add(STRING, AndroidBootLoaderConstants.BOOTLDR_MAGIC_SIZE, "magic", null); + struct.add(DWORD, "num_images", null); + struct.add(DWORD, "start_offset", null); + struct.add(DWORD, "bootldr_size", null); + for (int i = 0; i < numberOfImages; ++i) { + struct.add(imageInfoList.get(i).toDataType(), "img_info[" + i + "]", null); + } + return struct; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootldr/AndroidBootLoaderImageInfo.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootldr/AndroidBootLoaderImageInfo.java new file mode 100644 index 0000000000..b9a88fa5f6 --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/bootldr/AndroidBootLoaderImageInfo.java @@ -0,0 +1,53 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.bootldr; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.StructConverter; +import ghidra.program.model.data.DataType; +import ghidra.program.model.data.Structure; +import ghidra.program.model.data.StructureDataType; +import ghidra.util.exception.DuplicateNameException; + +public class AndroidBootLoaderImageInfo implements StructConverter { + + private String name; + private int size; + + public AndroidBootLoaderImageInfo(BinaryReader reader) throws IOException { + name = reader.readNextAsciiString(AndroidBootLoaderConstants.IMG_INFO_NAME_LENGTH).trim(); + size = reader.readNextInt(); + } + + public String getName() { + return name; + } + + public int getSize() { + return size; + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure struct = new StructureDataType(AndroidBootLoaderConstants.IMG_INFO_NAME, 0); + struct.add(STRING, AndroidBootLoaderConstants.IMG_INFO_NAME_LENGTH, "magic", null); + struct.add(DWORD, "size", null); + return struct; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/cdex/CDexCodeItem.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/cdex/CDexCodeItem.java new file mode 100644 index 0000000000..d8d79beebd --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/cdex/CDexCodeItem.java @@ -0,0 +1,137 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.cdex; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.file.formats.android.dex.format.CodeItem; +import ghidra.program.model.data.*; +import ghidra.util.exception.DuplicateNameException; + +/** + * https://android.googlesource.com/platform/art/+/refs/heads/android10-release/libdexfile/dex/compact_dex_file.h + *
+ * https://android.googlesource.com/platform/art/+/master/libdexfile/dex/compact_dex_file.h + */ +public class CDexCodeItem extends CodeItem { + + public final static short kRegistersSizeShift = 12; + public final static short kInsSizeShift = 8; + public final static short kOutsSizeShift = 4; + public final static short kTriesSizeSizeShift = 0; + public final static short kInsnsSizeShift = 5; + + public final static short kBitPreHeaderRegisterSize = 0; + public final static short kBitPreHeaderInsSize = 1; + public final static short kBitPreHeaderOutsSize = 2; + public final static short kBitPreHeaderTriesSize = 3; + public final static short kBitPreHeaderInsnsSize = 4; + public final static short kFlagPreHeaderRegisterSize = 0x1 << kBitPreHeaderRegisterSize; + public final static short kFlagPreHeaderInsSize = 0x1 << kBitPreHeaderInsSize; + public final static short kFlagPreHeaderOutsSize = 0x1 << kBitPreHeaderOutsSize; + public final static short kFlagPreHeaderTriesSize = 0x1 << kBitPreHeaderTriesSize; + public final static short kFlagPreHeaderInsnsSize = 0x1 << kBitPreHeaderInsnsSize; + + public final static short kFlagPreHeaderCombined = + kFlagPreHeaderRegisterSize | kFlagPreHeaderInsSize | kFlagPreHeaderOutsSize | + kFlagPreHeaderTriesSize | kFlagPreHeaderInsnsSize; + + private short fields_; + private short insns_count_and_flags_; + + public CDexCodeItem(BinaryReader reader) throws IOException { + super(); + + long startIndex = reader.getPointerIndex();//used for reading preheaders... + + /* + * Packed code item data, + * 4 bits each: [registers_size, ins_size, outs_size, tries_size] + */ + fields_ = reader.readNextShort(); + + registersSize = (short) ((fields_ >> kRegistersSizeShift) & 0xf); + incomingSize = (short) ((fields_ >> kInsSizeShift) & 0xf); + outgoingSize = (short) ((fields_ >> kOutsSizeShift) & 0xf); + triesSize = (short) ((fields_ >> kOutsSizeShift) & 0xf); + + /* + * 5 bits, if either of the fields required preheader extension, + * 11 bits for the number of instruction code units. + */ + insns_count_and_flags_ = reader.readNextShort(); + + instructionSize = (Short.toUnsignedInt(insns_count_and_flags_) >> kInsnsSizeShift); + + if (hasPreHeader()) { + if (hasPreHeader(kFlagPreHeaderInsnsSize)) { + startIndex -= 2; + instructionSize += reader.readShort(startIndex); + startIndex -= 2; + instructionSize += (reader.readShort(startIndex) << 16); + } + if (hasPreHeader(kFlagPreHeaderRegisterSize)) { + startIndex -= 2; + registersSize += reader.readShort(startIndex); + } + if (hasPreHeader(kFlagPreHeaderInsSize)) { + startIndex -= 2; + incomingSize += reader.readShort(startIndex); + } + if (hasPreHeader(kFlagPreHeaderOutsSize)) { + startIndex -= 2; + outgoingSize += reader.readShort(startIndex); + } + if (hasPreHeader(kFlagPreHeaderTriesSize)) { + startIndex -= 2; + triesSize += reader.readShort(startIndex); + } + } + + if (getInstructionSize() == 0) { + instructionBytes = new byte[0]; + instructions = new short[0]; + } + else { + instructionBytes = reader.readNextByteArray(getInstructionSize() * 2); + instructions = reader.readNextShortArray(getInstructionSize()); + } + + } + + public boolean hasPreHeader() { + return (insns_count_and_flags_ & kFlagPreHeaderCombined) != 0; + } + + public boolean hasPreHeader(short flag) { + return (insns_count_and_flags_ & flag) != 0; + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + String name = "cdex_code_item" + "_" + (getInstructionSize() * 2); + Structure structure = new StructureDataType(name, 0); + structure.add(WORD, "fields_", null); + structure.add(WORD, "insns_count_and_flags_", null); + if (getInstructionSize() > 0) { + structure.add(new ArrayDataType(WORD, getInstructionSize(), WORD.getLength()), "insns_", + null); + } + structure.setCategoryPath(new CategoryPath("/dex/cdex_code_item")); + return structure; + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/cdex/CDexConstants.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/cdex/CDexConstants.java new file mode 100644 index 0000000000..8a068cfa9b --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/cdex/CDexConstants.java @@ -0,0 +1,73 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.cdex; + +import ghidra.program.model.listing.Program; + +/** + * Android .CDEX files. + * + * CompactDex is a currently ART internal dex file format that aims to reduce + * storage/RAM usage. + * + * https://android.googlesource.com/platform/art/+/master/runtime/dex/compact_dex_file.h + * + * https://android.googlesource.com/platform/art/+/master/libdexfile/dex/compact_dex_file.h + */ +public final class CDexConstants { + + public final static String NAME = "Compact Dalvik Executable (CDEX)"; + + /** + *
+	 * static constexpr uint8_t kDexMagic[kDexMagicSize] = { 'c', 'd', 'e', 'x' };
+	 * 
+ */ + public final static String MAGIC = "cdex"; + + /** + *
+	 * static constexpr uint8_t kDexMagicVersion[] = {'0', '0', '1', '\0'};
+	 * 
+ */ + public final static String VERSION_001 = "001"; + + /** + *
+	 * static constexpr uint8_t kDexMagicVersion[] = {'0', '0', '2', '\0'};
+	 * 
+ */ + public final static String VERSION_002 = "002"; + + /** + * Returns true if the given program contain CDEX information. + * @param program the program to inspect + * @return true if the given program contain CDEX information + */ + public final static boolean isCDEX(Program program) { + if (program != null) { + try { + byte[] bytes = new byte[MAGIC.length()]; + program.getMemory().getBytes(program.getMinAddress(), bytes); + return MAGIC.equals(new String(bytes)); + } + catch (Exception e) { + //ignore + } + } + return false; + } +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/cdex/CDexHeader.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/cdex/CDexHeader.java new file mode 100644 index 0000000000..ba377ee41b --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/cdex/CDexHeader.java @@ -0,0 +1,136 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.cdex; + +import java.io.IOException; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.file.formats.android.dex.format.DexHeader; +import ghidra.program.model.data.*; +import ghidra.util.InvalidNameException; +import ghidra.util.exception.DuplicateNameException; + +/** + * CDEX Header extends DEX header, but adds additional members + * + * class Header : public DexFile::Header { + * + * + * https://android.googlesource.com/platform/art/+/master/libdexfile/dex/compact_dex_file.h + */ +public class CDexHeader extends DexHeader { + + private int feature_flags_; + private int debug_info_offsets_pos_; + private int debug_info_offsets_table_offset_; + private int debug_info_base_; + private int owned_data_begin_; + private int owned_data_end_; + + public CDexHeader(BinaryReader reader) throws IOException { + super(reader); + + feature_flags_ = reader.readNextInt(); + debug_info_offsets_pos_ = reader.readNextInt(); + debug_info_offsets_table_offset_ = reader.readNextInt(); + debug_info_base_ = reader.readNextInt(); + owned_data_begin_ = reader.readNextInt(); + owned_data_end_ = reader.readNextInt(); + } + + public int getFeatureFlags() { + return feature_flags_; + } + + /** + * Position in the compact dex file for the debug info table data starts. + * @return position in the compact dex file for the debug info table data starts + */ + public int getDebugInfoOffsetsPos() { + return debug_info_offsets_pos_; + } + + /** + * Offset into the debug info table data where the lookup table exists. + * @return offset into the debug info table data where the lookup table is. + */ + public int getDebugInfoOffsetsTableOffset() { + return debug_info_offsets_table_offset_; + } + + /** + * Base offset of where debug info starts in the dex file. + * @return base offset of where debug info starts in the dex file + */ + public int getDebugInfoBase() { + return debug_info_base_; + } + + /** + * Range of the shared data section owned by the dex file. + * @return range of the shared data section owned by the dex file + */ + public int getOwnedDataBegin() { + return owned_data_begin_; + } + + /** + * Range of the shared data section owned by the dex file. + * @return range of the shared data section owned by the dex file. + */ + public int getOwnedDataEnd() { + return owned_data_end_; + } + + @Override + public boolean isDataOffsetRelative() { + return true; + } + + @Override + protected void checkMagic() throws IOException { + if (!CDexConstants.MAGIC.equals(new String(getMagic()))) { + throw new IOException("not a cdex file."); + } + } + + @Override + public DataType toDataType() throws DuplicateNameException, IOException { + Structure structure = (Structure) super.toDataType(); + try { + structure.setName("cdex_header"); + } + catch (InvalidNameException e) { + //ignore, can't happen + } + structure.setCategoryPath(new CategoryPath("/cdex")); + + structure.add(DWORD, "feature_flags_", null); + structure.add(DWORD, "debug_info_offsets_pos_", null); + structure.add(DWORD, "debug_info_offsets_table_offset_", null); + structure.add(DWORD, "debug_info_base_", null); + structure.add(DWORD, "owned_data_begin_", null); + structure.add(DWORD, "owned_data_end_", null); + + // remove comments to prevent data type conflicts + for (DataTypeComponent component : structure.getComponents()) { + component.setComment(null); + } + + return structure; + } + +} diff --git a/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/cdex/CDexLoader.java b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/cdex/CDexLoader.java new file mode 100644 index 0000000000..e92522141d --- /dev/null +++ b/Ghidra/Features/FileFormats/src/main/java/ghidra/file/formats/android/cdex/CDexLoader.java @@ -0,0 +1,179 @@ +/* ### + * IP: GHIDRA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package ghidra.file.formats.android.cdex; + +import java.io.IOException; +import java.io.InputStream; +import java.util.*; + +import ghidra.app.util.Option; +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.opinion.*; +import ghidra.file.formats.android.dex.DexHeaderFactory; +import ghidra.file.formats.android.dex.format.*; +import ghidra.file.formats.android.dex.util.DexUtil; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.MemoryBlock; +import ghidra.util.task.TaskMonitor; + +public class CDexLoader extends DexLoader { + + @Override + public String getName() { + return CDexConstants.NAME; + } + + @Override + public LoaderTier getTier() { + return LoaderTier.UNTARGETED_LOADER; + } + + @Override + public int getTierPriority() { + return 100; + } + + @Override + public Collection findSupportedLoadSpecs(ByteProvider provider) throws IOException { + List loadSpecs = new ArrayList<>(); + + BinaryReader reader = new BinaryReader(provider, true); + try { + byte[] magicBytes = provider.readBytes(0, CDexConstants.MAGIC.length()); + if (CDexConstants.MAGIC.equals(new String(magicBytes))) { + DexHeader header = DexHeaderFactory.getDexHeader(reader);//should be CDEX + if (CDexConstants.MAGIC.equals(new String(header.getMagic()))) { + List queries = + QueryOpinionService.query(getName(), DexConstants.MACHINE, null); + for (QueryResult result : queries) { + loadSpecs.add(new LoadSpec(this, 0, result)); + } + if (loadSpecs.isEmpty()) { + loadSpecs.add(new LoadSpec(this, 0, true)); + } + } + } + } + catch (Exception e) { + //ignore + } + return loadSpecs; + } + + @Override + public boolean supportsLoadIntoProgram() { + return true; + } + + @Override + public void load(ByteProvider provider, LoadSpec loadSpec, List