Сегодня в выпуске: выполнение shell-кода с помощью JIT-компилятора, рассказ о том, куда смартфоны сливают данные, десять самых популярных вопросов о Kotlin, рассказ о WorkManager и Slices, представленных на Google I/O, и, конечно же, подборка свежих инструментов и библиотек.
 

Инструменты

  • House — основанный на Frida инструмент для динамического анализа приложений под Android. Позволяет быстро сгенерировать скрипт и внедрить его в приложение;
  • node-applesign — модуль Node.js и утилита командной строки для подписи приложений iOS (файлов IPA);
  • frida-ipa-dump — скрипт для извлечения расшифрованных iOS-приложений с устройства;
  • iOS writeups — огромное количество документов и книг, посвященных безопасности iOS.
 

Почитать

 

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

Back To The Future | Going Back In Time To Abuse Android’s 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 запускает каждое приложение в собственной виртуальной машине, и память одной виртуальной машины не пересекается с другой.

 

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

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

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

  • graph.facebook.com
  • mobile.pipe.aria.microsoft.com
  • www.googleadservices.com

Facebook, как всегда, впереди.

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

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

 

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

Top 10 Kotlin Stack Overflow questions — десять (на самом деле девять) наиболее часто задаваемых вопросов о 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

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

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

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

 

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

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

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

  1. Google придумала ряд новых интентов, таких как actions.intent.GET_CRYPTOCURRENCY_PRICE и actions.intent.PLAY_GAME, которые отправляются приложениям, когда пользователь делает определенный запрос в ассистенте. В данном случае это может быть что-то вроде «хочу поиграть в игру» и «цена биткойна».
  2. Ассистент обрабатывает запрос, вычленяет из него семантическую часть, затем рассылает приложениям соответствующий интент. Приложение может ответить на этот интент с помощью SliceProvider’а, который позволяет запрограммировать карточку с информацией для Google Assistant. Эту карточку Google Assistant выведет на экран.
Пример слайса с ответом на запрос «цена биткойна»
Пример слайса с ответом на запрос «цена биткойна»
 

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

Doing work with Android’s new 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()
 

Инструменты

  • Bundletool — утилита для манипуляции Android App Bundle, позволяет собирать, разбирать бандлы и генерировать APK для разных устройств;
  • Swarmer — инструмент для запуска нескольких Android-эмуляторов одновременно;
  • adb-enhanced — скрипт-обертка для ADB, позволяющий выполнить множество различных действий: включение/выключение Doze, мобильных данных, режима полета, разрешений, нажатие кнопок, снятие скриншотов и многие другие.
 

Библиотеки

  • Android-IO18 — коллекция ссылок на презентации, документацию, семплы кода и все, что было связано с Android на Google I/O 2018;
  • CryptoPrefs — библиотека для шифрования настроек Android, созданных с помощью SharedPrefences;
  • JsonToKotlinClass — плагин Android Studio для генерации data-классов Kotlin из JSON;
  • KotlinTest — удобный фреймворк для тестирования приложений, написан на Kotlin с применением DSL;
  • KTFLITE — приложение-пример, демонстрирующее использование Tensorflow Lite для реализации компьютерного зрения;
  • PhotoEditor — библиотека с реализацией редактора фотографий: инструменты рисования, фильтры, эмоджи-стикеры и другое;
  • AndroidButtonProgress — два в одном: кнопка и прогрессбар;
  • FilePicker — диалог выбора файлов;
  • TheGlowingLoader — красивый индикатор загрузки;
  • morph-bottom-navigation — нижняя панель навигации;
  • SaveState — библиотека для автоматического сохранения состояния без необходимости реализовать методы onSaveInstanceState и onRestoreInstanceState.

Оставить мнение

Check Also

Энкодеры msfvenom. Разбираемся с кодированием боевой нагрузки при бинарной эксплуатации

Генерация полезной нагрузки — неотъемлемая часть эксплуатации. При использовании модулей M…