Merge pull request #4 from exjam/improve

Improve RPL loading
This commit is contained in:
Maschell 2019-10-03 13:05:09 +02:00 committed by GitHub
commit 9d4a4fdcfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 779 additions and 405 deletions

View File

@ -2,6 +2,14 @@
This is a (WIP) simple extension to open .rpx and .rpl files with Ghidra. 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 # 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. 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.

View File

@ -1,5 +1,6 @@
<opinions> <opinions>
<constraint loader="Wii U Executable (RPX/RPL)" compilerSpecID="default"> <constraint loader="Wii U / CafeOS Binary (RPX/RPL)" compilerSpecID="default">
<constraint primary="0" processor="PowerPC" endian="big" size="32" /> <constraint primary="wiiu" processor="PowerPC" endian="big" size="32" variant="Gekko/Broadway" />
</constraint> <constraint primary="wiiu" processor="PowerPC" endian="big" size="32" variant="default" />
</constraint>
</opinions> </opinions>

View File

@ -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) {
}
}
}
}

View File

@ -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
}
}

View File

@ -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<int32_t *>(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;
}
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException {
byte[] header = new byte[] { 0x7F, 0x45, 0x4C, 0x46, 0x01, 0x02, 0x01, (byte) 0xCA, (byte) 0xFE };
List<LoadSpec> loadSpecs = new ArrayList<>();
if (Arrays.equals(provider.readBytes(0, header.length), header)) {
List<QueryResult> 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<Option> options, Program program,
TaskMonitor monitor, MessageLog log) throws IOException {
try {
GenericFactory factory = MessageLogContinuesFactory.create(log);
byte[] data = RplConverter.convertRpl(provider, monitor);
RplHeader rpl = RplHeader.createRplHeader(factory, new ByteArrayProvider(data));
ElfProgramBuilder.loadElf(rpl, program, options, log, monitor);
} catch (ElfException e) {
throw new IOException(e.getMessage());
} catch (CancelledException e) {
throw new IOException(e.getMessage());
} catch (DataFormatException e) {
throw new IOException(e.getMessage());
}
}
}

View File

@ -1,162 +0,0 @@
package ghidra.app.util.opinion;
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 de.mas.ghidra.wiiu.RPXUtils;
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.bin.format.elf.ElfHeader;
import ghidra.app.util.bin.format.elf.ElfRelocation;
import ghidra.app.util.bin.format.elf.ElfRelocationTable;
import ghidra.app.util.bin.format.elf.ElfSectionHeader;
import ghidra.app.util.bin.format.elf.ElfSymbol;
import ghidra.app.util.importer.MemoryConflictHandler;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.importer.MessageLogContinuesFactory;
import ghidra.program.database.external.ExternalManagerDB;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressSpace;
import ghidra.program.model.listing.Program;
import ghidra.program.model.symbol.ExternalLocation;
import ghidra.program.model.symbol.RefType;
import ghidra.program.model.symbol.SourceType;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.InvalidInputException;
import ghidra.util.task.TaskMonitor;
public class RPXLoader extends ElfLoader {
@Override
public Collection<LoadSpec> findSupportedLoadSpecs(ByteProvider provider) throws IOException {
byte[] header = new byte[] { 0x7F, 0x45, 0x4C, 0x46, 0x01, 0x02, 0x01, (byte) 0xCA, (byte) 0xFE };
List<LoadSpec> loadSpecs = new ArrayList<>();
if (Arrays.equals(provider.readBytes(0, header.length), header)) {
List<QueryResult> results = QueryOpinionService.query(getName(), "0", null);
for (QueryResult result : results) {
loadSpecs.add(new LoadSpec(this, 0, result));
}
if (loadSpecs.isEmpty()) {
loadSpecs.add(new LoadSpec(this, 0, true));
}
return loadSpecs;
}
return loadSpecs;
}
@Override
public String getName() {
return "Wii U Executable (RPX/RPL)";
}
@Override
public LoaderTier getTier() {
return LoaderTier.SPECIALIZED_TARGET_LOADER;
}
@Override
public int getTierPriority() {
return 0;
}
// public static final int R_PPC_REL24 = 10;
@Override
public void load(ByteProvider provider, LoadSpec loadSpec, List<Option> options, Program program,
MemoryConflictHandler handler, TaskMonitor monitor, MessageLog log) throws IOException {
try {
GenericFactory factory = MessageLogContinuesFactory.create(log);
byte[] data = RPXUtils.convertRPX(provider, monitor);
ElfHeader elf = ElfHeader.createElfHeader(factory, new ByteArrayProvider(data));
ElfProgramBuilder.loadElf(elf, program, options, log, handler, monitor);
AddressSpace aspace = program.getAddressFactory().getDefaultAddressSpace();
for (ElfRelocationTable table : elf.getRelocationTables()) {
for (ElfRelocation reloc : table.getRelocations()) {
int sindex = reloc.getSymbolIndex();
ElfSymbol symbol = table.getAssociatedSymbolTable().getSymbols()[sindex];
ElfSectionHeader section = elf.getSections()[symbol.getSectionHeaderIndex()];
if (section.getType() == RPXUtils.SHT_RPL_IMPORTS) {
int offset = (int) (reloc.getOffset() & ~3);
String rplName = section.getNameAsString();
if (rplName.contains("import_")) {
rplName = rplName.split("import_")[1];
if (!rplName.endsWith(".rpl")) {
rplName += ".rpl";
}
} else {
continue;
}
Address addr = aspace.getAddress(offset);
boolean isData = section.getNameAsString().startsWith(".d");
ExternalManagerDB em = (ExternalManagerDB) program.getExternalManager();
ExternalLocation location;
RefType type = RefType.UNCONDITIONAL_CALL;
if (!isData) {
location = em.addExtFunction(rplName, symbol.getNameAsString(), null, SourceType.IMPORTED);
} else {
type = RefType.DATA;
location = em.addExtLocation(rplName, symbol.getNameAsString(), null, SourceType.IMPORTED);
}
// Attempt to remove to auto analyzed memory reference that's created due to the
// relocation.
// if (reloc.getType() == R_PPC_REL24) {
// Relocation r = program.getRelocationTable().getRelocation(addr);
// program.getRelocationTable().remove(r);
// }
// We need this to have working references. (=> clicking on Imports, Show
// Referenences to.. is working)
// Setting the RefType.INVALID works for some reason!
// If the set it to DATA, everything is treated like DATA, and if we use
// something like "UNCONDITIONAL_CALL" for functions
// then decompiler doesn't get the right function names anymore.
program.getReferenceManager().addExternalReference(addr, 1, location, SourceType.IMPORTED,
RefType.INVALID);
// force the memory reference to the target address, even if the referenced
// address is too far away!
program.getReferenceManager().addMemoryReference(addr, aspace.getAddress(symbol.getValue()),
type, SourceType.IMPORTED, 0);
// Add a comment to easily see from which rpl the function is coming.
program.getListing().setComment(addr, 0, rplName + "::" + symbol.getNameAsString());
}
}
}
} catch (ElfException e) {
throw new IOException(e.getMessage());
} catch (CancelledException e) {
// TODO: Caller should properly handle CancelledException instead
throw new IOException(e.getMessage());
} catch (DataFormatException e) {
throw new IOException(e.getMessage());
} catch (DuplicateNameException e) {
throw new IOException(e.getMessage());
} catch (InvalidInputException e) {
throw new IOException(e.getMessage());
}
}
}