Controlling Android. Dangerous APIs enable hackers to intercept data and reset smartphone settings

In addition to traditional permissions, Android has three metapermissions that open access to very dangerous APIs enabling the attacker to seize control over the device. In this article, I will explain how to use them so that you can programmatically press smartphone buttons, intercept notifications, extract text from input fields of other apps, and reset device settings.

The risky APIs

  1. The Device Administration API is designed for corporate apps. It allows to reset and set screen lock passwords, reset the smartphone settings to factory defaults, and set the minimum password complexity rules. One of the specific features of this API is prohibition to delete apps having the admin rights – and malware creators enthusiastically use it.
  2. The Accessibility API makes it possible to implement apps for users with disabilities. In other words, this API allows you to create alternative ways to control the device and hence opens enormous possibilities for abuse. For instance, using it, you can get access to the screen of nearly any app, press interface buttons, and press the smartphone buttons at the software level. But there is also a mechanism protecting the device: the app developer can make some elements of the app inaccessible to Accessibility services.
  3. The Notifications API allows to get access to all notifications displayed on the notification bar. Using this API, an app can read all information about a notification, including its header, text, and content of control buttons; it can click on these buttons and even swipe the notification. The API is extremely popular among developers of banking trojans enabling the malefactors to view confirmation codes and swipe warning messages received from the bank.

After getting access to the above APIs, the malicious app can do whatever it wants with your smartphone. This is why these APIs are protected not by traditional permission requests (that the user may accidentally grant by automatically answering “Yes”), but by an interface hidden deep in the settings. When activated, this interface displays a warning message. Therefore, an app that needs such a permission can only take the user to the Settings window; then the user has to select this app, move the slider, and accept the warning message.

An app can also try to get the permission to use the dangerous APIs by tricking the user. Malicious programs often pretend to be legitimate apps that need such a permission to enable their key functionality. For instance, they can impersonate an app that logs notifications or an app for alternative gesture navigation (it needs an Accessibility service to press navigation buttons). Another way is to use the Cloak & Dagger attack to overlay the settings window with another innocent-looking screen.

Pressing smartphone buttons

Below is an example of a simple Accessibility service (the code is written in Kotlin):

class AccessService: AccessibilityService() {
companion object {
var service: AccessibilityService? = null
// Method that programmatically presses the Home button
fun pressHome() {
override fun onServiceConnected() {
service = this
override fun onUnbind(intent: Intent?): Boolean {
service = null
return super.onUnbind(intent)
override fun onInterrupt() {}
override fun onAccessibilityEvent(event: AccessibilityEvent) {}

To inform the system of this service, it has to be declared in AndroidManifest.xml:

<action android:name="android.accessibilityservice.AccessibilityService" />
android:resource="@xml/accessibility_service_config" />

The above description refers to the config file accessibility_service_config.xml that must be defined in the xml directory of the project. In this particular case, the following config will suffice:

<accessibility-service xmlns:android=""
android:description="@string/accessibility_description" />

As soon as the user enables my handmade Accessibility service in the Apps Icon → Settings → Accessibility window, the system will automatically launch it, thus, allowing me to execute the pressHome() function that presses the Home button:

// If the service is not null, then the system has successfully launched it
if (AccessService.service != null) {

This functionality alone is sufficient to implement ransomware that calls the pressHome() function in a loop returning the user to the home screen and making the phone unusable.

Enabling an Accessibility service in Android 11
Enabling an Accessibility service in Android 11
Enabling an Accessibility service in Android 11

But the true power of the Accessibility API is not in pressing navigation buttons, but in its ability to control other apps.

Stealing contents of input fields

The Accessibility API was developed for users with disabilities. Using it, you can create an app that reads captions on all interface elements and enables you to activate these elements with your voice. This became possible because Accessibility grants you full access to the app interface in the form of a tree of elements: you can navigate through it and perform certain operations with its elements.

To teach my app to navigate through the app interface, I have to change the service description in the settings. The below config grants full access to the interface of any app:


Then I have to write a simple keylogger. To do so, I add the following function to the service code:

override fun onAccessibilityEvent(event: AccessibilityEvent) {
if (event.source?.className == "android.widget.EditText") {
Log.d("EditText text: ", event.source?.text.toString())

Now all information entered by the user in any input field of any app will be displayed in the console.

The Accessibility API enables you to navigate through the tree of elements, copy the text of the elements, paste text into them, and perform many other operations. This tool is truly dangerous; so, Android will use every possibility to revoke the Accessibility rights from my app. For instance, this will happen when the service crashes for the first time. In addition, Android enables the developers to protect critical app components with the importantForAccessibility flag:

... />

This piece of code conceals the layout and all its descendants from the Accessibility services.

In Kotlin, the same functionality looks as follows:


Dumping UI tree

There is a handy way to dump the UI of any app as it’s seen by the Accessibility service:

$ adb shell uiautomator dump
$ adb pull /sdcard/window_dump.xml
Dumping the Telegram interface
Dumping the Telegram interface

Blocking the device and protecting your app against deletion

Time to employ the Device Administration API. As said above, this API remotely controls mechanisms protecting the device; for instance, it can be used to reset passwords, enable a password complexity policy, or reset the smartphone settings. It’s as easy-to-use as the Accessibility API, although its operation principle is different.

First, I have to create a receiver to be called after enabling/disabling the admin rights. There is no need to add meaningful code to this receiver – it just has to exist:

class DeviceAdminPermissionReceiver : DeviceAdminReceiver() {
override fun onDisabled(aContext: Context, aIntent: Intent) {

Then I add the receiver to the manifest:

android:resource="@xml/admin_policies" />
<action android:name="" />

The above piece of code refers to the config file xml/admin_policies.xml; so, I create it and add to it the following strings:

<reset-password />
<force-lock />
<wipe-data />

The config states that my app can reset and change the lock screen password, turn off the screen, lock it with the password, and reset the settings to factory defaults.

As soon as the user permits my app to use the admin rights in the Settings → Security → Device admin apps window, I will be able to check whether I have actually gained these rights and then use them:

// Function that verifies my rights
fun checkAdminPermission() {
val adminComponent = ComponentName(this,
val policyManager = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
return policyManager.isAdminActive(adminComponent))
val policyManager = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
// Lock the device and forcibly request the password
// Reset the device to factory defaults
// Change the lock screen password (doesn't work on Android 7+)
policyManager.resetPassword("1234", 0)

Note that you can lock the device and even reset it factory defaults, but starting with Android 7, you cannot change the current lock screen password.

To be able to change the current password on modern devices, the app must possess the device owner status. This status can be acquired in two ways:

  • install an admin app on a clear device using a QR code. This operation requires a special API whose description is beyond the scope of this article; or 
  • set an app as the device owner using ABD or root rights. To do so, enter the following command:
$ dpm set-device-owner

But even without the device owner status, the ability to reset and lock the phone enables me to write an app that demands a ransom from the user threatening to destroy all data or lock the device in a loop (and the user won’t be able to delete this app without revoking its admin rights first).

Device admin apps screen
Device admin apps screen
Device admin apps screen

Intercepting and swiping notifications

One might think that the interception of notification is of little use to hackers, but on modern devices, they contain plenty of important information: text messages with one-time authentication codes, communication via messengers, message headers and their content, service messages, etc.

Similar to the Accessibility API, the interception of messages requires a service to be controlled by the system:

class NLService: NotificationListenerService() {
private var connected = false
override fun onListenerConnected() {
connected = true
override fun onListenerDisconnected() {
connected = false
override fun onNotificationPosted(sbn: StatusBarNotification) {
override fun onNotificationRemoved(sbn: StatusBarNotification?) {

The above service has four main callbacks. Two of them are called when the service is enabled/disabled (normally this happens when the app is launched and terminated and when the access to notifications is enabled or disabled). The two other callbacks are required to handle the appearance/disappearance of notifications.

My simple service immediately swipes a popped-up notification and does nothing when it disappears. However, I can, if necessary, remember its header, text, and package the notification belongs to:

val extras = sbn.notification.extras
val title = extras.getCharSequence(Notification.EXTRA_TITLE)
val text = extras.getCharSequence(Notification.EXTRA_TEXT)
val package = sbn.packageName

Banking trojans normally examine the app package, compare it with the database of bank clients, and parse the notification header and text searching for strings typical for bank’s notifications. Then the notification is programmatically swiped.

To make this service work, I have to declare in the manifest.

<action android:name="android.service.notification.NotificationListenerService" />

After being enabled in the settings (Apps & Notifications → Advanced → Special App Access → Notification Access), the service will start working.

Notification Access window
Notification Access window
Notification Access window


Isn’t this great? After writing just a few dozen strings, you can programmatically press smartphone buttons, intercept notifications, extract text from input fields of other apps, and even reset smartphone settings. Such enormous possibilities are truly scary, especially in contrast with iOS. On the other hand, the situation with Android is much better if compared to Windows, Linux and macOS where all you have to do is force the user to click “Yes” in a single dialog – and ?voila?, you gain complete control over victim’s computer.

Leave a Reply

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>