Здравствуйте, меня зовут Виталий.
Мне 25 лет, закончил магистратуру СПБГЭТУ «ЛЭТИ» в своем родном городе. Уже 10 лет занимаюсь программированием, из которых 4 пишу под Android. Автор многих Homebrew программ, известный под ником VITTACH, для Sony PlayStation Portable (PSP).
Сегодня я бы хотел обсудить с вами проблему безопасности мобильных приложений. Разработчики из Google постоянно улучшают Android, находя и исправляя уязвимости не без помощи обширного сообщества, собранного благодаря программе Android Security Rewards, о которой мы поговорим позже. Тем не менее, проблемы все еще остаются, и наша общая задача как коммьюнити сообщать о них, чтобы их своевременно исправляли.
Уязвимость о которой я буду говорить, относится к классу с Priority:? P2 и Severity:? S2, что согласно таблице в широком смысле означает:
Проблему, которую необходимо решить в разумные сроки;
Проблему, которая важна для большого процента пользователей и связана с основными функциями.
Runtime permission
Речь в статье пойдет о такой известной всем разработчикам вещи как Runtime permission, а именно – о возможности введения в заблуждение конечного пользователя путем демонстрации диалогового окна выдачи разрешения со своим текстом и иконкой поверх системного. Нетрудно догадаться, что подобный подход позволил бы разработчикам запрашивать у пользователя разрешение, скажем, к файловой системе, а по факту – к выдаче доступа к геопозиционированию, камере или чему-то еще.
Это невозможно
Не один раз задавался подобный вопрос на специализированных форумах, в частности на StackOverflow. Единственным правильным ответом было то, что это невозможно. И это действительно так: невозможно подменить текст в самом системном диалоге, но возможно его перекрыть своим.
Что под капотом
Runtime Permission впервые появились в Android 6.0
в ответ на потребность повышенного внимания в области выдачи dangerous-разрешений. Фактически основная идея состоит в том, чтобы взаимодействовать с пользователем при запросе разрешений через всплывающее окно. Поэтому теперь разрешения из списка dangerous необходимо запрашивать у пользователя как только они понадобятся приложению.
Dangerous permissions
android.permission_group.CALENDAR
android.permission.READ_CALENDAR
android.permission.WRITE_CALENDAR
android.permission_group.CAMERA
android.permission.CAMERA
android.permission_group.CONTACTS
android.permission.READ_CONTACTS
android.permission.WRITE_CONTACTS
android.permission.GET_ACCOUNTS
android.permission_group.LOCATION
android.permission.ACCESSFINELOCATION
android.permission.ACCESSCOARSELOCATION
android.permission_group.MICROPHONE
android.permission.RECORD_AUDIO
android.permission_group.PHONE
android.permission.READPHONESTATE
android.permission.CALL_PHONE
android.permission.READCALLLOG
android.permission.WRITECALLLOG
android.permission.ADD_VOICEMAIL
android.permission.USE_SIP
android.permission.PROCESSOUTGOINGCALLS
android.permission_group.SENSORS
android.permission.BODY_SENSORS
android.permission_group.SMS
android.permission.SEND_SMS
android.permission.RECEIVE_SMS
android.permission.READ_SMS
android.permission.RECEIVEWAPPUSH
android.permission.RECEIVE_MMS
android.permission.READCELLBROADCASTS
android.permission_group.STORAGE
android.permission.READEXTERNALSTORAGE
android.permission.WRITEEXTERNALSTORAGE
Для отображения системного диалога в Android есть GrantPermissionsActivity, который запускается после запроса на выдачу разрешений и отображает нам диалог со знакомым интерфейсом.
ActivityCompat.requestPermissions(
MainActivity.this,
arrayOf(Manifest.permission.READ_CONTACTS),
PERMISSION_REQUEST_CODE
)
А раз это Activity, которая перекрывает UI нашего приложения, значит можно попробовать пойти тем же путем и создать свою Activity, которая уже будет перекрывать системную.
Теперь давайте посмотрим на пример:
Пусть есть Activity с флагом android:windowIsTranslucent=true
(чтобы сделать Activity с прозрачным фоном, позволяющим видеть, что за ним стоит) и оно запускается другим Activity , который я назову фоновым. Визуально вы все еще можете видеть некоторую часть фонового Activity через прозрачные пиксели в Activity переднего плана.
Синий – это активное Activity с полупрозрачным окном, а фиолетовый – Activity прямо под ним. Что произойдет с Activity, если вы поместите приложение в фоновый режим?
Фоновая активность, несомненно, будет убита, а передний план деятельности может выжить, пока вы не переключитесь на другое приложение. И вот что произойдет, когда вы вернете свое приложение на передний план:
Фоновая Activity создается, а затем onResume
и onPause
вызываются по порядку. Затем оживает передний план Activity.
Идея заключается в том, чтобы перекрыть эту Activity в стеке своей собственной с кастомным диалогом, но так чтобы оставить возможность взаимодействовать с кнопками системного диалога. И вот это уже – возможно!
Попробуй сам, это не сложно
Для демонстрации использован язык программирования Kotlin
Создать стиль
К сожалению, невозможно настроить эти параметры иначе как из стилей
<style name="Theme.Transparent" parent="AppTheme"> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowIsTranslucent">true</item> </style>
Прописать в манифесте Activity со стилем
... <activity android:name=".PermissionActivity" android:theme="@style/Theme.Transparent">
Создать PermissionActivity со своим layout
В методе onCreate написать код:
window.addFlags( FLAG_NOT_FOCUSABLE or FLAG_NOT_TOUCH_MODAL or FLAG_NOT_TOUCHABLE )
Здесь мы используем трюк с использованием следующих флагов:
FLAG_NOT_FOCUSABLE
: window, в которой установлен параметрFLAG_NOT_FOCUSABLE
, не может взаимодействовать с методом ввода;FLAG_NOT_TOUCH_MODAL
: разрешит отправку любых событий за пределы окна в окна позади него, в противном случае он сам будет использовать все события указателя, независимо от того, находятся ли они внутри окна;FLAG_NOT_TOUCHABLE
: это окно никогда не может получать события касания.
В MainActivity вызвать запрос
ActivityCompat.requestPermissions( MainActivity.this, arrayOf(Manifest.permission.READ_CONTACTS), REQUEST_CODE )
И сразу после этого из MainActivity запустить другую активити: PermissionActivity.
startActivity(Intent(this, PermissionActivity::class.java))
PermissionActivity с прозрачным фоном и отсутствием фокуса и прокидыванием событий на Activity позади себя запустится и позволит добиться перекрытия системного диалога с сохранением полного взаимодействия с ним. Результат достигнут!
Android >= 7.1.1
Хотя Runtime Permission появились в версии Android 6.0
, но до версии 7.1.1
эту уязвимость использовать не получится, т.к. ранние версии Android ругаются на обнаружение перекрытия при попытке нажатия на кнопку Разрешить
.
Если вы попробуете запустить мой код на версии Android 6.0
, то получите это предупреждение. По неизвестной мне причине, Google отказался от этих ограничений в новых версиях.
Android Rewards Program
Я подал заявку и приложил все объяснительные и демонстрационные документы по данной уязвимости. В данный момент заявка находится в стадии рассмотрения, поэтому я не могу разглашать подробности, потому что подписал соответствующее соглашение.
А как проще?
Для удобства эксплуатации уязвимости мною была написана библиотека
aamonster
А можно отформатировать не как исходник, чтобы строки переносились и на телефоне читать было можно? Спасибо.
Newbilius
Тогда на десктопе будет менее удобно. Кажется надо Boomburum просить, чтобы сделали на телефоне удобно.
aamonster
Да нет, там просто раньше весь текст статьи был отформатирован как код,
автор это давно исправил.