Skip to content

Commit

Permalink
Now rpx/rpl files can be imported directly.
Browse files Browse the repository at this point in the history
  • Loading branch information
Maschell committed Mar 8, 2019
1 parent a366f0e commit f9bc953
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 338 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This is a (WIP) simple extension to open .rpx and .rpl files with Ghidra.

Install the extension by using the `Install Extensions` option inside Ghidra or extracting the .zip manually into `[GHIDRA_ROOT]\Ghidra\Extensions`. Make sure to restart the program after installing.

Once the extension is installed, you can open a .rpx/.rpl via `File->Open File System...`. After opening the file, you should a filetree with a `converted.elf`, right click on it and select `Import`, confirm by pressing `Single file`.
Once the extension is installed, you import a .rpx/.rpl via `File->Import...`.

# Building

Expand Down
5 changes: 5 additions & 0 deletions data/languages/RPX.opinion
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<opinions>
<constraint loader="Wii U Executable (RPX/RPL)" compilerSpecID="default">
<constraint primary="0" processor="PowerPC" endian="big" size="32" />
</constraint>
</opinions>
27 changes: 27 additions & 0 deletions src/main/java/de/mas/ghidra/utils/Utils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package de.mas.ghidra.utils;

import java.nio.ByteBuffer;

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;
}
}
155 changes: 155 additions & 0 deletions src/main/java/de/mas/ghidra/wiiu/RPXUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
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.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();

for (ElfSectionHeader h : elfFile.getSections()) {
monitor.checkCanceled();
long curSize = h.getSize();
long flags = h.getFlags();
long offset = h.getOffset();

if (offset != 0) {
if ((flags & SHT_NOBITS) != SHT_NOBITS) {
byte[] data = h.getData();

if ((flags & SHF_RPL_ZLIB) == SHF_RPL_ZLIB) {
monitor.setMessage("Decompressing section " + h.getTypeAsString());
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);
// System.out.println("Write data " + String.format("%08X",
// shdr_data_elf_offset));
buffer.put(data);
offset = shdr_data_elf_offset;
shdr_data_elf_offset += curSize;
}
}

// Hacky way to fix import relocations
if (h.getType() == ElfSectionHeaderConstants.SHT_SYMTAB) {
monitor.setMessage("Fix import relocations " + h.getTypeAsString());
int symbolCount = (int) ((int) (curSize) / h.getEntrySize());
long entryPos = 0;
for (int i = 0; i < symbolCount; i++) {
monitor.checkCanceled();
long test_offset = (int) (offset + entryPos + 4);
buffer.position((int) test_offset);
int val = buffer.getInt();

if ((val & 0xF0000000L) == 0xC0000000L) {
long fixedAddress = val - 0xC0000000L + 0x01000000L;
buffer.position((int) test_offset);
buffer.putInt((int) fixedAddress);
}
entryPos += h.getEntrySize();
}
}

buffer = Utils.checkAndGrowByteBuffer(buffer, shdr_elf_offset + 0x28);

monitor.setMessage("Converting section " + h.getTypeAsString());

buffer.position((int) shdr_elf_offset);
System.out.println("Write header " + String.format("%08X", shdr_elf_offset));
buffer.putInt(h.getName());
if (h.getType() == SHT_RPL_CRCS || h.getType() == SHT_RPL_FILEINFO || h.getType() == SHT_RPL_EXPORTS
|| h.getType() == SHT_RPL_IMPORTS) {
buffer.putInt(ElfSectionHeaderConstants.SHT_NULL);
} else {
buffer.putInt(h.getType());
}
buffer.putInt((int) flags);

// Hacky way to fix import relocations
if ((h.getAddress() & 0xF0000000L) == 0xC0000000L) {
long fixedAddress = h.getAddress() - 0xC0000000L + 0x01000000L;
buffer.putInt((int) fixedAddress);
} else {
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;
}

monitor.setMessage("Create new ELF header");

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();
}
}
79 changes: 79 additions & 0 deletions src/main/java/ghidra/app/util/opinion/RPXLoader.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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.importer.MemoryConflictHandler;
import ghidra.app.util.importer.MessageLog;
import ghidra.app.util.importer.MessageLogContinuesFactory;
import ghidra.program.model.listing.Program;
import ghidra.util.exception.CancelledException;
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;
}

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

}
Loading

0 comments on commit f9bc953

Please sign in to comment.