# OOBdump: Relocation Oriented Programming

Missing bounds check in binutils BFD's FR30 relocation handler gives a heap OOB write in objdump -g, escalated to code execution defeating ASLR/PIE.

- Payout: Research
- Severity: high
- Program: binutils
- Write-up by: Jun Rong
- Disclosed: 2026-06-08
- Write-up quality: 10/10
- Original: https://blog.calif.io/p/oobdump-relocation-oriented-programming
- Library page: https://bugbountylibrary.com/r/blog%3Ablog-calif-io-p-oobdump-relocation-oriented-programming
- Tags: area:oss domain:oss impact:rce kind:research platform:research sev:high tech:linux technique:chained technique:race-window technique:source-review vuln:memory-corruption vuln:rce

**Target:** objdump -g / BFD FR30 relocation handler (bfd/elf32-fr30.c)
**Root cause:** fr30_elf_i32_reloc computes a write offset from attacker-controlled reloc_entry->address + 2 and calls bfd_put_32 without validating it against the section buffer size, allowing OOB writes into the heap.
**Steps:**
1. Craft an FR30 relocatable object with a .debug_addr/.debug_info section and a .rela.debug_info relocation section; objdump -g loads and relocates .debug_info before parsing DWARF.
2. Use relocations to write 0xFFFFFFFF into the high dword of a later arelent's address field, wrapping its 64-bit address to enable backward writes.
3. Partial-overwrite abfd->xvec's low 2 bytes to crx_elf32_vec (same 64KB .data.rel.ro page) to flip bfd_put_32 to little-endian.
4. Partial-overwrite a reloc howto pointer to R_386_PC32 (partial_inplace) to convert OOB writes into OOB increments; inflate section size on the heap to bypass i386 range checks.
5. Apply 4 OOB increments to the heap FILE* (iostream): _IO_file_jumps->_IO_wfile_jumps, &_IO_list_all->system, _lock->fp+80, _wide_data->fp-88 so _wide_vtable overlaps _lock.
6. Fill FILE state (write_ptr>write_base, fake wide_data, command in _flags), set abfd->iostream=NULL so bfd_close skips fclose; on exit() glibc flushes _IO_list_all and calls system(fp).
**How found:** Hunting bugs in bug-finding tools; noted BFD relocation handlers are bug-prone across architectures and audited FR30's fr30_elf_i32_reloc, spotting the unchecked reloc_entry->address write offset.
**Exploitation:** Single crafted FR30 object file fed to objdump -g. Relocation OOB writes wrap offsets for backward writes, partial pointer overwrites retarget xvec and howto to little-endian/in-place handlers, then OOB increments corrupt the heap FILE struct for House of Apple 2 FSOP, yielding system() on exit with no leak.
**Escalation:** Heap OOB write -> OOB increment primitive via borrowed R_386_PC32 partial_inplace -> FILE struct corruption -> FSOP House of Apple 2 -> code execution, all without ASLR/PIE leak.
**Insight:** When you have a forward-only OOB write with no leak, look for a later heap object (like the arelent array) you can edit to wrap a 64-bit offset into a backward write, and use 64KB-aligned PIE partial overwrites plus borrowed partial_inplace reloc types to turn writes into in-place pointer increments.

## Proof of concept (copied exactly from the original report)

**Backward write via offset wrap primitive**

```
def write_backward(target, value):
 """Write `value` at a negative offset from data (a backward write)."""
 next_index = len(relocations) + 1
 # sizeof entry is 32 bytes, high bytes of address field is at offset 12
 address_hi = R + next_index * 32 + 12
 relocations.append((address_hi - 2, 0xFFFFFFFF)) 
 relocations.append(((target - 2) & 0xFFFFFFFF, value))
```

**Vulnerable FR30 relocation handler**

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

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

