Android: куда смартфоны сливают данные, 10 вопросов о Kotlin и запуск shell-кода с помощью JIT

ATR

Величайший
PR-group
ПРОВЕРЕННЫЙ ПРОДАВЕЦ
ДРУЗЬЯ ФОРУМА

ATR

Величайший
PR-group
ПРОВЕРЕННЫЙ ПРОДАВЕЦ
ДРУЗЬЯ ФОРУМА
Регистрация
30 Июн 2018
Сообщения
2,003
Реакции
722
Репутация
16
Сегодня в выпуске: выполнение shell-кода с помощью JIT-компилятора, рассказ о том, куда смартфоны сливают данные, десять самых популярных вопросов о Kotlin, рассказ о WorkManager и Slices, представленных на Google I/O, и, конечно же, подборка свежих инструментов и библиотек.

Инструменты

  • Пожалуйста Авторизуйтесь или Зарегистрируйтесь для просмотра скрытого текста.

    — основанный на Frida инструмент для динамического анализа приложений под Android. Позволяет быстро сгенерировать скрипт и внедрить его в приложение;

  • Пожалуйста Авторизуйтесь или Зарегистрируйтесь для просмотра скрытого текста.

    — модуль Node.js и утилита командной строки для подписи приложений iOS (файлов IPA);

  • Пожалуйста Авторизуйтесь или Зарегистрируйтесь для просмотра скрытого текста.

    — скрипт для извлечения расшифрованных iOS-приложений с устройства;

  • Пожалуйста Авторизуйтесь или Зарегистрируйтесь для просмотра скрытого текста.

    — огромное количество документов и книг, посвященных безопасности iOS.

Почитать

Выполнение shell-кода с помощью JIT-компилятора


Пожалуйста Авторизуйтесь или Зарегистрируйтесь для просмотра скрытого текста.

— презентация, посвященная внедрению shell-кода в Android путем эксплуатации уязвимости в виртуальной машине Dalvik. Как мы все знаем, приложения для Android написаны на Java (или Kotlin) и скомпилированы в так называемый байт-код, исполняет который, в отличие от машинных инструкций, не процессор напрямую, а виртуальная машина.

В Android эта виртуальная машина изначально носила имя Dalvik, но была достаточно медлительной из-за того, что интерпретировала байт-код последовательно. С выходом версии Android 2.1 разработчики это поправили, внедрив в Dalvik так называемый JIT-компилятор (в версии Android 5.0 он был заменен на AOT-компилятор, но в 7.0 вернулся). Он транслирует в машинные инструкции целые куски байт-кода, так что виртуальной машине не приходится делать это последовательно, расходуя драгоценное время.

Оказалось, однако, что JIT-компилятор уязвим к подмене кода. То есть скомпилированные им фрагменты машинных инструкций можно подменить, заставив виртуальную машину выполнить совершенно другой код.

Виртуальная машина хранит ссылки на участки памяти со скомпилированными машинными инструкциями в структурах JitEntry, которые организованы в таблицу JitTable. Используя рефлексию и классы libcore.io.Posix (содержит методы mmap и munmap для манипуляции памятью), а также классы libcore.io.Memory и libcore.io.MemoryBlock, в память можно загрузить shell-код, а затем подменить адрес в JitEntry, чтобы вместо актуального машинного кода он ссылался на shell-код.

Механизм выполнения shell-кода отличается в разных версиях Android, но возможен и в 4.4.4, и в 7.1. А вот в Android P, скорее всего, начнутся проблемы, так как она запрещает вызов закрытых от сторонних приложений API. Также авторы исследования не упомянули, работает ли этот метод в отношении других приложений и системы в целом или только текущего процесса. Ведь Android запускает каждое приложение в собственной виртуальной машине, и память одной виртуальной машины не пересекается с другой.

К каким доменам чаще всего подключаются смартфоны


Пожалуйста Авторизуйтесь или Зарегистрируйтесь для просмотра скрытого текста.

— очень короткая, но занятная статья о том, какие веб-сайты и сервисы следят за пользователями Android. Автор написал блокировщик рекламы для смартфонов Samsung, который перенаправляет DNS-запросы устройства на специальные DNS-серверы. Вместо IP-адресов рекламных и трекинговых сетей эти серверы возвращают 127.0.0.1, чем блокируют рекламу и трекеры.

Спустя несколько месяцев работы серверы накопили статистику заблокированных адресов. И первые три места с большим отрывом занимают следующие адреса:
Facebook, как всегда, впереди.
1.png

Разработчику

10 самых популярных вопросов о Kotlin


Пожалуйста Авторизуйтесь или Зарегистрируйтесь для просмотра скрытого текста.

— десять (на самом деле девять) наиболее часто задаваемых вопросов о Kotlin на Stack Overflow и ответы на них. Приводим очень краткую выжимку (лучше все-таки почитать оригинал).

1. Чем отличаются Array и IntArray?

Первый создает массив высокоуровневого типа Integer, второй — примитивного типа int. IntArray более высокопроизводительный и рекомендуется к использованию в любых ситуациях.

2. Чем отличается Iterable и Sequence?

Iterable — это стандартный интерфейс Java. Реализующие его классы (List и Set, например) обрабатывают всю коллекцию целиком, что может плохо сказаться на производительности. Например, следующий код выполнит две операции (filter и map) над всеми элементами списка, перед тем как взять первые пять элементов (take):

val people: List<Person> = getPeople()
val allowedEntrance = people
.filter { it.age >= 21 }
.map { it.name }
.take(5)

Sequence, с другой стороны, обрабатывает коллекции в ленивом режиме. Такой подход позволяет более эффективно обрабатывать коллекции в несколько проходов (как в этом примере) и выполнять процессинг только необходимого количества элементов:

val people: List<Person> = getPeople()
val allowedEntrance = people.asSequence()
.filter { it.age >= 21 }
.map { it.name }
.take(5)
.toList()

В отличие от предыдущего, этот код будет обрабатывать каждый элемент списка отдельно, до тех пор пока не наберется пять элементов.

Что использовать? В случае небольших коллекций Iterable показывает лучшую производительность. Но если ты имеешь дело с очень большой коллекцией, тогда лучше задуматься о применении Sequence. Если же тебе нужна генерируемая на лету бесконечная коллекция, то Sequence (с его функцией generateSequence()) — твой единственный выход.

3. Проход по элементам коллекции

В Kotlin есть множество способов обойти все элементы коллекции в цикле. Однако самые производительные из них следующие:

for (arg in args) {
println(arg)
}

args.forEach { arg ->
println(arg)
}

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

Вариант с индексами:

for ((index, arg) in args.withIndex()) {
println("$index: $arg")
}

args.forEachIndexed { index, arg ->
println("$index: $arg")
}


4. SAM-преобразования

Как и Java 8, Kotlin поддерживает SAM-преобразования (Single Abstract Method). Это значит, что вместо такого кода:

button.setListener(object: OnClickListener {
override fun onClick(button: Button) {
println("Clicked!")
}
})

можно написать такой:

button.setListener {
println("Clicked!")
}

и компилятор поймет, что к чему.

Но есть несколько нюансов.
  • Если ты используешь SAM-преобразование, ты не можешь обратиться к анонимному объекту, созданному в процессе (объекту OnClickListener, если говорить о примере выше).
  • Ты можешь столкнуться с ошибкой компилятора, который заявит, что тип возвращаемого значения не совпадает. Такое происходит, если оригинальный метод требует вернуть значение определенного типа, а внутри лямбды ты вызываешь функцию, которая возвращает другой тип. В этом случае надо лишь добавить в конце лямбды значение нужного типа, например true или false, если необходимо вернуть Boolean.
5. Статические поля и методы

В Kotlin нет поддержки статических полей и методов. Если тебе необходимо создать класс, содержащий только статические члены, просто объяви его как объект:

object Foo {
fun x() { ... }
}


Если же нужно сделать статическими только отдельные поля и методы, используй companion object:

class Foo {
companion object {
fun x() { ... }
}
fun y() { ... }
}

Если какой-то метод должен быть запущен только один раз независимо от количества созданных на основе класса объектов, используй статический инициализатор:

class X {
companion object {
init {
println("Static initialization!")
}
}
}

6. Умное приведение типов и null

Если ты попробуешь сделать так:

class Dog(var toy: Toy? = null) {
fun play() {
if (toy != null) {
toy.chew()
}
}
}

Kotlin сообщит тебе, что не может использовать умное приведение типов, потому что toy — это изменяемое свойство. Так происходит потому, что компилятор не может быть уверен, что между проверкой toy на null и вызовом метода chew() другой поток не сделает toy = null.

Чтобы это исправить, достаточно сделать так:

class Dog(var toy: Toy? = null) {
fun play() {
it?.chew()
}
}

Или так:

class Dog(var toy: Toy? = null) {
fun play() {
toy?.let {
it.chew()
}
}
}

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

7. Что конкретно делает оператор !!?

Оператор !!, заставляющий среду исполнения Kotlin выполнить код даже в том случае, если его левая часть равна null, на самом деле выполняет то же самое, что такая функция-расширение:

fun <T> T?.forceNonNull(): T {
if (this == null) {
throw KotlinNullPointerException("Oops, found null")
} else {
return this as T
}
}

Следующие две строки равнозначны:
airdate!!.getWeekday()
airdate.forceNonNull().getWeekday()

8. Что делать с аргументами при переопределении функции Java?

Программируя на Kotlin, ты всегда знаешь, может ли аргумент метода быть null. Но что делать, если ты переопределяешь метод, написанный на Java — языке, который не умеет сообщать, может ли аргумент быть null?

В этом случае тебе ничего не остается, кроме как всегда проверять аргументы на null. Это избыточно и не очень красиво выглядит в коде, но лучше перестраховаться.

9. Как использовать несколько функций-расширений с одинаковыми именами?

Одна из самых интересных возможностей Kotlin — это функции-расширения. Ты можешь в любое время добавить к любому доступному тебе классу любой дополнительный метод, независимо от того, имеешь ты доступ к коду класса или нет.

Но возникает одна проблема: если ты захочешь использовать две функции-расширения с одинаковыми именами, ты не сможешь обратиться к ним, используя полное имя пакета (например, com.example.code.indent()). В этом случае следует сделать так:
import com.example.code.indent as indent4
import com.example.square.indent as indent2
То есть просто импортировать функции под разными именами.

Список доступных для использования системных API


Пожалуйста Авторизуйтесь или Зарегистрируйтесь для просмотра скрытого текста.

. Разработчики Android уже рассказывали об ограничениях, которые Android P будет накладывать на использование недокументированных/скрытых API. Вкратце: если ты попытаешься использовать рефлексию, чтобы получить доступ к скрытым от сторонних приложений API, то ОС выбросит исключение NoSuchFieldException или NoSuchMethodException.

Поначалу эта функция будет работать только в отношении очень редко используемых или совсем не используемых API. Однако в следующих версиях Google начнет ужесточать правила и расширять запрет на все большее количество API, предлагая взамен открытую для использования альтернативу.

Какие API доступны, а какие уже нельзя использовать? Вот так называемый

Пожалуйста Авторизуйтесь или Зарегистрируйтесь для просмотра скрытого текста.

. В нем более 11 тысяч методов и полей.

Что такое Slices в Android P и как их использовать?


Пожалуйста Авторизуйтесь или Зарегистрируйтесь для просмотра скрытого текста.

— статья с рассказом о так называемых слайсах (Slice), новой функции Android, которая появилась в Android P, но вряд ли будет активирована к релизу (а может, и будет).

Слайсы — это часть новой подсистемы Actions on Google, которая позволяет разработчикам интегрировать свои приложения в Google Assistent. Работает это так: Google придумала ряд

Пожалуйста Авторизуйтесь или Зарегистрируйтесь для просмотра скрытого текста.

, таких как actions.intent.PLAY_GAME и actions.intent.GET_CRYPTOCURRENCY_PRICE, которые отправляются приложениям, когда пользователь делает определенный запрос в ассистенте. В данном случае это может быть что-то вроде «хочу поиграть в игру» и «цена биткойна».

Ассистент обрабатывает запрос, вычленяет из него семантическую часть, затем рассылает приложениям соответствующий интент. Приложение может ответить на этот интент с помощью SliceProvider’а, который позволяет запрограммировать карточку с информацией для Google Assistent. Эту карточку Google Assistent выведет на экран.

1a.png
Пример слайса с ответом на запрос «цена биткойна»​

Как работать с WorkManager


Пожалуйста Авторизуйтесь или Зарегистрируйтесь для просмотра скрытого текста.

— хорошая краткая статья о том, как использовать WorkManager, новую support-библиотеку Google для выполнения фоновой работы.

WorkManager был разработан как ответ на бардак в средствах фонового исполнения в разных версиях Android. До Android 5.0 нам предлагали использовать AlarmManager и сервисы для выполнения фоновых задач. Начиная с Android 5.0 появился JobScheduler, который толком не работал до версии Android 6.0, и вместо него приходилось использовать Firebase JobDispatcher, хотя сервисы продолжали нормально поддерживаться вплоть до версии Android 8, где Google ввела ограничение на их исполнение в несколько минут при уходе приложения в сон или получении push-уведомления.

WorkManager скрывает все эти нюансы и предлагает простой в использовании API для запуска фоновых задач. В зависимости от версии Android, на которой будет запущено приложение, он сам выберет лучший способ исполнения задачи, позволив тебе выбросить всю эту кашу из головы.

В простейшем случае создание и запуск задачи с помощью WorkManager выглядит так. Создаем задачу, наследуясь от класса Worker:

class YourWorker: Worker {
override fun WorkerResult doWork() {
// Делаем свои дела
return WorkerResult.SUCCESS
}
}

Затем создаем запрос на запуск задачи и ставим ее в очередь:

val work: OneTimeWorkRequest = OneTimeWorkRequest.Builder(YourWorker::class.java).build()
WorkManager.getInstance().enqueue(work)

Так как мы не указали никаких условий выполнения задачи, она будет выполнена сразу.

Добавим условия:

val constraints: Constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresCharging(true)
.build()

val work: OneTimeWorkRequest = OneTimeWorkRequest.Builder(SomeWorker::class.java)
.setConstraints(constraints)
.build()

Чтобы запустить периодическую (повторяющуюся) задачу, используем PeriodicWorkRequest:

val recurringWork: PeriodicWorkRequest = PeriodicWorkRequest.Builder(YourWorker::class.java, 3, TimeUnit.HOURS).build()
WorkManager.getInstance().enqueue(recurringWork)

Задачи также можно объединять в цепочки:

WorkManager.getInstance()
.beginWith(firstWork)
.then(secondWork)
.then(thirdWork)
.enqueue()

Инструменты

  • Пожалуйста Авторизуйтесь или Зарегистрируйтесь для просмотра скрытого текста.

    — утилита для манипуляции Android App Bundle, позволяет собирать, разбирать бандлы и генерировать APK для разных устройств;

  • Пожалуйста Авторизуйтесь или Зарегистрируйтесь для просмотра скрытого текста.

    — инструмент для запуска нескольких Android-эмуляторов одновременно;

  • Пожалуйста Авторизуйтесь или Зарегистрируйтесь для просмотра скрытого текста.

    — скрипт-обертка для ADB, позволяющий выполнить множество различных действий: включение/выключение Doze, мобильных данных, режима полета, разрешений, нажатие кнопок, снятие скриншотов и многие другие.


Библиотеки
Евгений Зобнин​
 
Сверху