Description
Hi, I love what you are doing with this reverse engineering effort, and I found many useful info on how the PSP firmware works, thanks to this repo. However, I am stuck understanding something very basic.
I am reverse engineering the game Breath of Fire 3 for the PSP, with the goal of modifying parts of the code of the BOOT.BIN, which is a relocatable ELF.
In order to do that, my idea was to add a new segment to the program header of BOOT.BIN. I tried multiple attempts, but I always get a 0x80020148 error when running on the PSP. When running instead from the PPSSPP emulator everything works as expected and my new segment is loaded at the desired address, and my code runs as I want (since the ELF is relocatable, I installed a hook on the main entry of the .text section that jumps to the new segment, which takes care of patching .text at runtime).
I was hoping you could help me shed some light on what is going on on real hardware.
- The simplest way I tried to add a segment, without having to modifying all the offsets in the rest of the ELF, is to simply change the offset of the program header which is stored in the elf header, so to move the program header at the bottom of the ELF. There I write the original program header + a new segment entry. I realized this does not work on real hardware probably because the loader verifies the ELF by only loading the first 512 bytes of the file, and thus the new program header is not available. This is at least according to
uofw/src/kd/modulemgr/modulemgr.c
Line 454 in 8d78362
- The second attempt was to use a proper ELF manipulation library: I am using ELFIO with C++, and I add a new segment, together with even a new section pointing to it, and the program header stays at the beginning of the file. Ghidra, PRXTool, readelf, and even PPSSPP are able to open the file, and the game works fine in PPSSPP. You can find the code I am using to add the segment below.
Can you see any obvious mistakes? Or even better, are you aware of a better/proper way to extend a PRX/ELF with a new segment or simply add space for custom code?
Just to clarify, I am making my new segment RWX and I am also changing the first code segment as well as the section .text to RWX as well, since I am changing code at runtime.
#include <elfio/elfio.hpp>
#include <iostream>
#include <cstring>
#include <cstdint>
#define ALIGN 0x40
#define SEGMENT_SIZE 0x800
static ELFIO::Elf32_Addr align(ELFIO::Elf32_Addr value, size_t align){
return (value + (align-1))/align*align;
}
int main() {
ELFIO::elfio reader;
// Load existing 32-bit MIPS ELF file
if (!reader.load("input_mips.elf")) {
std::cerr << "Failed to load ELF file\n";
return 1;
}
// Check it's ELF32 and MIPS
if (reader.get_class() != ELFIO::ELFCLASS32 || reader.get_machine() != ELFIO::EM_MIPS) {
std::cerr << "Not a 32-bit MIPS ELF file\n";
return 1;
}
auto flags = reader.segments[0]->get_flags();
reader.segments[0]->set_flags(flags | ELFIO::PF_W);
auto last_segment = reader.segments[reader.segments.size()-1];
ELFIO::Elf32_Addr new_segment_virtual_addr = align(
last_segment->get_virtual_address() + last_segment->get_memory_size(),ALIGN);
// Add a PT_LOAD segment
ELFIO::segment* new_seg = reader.segments.add();
new_seg->set_type(ELFIO::PT_LOAD);
new_seg->set_flags(ELFIO::PF_R | ELFIO::PF_W | ELFIO::PF_X);
new_seg->set_align(ALIGN);
new_seg->set_virtual_address(new_segment_virtual_addr);
new_seg->set_file_size(SEGMENT_SIZE);
new_seg->set_memory_size(SEGMENT_SIZE);
// Create new section
ELFIO::section* new_sec = reader.sections.add(".mydata");
new_sec->set_type(ELFIO::SHT_PROGBITS);
new_sec->set_flags(ELFIO::SHF_ALLOC | ELFIO::SHF_WRITE | ELFIO::SHF_EXECINSTR);
new_sec->set_addr_align(ALIGN);
new_sec->set_address(new_segment_virtual_addr);
std::vector<char> data(SEGMENT_SIZE);
new_sec->set_data(data.data(), data.size());
// Connect section to segment
new_seg->add_section_index(new_sec->get_index(), new_sec->get_addr_align());
// Save to new file
if (!reader.save("output_mips.elf")) {
std::cerr << "Failed to save ELF\n";
return 1;
}
std::cout << "Segment and section added to MIPS ELF32\n";
return 0;
}