mirror of
https://github.com/Maschell/GhidraRPXLoader.git
synced 2024-11-29 11:24:22 +01:00
commit
9d4a4fdcfb
@ -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.
|
||||||
|
@ -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 primary="wiiu" processor="PowerPC" endian="big" size="32" variant="default" />
|
||||||
</constraint>
|
</constraint>
|
||||||
</opinions>
|
</opinions>
|
||||||
|
324
src/main/java/cafeloader/Cafe_ElfExtension.java
Normal file
324
src/main/java/cafeloader/Cafe_ElfExtension.java
Normal 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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
src/main/java/cafeloader/Cafe_ElfRelocationConstants.java
Normal file
35
src/main/java/cafeloader/Cafe_ElfRelocationConstants.java
Normal 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
|
||||||
|
}
|
||||||
|
}
|
149
src/main/java/cafeloader/Cafe_ElfRelocationHandler.java
Normal file
149
src/main/java/cafeloader/Cafe_ElfRelocationHandler.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
127
src/main/java/cafeloader/RplConverter.java
Normal file
127
src/main/java/cafeloader/RplConverter.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
33
src/main/java/cafeloader/RplHeader.java
Normal file
33
src/main/java/cafeloader/RplHeader.java
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
87
src/main/java/ghidra/app/util/opinion/CafeLoader.java
Normal file
87
src/main/java/ghidra/app/util/opinion/CafeLoader.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user