
بحث LPE لنواة Android حول CVE-2026-98260 (استخدام بعد التحرير في posix-cpu-timers): أداة PoC للاستغلال، وسلاسل أوامر shell بصلاحيات uid-0 عبر بيانات اعتماد مزيفة وحقيقية، بالإضافة إلى تطبيق تمهيدي لـ Shizuku/KernelSU.
English | 简体中文
بحث تصعيد امتيازات محلي على Android لثغرة استخدام-بعد-التحرير في مسار خروج posix-cpu-timers. يتضمن المستودع PoC للتشغيل فقط، واستغلالاً للتحقق من بيانات اعتماد مزيفة، ومرحلة صدفة uid-0 ببيانات اعتماد حقيقية، وتطبيق KernelSU قائم على Shizuku، وفحصاً مسبقاً للثغرة.
الفرع
magicos-11.0.0.109-6.12.58: هذا الفرع ثنائي الملف التعريفي. يختار ملف APK والملفات الثنائية الأصلية ملفاً تعريفياً منuname -rالدقيق وقت التشغيل: MagicOS 10.0.0 / 6.12.38 هو الملف التعريفي المُتحقَّق منه، بينما MagicOS 11.0.0.109 / 6.12.58 مُكيَّف بشكل ساكن ولا يزال التحقق من تشغيله على الجهاز معلّقاً. يتضمن ملف APK كلا جدوليko-symsالبالغين 226 رمزاً ويختار الجدول المطابق.
| العنصر | القيمة |
|---|---|
| CVE | CVE-2026-98260 |
| الفئة | LPE (استخدام-بعد-التحرير، محو شجرة rb في timerqueue) |
| الهدف | Honor MagicPad3 Pro 13.3 YLP-W00، MagicOS 10.0.0 / 11.0.0.109 (Android 16)، الفرع magicos-11.0.0.109-6.12.58 |
| النواة | 6.12.38-android16-5-gfde7767f6ef6-abogki481467632-4k (مُتحقَّق منه)6.12.58-android16-6-g3a7ac1674240-abogki541502914-4k (مُكيَّف) |
| حالة الملف التعريفي | ثنائي الملف التعريفي؛ 6.12.38 مُتحقَّق منه، التحقق من تشغيل 6.12.58 معلّق |
| SELinux | Enforcing |
| التصحيحات | غائبة في البنية المُختبَرة |
| التخفيفات | KASLR مفعّل، SMEP/SMAP/PAN مفعّلة، KASAN مُجمَّع لكنه معطّل |
| محمّل الإقلاع | مقفل وغير قابل للفتح؛ لا كتابة على الأقسام |
| المكوّن | الحالة | النتيجة |
|---|---|---|
src/poc.c | مُتحقَّق منه على 6.12.38 | مشغّل مدمّر؛ يُسبب reap حالة panic في cleanup_timerqueue |
src/exp.c | مُتحقَّق منه على 6.12.38 | بيانات اعتماد مزيفة؛ getuid()==0 && geteuid()==0؛ لا يمكنه تنفيذ exec |
src/uid0.c | مُتحقَّق منه على 6.12.38 | بيانات اعتماد حقيقية؛ صدفة uid-0 في u:r:shell:s0 |
app/ | مُتحقَّق منه على 6.12.38 | تمهيد Shizuku + KernelSU؛ uid 0 في u:r:ksu:s0 |
src/vuln_precheck.h | مُضاف | بوابة الملف التعريفي/البيئة؛ --check / --force |
| الملف التعريفي 6.12.58 | مُكيَّف | أُعيد اشتقاق المواقع وselinux_state وجدول ko-syms البالغ 226 رمزاً؛ التشغيل على الجهاز معلّق |
| ملف APK ثنائي الملف التعريفي | مُختبَر على 6.12.38 | اختيار الملف التعريفي وko-syms عبر uname -r؛ نجح مسار KSU الحيّ على 6.12.38 |
الإصدار الحالي: v1.4.3-app.
سُحب الإصدار المسبق v1.4.2-app في 2026-10-30؛ ويُحتفظ بوسمه
للأرشيف فقط. راجع docs/release-notes.md لسجل
الإصدار والثغرات المعروفة.
src/poc.c trigger only; the reap panics on the tested kernel
src/exp.c fake-cred chain; verifies uid 0 via getuid/geteuid
src/uid0.c real-cred chain; zeroes cred->uid/gid and execs sh
src/kernel_profile.h per-release call sites, symbols and structure offsets
src/trace_ids.h runtime kmem trace event IDs; unprivileged probe
src/vuln_precheck.h shared profile/environment gate
app/ Android root app (Shizuku + KernelSU bootstrap)
docs/analysis.md root cause, exploit chain and device notes
scripts/dump_device.sh root device dump: all .ko/modinfo, kernel/config/policy, perf/tracefs, vendor mount, harness, active boot.img
Makefile single build entry (NDK cross-compile, app APK)
README.zh-CN.md Chinese version of this document
خيط غير قائد أنشأ مؤقت posix من نوع CLOCK_THREAD_CPUTIME_ID ثم فشل في execve() بعد de_thread() يُبقي عقدة المؤقت في قائمة الانتظار. إشارة SIGKILL التي تقاطع تنفيذ exec تجعل الخيط يخرج عبر exit_itimers()، والتي لا تستطيع تعطيل مؤقت مهمته بالفعل في طور الخروج. يتم تحرير k_itimer بينما لا يزال timerqueue_node الخاص به مرتبطًا في قائمة posix_cputimers الخاصة بالمهمة. عندما يسترد الأب الزومبي، يستدعي release_task() -> posix_cpu_timers_exit() -> cleanup_timerqueue() الدالة timerqueue_del()/rb_erase() على العقدة المحررة.
المحو هو البدائية الكتابية: تخزّن rb_erase() قيمة __rb_parent_color مُتحكَّم بها في هدف rb_left مُتحكَّم به. يقوم الاستغلال بتنسيق استرداد slab/page بحيث تستقر العقدة المحررة في صفحة مملوءة بحمولة المهاجم، ثم يختار الهدف والقيمة.
راجع docs/analysis.md للحصول على أثر الذعر المرصود، وإزاحات YLP-W00، والتسريبات القائمة على tracefs، وتنسيق SLUB/RCU، وسلسلة fake-cred ذات المرحلتين وسلسلة real-cred ذات الكتابة الواحدة.
المتطلبات: Android NDK r29 في /opt/android-ndk-r29 (يمكن تجاوزه بـ NDK=). الأهداف هي aarch64، وAPI 24 افتراضيًا. يحتاج التطبيق إضافةً إلى ذلك إلى Android SDK (build-tools 37.0.0، platform 37)، وJDK، وملفات Shizuku jars تحت ~/.local/share/android-cve-env/cve98260-app (يمكن تجاوزها بـ APP_DEPS=). تبقى مخرجات البناء في build/ ويتم تجاهلها بواسطة git. ملفات Shizuku jars يوفّرها المستخدم؛ يتحقق scripts/fetch-deps.sh منها مقابل deps.lock (SHA-256) ولا يقوم بتنزيل أي شيء أبدًا.
| الهدف | المخرجات / الإجراء |
|---|---|
make / make poc | build/poc |
make exp | build/exp |
make uid0 | build/uid0 (ثابت؛ مرحلة الصدفة تعيد تنفيذ نفسها) |
make build-all | بناء poc + exp + uid0 + apk |
make host | build/poc.static للتحقق عبر qemu من جانب المضيف |
make emu | تشغيل build/poc.static تحت qemu (مع --force) |
make apk | app/CVE98260Root.apk |
make clean | إزالة build/ |
make help | سرد الأهداف |
تستدعي poc وexp وuid0 بوابة ملف تعريف قبل لمس النواة. تقبل إما إصدار ملف تعريف مطابق تمامًا (6.12.38 مُتحقَّق منه، 6.12.58 مُكيَّف)، وتتطلب arm64 وصفحات بحجم 4 KiB، وبالنسبة لـ exp/uid0 تتحقق أيضًا من توفر tracefs/kmem و(بالنسبة لسلسلة fake-cred) من panic_on_warn=0. أي إصدار آخر غير مؤكد ويخرج البرنامج بالرمز 2:
build/uid0 --check # print the verdict, do not trigger
build/uid0 --check --force # same, overriding an unconfirmed environment
استخدم --force أو CVE98260_FORCE=1 فقط بعد التحقق من النواة
بشكل مستقل. --force يخفف فحوصات البيئة فقط؛ لا يزال exp وuid0
والتطبيق يتطلبون ملف تعريف مطابق تمامًا من kernel_profile.h. البوابة
ليست مسبارًا وقت التشغيل عن قصد: فالمسبار الذي يصل إلى الثغرة
سيترك بنفسه عقدة timerqueue قديمة.
على YLP-W00 لا تستطيع صدفة غير مميزة قراءة /proc/sys/kernel/panic_on_warn
أو /proc/cmdline؛ بالنسبة لملف تعريف معروف تفترض البوابة القيمة
المُتحقَّق منها لـ 6.12.38 وهي 0. يُستخدم الافتراض نفسه لملف التعريف
المُكيَّف 6.12.58 حتى تكتمل عملية التحقق منه وقت التشغيل. تبقى
الإصدارات غير المعروفة محظورة.
يبدأ الاستغلال كعملية صدفة عادية (uid 2000، SELinux
u:r:shell:s0) ولا يستخدم su. لا تشغّله عبر su: فالشرط
المُختبَر هو بدء غير مميز.
make uid0
adb push build/uid0 /data/local/tmp/uid0
adb shell chmod 755 /data/local/tmp/uid0
adb shell /data/local/tmp/uid0 # runs id and prints the context
adb shell /data/local/tmp/uid0 shell # interactive sh -i
قبل السلسلة الكاملة، استخدم الأوضاع غير المدمِّرة:
adb shell /data/local/tmp/uid0 --probe-ids # discover kmem trace event IDs
adb shell /data/local/tmp/uid0 leak # timer/cred leak only; no UAF/reap
تُسنَد معرّفات أحداث تتبّع kmem عند الإقلاع ولا تكون مستقرة عبر عمليات بناء النواة (6.12.38 يستخدم 318/319/322/324). يعزل --probe-ids حدثًا واحدًا في كل مرة عبر rss_stat/trigger القابل للكتابة ويكتشف أنواع السجلات الخام؛ أما التشغيلات العادية فتقوم بالاستكشاف تلقائيًا. يطبع leak القيمة [base]، وtimer_site/slide الكاملين، وموقع تخصيص cred دون الوصول إلى run_plant().
root ثابت، ويعيد ابنه تنفيذ الملف الثنائي للحصول على cred حقيقي خاص، لذا يجب أن يبقى في المسار المدفوع (يُexec عبر /proc/self/exe).
عند النجاح ينتهي الخرج بـ: