
استغلال ثغرة في نواة 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
لتحديد موقع على الكومة، مما يسرّب عنوان صفحة كومة النواة
المستخدم كقاعدة رشّ الكائنات المزيفة. يُفصل المنتظرون (لا يُضمّون) عند
التنظيف لتجنب OOM مكدس النواة الذي أسقط البناءات قبل #26.تعيد كلمات 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.
يدفع tools/run_pipei.sh الثنائي، ويتحقق من sha256 على الجهاز (ملف
preload.so قديم ينتج أحكاماً غير قابلة للقراءة بصمت — شغّل السلم مرة
بناءً < #22 يختلف تنسيق سجله)، ثم يشغّل المسح الكامل لـ 168 مرشحاً مقسّماً
3x56 بحيث يفقد panic قطعة واحدة على الأكثر (~10 دقائق). يتدفق stdout
الخاص بالاستغلال مباشرة عبر adb (لا إعادة توجيه على الجهاز — tee على
جانب الجهاز يفقد الذيل عند panic)؛ تُحفظ نسخة على جانب المضيف من كل قطعة
في /tmp/m1q_pipei_chunk{1..3}_stream.log:
sh tools/run_pipei.sh
PASS = أي قطعة تُظهر tmp_page uname changed + after uname='CatOS.
يجب ضبط PIPEI_CHILD_REGIONS على الأقل بحجم القطعة وإلا تبقى مناطق
rt_mutex لكل طفل غير مزروعة ويموت المسح عند المرشح 0. تتطلب التغطية
الكاملة أيضاً TMP_UNAME_PIPEI_SPLIT_ORDER_PAGES=1 — بدونها تُمسح صفحة
واحدة فقط من كتلة mm المحررة ذات 8 صفحات. run_pipei_slim.sh هو المتغير
التشخيصي لمرشح واحد؛ run_m1q_ladder.sh هو بطارية المسير/العزل (B16-2
oracle، آلية slide، خطوات المسح).
exploit/
Makefile
assets/ # wallpaper.webp (embedded post-root artifact)
src/
main.c # run_exploit() entry point (shared)
preload.c # LD_PRELOAD constructor, su/wallpaper install
util.c # addr translation, page prep, configfs helpers
pipe.c # pipe-based physical read/write
root.c # cred patching / task walk
slide.c # KASLR slide (tracefs + boot_id routes)
fops.c # pselect corruption + fops manipulation
faketables.c # stage-3 fake page-table builders (3-level)
stage3.c # pgd-swap bridge child (no-op unless configured)
stage3_loop.S, stage3_poll.S # bridge blob + enter trampoline
su_daemon.c # root daemon (compiled as separate PIE)
su_blob.S, wallpaper_blob.S # embedded binaries
offset.h # pulls per-target target.h via -DTARGET_CONFIG_H
kernelsnitch/ # mm_struct futex-hash leak library
targets/<codename>-<build>/ # per-device target.h + optional overrides
poc/poc.c # standalone POC (separate from the exploit)
تتجاوز الأهداف المصادر المشتركة عبر آلية pick_src في Makefile: إذا وُجد
src/targets/<PROJECT>/<file>.c يُستخدم بدلاً من src/<file>.c المشترك؛
وإلا تُستخدم النسخة المشتركة.
-EDEADLK (errno 35). الدورة المفقودة تظهر كـ success=1 مع
تأثير صفري على النواة.PSELECT_WAITER_WORD_SHIFT = 0 (أعماق مكدس ZF1
مُتحقق منها من ELF). لا تزال الأهداف الأخرى افتراضياً على 1 — تحقق لكل
جهاز قبل الوثوق بها.p0_data_alias() يستخدم DIRECT_MAP_BASE (الخريطة الخطية)، والذي
يختلف عن P0_PAGE_OFFSET على ZF1. يجب أن تستخدم data aliases للكائنات
المُشار إليها في النواة الخريطة الخطية.pr_error() قاتل (exit(-1))، بخلاف ماكرو التحذير/المعلومات.SLIDE_LOGGERS_0_1، وليس SLIDE_NFULNL_LOGGER؛
الكلمة هي alias للخريطة الخطية غير عشوائي، لذا مسار boot_id مستقل عن
slide (استخدمه فقط عند slide=0 — شغّل تسريب tracefs أولاً).هذا كود استغلال لأجهزة Android ومخصص لأبحاث الأمن، واختبار الثغرات المصرح به، وأعمال الدفاع على أجهزة تملكها أو لديك إذن صريح باختبارها. إساءة الاستخدام على أجهزة لا تملكها غير قانونية. المؤلف غير مسؤول عن الأجهزة المعطوبة، أو النوى المعطوبة، أو الضمانات الملغاة.
mm_structwrite_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
والمتغيرات المحلية) ويبدّل خلفية الشاشة.| Variable | Default | Purpose |
|---|
PSELECT_ROUTE_SHIFT | compile-time | A/B fd_set word shift for the main route (m1q) |
SLIDE_SHIFT | compile-time | A/B fd_set word shift for the slide route |
PSELECT_ROUTE_DELAY_USEC | 50000 | Consumer delay before sched_setattr (must be > 0) |
SLIDE_CONSUME_USEC, SLIDE_ENTER_DELAY_USEC, SLIDE_CONSUMER_CORE | — | Slide route timing/pinning |
SKIP_SLIDE | 0 | Use direct-map fallback (slide = 0) |
SLIDE_ONLY | 0 | Run only the KASLR slide and exit |
SLIDE_P0_OFFSET, SLIDE_BOOTID_OFF | — | Override slide p0-alias / boot_id offsets |
SLIDE_FORCE_BOOTID | 0 | Force the boot_id pselect route after the tracefs slide (mechanism test) |
KSNITCH_COLLISIONS | 4 | KernelSnitch collision count |
STAGE3 | 0 | Enable the pgd-swap bridge child phase (m1q) |
STAGE3_DRYRUN | 0 | Stage/verify tables then abort before the swap |
PAGE_RECLAIM_SENDS, PSELECT_SIMPLE_LAYOUT | — | Spray/fd-set layout tweaks |
TMP_PAGE_UNAME | 0 | Run the m1q pipei tmp_page-uname bootstrap |
TMP_UNAME_PIPEI_SWEEP, TMP_UNAME_PIPEI_ALIGN_ORDER, TMP_UNAME_PIPEI_SPLIT_ORDER_PAGES | — | Sweep mode; split_order sweeps all 8 pages of the freed mm block |
TMP_UNAME_PIPEI_SLOT, TMP_UNAME_PIPEI_SLOT_CANDIDATES | — | Sweep range (chunk start / count) |
PIPEI_CHILD_REGIONS | 0 | Per-child seeded rt_mutex regions; MUST be >= sweep chunk or unseeded regions panic at candidate 0 |
PIPEI_RECLAIM_COUNT, PIPEI_DRAIN_COUNT | — | Reclaim/drain pipe pairs; raise on a reclaim miss |
PIPEI_SPONGE_MB | 3072 | Order-0 flood sponge held across the reclaim (build #29) |
PSELECT_W0_PRIO_OVERRIDE | 130 | Top-waiter prio; inert with seeded regions (legacy stale-tree guard, A/B only) |
PSELECT_SKB_DELTA_OVERRIDE | 0 | A/B payload intra-page offset (only if a device differs from -0xe78) |
TMP_UNAME_NAME, TMP_UNAME_HOLD_SEC | "CatOS", 5 | Marker name / hold time after success |
SKB_DATA_DELTA هو -0xe78SKB_DATA_DELTAPSELECT_SKB_DELTA_OVERRIDEASHMEM_SET_NAME لا يتوافق مع
configfs_bin_write_iter. استخدم bootstrap الخاص بـ pipei tmp_page.
حراس PSELECT_WRITE_RIGHT_SPRAYED / PSELECT_PI_TOP_TASK_SELF في
target.h الخاص بـ m1q تعيد توجيه كتابات [11] للمسير بعيداً عن fops
الثابتة للقراءة فقط.payload+0x6000 + idx*0x40) داخل الصفحة
المستعادة. مسحات fake_lock المشتركة أُصيبت بـ panic عند المرشح ~9-12
(عقد مكدس نواة قديمة أُعيد تدويرها)، ومناطق build #32 الفارغة أُصيبت بـ
panic عند المرشح 0 ([9] owner==NULL wake على fake_task المملوء بـ 0x41).
يجب ضبط PIPEI_CHILD_REGIONS وإلا تبقى المناطق غير مزروعة.