
بحث حول CVE-2026-43499 (GhostLock) على HUAWEI MatePad Pro 11 GOT-W29
بحث تصعيد الامتيازات لـ CVE-2026-43499 (rtmutex/futex-PI UAF، "GhostLock") على GOT-W29 (HarmonyOS 4.0، kernel 4.19.157-perf+).
الاستنتاجات الأساسية:
sysctl_bootid في النواة بمساعدة KPM، واستنتاج KASLR slide).| العنصر | القيمة |
|---|---|
| الطراز | HUAWEI MatePad Pro 11 GOT-W29 |
| SoC | Qualcomm kona (SM8250, Snapdragon 870) |
| النظام | HarmonyOS 4.0 (104.0.0.136) |
| النواة | 4.19.157-perf+ |
| VA | 39-bit, 4K pages, KASLR on |
في kernel/locking/rtmutex.c، تستخدم الدالة remove_waiter() في مسار التراجع عن rt_mutex_start_proxy_lock() المتغير current بدلاً من waiter->task للتنظيف، مما يؤدي إلى pi_blocked_on معلق (stack UAF). تؤثر على 2.6.39 ~ 7.1 (هذه النواة ضمن النطاق). الإصلاح الرسمي في commit 3bfdc63936dd.
تم التأكيد على هذا الجهاز: الكود المصدري rtmutex.c:1110-1112، وفك تجميع boot.elf، والتحفيز على الجهاز الحقيقي — كلها تم التحقق منها.
إنشاء حلقة PI لجعل FUTEX_CMP_REQUEUE_PI يُرجع -EDEADLK، مما يؤدي إلى التراجع وتشغيل خطأ remove_waiter، تاركًا pi_blocked_on معلقًا (يشير إلى rt_waiter على مكدس نواة خيط waiter).
كان التحفيز القديم يجعل waiter يمتلك futex هدف إعادة التوجيه بنفسه (self-own)، مما يصادف بالضبط فحص owner==task المبكر في task_blocks_on_rt_mutex في هذه النواة (boot.elf 0x3808-0x3868)، ويعود قبل كتابة pi_blocked_on → لا يتم إنشاء مؤشر معلق أبدًا → وضع overlay كان تشخيصًا خاطئًا (بدون انهيار + boot_id بدون تغيير).
حلقة PI: المالك FUTEX_LOCK_PI(target) يمتلك هدف إعادة التوجيه؛ waiter يمتلك chain futex؛ ثم يمنع المالك نفسه على chain (الحلقة: waiter→target→owner→chain→waiter). أثناء إعادة التوجيه، يكتشف فحص السلسلة rt_mutex_owner(chain)==top_task → -EDEADLK → التراجع يمسح pi_blocked_on للشخص الخطأ → يصبح pi_blocked_on الخاص بـ waiter معلقًا. يجب خفض أولوية المالك (nice=10) بحيث يختلف prio بعد boost عن owner_waiter->prio، وإلا فسيحدث خروج مبكر في rt_mutex_waiter_equal.
في shell (uid 2000) مع perf_event_paranoid=-1، يقوم perf_event_open(PERF_SAMPLE_IP, exclude_user=1) بأخذ عينات من مجموعة عناوين نص النواة، ومحاذاتها مع إزاحات الرموز المعروفة للحصول على slide.
samples=27651 kernel_ips=1685 lo=0xffffff948728176c hi=0xffffff9488ebfc7c
KASLR slide=0x147f200000 (40/40 IP mapped into kernel text region verified)
runtime _stext=0xffffff9487280800
الأداة: tools/perf_kaslr.c. المتطلبات المسبقة: shell (Shizuku rish)، بدون اعتراض seccomp.
[M] CMP_REQUEUE_PI ret=-1 errno=35 (EDEADLK!)
[W] WAIT_REQUEUE_PI ret=-1 errno=110 (ETIMEDOUT) ← waiter returned
[M] waiter_returned=1 ← left dangling pi_blocked_on
الأداة: tools/edeadlk_probe.c (variant 8+2+1 = 11، أو 27).
في rt_mutex_adjust_prio_chain الخطوة [7]، يتم تنفيذ rb_erase على fake waiter (مسار فرع أيسر واحد): *(tree_left) = tree_pc + كتابة تدريجية __rb_change_child. جميع الإزاحات في target.h تم قياسها من فك تجميع boot.elf.
انظر exploit/ghostlock-source/src/target.h. النقاط الرئيسية:
وحدة KPM مكتوبة ذاتيًا (tools/kpm-debug/rtmutex-dbg.c، KernelPatch 0.13.5 inline-hook) تعيد بناء overlay (tree/task/lock) أثناء المسار الوهمي وتعيد كتابة معامل next_lock إلى empty_zero_page (يستخدم KernelPatch _transit8 الدالة الأصلية مع fargs المعدلة)، بحيث يمر [3] next_lock==waiter->lock في rt_mutex_adjust_prio_chain، وينجح [5] trylock على قفل صفري، و[6] بدون مالك، ويتم تنفيذ [7] rt_mutex_dequeue (rb_erase بفرع أيسر واحد) — تمت إعادة كتابة sysctl_bootid إلى &loggers[0][1]، slide-kaslr-ok.
REPAIR3 waiter=0xffffff801ecdbc00 lock=0xffffffa7c4950000
slide boot_id_leaked_nfulnl_logger value=ffffffa7c4612320
slide-kaslr-ok base=ffffffa7c1280000 slide=00000027b9200000
هندسة المكدس مؤكدة من أحجام إطارات boot.elf: rt_waiter في __arm64_sys_futex(0x70) + do_futex(0x60+0x1a0) عند sp+0xc0 → العمق 0x1b0؛ stack_fds[0] في مسار pselect عند العمق 0x210، الفرق 0x60 = 12 كلمة. لذلك تقع word_i في stack_fds[12+i]: الكلمات 0-2 في منطقة إدخال ex[2..4] (قابلة للتحكم المباشر)، الكلمات 6-7 (task/lock) في res_in[3..4] (مشفرة باستخدام in[3..4] + fd جاهز POLLIN)، الكلمات 3-5/8-10 تُترك 0. يعود pselect فورًا بسبب fd الجاهز → ينتظر waiter في وضع المستخدم بحلقة انتظار (بدون إشارات، بدون syscalls) حتى يكمل المستهلك.
تقع كلمتا task/lock في overlay في res_in[3]/[4] (مشفرة بجاهزية fd). تم قياس أن res_in[4] (lock) يتم استبدالها بشكل حتمي في مسار إرجاع pselect (بقايا إطار rt_sigreturn)، ولا تساوي أبدًا fake_lock في الحمولة؛ res_in[3] (task) سليمة أحيانًا. يمكن للتجنب في وضع المستخدم (عمليات FP + sched_yield) فقط خفض معدل تشغيل do_notify_resume إلى ~21%، لكن استبدال lock شبه مؤكد.
حقيقتان إضافيتان ذات صلة:
rt_mutex_adjust_pi() يحتوي على if (!owner) return 0;، ولا يتم استدعاء adjust_prio_chain عندما لا يملك fake_lock مالكًا. لا يحتوي popsicle (6.12) على هذا الفحص. تم نقل حمولة مع مالك (fake_lock owner=fake_task|1)، لكن لا يمكن تشغيلها بشكل موثوق لأن كلمة lock سيتم استبدالها حتمًا.بعد فك تجميع __arm64_sys_ppoll / do_sys_poll في boot.elf تم التأكيد: pollfd هو بنية من 16 بايت (fd 4B + events 4B + revents 4B + pad 4B)، ولا يمكنها حمل كلمتي task/lock (64 بت) لـ fake waiter — قيمة fd محدودة (يجب أن تكون fd حقيقية)، events فقط 4 بايت وغير متصلة، revents مكتوبة بواسطة النواة (غير قابلة للتحكم).
يمكن تصعيد الامتيازات بالكامل على smt878u / popsicle: هندسة المكدس لديهم تسمح بوقوع الكلمات في in/out/ex القابلة للتحكم من قبل المستخدم (3 مجموعات fd في pselect). موضع waiter في GOT-W29 (bits+0x60 → task/lock في res_in[3]/[4]) لا يحتوي على هذه النافذة. هذا اختلاف في هندسة مكدس النواة، وليس عيبًا في التنفيذ.
لا توجد قناة KASLR جاهزة في نطاق التطبيق (untrusted_app) (perf/kallsyms/pagemap/dmesg كلها مرفوضة)؛ يُرجع CMP_REQUEUE_PI القيمة 1 (نجاح إعادة التوجيه) ولا يمر عبر تراجع EDEADLK؛ major_only في cpuset غير الجذر يقصر دائرة الخطوة [6] بشكل صارم. المدخل الوحيد لتغيير QOS /dev/iaware_qos_ctrl مرفوض بواسطة SELinux. لذلك لا يمكن لتطبيق عادي تشغيل هذه الثغرة.
وحدة KernelPatch مكتوبة ذاتيًا (rtmutex-dbg) لمراقبة fake waiter وبدائية الكتابة على الجهاز الحقيقي، مبنية على ملفات رأس LyraVoid/KernelPatch 0.13.5 (نفس مصدر FolkPatch).
cd <KernelPatch>/kpms/rtmutex-dbg
make TARGET_COMPILE=aarch64-linux-android- \
CC=$PREFIX/bin/aarch64-linux-android-clang \
LD=$PREFIX/bin/aarch64-linux-android-ld
الناتج rtmutex_dbg.kpm. يجب أن تتضمن أعلام الترجمة
-fno-pic -fno-pie -fno-asynchronous-unwind-tables -fno-unwind-tables
(مضمنة بالفعل في Makefile): ينتج clang افتراضيًا PIC مع إعادة توطين GOT، وينتج افتراضيًا .eh_frame
(R_AARCH64_PREL32) — لا يدعم محمل KPM أيًا منهما → فشل التحميل -1.
المفتاح الفائق لـ FolkPatch هو su (وليس KernelPatch الافتراضي في APatch). استخدم أداة sc_kpm_load (الكود المصدري sc_kpm_load.c):
adb shell /data/local/tmp/sc_kpm_load su /sdcard/Download/rtmutex_dbg.kpm # تحميل
adb shell /data/local/tmp/sc_kpm_load unload rtmutex-dbg su # إلغاء التحميل
adb shell /data/local/tmp/sc_kpm_load ctl rtmutex-dbg counts su # العدادات
run_rtmdbg_test.sh (على الجهاز /data/local/tmp/ghostlock-test/): تشغيل GhostLock بهوية shell
(المسار الحقيقي GOT_SLIDE_NO_RT=1)، حلقة مزامنة 0.5s لمنع فقدان السجلات،
dmesg -w على القرص، جمع تلقائي بعد 90s (SIGSTOP لمنع إعادة التشغيل بسبب soft-lock). قبل الاختبار:
adb shell 'su -c "sh /sdcard/ghostlock-test/set_debug_no_reboot.sh"' # منع إعادة التشغيل
[RTMDBG])REPAIR3: إعادة بناء overlay وإعادة كتابة معامل next_lock أثناء التحقق من بدائية الكتابةFAKEWALK skip: تخطي المسار الوهمي المغطى (بدون كتابة وبدون انهيار)prio_chain[N] / prio_chain_ret: استدعاءات المسار وقيم الإرجاع (0=اكتمل؛
4294967261=-EDEADLK)do_select n=320: res_in[3]/[4] من جانب النواةfutex op=13/14: CMP_REQUEUE_PI / WAIT_REQUEUE_PIسجلات oops/panic الكاملة من HUAWEI في /data/log/bbox/history.log.
tools/ أدوات التحقق (perf KASLR, مسبار EDEADLK, سلسلة أدوات KPM)
target/ جميع الإزاحات المقاسة
exploit/ slide.c المنقول (بما في ذلك تعديلات تحفيز EDEADLK)
| hook | الوظيفة |
|---|
rt_mutex_adjust_pi | تسجيل تعديلات PI؛ تخطي عند تغطية overlay (مسح pi_blocked_on) |
rt_mutex_adjust_prio_chain | تخطي غير مشروط للمسار الوهمي؛ تفريغ كامل لـ waiter |
__arm64_sys_pselect6 / __arm64_sys_ppoll | مسح _TIF_WORK_MASK في مسار الإرجاع؛ مراقبة fd_set |
do_select | قراءة res_in[3]/[4] من جانب النواة |
__arm64_sys_futex | تتبع WAIT_REQUEUE_PI / CMP_REQUEUE_PI |
rt_mutex_dequeue | تأكيد تنفيذ بدائية الكتابة في الخطوة [7] وشكل الشجرة |