accessibility-services-abuse.md (21558B)
1 --- 2 title: "Android Accessibility Service Abuse" 3 section: "Mobile" 4 sectionSlug: "mobile-pentesting" 5 sourcePath: "src/mobile-pentesting/android-app-pentesting/accessibility-services-abuse.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/mobile-pentesting/android-app-pentesting/accessibility-services-abuse.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Android Accessibility Service Abuse 14 15 ## Overview 16 17 `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_.<sup>[[4]](#references)</sup> 18 19 Modern Android banking Trojans and Remote-Access-Trojans (RATs) such as **PlayPraetor, SpyNote, BrasDex, SOVA, ToxicPanda** and many others follow the same recipe: 20 21 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). 22 2. Leverage the service to 23 * capture every UI event & text that appears on screen, 24 * inject synthetic gestures (`dispatchGesture`) and global actions (`performGlobalAction`) to automate any task the operator desires, 25 * draw full-screen overlays on top of legitimate apps using the **TYPE_ACCESSIBILITY_OVERLAY** window type (no `SYSTEM_ALERT_WINDOW` prompt!), 26 * silently grant additional runtime permissions by clicking on the system dialogs on the victim’s behalf. 27 3. Exfiltrate data or perform **On-Device-Fraud (ODF)** in real-time while the user is looking at a perfectly normal screen. 28 29 --- 30 31 ### Packed Accessibility droppers 32 33 ClayRat v3.0.8 couples its Accessibility RAT with a staged payload hidden under `assets/`. At runtime the host APK:<sup>[[1]](#references)</sup> 34 35 1. Streams the encrypted blob from `assets/*.dat`. 36 2. Decrypts it with a hard-coded AES/CBC key + IV embedded inside the Java/Kotlin loader. 37 3. Writes the plaintext DEX to the app's private dir and loads it via `DexClassLoader`, exposing the actual spyware classes only in memory. 38 39 ```java 40 byte[] blob = readAsset("payload.enc"); 41 Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding"); 42 SecretKeySpec key = new SecretKeySpec(hex("A1..."), "AES"); 43 c.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); 44 byte[] dex = c.doFinal(blob); 45 DexClassLoader cl = new DexClassLoader(writeTemp(dex), getCodeCacheDir().getPath(), null, getClassLoader()); 46 cl.loadClass("com.clayrat.Core").newInstance(); 47 ``` 48 49 This packing pattern (ATT&CK T1406.002) keeps the Accessibility module off-disk until the dropper executes, defeating static signature scans and Play Protect until the user already granted the dangerous permissions. 50 51 Zimperium publishes a companion ClayRat v3 indicator set that defenders can use to correlate the analyzed samples and infrastructure.<sup>[[2]](#references)</sup> 52 53 --- 54 55 ## Requesting the permission 56 57 ```xml 58 <!-- AndroidManifest.xml --> 59 <service 60 android:name="com.evil.rat.EvilService" 61 android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" 62 android:exported="false"> 63 64 <intent-filter> 65 <action android:name="android.accessibilityservice.AccessibilityService" /> 66 </intent-filter> 67 68 <meta-data android:name="android.accessibilityservice" 69 android:resource="@xml/evil_accessibility_config"/> 70 </service> 71 ``` 72 73 The companion XML defines how the fake dialog will look like: 74 75 ```xml 76 <?xml version="1.0" encoding="utf-8"?> 77 <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" 78 android:description="@string/service_description" 79 android:accessibilityEventTypes="typeAllMask" 80 android:accessibilityFeedbackType="feedbackGeneric" 81 android:notificationTimeout="200" 82 android:canPerformGestures="true" 83 android:canRetrieveWindowContent="true"/> 84 ``` 85 86 --- 87 88 ## Remote UI automation primitives 89 90 <details> 91 <summary>Accessibility service automation skeleton</summary> 92 93 ```java 94 public class EvilService extends AccessibilityService { 95 @Override 96 public void onAccessibilityEvent(AccessibilityEvent event) { 97 // harvest text or detect foreground app change 98 } 99 100 // Simulate HOME / BACK / RECENTS … 101 private void navHome() { performGlobalAction(GLOBAL_ACTION_HOME); } 102 private void navBack() { performGlobalAction(GLOBAL_ACTION_BACK); } 103 private void openRecents() { performGlobalAction(GLOBAL_ACTION_RECENTS); } 104 105 // Generic tap / swipe 106 public void tap(float x, float y) { 107 Path p = new Path(); p.moveTo(x, y); 108 GestureDescription.StrokeDescription s = new GestureDescription.StrokeDescription(p, 0, 50); 109 dispatchGesture(new GestureDescription.Builder().addStroke(s).build(), null, null); 110 } 111 } 112 ``` 113 114 </details> 115 116 With only these two APIs an attacker can: 117 * Unlock the screen, open the banking app, navigate its UI tree and submit a transfer form. 118 * Accept every permission dialog that pops up. 119 * Install/update extra APKs via the Play Store intent. 120 121 --- 122 123 ## Abuse patterns 124 125 ### 1. Overlay Phishing (Credential Harvesting) 126 A transparent or opaque `WebView` is added to the window manager: 127 128 ```java 129 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 130 MATCH_PARENT, MATCH_PARENT, 131 TYPE_ACCESSIBILITY_OVERLAY, // ⬅ bypasses SYSTEM_ALERT_WINDOW 132 FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL, // touches still reach the real app 133 PixelFormat.TRANSLUCENT); 134 wm.addView(phishingView, lp); 135 ``` 136 137 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. 138 139 > Detailed example: the *Accessibility Overlay Phishing* section inside the Tapjacking page. 140 141 ClayRat exposes this capability with the `show_block_screen` / `hide_block_screen` commands that download overlay templates from the C2. Operators can switch layouts on the fly to:<sup>[[1]](#references)</sup> 142 143 - **Black out** the panel so the victim assumes the handset is off or frozen while automated gestures disable Play Protect or grant more permissions. 144 - Display fake **system update / battery optimization** panels that justify why the device is “busy” while background automation continues. 145 - Show an **interactive PIN pad** overlay that mirrors the system lock screen—the malware captures every digit and streams it to the operator as soon as a 4‑digit code is entered. 146 147 Because TYPE_ACCESSIBILITY_OVERLAY windows never raise the `SYSTEM_ALERT_WINDOW` permission prompt, the victim only sees the decoy UI while the RAT keeps interacting with the real apps underneath. 148 149 ### 2. On-Device Fraud automation 150 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.<sup>[[3]](#references)</sup> 151 152 ### 3. Screen streaming & monitoring 153 ClayRat upgrades the usual MediaProjection trick into a remote desktop stack:<sup>[[1]](#references)</sup> 154 155 1. `turbo_screen` triggers the MediaProjection consent dialog; the Accessibility service clicks “Start now” so the victim never intervenes. 156 2. With the resulting `MediaProjection` token it creates a `VirtualDisplay` backed by an `ImageReader`, keeps a `ForegroundService` alive, and drains frames on worker threads. 157 3. Frames are JPEG/PNG encoded according to the operator-supplied `set_quality` parameter (defaults to `60` when missing) and shipped over an HTTP→WebSocket upgrade advertising the custom `ClayRemoteDesktop` user-agent. 158 4. `start_desktop` / `stop_desktop` manage the capture threads while `screen_tap`, `screen_swipe`, `input_text`, `press_home`, `press_back` and `press_recents` replay gestures against the live framebuffer. 159 160 The result is a VNC-like feed delivered entirely through sanctioned APIs—no root or kernel exploits—yet it hands the attacker live situational awareness with millisecond latency. 161 162 ### 4. Lock-screen credential theft & auto-unlock 163 ClayRat subscribes to `TYPE_WINDOW_CONTENT_CHANGED` / `TYPE_VIEW_TEXT_CHANGED` events emitted by `com.android.systemui` (`Keyguard`). It reconstructs whatever guard is active:<sup>[[1]](#references)</sup> 164 165 - **PIN** – watches keypad button presses until the locker reports completion. 166 - **Password** – concatenates strings seen in the focused password field for each `AccessibilityEvent`. 167 - **Pattern** – records the ordered node indices inferred from gesture coordinates across the 3×3 grid. 168 169 Secrets plus metadata (lock type + timestamp) are serialized into `SharedPreferences` under `lock_password_storage`. When the operator pushes `auto_unlock`, the service wakes the device with `unlock_device` / `screen_on`, replays the stored digits or gestures through `dispatchGesture`, and silently bypasses the keyguard so subsequent ODF workflows can continue. 170 171 ### 5. Notification phishing & harvesting 172 A companion Notification Listener turns the shade into a phishing surface:<sup>[[1]](#references)</sup> 173 174 - `get_push_notifications` dumps every currently visible notification, including OTP / MFA messages. 175 - The `notifications` command toggles a `notifications_enabled` flag so each future `onNotificationPosted()` payload is streamed to the C2 in real time. 176 - `send_push_notification` lets operators craft fake, interactive notifications that impersonate banking or chat apps; any text the victim submits is parsed as credentials and exfiltrated immediately. 177 178 Because Accessibility can open/dismiss the notification shade programmatically, this method harvests secrets without touching the targeted apps. 179 180 ### 6. Telephony & SMS command channel 181 After coercing the user into setting the RAT as the default SMS app, the following commands provide complete modem control:<sup>[[1]](#references)</sup> 182 183 - `send_sms` and `retransmishion` send arbitrary or replayed messages to attacker-controlled numbers. 184 - `messsms` iterates over the entire contacts database to spam phishing links for worm-like propagation. 185 - `make_call` initiates voice calls that support social-engineering workflows. 186 - `get_sms_list` / `get_sms` and `get_call_log` / `get_calls` dump inboxes and call history so MFA codes or call metadata can be abused instantly. 187 188 Combined with Accessibility-driven UI navigation, ClayRat can receive an OTP via notification/SMS and immediately input it inside the target banking or enterprise app. 189 190 ### 7. Discovery, collection & proxying 191 Additional ClayRat commands map the environment and keep C2 resilient:<sup>[[1]](#references)</sup> 192 193 - `get_apps` / `get_apps_list` enumerate installed packages (ATT&CK T1418). 194 - `get_device_info` reports model, OS version and battery state (T1426). 195 - `get_cam` / `get_camera` capture front-camera stills, while `get_keylogger_data` serializes lock PINs plus passwords, view descriptions and hints scraped from sensitive fields. 196 - `get_proxy_data` fetches a proxy WebSocket URL, appends the unique device ID and spins a job that tunnels HTTP/HTTPS over the same bidirectional channel (T1481.002 / T1646). 197 198 ### 8. Region-scoped keypad interception and gesture replay 199 200 Instead of drawing a fake banking form, an Accessibility RAT can locate the numeric-key nodes in the **real** application, union their `getBoundsInScreen()` rectangles, and place a touch-consuming overlay only over that keypad. For every victim tap, it records the coordinates and nearby node, temporarily stops intercepting touches, replays a tap at the same coordinates with `dispatchGesture()`, and restores the overlay. The legitimate application therefore receives the expected input and continues normally while the RAT reconstructs the PIN from the intercepted sequence.<sup>[[7]](#references)</sup> 201 202 This differs from a pass-through overlay: the malicious window receives the original event and Accessibility injects a second event only after interception is disabled. During reverse engineering, look for `AccessibilityNodeInfo` bounds aggregation next to a small overlay window, coordinate hit-testing, rapid changes to touchability/visibility, and `dispatchGesture()` callbacks that re-arm the overlay.<sup>[[7]](#references)</sup> 203 204 ### 9. Semantic Accessibility keylogging 205 206 Raw `TYPE_VIEW_TEXT_CHANGED`, `TYPE_VIEW_CLICKED`, and window-content events become more useful when records are enriched with the source package, timestamp, target-list membership, and whether text came from Autofill or manual entry. The collector can then classify values such as lock-screen input, 4–6 digit OTPs, passwords, email logins, long messages, and possible wallet recovery phrases before upload, allowing operators to prioritize authentication material rather than reviewing an undifferentiated event stream.<sup>[[7]](#references)</sup> 207 208 ### 10. Peer-assisted store-and-forward exfiltration 209 210 An Accessibility RAT can keep collected files and command results in a durable queue, encrypt each package with AES-GCM, and relay it through nearby infected phones when direct C2 access is unavailable. One observed design tries an established Wi-Fi Direct peer first, then queries classic Bluetooth RFCOMM or BLE GATT peers for Internet reachability. A reachable peer accepts the encrypted package and forwards it toward C2; unroutable items remain queued for retry, and a hop counter (four hops by default in the observed implementation) bounds multi-device forwarding.<sup>[[7]](#references)</sup> 211 212 Static and dynamic triage should correlate queue persistence and `AES/GCM/NoPadding` with `WifiP2pManager`, RFCOMM `BluetoothSocket`, `BluetoothGatt`, reachability probes, and decrementing hop/TTL fields. Containment must isolate local radios or physically separate suspected devices as well as blocking Internet access, because a nearby compromised peer may provide the egress path.<sup>[[7]](#references)</sup> 213 214 --- 215 216 ## PlayPraetor – command & control workflow 217 218 1. **HTTP(S) heartbeat** – iterate over a hard-coded list until one domain answers `POST /app/searchPackageName` with the active C2. 219 2. **WebSocket (port 8282)** – bidirectional JSON commands: 220 * `update` – push new conf/APKs 221 * `alert_arr` – configure overlay templates 222 * `report_list` – send list of targeted package names 223 * `heartbeat_web` – keep-alive 224 3. **RTMP (port 1935)** – live screen/video streaming. 225 4. **REST exfiltration** – 226 * `/app/saveDevice` (fingerprint) 227 * `/app/saveContacts` | `/app/saveSms` | `/app/uploadImageBase64` 228 * `/app/saveCardPwd` (bank creds) 229 230 The **AccessibilityService** is the local engine that turns those cloud commands into physical interactions.<sup>[[3]](#references)</sup> 231 232 --- 233 234 ## Detecting malicious accessibility services 235 236 * `adb shell settings get secure enabled_accessibility_services` 237 * Settings → Accessibility → *Downloaded services* – look for apps that are **not** from Google Play. 238 * MDM / EMM solutions can enforce `ACCESSIBILITY_ENFORCEMENT_DEFAULT_DENY` (Android 13+) to block sideloaded services. 239 * Analyse running services: 240 ```bash 241 adb shell dumpsys accessibility | grep "Accessibility Service" 242 ``` 243 244 --- 245 246 ## Hardening recommendations for app developers 247 248 * Mark sensitive views with `android:accessibilityDataSensitive="accessibilityDataPrivateYes"` (API 34+). 249 * Combine `setFilterTouchesWhenObscured(true)` with `FLAG_SECURE` to prevent tap/overlay hijacking. 250 * Detect overlays by polling `WindowManager.getDefaultDisplay().getFlags()` or the `ViewRootImpl` API. 251 * Refuse to operate when `Settings.canDrawOverlays()` **or** a non-trusted Accessibility service is active. 252 253 --- 254 255 ## ATS automation cheat-sheet (Accessibility-driven) 256 Malware can fully automate a bank app with only Accessibility APIs. Generic primitives: 257 258 <details> 259 <summary>Helper methods for ATS automation</summary> 260 261 ```java 262 // Helpers inside your AccessibilityService 263 private List<AccessibilityNodeInfo> byText(String t){ 264 AccessibilityNodeInfo r = getRootInActiveWindow(); 265 return r == null ? Collections.emptyList() : r.findAccessibilityNodeInfosByText(t); 266 } 267 private boolean clickText(String t){ 268 for (AccessibilityNodeInfo n: byText(t)){ 269 if (n.isClickable()) return n.performAction(ACTION_CLICK); 270 AccessibilityNodeInfo p = n.getParent(); 271 if (p != null) return p.performAction(ACTION_CLICK); 272 } 273 return false; 274 } 275 private void inputText(AccessibilityNodeInfo field, String text){ 276 Bundle b = new Bundle(); b.putCharSequence(ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text); 277 field.performAction(ACTION_SET_TEXT, b); 278 } 279 private void tap(float x, float y){ 280 Path p = new Path(); p.moveTo(x,y); 281 dispatchGesture(new GestureDescription.Builder() 282 .addStroke(new GestureDescription.StrokeDescription(p,0,40)).build(), null, null); 283 } 284 ``` 285 286 </details> 287 288 Example flow (Czech → English labels):<sup>[[5]](#references)</sup> 289 - "Nová platba" (New payment) → click 290 - "Zadat platbu" (Enter payment) → click 291 - "Nový příjemce" (New recipient) → click 292 - "Domácí číslo účtu" (Domestic account number) → focus and `ACTION_SET_TEXT` 293 - "Další" (Next) → click → … "Zaplatit" (Pay) → click → enter PIN 294 295 Fallback: hard-coded coordinates with `dispatchGesture` when text lookup fails due to custom widgets. 296 297 Also seen: pre-steps to `check_limit` and `limit` by navigating to limits UI and increasing daily limits before transfer.<sup>[[5]](#references)</sup> 298 299 ## Text-based pseudo-screen streaming 300 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. 301 302 ```java 303 private void dumpTree(AccessibilityNodeInfo n, String indent, StringBuilder sb){ 304 if (n==null) return; 305 Rect b = new Rect(); n.getBoundsInScreen(b); 306 CharSequence txt = n.getText(); CharSequence cls = n.getClassName(); 307 sb.append(indent).append("[").append(cls).append("] ") 308 .append(txt==null?"":txt).append(" ") 309 .append(b.toShortString()).append("\n"); 310 for (int i=0;i<n.getChildCount();i++) dumpTree(n.getChild(i), indent+" ", sb); 311 } 312 ``` 313 314 This is the basis for commands like `txt_screen` (one-shot) and `screen_live` (continuous).<sup>[[5]](#references)</sup> 315 316 ## Device Admin coercion primitives 317 Once a Device Admin receiver is activated, these calls increase opportunities to capture credentials and maintain control:<sup>[[5]](#references)</sup> 318 319 ```java 320 DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(DEVICE_POLICY_SERVICE); 321 ComponentName admin = new ComponentName(this, AdminReceiver.class); 322 323 // 1) Immediate lock 324 dpm.lockNow(); 325 326 // 2) Force credential change (expire current PIN/password) 327 dpm.setPasswordExpirationTimeout(admin, 1L); // may require owner/profile-owner on recent Android 328 329 // 3) Disable biometric unlock to force PIN/pattern entry 330 int flags = DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT | 331 DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS; 332 dpm.setKeyguardDisabledFeatures(admin, flags); 333 ``` 334 335 Note: the exact availability of these policies varies by Android version and OEM; validate the device policy role (admin vs owner) during testing. 336 337 ## Crypto wallet seed-phrase extraction patterns 338 Observed flows for MetaMask, Trust Wallet, Blockchain.com and Phantom:<sup>[[5]](#references)</sup> 339 - Unlock with stolen PIN (captured via overlay/Accessibility) or provided wallet password. 340 - Navigate: Settings → Security/Recovery → Reveal/Show recovery phrase. 341 - Collect phrase via keylogging the text nodes, secure-screen bypass, or screenshot OCR when text is obscured. 342 - Support multiple locales (EN/RU/CZ/SK) to stabilise selectors – prefer `viewIdResourceName` when available, fallback to multilingual text matching. 343 344 ## NFC-relay orchestration 345 Accessibility/RAT modules can install and launch a dedicated NFC-relay app (e.g., NFSkate) as a third stage and even inject an overlay guide to shepherd the victim through card-present relay steps.<sup>[[6]](#references)</sup> 346 347 Background and TTPs: https://www.threatfabric.com/blogs/ghost-tap-new-cash-out-tactic-with-nfc-relay<sup>[[6]](#references)</sup> 348 349 --- 350 351 ## References 352 - [1] [Return of ClayRat: Expanded Features and Techniques](https://zimperium.com/blog/return-of-clayrat-expanded-features-and-techniques) 353 - [2] [ClayRat v3 IoCs (Zimperium)](https://github.com/Zimperium/IOC/tree/master/2025-12-ClayRatv3) 354 - [3] [PlayPraetor's evolving threat: How Chinese-speaking actors globally scale an Android RAT](https://www.cleafy.com/cleafy-labs/playpraetors-evolving-threat-how-chinese-speaking-actors-globally-scale-an-android-rat) 355 - [4] [Android accessibility documentation – Automating UI interaction](https://developer.android.com/guide/topics/ui/accessibility/service) 356 - [5] [The Rise of RatOn: From NFC heists to remote control and ATS (ThreatFabric)](https://www.threatfabric.com/blogs/the-rise-of-raton-from-nfc-heists-to-remote-control-and-ats) 357 - [6] [GhostTap/NFSkate – NFC relay cash-out tactic (ThreatFabric)](https://www.threatfabric.com/blogs/ghost-tap-new-cash-out-tactic-with-nfc-relay) 358 - [7] [Manic: Blend between Banking Malware & Spyware (ThreatFabric)](https://threatfabric.com/blogs/manic-blend-between-banking-malware-and-spyware)