Pull to refresh
390.87
Яндекс
Как мы делаем Яндекс

Android accessibility — волк в овечьей шкуре? Лекция Яндекса

Reading time 10 min
Views 16K
Месяц назад на очередной Droid Party старший разработчик Данила Фетисов подробно разобрал принцип действия службы, которая отвечает за accessibility-функции Android. Вы узнаете о том, как использовать её для улучшения доступности своих проектов, а также об опасной уязвимости под названием clickjacking.


— Меня зовут Данила Фетисов, я из московского офиса Яндекса, конкретнее — из Такси, конкретнее — из Таксометра. Сегодня мы с вами поговорим о том, что же такое Android accessibility и почему же я такую святую штучку для людей с ограниченными возможностями решил назвать волком в овечьей шкуре.



Итак, план доклада. Сначала мы с вами посмотрим, как эту штуку можно применять, создавать и настраивать. Может быть, расскажу парочку лайфхаков, продуктовых ништяков. И потом о самом интересном — о том, почему же эта штука является опасной.

Как вообще мы можем это применять? Вообще что это такое, для тех, кто не знает? В системе происходят какие-либо события, они падают нам в сервис, и мы их обрабатываем.

Раз нам что-то падает, мы можем взять оттуда контент и озвучить с помощью того же Yandex SpeechKit, Google Text-to-Speech или еще чего нибудь, что вам нравится. Но что самое интересное, мы можем не просто получить, но и закинуть событие системы. Еще можно что-нибудь сказать. Закинул событие, нажал кнопочку, и прямо все отлично, радуешься жизни.

Если говорить про эти два пункта, они, по сути, являются дефолтными. Google и запустил эту фичу для того, чтобы вашим приложением могли пользоваться люди с ограниченными возможностями. Но как все знают, мы, разработчики, скучать не хотим: садимся, смотрим код и думаем: как же можно применить? И пошло-поехало.

Автоматизация, тестирование с помощью имитации действий пользователя, контекстная реклама. Сами понимаете: раз мы получаем контент с экрана, то мы можем понимать, как третировать этого пользователя.

Что еще интересного? Обработка USSD-запросов. К сожалению, нормальный API появилась только с 26-го SDK, и разработчикам как-то нужно выживать. И вы представьте, они серьезно парсят USSD-запросы, парсят UI при помощи AccessibilityService.

Шестой пункт — многоточие. Почему? Потому что после этого доклада у вас примерно сложится понимание, как это применять и для чего. А дальше буквально открываете свой поток фантазии, берете чашечку чаю, кофе, и погнали кодить свои крутые фичи.

С чего начать? Следующий слайд будет до боли банальным.


Берем сервис, наследуем от AccessibilityService и радуемся жизни.

Закидываем потом этот сервис в Manifest, и что же тут интересного? По сути, обычная запись сервиса. Но обратите внимание на поле label. Сейчас я не буду рассказывать, что это такое. Запомните, это будет чуть позже.



Дальше по Manifest у нас просто файлик конфигов, тоже ничего интересного.


А сам файлик конфигов немножечко пострашнее выглядит. Из того, что вам понадобится, скорее всего, процентов 85–90 — это поле настройки фильтра для входящих событий.

Допустим, здесь стоит, что мы хотим получать абсолютно все события. Вы можете поставить фильтр только на клики, на изменение контента или на что-то еще.

Далее — фильтр на пакеты приложений. Для чего это нужно? Вы сами прекрасно понимаете, что получать события от абсолютно всех приложений в системе было бы слишком для одного приложения. Поэтому Google решил это как-то ограничить. Добавил фильтр.

Далее description. Было поле label, а теперь используют description. По сути, они создают пару, которая затем расскажет пользователю, для чего нужно включить ваш сервис.

И последний пункт: нужен или не нужен контент из текущего открытого окна. Если вы хотите просто понимать, что произошло какое-то событие, ставьте false и не мучайте лишний раз пользователя.


Включение сервиса. К сожалению, чтобы включить эту штукенцию, вам придется помучить полдьзователя и заставить его пройти через семь кругов ада настроек, условно говоря.

Сначала открываем ему специальные возможности. Крутой AccessibilityService — это как раз таки и есть тот label из manifest, о котором я вам говорил на предыдущих слайдах.


Дальше. Пользователю нужно выбрать ваш сервис, и тут он начинает читать description, зачем же вы хотите включить этот сервис и получить определенный контроль над устройством.


Окей, включает. Но не тут-то было. Вылезает какой-то страшный диалог, который говорит: «Чувак, если ты сейчас включишь, мы захватим полный контроль над твоим приложением, условно говоря, будет все страшно». Пользователи не всегда все это читают, просто кликают «ОК», чтобы мы от них отстали. Так что живем.

Ну, что же, где же обработка событий по факту у нас получается? Следующий слайд немножко поумнее первого слайда про сервис, но тем не менее.


Вот наш DummyAccessibilityService, и мы закидываем туда два метода, в котором будем все писать. По сути, interrupt — такой метод, в котором вы должны почистить ваши ссылочки, остановить подписки или что-нибудь еще.

Самое интересное — это класс AccessibilityEvent, который к нам падает. Что же мы можем из него достать?

Для начала мы можем понять, что же, на самом деле, произошло у вас в системе. Берем EventType, и что тут достаем?

А достаем, что у нас какая-то Activity там открылась, или Dialog, или Popup, или вообще нотификация свалилась. Прямо все туда падает на window_state_changed.

Далее понимаем, что контент у какой-то вьюхи изменился. Подвинули ее, закинули какую-то строчку в text view или что-нибудь подобное.

А дальше мы узнаем, когда пользователь кликает, или выбирает какую-либо вьюшку. По-моему, 85, да даже 98, наверное, процентов основных продуктовых задач вы покроете при помощи этих event type.

Но если же вам их не хватает — пожалуйста, можете ходить в документацию, я ссылочку в конце доклада вам покажу, и, конечно, вы скачаете доклад, сможете перейти. Просто это чтение на целый вечер, там громадный список. Серьезно, за один раз не осилишь.

Теперь вопрос у нас: как же найти view? Мы узнали, что произошло. А теперь давайте поймем, с какой же view, с каким компонентом это произошло.

Берем наш AccessibilityEvent.

Берем у него Source, и, вуаля, метаинформация по view уже у нас.

Но что делать, если вы хардкорный разработчик, и вам не хватает информации про одну view, вам хочется понять, что в принципе происходит сейчас на экране? Тут есть ограничение на SDK, но оно мелкое.

Берем наш AccessibilityService, и получаем, по сути, ссылочку на корневой элемент во всей иерархии на текущем экране. И уже потом достаем нашу мета-информацию по view.

Это, конечно, классно, но сейчас у вас в голове должен быть вопрос: ну, получили мы ссылку на корневой элемент. Что же нам дальше-то делать?

Если хотите пойти безопасным путем, то, пожалуйста, первый вариант. Берете ID view, и просто находите одним методом.

Если вам кажется, что это как-то скучновато, охота чего-то повеселее, то берем текст из view, находим по тексту.

Если и это вам недостаточно интересно — ну, ладно, ребята, берите Child и Recursion, ходите по нему. Тогда точно поймете все, что происходит.

Отлично. Мы с вами поняли, как находить view. Как же теперь взаимодействовать с ней?

Опять берем наш AccessibilityNodeInfo, и теперь уже закидываем Action в него. Какие же Action мы можем туда закинуть?

Для начала мы можем закинуть Click. Почему в итоге я сюда поставил не после просто Click, а еще Click и Select? Да потому что есть куча corner case, когда обычный Click вас не спасет. Допустим, пользователь выбрал текст, и клик по вьюхе чисто уберет selection с этого текста. Или же на некоторых девайсах в принципе вьюха не кликабельна, пока Select у нее не вызовешь. В общем, очень много проблем. И если вы хотите просто париться — делайте Select, делайте Click. Два Action, и все, счастье есть.

Что же дальше у нас? А дальше у нас установка текста. Тут тоже все просто. Берем Action_set_text, создаем Bundle с нашей строчкой, и живем.

И третий пункт — это go в документацию. Опять же, это второй вечер уже у вас будет. Куча экшенов прямо, по-моему, еще больше, чем событий.

Ура! Самая скучная часть доклада закончилась. Теперь пошло время всяких ништячков.

Допустим, вы создали сервис, настроили его, закинули какую-то обработку событий. Теперь вам нужно понять, как заставить пользователя, как попросить его все-таки включить этот сервис. Чтобы это понять, вам нужно узнать, в принципе сейчас включен сервис или нет.


Тут до банальности все просто. Берем AccessibilityManager и спрашиваем у него все включенные на текущий момент сервисы в системе, и там находим наш.

Но все было бы, конечно, классно, если бы AccessibilityManager был нормальным парнем, и не было такого, что ты у него спрашиваешь: «Чувак, дай мне, пожалуйста, все доступные сервисы», а он говорит: «Извини, я сегодня не в настроении. Вот тебе пустой список». И ты такой сидишь и думаешь: «Блин, нормально же общались».


Ну ладно, есть старые добрые друзья — это Settings.Secure.getInt и getString. Сначала мы спрашиваем, включены ли вообще сервисы в системе. Если включены, то в одну строчку закидываются все, и там уж мы через контент какой-нибудь ищем наш.

И тут встречаемся с тем, что мы все сделали правильно. Мы создали конфигурацию, создали на стройку всяких событий, включили сервис. Прямо точно видим, что включили сервис, а события к нам не прилетают. Причем так, что то прилетают, то не прилетают. В тот момент я подумал, что в моей жизни какая-то черная полоса наступила. Прямо никаких не было вариантов.

Думал-думал, ресерчил-ресерчил, и тут, бинго, я понял, что это — это Xiaomi с их MIUI. Вот просто, ребята, боль! Вот серьезно.

Окей, поняли, в чем проблема. Как же теперь ее решать? К сожалению, на некоторых прошивках в MIUI все сделано так, что если вашего приложения нет в автостарте, то система не будет в принципе стартовать даже ваш сервис.


Но тут одно решение — просим пользователя добавить ваше приложение в автостарт, и живем дальше, радуемся жизни.

И вот наступила вишенка на торте. Почему же этот сервис, такой добрый для людей с ограниченными возможностями, является волком в овечьей шкуре?

Мне кажется, многие из вас уже догадались, что если пользователь включит этот сервис, то мы спокойно сможем украсть достаточно много конфиденциальной информации с его устройства. Мало того, что мы сможем ее украсть, мы можем закинуть ее куда-нибудь, допустим, экран разблокировать, если он заблокирован при помощи пин-кода. Ну, окей, разблокируем экран. Что дальше? Накликаем себе рекламки, заработаем кучу денег, прямо заживем. И, в конце концов, можем в принципе сымитировать абсолютно любые действия пользователя, и, допустим, поставить какое-нибудь приложение, выдать ей админские права, и сказать пользователю: «Давай до свидания. Прощайся со своим устройством».

Окей, мы можем все это сделать, но вы сейчас скажете: «Ты, конечно, молодец. Рассказал нам, как мы можем какие данные украсть. Но как ты заставишь пользователя включить ваш AccessibilityService, причем если еще приложение какое-то левое?». Окей, пользователь, мне кажется, просто откроет настройки, поймет, что там написан какой-то бред, закроет, удалит приложение и забудет. И знаете, вы будете правы в том, что первый способ включения через системные настройки сходит на «нет», потому что, сами понимаете, это бред какой-то. И тут к нам на помощь приходит уязвимость, которая, по сути, называется clickjacking. Ребята, кто из вас знаком в принципе с clickjacking? Отлично, будет интересно.

Так вот, на основе этой уязвимости некоторые ребята — ссылочку небольшую я приложил, потому что все следующие скрины будут с ресурса этих ребят — придумали атаку, которая называется Cloak and Dagger. По сути, при помощи всего лишь двух permission и минимального взаимодействия с пользователем вы получаете доступ ко всему приложению прямо от слова «совсем».
Но окей, что нам нужно сделать, чтобы все-таки взломать пользователя?

Мы закидываем наше приложение в Google Play. Мы надеемся, что у пользователя Android версии меньше 8.

Сами понимаете, чуть-чуть — 95% на текущий момент. И все, пользователь скачивает наше приложение.


Если вкратце, то эта malware рассказывает пользователю, как стать хорошим парнем, показывает в самом конце видюшечку. Сейчас сами все увидите.


Окей, стартуем. Небольшой текст. Нам говорят: «Если ты сейчас начнешь tutorial, то станешь хорошим парнем». Окей, конечно, начинаем.


Еще какой-то текст. Дальше люди будут представлены в виде зеленых человечков. Ну, ладно, давайте, нажимаем Next.


Еще текст. Ну, ладно. Сейчас я его дочитаю и точно увижу видюшку, на которой мне расскажут, как стать хорошим парнем.



Нажимаю «Окей», и там запускается видюшка. Это нам не столь важно. Важно, что на самом деле происходило на устройстве в этот момент. Пользователь запускает приложение, а так как мы его поставили из Google Play, нам автоматом выдаются permission на overlay. Вуаля, мы открываем окошко специальных возможностей, и сверху просто overlay. Пользователь кликает на Start Tutorial, а на самом деле выбирает наш AccessibilityService. Ну, вы понимаете, что пошло дальше.


Дальше мы нажимаем Next, выбираем тумблер.


В самом конце мы включаем AccessibilityService, и пользователь даже не догадывается, что это произошло.

Знаете, в чем тут проблема? Overlay устроены таким способом, что они либо полностью поглощают все события, все touch и так далекк, либо они полностью пропускают. И фишка именно этой атаки в том, что мы заполняем overlay, весь экран кроме одного пространства. В данном случае это кнопочка OK. Эта кнопочка — действительно из диалога. Когда мы на нее кликаем, мы получаем ивент, узывающий, что произошел touch outside, и все отлично.

Как же Google реагирует на все эти проблемы? Сами понимаете, это достаточно жестокая уязвимость.

Началось все достаточно интересно — все с восьми лет молчания. Google зарелизил эту штучку, сказал: «Ребята, пользуйтесь. Это безопасно. Точно говорю». Ну окей. Google закидывали. Те же самые ребята, которые Cloak and Dagger придумали, закидывали и саппорт, и issue trackers, и bug trackers.

Потом Google сказал: «Ладно, ладно, ребята, успокойтесь. Я закрою clickjacking в Oreo». Ну ладно, закрыли. Сидят такие еще, думают: «Ну, блин, ребята, все-таки если злоумышленники заставят пользователя включить ассеssibility, это будет плохо. Давайте-ка немножечко над разработчиками поиздеваемся.

И отправляют письмо: «Сделайте то, не знаю, что, иначе ваше приложение будет удалено из Google Play». Если кратко сказать, то в письме было следующее: «Ребята, расскажите нам и пользователям, для чего вам нужны AccessibiiltyService». Как рассказать? В каком формате? Где? Вообще нет понимания. И в письме, конечно же, это не указано.

Ну окей. Громадные треды на reddit, куча писем в саппорт. И потом какой-то разработчик выкладывает на reddit письмо, и в нем говорится: «Да там, ребята, ничего сложного. Закиньте в description, который я вам показал, в manifest, закиньте в описание в Google Play, и все будет нормально, я от вас отстану».

Я в этот момент подумал: ну окей, нашел я это письмо в каких-то кулуарах Reddit. А что если я молодой разработчик, только познакомился с AccessibilityService и хочу зарелизить приложение с этим? Откуда я должен узнать, что мне нужно произвести такие действия, нужно в description, в Google Play закинуть т. д.? Думал-думал, искал-искал и, знаете, ребята, ничего не нашел, от слова совсем.

Ну, серьезно, ничего нет. Есть просто описание в основной документации: «Ребята, это чисто для людей с ограниченными возможностями и точка. Больше никак использовать нельзя». А на самом-то деле, оказывается, можно.

Что ж, мы с вами подошли к концу. Что же мы смогли с вами проговорить? Мы проговорили, как это можно использовать, поняли, как это все создавать, настраивать, поняли, как обрабатывать UI-события. И, конечно, поняли, как можно, но не нужно взаимодействовать с AccessibilityService. Конечно, я вам не рекомендую так делать, это было просто для информации. Вот обещанные источники:

developer.android.com/guide/topics/ui/accessibility/services
developer.android.com/reference/android/view/accessibility/AccessibilityEvent
developer.android.com/reference/android/view/accessibility/AccessibilityNodeInfo
cloak-and-dagger.org

Ребята, спасибо большое за внимание!
Tags:
Hubs:
+10
Comments 0
Comments Leave a comment

Articles

Information

Website
www.ya.ru
Registered
Founded
Employees
over 10,000 employees
Location
Россия
Representative