Sploitus

Exploit for GhostLock-GOT-W29

kitploit · 2026-08-29

Exploit Code

MARKDOWN192 lines
## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-ZZZXXXXXXXXXX-GHOSTLOCK-GOT-W29
# 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).
  * **لكن التصعيد الحقيقي (shell بدون KPM وبدون RT) غير ممكن** : مسار إرجاع pselect في نواة 4.19 يستبدل بشكل حتمي كلمة lock في overlay، ولا يوجد ناقل بديل. هذا طريق مسدود تحدده هندسة المكدس في النواة، وليس عيبًا في التنفيذ (بالمقارنة، يمكن تصعيد الامتيازات بالكامل على أجهزة smt878u/popsicle بسبب اختلاف هندسة المكدس).



## الجهاز

العنصر| القيمة  
---|---  
الطراز| 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، تم تنفيذه)

حلقة 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`.

## النتائج

### تسريب KASLR (perf_event_open)

في shell (uid 2000) مع `perf_event_paranoid=-1`، يقوم `perf_event_open(PERF_SAMPLE_IP, exclude_user=1)` بأخذ عينات من مجموعة عناوين نص النواة، ومحاذاتها مع إزاحات الرموز المعروفة للحصول على slide.

root@kitploit:~
    
    
    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.

### تحفيز EDEADLK

root@kitploit:~
    
    
    [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`. النقاط الرئيسية:

  * task_struct: cred=0x988, prio=0x184, pi_blocked_on=0xa90, usage=0x68, mm=0x728
  * rt_mutex_waiter (HW_FUTEX_PI): tree@0x0, pi_tree@0x18, task@0x30, lock@0x38, major@0x40, prio@0x48, deadline@0x50
  * PAGE_OFFSET=0xffffffc000000000, PHYS_OFFSET=0x80000000 (kona), KIMAGE_TEXT_BASE=0xffffff8008080000



### التحقق من بدائية الكتابة على الجهاز الحقيقي

وحدة 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`**.

root@kitploit:~
    
    
    REPAIR3 waiter=0xffffff801ecdbc00 lock=0xffffffa7c4950000
    slide boot_id_leaked_nfulnl_logger value=ffffffa7c4612320
    slide-kaslr-ok base=ffffffa7c1280000 slide=00000027b9200000
    

## تصميم Overlay

هندسة المكدس مؤكدة من أحجام إطارات 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) حتى يكمل المستهلك.

## لماذا التصعيد الحقيقي غير ممكن

### 1\. كلمة lock في ناقل pselect يتم استبدالها في مسار الإرجاع

تقع كلمتا 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 شبه مؤكد.

حقيقتان إضافيتان ذات صلة:

  * **مسار بدون مالك محجوب في 4.19** : `rt_mutex_adjust_pi()` يحتوي على `if (!owner) return 0;`، ولا يتم استدعاء adjust_prio_chain عندما لا يملك fake_lock مالكًا. لا يحتوي popsicle (6.12) على هذا الفحص. تم نقل حمولة مع مالك (fake_lock owner=fake_task|1)، لكن لا يمكن تشغيلها بشكل موثوق لأن كلمة lock سيتم استبدالها حتمًا.
  * **لا يمكن استخدام empty_zero_page كهدف كتابة قسري** : كتابتها تدمر الصفحة الصفرية المشتركة للنظام → عاصفة oops بعد الاختبار.



### 2\. الناقل البديل (ppoll) غير ممكن على مستوى الترميز

بعد فك تجميع `__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 مكتوبة بواسطة النواة (غير قابلة للتحكم).

### 3\. الاختلاف عن الأجهزة الأخرى

يمكن تصعيد الامتيازات بالكامل على smt878u / popsicle: هندسة المكدس لديهم تسمح بوقوع الكلمات في in/out/ex القابلة للتحكم من قبل المستخدم (3 مجموعات fd في pselect). موضع waiter في GOT-W29 (bits+0x60 → task/lock في res_in[3]/[4]) لا يحتوي على هذه النافذة. **هذا اختلاف في هندسة مكدس النواة، وليس عيبًا في التنفيذ**.

### 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).

### مجموعة hooks

### الترجمة

root@kitploit:~
    
    
    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`):

root@kitploit:~
    
    
    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). قبل الاختبار:

root@kitploit:~
    
    
    adb shell 'su -c "sh /sdcard/ghostlock-test/set_debug_no_reboot.sh"'  # منع إعادة التشغيل
    

### نقاط المراقبة (dmesg `[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`.

## الدليل

root@kitploit:~
    
    
    tools/      أدوات التحقق (perf KASLR, مسبار EDEADLK, سلسلة أدوات KPM)
    target/     جميع الإزاحات المقاسة
    exploit/    slide.c المنقول (بما في ذلك تعديلات تحفيز EDEADLK)
    

## الشكر والتقدير

  * PoC الرسمي: x-spy/CVE-2026-43499-popsicle, soralis0912/CVE-2026-43499-aristotle, JoinChang/ghostlock-oneplus, Wtrwx/smt878u-ionstack-poc (GPL-3.0)
  * CVE: NVD, Red Hat RHSB-2026-010