Перехват информации всегда был в тренде и по множеству шпионких фильмов мы не раз видели как спец службы могут подключится к любому мобильному устройству. На самом деле это задача очень не простая и по просочившимся в прессу кейсам мы понимаем что это могут позволить себе далеко не рядовые полицейские. А если посмотреть с другой стороны? Могут ли хакеры сделать тоже самое? Как показывает практика иследования уязвимости StrandHogg в Android это вполне осуществимая задача. О том как получить доступ к камере и микрофону телефона сегодня и пойдет речь.
StrandHogg — уязвимость с «подменой» приложений
The StrandHogg vulnerability — подробности нашумевшей уязвимости всех версий Android (включая 10), позволяющей подменить экран (активность) одного приложения на экран другого.
Что произошло: специалисты норвежской компании Promon обнаружили в Android уязвимость, которая позволяет подменить экран (активность) легитимного приложения, подсунув вместо нее экран зловредного приложения. В результате у злоумышленников появляется возможность провести атаку с использованием фишинга или, например, запросить для зловредного приложения дополнительные полномочия якобы от лица другого приложения.
Что произошло на самом деле: в Android у активностей есть флаг taskAffinity
. Он позволяет указать имя задачи (task), в стек активностей которой попадет указанное приложение. Это нужно для более тонкого управления стеками обратных переходов. Но есть одна проблема: по умолчанию значение taskAffinity
равно имени пакета приложения, а это значит, что в ряде случаев можно незаметно всунуть активность своего приложения в стек активностей чужого. А если еще и указать флаг allowTaskReparenting="true"
, эту активность можно передвинуть на самый верх и при следующем клике по иконке целевого приложения она появится на экране первой (то есть будет находиться на вершине стека).
Страшно? Конечно, но, как и обычно, стоит копнуть глубже — и страх пройдет.
Во-первых, об этой «уязвимости» известно уже много лет. Предыдущая ее вариация называлась Android Task Hijacking.
Во-вторых, эксплуатация уязвимости требует, чтобы и зловредное приложение, и приложение-жертва были уже запущены. Последовательность действий пользователя должна быть такой: он запускает приложение-жертву, возвращается на рабочий стол, затем запускает зловредное приложение, затем снова запускает приложение-жертву. В этот момент вместо холодного запуска Android просто показывает самую «верхнюю» активность в стеке, а ей оказывается активность зловредного приложения.
В-третьих, Google действительно отказывается исправлять уязвимость на протяжении нескольких лет. Просто потому, что это так называемый design flaw, то есть ошибка проектирования, исправление которой сломает существующий софт.
Наконец, в-четвертых, у проблемы уже давно есть решение. Достаточно указать taskAffinity=""
в элементе Application
, и все активности твоего приложения станут неуязвимы к атаке.
Мораль: не читайте советских газет, читайте доки по безопасности.
Обход защиты на снятие скриншотов
Android Frida hooking: disabling FLAG_SECURE — статья о том, как с помощью Frida отключить защиту на снятие скриншота приложения.
Кратко: Android позволяет разработчику приложения запретить снимать скриншоты определенных активностей приложения. Для этого необходимо установить флаг FLAG_SECURE
для окна:
public class FlagSecureTestActivity extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); setContentView(R.layout.main); ... } }
Обойти эту защиту можно несколькими способами. Например, использовать модуль Xposed DisableFlagSecure, который перехватывает функцию setFlags
и просто отфильтровывает флаг FLAG_SECURE
:
@Override public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { XposedHelpers.findAndHookMethod(Window.class, "setFlags", int.class, int.class, mRemoveSecureFlagHook); } private final XC_MethodHook mRemoveSecureFlagHook = new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { Integer flags = (Integer) param.args[0]; flags &= ~WindowManager.LayoutParams.FLAG_SECURE; param.args[0] = flags; } };
Однако Xposed требует права root на устройстве (есть проект VirtualXposed, не требующий root, но многие модули в нем не работают). Frida, с другой стороны, может работать на любом устройстве без необходимости получать права root.
Вот как выглядит тот же код отключения в варианте для Frida:
Java.perform(function () { var FLAG_SECURE = 0x2000; var Window = Java.use("android.view.Window"); var setFlags = Window.setFlags; setFlags.implementation = function (flags, mask) { console.log("Disabling FLAG_SECURE..."); flags &= ~FLAG_SECURE; setFlags.call(this, flags, mask); }; });
Использовать так:
$ frida -U -l наш_скрипт.js -f имя.пакета.приложения --no-pause
Обход ограничения на доступ к камере, микрофону и местоположению в фоне
Androids Invisible Foreground Services and How to (Ab)use Them — доклад с конференции Black Hat Europe 2019 с описанием очень простой техники обхода защиты на доступ к камере, микрофону и местоположению в Android 9 и 10.
Проблема: в Android 9 появилось ограничение на доступ к камере и микрофону, если в данный момент приложение находится в фоне. В Android 10 добавилось ограничение на доступ к местоположению с возможностью выбрать, хочет ли пользователь, чтобы приложение могло получать координаты, только пока видимо на экране, или в фоне тоже.
Исключение дается только приложениям, имеющим активный foreground-сервис, который, в свою очередь, обязан вывести видимое пользователю уведомление и иконку в строке состояния.
Если мы хотим создать гипотетическую малварь, лучше оставаться скрытными и никаких уведомлений не показывать. Но как это сделать?
Решение: начиная с пятой версии в Android есть механизм JobScheduler, который позволяет запускать код периодически или при наступлении определенных событий (например, подключение к зарядному устройству). Мы могли бы использовать его для периодического запуска наших шпионских функций, но задачи JobScheduler’а тоже выполняются в фоне.
Выход состоит в том, чтобы при наступлении события JobScheduler’а запустить сервис, затем сделать его foreground-сервисом с помощью startForeground
, быстро получить доступ к камере, микрофону и местоположению и остановить сервис:
@Override public int onStartCommand(Intent intent, int flags, int startId){ Notification notification = createCustomNotification(); this.startForeground(1, notification) accessMicrophone(); stopForeground(true); return START_STICKY; }
Трюк состоит в том, что, если сделать работу достаточно быстро (по утверждению автора доклада — за пять секунд), система просто не покажет уведомление.
Proof of concept есть на GitHub. Баг уже исправлен, теперь уведомление будет показано в любом случае.
Разработчику
Хорошие и плохие приемы программирования на Kotlin
Good And Bad Practices Of Coding In Kotlin — статья о практиках программирования на Kotlin, хороших и плохих.
Первый пример
fun main { val file = File("/my_file") val writer = file.printWriter() try { writer.println("Hello World") } catch(__: Exception){ // Обработка исключения } finally { writer.close() } }
Это стандартный способ записи строки в файл: открываем файл и с помощью PrintWriter
записываем строку. В завершение закрываем PrintWriter даже в том случае, если будет выброшено исключение.
Этот код можно переписать так:
fun main () { val file = File("/my_file") file.printWriter().use { it.println("Hello World") } }
В случае с объектами, реализующими интерфейс Closeable
(здесь PrintWriter
), функция-расширение use
автоматически вызывает метод close
после выполнения кода лямбды.
Второй пример
val list = listOf("Ahsen", "Bob", "Alex", "Jasmine") val anotherList = listOf(1, 2, 3, 4) fun main() { val size = list.size for(i in 0..size) println("Name -> ${list[i]} and number -> ${anotherList[i]}") }
Этот код проходит в цикле сразу по двум спискам. Но в нем есть одна проблема: если второй список будет длиннее первого, приложение упадет с исключением ArrayIndexOutOfBoundsException.
Kotlin позволяет решить эту проблему и заодно сделать код гораздо более элегантным:
val list = listOf("Ahsen", "Bob", "Alex", "Jasmine") val anotherList = listOf(1, 2, 3, 4, 5, 6, 7) fun main() { for((name, number) in list.zip(anotherList)) println("Name -> $name and number -> $number") }
Метод zip
создает набор пар (pair), где первый элемент пары — значение из первого списка, второй элемент — значение из второго с тем же индексом. В этом примере пара автоматически раскладывается на переменные name
и number
с помощью деструктивного оператора.
Третий пример
val firstList = listOf("Ahsen", "Bob", "Alex", "Jasmine") val secondList = listOf("Ahsen", "Jack", "Alen", "Jasmine") fun main() { val result = mutableListOf<String>().apply { addAll(secondList) } for(name in firstList) if(!secondList.contains(name)) result.add(name) println(result) }
Данный код объединяет два однотипных списка так, чтобы их значения не повторялись. Но в Kotlin у списков есть метод union
, предназначенный именно для этого:
val firstList = listOf("Ahsen", "Bob", "Alex", "Jasmine") val secondList = listOf("Ahsen", "Jack", "Alen", "Jasmine") fun main() { val result = firstList.union(secondList) println(result) }
Четвертый пример
const val NAME = "Ahsen" val x = NAME as Int fun main() { println(x) }
Данный код выбросит исключение ClassCastException, потому что константа NAME имеет тип не Int. Чтобы такого не происходило, можно использовать оператор as?
:
const val NAME = "Ahsen" val x = NAME as? Int fun main() { println(x) }
Если приведение типов будет невозможно, переменная x
получит значение null.
Typealias в Kotlin
Maintaining crisp code with type aliases — заметка о ключевом слове typealias, которое можно использовать для назначения произвольных имен различным типам данных.
Представим, что у нас есть список юзеров List<User>
. Использовать его в коде неудобно, автодополнение работает плохо. Поэтому мы хотели бы придумать для него более простое имя.
Традиционный способ сделать это — завернуть список в класс:
data class Users(val userList: List<User>)
Но в Kotlin есть более простой и лишенный оверхеда вариант:
typealias Users = List<User>
Typealias не вводит новый тип данных и не создает классов, это просто второе имя для определенного типа данных. Его можно использовать не только для сокращения записи, но и для разделения неймспейсов, если несколько пакетов содержат классы с одинаковыми именами.
Другие полезные примеры использования typealias:
-
Более наглядный способ объявления лямбд:
typealias Result<T> = (T) -> Unit fun fetchData(callback: Result<String>)
-
Маркировка nullable-типов:
typealias MaybeBook = Book?
-
Быстрый доступ к вложенным классам:
typealias DialogBuilder = AlertDialog.Builder val dialog = DialogBuilder().build()
Инлайновые классы в Kotlin
Low-overhead wrappers using inline classes — заметка, рассказывающая, как использовать инлайновые классы Kotlin, чтобы защититься от ошибок и улучшить читаемость кода.
Представим, что у нас есть такая функция:
fun renderLocation(latitude: Double, longitude: Double) { map.render(latitude, longitude) }
На первый взгляд все хорошо, но есть проблема: что, если ты случайно передашь ей долготу вместо широты и широту вместо долготы? Они обе имеют тип Double
, поэтому у компилятора не будет возможности предупредить тебя об ошибке.
Помогут классы-врапперы:
class Latitude(val value: Double) class Longitude(val value: Double) fun renderLocation(latitude: Latitude, longitude: Longitude) { map.render(latitude, longitude) }
Но так ты получишь оверхед, компилятору придется создать, по сути, два лишних объекта и выделить для них память в хипе.
Для решения этой проблемы в Kotlin 1.3 появились так называемые инлайновые классы:
inline class Latitude(val value: Double) inline class Longitude(val value: Double) fun renderLocation(latitude: Latitude, longitude: Longitude) { map.render(latitude, longitude) }
Теперь компилятор сможет автоматически заменить все использования объектов классов Latitude
и Longitude
на Double
. Вот так будет выглядеть функция renderLocation
, декомпилированная в Java:
public static final void renderLocation-vKZqJUM(double latitude, double longitude) { map.render-vKZqJUM(latitude, longitude); }
Обрати внимание на -vKZqJUM
в конце имени функции. Это хеш, который добавляет компилятор ко всем функциям, принимающим инлайновые классы в качестве аргументов. Он нужен, чтобы в коде случайно не появились две одинаковые функции с одинаковыми аргументами (например, если ты создаешь еще одну функцию renderLocation
с двумя аргументами типа Double
).
Корректное выделение текста на фоне
Contrasting text and icons over background — статья о том, как сделать текст и иконки читаемыми на разном фоне. Автор предлагает алгоритм автоматического выбора темного или светлого цвета текста в зависимости от используемого фона.
Для начала создадим простую функцию-расширение для определения «уровня темности» фона:
fun @receiver:ColorInt Int.isDark(): Boolean = ColorUtils.calculateLuminance(this) < 0.5
Функция использует класс ColorUtils
из библиотеки androidx.core и возвращает true, если фон достаточно темный, чтобы белый текст на нем читался хорошо.
Использовать эту функцию можно так:
val color = Color.parseColor("#e91e63") if (color.isDark()) { colorHex.setTextColor(R.color.white) favIcon.setImageResource(R.drawable.ic_favorite_border_white_24dp) } else { colorHex.setTextColor(R.color.black) favIcon.setImageResource(R.drawable.ic_favorite_border_black_24dp) }
Здесь color
— это цвет фона, а colorHex
и favIcon
— текстовый виджет и виджет иконки. Если цвет темный, текст и иконка окрашиваются в белый, иначе — в черный.
Но что, если в качестве фона используется картинка? В этом случае мы можем извлечь из картинки наиболее значимые цвета (палитру) с помощью класса Palette
и проверить их яркость с помощью функции isDark
:
Palette.from(bitmap) .maximumColorCount(3) .clearFilters() .setRegion(bitmap.width - iconSize , 0, bitmap.width, iconSize) .generate { palette -> val lightness = ColorUtils.isDark(palette) val isDark = if (lightness == ColorUtils.LIGHTNESS_UNKNOWN) { ColorUtils.isDark(bitmap, bitmap.width - iconSize / 2, iconSize / 2) } else { lightness == ColorUtils.IS_DARK } if (isDark) { // Make back icon dark on light images favIcon.setImageResource(R.drawable.ic_favorite_border_white_24dp) } else { favIcon.setImageResource(R.drawable.ic_favorite_border_black_24dp) } }
Предполагается, что мы размещаем нашу иконку в правом верхнем углу картинки, поэтому с помощью функции setRegion
указываем правый верхний угол. Затем с помощью ColorUtils.isDark
мы выясняем, темная ли это палитра. Если выяснить это не удалось, пробуем определить яркость отдельно взятого пикселя посередине интересующей нас области. Затем мы меняем цвет иконки в зависимости от полученных результатов.
Стоит отметить, что в стандартном классе ColorUtils
из пакета androidx.core нет метода isDark. В данном примере используется класс ColorUtils из проекта Plaid.
Статистика распределения версий Android
В мае этого года Google перестала публиковать статистику по версиям Android. Однако PornHub исправил этот недочет, опубликовав собственную статистику.
Что следует иметь в виду при изучении этих данных:
- Статистика Google покрывает все устройства, которые так или иначе подключались к Google Play. Среди этих устройств есть множество «звонилок», старых смартфонов, которые люди продолжают использовать спустя много лет после релиза. Ведь звонить, писать и просматривать сводку погоды можно с комфортом и на устройстве десятилетней давности.
- Статистика PornHub, кроме устройств с сервисами Google, также покрывает несертифицированные устройства и устройства с кастомными прошивками, но без сервисов Google.
- Основная масса пользователей PornHub — люди в возрасте от 18 до 34 лет (данные из той же статистики). Другими словами, это те самые «активные» пользователи смартфонов, которые регулярно покупают новые устройства.
Инструменты
- Android Malware Sandbox — виртуальная машина для быстрого запуска малвари.
Библиотеки
- Keigen — быстрая библиотека линейной алгебры;
- Extensions-list — список всех функций-расширений, доступных в библиотеках Android-KTX;
- Store — библиотека для загрузки, кеширования и обновления данных из сети;
- Tinker — библиотека для обновления кода и ресурсов приложения без обновления всего пакета;
- FlowBinding — аналог RxBinding на основе Kotlin Flow;
- AestheticDialogs — различные виды диалогов;
- Flourish — библиотека для показа/скрытия лайотов с анимацией;
- MaterialDialog-Android — библиотека для создания диалогов в стиле Material Design;
- ParallaxScrollingView — галерея с эффектом параллакса.
1 comments On Как получить доступ к камере и микрофону Android телефона.
Мне очень нравится VirtualXposed. Это позволяет мне использовать модуль Youtube Adaway Xposed без доступа пользователя root.