OOBdump: Relocation Oriented Programming: Arbitrary code execution in objdump -g

matt_d1 pts0 comments

OOBdump: Relocation Oriented Programming - Calif

Calif

SubscribeSign in

OOBdump: Relocation Oriented Programming<br>Arbitrary code execution in objdump -g.<br>Jun 08, 2026

Share

We have a thing for finding bugs in bug finding tools. IDA Pro, Ghidra, Binja Sidekick, or radare2. You name it we hacked it. Our friends were saying we should try objdump. So here we go.

objdump -g should be boring. It reads an object file, prints debug information, and exits. But with the right FR30 object file, it can be persuaded to execute arbitrary code.<br>The bug is a missing bounds check in the FR30 relocation handler. Pretty boring by today's standards. What's cool is how we turned this simple heap OOB into an exploit that defeats ASLR, PIE, and heap hardening mitigations with just a single crafted input.<br>The bug only affected a rare build configuration of objdump. The security policy of binutils, the parent project, explicitly excludes issues of this kind from being treated as security vulnerabilities, and instead requires them to be disclosed publicly. We followed that process, and the issue was fixed promptly.<br>The exploit itself is beautiful. It is rare to see a heap overflow that can be exploited in a true single shot while still defeating ASLR.<br>The forgotten target

FR30 is a Fujitsu embedded RISC core from the late 1990s, part of the proprietary 32-bit FR family. Binutils still ships support for it, but stock host-focused objdump builds usually do not enable that backend. The realistic exposure is custom or multi-target builds: --enable-targets=all, an explicit fr30-*-elf target, SDK toolchains, CI images, and binary-analysis environments that want one tool to recognize everything.<br>Why relocate?

You might be wondering why objdump needs to perform relocations on the input object. Why can't it just read and print the bytes as-is?<br>The FR30 file in the exploit is a relocatable object file, not a finished executable. The C compiler emits one object file (.o) for each source file, and the linker later combines them into an executable. Since the compiler doesn't know where each section will land in the final program, it leaves placeholder values and records relocations that mark which spots to patch. Debug sections work the same way, and those are what objdump -g reads.<br>In this example, the .debug_addr section has a header followed by two zero placeholder entries for code addresses:<br>.debug_addr<br>offset 0x00: header<br>offset 0x08: address slot 0 = 0x0<br>offset 0x10: address slot 1 = 0x0

That changes when the corresponding relocation section (.rela.debug_addr) is processed:<br>.rela.debug_addr:<br>offset 0x08 -> .text<br>offset 0x10 -> .text + 0x10

In the normal build process, a linker looks at the relocation section and applies the patches to the binary it produces.<br>But objdump -g runs on the original object file, with no linker around to do the patching. That job falls to binutils' Binary File Descriptor (BFD) library, which is where our bug lives.<br>The relocation above is simple, but real relocation formats are far more varied. Each architecture defines its own relocation types and how they're applied, which makes this a particularly bug-prone area for a multi-target library like BFD.<br>The missing check

Anthropic discovered this bug and shared it with us.<br>FR30's R_FR30_48 relocation handler is fr30_elf_i32_reloc in bfd/elf32-fr30.c:<br>typedef uint64_t bfd_vma;

static bfd_reloc_status_type<br>fr30_elf_i32_reloc (bfd *abfd, arelent *reloc_entry,<br>asymbol *symbol,<br>void *data, asection *input_section, ...)<br>/* first three terms = virtual (mapped) address of the symbol (eg .text) */<br>bfd_vma relocation = symbol->value<br>+ symbol->section->output_section->vma<br>+ symbol->section->output_offset<br>// addend, or offset from the base symbol<br>+ reloc_entry->addend;

/* bfd_put_32 (bfd *abfd, bfd_vma value_to_write, void *destination_pointer) */<br>bfd_put_32 (abfd, relocation, (char *) data + reloc_entry->address + 2);

return bfd_reloc_ok;

The function first calculates relocation, the value to be written. The attacker controls the symbol and addend terms, and the section state is predictable here, so we control what gets written.<br>It then calls bfd_put_32 to apply the patch. The write lands in data, the heap buffer that holds the target section's contents. In our exploit, that section is .debug_info.<br>Its offset comes straight from reloc_entry->address, plus two bytes to skip the 16-bit instruction prefix. Nothing checks that offset against the buffer size. Since we control both the value and the offset, an out-of-bounds write is trivial.<br>The handler runs once for every relocation entry, and we can add as many entries as we like, so one file gives us as many writes as we want.<br>We use .debug_info because objdump's DWARF reader loads and relocates it before parsing the DWARF inside. The section can be all zeros and every write still fires.<br>The heap layout

While the OOB write is powerful, two obstacles still stand in our way:<br>We can only modify memory at a higher...

relocation objdump section file offset fr30

Related Articles