Bypassing Intent Destination Checks, LaunchAnyWhere Privilege Escalation
Qidan He
DEF CON 33 · Day 2 · Main Stage
Overview
LaunchAnyWhere is one of Android's most consequential historical vulnerability classes: an unprivileged application leveraging a privileged bridge to invoke protected or unexported activities on its b

Key moments
- 0:26 So a brief introduction about myself.
- 6:42 So uh what's the non launch anywhere?
- 14:07 So uh recall that in the pos mismatch scenarios that we want to make the account...
- 24:58 So given this time frame,
- 30:42 So in the lower part uh the right part of the left part of the slide we c
- 33:30 this restriction because installing update to an application will not clear the...
- 40:18 and here we use bad resolve to start the ami user chooser activity.
Dead Made Alive Again: Bypassing Intent Destination Checks and Reintroducing LaunchAnyWhere Privilege Escalation
Speakers: Qidan He
Conference: DEF CON 33 (August 2025)
YouTube: https://www.youtube.com/watch?v=e7UnYV-m23c
Slides: https://media.defcon.org/DEF%20CON%2033/DEF%20CON%2033%20presentations/Qidan%20He%20-%20Dead%20Made%20Alive%20Again%20Bypassing%20Intent%20Destination%20Checks%20and%20Reintroducing%20LaunchAnyWhere%20Privilege%20Escalation.pdf
Overview
LaunchAnyWhere is one of Android's most consequential historical vulnerability classes: an unprivileged application leveraging a privileged bridge to invoke protected or unexported activities on its behalf, effectively borrowing system-level permissions. Google and device vendors spent years patching the original vector with intent destination checks that verify the target of any outgoing intent against the caller's signature. Qidan He of the Dawn Security Lab demonstrates at DEF CON 33 that these checks remain insufficient. His BadResolve technique exploits a race condition between intent resolution and activity launch in Android's intent dispatch pipeline, enabling a zero-permission application to achieve LaunchAnyWhere against all Android versions including Android 16. Two CVEs were assigned and patched by Google. The talk also presents an LLM agent pipeline for systematically identifying BadResolve-vulnerable code patterns across the Android Open Source Project (AOSP) and vendor-specific closed-source codebases.
Background
▶ Watch: So a brief introduction about myself. (0:26)
Android Intents and the IPC Model
Intents are Android's core inter-process communication primitive — typed messages that carry embedded data between application components. The five major component types (activities, services, broadcast receivers, content providers, and fragment managers) all accept intents. An intent is itself Parcelable, meaning intents can be nested: an intent can contain a bundle that contains another intent, recursively, without limit.
Intent resolution takes two forms. Explicit intents name their target component directly via setComponent() — the system routes them without a resolution process. Implicit intents carry an action string and optional category, and the system resolves them to a concrete component via resolveIntent(), which considers component intent filters (declared in AndroidManifest.xml), the calling application's permissions, and user preferences. The security restriction is that normal applications cannot directly start protected or unexported activities belonging to other applications.
The Original LaunchAnyWhere (2014)
The original LaunchAnyWhere pattern exploited privileged "bridge" applications — most commonly the Settings app, which runs with the system UID and holds permissions to invoke arbitrary activities. The attack flow:
- Attacker triggers account creation via the
AccountManagerAPI. - The system server calls back to the attacker's application.
- The attacker's application returns a bundle containing a nested intent pointing to a privileged activity (e.g., install package, make phone call, execute arbitrary commands).
- Settings processes the bundle and calls
startActivity()on the nested intent without verifying the target. - The protected activity launches with Settings as the caller, inheriting its permissions.
This was weaponized against billions of devices. Google's Round 1 fix added checkIntent() to AccountManagerService: before launching any nested intent, the system calls resolveActivity() on it and verifies that the resolved target has the same signing certificate as the calling (attacker) application. If not, the launch is blocked.
Round 2 Bypass: Parcelable Mismatch
Attackers — and researchers including He — subsequently discovered that the check could be bypassed via Parcelable serialization mismatches. Certain Android Parcelable implementations had different behavior on writeToParcel() versus readFromParcel() — for example, a CertInfoParcelable that wrote a length field with > 0 semantics but read with == 0 semantics. This caused the deserialized object to differ depending on how many serialization-deserialization cycles it underwent.
The exploit structure: the system server deserializes the attacker's bundle (cycle 1) and sees an intent pointing to a Settings component (check passes). Settings deserializes the same data (cycle 2) and receives an intent pointing to the privileged target. The check and the launch see different intents. More than 100 such Parcelable mismatches were identified across Android's codebase, and this class was actively exploited in the wild against billions of devices before Google mitigated it.
Key Findings
▶ Watch: So uh recall that in the pos mismatch scenarios that we want to make the acco... (14:07)
The BadResolve technique introduces a new bypass mechanism that does not depend on Parcelable mismatches. Instead, it exploits the temporal gap between checkIntent() and startActivity() in the same code path, combined with Android's ability for any application to enable or disable its own components at runtime.
Two CVEs were assigned for specific vulnerable code patterns in AccountManagerService and related AOSP components. All reported vulnerabilities affect Android versions up to and including Android 16 (Android 16 Beta 3 was confirmed vulnerable during preparation). Google confirmed and patched the issues prior to the talk.
Technical Deep Dive
▶ Watch: So given this time frame, (24:58)
BadResolve: The Core Primitive
The foundation of the attack is a standard Android API: ActivityManager.setComponentEnabledSetting(). Any application can call this method to enable or disable its own components at runtime. When a component is disabled, it vanishes from the intent resolution process — it will not appear in the results of resolveActivity() or queryIntentActivities().
The exploit proceeds as follows:
- The attacker's application registers an activity with an intent filter that matches the same action and category as a target privileged activity (e.g.,
com.android.phone/.CallPrivileged). - When presented with a disambiguation chooser, the user selects "Always" for the attacker's activity. This persists as a preferred resolution for that intent, surviving app updates and reinstalls.
- The attacker triggers the vulnerable AccountManagerService code path, causing the system to call
checkIntent()on a crafted implicit intent. checkIntent()callsresolveActivity(), which returns the attacker's preferred activity (same signing certificate — check passes).- In the time window between
checkIntent()returning andstartActivity()executing, the attacker callssetComponentEnabledSetting()to disable their own activity. startActivity()callsresolveActivity()again. The attacker's component is now disabled. With it gone, the resolution returns only the privileged target activity. The launch proceeds with Settings as the caller, granting it the caller's permissions.
Resolution Priority Mechanics
Android's multi-target resolution algorithm matters here:
- Disabled components are excluded first.
- Of remaining candidates, the highest declared priority wins (platform/system apps typically declare positive priority; normal apps are capped at 0).
- If a preferred selection exists for the calling context, it wins unconditionally.
- If multiple candidates remain with equal priority, a chooser dialog is shown.
The preferred selection is the linchpin. It is set once via the chooser "Always" option and persists across the target app's lifecycle. The attacker's app sets it legitimately, then races to revoke its own visibility.
Timing Analysis
Profiling with Perfetto places the window between resolveIntent() (in checkIntent()) and the final startActivity() at approximately 1 millisecond on high-end devices (Pixel 7/8 Pro). Low-end devices (Galaxy S21) extend this to approximately 1 second due to slower I/O and weaker CPUs. The race can be retried on failure.
Extending the Window
For high-end devices, 1 ms is a difficult race. He found two mechanisms to reliably extend it:
Malformed Manifest Categories: Android's manifest parser imposes no practical limit on the number of categories declared in an intent filter. Inserting 10,000 to 40,000 categories in a single intent filter does not fail package verification or XML parsing limits. When DEBUG_LOG_RESOLUTION logging is enabled in the PackageManager, each category match triggers a log call — a linear-time operation. With 30,000 categories, the resolution window extends to 50–400 milliseconds. With ~10,000 categories, it reaches ~100ms without crashing.
PackageManager Snapshot Semantics: PackageManagerService creates a frozen snapshot of the component resolver state at the start of resolveIntentInternal(). This snapshot is not updated during the call, even if components are enabled or disabled while resolution is in progress. This means the attacker can disable their component after the snapshot is created but while resolution is still running, and the snapshot will not reflect the change — the check still sees the attacker's component, but by the time startActivity() consults the live resolver, the component is gone.
Exploit Chains
He identified several "gadget" activities in AOSP and vendor code that could be used as bridges once BadResolve bypasses the initial check:
- SearchTrampolineActivity (AOSP): Retrieves a URI from the incoming intent, calls
Intent.parseUri()to reconstruct an intent, and launches it. Because the caller identity is now the Settings process, the caller package check is satisfied for any target. - MIUIChooser (Xiaomi): Verifies the caller's permission to reach the target, but since the caller is Settings, the check passes.
- HWChooser (Honor/Huawei): Verifies caller permission plus target exported status. A chain through the AOSP Chooser (which has priority 500 vs. custom choosers with no priority) leads to a second check where the effective caller is again Settings.
- TruthActivity: Parses a Parcelable from the intent and calls
startActivity()on it.
LLM Agent Pipeline for Vulnerability Discovery
Given the size of AOSP and vendor-specific codebases (OEM skins like MIUI, OneUI, and HyperOS add millions of lines of closed-source Java), manual auditing is impractical. He built a three-agent LLM system:
- Manager Agent: Uses MCP to fetch AOSP source and vendor code via GitHub tooling. Searches for patterns matching
resolveActivity*andqueryIntent*call sites. - Auditor Agent: Analyzes each candidate call site for BadResolve applicability: is the result used in a
startActivity()call? Is the intent implicit (nosetComponentor explicit target)? Does the code check the resolved component before launching? - Reviewer Agent: Performs false positive and false negative filtering on the Auditor's output.
The pipeline handles hallucinations and missed Android security context imperfectly, but substantially reduces the search space for human review. Four similar vulnerable patterns were found in AOSP beyond the primary AccountManagerService bug; two resulted in additional CVE assignments.
Demo / PoC
▶ Watch: this restriction because installing update to an application will not clear t... (33:30)
The demos confirmed BadResolve against live devices:
- Modify Lock Password (Android device): A zero-permission application triggers the AccountManager flow, races the component disable, and invokes the Settings password-change fragment without user interaction — a protected Settings action that should require the caller to hold system privileges.
- Android 16 Beta 3 — Phone Call Invocation: The privileged
CallPrivilegedactivity was launched from a zero-permission app on an Android 16 Beta 3 device. (At Google's request, the demo scripted a call to a non-emergency number rather than live execution.)
Both demos required no prior device compromise and ran from a freshly installed zero-permission application.
Defensive Implications
▶ Watch: and here we use bad resolve to start the ami user chooser activity. (40:18)
For Android developers and OEM engineers: Any privileged component that processes an implicit intent received from an external source and then calls startActivity() on that intent (or on a derived intent) is a potential BadResolve gadget. The correct fix is to make the intent explicit before launch — set the target component or package via setComponent() or setPackage() after resolution, and launch the explicit form. Do not allow implicit resolution to be the final arbiter of what gets launched.
For AOSP and vendor code auditors: The LLM agent search pattern — find all resolveActivity() / queryIntentActivities() call sites where the result feeds into startActivity() — is a tractable starting audit query. Focus on privileged processes (system UID, settings, account management) that accept incoming intents from untrusted callers.
For security researchers: The preferred-activity race window is a general exploitation primitive for any Android intent dispatch path that separates resolution from launch. The timing window extends significantly on low-end devices, making the attack more reliable on the large installed base of budget Android hardware.
For end users: Keep Android devices updated. Google patched the specific AccountManagerService bugs identified in this research. CVE-numbered patches were available prior to the DEF CON presentation.
Key Takeaways
- LaunchAnyWhere is not dead. BadResolve bypasses intent destination checks on all Android versions, including Android 16.
- The attack exploits a race between
checkIntent()andstartActivity(): the attacker's component is the preferred resolution during the check, then disabled before the launch. - Timing windows can be extended to 50–400 ms by inserting thousands of categories in a manifest intent filter, or by exploiting PackageManager snapshot semantics.
- Zero-permission applications can achieve system-level activity invocation via gadgets like SearchTrampolineActivity.
- LLM agent pipelines can partially automate the search for BadResolve-vulnerable patterns in large codebases but require human review to manage false positives and false negatives.
- Google confirmed and patched the bugs. Two CVEs were assigned. Auditing for similar patterns in vendor-specific OEM code remains an open task.
About the Speaker
Qidan He is Center Director and Chief Security Researcher at gd.com, where he leads the Dawn Security Lab. The lab focuses on anti-fraud, client security, and security research. He is a former winner of Pwn2Own and Mobile Pwn2Own competitions and received the 2022 Pwnie Award for Best Privilege Escalation. He has spoken at Black Hat, DEF CON, Hack in the Box, and other international security conferences.
Reviews
Dr. Zero (Offensive Security Researcher) — STRONG ACCEPT
Qidan He's BadResolve technique resurrects LaunchAnyWhere privilege escalation on all Android versions including Android 16 by exploiting a race condition between intent resolution and launch, with an LLM-assisted pipeline to find additional gadgets.
Heather Calloway (CISO) — STRONG ACCEPT
Qidan He demonstrates BadResolve, a race condition between intent resolution and activity launch in Android's intent dispatch pipeline that enables a zero-permission application to invoke protected system activities—resurrecting the LaunchAnyWhere vulnerability class on all Android versions including Android 16. Two CVEs were assigned and patched. The talk also presents an LLM agent pipeline for finding similar vulnerable patterns in AOSP and OEM codebases at scale.