Now rpx/rpl files can be imported directly.

This commit is contained in:
Maschell 2019-03-08 19:22:40 +01:00
parent a366f0eafc
commit f9bc953bd6
6 changed files with 267 additions and 338 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,337 +0,0 @@
/****************************************************************************
* Copyright (C) 2019 Maschell
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
****************************************************************************/
package rpx;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import org.apache.commons.collections4.map.HashedMap;
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.formats.gfilesystem.FSRL;
import ghidra.formats.gfilesystem.FSRLRoot;
import ghidra.formats.gfilesystem.FSUtilities;
import ghidra.formats.gfilesystem.FileSystemIndexHelper;
import ghidra.formats.gfilesystem.FileSystemRefManager;
import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.formats.gfilesystem.GFile;
import ghidra.formats.gfilesystem.GFileSystem;
import ghidra.formats.gfilesystem.annotations.FileSystemInfo;
import ghidra.formats.gfilesystem.factory.GFileSystemFactoryFull;
import ghidra.formats.gfilesystem.factory.GFileSystemProbeFull;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
*
*/
@FileSystemInfo(type = "rpx", description = "RPX", factory = RPXFileSystem.RPXFileSystemFactory.class, priority = FileSystemInfo.PRIORITY_HIGH)
public class RPXFileSystem implements GFileSystem {
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;
private final FSRLRoot fsFSRL;
private FileSystemIndexHelper<ElfData> fsih;
private FileSystemRefManager refManager = new FileSystemRefManager(this);
private ByteProvider provider;
/**
* File system constructor.
*
* @param fsFSRL The root {@link FSRL} of the file system.
* @param provider The file system provider.
*/
public RPXFileSystem(FSRLRoot fsFSRL, ByteProvider provider) {
this.fsFSRL = fsFSRL;
this.provider = provider;
this.fsih = new FileSystemIndexHelper<>(this, fsFSRL);
}
/**
* Mounts (opens) the file system.
*
* @param monitor A cancellable task monitor.
*/
public void mount(TaskMonitor monitor) {
monitor.setMessage("Opening " + RPXFileSystem.class.getSimpleName() + "...");
try {
ElfData data = convertRPX(provider, monitor);
fsih.storeFile("converted.elf", 0, false, data.elf_size, data);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Based on https://github.com/Relys/rpl2elf/blob/master/rpl2elf.c
*
* @param monitor
*
* @return
* @throws CancelledException
*/
public static ElfData convertRPX(ByteProvider bProvider, TaskMonitor monitor)
throws ElfException, IOException, DataFormatException, CancelledException {
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 = checkBuffer(buffer, newEnd);
buffer.position((int) 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 = checkBuffer(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.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());
byte[] dataArray = buffer.array();
return new ElfData(dataArray, (int) bProvider.length());
}
private static ByteBuffer checkBuffer(ByteBuffer buffer, long newEnd) {
// This probably the worst way to do this.
if (buffer.remaining() < newEnd) {
ByteBuffer newBuffer = ByteBuffer.allocate((int) (buffer.capacity() + newEnd - buffer.remaining()));
newBuffer.put(buffer.array());
return newBuffer;
}
return buffer;
}
@Override
public void close() throws IOException {
refManager.onClose();
if (provider != null) {
provider.close();
provider = null;
}
fsih.clear();
}
@Override
public String getName() {
return fsFSRL.getContainer().getName();
}
@Override
public FSRLRoot getFSRL() {
return fsFSRL;
}
@Override
public boolean isClosed() {
return provider == null;
}
@Override
public int getFileCount() {
return fsih.getFileCount();
}
@Override
public FileSystemRefManager getRefManager() {
return refManager;
}
@Override
public GFile lookup(String path) throws IOException {
return fsih.lookup(path);
}
@Override
public InputStream getInputStream(GFile file, TaskMonitor monitor) throws IOException, CancelledException {
ElfData metadata = fsih.getMetadata(file);
return (metadata != null) ? new ByteArrayInputStream(metadata.data) : null;
}
@Override
public List<GFile> getListing(GFile directory) throws IOException {
return fsih.getListing(directory);
}
@Override
public String getInfo(GFile file, TaskMonitor monitor) throws IOException {
ElfData metadata = fsih.getMetadata(file);
return (metadata == null) ? null : FSUtilities.infoMapToString(getInfoMap(metadata));
}
private Map<String, String> getInfoMap(ElfData metadata) {
Map<String, String> infos = new HashedMap<>();
infos.put("elf_size", Integer.toString(metadata.elf_size));
infos.put("rpx_size", Integer.toString(metadata.rpx_size));
return infos;
}
public static class RPXFileSystemFactory implements GFileSystemFactoryFull<RPXFileSystem>, GFileSystemProbeFull {
@Override
public RPXFileSystem create(FSRL containerFSRL, FSRLRoot targetFSRL, ByteProvider byteProvider,
File containerFile, FileSystemService fsService, TaskMonitor monitor)
throws IOException, CancelledException {
RPXFileSystem fs = new RPXFileSystem(targetFSRL, byteProvider);
fs.mount(monitor);
return fs;
}
@Override
public boolean probe(FSRL containerFSRL, ByteProvider byteProvider, File containerFile,
FileSystemService fsService, TaskMonitor monitor) throws IOException, CancelledException {
byte[] header = byteProvider.readBytes(0, RPX_MAGIC.length);
return Arrays.equals(header, RPX_MAGIC);
}
}
private static class ElfData {
private byte[] data;
private int elf_size;
private int rpx_size;
public ElfData(byte[] data, int rpx_size) {
this.data = data;
this.elf_size = data.length;
this.rpx_size = rpx_size;
}
}
}