gimli-rs / object

A unified interface for reading and writing object file formats
https://docs.rs/object/
Apache License 2.0
673 stars 156 forks source link

Adding a new section & segment to .ELF without disrupting file #719

Open NotNite opened 3 months ago

NotNite commented 3 months ago

Hi, I was wondering if it's possible to add a custom section/segment to an ELF file. For context, I'm currently working on a tool to patch PlayStation 3 ELFs to allow custom code. I need to add a bit of shellcode into the executable, so I wanted to make a custom section to store my code in and then modify the entrypoint. My code to modify the executable is a little complex, so I made this simple example:

fn main() {
    let input = std::fs::read("hbtest.elf").unwrap();
    let mut builder = object::build::elf::Builder::read(input.as_slice()).unwrap();

    // 32 NOPs (0x60000000 in PowerPC)
    let mut custom_data: Vec<u8> = Vec::new();
    for _ in 0..32 {
        custom_data.push(0x60);
        custom_data.push(0x00);
        custom_data.push(0x00);
        custom_data.push(0x00);
    }

    let section_id = {
        let section = builder.sections.add();
        section.name = ".sprxpatcher".into();
        section.sh_type = object::elf::SHT_PROGBITS;
        section.sh_flags = (object::elf::SHF_ALLOC | object::elf::SHF_EXECINSTR) as u64;
        section.sh_addralign = 16;
        section.sh_size = custom_data.len() as u64;
        section.data = object::build::elf::SectionData::Data(custom_data.into());
        section.id()
    };

    let section_addr = {
        let segment = builder
            .segments
            .add_load_segment(object::elf::PF_R | object::elf::PF_X, builder.load_align);
        let section = builder.sections.get_mut(section_id);
        segment.append_section(section);
        section.sh_addr
    };

    println!("Section address: 0x{:x}", section_addr);

    let output = std::fs::File::create("hbtest.modified.elf").unwrap();
    let mut buffer = object::write::StreamingBuffer::new(output);
    builder.write(&mut buffer).unwrap();
}

However, when running this, it panics:

Section address: 0x267b10
thread 'main' panicked at src/main.rs:38:32:
called `Result::unwrap()` on an `Err` value: Error("Unsupported sh_offset value 0x200 for section '.init', expected at least 0x238")
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
error: process didn't exit successfully: `target\debug\sprxpatcher.exe` (exit code: 101)

I assume this is because the addition of the new section/segment into the program header table has shifted the file offsets by 0x38 (the size of the new entry). Is it possible to repair these offsets such that the modified ELF writes successfully? I tried just shifting every sh_offset by 0x38, but that didn't seem to work. I suspect there is more work to be done here (or it's just flat out impossible).

philipc commented 3 months ago

There's code in the object-rewrite crate to do this, but there's no public API for it. It's possible we could move this code to object::write::elf, but I'm uncertain what a public API for this would look like. The main issue is that it relies on heuristics, so I'd prefer if a public API allowed those heuristics to be changed.

Another option would be to extend the object-rewrite API to allow adding a section.

NotNite commented 3 months ago

I tried using object-rewrite, but it seems to not want to move to adjust for the new program header entry:

[2024-08-11T01:09:20Z INFO  object_rewrite::elf] Immovable program headers (end address 10238) overlaps immovable .init (start address 10200)

Commenting out the block addition for the program headers (just for fun, probably would break things anyways) leads to the same error as before:

[2024-08-11T01:08:53Z INFO  object_rewrite::elf] Moving 1 sections, adding 1 PT_LOAD segments, splitting 0 segments
[2024-08-11T01:08:53Z INFO  object_rewrite::elf] Moved .sprxpatcher to offset 247b10, addr 277b10
[2024-08-11T01:08:53Z INFO  object_rewrite::elf] Added PT_LOAD segment with p_flags 5, offset 247b10, addr 277b10, size 8e
Error: Unsupported sh_offset value 0x200 for section '.init', expected at least 0x270

I'm unsure if resizing the program headers is even something that's possible... 🤔

philipc commented 3 months ago

Ah okay, in my tests so far the section following the program headers has been safe to move.

I'm not sure, but it's likely that the .init section isn't safe to move without patching any relative addressing instructions within it. That's not something this crate can do, since it requires disassembling/assembling instructions.

The alternative is to try moving the elf header and program headers down a page to make room. I think this is theoretically possible, but not something I've tried.

s5bug commented 3 months ago

Could you not keep the ELF header in the same spot and move the program header table to the end of the file? Program header entries if I recall don't use any sort of relative positioning / can be placed anywhere in the ELF.

philipc commented 3 months ago

That is a possibility. There was a linux bug that meant it didn't support program headers that aren't in the first PT_LOAD segment, but that seems to be fixed: https://github.com/torvalds/linux/commit/0da1d5002745cdc721bc018b582a8a9704d56c42

object::build::elf::Builder will need support for moving it too (currently a TODO).