Android Intents and the IPC Foundation
Android uses intents as its core inter-process communication mechanism. When activity A calls startActivity() with an intent, the Activity Manager Service routes it and triggers the target’s onCreate callback. App components declare intent filters in their manifests listing accepted actions and categories. Implicit intents go through a resolution process that matches these filters, then applies permission checks and exported-state restrictions. A normal third-party app cannot start an unexported or protected activity belonging to another app. This permission boundary is exactly what the BadResolve attack chain bypasses.
LaunchAnyWhere: How System Apps Become Attack Bridges
LaunchAnyWhere vulnerabilities exploit a specific pattern: a system-privileged app (running as system UID, like Settings) accepts a nested intent from an attacker app and calls startActivity() on it without verification. The 2014 account management bug is the canonical example. An attacker registers an account authenticator, provides a callback bundle to the system server, and Settings blindly extracts an embedded intent and starts it. Because Settings holds system privilege, it can launch any activity, including protected ones the attacker’s own app could never reach directly.
The Check-Then-Start Race: BadResolve’s Core Idea
The fix added checkIntent() inside AccountManagerService: resolve the implicit intent, then verify the resolved target shares the caller’s app signature. He saw a different angle. The check uses one resolve call; Settings’ startActivity() triggers another. Both calls read from a snapshot of the package manager’s component mappings, but they are separate snapshots taken at separate times. If the resolution result changed between the two calls, a benign target could pass the check while a privileged target handles the actual launch.
Extending the Race Window from 1ms to 400ms
Perfetto traces showed only a 1-millisecond window between the two resolution calls. The package manager resolves against a frozen snapshot, so disabling a component mid-resolution has no effect on that snapshot’s result. He needed to slow down the resolution itself. Declaring 30,000 categories in a single intent filter bloated the component resolver’s linear search. Combined with FLAG_DEBUG_LOG_RESOLUTION, this stretched resolution time to 50-400 milliseconds: 200-300ms on Pixel 7 Pro and Pixel 8 Pro, up to 1 second on a Galaxy S21.
Attack Flow, Preferred Activities, and Vendor Gadget Chains
The full exploit: mark an attacker activity as preferred by tricking the user into clicking “Always” in the chooser dialog. Trigger addAccount(). AccountManagerService resolves the implicit intent, finds the preferred attacker activity, and passes the check. A background thread then disables that activity mid-resolution of the second check. Settings calls startActivity(), finds only one candidate remaining, and launches the privileged protected activity. On Xiaomi, He chained through the MIUI chooser activity as a gadget, which passed caller checks because Settings appeared as the caller.
AI-Assisted Discovery of BadResolve Variants
Any code that resolves an implicit intent, checks the result, then calls startActivity() again has the same flaw. He found four additional vulnerable code paths in AOSP and reported them to Google, with two CVEs assigned. To scale the search, he built a three-agent LLM pipeline: a manager agent fetches candidate code from AOSP via MCP; an auditor agent checks for bad-resolve patterns using few-shot examples; a reviewer agent filters false positives. The system produced few false positives but had false negatives around tricky Android permission semantics.
Notable Quotes
This is very ideal situation for us. Qidan “flanker_hqd” He · ▶ 32:45
this is the basic idea of our talkto. Qidan “flanker_hqd” He · ▶ 17:35
and there are two CVS assigned already. Qidan “flanker_hqd” He · ▶ 38:01
Key Takeaways
- BadResolve exploits the time gap between intent resolution checks in Android’s AccountManagerService and Settings.
- Declaring 30,000 intent filter categories in a manifest extends the race window from 1ms to 400ms.
- The vulnerability class affects any code that resolves an implicit intent before calling
startActivity()separately. - Vendor chooser activities on Xiaomi and Huawei devices serve as privilege-escalation gadgets when chained with BadResolve.
- A three-agent LLM pipeline found four additional AOSP code paths vulnerable to the same pattern.
About the Speaker(s)
Qidan He (a.k.a Edward Flanker, CISSP) is the Director and Chief Security Researcher at Dawn Security Lab, JD.com, where his team works on anti-fraud, client security, and security research. He is a multiple Pwn2Own champion and won the 2022 Pwnie Award for best privilege escalation. He has spoken at Black Hat, DEF CON, RECON, CanSecWest, MOSEC, HITB, and PoC, and serves as a committee member and judge for GeekPwn and GeekCon.