
استغلال ثغرة في نواة Android GKI 6.12 للإصدار CVE-2026-43499، عبر تسلسل خطأ rollback في rt_mutex مع الكتابة فوق المكدس pselect للحصول على صلاحيات الجذر على أجهزة Samsung وPixel.
استغلال لنواة Linux لخط GKI 6.12 على Android (أجهزة Samsung وPixel)
يستهدف CVE-2026-43499: خلل في التراجع remove_waiter() داخل
rt_mutex_start_proxy_lock() يستخدم current بدلاً من waiter::task،
تاركاً pi_blocked_on الخاص بالمنتظر مشيراً إلى rt_mutex_waiter على
مكدس النواة الخاص به (الذي يُفرَّغ لاحقاً). بالاقتران مع الكتابة فوق مكدس
fd_set عبر pselect() ومسير rb_tree المزيف المدفوع بـ sched_setattr من
مستهلك، ينتج عن ذلك بدائية كتابة حتمية في النواة وصلاحيات root كاملة.
يعمل الاستغلال كمكتبة مشتركة LD_PRELOAD (preload.so) ويثبّت خادم su
بصلاحيات root بالإضافة إلى خلفية الشاشة كمخلفات ما بعد الحصول على root.
الحالة: تطوير نشط. هدف m1q (ZF1) هو محور التشغيل الأولي. bootstrap الخاص بـ pipei tmp_page-uname هو المسار النشط حالياً: المسير مُثبت نظيفاً على الجهاز (build #33: مناطق rt_mutex مزروعة لكل طفل) والمسح الكامل لـ 168 مرشحاً قيد التنفيذ. مسار configfs CFI طريق مسدود على m1q (Rust ashmem — لا يوجد فتحة fops قابلة للحقن) ويبقى المسار لأهداف C-ashmem. تختلف الإزاحات لكل هدف حسب الجهاز — تحقق منها مقابل ثنائي النواة الفعلي قبل الوثوق بها.
عندما تعيد FUTEX_CMP_REQUEUE_PI إدراج منتظر وتعيد سلسلة كشف الجمود
-EDEADLK، يتراجع __rt_mutex_start_proxy_lock() عبر remove_waiter()
(rtmutex.c:1535). يقوم التراجع بشكل صحيح بإزالة المنتظر من شجرة الانتظار
لكنه يمسح pi_blocked_on الخاص بـ مستدعي إعادة الإدراج بدلاً من
waiter->task->pi_blocked_on. يبقى pi_blocked_on الخاص بالمنتظر معلقاً
عند rt_mutex_waiter على مكدسه، والذي يُفرَّغ بمجرد انتهاء مهلة futex.
بعد انتهاء المهلة، يُعاد استخدام منطقة مكدس النواة الخاصة بالمنتظر:
ينسخ core_sys_select() مجموعات fd الثلاث إلى مخزن المكدس هذا (مسار المكدس
nfds < 344 على ZF1). تعيد مصفوفة كلمات fd_set المصممة بعناية تشكيل
rt_mutex_waiter مزيف / مهمة مزيفة / rt_mutex مزيف (بجذر rb_tree
مؤشراته مُتحكَّم بها). ثم يستدعي خيط مستهلك sched_setattr_tid(waiter) →
rt_mutex_adjust_pi() → rb_erase_cached، مما ينتج كتابة عنوان عشوائي
بقيمة مُتحكَّم بها.
تتطلب البدائية بأكملها دورة سلسلة PI: المالك يحمل f_pi_target ويحجب
أيضاً على f_pi_chain (الذي يحمله المنتظر)، لذا تصطدم سلسلة المسير بـ
owner → chain → waiter → target → owner وتفشل بـ -EDEADLK. إزالة الدورة
تجعل إعادة الإدراج تعود بالنجاح مع تأثير صفري على النواة.
sched_blocked_reason (m1q الأساسي، مُثبت على الجهاز):
يُقرأ PC العودة المحفوظ لعامل kworker محجوب (stack_trace_save_tsk) من
المخزن الحلقي ويُقارن بإزاحة worker_thread المُضمَّنة في البناء. يعمل
قبل استخدام أي كلمات من مسار boot_id حتى تبقى أهداف كتابة data-alias
(data_addr() = p0 alias + slide_p0_offset) صحيحة عند slide غير صفري.SLIDE_LOGGERS_0_1 في بيانات sysctl الخاصة بـ boot_id عبر alias
للخريطة الخطية؛ القيمة المسرَّبة تعيد بناء stext. مُعلَّق على m1q:
هدف W1 الخاص به (SLIDE_RANDOM_BOOT_ID_DATA_OFF، ذاكرة فيزيائية
منخفضة) له قابلية كتابة تعتمد على الصفحة — يحرّك slide الهدف عبر حدود
الصفحات في كل إقلاع، لذا تفشل ~6/7 من التشغيلات. يُستخدم فقط صراحةً عبر
SLIDE_FORCE_BOOTID (اختبار آلية) مع إعادة توجيه tree_pc/tree_left إلى
الصفحة المرشوشة.mm_struct واستخدم تصادم futex-hash
لتحديد موقع mm_struct على الكومة، مما يسرّب عنوان صفحة كومة النواة
المستخدم كقاعدة رشّ الكائنات المزيفة. يُفصل المنتظرون (لا يُضمّون) عند
التنظيف لتجنب OOM مكدس النواة الذي أسقط البناءات قبل #26.write_iter ثابتة قابلة للحقن وكائن كومة ASHMEM_SET_NAME
هو KVec لا يتوافق تخطيطه مع private_data الخاص بـ
configfs_bin_write_iter. يتحول m1q إلى كتابة kmalloc بدلاً من ذلك:
استعادة كتلة mm المسرَّبة من الرتبة 3 ككائنات pipe_inode_info واستخدام
كتابة pselect W1 (*(tree_left) = tree_pc) للكتابة فوق فتحة tmp_page
الخاصة بمرشح (+0x90) بصفحة UTS namespace (init_uts_ns). كتابة fanout
تزرع اسم علامة عند إزاحة sysname ويُبلّغ uname() عن "CatOS" — كتابة
نواة قابلة للتحقق دون اعتماد على كائن ثابت. تحتفظ أهداف C-ashmem بمسار
configfs.preload.c خادم su المُضمَّن (مُثبَّت عبر tmpfs في
/apex/com.android.virt/bin، بالإضافة إلى متغيرات adbd-namespace
والمتغيرات المحلية) ويبدّل خلفية الشاشة.تعيد كلمات fd_set الخاصة بـ pselect تشكيل rt_mutex_waiter مزيف على مكدس
النواة الخاص بالمنتظر؛ يستدعي sched_setattr_tid(waiter) من خيط مستهلك
المسير عبر rt_mutex_adjust_pi(). خريطة كلمات ZF1 (مُتحقق منها بالتفكيك):
PSELECT_WAITER_WORD_SHIFT = 0، الكلمة 12 = task (@+0x50)، الكلمة 13 =
lock (@+0x58)، الكلمة 14 = wake_state (@+0x60 = 3). مع rt_mutex مزيف
مزروع يكتمل المسير بنظافة: rb_erase W1 *(tree_left)=tree_pc / W2
*(tree_pc&~3+8)=tree_left الخاصة بـ [7] هما كتابتا النواة الوحيدتان؛ [11]
(setprio / dequeue_pi) و[9] wake كلاهما متخطى (المنتظر المزيف المحلي عند
أولوية 100 يبقي عقدة المكدس خارج top-waiter). كل اكتمال دخل المسير منذ
build #19 ينجو على الجهاز؛ الأعطال الحتمية قبل ذلك كانت خطأ في وضع حمولة
8 بايت (SKB_DATA_DELTA)، وليس عطلاً في جسم المسير.
مرحلة اختيارية STAGE3=1 تفرّخ طفل جسر، تبدّل mm->pgd الخاص به إلى
جدول صفحات مزيف مُجهَّز (3 مستويات: PGD→L1→L2، ورقة RX loop + ورقة RW
stack)، وتدع الطفل يشغّل كتلة تجميع سجلات فقط تعدّل cred الخاص به عبر نافذة
مسح فيزيائي 2MB — قراءة/كتابة فيزيائية لكل الذاكرة دون بدائيات configfs/
pipe. موصولة حالياً فقط بهدف m1q ولم تُشغَّل على الجهاز.
41 هدفاً في src/targets/<codename>-<build>/، كل منها يتطلب على الأقل
target.h (إزاحات رموز النواة، KIMAGE_TEXT_BASE، قاعدة الخريطة المباشرة،
إزاحات البنى). أهداف Pixel (comet، tokay، tegu، caiman، komodo،
frankel، mustang، rango، stallion، blazer) تتجاوز المصادر
المشتركة؛ أهداف Samsung (m1q-*) تضيف منطقاً خاصاً بالجهاز.
make list-projects # full list
تختلف تخطيطات بنى النواة حسب الجهاز — يجب التحقق من الإزاحات مقابل ثنائي النواة الفعلي / kallsyms لكل هدف.
المخرجات: build/<PROJECT>/bin/preload.so (مكتبة مشتركة تُحمَّل عبر
LD_PRELOAD).
# Default project
CC=clang make
# Specific device target (default: blazer-CP2A.260605.012)
CC=clang make PROJECT=m1q-BP4A.251205.006
# Without CC=clang: uses NDK if found ($NDK_ROOT / $ANDROID_NDK_HOME /
# $ANDROID_NDK_ROOT), otherwise host clang + Android sysroot
make PROJECT=m1q-BP4A.251205.006
# Show build configuration
make info
# Clean
make clean
يضمّن البناء ثنائي su_daemon بصيغة PIE (src/su_daemon.c، يُبنى إلى
build/embed/su_daemon_aarch64_pie) وassets/wallpaper.webp في
preload.so عبر src/su_blob.S / src/wallpaper_blob.S.
ادفع مخرجات البناء إلى /data/local/tmp، ثم شغّل الاستغلال تحت
LD_PRELOAD. شغّله بدون tee على هدف تشغيل أولي حي — tee يخزّن stdio
الخاص به ويفقد ذيل السجل عند panic النواة (stdout الخاص بالاستغلال غير
مخزّن، لذا إعادة التوجيه مباشرة إلى ملف تحفظ كل سطر):
adb push build/m1q-BP4A.251205.006/bin/preload.so /data/local/tmp/preload.so
adb push build/embed/su_daemon_aarch64_pie /data/local/tmp/su_daemon_aarch64_pie
adb shell "chmod 755 /data/local/tmp/preload.so /data/local/tmp/su_daemon_aarch64_pie"
adb shell "LD_PRELOAD=/data/local/tmp/preload.so \
/data/local/tmp/su_daemon_aarch64_pie \
> /data/local/tmp/output.log 2>&1"
عند النجاح يستمع الخادم على /data/local/tmp/temp_su.sock ويُثبَّت su
تحت /apex/com.android.virt/bin.