Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
ghostlock-pfem10 — GhostLock (CVE-2026-43499) لـ OPPO Find X5 Pro (PFEM10) — هندسة عكسية لكاشف OPlus watchdog و heap-spray | Kitploit
أدوات/GitHubGitHub/imeiplus/ghostlock-pfem10
أمان أندرويدتصعيد الامتيازاتتحليل الذاكرة الجنائيتحليل الثغرات الأمنيةالاستغلالالهندسة العكسيةأمن الجوالتطوير الحمولاتاستغلال الملفات الثنائية
GitHubimeiplus/ghostlock-pfem10

ghostlock-pfem10

GhostLock (CVE-2026-43499) لـ OPPO Find X5 Pro (PFEM10) — هندسة عكسية لكاشف OPlus watchdog و heap-spray

منذ 2 أياملم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة
عرض المستودع

GhostLock — OPPO Find X5 Pro (PFEM10)

English · 中文

build

منفذ 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 — الشكل قبل الإصلاح.

الجهاز

الحالة

بخصوص "بقاء عملية الجذر": التشغيلات في 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

thread_info

الحقلالإزاحة

cred

مسار الاستغلال```

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

root@kitploit:~
**يجب إعطاء المرحلة 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 عن قصد.
  • محتفظ به باعتباره الزوج المتسق الوحيد المُثبَت. سلسلة 09-14 التي وصلت إلى ksud كتبت عنوانًا ثابتًا واحدًا (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

root@kitploit:~
يجب أن يكون `write_value` محاذيًا لـ 8 بايت مع بت 0 صافٍ — فهو إما `0` أو عنوان kernel صالح. **لهذا السبب لا يمكن تعيين `g_boot_state` باستخدام هذه البدائية**: البايت الذي تحتاجه ليصبح `1` يتم فرض بتّه المنخفض إلى `0` بواسطة متطلب المحاذاة، و`write_value` هو نفس الكمية كالعنوان الذي يهبط فيه التأثير الجانبي.

### التأثير الجانبي يكتب في أي شيء يشير إليه `write_value`

`write_value` هو في آنٍ واحد *القيمة المخزّنة* و*العنوان الذي يكتب إليه التأثير الجانبي* (عند `+8`). وجّهه إلى كائن kernel عام وستُفسد ذلك الكائن.

**W7 كان يفعل هذا بالضبط** — حيث كان يوجّه `write_value` إلى الاسم المستعار `init_cred` — وهذا مرئي في القراءة المرتجعة. من `out/t5_w7_778.txt`:```
shape shift=0 wps=5: in[0]=0xffffff802a7e0be0 (write_value) in[2]=0xffffff8800cdd178 (write_target)
W7[W7] write_target= 0xffffff8800cdd178
Uid:	0	0	4294967176	0

كان write_value هو الاسم المستعار لـ init_cred وكان write_target هو child_task+0x778. init_cred+8 هو gid/suid، لذا خزّن الأثر الجانبي 0xffffff8800cdd178 هناك: init_cred.gid = 0x00cdd178 و init_cred.suid = 0xffffff88 = 4294967176 — وهو تحديداً الحقل الرابع في awk من سطر Uid: أعلاه. تصفير init_cred+8 أصلحه (out/t5_repair.txt: Uid: 0 0 4294967176 0 → )، وهذا كل ما كانت عليه "W7 stage 3" على الإطلاق.

هذا المسار مرفوض الآن في الكود. يُجهض V12_W7_INIT_CRED=1 مع شرح ما لم يُضبط V12_ALLOW_INIT_CRED=1 أيضاً، ولم تعد مسارات W2/W6/LTC ترتد إلى init_cred عند فقدان صفحة cred الخاصة — بل تُجهض بدلاً من ذلك. الافتراضي، والمسار الوحيد المعقول، هو صفحة cred المرشوشة.

لا يمكن تجنب الأثر الجانبي نفسه: يجب أن يكون write_value هو مؤشر cred، لذا يُطمس cred+8 دائماً بهدف الكتابة. موقعه فقط هو الخيار — والإصلاح الآن هو تصفير محلي لـ cred_page+8 (stage 3)، وليس كتابة في كائن عام.

قراءة groups= غير الصحيحة هي عرض منفصل، وليست هذه. لقد شوهدت في تشغيل حيث قُرئ gid و egid بشكل سليم، لذا لا يمكن أن تأتي من الأثر الجانبي لـ init_cred+8؛ إنها تشير إلى حقل group_info الخاص بـ cred المزيف. انظر evidence/notes.md §10.6.

★ الهبوط في حقل واحد يترك المهمة متباعدة — وهذا BUG_ON صارم

تخزّن البدائية في عنوان واحد بالضبط لكل تمريرة. task+0x778 (real_cred) و task+0x780 (cred) عنوانان منفصلان، لذا أي كتابة هبطت على 0x778 فقط أو 0x780 فقط تترك المهمة بـ cred != real_cred — وهي حالة تباعد.

على هذه الصورة تلك الحالة هي panic صارم، وليست تحذيراً. يفتتح commit_creds بـ BUG_ON(task->cred != task->real_cred):``` commit_creds @0xffffffc008186784 0x1867a4 ldr x19, [x20, #0x778] ; old = task->real_cred 0x1867a8 ldr x8, [x20, #0x780] ; task->cred 0x1867ac cmp x8, x19 0x1867b0 b.ne #0xffffffc008186b68 0x186b68 brk #0x800 ; == BUG()

root@kitploit:~
وتم بناء النواة باستخدام **`CONFIG_PANIC_ON_OOPS=y`** (`CONFIG_PANIC_ON_OOPS_VALUE=1`).
`__put_cred @0xffffffc008185530` يحمل نفس عائلة التأكيدات
(`usage != 0` → BUG؛ `cred == current->cred` / `current->real_cred` → BUG).

إذن التباعد *كامن* — لا يفعل شيئًا بينما الضحية تدور فقط —
حتى يحدث **أي** `commit_creds` على تلك المهمة: `setresuid` / `setresgid` /
`setuid` / `setgid` / `capset`، أو **`execve` عبر `install_exec_creds`**.

> **⛔ مسحوب (2026-09-18 متأخرًا): هذه ليست آلية إعادة التشغيل.**
>
> نسخة سابقة من هذا القسم وصفت التباعد بأنه "المرشح الآلية الرائدة لإعادات
> التشغيل" وقالت إنه "يفسر انقسام الشكل". إنه لا يفعل ذلك، والسبب الآن مقيس
> بدلًا من أن يكون محل جدال:
>
> * `commit_creds` يأخذ مهمته من **`current`** — `0x1867a0 mrs x20, sp_el0`.
>   توقيعه هو `commit_creds(struct cred *new)`؛ لا توجد وسيطة مهمة.
>   لذا فإن التباعد يهم فقط إذا كانت المهمة **الحاملة** له تستدعي `commit_creds`
>   بنفسها.
> * كانت جميع تشغيلات إعادة التشغيل `V12_NO_EXEC=1` (مذكورة حرفيًا في
>   `run3_0445.log:18`، `run9_0606.log:20`، `run10_0616.log:24`)، لذا لم تصدر
>   الضحية `execve` أبدًا ولم تصل إلى `commit_creds` إطلاقًا.
> * التشغيل 10 لم يكن فيه أي poke على الإطلاق (`grep -c poke` = 0).
> * `exit_creds` يصفّر **كلا** المؤشرين قبل `put_cred`
>   (`0x185cb8 str xzr,[x19,#0x778]`؛ `0x185d24 str xzr,[x19,#0x780]`)، لذا فإن
>   `_exit(0)` للضحية **يمحو** التباعد بدلًا من أن يتعثر به.
>
> ⇒ في تلك التشغيلات كان التباعد **خاملًا**. إن `BUG_ON` حقيقي، لكنه لغم أرضي
> لم ينفجر. "الشكل A لا يعيد التشغيل أبدًا" يعود ليكون ارتباطًا. ما يقيّده اللغم
> فعليًا هو **غسيل الاعتماد**، لأن `setresgid`/`setresuid` تستدعي `commit_creds`
> بنفسها.
>
> **الكمية التي تفصل السلاسل فعليًا هي تساوي المؤشرات، وهذا يعني أن الطلقتين
> يجب أن تكتبا قيمة واحدة** — انظر القسم الفرعي التالي.

### ★★★ الطلقتان يجب أن تكتبا القيمة *نفسها* — لا مجرد أن تنجحا كلتاهما

`BUG_ON` يقارن **المؤشرات**. صفحتان تحملان كلتاهما `uid 0` هما مع ذلك كائنان
مختلفان. اللقطات تجعل التمييز ملموسًا:

| السلسلة | طلقة 0x778 | طلقة 0x780 | المؤشرات |
|---|---|---|---|
| القديمة (`t5loop.sh MODE=CRED`) | `in[0]=0xffffff802a7e0be0` | `in[0]=0xffffff802a7e0be0` | **متساوية** → تم تحميل ksud، والمدير بقي حيًا 120 ث |
| الجديدة (`run_bootA.sh`) | `0xffffff88679bade0` (تشغيل 9) | `0xffffff8785d6ade0` | **مختلفة** → متباعدة حتى مع نجاح كلتيهما |

`tools/t5loop.sh` يطبّق **`$ENVV` واحدة** على **كل** إزاحة، لذا جعل `MODE=CRED`
الطلقتين متطابقتين *بحكم البناء*. أما `run_bootA.sh` فقد أطلق الخطوة 5 والخطوة 6
كلًا بـ `$extra` فارغة، لذا رشت كل واحدة **صفحتها الخاصة**.

⇒ الشرط هو **"أن تكتب الطلقتان القيمة نفسها"**. `run_bootA.sh` يفرض ذلك الآن
(`SAME_VALUE=1`، وهو الافتراضي): الخطوة 6 تعيد استخدام `write_value` المرصودة
من الخطوة 5 حرفيًا، و**ترفض الإطلاق إطلاقًا** إذا لم تستطع استعادة تلك القيمة —
لأن الإطلاق سيبني زوجًا متباعدًا.

⚠ يجب أن يعيش `HOLD` أطول من الطلقة الثانية. إذا مات طفل PIN الخاص بالطلقة
الأولى أولًا، تُحرَّر الصفحة وتُعاد تخصيصها ويصبح "القيمة نفسها" مؤشرًا معلقًا.
الافتراضي `HOLD=20` **قصير جدًا**؛ استخدم `HOLD=600`. هذا هو الآن *الافتراضي*
كلما كان `SAME_VALUE=1` — فالقيمة غير المشروطة القديمة البالغة 20 ث كانت تعني أن
الإعداد الافتراضي هو نفسه الفخ — و`HOLD` قصير صريح مع `SAME_VALUE=1` يحذّر الآن
بصوت عالٍ بدلًا من إنتاج مؤشر معلق بصمت.

⚠ كان `CONTROL=1` يغيّر **الخطوة 5 فقط**، لذا كان ينتج
`(init_cred, fresh page)` — زوجًا متباعدًا — بينما ادّعى هذا الملف أنه يعيد إنتاج
الخلية 2. تم الإصلاح: الآن يضبط الطلقتين على `init_cred`. (تكلفة الخلية 2 قائمة:
الأثر الجانبي يفسد `init_cred+8` عالميًا، وهو ما يمثله
`Uid: 0 0 4294967176 0`.)

**التبعات على أي شيء يريد غسل الاعتماد**
(`setresgid` + `setresuid`، لتبديل الصفحة المرشوشة بـ `struct cred` حقيقي):

- الآلية حقيقية ومتحقق منها — `commit_creds` يكتب `x21` في **كلا**
  `task+0x778` و`task+0x780` (`0x186998` / `0x1869a0`)، لذا استدعاء واحد يصلح
  الانقسام نهائيًا؛ `prepare_creds @0xffffffc008186070` هو
  `kmem_cache_alloc(cred_jar)` + `memcpy(new, task->cred, 0xA8)` +
  `security_prepare_creds(...)`، و147/149 كلاهما على قائمة إعفاء الحارس.
- **لكن شرطه المسبق هو عكس "تخطَّ طلقة 0x778".** الغسل نفسه يستدعي
  `commit_creds`، لذا يجب ألا يُصدر إلا عندما يكون **كلا** المؤشرين يحملان
  القيمة نفسها بالفعل.
- `V12_LAUNDER=1` مشروط بـ**شيئين**، والأول ليس ملاحظة:
  1. **`V12_W7_SAME_VALUE=1`** — حقيقة *المصدر* بأن الطلقتين أُعطيتا القيمة
     نفسها. بدون بدائية قراءة، تكون هوية المؤشر غير قابلة للملاحظة، لذا لا يمكن
     استبدال هذا بفحص أفضل في مساحة المستخدم؛ يجب التصريح به.
  2. `consistent=1` — توافق رؤية `0x780` (`getuid()`) مع رؤية `0x778`
     (`/proc/self/status` `Uid:`). **ضروري لكنه غير كافٍ بمفرده**: صفحتان
     متميزتان تحملان كلتاهما `uid 0` تُقرآن متساويتين بينما المؤشرات مختلفة
     — وهي بالضبط الحالة التي كان المشغّل يصنعها. بالنظر إلى (1)، يصبح كافيًا:
     توافق + القيمة نفسها ⇒ نجحت كلتاهما على الصفحة نفسها.
  فشل أي من الفحصين ⇒ ارفض، وجدول الحالات الأربع يدخل في الأدلة.
  سطر تقرير LT يطبع كلا الرؤيتين (`uid=` / `real_uid=` / `consistent=`) بالإضافة
  إلى `same_value_declared=` بحيث تُقرأ الحالة، لا تُستنتج.

### ★ الأداة 1 — الأثر الجانبي هو ختم موجّه نحو الهدف

`*(write_value + 8) = write_target`، و`cred+8` / `cred+0xc` هما `gid` / `suid`،
لذا مخزن واحد بحجم 8 بايت يهبط عبر كليهما:```
cred.gid  = low32(write_target)
cred.suid = hi32(write_target)

هذا قياس، وليس نموذجًا. يحتوي out/t5_w7_778.txt على write_target = 0xffffff8800cdd178 وUid: 0 0 4294967176 0، حيث 4294967176 = 0xffffff88 = hi32(write_target)؛ ويسجّل notes.md §11 النصف الآخر، init_cred.gid = 0x00cdd178 = low32(write_target).

استخدامان:

  1. إنه أوراكل الهبوط لـ task+0x778. يقرأ /proc/<pid>/status real_cred = task+0x778 — وهو بالضبط الـ cred المُثبَّت للتو — لذا فإن الطابع قابل للقراءة مباشرة من مساحة المستخدم. اقرأه قبل المرحلة 3: فالإصلاح يصفّر cred+8 ويمحوه (يقرأ t5_repair.txt في notes.md §11 4294967176 قبل إصلاح ناجح و0 بعده).
  2. إنه سبب ثانٍ مستقل يمكن لبوابة الغسيل من خلاله التقاط صفحتين مختلفتين. لا يمكن لنصف الـ uid وحده فعل ذلك: فأي صفحة ذات uid 0 تقرأ 0، لذا تُبلّغ صفحتان متمايزتان كلتاهما عن "التوافق". لكن low32(T+0x778) و low32(T+0x780) يختلفان بمقدار 8 بالضبط، لذا مع صفحتين يتعارض getgid() (من ) و (من ) — و يقارن gid فضلًا عن uid.

⇒ V12_W7_SAME_VALUE هو البوابة الثانية، وليس الوحيدة. وهو لا يزال مهمًا: فالطابع لا يميّز إلا إذا أُطلقت الآثار الجانبية كليهما، لذا تسدّ قاعدة القيمة نفسها تلك الثغرة المتبقية. ولاحظ ما هي البوابة بالضبط — إنها كاشف، وليست مانعًا. لا يمكنها إلا أن ترفض؛ فهي تترك المهمة متباعدة لبقية الإقلاع. وقاعدة القيمة نفسها هي ما يجعل الزوج صحيحًا، وهو ما كانت السلسلة القديمة تملكه وما يلزم للوصول إلى execve أصلًا.

★ الأداة 2 — probe_state ليس معيار هبوط

لقد كان خاطئًا ثلاث مرات في هذا المشروع: هبط W1 على العام وأبلغ R؛ وكان D في التشغيل 12 موجّهًا إلى عام بدلًا من cred؛ وكُتب R في التشغيل 11 في جدول كما لو كان هبوطًا (run11_w778r1_miss.txt وw7_w7781.txt في التشغيل 7 متماثلان سطرًا بسطر — كلاهما probe_state = R، probe_done = 0). استخدم أوراكل لكل هدف:

يستخدم run_bootA.sh الآن الطابع للمرحلة 1 — وهذا ما يجعل إعادات المحاولة ROUNDS>1 على task+0x778 ذات معنى، لأن الجولة الفاشلة قابلة للقراءة بدلًا من أن تُستنتج — ولن يطلق المرحلة 2 ما لم تهبط المرحلة 1.

⛔ قل "حقل awk"، ولا تقل أبدًا "الحقل الثالث". يطبع uid_line التسمية أيضًا (Uid: 0 0 4294967176 0)، لذا فإن $1 في awk هو "Uid:" وقيم المعرّفات الأربع هي $2..$5: $2=uid $3=euid $4=suid $5=fsuid. يقع الطابع عند cred+8، أي gid (low32) وsuid (hi32) — لذا فهو Gid: $2 و Uid: . وتسميته "الحقل الثالث" (وهو ما يعدّ ، وهكذا تصوغه §11) يغري الكود بقراءة ، وهو = على الـ cred المزيف ولا يمكن أن يساوي أبدًا. كان هذا الخطأ بمقدار واحد موجودًا هنا: أعاد "لا طابع" لطلقة هبطت، فلم تُطلق المرحلة 2 أبدًا ورفضت بوابة الغسيل إلى الأبد — ، لأن "لا طابع" هو أيضًا النتيجة الطبيعية لخطأ حقيقي.

المعيار الذي لا يُختبر أبدًا مقابل عيّنة موجبة معروفة ليس معيارًا، بل تخمين — وهذه الفئة من الإخفاقات (هذا الخطأ بمقدار واحد، probe_state، dmesg -w، klog.host الفارغ، القراءة المرتجعة الفارغة) تظهر دائمًا كـ "لم يحدث شيء"، وهي أيضًا نتيجة تجريبية مشروعة. لذا أصبح الفحص الآن مدفوعًا مرتين:

  • stamp_selftest() يعمل في الفحص المسبق ويُخرج exit 9 عند الفشل، مشغّلًا نفس دوال الاستخراج التي تستخدمها البوابة مقابل القيم المقيسة من out/t5_w7_778.txt (write_target = 0xffffff8800cdd178 → Uid $4 = 4294967176، Gid $2 = 13488504) بالإضافة إلى عيّنات سلبية وغير قابلة للقراءة. الاختبار الذاتي الذي يعيد تنفيذ الفحص لا يثبت شيئًا، لذا فإن استخراج الحقول مُفصَّل في uid_suid_field / gid_gid_field.
  • tools/test_stamp_criterion.sh — الشيء نفسه كاختبار انحدار مستقل، يستخرج الدوال الحقيقية من .

يعيد stamp_ok() ثلاث حالات، لأن "لا يمكن القراءة" ليس "لا طابع" (هذا الخلط هو ما جعل التشغيل 13 يبدو كـ "لا تغيير"): 0 = موجود، 1 = قابل للقراءة ولا طابع، 2 = غير قابل للقراءة. وعندما يعيد 1 بينما probe_state = D، يطبع المشغّل ⛔ ORACLE INCONSISTENT — "اذهب وافحص المعيار" — بدلًا من رسالة "لم يهبط"، التي ترسل المشغّل إلى مكان مختلف تمامًا (إقلاع جديد، أو البحث عن معدل الإصابة).

الاشتقاق الكامل: evidence/2026-09-18-divergence-is-latent.md وevidence/2026-09-18-cred-launder-verification.md (§2.3 في الأخير مسحوب في مكانه). الفحص الذاتي لتداخل شكل الكتابة مغلق دون اتصال: تعيش كلمات الشكل في شبكة fd_set على مكدّس النواة بينما يهبط الأثر الجانبي داخل الصفحة المرشوشة، لذا لا يمكن للاثنين أن يتداخلا في أي من الشكلين.

مسارات الكشف

ثلاثة مُبلّغين مستقلين. لا أحد منهم بديل عن الآخر، والمسار 1 وحده يمكنه قتل المهمة المستدعية.

يُطلق المسار 2 عند execve، وليس عند تغيّر بيانات الاعتماد، وهو مسار كود منفصل عن المسار 1. يبلّغ عبر kevent_send_to_user، لذا ما يحدث بعد ذلك قرار خفيّة في مساحة المستخدم، وليس قرار النواة.

الفحص على مسار الصورة التي يُنفَّذ عليها exec، لذا لا يكفي تحميل الحمولة الخاصة بالمحمّل عبر memfd: إذا نُفِّذ المحمّل نفسه من /data/local/tmp، فإن أول execve يبلّغ بالفعل. d_path() على memfd هو /memfd:…، لذا يجب تنفيذ المحمّل نفسه عبر memfd — V12_EXEC_MEMFD مُفعَّل الآن افتراضيًا لهذا السبب بالضبط. السلوك القديم مرئي في RUN 4:``` LT child exec /data/local/tmp/glx12 (4 args) <- execve of a /data path while uid=0 LT child memfd loaded 5014624 bytes (fd=5) <- memfd only protected the second image

root@kitploit:~
علامات قابلة للبحث لمسار 2:```
[ROOTCHECK-EXEC-INFO]:common %s result %s      with  "execve_report" / "execve_block"
%d,path@@%s                                    kevent payload fragment

لأن المسار 2 والمسار 3 يُبلّغان فقط عبر kevent، فإن "عدم وجود [ROOTCHECK-*] في سجل النواة" لا يستبعد أن يكون أيٌّ منهما قد أُطلق. هذا الاستنتاج يحتاج إلى مستقبِل مساحة المستخدم، والذي لم نتمكن من تحديد موقعه.

Watchdog — oplus_security_guard.ko

ذاكرة sys_enter المؤقتة:```asm ldrsw x8, [x1, #0x118] ; regs->syscallno mrs x9, sp_el0 ; current ldr x10, [x9, #0x780] ; cred str x8, [x9, #0xdf8] ldr w8, [x10, #4] → str x8, [x9, #0xe00] ; uid ldr w8, [x10, #0x14] → str x8, [x9, #0xe08] ; euid ldr w8, [x10, #8] → str x8, [x9, #0xe10] ; gid ldr w8, [x10, #0x18] → str x8, [x9, #0xe18] ; egid

root@kitploit:~
`sys_exit` check:```asm
ldr   x0,  [x8, #0xe00]         ; cached uid
cbz   x0, #0x48c                ; cached uid == 0 → return
adrp  x9, #0 ; ldrb w9, [x9]    ; g_boot_state
tbnz  w9, #0, #0x48c            ; is_unlocked → return
ldr   x9,  [x8, #0x780]         ; cred
ldr   w3,  [x8, #0xdf8]         ; cached syscallno
cmp   x0, w10 ; b.hi #0x468     ; uid descending → kill path
                                ; euid / gid / egid, same shape
ldr   x9,  [x8, #8]             ; addr_limit
cmp   x9,  #0x8000000001
b.lo  #0x48c                    ; addr_limit != KERNEL_DS → return
sub   w9,  w3, #0x8f            ; syscallno - 143
cmp   w9,  #0x47
b.hi  #0x4a0                    ; outside 143..214 → kill
ldrsw x12, [x10, x9, lsl #2]    ; jmp table @ .rodata+0
br    x11
0x48c: ret
0x4a0: bl oplus_root_check_succ ; printk + kevent_send_to_user
       bl oplus_root_killed     ; printk + do_exit(SIGKILL)

ملاحظة تدفق التحكم (مهمة لترتيب الاستغلال). مقارنات الحواف الأربع التنازلية تتفرع مباشرة إلى 0x468، وهو إرسال رقم استدعاء النظام — وهي لا تسقط عبر بوابة addr_limit. لا يتم الوصول إلى 0x454–0x464 إلا عندما لا ينحدر أي معرّف. لذا يتم الدخول إلى الإرسال عندما إما ينحدر بعض المعرّفات أو addr_limit == KERNEL_DS؛ وهو ليس محكومًا بـ addr_limit.

النتائج المترتبة:

  • يُطلق القاتل على استدعاء النظام الذي تغيّرت خلاله بيانات الاعتماد — ذلك الذي كان sys_enter الخاص به لا يزال يخزّن مؤقتًا uid القديم. إذا كانت المهمة بالفعل uid=0 عند دخول استدعاء نظام (0x400 cbz)، فإن الخطاف يعود ويبقى أعمى من ذلك الحين فصاعدًا.
  • لذلك يمكن النجاة من تغيير بيانات الاعتماد دون لمس الوحدة: دع مهمة أخرى تنفذ الكتابة بينما تدور الضحية في مساحة المستخدم، أو وجّه التغيير عبر أحد استدعاءات النظام الاثني عشر المعفاة. انظر delivery/外部建议评审_2026-09-18.md.

g_boot_state — بايت واحد .data..ro_after_init، يُضبط عند تهيئة الوحدة من verified_bootstate عبر strstr. is_unlocked() = LDRB + RET.

كتابات VA للوحدة تسبب خطأ (CONFIG_STRICT_MODULE_RWX=y) — استخدم الاسم المستعار physmap 0xffffff80….

حمولة التقرير: $$sys_call_number@@%d، $$set_id_flag@@%d، $$addr_limit@@%lx، $$enforce@@%d.

استدعاءات النظام المعفاة — .rodata+0، الفهارس 143–214

الإدخالات الستون المتبقية → تقرير + قتل.

لا تحجب خيطًا على sendmsg (211) أثناء تغيّر بيانات اعتماده. فهو ليس في الجدول، لذا يتم الإبلاغ عن الخيط وقتله. استدعاءات النظام الوحيدة الآمنة للحجب فيها لهذا الغرض هي الاثنا عشر أعلاه: setregid، setgid، setreuid، setuid، setresuid، setresgid، connect، getsockname، setsockopt، shutdown، readahead، brk.

تصحيح (2026-09-18). نسخة سابقة من هذا الجدول كانت تُسمّي كل إدخال أقل بواحد من رقم استدعاء النظام الحقيقي في arm64 (146 كان يُسمى setresuid؛ وهو setuid — أما setresuid فهو 147). كانت الأرقام دائمًا صحيحة؛ الأسماء فقط كانت خاطئة. تُستخرج الأسماء الآن من sys_call_table @ 0xffffffc00a13d8c0 في صورة نواة هذا الجهاز. على وجه الخصوص sendmsg (211)، وmunmap (215)، وgetsockopt (209)، و getpeername (205) ليست معفاة — حجب خيط في أي منها أثناء تغيّر بيانات اعتماده هو قتل، وليس تمريرًا. أعد التوليد باستخدام tools/gen_exempt_table.py.

كاشف رشّ الكومة — oplus_secure_harden.ko

root@kitploit:~
`entry_handler_setxattr` → `oplus_heapspray_check(7)`، `entry_handler_cpuinfo` → `(6)`، كلاهما غير مشروط.

`oplus_heapspray_check` — العدادات `heapspary_ip4` / `ip6` / `xttr` / `cpuinfo`، فرق `ktime_get_real_ts64`، 7 مواقع تقرير (`snprintf` + `printk` + `kevent_send_to_user`)، مشروطة بـ `verified_bootstate`.

### التجنب

| البدائية | |
|---|---|
| `setsockopt` optname 46 `MCAST_JOIN_SOURCE_GROUP` | غير مفلترة |
| `setsockopt` level `SOL_IPV6` (41) | غير مفلترة إذا قرأ الفلتر `level` |
| `setxattr` | يُحسب دائمًا |
| `/proc/cpuinfo` | يُحسب دائمًا |
| `socket()` / `socketpair()` | غير مرتبطة |
| `sendmsg`، `pipe`، `memfd`، `add_key`، `io_uring`، mmap | غير مرتبطة |

## الإعداد```
CONFIG_CFI_CLANG=y
CONFIG_PTR_AUTH=y
CONFIG_SHADOW_CALL_STACK=y
CONFIG_STRICT_MODULE_RWX=y
CONFIG_STATIC_USERMODEHELPER=y
CONFIG_STATIC_USERMODEHELPER_PATH=""
CONFIG_SET_FS=y
CONFIG_RANDOMIZE_BASE=y
CONFIG_RANDOMIZE_MODULE_REGION_FULL=n
CONFIG_UNMAP_KERNEL_AT_EL0=y
CONFIG_ARM64_VA_BITS=39
CONFIG_ARM64_SW_TTBR0_PAN=y
CONFIG_SLAB_FREELIST_RANDOM=y
CONFIG_SLAB_FREELIST_HARDENED=y
CONFIG_INIT_ON_ALLOC_DEFAULT_ON=y
CONFIG_RANDOM_KMALLOC_CACHES=n
CONFIG_USER_NS=n
CONFIG_NF_TABLES=n
CONFIG_SYSVIPC=n
CONFIG_ANDROID_BINDER_IPC=y
CONFIG_KASAN=y

perf_event_paranoid = -1

البناء

NDK r28c. الخيارات -O1 / API 26 / -D__ARM=1 ثابتة — فهي تحافظ على هندسة إطار المكدس الخاص بالاسترجاع (معايرة delta=0). تغيير أيٍّ منها يتطلب إعادة المعايرة على الجهاز.```bash export ANDROID_NDK_HOME=/path/to/android-ndk-r28c make # → exploit_guard ./build.sh # same, with NDK auto-detection

root@kitploit:~
يدوي:```bash
"$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android26-clang" \
  -D__ARM=1 -O1 -Wall -Wextra -pthread -Isrc/core -Isrc/devices/pfem10 \
  -o exploit_guard src/core/exploit.c

تُبنى عمليات CI عند كل دفعة (.github/workflows/build.yml، Ubuntu + NDK r28c، المخرَج exploit_guard).

الإعداد```bash

adb push exploit_guard /data/local/tmp/e adb shell chmod 755 /data/local/tmp/e adb shell /data/local/tmp/e

root@kitploit:~
## الملفات```
src/core/                 exploit.c  payload.c  payload.h  fdset_map.h
src/lib/                  KernelSnitch — kernelsnitch.h  futex_hash.h  timeutils.h  utils.h
src/devices/pfem10/       pfem10_target.h
model/                    model.c — host-side rtmutex chain-walk model
tools/                    kdis.py  kdis_ko.py  kdis_ko_reloc.py  gen_guard_disasm.py
                          gen_exempt_table.py  mod_layout.py  sct_dump.py
                          find_task_off.py  slide_resolve.py
                          test_stamp_criterion.sh   regression test for the 0x778
                                                    landing criterion (run it after
                                                    touching uid_line/gid_line)
artifacts/                guard_post_handler.s   kill chain, relocations resolved
                          guard_relocs.txt       raw .text relocation dump
                          guard_disasm.txt       guard + heap-spray detector
                          guard_exempt_table.txt 72-slot jump table, real names
evidence/                 kill.log  notes.md — device captures and their limits
Makefile  build.sh        exploit build (-O1, API 26, NDK r28c)
run.sh                    device-side run orchestration (retry across reboots)
.github/workflows/        build.yml — cloud build + artifact

الأدلة

evidence/kill.log — نصوص adb shell الحرفية لأربع عمليات تشغيل بصلاحيات الجذر: الجدول الزمني الكامل، ولحظة تحوّل uid للمهمة الهدف إلى 0، وتحميل kernelsu.ko، والحالة بعد ذلك. اقرأ كتلة الترويسة أولاً: فهي تسرد ما لا يحتويه الملف ولِمَ.

evidence/notes.md — الجانب النواة. عناوين الوحدة و أي قنوات /proc تعمل في أي حالة SELinux؛ واشتقاق g_boot_state الكامل بما في ذلك مفتاح strstr؛ وجدول الإعفاء المصحَّح؛ ووصفة الالتقاط التي ستُنتج النصف الناقص من النواة؛ وقائمة بما لا يزال مفتوحاً.

evidence/2026-09-18-bootA/ — أول تشغيل للجهاز بالتصميم الحالي، 13 إقلاعاً. عمليات إعادة التشغيل منتظمة (bootreason=reboot) ولم يُلتقط أي سطر panic على الإطلاق — لكن اقرأ ذلك كعيّنة من واحدة، لا ثلاث عشرة. من بين عمليات التشغيل الأربع التي أعادت التشغيل، حفظت اثنتان klog.host فارغاً، وحفظت واحدة سجل الإقلاع التالي، ونافذة واحدة فقط يمكن أن تُحتمل أنها تؤطّر إعادة تشغيلها الخاصة. وبالمثل، فإن probe_state وقراءة الضحية في إحدى عمليات التشغيل كلاهما فارغ (كان الجهاز قد اختفى بالفعل)، لذا لا يحمل أي معلومة حول ما إذا كانت كتابته قد استقرّت — الحقل الفارغ ليس "لا تغيير". كما يوثّق الدليل خطأ المنهجية الجدير بالمعرفة: dmesg -w عملية لا أثر لها على هذا الجهاز (toybox يُفرّغ مرة واحدة ويخرج)، لذا احتوى سجل نواة عملية تشغيل سابقة على تاريخ ما قبل الالتقاط فقط — "لا [ROOTCHECK-*]" لم يكن دليلاً على أي شيء. يحمل evidence/notes.md §6 وصفة الاستقصاء والبث إلى المضيف المصحَّحة.

evidence/2026-09-18-cred-launder-verification.md — التحقق من مقترح غسل بيانات الاعتماد مقابل تفكيك هذه الصورة نفسها (وليس مصدر 5.10 العام): المخزن المزدوج في commit_creds، وتخصيص prepare_creds وsizeof(struct cred) = 0xA8 المقيس، وخطر التباعد BUG_ON(cred != real_cred) أعلاه، والفحص الذاتي المغلق لتداخل شكل الكتابة، وتدقيق تغطية الأدلة لعمليات الإقلاع الـ13. §2.3 مسحوبة في مكانها — التباعد كامن، وليس آلية إعادة التشغيل.

evidence/2026-09-18-divergence-is-latent.md — تحقق الجولة الثانية. يأخذ commit_creds مهمته من current (0x1867a0 mrs x20, sp_el0)، ويُصفّر exit_creds كلا المؤشرين قبل put_cred، وتُظهر اللقطات الخام أن السلسلة القديمة كتبت قيمة واحدة متطابقة (0xffffff802a7e0be0) في كلا الفتحتين بينما كتبت السلسلة الجديدة صفحتين مختلفتين. إذن المعيار هو تساوي المؤشرات — "كلا الطلقتين تكتبان القيمة نفسها"، وليس "كلا الطلقتين تستقرّان".

postreboot_forensics.sh — تحقيق ما بعد إعادة التشغيل الذي لا يعتمد على المستقصي. المعيار شرط واحد: CONFIG_PSTORE_CONSOLE=y يجعل panic() يكتب ذيل الطرفية في ramoops عند kmsg_dump(KMSG_DUMP_PANIC) — قبل أي إعادة ضبط — لذا لا يهم ما إذا كان الصندوق بعدها يعيد التشغيل أو يتعلّق. يسحب /sys/fs/pstore/، ويبحث عن kernel BUG / __put_cred / cred.c، ويطبع سلسلة سبب الإقلاع (حملت مدخلات السجل لواحق reboot,shell / bootloader / reboot,edl، لذا يميّز السبب فاعلاً حيث لا تميّزه الحقبة).

⚠ لا تقرأ "bootreason=reboot نظيف" على أنه "لا panic". على QCOM تُعاد ضبط إشارة مراقب SoC عبر كتلة PMIC PON، لذا فإن panic → panic_timeout=-1 → hang → watchdog → PMIC reset → clean bootreason سلسلة متسقة ذاتياً لا يمكن تمييزها عن إعادة ضبط عتادية بناءً على الأدلة التي نملكها. إن total_17_dump_0_pmic_17 في هذا المستودع نفسه ينسب جميع عمليات إعادة التشغيل الشاذة الـ17 إلى pmic، وهو بالضبط الشكل الطبيعي للمراقب، وليس دليلاً على "ليس النواة". لا يضيّق bootreason شيئاً هنا؛ ramoops هو المعيار الوحيد.

شرطان مسبقان، وإلا كان حكم السكربت باطلاً (铁律 8 — الاستنتاج بعدم وجود إشارة يتطلب إثبات إمكانية الوصول إلى القناة أولاً):

  • حالة ثالثة مطلوبة. /sys/fs/pstore/* للمستخدم الجذر فقط، لذا تحت Enforcing يفشل كل من adb pull وcat — و"تعذّر القراءة" يُنتج المخرجات نفسها التي يُنتجها "قرأته وكان فارغاً". سكربت بحالتين يطبع "pstore فارغ ⇒ panic مدحوض" من قناة لم يفتحها قط. لذا يُصدر السكربت CHANNEL UNREACHABLE (فشل ls، أو فشلت جميع المدخلات المعروفة في القراءة بدلاً من عدم وجودها) ويبلّغ عن getenforce إلى جانب ذلك.
  • اختبار الصفر أولاً. إعادة تشغيل نظيفة عبر adb reboot يتبعها جلب فوري. إذا لم يُنتج إقلاع سليم معروف شيئاً قابلاً للقراءة، فالقناة غير مُثبتة وكل "pstore فارغ" لاحق ليس دليلاً. الترتيب مهم: ينقل الجهاز السجل ويفصل رابطه بعد الإقلاع بقليل، لذا فإن التسلسل هو إعادة التشغيل → الحصول على Permissive (W1) → تشغيل السكربت فوراً.

run_bootA.sh — تنسيق ذلك الإقلاع الواحد، بالترتيب المهم (0x778 → 0x780 بالقيمة نفسها → إصلاح محلي لبيانات الاعتماد التي ثُبّتت فعلاً → التأكيد → وعندها فقط النبش). ADB=/SER=/ BIN_LOCAL= قابلة للتجاوز؛ SAME_VALUE=1 (افتراضي) يفرض قاعدة القيمة نفسها، وLAUNDER=1 يفعّل الغسل المُبوّب، وHOLD=600 مطلوب لتسلسل القيمة نفسها.

إعادات المحاولة مقسّمة لكل مرحلة (R5/R6)، لأن المرحلتين لهما ملفّا مخاطر متعاكسان:

القيمة الافتراضية لـR5 هي ROUNDS، ولـR6 هي 1.

تشغيل الغسل الموصى به — مسار الصفحة المرشوشة الافتراضي، وليس CONTROL=1:```bash LAUNDER=1 R5=3 R6=1 HOLD=600 CHAINWAIT=6000 NODRAIN=1 WATCH=180 ./run_bootA.sh

root@kitploit:~
`CONTROL=1` قد ينتج أيضًا زوجًا متسقًا، ولكن من خلال كتابة مؤشر `init_cred`، الذي تُفسد آثاره الجانبية `init_cred+8` **عالميًا** — و"موت الإطار" هو أحد الأشياء التي تتم مراقبتها، لذا فإن نقل عطل على مستوى الجهاز إلى خلفية القياس يشوّش بالضبط القراءة التي وُجدت هذه الجولة لأخذها. مسار الصفحة المرشوشة يكلّف فقط "يجب أن تصيب كلتا الطلقتين"، وهذا ما وُجد `R5=3` من أجله. يبقى `CONTROL=1` كالزوج المتسق الوحيد *المُثبت* وكعنصر تحكم، وليس كالتكوين الموصى به.

**أي صفحة تم تثبيتها يُقرأ من السطر غير الشرطي.** يطبع `run_w7` قيمة الكتابة على سطرين، وواحد منهما فقط غير شرطي:```
L1793  W7[..] write value = private cred page 0x..   — spray path only
L1802  W7[..] write_value = 0x..                     — after the if/else, ALL paths

المشغّل كان يطابق الصياغة الأولى، لذا عند CONTROL=1 عاد الاستخراج فارغًا، فتخطّى if [ -n "$CRED" ] الإصلاح، وأبقى حدّ [ -n "$CRED" ] الخاص بالاقتران ذي القيمة نفسها عند 0 — وكانت بوابة الغسل سترفض إلى الأبد. عملية لا-فعل صامتة على كلا الجانبين، من تعبير نمطي تعرّف على واحد من موقعَي طباعة. كلاهما الآن، واستخراج write_target (وهو مدخل معيار الطابع)، يمرّان عبر wv_from/wt_from، وهما مُختبَران بواسطة اختبار الانحدار إلى جانب المعيار نفسه.

كلا التدفقين يبدآن قبل الشيء الذي يقيسانه. يعمل uid.stream من المرحلة 1؛ ويبدأ cred.stream عند النكزة (poke)، لا بعد المراقبة — فالنكزة تُطلق التابع إلى حلقة تقرير NO_EXEC الخاصة به، وهي 240 × 0.5 ث = 120 ث ثم _exit(0) (exploit.c: "LT child NO-EXEC mode done (120s)")، لذا فإن الموضع القديم عند t+~135 ث بدأ أخذ العينات بعد أن كان التابع قد ذهب بالفعل، في النافذة بالضبط التي توجد الأداة من أجلها. كما يرفض المشغّل المتابعة إذا فشل stamp_selftest()، ويطبع ORACLE INCONSISTENT بدلًا من "did not land" عندما يختلف الطابع وprobe_state.

artifacts/guard_post_handler.s — سلسلة القتل مع تعبئة الإزاحات (relocations). إن adrp x9, #0 في القوائم الأقدم هو .data..ro_after_init؛ وbl #0x4ac هو oplus_root_check_succ. أعد التوليد بـ tools/gen_guard_disasm.py بعد سحب وحدات البائع (vendor modules) من جهازك.

tools/kdis_ko.py — RELA مطابق بواسطة sh_info؛ في هذه البِنى توجد إزاحات .text في .rela.text.<func>، لذا فإن البحث بالاسم لا يعيد شيئًا.

Related

Project
JoinChang/ghostlock-oneplusreference implementation; 5.10 compact waiter
NebuSec CyberMeowfiaoriginal GhostLock research

License

GPL-3.0 — see LICENSE.

تنزيل الأداة
الجهازOPPO Find X5 Pro (PFEM10)
SoCSM8450 / Adreno 730
نظام التشغيلColorOS 16.0.3.520 (CN01)
النواة5.10.236-android12-9-o-gaf2075ad2c06
محمّل الإقلاعمقفل، أخضر
VA_BITS39 — 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=""
الحقلالإزاحة
real_cred / cred0x778 / 0x780
syscallno المخزّن مؤقتاً0xdf8
uid / euid / gid / egid المخزّنة مؤقتاً0xe00 / 0xe08 / 0xe10 / 0xe18
flags
0x0
addr_limit0x8
ttbr00x10
preempt_count0x18
الحقلالإزاحةالحقلالإزاحة
uid0x4cap_inheritable0x28
gid0x8cap_permitted0x30
suid0xccap_effective0x38
sgid0x10cap_bset0x40
euid0x14cap_ambient0x48
egid0x18
fsuid / fsgid0x1c / 0x20
Uid: 0 0 0 0
cred
status_gid
real_cred
lt_cred_ids_agree()
الهدفأوراكل الهبوط
task+0x778Uid: حقل awk الرابع = hi32(write_target) و Gid: حقل awk الثاني = low32(write_target) — الطابع أعلاه؛ يُقرأ قبل المرحلة 3
task+0x780getuid() الخاصة بالضحية
العام selinux_enforcinggetenforce
probe_state❌ ليس معيارًا. تلميح عن السلسلة في أفضل الأحوال؛ وليس أبدًا دليلًا على أن كتابة قد هبطت
$4
القيم
notes.md
$3
euid
0
hi32(write_target)
stamp_ok()
دون أي خطأ في أي مكان
run_bootA.sh
#الخطافالمُشغِّلالإجراء
1oplus_root_check_post_handler، نقطة تتبع sys_exitنزل بعض المعرّفات، أو addr_limit == KERNEL_DSoplus_root_killed → printk + do_exit(SIGKILL)؛ وoplus_root_check_succ → kevent_send_to_user
2oplus_exe_block_ret_handler، sys_exit لكن فقط لـ execve (221)يبدأ d_path(mm->exe_file) بـ /data، /data/local/tmp، /data/nativetest، /data/nativetest64oplus_RWO_root_check → printk + kevent_send_to_user (بدون do_exit)
3kretprobes الخاصة بـ oplus_secure_hardensetsockopt optname ∈ {41,42,48}، setxattr، /proc/cpuinfo، إعادة تحميل سياسة SELinuxoplus_heapspray_check → kevent_send_to_user
143 setregid144 setgid145 setreuid146 setuid
147 setresuid149 setresgid203 connect204 getsockname
208 setsockopt210 shutdown213 readahead214 brk
kretprobeالخطافاتالفلتر
socket_kretprobeip_setsockoptregs[1] ∈ {41, 42, 48}
socket_ip6_kretprobedo_ipv6_setsockoptregs[1] ∈ {41, 42}
cpuinfo_kretprobecpuinfo_open—
setxattr_kretprobesetxattr—
sepolicy_reload_kretprobespolicy_reload—
ldr w8, [x1, #8] ; regs[1]
cmp w8, #0x29 ; 41 IP_MSFILTER
b.eq #0xd58
cmp w8, #0x30 ; 48 MCAST_MSFILTER
b.eq #0xd60
cmp w8, #0x2a ; 42 MCAST_JOIN_GROUP
b.ne #0xd68 ; else → return, no call
bl oplus_heapspray_check
المرحلةأمان إعادة المحاولة
R5الخطوة 5، task+0x778آمنة — الإخفاق لا يثبّت شيئاً، ومعيار الطابع يجعل الجولة الفاشلة قابلة للقراءة، لذا فطلقة أخرى مجرد محاولة أخرى. R5=3 يرفع معدل الإصابة لكل طلقة من ~p إلى ~1−(1−p)³.
R6الخطوة 6، task+0x780غير آمنة، وغير مطلوبة — فهي لا تُطلق إلا بعد استقرار الخطوة 5، لذا تُطلق إعادة المحاولة النار على مهمة متباعدة بالفعل: فرصة أخرى لاستقرار صفحة ثانية مختلفة، دون أي فائدة، لأن استقرار واحدة يكمل الزوج. أبقِها على 1.