
GhostLock (CVE-2026-43499) لـ OPPO Find X5 Pro (PFEM10) — هندسة عكسية لكاشف OPlus watchdog و heap-spray
English · 中文
منفذ GhostLock (CVE-2026-43499) لجهاز OPPO Find X5 Pro على ColorOS 16. يصل إلى عملية فرعية uid=0 ووحدة kernelsu.ko محمّلة؛ يتم اعتراض عملية الجذر.
CVE-2026-43499 — استخدام-بعد-التحرير في futex PI. تقوم remove_waiter() بمسح current->pi_blocked_on عندما يكون current هو المُعيد للطلب، على مسار التراجع -EDEADLK الخاص بـ rt_mutex_start_proxy_lock().
remove_waiter @ 0xffffffc0081ed254 — الشكل قبل الإصلاح.
| الجهاز | OPPO Find X5 Pro (PFEM10) |
| SoC | SM8450 / Adreno 730 |
| نظام التشغيل | ColorOS 16.0.3.520 (CN01) |
| النواة | 5.10.236-android12-9-o-gaf2075ad2c06 |
| محمّل الإقلاع | مقفل، أخضر |
| VA_BITS | 39 — KIMAGE_TEXT_BASE = 0xffffffc008000000 |
| المرحلة | |
|---|---|
مُشغّل waiter المضغوط (CMP_REQUEUE_PI → EDEADLK) | يعمل |
تسريب task_struct (perf) | يعمل |
كتابة PI (8 بايت؛ القيمة = 0 أو عنوان نواة صالح) | يعمل |
task+0x778 أو task+0x780 وحده → Uid=root | يعمل — لكن الهبوط بحقل واحد يترك المهمة متباعدة، وهذا BUG_ON صلب كامن. انظر خطر التباعد |
| كتابة كلا الحقلين بقيمة واحدة (زوج متسق) | ❌ لم يتحقق أبداً بصفحة مرشوشة. لوحظ فقط مع الاسم المستعار العام init_cred (09-14، CONTROL=1). المُشغّل الآن يفرضه (SAME_VALUE=1)؛ لم يُشغَّل على الجهاز |
غسل بيانات الاعتماد (setresgid + setresuid) | مُنفَّذ خلف V12_LAUNDER=1؛ لم يُشغَّل على الجهاز |
تحميل kernelsu.ko | يعمل |
| بقاء عملية الجذر | ⚠ غير مُثبت — انظر أدناه |
| آلية إعادة التشغيل | ❌ غير مُثبتة. أحد المرشحين (التباعد) أصبح الآن مستبعداً؛ انظر أدناه |
probe_state كمعيار للهبوط | ❌ خاطئ — لا تستخدمه. ثلاثة أمثلة مضادة؛ انظر الجدول أدناه |
| قناة panic الخاصة بـ pstore/ramoops | ⚠ الأداة موجودة؛ القناة لم تُتحقق منها أبداً (لا اختبار فارغ بعد) |
| "الضحية تدور في فضاء المستخدم الخالص" | ⚠ لا قراءة بعد — uid.stream الآن يسجّل utime/stime/nvcsw حتى يمكن التحقق |
| الكتابة المزدوجة أحادية التمرير من جهة pi | ⚠ غير مُثبتة؛ pi.pc/pi.left مُثبَّتان على 0 في fdset_map.h |
المسار A (UMH / modprobe_path) | STATIC_USERMODEHELPER_PATH="" |
بخصوص "بقاء عملية الجذر": التشغيلات في evidence/kill.log
تصل إلى uid=0 وتحمّل kernelsu.ko، وفي التشغيل الذي استطلعها فعلاً
نجت عملية مدير KernelSU لمدة 120 ثانية مع بقاء kernelsu بحالة Live في
/proc/modules. في تشغيل لاحق، تركت نفس السلسلة خدمات إطار عمل Android غير قابلة للوصول (Can't find service: package/power/input/phone/wifi)
بينما كانت الوحدة لا تزال Live. لم يُلتقط أبداً أي سطر نواة [ROOTCHECK-*] ولا أي حمولة $$sys_call_number@@، لذا لا يُنسب سبب حالة التشغيل اللاحق. انظر evidence/notes.md
§2.3 و§2.4 و§7.
task_struct
| الحقل | الإزاحة |
|---|---|
real_cred / cred | 0x778 / 0x780 |
syscallno المخزّن مؤقتاً | 0xdf8 |
uid / euid / gid / egid المخزّنة مؤقتاً | 0xe00 / 0xe08 / 0xe10 / 0xe18 |
thread_info
| الحقل | الإزاحة |
|---|---|
flags | 0x0 |
addr_limit | 0x8 |
ttbr0 | 0x10 |
preempt_count | 0x18 |
cred
| الحقل | الإزاحة | الحقل | الإزاحة |
|---|---|---|---|
uid | 0x4 | cap_inheritable | 0x28 |
gid | 0x8 | cap_permitted | 0x30 |
suid | 0xc | cap_effective | 0x38 |
sgid | 0x10 | cap_bset | 0x40 |
euid | 0x14 | cap_ambient | 0x48 |
egid | 0x18 | ||
fsuid / fsgid | 0x1c / 0x20 |
LT perf leak target task_struct → file W7 stage 1 task+0x778 = V (real_cred → private sprayed page; V observed) W7 stage 2 task+0x780 = V (cred) ★ V12_W7_VALUE=V — THE SAME VALUE, not a new page W7 stage 3 V+8 = 0 (LOCAL repair of the page that was installed, ZERO shape) LT child fexecve(memfd of loader) — no execve of a /data path loader ksud late-load → kernelsu ... Live
**يجب إعطاء المرحلة 2 قيمة المرحلة 1 بشكل صريح.** المرحلتان 1 و2 عمليتان
مستقلتان، لكل منهما رشّتها الخاصة، لذا فإن "كتابة صفحة بيانات الاعتماد إلى كلا
الفتحتين" هو فخ: عند قراءته بسذاجة ينتج `(pageA, pageB)`، ولأن
`commit_creds` يقارن **المؤشرات**، فإن هذا الزوج متباعد حتى عندما ينجح كلا
الكتابتين. هذا ليس افتراضيًا — بل هو بالضبط ما فعلته التشغيلتان 3 و9:```
run 9 0x778 shot write value = 0xffffff88679bade0
0x780 shot write value = 0xffffff8785d6ade0 <- a different page
run 3 0x778 shot write value = 0xffffff8787b5ade0
0x780 shot write value = 0xffffff881bad2de0 <- a different page
run_bootA.sh لذلك يُشغّل المرحلة 2 مع V12_W7_VALUE=<القيمة المرصودة من المرحلة 1> ويرفض تشغيلها إطلاقًا إذا تعذّر استرجاع تلك القيمة. يجب أن يبقى HOLD حيًّا بعد المرحلة 2، وإلا فسيتم تحرير صفحة المرحلة 1 وإعادة تخصيصها وتصبح "القيمة نفسها" مؤشرًا معلّقًا. انظر
قاعدة القيمة نفسها.
تُصلَّح صفحة واحدة لكل إقلاع. المرحلة 3 تُصفّر V+8. مع صفحتين مختلفتين، فإن تصفير كلتيهما سيمحو ختم gid/suid (أدناه) ويجعل التباعد يبدو كتوافق، لذلك يُصلح المشغّل فقط الصفحة التي ثُبّتت فعليًا ويتوقف إذا اختلفت القيمتان.
تُبنى صفحة cred بواسطة payload.c: جميع حقول المعرّفات الثمانية صفر، وجميع مجموعات القدرات الخمس ممتلئة، وuser / user_ns / group_info تشير إلى root_user / init_user_ns / init_groups. المرحلة 3 موجودة لأن الأثر الجانبي للكتابة يُفسد دائمًا cred+8 (gid/suid) لأي cred يُثبّته.
init_cred — ثنائية صريحةكان قسمان هنا يتناقضان سابقًا ("أبدًا init_cred العام" مقابل "CONTROL=1 يعيد إنتاج الخلية 2"، والخلية 2 هي init_cred). كلا العبارتين صحيحتان لأدوار مختلفة:
init_cred تجعل الأثر الجانبي يُفسد init_cred+8 عالميًا — init_cred مشترك بين كل خيوط النواة، وUid: 0 0 4294967176 0 هو بالضبط ذلك الإفساد. يرفض الكود هذا المسار إلا إذا تم تعيين V12_ALLOW_INIT_CRED=1 عن قصد.0xffffff802a7e0be0) في كلا الفتحتين، لذا real_cred == cred بالبناء — ولهذا نجت حتى execve. CONTROL=1 يعيد إنتاجها. إنها ضابط تحكم، وليست إعدادًا يُبنى عليه.تسريب perf: PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_CLOCK، PERF_SAMPLE_REGS_INTR، exclude_user=1.
اقبل [0xffffff8400000000, 0xffffff90000000)، أصوات ≥ 15%.
يُقاد الـ UAF عبر rb_erase_cached الحالة 1-يسار. يمنحنا ذلك مخزنين، لا واحدًا:```
*(write_target) = write_value // the store you aim
*(write_value + 0x08) = write_target // unavoidable side effect
يجب أن يكون `write_value` محاذيًا لـ 8 بايت مع بت 0 صافٍ — فهو إما `0` أو عنوان kernel صالح. **لهذا السبب لا يمكن تعيين `g_boot_state` باستخدام هذه البدائية**: البايت الذي تحتاجه ليصبح `1` يتم فرض بتّه المنخفض إلى `0` بواسطة متطلب المحاذاة، و`write_value` هو نفس الكمية كالعنوان الذي يهبط فيه التأثير الجانبي.
### التأثير الجانبي يكتب في أي شيء يشير إليه `write_value`
`write_value` هو في آنٍ واحد *القيمة المخزّنة* و*العنوان الذي يكتب إليه التأثير الجانبي* (عند `+8`). وجّهه إلى كائن kernel عام وستُفسد ذلك الكائن.