
منفذ C عبر المنصات لـ Copy Fail Linux LPE (CVE-2026-31431). تم الإفصاح عنه في 2026-04-29 بواسطة Theori / Xint.
English (en) ∙ 日本語 (ja) ∙ 简体中文 (zh-cn) ∙ 한국어 (ko) ∙ Русский (ru)
إعادة تنفيذ بلغة C عبر الأنظمة لثغرة Copy Fail Linux LPE (CVE-2026-31431)، التي تم الكشف عنها في 2026-04-29 بواسطة Theori / Xint. انظر إلى الشرح الرسمي على copy.fail للوصف الكامل للثغرة، والجدول الزمني، وعملية اكتشاف Theori.
الإثبات العملي المنشور هو سكريبت بايثون بحجم 732 بايت. هذه النسخة بلغة C تُظهر أن نفس الاستغلال يمكن التعبير عنه بلغة C محمولة يمكن تجميعها لأي بنية يدعمها nolibc، دون وجود كتل سداسية عشرية خاصة بكل بنية أو تجميع مباشر في المصدر الخاص بالمشروع.
مؤلف هذه النسخة: Tony Gies [email protected]. الاكتشاف والإفصاح الأصلي: Theori / Xint.
copy-fail-c/
├── exploit.c the dropper (binary-mutation variant)
├── exploit-passwd.c the dropper (/etc/passwd UID-flip variant)
├── vulnerable.c non-destructive vulnerability checker
├── payload.c the body that gets dropped (setgid+setuid+execve sh)
├── utils.c, utils.h shared AF_ALG/splice page-cache mutation primitive
├── Makefile build orchestration
├── nolibc/ vendored from torvalds/linux tools/include/nolibc
└── README.md this file
بعد make:
├── payload tiny static ELF, embedded into the dropper as bytes
├── payload.o payload wrapped as a relocatable .o by `ld -r -b binary`
├── exploit dropper, binary-mutation variant
├── exploit-passwd dropper, /etc/passwd UID-flip variant
└── vulnerable non-destructive vulnerability checker
exploit.c يفتح الهدف الثنائي للقراءة فقط، ثم لكل نافذة بحجم 4 بايت من الحمولة المضمنة يقوم بتنفيذ فك تشفير AEAD زائف واحد عبر AF_ALG يتم تغذية مدخلات النص المشفر له عن طريق splice() من صفحات مخبأ الصفحات للهدف. التحسين الموضعي لقالب authencesn يعالج صفحات المصدر المنقولة كمدخلات النص المشفر ووجهة النص العادي في نفس الوقت، لذا فإن فك التشفير (الفاشل) قد قام بالفعل بالكتابة فوق صفحة مخبأ الصفحات بحلول الوقت الذي يرفض فيه التحقق من المصادقة الطلب. بعد 4 * N تكرار، تم استبدال الصورة المخبأة للهدف بايت ببايت بالحمولة. يؤدي تنفيذ execve() على الهدف إلى تحميل الصفحات المحورة؛ لا يزال inode على القرص setuid root، لذا يمنح النواة صلاحيات root ويشغل الحمولة.
payload.c عبارة عن C محمولة عادية: setgid(0); setuid(0); execve("/bin/sh", ...). يوفر nolibc _start، وآلية استدعاء النظام، والتلاعب بالسجلات الخاص بكل بنية.
نموذج ثانٍ، exploit-passwd.c، يحوّر أربعة بايتات من مخبأ صفحات /etc/passwd بدلاً من صورة الهدف الثنائي setuid. لا يحتاج إلى حمولة مضمنة ويعمل على الأنظمة التي يكون فيها مسار تحوير الثنائي محظورًا، ولكن سطح السحب الخاص به أضيق بكثير.
vulnerable.c ليس استغلالًا. يقوم بإنشاء ملف محلي testfile يحتوي على السلسلة init، ثم يشغل نفس الأولية patch_chunk() ضد مخبأ صفحات ذلك الملف الخاص به لاستبدال البايتات بـ vulnerable. إذا تطابقت المحتويات المقروءة، فإن النواة قيد التشغيل في نطاق CVE-2026-31431. لا يتم تعديل inode على القرص أبدًا؛ تتم إزالة testfile عند الخروج؛ يتبخر تحوير مخبأ الصفحات معه. يعمل بدون صلاحيات. يخرج برمز 100 إذا كان ضعيفًا، و0 إذا تم تشغيل الأولية لكن التحوير لم ينجح، و2 إذا كانت عائلة مقبس AF_ALG أو قالب authencesn غير متوفرة بحيث لا يمكن تحديد حالة التصحيح، و1 لأخطاء تشغيلية أخرى.
الافتراضي (محلي للبنية المضيفة):
make
ترجمة متقاطعة لـ aarch64 (أو أي بنية Linux أخرى تم تثبيت سلسلة أدوات تقاطع لها):
make CC=aarch64-linux-gnu-gcc LD=aarch64-linux-gnu-ld
البنى المدعومة بواسطة nolibc المضمنة (وفقًا للمصدر الأعلى): x86_64، i386، arm، aarch64، riscv32/64، mips، ppc، s390x، loongarch، m68k، sh، sparc. يقوم nolibc بالتوجيه بناءً على ماكرو بنية المترجم، لذا فإن اختيار CC/LD المناسب كافٍ.
المتطلبات للبناء:
cc، gcc، أو أي متغير تقاطع)ld -r -b binary (كل من binutils ld و lld يدعمانه)linux/if_alg.h و <asm/unistd.h> (Debian/Ubuntu: linux-libc-dev؛ المتغيرات المتقاطعة: عادة ما يتم جلبها بواسطة حزمة سلسلة الأدوات المتقاطعة)مجموعات الرؤوس الأقدم من Linux 5.6 تسبق __kernel_old_time_t و struct __kernel_old_timespec، والتي يستخدمها nolibc المضمن. يوفر compat.h (المضمن قسرًا في بناء الحمولة) هذه الأنواع عند غيابها، لذا فإن linux-libc-dev الأقدم لا يزال يبني. إنه لا يفعل شيئًا على الرؤوس 5.6+.
لا توجد تبعيات مكتبة خارجية. يتم بناء الحمولة بشكل مستقل ضد nolibc؛ يرتبط القاذف بمكتبة C المضيفة فقط من أجل fprintf و perror.
تحمل بعض ميزات سلسلة الأدوات الصغيرة معظم الثقل في الحفاظ على المصدر محمولاً والحمولة صغيرة.
nolibc/ هو بديل النواة الصغير لمكتبة C المعتمد على الرؤوس فقط، والمضمن من torvalds/linux tools/include/nolibc/. يوفر _start، وماكرو syscall() محمول، وواجهات استدعاء نظام مضمنة، مع ترميز اصطلاحات السجلات الخاصة بكل بنية في nolibc/arch-*.h. بناء الحمولة باستخدام -nostdlib -static -ffreestanding -Inolibc ينتج ELF ثابت صغير يستدعي النواة مباشرة دون جر بدء تشغيل glibc، أو تهيئة TLS، أو أنابيب حماية المكدس. النتيجة بعد التعبئة وإزالة أقسام الرأس (كلاهما أدناه): حوالي 720 بايت على x86_64، وحوالي 1.2 كيلوبايت على aarch64، مقابل حوالي 17 كيلوبايت لنفس payload.c المرتبط بـ musl-static أو حوالي 700 كيلوبايت مقابل glibc-static.
ld -r -b binary للتضمينيقوم ملف Makefile بتحويل الحمولة المبنية payload ELF إلى payload.o عبر ld -r -b binary -o payload.o payload. يصدر الرابط بايتات الإدخال حرفيًا كقسم بيانات لملف كائن قابل لإعادة الترحيل ويصنع ثلاثة رموز من اسم ملف الإدخال:
_binary_payload_start عنوان أول بايت من الحمولة
_binary_payload_end عنوان بعد آخر بايت من الحمولة
_binary_payload_size رمز مطلق قيمته الحجم بالبايتات
يعلن exploit.c عن الأولين كـ extern const unsigned char[] ويحسب الحجم كـ _binary_payload_end - _binary_payload_start.
-Wl,-N بالإضافة إلى max-page-size الضيقيتم ربط الحمولة بشكل ثابت مع -Wl,-N -Wl,-z,max-page-size=0x10، مما يدمج .text/.rodata/.data في مقطع LOAD واحد بمحاذاة ملف 16 بايت بدلاً من المحاذاة الافتراضية 4 كيلوبايت لكل مقطع. ينتج عن هذا تحذير "RWX permissions" من ld، وهو إعلامي فقط - حماية ذاكرة الحمولة الزمنية لا تهم برنامجها ذو الغرض الوحيد. بدون هذا العلم، يرتبط نفس الكود بحوالي 13 كيلوبايت على x86_64 (معظمها حشو صفري بين المقاطع)؛ معه، حوالي 1.3 كيلوبايت قبل إزالة رأس القسم أدناه.
بعد الربط، يقوم objcopy --strip-section-headers بإزالة جدول رأس قسم الحمولة و .shstrtab. يقوم محمل ELF للنواة بتعيين البرنامج من رؤوس البرنامج فقط، لذا فإن هذه البايتات لا تُحمّل أبدًا في وقت التشغيل، ولأن payload مضمن حرفيًا فإنها تضخم أيضًا عملية الإسقاط وعدد تكرارات patch_chunk. إزالتها تقلل حمولة x86_64 من حوالي 1.3 كيلوبايت إلى 720 بايت (من 322 تكرارًا بأربعة بايتات إلى 180). علمان في وقت الربط يحلقان الباقي: -Wl,--build-id=none يسقط ملاحظة build-id، و -fcf-protection=none يسقط ملاحظة x86 CET حيث يدعم المترجم ذلك.
يحتاج الشريط إلى binutils >= 2.40. تمرر البنيات المتقاطعة objcopy الهدف عبر OBJCOPY= (مثال: OBJCOPY=aarch64-linux-gnu-objcopy)؛ عندما لا يستطيع objcopy القيام بذلك، يطبع البناء ملاحظة ويحتفظ بحمولة صالحة وأكبر.
يحتوي هذا المستودع على نموذجين للاستغلال يشتركان في أولية تحوير مخبأ الصفحات AF_ALG/splice لكنهما يسحبان إلى تنفيذ root بشكل مختلف. ملفات الاعتمادية الخاصة بهما غير متكافئة، وهذا الاختلاف مهم عند التفكير في نماذج التهديد الواقعية.
exploit)يحوّر مخبأ صفحات هدف ثنائي setuid ببايتات الحمولة المضمنة، ثم ينفذ الثنائي. تمنح النواة صلاحيات root من بت setuid للثنائي غير المتغير على القرص، وتحمّل الصورة المعدلة في الذاكرة، وتشغّل الحمولة.
يعمل حيثما يمكن للمهاجم open(target, O_RDONLY) لأي ثنائي setuid root على النظام. يتم إحباطه إلى حد كبير بواسطة البيئات التي تضع ثنائيات setuid خلف أدلة مقيدة القراءة وبواسطة تصاميم أنظمة خالية من setuid.
exploit-passwd)يحوّر أربعة بايتات من مخبأ صفحات /etc/passwd لتعيين حقل UID للمستخدم الجاري إلى "0000". /etc/passwd قابل للقراءة عالميًا على كل نظام Linux قياسي، لذا فإن التحوير عالمي. ترجمته إلى تنفيذ root تعتمد على قيام عملية من جانب root بحل المستخدم عبر getpwnam/getpwuid والتصرف بناءً على uid المُحل دون تحقق متقاطع. توجد العديد من هذه المستهلكات؛ العديد منها يتحقق بشكل دفاعي مقابل رؤية النواة لـ uid المتصل أو مقابل ملكية الملف على القرص، مما يكسر السحب.
suيقوم exploit-passwd بتنفيذ su <user> بعد التحوير، كأبسط سحب ممكن. يعمل ذلك ضد su من util-linux لكنه يفشل ضد su من shadow-utils مع "Cannot determine your user name." لا يزال تحوير مخبأ الصفحات قائماً في تلك النقطة، والتحول إلى أي سحب آخر (مثل استخدام خفي يحل المستخدمين عبر getpwnam دون تحقق متقاطع) ممكن في تلك المرحلة. قم بتشغيل echo 3 > /proc/sys/vm/drop_caches كـ root لمسح مخبأ الصفحات التالف بعد الانتهاء من الاختبار.
الأرضية: torvalds/linux 72548b093ee3 أغسطس 2017, v4.14
(إعادة صياغة iov_iter لـ AF_ALG التي
قدمت أولية كتابة صفحات الملف عبر splice
في قائمة scatter الخاص بـ AEAD)
السقف: torvalds/linux a664bf3d603d أبريل 2026, mainline
(يعكس تحسين الموضع 2017 algif_aead;
يفصل قوائم scatter المصدر والوجهة بحيث
لا يمكن لصفحات مخبأ الصفحات أن تكون
وجهة تشفير قابلة للكتابة بعد الآن)
بينهما: كل نواة توزيع رئيسية لم ترجع الإصلاح. تم تأكيد أن Ubuntu، RHEL، SUSE، Amazon Linux، و Debian جميعها ضعيفة في صورها السحابية الافتراضية في وقت الإفصاح. بدأت الإصلاحات على مستوى التوزيع بالظهور حوالي 2026-04-29 بالتزامن مع الإفصاح العام. للتحقق مما إذا كانت النواة الهدف في النطاق، تحقق مما إذا كان a664bf3d603d (أو الإصلاح الخلفي الخاص بالتوزيعة) موجودًا في سجل git للنواة أو سجل التغييرات للتوزيعة.
للمراجعة الأمنية المدفوعة، أو أعمال التخصيص، أو الاستشارات الأمنية الخاصة، اتصل بالمؤلف من خلال شركته الاستشارية، Crash United, LLC.
الاتصال: [email protected] · https://crashunited.com
GitHub: @tgies · X/Twitter: @me_irl
الاكتشاف والإفصاح الأصلي لـ CVE-2026-31431: Theori / Xint. الشرح العام: https://copy.fail/.
هذه النسخة بلغة C: Tony Gies [email protected]
nolibc/: مضمنة من شجرة نواة Linux، مرخصة بشكل مزدوج
LGPL-2.1-or-later OR MIT (انظر nolibc/nolibc.h ورؤوس SPDX للملفات الفردية).
يتم إصدار مصادر القاذف والحمولة في هذا المستودع تحت نفس شروط الترخيص المزدوج LGPL-2.1-or-later OR MIT مثل شجرة nolibc التي تعتمد عليها، للحفاظ على توافق الترخيص بسهولة لأي شخص يقوم بتضمين هذا الدليل بأكمله في عمله الخاص.
يتم نشر الاستغلال والحمولة لأغراض البحث الأمني والكشف الدفاعي. استخدامها ضد أنظمة لا تملكها أو ليس لديك إذن صريح باختبارها هو مشكلتك، وليس مشكلة المؤلف.
| السحب | إعداد ما قبل root مطلوب | ملاحظات |
|---|
| بدء جلسة WSL2 | لا | setuid(getpwnam(default_user)->pw_uid) الخاص بجلسات WSL لا يقوم بأي تحقق. يعمل بشكل نظيف. |
su من util-linux | لا | معالجة مباحة لهوية المتصل. |
su من shadow-utils | نعم | فحص هوية المتصل getpwuid(getuid()) يفشل لأن التحوير يزيل UID الحقيقي. |
sshd (الوضع الافتراضي StrictModes yes) | نعم (تعطيل StrictModes) | يتطلب StrictModes أن يكون الدليل الرئيسي مملوكًا لـ root أو pw->pw_uid. التحوير يجعل pw_uid=0؛ المالك على القرص يبقى بالـ UID الأصلي؛ عدم التطابق يرفض المصادقة. |
| تسليم البريد المحلي لـ MTA (postfix، exim، إلخ) | متغير | يعتمد على التحقق من صلاحيات المنزل الخاص بـ MDA. اختبر لكل MTA. |