Android Task Hijacking — уязвимость Android, которая позволяет подменить любое приложение, используя только стандартные механизмы и не требуя специальных разрешений. Такой подход не требует наличия root-прав на устройстве и Google спокойно пропускает такие приложения в Store. Из-за того что уязвимость находится на уровне системы, подмене подвержены все приложения на устройстве, в том числе системные. Докладчик расскажет о технических подробностях, покажет, как работает эта уязвимость, и поделится возможными решениями.
3. History
• USENIX Security Symposium 2015,
Towards Discovering and Understanding Task Hijacking in Android
In our research we find more interesting features…
16. Useful advance to solve the problem
• Transition #1
– Don’t specify launchMode = “singleTask”
– Don’t set FLAG_ACTIVITY_NEW_TASK
If it necessary, use it with:
FLAG_ACTIVITY_CLEAR_TASK
21. Useful advance to solve the problem
• Transition #2 and #3
– May be it’s good idea to create service, that would
check another task with “taskAffinity” of your
application
– Or create service that will compare certificate of
application that run with taskAffinity of your
application
26. Useful advance to solve the problem
• Transition #5
– Use explicit Intents if the destination Activity is
predetermined
– Verify the destination Activity if linking with another
application
28. - Is my application vulnerable?
- Yes.
Vulnerability % of apps*
Run malware from Launcher instead of legitim App 100
Send implicit intent for exported activities 93,9
Send implicit intent for exported activities and use intent
FLAG_ACTIVITY_NEW_TASK
65,5
Contains public exported activity and
launchMode=“singleTask”
14,2
Contains public exported activity and
allowTaskReparenting=“true”
1,4
* Statistics from research in 2015
29. Android versions & devices vulnerable to spoof from launcher
Android version Vulnerable
Android 5.x Yes
Android 6.x Yes
Android 7.x Yes
CyanogenMod 12.1 No
MIUI Yes
Device Vulnerable
Nexus x.x Yes
Xiaomi Yes
Samsung Yes
LG Yes
* No permission needed
* System Apps also vulnerable
31. Useful advance to solve the problem
• Transition #1
– Don’t specify launchMode = “singleTask”
– Don’t set FLAG_ACTIVITY_NEW_TASK
If it necessary, use it with:
FLAG_ACTIVITY_CLEAR_TASK
• Transition #4
– Don’t specify allowReparenting
– Don’t specify taskAffinity
32. Useful advance to solve the problem
• Transition #5
– Use explicit Intents if the destination Activity is
predetermined
– Verify the destination Activity if linking with another
application
• Transition #2 , #3 and #6
– May be it’s good idea to create service, that would
check another task with “taskAffinity” of your
application
Когда мы начали развивать инициативу анализа мобильных приложений, искали гайды, изучали чеклисты и наткнулись на уязвимость, назваемую Activity Hijacking. Начали копать дальше и нашли доклад, представленный на одной из конференций. К сожалению, широкой огласки он не получил, и упоминаний о Task Hijacking больше нигде не встречалось. Прочитав, нам стало интересно и мы провели своё исследование на основе этого доклада. При этом выяснились интересные подробности и новые уязвимости, о которых мы расскажем сегодня.
В рамках доклада расскажем частично материалы, которые мы почерпнули и адаптировали из доклада, а так же результаты своего исследования.
Немного теории для дальнейшего понимания происходящего и того, как устроены приложения и процессы в Андроид.
Основные компоненты, о которых пойдет речь в дальнейшей презентации:
Активити – один из возможных компонентов приложения. Каждая активити – это отдельный графический экран со своими элементами, то, что пользователь видит на экране устройства. Каждое приложение имеет несколько активити для различных действий пользователя, то есть каждый новый экран – это Активити. Все Активити описаны в файле манифеста приложения.
Таск – это коллекция активити, с которыми взаимодействовал пользователь во время работы приложения.
Activity в task хранятся в виде стека, называемого back stack. Каждая новая Activity, запускаемая пользователем, располагается системой на верхушке этого стека. Таким образом, при нажатии на кнопку back – Activity, которая была на верху стека закрывается (уничтожается) и отображается Activity, которая была под ней. Отсюда и название – Back Stack.
В дальнейшем будем использовать несколько понятий:
Foreground Activity - Activity отображаемая на экране, т.е. находящаяся на верхушке стэка.
Foreground Task – это task, в котором располагается foreground Activity.
В системе может быть только одна foreground task, все остальные background. При переключении на задачу, находящуюся в background, все активити в таске останавливаются и остаются нетронутыми до тех пор, пока пользователь не вернется в приложение. Таким образом они попадают именно в то место и с тем backstack, на котором остановились в прошлый раз.
Activity внутри back stack могут относится не только к запущенному приложению, но и к другим приложениям.
Таким образом в Android построена бесшовная интеграция между различными приложениями.
Рассмотрим ситуацию, когда в приложении осуществлена возможность просмотра видео
Имеем следующее:
На устройстве установлено приложение, которое позволяет просматривать видео.
В другом приложении, с которым работает пользователь, есть видео-ролик
При нажатии на кнопку просмотра видео, приложение запускает Activity, но не свою (зачем разрабатывать и тратить силы, когда уже всё готово), а Activity стороннего приложения, которое умеет воспроизводить видео.
Запускается Activity видео-плеера, и эта Активити запускается в back stack`е нашего приложения, оказавшись на его верхушке.
При нажатии на кнопку «Назад», Activity видео-плеера закрывается и мы возвращаемся экран нашего приложения.
Это сделано для того, чтобы для пользователя создавалось ощущение работы с единым приложением и не было необходимости переключаться между приложениями для выполнения разовой функции.
Каждая task в системе характеризуется таким атрибутом как taskAffinity. Он объявляет, к какому task`у должна присоединиться Activity при запуске. Это строка, которая либо определяется в манифесте приложения свойством android:taskAffinity=“application_1", либо по умолчанию равна ID приложения в системе (applicationId). Affinity task`а определяется значением taskAffinity его root-Activity (нижней в стэке).
Если явно указывать значение taskAffinity, то можно заставить запускаться Activity в рамках произвольного таска.
Итак, Android API при определении Activity позволяет задать произвольное значение для taskAffinity, таким образом указав task, к которой будет относиться эта Activity.
Таким образом, каждое приложение в Android может породить несколько task.
В т.ч. task, с taskAffinity стороннего приложения.
А теперь немножко магии, чтобы стало понятно, о чем мы будем рассказывать и как это вообще работает
Когда мы только опубликовали приложение, возник резонный вопрос, а кто же его будет скачивать, оно же находится черти-где и как его вообще найти.. Но тут на помощь пришёл незнакомец и предложил воспользоваться его услугами)
Итак, давайте я попробую объяснить каким образом произошло то, что вы сейчас видели.
Для начала, разберемся, как происходит запуск Activity в Android вообще:
За загрузку Activity в Android отвечает Activity Manager Service. Покопавшись немного в исходниках, стало понятно, что при определенном launchMode=“singleTask” или интенте с FLAG_ACTIVITY_NEW_TASK сервис действует следующим образом:
Для начала, если instance Activity уже существует, то Андроид выводит его на передний план, а не запускает новый
Если же всё-таки требуется создание Activity, то AMS выбирает task в который необходимо «положить» созданную Activity при помощи поиска «совпадения» со всеми имеющимися task. Activity «совпадает» c task, если у них указано одинаковое taskaffinity. После найденного совпадения сервис кладет новый Activity на верхушку в «совпавший» task.
Если же совпадений не найдено, сервис создаёт task и созданная Activity становится root-Activity
Итак, попробуем теперь разобраться, что же происходило на самом деле:
Первое. На нашем устройстве запущено вредоносное приложение, которое находится в background и исключено из списка недавних приложений (то есть пользователь никак его не видит)
Второе, мы запускаем наше банковское приложение, в котором есть кнопка Intro, по нажатию на которую происходит проигрывание видео. При этом запуск Activity с проигрывателем видео осуществляется с помощью Intent с флагом FLAG_ACTIVITY_NEW_TASK или в Activity Player указан launchMode=“singleTask”. А у вредоносного приложения указан taskAffinity плеера. Таким образом, исходя из работы AMS – он нашёл «совпадающий» task, и положил плеер на верхушку его стека.
В результате, вместо того, чтобы присоединиться к банковскому таску или запуститься в новом таске, приложение видеоплеера помещается на верхушку стэка вредоносного Task. После просмотра видео при нажатии на кнопку Back пользователь попадет уже в банковское приложение, во вредосное Mal-Activity 2.
Для предотвращения уязвимостей, связанных с TaskHijacking, можно придерживаться следующих правил:
Случай 1:
Не указывать для activity launchMode = singleTask
Не использовать FLAG_ACTIVITY_NEW_TASK. Если же его использование необходимо, то использовать его вместе с флагом FLAG_ACTIVITY_CLEAR_TOP
Случай 4:
Желательно не использовать allowReparenting и указания taskAffinity
Рассмотрим самый интересный из приведенных примеров, подмена приложения из Launcher, которому подвержено любое приложение
А вот тут самое интересное. Как мы помним из предыдущего примера, если в системе существует task с taskAffinity = taskAffinity запускаемого Activity, то сервис по управлению Activity должен положить вновь запускаемую активити на верхушку стека существующего приложения.
То есть, в нормальном случае, согласно документации, должна запускаться Activity вызываемого приложения, AMS проверяет, если task с таким affinity и кладёт на верхушку стека этогого task. Пользователь видит нормальную активити, под которой лежит вредоносная.
Но из-за бага в Android этого не происходит, а вместо этого восстанавливается уже запущенная ранее вредоносная Activity.
Существует ещё один способ подменить приложение при запуске из Launcher.
По умолчанию, как только Activity запускается и ассоциируется с Task, эта связь сохраняется на всём протяжении жизненного цикла Activity. Однако Android API позволяет указать такое свойство активити при её определении в манифесте как allowTaskReparenting, который работает следующим образом. Одновременно с этим свойством для активити указывается taskAffinity той задачи, на которую нужно пересадить эту активити при появлении указанной task. Пока в системе не будет этой task activity будет запускаться в таске своего приложения, т.е. с taskAffinity=applicationId. Как только в системе появится tasks с указанной affinity, activity тут же будет пересажена на верхушку её бэкстека.
Таким образом для проведения атаки вредоносное приложение должно объявить для той активити, которую нужно отобразить пользователю вместо целевого приложения, соотв. св-ва – allowTaskReparenting и taskAffinity целевого приложения. Когда пользователь запустить целевое приложение, система тут же пересадит вредоносную активити на верхушку бекстека целевого приложения и отобразит её.
-----
Однако, в Android существует флаг с названием allowTaskReparenting, который позволяет уже существующей Activity «перемещаться\пересаживаться» на созданный в системе task с таким же taskAffinity
Как это происходит.
В системе создаётся background task, который имеет 2 Activity (root Activity Mal-Activity 1) и Activity с настройкой allowTaskReparenting и taskAffinity необходимого приложения.
Пользователь запускает приложение, создаётся новый task с taskAffinity, указанной в Mal-Activity 2
Во время запуска Mal-Activity 2 «пересаживается» на верхушку нового task и пользователь уже видит вредоносную Activity вместо обычной
Случай 5:
Использовать явные интенты, если известна конечная Activity
Проверять, какая из Activity запущена при привязке к другому приложению
Во всех остальных случаях, ждем исправления от AndroidTeam, но наша идея на данный момент заключается в том, что можно сделать сервис, который бы мониторил список task в системе и определял в нем наличие task с taskaffinity = id вашего приложения.
Или сделать сервис, который бы проверял цифровую подпись приложения, от имени которого запущена root-Activity и куда «пересаживаются» созданные вашим приложением Activity
В обратном случае, если необходимо поддержать бесшовную интеграцию между двумя приложениями, разработчики могут указать в валидном приложении allowTaskReparenting и указать taskAffinity другого легитимного приложения.
Что произойдет в случае, если на устройстве есть malware:
В background запущен вредоносный task с taskAffinity = taskAffinity, указанным в одной из Activity легитимного приложения
Пользователь работает с приложением, запускает Activity, например чат, который по задумке разработчиков должен пересаживаться на другой таск
AMS проверяет, что уже есть task с таким taskAffinity и пересаживает легитимную Activity на верхушку стека вредоносного task.
Теперь, если пользователь захочет вернуться назад, то при нажатии на кнопку back попадёт во вредоносную Activity
Для предотвращения уязвимостей, связанных с TaskHijacking, можно придерживаться следующих правил:
Случай 1:
Не указывать для activity launchMode = singleTask
Не использовать FLAG_ACTIVITY_NEW_TASK. Если же его использование необходимо, то использовать его вместе с флагом FLAG_ACTIVITY_CLEAR_TOP
Случай 4:
Желательно не использовать allowReparenting и указания taskAffinity
Возможен ещё один путь перенаправления пользователя на свою вредоносную Activity.
Предположим следующее:
В системе есть вредоносное приложение, одно из Activity которого выполняет вполне легитимную функцию, к примеру просмотр видео. При этом оно маскируется (имеет иконку) похожую на иконку искомого приложения. К примеру, у нас есть банковское приложение BankApp и мы делаем видео плеер с такойже иконкой и названием – BankApp Player
Пользователь из легитимного приложения выбирает опцию просмотра видео, при этом это реализовано неявным интентом (то есть ему представляется выбор), каким приложением открыть данный файл. Пользователь видит, что среди программ есть приложение с аналогичной иконкой и похожим названием и выбирает его
Вредоносная Activity запускается, оказывается на верхушке стека легитимного приложения и проигрывает файл
Во вредоносной Activity переопределен метод onBackPressed, то есть переопределено поведение при нажатии кнопки Back. Его функция – перенаправлять пользователя во вредоносное приложение
Пользователь посмотрел видео, нажимает на кнопку back, и попадает во вредоносное приложение.
????
Profit!
Случай 5:
Использовать явные интенты, если известна конечная Activity
Проверять, какая из Activity запущена при привязке к другому приложению
Во всех остальных случаях, ждем исправления от AndroidTeam, но наша идея на данный момент заключается в том, что можно сделать сервис, который бы мониторил список task в системе и определял в нем наличие task с taskaffinity = id вашего приложения.
На данный момент эту уязвимость нельзя закрыть.
Системные приложения по сути, не отличаются от устанавливаемых пользователем. Попробуем защититься от удаления нашего приложения.
Что мы имеем:
В системе запущена background task с affinity = com.android.settings (то есть, с приложением настроек)
Пользователь хочет зайти в настройки и удалить приложение. Запускает из Launcher приложение настроек. AMS ищет в системе task с affinity=com.settings.android, находит и кладёт на верхушку стека Malware task
Пользователь ходит по меню, заходит в удаление приложений, выбирает зловред и нажимает «Удалить». После этого ему выпадает диалоговое окно с просьбой подтвердить удаление. Это диалоговое окно является ничем иным, как еще одной Activity («Uninstaller» на слайде).
В это время вредонос запускает свою Activity, с таким же taskAffinity и она попадает на верхушку стека, перекрывая собой диалоговое окно (Mal-Activity 2 на слайде)
При этом у вредоносной Activity переопределен метод OnBackPressed, который снова запускает “Mal-Activity 1” в background и очищает stack, что приводит нас к состоянию 1
А как же удаление через adb? Всё очень просто, чтобы подлючить adb нужно сначала включить usb-debugging в настройках. Дальнейшее развитие вполне понятно =)
В приведенной статье было проанализировано достаточно большое количество приложений из разных магазинов, на слайде приведен дополненный нами результат.
Стоит добавить, что вредоносному приложению для реализации подобного вида атак не требуется каких-либо повышенных привилегий и не нужно запрашивать какие-либо permissions.
Из-за того, что приложение использует только стандартные разрешенные способы взаимодействия с приложениями, Google без каких либо проблем и ограничений пропускает такие приложения в Play Market.
Время проверки нашего приложения перед его публикацией составило 3 часа.
После выкладывания приложения в google play энтузиасты стали присылать информацию о версиях ОС и устройствах на которых это всё работает. За что им огромный респект. Но одновременно стало ясно что всё плохо и работает это практически везде. Единственным исключением на данный момент стал cyanogenmod, на котором не заработала подмена приложения из лаунчера.
Стоит добавить, что вредоносному приложению для реализации подобного вида атак не требуется каких-либо повышенных привилегий и не нужно запрашивать какие-либо permissions.
Кроме того, как ранее сказал Юрий уязвимы все приложения, в том числе системные.
По итогам нашего исследования, был заведен баг в команду Android, он подтвержден, сейчас находится в работе.
Но, прошло уже 90 дней с момента публикации уязвимости и в соответствии с политикой google можем рассказать про найденную уязвимость.
Кстати, Anfroid определил эту уязвимость, как уязвимость среднего уровня. Ну что же, конечно, возможность подменить любое приложение, в том числе и системное – это конечно не серьёзная уязвимость.
Для предотвращения уязвимостей, связанных с TaskHijacking, можно придерживаться следующих правил:
Случай 1:
Не указывать для activity launchMode = singleTask
Не использовать FLAG_ACTIVITY_NEW_TASK. Если же его использование необходимо, то использовать его вместе с флагом FLAG_ACTIVITY_CLEAR_TOP
Случай 4:
Желательно не использовать allowReparenting и указания taskAffinity
Случай 5:
Использовать явные интенты, если известна конечная Activity
Проверять, какая из Activity запущена при привязке к другому приложению
Во всех остальных случаях, ждем исправления от AndroidTeam, но наша идея на данный момент заключается в том, что можно сделать сервис, который бы мониторил список task в системе и определял в нем наличие task с taskaffinity = id вашего приложения.
На данный момент эту уязвимость нельзя закрыть.