Black Hat Asia 2026 (Singapore, Apr 23–24) — Briefings Deep Dives

VsyncBreaker: Subverting Screen Trust via State Disruption and ONE-WAY Flooding

Talk 33 · Android platform security · analysis from the speaker's published deck

Sep 24, 2026 · conferences · 9 min read · 1772 words expert

VsyncBreaker — when what's drawn and what's touchable quietly disagree.

conferences blackhat android tapjacking aosp ui-security

Official recording, Black Hat YouTube channel · open on YouTube ↗

WeiMin Cheng, an independent AOSP (Android Open Source Project) researcher, spent years chasing a single question: is Android's layered defence against tapjacking (tricking a user into tapping something they can't see, the mobile cousin of web clickjacking, long weaponised by banking malware families like Anubis and by APT groups) actually complete, or does it just look complete? His answer, VsyncBreaker: a chain that gets around every layer Android has added since the original overlay-abuse bugs, culminating in a bug in how two of Android's own core graphics and input subsystems, SurfaceFlinger and InputDispatcher, quietly disagree about what "showing" a window even means. The core finding was reported in 2023 and only fixed in December 2025 as CVE-2025-48621. This is disclosed, patched research into Android's UI security model; I explain the conceptual weaknesses and their fixes, not the working bypass chain.

Post 33 of my Black Hat Asia 2026 series. Everything below is publicly disclosed and patched. I focus on why each defensive layer existed, what gap let research continue past it, and the underlying architectural lesson, not reproducible bypass steps.

1. What tapjacking is, and why it keeps coming back

Web clickjacking hides a legitimate control under attacker-controlled content so a click meant for one thing actually lands on another. Android's version, tapjacking, exploits the platform's ability to draw one window on top of another: if an attacker can overlay content over a legitimate app (say, a bank's PIN entry screen) while still routing the user's tap through to the real control underneath, the user believes they're interacting with the visible content while actually authorising whatever the hidden app wants. Because it requires no data exfiltration and no traditional malware payload, just control over what's drawn and how touches are routed, it has been a recurring building block in real banking-trojan and APT campaigns for years.

Two classic primitives made this practical on older Android versions: the system alert window permission (letting an app draw over everything else) combined with a flag that makes the overlay click-through (touches pass to whatever is underneath), and later a separate bug (CVE-2021-39617) that achieved a similar effect via an ordinary Activity rather than a system overlay. Google responded to each with a specific defensive mechanism, and the research this talk describes is essentially a history of testing whether each mechanism, once patched, actually closed the door or just moved it.

2. Three defensive layers, and what research found in each

DefenceWhat it's supposed to stop
Hide non-system overlay windowsLets a sensitive screen (like a banking PIN entry) request that no untrusted overlay be drawn above it at all, removing the overlay primitive entirely for that screen
ActivityRecordInputSink (ARIS)A protective input-handling layer inserted behind every Activity specifically to close the Activity-based variant (CVE-2021-39617), designed to catch touches that shouldn't be allowed through
Trusted overlay markingReserves the ability to draw an overlay that's allowed to sit above protected content to privileged, system-level processes only, denying that capability to ordinary apps

The research doesn't claim any single layer is fundamentally broken by design. Instead it describes finding, for each layer in turn, a narrower condition under which the protection's own logic didn't fully cover the case it was supposed to, and demonstrates that these narrow gaps could be chained together to produce a working end-to-end tapjacking primitive with no runtime permission and no elevated privilege required, a materially more severe finding than a bug requiring the user to have already granted a suspicious permission.

Why "no permission required" matters so much. Android's whole permission model exists to make the user (or the platform) a checkpoint before a sensitive capability is granted. A tapjacking primitive that needs zero permissions removes that checkpoint entirely, meaning ordinary app review and permission-prompt scrutiny, the defences most users and even reviewers actually rely on, never had a chance to catch it.

3. The deeper finding: two subsystems that don't agree on "visible"

After exhausting the layered mechanism-specific bugs, the research asks a more architectural question: what if the individual protection mechanisms have no logic bugs left to find at all, is the attack surface actually closed? The answer it arrives at is no, because of a subtler, structural mismatch between two of Android's core subsystems that have to cooperate constantly but were built with different concerns in mind:

  • InputDispatcher decides which window receives a touch by searching from the topmost window down, based on where a touch is allowed to land. Critically, it doesn't care whether that window is actually visible on screen, only whether it's touchable.
  • SurfaceFlinger, Android's compositor, decides what gets drawn to the screen. It doesn't care whether a given window is touchable, only whether it should be shown.

These are two independently-reasonable responsibilities, and normally they cooperate closely: SurfaceFlinger's rendering pipeline (driven by the display's VSYNC timing signal) packages up current window/frame data and hands it off to InputDispatcher so touch routing stays consistent with what's actually on screen. The research's central architectural insight is that "touchable" and "visible" are two different properties, tracked by two different subsystems, kept in sync by a data hand-off rather than by a single shared source of truth, and that a component with enough control over what it reports about its own window can influence what SurfaceFlinger tells InputDispatcher, independent of what's actually rendered. Whenever two systems must agree on a property but each derives that property independently rather than sharing one canonical implementation, the gap between them is exploitable, exactly the recurring theme in this series's parser-differential and trust-boundary talks (see also the macOS PID-domain research and Overkill).

4. A second, independent gap: how inter-process messages are delivered

Separately from the visibility/touchability mismatch, the research also examines how Android's Binder IPC mechanism, the core inter-process communication layer nearly everything on the platform relies on, delivers messages between different system components. Binder supports two delivery modes: a synchronous mode where the caller waits for a response and can observe failure, and a fire-and-forget one-way (oneway) mode intended for calls where the caller doesn't need to wait or confirm delivery. Oneway calls also carry a materially smaller maximum message size than the synchronous mode.

The research observes that not every interface between Android's UI-relevant system components consistently applies the same scrutiny to oneway-mode traffic that synchronous traffic receives, and that oversized oneway messages can fail delivery in ways that aren't uniformly and safely handled by every recipient across the platform's many interacting components (window management, transition/animation handling, and the compositor itself). This is presented as a second, independent architectural weak point, distinct from the visibility/touchability mismatch, but rhyming with it: a boundary between two cooperating components where one side's assumptions about reliable, complete data delivery don't hold under adversarial conditions.

5. Disclosure timeline

The core finding underlying this research was originally noticed in 2021, before the researcher was doing formal security work and before its significance as a vulnerability was recognised. It was formally reported in 2023, and a fix landed in the AOSP security bulletin on 20 December 2025, assigned CVE-2025-48621. That multi-year gap between an initial observation and its eventual formal disclosure and fix is a reminder that architectural mismatches like this one (two subsystems disagreeing about a shared concept) are often much harder to fully characterise, and much harder to patch cleanly, than a single memory-safety bug in one function.

6. What this means for platform and app developers

  • Layered, mechanism-specific defences accumulate coverage gaps over time. Each new protection (hide-overlay flags, input sinks, trusted-overlay marking) closes the specific bypass that prompted it, but doesn't guarantee the underlying capability (making a window untouchable-but-visible, or visible-but-untouchable, in ways a user can't perceive) is closed everywhere else it might still be reachable.
  • Where two subsystems must agree on one property, that property should have a single source of truth, not two independently-computed answers reconciled only by a best-effort data hand-off. This is the general architectural lesson, not just an Android-specific one.
  • For app developers building sensitive UI (payment confirmation, credential entry, permission grants): don't assume platform-level overlay protections are a complete guarantee. Where the platform offers an explicit "this screen must not be obscured" API, use it, and treat it as one layer of defence rather than the only one.
  • For platform security teams: a component that reports its own state (visibility, size, touchability) to another subsystem is a trust boundary. Audit whether that self-reported state can be influenced independently of the property it's meant to represent.
Is this still exploitable on current Android?

No. The core finding was patched in the December 2025 AOSP security bulletin as CVE-2025-48621. Keeping devices on a current security patch level is the mitigation.

Did this require the user to install a malicious app with special permissions?

The research's key point is that its full chain required no runtime permission grant and no elevated privilege, which is what made it more severe than earlier overlay-permission-based tapjacking variants.

Is this a bug in one specific Android component, or something broader?

The most severe part of the research is architectural: a mismatch between how two separate core subsystems (the compositor and the input dispatcher) each independently determine window state, rather than a single isolated coding mistake in one function.

Why did it take from 2021 to 2025 to get fixed?

The finding wasn't formally reported until 2023 (the researcher didn't initially recognise its security significance), and architectural mismatches between core subsystems generally take longer to characterise and fix safely than isolated memory-safety bugs, since a fix has to preserve legitimate functionality for every other caller of both subsystems.

Takeaways

  • Tapjacking defences on Android accumulated in layers (hide-overlay flags, input sinks, trusted-overlay restrictions), each closing a specific prior bypass rather than the underlying capability.
  • The research's core architectural finding: SurfaceFlinger (what's drawn) and InputDispatcher (what's touchable) independently track different properties of the same window, reconciled only by a data hand-off rather than a shared source of truth.
  • A second, independent gap involves inconsistent handling of Android's Binder one-way IPC mode across cooperating system components.
  • The full chain required no runtime permission and no elevated privilege, a materially more severe bar than earlier overlay-permission-based tapjacking.
  • Reported in 2023, fixed December 2025 as CVE-2025-48621. Keep devices patched; sensitive-screen developers should use platform obscured-window protections as one layer, not the only one.

References & further reading

← prev: JVM Memory Shell Search next: N-Day Chromium Defence →
© cvam — written in plaintext, served warm