diff --git a/README.md b/README.md index 36d4bcc..f350050 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,14 @@ This is a (WIP) simple extension to open .rpx and .rpl files with Ghidra. +# Dependencies + +This loader uses the Gekko/Broadway processor definitions for Ghidra if found - it is recommended that this should be installed prior to using the loader. + +https://github.com/aldelaro5/ghidra-gekko-broadway-lang + +The loader will fallback to the default PowerPC processor if the Gekko/Broadway language is not found, but do not expect good results if the program uses any paired single instructions. + # Usage Install the extension using the `Install Extensions` option inside Ghidra or extract the .zip manually into `[GHIDRA_ROOT]\Ghidra\Extensions`. Make sure to restart the program after installing. diff --git a/data/languages/RPX.opinion b/data/languages/RPX.opinion index e7bdf04..5ec29b7 100644 --- a/data/languages/RPX.opinion +++ b/data/languages/RPX.opinion @@ -1,5 +1,6 @@ - - - + + + + diff --git a/src/main/java/cafeloader/Cafe_ElfExtension.java b/src/main/java/cafeloader/Cafe_ElfExtension.java new file mode 100644 index 0000000..404bfb4 --- /dev/null +++ b/src/main/java/cafeloader/Cafe_ElfExtension.java @@ -0,0 +1,324 @@ +package cafeloader; + +import java.io.IOException; +import java.math.BigInteger; + +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.format.elf.*; +import ghidra.app.util.bin.format.elf.extend.*; +import ghidra.program.model.address.*; +import ghidra.program.model.listing.*; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.mem.MemoryAccessException; +import ghidra.program.model.symbol.SourceType; +import ghidra.program.model.data.*; +import ghidra.program.model.lang.Register; +import ghidra.util.*; +import ghidra.util.exception.*; +import ghidra.util.task.TaskMonitor; + +public class Cafe_ElfExtension extends ElfExtension { + /* + * Note that these values are not the REAL CafeOS section header types, we + * transform them to this in the RpxConversion stage because ElfSectionHeaderType + * expects an int > 0 and unfortunately the real CafeOS SHT values are invalid for this. + */ + public static final ElfSectionHeaderType SHT_RPL_EXPORTS = new ElfSectionHeaderType(0x0CAFE001, + "SHT_RPL_EXPORTS", "Section contains RPL exports"); + public static final ElfSectionHeaderType SHT_RPL_IMPORTS = new ElfSectionHeaderType(0x0CAFE002, + "SHT_RPL_IMPORTS", "Section contains RPL imports"); + public static final ElfSectionHeaderType SHT_RPL_CRCS = new ElfSectionHeaderType(0x0CAFE003, + "SHT_RPL_CRCS", "Section contains RPL crcs"); + public static final ElfSectionHeaderType SHT_RPL_FILEINFO = new ElfSectionHeaderType(0x0CAFE004, + "SHT_RPL_FILEINFO", "Section contains RPL file info"); + + public static final int RPL_FILEINFO_V3 = 0xCAFE0300; + public static final int RPL_FILEINFO_V4_1 = 0xCAFE0401; + public static final int RPL_FILEINFO_V4_2 = 0xCAFE0402; + + @Override + public boolean canHandle(ElfHeader elf) { + return elf instanceof RplHeader; + } + + @Override + public boolean canHandle(ElfLoadHelper elfLoadHelper) { + return canHandle(elfLoadHelper.getElfHeader()); + } + + @Override + public String getDataTypeSuffix() { + return "_PPC"; + } + + /* + * Process elf symbols which are in SHT_RPL_IMPORTS sections to be external imports. + */ + @Override + public Address evaluateElfSymbol(ElfLoadHelper elfLoadHelper, ElfSymbol elfSymbol, + Address address, boolean isExternal) { + ElfHeader elf = elfLoadHelper.getElfHeader(); + ElfSectionHeader section = elf.getSections()[elfSymbol.getSectionHeaderIndex()]; + + if (section.getType() != SHT_RPL_IMPORTS.value) { + return address; + } + + String name = elfSymbol.getNameAsString(); + if (name == null) { + return address; + } + + if (!elfSymbol.isFunction() && !elfSymbol.isObject()) { + return address; + } + + String sectionName = section.getNameAsString(); + if (!sectionName.startsWith(".fimport_") && !sectionName.startsWith(".dimport_")) { + return address; + } + + String rplName = sectionName.split("import_")[1]; + if (!rplName.endsWith(".rpl")) { + rplName += ".rpl"; + } + + try { + Program program = elfLoadHelper.getProgram(); + elfLoadHelper.setElfSymbolAddress(elfSymbol, address); + elfLoadHelper.createSymbol(address, name, true, elfSymbol.isAbsolute(), null); + + if (elfSymbol.isFunction()) { + program.getExternalManager().addExtFunction(rplName, name, address, + SourceType.IMPORTED); + } else if (elfSymbol.isObject()) { + program.getExternalManager().addExtLocation(rplName, name, address, + SourceType.IMPORTED); + } + + return null; + } catch (InvalidInputException e) { + } catch (DuplicateNameException e) { + } + + return address; + } + + @Override + public Boolean isSectionWritable(ElfSectionHeader section) { + // For some reason .rpl files have .rodata marked with W flag, + // forcing it to read only will help improve decompiler output. + String name = section.getNameAsString(); + if (name != null && name.contentEquals(".rodata")) { + return false; + } + + // Force .dimport section to writeable so compiler does not inline + // the value... even though its external... + // TODO: Maybe there is a better way to define .dimport/.fimport + // sections as not real loaded in memory sections so that the + // compiler does not inline it's values? + if (name != null && name.startsWith(".dimport")) { + return true; + } + + return (section.getFlags() & ElfSectionHeaderConstants.SHF_WRITE) != 0; + } + + @Override + public void processElf(ElfLoadHelper elfLoadHelper, TaskMonitor monitor) + throws CancelledException { + ElfHeader elf = elfLoadHelper.getElfHeader(); + for (ElfSectionHeader sectionHeader : elf.getSections()) { + int headertype = sectionHeader.getType(); + if (headertype == SHT_RPL_CRCS.value) { + processRplCrcs(elfLoadHelper, sectionHeader); + } else if (headertype == SHT_RPL_FILEINFO.value) { + processRplFileInfo(elfLoadHelper, sectionHeader); + } else if (headertype == SHT_RPL_IMPORTS.value) { + processRplImports(elfLoadHelper, sectionHeader); + } else if (headertype == SHT_RPL_EXPORTS.value) { + processRplExports(elfLoadHelper, sectionHeader); + } + } + } + + private void processRplImports(ElfLoadHelper elfLoadHelper, ElfSectionHeader sectionHeader) { + // Clear the section data otherwise analysis will identify strings in it. + Address sectionAddress = elfLoadHelper.findLoadAddress(sectionHeader, 0); + int sectionSize = (int) sectionHeader.getSize(); + elfLoadHelper.createUndefinedData(sectionAddress, sectionSize); + + byte[] zeroes = new byte[sectionSize]; + try { + elfLoadHelper.getProgram().getMemory().setBytes(sectionAddress, zeroes); + } catch (MemoryAccessException e) { + } + } + + private void processRplExports(ElfLoadHelper elfLoadHelper, ElfSectionHeader sectionHeader) { + String sectionName = sectionHeader.getNameAsString(); + if (sectionName.contentEquals(".dexports")) { + // Create symbols for data exports + BinaryReader reader = elfLoadHelper.getElfHeader().getReader(); + reader.setPointerIndex(sectionHeader.getOffset()); + + try { + int count = reader.readNextInt(); + /* int signature = */ reader.readNextInt(); + for (int i = 0; i < count; ++i) { + int value = reader.readNextInt(); + int nameOffset = reader.readNextInt(); + /* boolean isTlsExport = (nameOffset & 0x80000000) != 0; */ + String name = reader.readAsciiString(sectionHeader.getOffset() + (nameOffset & 0x7FFFFFFF)); + elfLoadHelper.createSymbol(elfLoadHelper.getDefaultAddress(value), name, true, false, null); + } + } catch (IOException e) { + e.printStackTrace(); + } catch (InvalidInputException e) { + e.printStackTrace(); + } + } + + // Clear the section data otherwise analysis will identify strings in it. + Address sectionAddress = elfLoadHelper.findLoadAddress(sectionHeader, 0); + int sectionSize = (int) sectionHeader.getSize(); + elfLoadHelper.createUndefinedData(sectionAddress, sectionSize); + + byte[] zeroes = new byte[sectionSize]; + try { + elfLoadHelper.getProgram().getMemory().setBytes(sectionAddress, zeroes); + } catch (MemoryAccessException e) { + } + } + + private void processRplCrcs(ElfLoadHelper elfLoadHelper, ElfSectionHeader sectionHeader) { + Address address = elfLoadHelper.findLoadAddress(sectionHeader, 0); + if (address == null) { + return; + } + + try { + for (long i = 0; i < sectionHeader.getSize(); i += 4) { + elfLoadHelper.createData(address.add(i), DWordDataType.dataType); + } + } catch (AddressOutOfBoundsException e) { + } + } + + private void processRplFileInfo(ElfLoadHelper elfLoadHelper, ElfSectionHeader sectionHeader) { + Address fileInfoAddr = elfLoadHelper.findLoadAddress(sectionHeader, 0); + if (fileInfoAddr == null) { + return; + } + + int version = RPL_FILEINFO_V3; + Program program = elfLoadHelper.getProgram(); + Memory memory = program.getMemory(); + try { + version = memory.getInt(fileInfoAddr); + } catch (MemoryAccessException e) { + Msg.warn(this, "Failed to read RplFileInfo version"); + } + + Structure fileInfo = + new StructureDataType(new CategoryPath("/ELF"), "Elf32_RplFileInfo", 0); + + int filenameOffset = 0; + int sdaBase = 0; + int sda2Base = 0; + if (version >= RPL_FILEINFO_V3) { + fileInfo.add(DWordDataType.dataType, "version", null); + fileInfo.add(DWordDataType.dataType, "textSize", null); + fileInfo.add(DWordDataType.dataType, "textAlign", null); + fileInfo.add(DWordDataType.dataType, "dataSize", null); + fileInfo.add(DWordDataType.dataType, "dataAlign", null); + fileInfo.add(DWordDataType.dataType, "loadSize", null); + fileInfo.add(DWordDataType.dataType, "loadAlign", null); + fileInfo.add(DWordDataType.dataType, "tempSize", null); + fileInfo.add(DWordDataType.dataType, "trampAdjust", null); + fileInfo.add(DWordDataType.dataType, "sdaBase", null); + fileInfo.add(DWordDataType.dataType, "sda2Base", null); + fileInfo.add(DWordDataType.dataType, "stackSize", null); + fileInfo.add(DWordDataType.dataType, "filenameOffset", null); + + try { + sdaBase = memory.getInt(fileInfoAddr.add(0x24)); + sda2Base = memory.getInt(fileInfoAddr.add(0x28)); + filenameOffset = memory.getInt(fileInfoAddr.add(0x30)); + } catch (MemoryAccessException e) { + Msg.warn(this, "Failed to read filenameOffset"); + } + } + + Address minAddress = program.getAddressFactory().getDefaultAddressSpace().getMinAddress(); + Address maxAddress = program.getAddressFactory().getDefaultAddressSpace().getMaxAddress(); + if (sdaBase != 0 && minAddress != null && maxAddress != null) { + Register r13 = elfLoadHelper.getProgram().getRegister("r13"); + try { + program.getProgramContext().setValue(r13, minAddress, maxAddress, BigInteger.valueOf(sdaBase)); + } catch (ContextChangeException e) { + Msg.warn(this, "Error setting r13 to sdabase: " + e); + } + } + + if (sda2Base != 0 && minAddress != null && maxAddress != null) { + Register r2 = elfLoadHelper.getProgram().getRegister("r2"); + try { + program.getProgramContext().setValue(r2, minAddress, maxAddress, BigInteger.valueOf(sda2Base)); + } catch (ContextChangeException e) { + Msg.warn(this, "Error setting r2 to sda2base: " + e); + } + } + + int tagOffset = 0; + if (version >= RPL_FILEINFO_V4_1) { + fileInfo.add(DWordDataType.dataType, "flags", null); + fileInfo.add(DWordDataType.dataType, "heapSize", null); + fileInfo.add(DWordDataType.dataType, "tagOffset", null); + + try { + tagOffset = memory.getInt(fileInfoAddr.add(0x3C)); + } catch (MemoryAccessException e) { + Msg.warn(this, "Failed to read tagOffset"); + } + } + + if (version >= RPL_FILEINFO_V4_2) { + fileInfo.add(DWordDataType.dataType, "minVersion", null); + fileInfo.add(DWordDataType.dataType, "compressionLevel", null); + fileInfo.add(DWordDataType.dataType, "trampAddition", null); + fileInfo.add(DWordDataType.dataType, "fileInfoPad", null); + fileInfo.add(DWordDataType.dataType, "cafeSdkVersion", null); + fileInfo.add(DWordDataType.dataType, "cafeSdkRevision", null); + fileInfo.add(WordDataType.dataType, "tlsModuleIndex", null); + fileInfo.add(WordDataType.dataType, "tlsAlignShift", null); + fileInfo.add(DWordDataType.dataType, "runtimeFileInfoSize", null); + } + elfLoadHelper.createData(fileInfoAddr, fileInfo); + + // Mark filename as a string + if (filenameOffset != 0) { + try { + elfLoadHelper.createData(fileInfoAddr.add(filenameOffset), TerminatedStringDataType.dataType); + } catch (AddressOutOfBoundsException e) { + } + } + + // Mark tags as strings + if (tagOffset != 0) { + try { + Address tagAddress = fileInfoAddr.add(tagOffset); + while (true) { + Data d = elfLoadHelper.createData(tagAddress, TerminatedStringDataType.dataType); + int length = d.getLength(); + if (length == 0) { + break; + } + tagAddress = tagAddress.add(length); + } + } catch (AddressOutOfBoundsException e) { + } + } + } +} diff --git a/src/main/java/cafeloader/Cafe_ElfRelocationConstants.java b/src/main/java/cafeloader/Cafe_ElfRelocationConstants.java new file mode 100644 index 0000000..7d484d7 --- /dev/null +++ b/src/main/java/cafeloader/Cafe_ElfRelocationConstants.java @@ -0,0 +1,35 @@ +package cafeloader; + +public class Cafe_ElfRelocationConstants { + public static final int R_PPC_NONE = 0; + public static final int R_PPC_ADDR32 = 1; + public static final int R_PPC_ADDR16_LO = 4; + public static final int R_PPC_ADDR16_HI = 5; + public static final int R_PPC_ADDR16_HA = 6; + public static final int R_PPC_REL24 = 10; + public static final int R_PPC_REL14 = 11; + public static final int R_PPC_DTPMOD32 = 68; + public static final int R_PPC_DTPREL32 = 78; + public static final int R_PPC_EMB_SDA21 = 109; + public static final int R_PPC_EMB_RELSDA = 116; + public static final int R_PPC_DIAB_SDA21_LO = 180; + public static final int R_PPC_DIAB_SDA21_HI = 181; + public static final int R_PPC_DIAB_SDA21_HA = 182; + public static final int R_PPC_DIAB_RELSDA_LO = 183; + public static final int R_PPC_DIAB_RELSDA_HI = 184; + public static final int R_PPC_DIAB_RELSDA_HA = 185; + public static final int R_PPC_GHS_REL16_HA = 251; + public static final int R_PPC_GHS_REL16_HI = 252; + public static final int R_PPC_GHS_REL16_LO = 253; + + // Masks for manipulating Power PC relocation targets + public static final int PPC_WORD32 = 0xFFFFFFFF; + public static final int PPC_WORD30 = 0xFFFFFFFC; + public static final int PPC_LOW24 = 0x03FFFFFC; + public static final int PPC_LOW14 = 0x0020FFFC; + public static final int PPC_HALF16 = 0xFFFF; + + private Cafe_ElfRelocationConstants() { + // no construct + } +} diff --git a/src/main/java/cafeloader/Cafe_ElfRelocationHandler.java b/src/main/java/cafeloader/Cafe_ElfRelocationHandler.java new file mode 100644 index 0000000..9fb2d29 --- /dev/null +++ b/src/main/java/cafeloader/Cafe_ElfRelocationHandler.java @@ -0,0 +1,149 @@ +package cafeloader; + +import ghidra.app.util.bin.format.elf.*; +import ghidra.app.util.bin.format.elf.relocation.ElfRelocationContext; +import ghidra.app.util.bin.format.elf.relocation.ElfRelocationHandler; +import ghidra.program.model.address.Address; +import ghidra.program.model.listing.Program; +import ghidra.program.model.mem.Memory; +import ghidra.program.model.mem.MemoryAccessException; +import ghidra.util.Msg; +import ghidra.util.exception.InvalidInputException; +import ghidra.util.exception.NotFoundException; +import ghidra.program.model.symbol.ExternalLocation; +import ghidra.program.model.symbol.RefType; +import ghidra.program.model.symbol.SourceType; + +public class Cafe_ElfRelocationHandler extends ElfRelocationHandler { + @Override + public boolean canRelocate(ElfHeader elf) { + return elf instanceof RplHeader; + } + + @Override + public void relocate(ElfRelocationContext elfRelocationContext, ElfRelocation relocation, + Address relocationAddress) throws MemoryAccessException, NotFoundException { + int type = relocation.getType(); + if (type == Cafe_ElfRelocationConstants.R_PPC_NONE) { + return; + } + + ElfHeader elf = elfRelocationContext.getElfHeader(); + Program program = elfRelocationContext.getProgram(); + Memory memory = program.getMemory(); + int symbolIndex = relocation.getSymbolIndex(); + int addend = (int) relocation.getAddend(); + int offset = (int) relocationAddress.getOffset(); + ElfSymbol sym = elfRelocationContext.getSymbol(symbolIndex); + int symbolValue = (int) elfRelocationContext.getSymbolValue(sym); + int oldValue = memory.getInt(relocationAddress); + int newValue = 0; + + /* + * If the symbol is in a SHT_RPL_IMPORTS section then we must add a memory + * reference because it will be too far away for the actual relocation to + * be valid itself. + */ + ElfSectionHeader symbolSection = elf.getSections()[sym.getSectionHeaderIndex()]; + if (symbolSection.getType() == Cafe_ElfExtension.SHT_RPL_IMPORTS.value) { + String symbolSectionName = symbolSection.getNameAsString(); + boolean isDataImport = false; + if (symbolSectionName.startsWith(".dimport_")) { + program.getReferenceManager().addMemoryReference(relocationAddress, + elfRelocationContext.getSymbolAddress(sym), RefType.DATA, SourceType.IMPORTED, 0); + isDataImport = true; + } else if (symbolSectionName.startsWith(".fimport_")) { + program.getReferenceManager().addMemoryReference(relocationAddress, + elfRelocationContext.getSymbolAddress(sym), RefType.UNCONDITIONAL_CALL, SourceType.IMPORTED, 0); + } + + String rplName = symbolSectionName.split("import_")[1]; + if (!rplName.endsWith(".rpl")) { + rplName += ".rpl"; + } + + ExternalLocation location = program.getExternalManager().getUniqueExternalLocation(rplName, sym.getNameAsString()); + if (location != null) { + try { + if (isDataImport) { + program.getReferenceManager().addExternalReference(relocationAddress, 1, + location, SourceType.IMPORTED, RefType.DATA); + } else { + program.getReferenceManager().addExternalReference(relocationAddress, 1, + location, SourceType.IMPORTED, RefType.UNCONDITIONAL_CALL); + } + } catch (InvalidInputException e) { + Msg.warn(this, "addExternalReference failed with " + e); + } + } else { + Msg.warn(this, "Failed to find location for " + sym.getNameAsString()); + } + } + + switch (type) { + case Cafe_ElfRelocationConstants.R_PPC_ADDR32: + newValue = symbolValue + addend; + memory.setInt(relocationAddress, newValue); + break; + case Cafe_ElfRelocationConstants.R_PPC_ADDR16_LO: + newValue = symbolValue + addend; + memory.setShort(relocationAddress, (short) newValue); + break; + case Cafe_ElfRelocationConstants.R_PPC_ADDR16_HI: + newValue = (symbolValue + addend) >> 16; + memory.setShort(relocationAddress, (short) newValue); + break; + case Cafe_ElfRelocationConstants.R_PPC_ADDR16_HA: + newValue = (symbolValue + addend + 0x8000) >> 16; + memory.setShort(relocationAddress, (short) newValue); + break; + case Cafe_ElfRelocationConstants.R_PPC_REL24: + newValue = (symbolValue + addend - offset) >> 2; + newValue = ((newValue << 2) & Cafe_ElfRelocationConstants.PPC_LOW24); + newValue = (oldValue & ~Cafe_ElfRelocationConstants.PPC_LOW24) | newValue; + memory.setInt(relocationAddress, newValue); + break; + case Cafe_ElfRelocationConstants.R_PPC_REL14: + newValue = (symbolValue + addend - offset) >> 2; + newValue = (oldValue & ~Cafe_ElfRelocationConstants.PPC_LOW14) | + ((newValue << 2) & Cafe_ElfRelocationConstants.PPC_LOW14); + memory.setInt(relocationAddress, newValue); + break; + case Cafe_ElfRelocationConstants.R_PPC_GHS_REL16_HA: + newValue = (symbolValue + addend - offset + 0x8000) >> 16; + memory.setShort(relocationAddress, (short) newValue); + break; + case Cafe_ElfRelocationConstants.R_PPC_GHS_REL16_HI: + newValue = (symbolValue + addend - offset) >> 16; + memory.setShort(relocationAddress, (short) newValue); + break; + case Cafe_ElfRelocationConstants.R_PPC_GHS_REL16_LO: + newValue = (symbolValue + addend - offset); + memory.setShort(relocationAddress, (short) newValue); + break; + case Cafe_ElfRelocationConstants.R_PPC_DTPREL32: + newValue = symbolValue + addend; + memory.setInt(relocationAddress, newValue); + break; + case Cafe_ElfRelocationConstants.R_PPC_DTPMOD32: + // TODO: Do we need a tlsModuleIndex? + // *virt_cast(target) = tlsModuleIndex; + memory.setInt(relocationAddress, 0); + break; + case Cafe_ElfRelocationConstants.R_PPC_EMB_SDA21: + // TODO: SDA relocations require sda / sda2 base from SHT_RPL_FILEINFO section + case Cafe_ElfRelocationConstants.R_PPC_EMB_RELSDA: + case Cafe_ElfRelocationConstants.R_PPC_DIAB_SDA21_LO: + case Cafe_ElfRelocationConstants.R_PPC_DIAB_SDA21_HI: + case Cafe_ElfRelocationConstants.R_PPC_DIAB_SDA21_HA: + case Cafe_ElfRelocationConstants.R_PPC_DIAB_RELSDA_LO: + case Cafe_ElfRelocationConstants.R_PPC_DIAB_RELSDA_HI: + case Cafe_ElfRelocationConstants.R_PPC_DIAB_RELSDA_HA: + default: + String symbolName = sym.getNameAsString(); + markAsUnhandled(program, relocationAddress, type, symbolIndex, symbolName, + elfRelocationContext.getLog()); + break; + } + } +} diff --git a/src/main/java/cafeloader/RplConverter.java b/src/main/java/cafeloader/RplConverter.java new file mode 100644 index 0000000..38f11b8 --- /dev/null +++ b/src/main/java/cafeloader/RplConverter.java @@ -0,0 +1,127 @@ +package cafeloader; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.DataFormatException; +import java.util.zip.Inflater; + +import generic.continues.RethrowContinuesFactory; +import ghidra.app.util.bin.BinaryReader; +import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.bin.format.FactoryBundledWithBinaryReader; +import ghidra.app.util.bin.format.elf.ElfConstants; +import ghidra.app.util.bin.format.elf.ElfException; +import ghidra.app.util.bin.format.elf.ElfSectionHeader; +import ghidra.app.util.bin.format.elf.ElfSectionHeaderConstants; +import ghidra.app.util.bin.format.elf.RplSectionHeader; +import ghidra.util.task.TaskMonitor; +import ghidra.util.*; + +public class RplConverter { + public static final int SHF_RPL_ZLIB = 0x08000000; + + public static final int SHT_RPL_EXPORTS = 0x80000001; + public static final int SHT_RPL_IMPORTS = 0x80000002; + public static final int SHT_RPL_CRCS = 0x80000003; + public static final int SHT_RPL_FILEINFO = 0x80000004; + + public static final byte ELFOSABI_CAFE = (byte) 0xCA; + public static final byte ELFOSABI_VERSION_CAFE = (byte) 0xFE; + + public static byte[] convertRpl(ByteProvider byteProvider, TaskMonitor monitor) + throws ElfException, IOException, DataFormatException { + // Read elf header + RplHeader elfHeader = RplHeader.createRplHeader(RethrowContinuesFactory.INSTANCE, byteProvider); + BinaryReader reader = elfHeader.getReader(); + + // Write elf header + ByteArrayOutputStream out = new ByteArrayOutputStream((int) byteProvider.length()); + DataConverter dc = BigEndianDataConverter.INSTANCE; + out.write(ElfConstants.MAGIC_BYTES); + out.write(ElfConstants.ELF_CLASS_32); + out.write(ElfConstants.ELF_DATA_BE); + out.write(ElfConstants.EV_CURRENT); + out.write(ELFOSABI_CAFE); + out.write(ELFOSABI_VERSION_CAFE); + out.write(new byte[7]); // ident padding + out.write(dc.getBytes(elfHeader.e_type())); + out.write(dc.getBytes(elfHeader.e_machine())); + out.write(dc.getBytes(elfHeader.e_version())); + out.write(dc.getBytes((int) elfHeader.e_entry())); + out.write(dc.getBytes((int) 0)); // phoff + out.write(dc.getBytes((int) 0x40)); // shoff + out.write(dc.getBytes(elfHeader.e_flags())); + out.write(dc.getBytes(elfHeader.e_ehsize())); + out.write(dc.getBytes((short) 0)); // phentsize + out.write(dc.getBytes((short) 0)); // phnum + out.write(dc.getBytes(elfHeader.e_shentsize())); + out.write(dc.getBytes(elfHeader.e_shnum())); + out.write(dc.getBytes(elfHeader.e_shstrndx())); + out.write(new byte[0x40 - 0x34]); // padding until section headers + + // Read sections + long sectionDataOffset = elfHeader.e_shoff() + (elfHeader.e_shnum() * elfHeader.e_shentsize()); + ByteArrayOutputStream sectionData = new ByteArrayOutputStream(); + + for (int i = 0; i < elfHeader.e_shnum(); ++i) { + long index = elfHeader.e_shoff() + (i * elfHeader.e_shentsize()); + reader.setPointerIndex(index); + ElfSectionHeader sectionHeader = RplSectionHeader.createElfSectionHeader((FactoryBundledWithBinaryReader) reader, elfHeader); + long size = sectionHeader.getSize(); + reader.setPointerIndex(sectionHeader.getOffset()); + + // Read & write section data + if (sectionHeader.getType() != ElfSectionHeaderConstants.SHT_NOBITS) { + if ((sectionHeader.getFlags() & SHF_RPL_ZLIB) == SHF_RPL_ZLIB) { + size = reader.readNextInt(); + byte[] inflatedData = new byte[(int) size]; + byte[] deflatedData = reader.readNextByteArray((int) sectionHeader.getSize() - 4); + Inflater inflater = new Inflater(); + inflater.setInput(deflatedData); + inflater.inflate(inflatedData); + inflater.end(); + sectionData.write(inflatedData); + } else if (size > 0) { + byte[] inflatedData = reader.readNextByteArray((int) size); + sectionData.write(inflatedData); + } + } + + // Ghidra reads section type as a signed integer which breaks the + // rpl section types, so we translate them to a different value. + int sectionType = sectionHeader.getType(); + if (sectionType == SHT_RPL_EXPORTS) { + sectionType = Cafe_ElfExtension.SHT_RPL_EXPORTS.value; + } else if (sectionType == SHT_RPL_IMPORTS) { + sectionType = Cafe_ElfExtension.SHT_RPL_IMPORTS.value; + } else if (sectionType == SHT_RPL_FILEINFO) { + sectionType = Cafe_ElfExtension.SHT_RPL_FILEINFO.value; + } else if (sectionType == SHT_RPL_CRCS) { + sectionType = Cafe_ElfExtension.SHT_RPL_CRCS.value; + } + + // Write section header + out.write(dc.getBytes(sectionHeader.getName())); + out.write(dc.getBytes(sectionType)); + out.write(dc.getBytes((int) sectionHeader.getFlags())); + out.write(dc.getBytes((int) sectionHeader.getAddress())); + + if (sectionHeader.getType() != ElfSectionHeaderConstants.SHT_NOBITS && size > 0) { + out.write(dc.getBytes((int) sectionDataOffset)); + out.write(dc.getBytes((int) size)); + sectionDataOffset += size; + } else { + out.write(dc.getBytes((int) sectionHeader.getOffset())); + out.write(dc.getBytes((int) sectionHeader.getSize())); + } + + out.write(dc.getBytes(sectionHeader.getLink())); + out.write(dc.getBytes(sectionHeader.getInfo())); + out.write(dc.getBytes((int) sectionHeader.getAddressAlignment())); + out.write(dc.getBytes((int) sectionHeader.getEntrySize())); + } + + out.write(sectionData.toByteArray()); + return out.toByteArray(); + } +} diff --git a/src/main/java/cafeloader/RplHeader.java b/src/main/java/cafeloader/RplHeader.java new file mode 100644 index 0000000..0c998ed --- /dev/null +++ b/src/main/java/cafeloader/RplHeader.java @@ -0,0 +1,33 @@ +package cafeloader; + +import java.lang.Throwable; + +import generic.continues.GenericFactory; +import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.bin.format.elf.ElfException; +import ghidra.app.util.bin.format.elf.ElfHeader; + +public class RplHeader extends ElfHeader +{ + public static RplHeader createRplHeader(GenericFactory factory, ByteProvider provider) + throws ElfException { + RplHeader elfHeader = (RplHeader) factory.create(RplHeader.class); + elfHeader.initElfHeader(factory, provider); + return elfHeader; + } + + @Override + public short e_machine() { + // Hack to force use of Cafe_ElfRelocationHandler and Cafe_ElfExtension instead of PowerPC_* + StackTraceElement[] trace = new Throwable().getStackTrace(); + if (trace.length >= 6 && + ((trace[6].getClassName() == "ghidra.app.util.bin.format.elf.relocation.PowerPC_ElfRelocationHandler" && + trace[6].getMethodName() == "canRelocate") || + (trace[6].getClassName() == "ghidra.app.util.bin.format.elf.extend.PowerPC_ElfExtension" && + trace[6].getMethodName() == "canHandle"))) { + return 0; + } + + return super.e_machine(); + } +} diff --git a/src/main/java/de/mas/ghidra/utils/Utils.java b/src/main/java/de/mas/ghidra/utils/Utils.java deleted file mode 100644 index 1e6d4de..0000000 --- a/src/main/java/de/mas/ghidra/utils/Utils.java +++ /dev/null @@ -1,51 +0,0 @@ -package de.mas.ghidra.utils; - -import java.nio.ByteBuffer; - -import ghidra.util.task.TaskMonitor; - -public class Utils { - private Utils() { - } - - /** - * Grows a ByteBuffer if needed. - * - * @param buffer the original buffer - * @param size the needed size. - * @return A byte buffer with the expected size. If the buffer was big enough, - * the original buffer will be returned, otherwise a new one will be - * created. - */ - public static ByteBuffer checkAndGrowByteBuffer(ByteBuffer buffer, long size) { - // This probably the worst way to do this. - if (buffer.remaining() < size) { - ByteBuffer newBuffer = ByteBuffer.allocate((int) (buffer.capacity() + size - buffer.remaining())); - newBuffer.put(buffer.array()); - return newBuffer; - } - return buffer; - } - - public static String stringFromStringTable(byte[] stringTable, int index) { - ByteBuffer buf = ByteBuffer.wrap(stringTable); - - int pos = index; - - StringBuilder result = new StringBuilder(); - - for (byte b; (b = buf.get(pos)) != 0; pos++) { - result.append((char) b); - } - - return result.toString(); - } - - public static void logWrapper(String msg, TaskMonitor monitor) { - if (monitor != null) { - monitor.setMessage(msg); - } - System.out.println(msg); - } - -} diff --git a/src/main/java/de/mas/ghidra/wiiu/RPXUtils.java b/src/main/java/de/mas/ghidra/wiiu/RPXUtils.java deleted file mode 100644 index d073b69..0000000 --- a/src/main/java/de/mas/ghidra/wiiu/RPXUtils.java +++ /dev/null @@ -1,189 +0,0 @@ -package de.mas.ghidra.wiiu; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.Arrays; -import java.util.zip.DataFormatException; -import java.util.zip.Inflater; - -import de.mas.ghidra.utils.Utils; -import generic.continues.RethrowContinuesFactory; -import ghidra.app.util.bin.ByteProvider; -import ghidra.app.util.bin.format.elf.ElfConstants; -import ghidra.app.util.bin.format.elf.ElfException; -import ghidra.app.util.bin.format.elf.ElfHeader; -import ghidra.app.util.bin.format.elf.ElfSectionHeader; -import ghidra.app.util.bin.format.elf.ElfSectionHeaderConstants; -import ghidra.app.util.bin.format.elf.ElfSymbol; -import ghidra.util.exception.CancelledException; -import ghidra.util.task.TaskMonitor; - -public class RPXUtils { - private static byte[] RPX_MAGIC = new byte[] { 0x7F, 0x45, 0x4C, 0x46, 0x01, 0x02, 0x01, (byte) 0xCA, (byte) 0xFE }; - public static final int SHF_RPL_ZLIB = 0x08000000; - public static final int SHT_NOBITS = 0x00000008; - - public static final int SHT_RPL_EXPORTS = 0x80000001; - public static final int SHT_RPL_IMPORTS = 0x80000002; - public static final int SHT_RPL_CRCS = 0x80000003; - public static final int SHT_RPL_FILEINFO = 0x80000004; - - public static byte[] convertRPX(ByteProvider bProvider, TaskMonitor monitor) - throws ElfException, IOException, CancelledException, DataFormatException { - ElfHeader elfFile = ElfHeader.createElfHeader(RethrowContinuesFactory.INSTANCE, bProvider); - elfFile.parse(); - - ByteBuffer buffer = ByteBuffer.allocate(0); - - long shdr_elf_offset = elfFile.e_ehsize() & 0xFFFFFFFF; - long shdr_data_elf_offset = shdr_elf_offset + elfFile.e_shnum() * elfFile.e_shentsize(); - - // Let's get / decompress the section header string table at first. - ElfSectionHeader sh_str_sh = elfFile.getSections()[elfFile.e_shstrndx()]; - byte[] sh_str_sh_data = new byte[0]; - if (sh_str_sh.getOffset() != 0) { - if ((sh_str_sh.getFlags() & SHT_NOBITS) != SHT_NOBITS) { - sh_str_sh_data = sh_str_sh.getData(); - if ((sh_str_sh.getFlags() & SHF_RPL_ZLIB) == SHF_RPL_ZLIB) { - long section_size_inflated = ByteBuffer.wrap(Arrays.copyOf(sh_str_sh_data, 4)).getInt() - & 0xFFFFFFFF; - Inflater inflater = new Inflater(); - inflater.setInput(sh_str_sh_data, 4, (int) sh_str_sh.getSize() - 4); // the first byte is the size - - byte[] decompressed = new byte[(int) section_size_inflated]; - - inflater.inflate(decompressed); - inflater.end(); - - sh_str_sh_data = decompressed; - } - } - } - - for (ElfSectionHeader h : elfFile.getSections()) { - monitor.checkCanceled(); - long curSize = h.getSize(); - long flags = h.getFlags(); - long offset = h.getOffset(); - String sectionName = Utils.stringFromStringTable(sh_str_sh_data, h.getName()); - - if (offset != 0) { - if ((flags & SHT_NOBITS) != SHT_NOBITS) { - byte[] data = h.getData(); - if (h.getType() == SHT_RPL_CRCS || h.getType() == SHT_RPL_FILEINFO) { - data = new byte[0]; - curSize = 0; - } else { - if ((flags & SHF_RPL_ZLIB) == SHF_RPL_ZLIB) { - Utils.logWrapper( - "Decompressing section " + Utils.stringFromStringTable(sh_str_sh_data, h.getName()), - monitor); - long section_size_inflated = ByteBuffer.wrap(Arrays.copyOf(data, 4)).getInt() & 0xFFFFFFFF; - Inflater inflater = new Inflater(); - inflater.setInput(data, 4, (int) h.getSize() - 4); // the first byte is the size - - byte[] decompressed = new byte[(int) section_size_inflated]; - - inflater.inflate(decompressed); - - inflater.end(); - - // Is this alignment really necessary? - curSize = (section_size_inflated + 0x03) & ~0x3; - flags &= ~SHF_RPL_ZLIB; - data = decompressed; - } - } - - long newEnd = shdr_data_elf_offset + curSize; - - buffer = Utils.checkAndGrowByteBuffer(buffer, newEnd); - buffer.position((int) shdr_data_elf_offset); - buffer.put(data); - offset = shdr_data_elf_offset; - shdr_data_elf_offset += curSize; - } - } - - if (h.getType() == ElfSectionHeaderConstants.SHT_SYMTAB - || h.getType() == ElfSectionHeaderConstants.SHT_DYNSYM) { - Utils.logWrapper("Fix imports for section " + sectionName + " (" + h.getTypeAsString() + ")", monitor); - int symbolCount = (int) ((int) (curSize) / h.getEntrySize()); - long entryPos = 0; - for (int i = 0; i < symbolCount; i++) { - monitor.checkCanceled(); - long entry_offset = (int) (offset + entryPos); - - int sectionIndex = buffer.getShort((int) entry_offset + 14) & 0xFFFF; - ElfSectionHeader curSection = elfFile.getSections()[sectionIndex]; - int type = curSection.getType(); - - if (type == SHT_RPL_IMPORTS) { - String symbolSectionName = Utils.stringFromStringTable(sh_str_sh_data, curSection.getName()); - buffer.position((int) (entry_offset + 4)); - // Set Value to a custom symbol address - buffer.position((int) (entry_offset + 12)); - - // Force FUNC type so the name will be used in the decompiler. - byte symbolType = ElfSymbol.STT_FUNC; - // But change to OBJECT for data imports - if (symbolSectionName.startsWith(".d")) { - symbolType = ElfSymbol.STT_OBJECT; - } - // Change type to LOCAL so it won't be in the export list. - buffer.put((byte) ((ElfSymbol.STB_LOCAL << 4) | symbolType)); // 12 - } - entryPos += h.getEntrySize(); - } - } - - buffer = Utils.checkAndGrowByteBuffer(buffer, shdr_elf_offset + 0x28); - - Utils.logWrapper("Converting section " + sectionName + " (" + h.getTypeAsString() + ")", monitor); - - buffer.position((int) shdr_elf_offset); - buffer.putInt(h.getName()); - if (h.getType() == SHT_RPL_CRCS || h.getType() == SHT_RPL_FILEINFO) { - buffer.putInt(ElfSectionHeaderConstants.SHT_NULL); - } else { - buffer.putInt(h.getType()); - } - buffer.putInt((int) flags); - - buffer.putInt((int) h.getAddress()); - buffer.putInt((int) offset); - buffer.putInt((int) curSize); - buffer.putInt(h.getLink()); - buffer.putInt(h.getInfo()); - - buffer.putInt((int) h.getAddressAlignment()); - buffer.putInt((int) h.getEntrySize()); - - shdr_elf_offset += 0x28; - } - - Utils.logWrapper("Create new ELF header", monitor); - - buffer = Utils.checkAndGrowByteBuffer(buffer, 36); - - buffer.position(0); - buffer.put(RPX_MAGIC); - buffer.position(0x10); - buffer.putShort(ElfConstants.ET_EXEC); // e.e_type()); - buffer.putShort(elfFile.e_machine()); - buffer.putInt(elfFile.e_version()); - buffer.putInt((int) elfFile.e_entry()); - buffer.putInt((int) elfFile.e_phoff()); - buffer.putInt(elfFile.e_ehsize()); // e.e_shoff()); - buffer.putInt(elfFile.e_flags()); - buffer.putShort(elfFile.e_ehsize()); - buffer.putShort(elfFile.e_phentsize()); - buffer.putShort(elfFile.e_phnum()); - buffer.putShort(elfFile.e_shentsize()); - buffer.putShort(elfFile.e_shnum()); - buffer.putShort(elfFile.e_shstrndx()); - - return buffer.array(); - } - -} \ No newline at end of file diff --git a/src/main/java/ghidra/app/util/bin/format/elf/RplSectionHeader.java b/src/main/java/ghidra/app/util/bin/format/elf/RplSectionHeader.java new file mode 100644 index 0000000..e3654ee --- /dev/null +++ b/src/main/java/ghidra/app/util/bin/format/elf/RplSectionHeader.java @@ -0,0 +1,12 @@ +package ghidra.app.util.bin.format.elf; + +import java.io.IOException; + +import ghidra.app.util.bin.format.FactoryBundledWithBinaryReader; + +public class RplSectionHeader extends ElfSectionHeader { + public static ElfSectionHeader createElfSectionHeader(FactoryBundledWithBinaryReader reader, + ElfHeader header) throws IOException { + return ElfSectionHeader.createElfSectionHeader(reader, header); + } +} diff --git a/src/main/java/ghidra/app/util/opinion/CafeLoader.java b/src/main/java/ghidra/app/util/opinion/CafeLoader.java new file mode 100644 index 0000000..0f032ad --- /dev/null +++ b/src/main/java/ghidra/app/util/opinion/CafeLoader.java @@ -0,0 +1,87 @@ +package ghidra.app.util.opinion; + +import cafeloader.*; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.zip.DataFormatException; + +import generic.continues.GenericFactory; +import ghidra.app.util.Option; +import ghidra.app.util.bin.ByteArrayProvider; +import ghidra.app.util.bin.ByteProvider; +import ghidra.app.util.bin.format.elf.ElfException; +import ghidra.app.util.importer.MessageLog; +import ghidra.app.util.importer.MessageLogContinuesFactory; +import ghidra.program.model.lang.LanguageCompilerSpecPair; +import ghidra.program.model.listing.Program; +import ghidra.util.exception.CancelledException; +import ghidra.util.task.TaskMonitor; + +public class CafeLoader extends ElfLoader { + @Override + public Collection findSupportedLoadSpecs(ByteProvider provider) throws IOException { + byte[] header = new byte[] { 0x7F, 0x45, 0x4C, 0x46, 0x01, 0x02, 0x01, (byte) 0xCA, (byte) 0xFE }; + List loadSpecs = new ArrayList<>(); + + if (Arrays.equals(provider.readBytes(0, header.length), header)) { + List results = QueryOpinionService.query(getName(), "wiiu", null); + boolean hasGekkoProcessor = false; + + for (QueryResult result : results) { + if (result.pair.languageID.getIdAsString().contains("Gekko")) { + hasGekkoProcessor = true; + } + } + + for (QueryResult result : results) { + if (result.pair.languageID.getIdAsString().contains("Gekko")) { + loadSpecs.add(new LoadSpec(this, 0, new QueryResult(result.pair, true))); + } else { + loadSpecs.add(new LoadSpec(this, 0, new QueryResult(result.pair, hasGekkoProcessor ? false : result.preferred))); + } + } + + if (loadSpecs.isEmpty()) { + loadSpecs.add(new LoadSpec(this, 0, new LanguageCompilerSpecPair("PowerPC:BE:32:default", "default"), true)); + } + } + + return loadSpecs; + } + + @Override + public String getName() { + return "Wii U / CafeOS Binary (RPX/RPL)"; + } + + @Override + public LoaderTier getTier() { + return LoaderTier.SPECIALIZED_TARGET_LOADER; + } + + @Override + public int getTierPriority() { + return 0; + } + + @Override + public void load(ByteProvider provider, LoadSpec loadSpec, List