Сегодня в выпуске: история о том, как Google безуспешно борется с малварью, статья о платной разблокировке загрузчика Huawei, гайд по борьбе с упаковщиками с помощью Frida. А также: подборка трюков и советов для Kotlin-программистов, инструменты профайлинга сетевых функций, лучшее объяснение отличия короутин от потоков и, конечно же, очередная подборка свежих библиотек и инструментов.

 

Почитать

 

Как Google борется с малварью

Combating Potentially Harmful Applications with Machine Learning at Google: Datasets and Models — рассказ разработчиков команды безопасности Android о том, как работает система Google Play Protect, которая выявляет вредоносные приложения в Google Play и на смартфонах пользователей. Несколько тезисов:

  • Google анализирует не только приложения из Google Play, но и любые APK-файлы, найденные в интернете.
  • Для каждого приложения запускаются процедуры статического и динамического анализа, которые выявляют определенные шаблоны: запрашиваемые разрешения, поведение приложения в тех или иных обстоятельствах.
  • Данные, полученные от статических и динамических анализаторов, передаются ИИ, натренированному на выявление определенных типов зловредных приложений: SMS-фрод, фишинг, повышение привилегий.
  • Кроме данных о самих приложениях, Google также собирает и агрегирует данные о приложении из Google Play: средняя оценка приложений разработчика, рейтинги, количество установок и удалений; эта информация также передается ИИ.
  • На основе всех этих данных ИИ выносит решение о том, может ли приложение быть потенциально зловредным.
  • Google постоянно совершенствует ИИ, скармливая ему данные свежевыявленных зловредов.

На фоне всей этой бравады стоит напомнить, что в тестах антивирусов Google Play Protect набирает 0 очков и плетется в конце рейтинга. По информации на январь 2018-го он смог обнаружить лишь 63% вирусов.

 

Huawei и платная разблокировка загрузчика

Bootloader unlocking is still possible for Huawei and Honor devices, but it’ll cost you — интересная заметка о страданиях владельцев смартфонов Huawei, желающих разблокировать загрузчик своего устройства.

В мае этого года Huawei официально заявила, что больше не будет предоставлять коды для разблокировки загрузчиков своих смартфонов (а также смартфонов своего суббренда Honor). Загрузчик отвечает за проверку целостности и цифровой подписи ядра ОС, а также за возможность прошивки устройства, так что те, кто не успел получить свой код разблокировки, теперь не могут установить на нее кастомную прошивку или даже получить права root с помощью Magisk.

Выяснилось, однако, что совсем недавно сервис FunkyHuawei, занимающийся выпуском утилит для восстановления окирпиченных смартфонов и смены их региона, начал предоставлять возможность разблокировки загрузчика всех моделей Huawei, включая самые свежие. Но есть один нюанс: стоимость кода разблокировки составляет 55 долларов, и он привязан к IMEI устройства, а значит, не может быть использован повторно.

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

 

Как использовать Frida для обхода упаковщиков

How-to Guide: Defeating an Android Packer with FRIDA — вводная статья об использовании Frida для помощи в анализе вируса.

Дано: вредоносное приложение с подозрительным файлом внутри пакета и небольшим сильно обфусцированным исполняемым dex-файлом. Анализ логов запуска logcat показывает, что приложение при работе создает и загружает еще один dex-файл (запакованный в JAR), а это значит, что, скорее всего, первый исполняемый файл — это всего лишь загрузчик (а точнее, упаковщик), а найденный ранее подозрительный файл — зашифрованный код приложения. При загрузке приложения упаковщик дешифрует файл и загружает его. Но есть одна проблема — сразу после загрузки дешифрованный файл удаляется и его невозможно проанализировать.

К сожалению, декомпиляция и статический анализ упаковщика ничего не дают — он слишком сильно обфусцирован и почти непригоден для чтения. Однако запуск приложения под управлением трассировочной утилиты strace показывает, что удаление происходит с помощью системного вызова unlink.

Конечная идея — переопределить с помощью Frida код функции unlink так, чтобы она ничего не удаляла. В этом случае исследователь сможет просто достать расшифрованный dex-файл из устройства и проанализировать его. Код функции перехвата для Frida:

console.log("[*] FRIDA started");
console.log("[*] skip native unlink function");

var unlinkPtr = Module.findExportByName(null, 'unlink');

Interceptor.replace(unlinkPtr, new NativeCallback(function (){  
    console.log("[*] unlink() encountered, skipping it.");
}, 'int', []));
Трассировочный листинг, показывающий создание и удаление файла с расшифрованным кодом приложения
Трассировочный листинг, показывающий создание и удаление файла с расшифрованным кодом приложения
 

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

 

Трюки с Kotlin

Kotlin fun and education on Twitter — создатель Kotlin Academy и автор книги «Android Development with Kotlin» Марсин Москала (Marcin Moskala) рассказывает об интересных трюках, которые можно провернуть в Kotlin.


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


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


Как и многие другие языки, Kotlin поддерживает перегрузку операторов.


fold — одна из самых мощных операций для работы с коллекциями. Она объединяет все элементы коллекции с помощью указанной функции. Например, с помощью fold очень легко сложить или перемножить все элементы. Но можно сделать и более интересные вещи.


Еще несколько полезных функций для выполнения операций над несколькими последовательными элементами.

 

Профайлинг сетевых функций приложения

Various methods to debug HTTP traffic in Android applications — статья о том, как выполнять профайлинг сетевых функций приложения. Автор предлагает использовать пять различных методов.

  • Android Profiler — включен в состав Android Studio. Он показывает объем входящих и исходящих данных, задержки и даже позволяет взглянуть на сами данные (если приложение использует HttpURLConnection или OkHttp). По умолчанию сетевой профайлер отключен. Чтобы включить его, необходимо зайти в меню Run → Edit Configurations, открыть таб Profiling и поставить галочку напротив Enable advanced profiling. После этого он появится в окне стандартного профайлера (запускается через нижнюю панель Android Studio).

  • OkHttp Profiler plugin — плагин Android Studio для отладки реквестов OkHttp. Умеет показывать JSON в виде дерева и генерировать модели для парсера GSON. Недостаток: требуется установка плагина и модификация приложения.

  • Stetho — инструмент отладки приложений с помощью Chrome DevTools. Кроме инструментов для анализа лайотов и баз данных, включает в себя мощный сетевой профайлер. Требует установки библиотеки и модификации приложения.

  • Charles Proxy — десктопный прокси со встроенным сниффером и множеством дополнительных функций: это мониторинг сокетов, модификация сетевых пакетов, генератор реквестов и многое другое. Требует настройки на эмуляторе/телефоне прокси и установки SSL-сертификата (в случае если требуется отладка HTTPS-трафика). К тому же стоит 50 долларов (есть триальная версия).

  • AppSpector — инструменты профайлинга для Android и iOS. Позволяют просматривать логи, изучать базы данных и сетевые запросы. Необходима модификация приложения и регистрация на сайте. Управление только через веб-сайт разработчиков, так что возникает вопрос о конфиденциальности данных.

Network Profiler в Android Studio
Network Profiler в Android Studio
 

Не используй массивы в data-классах

What you didn’t know about arrays in Kotlin — познавательная и полезная заметка о том, почему не стоит использовать массивы в data-классах в Kotlin.

Data-классы в Kotlin — очень полезный элемент языка, позволяющий быстро создавать классы, не обременяя себя написанием однотипного кода. Возьмем, к примеру, следующий код:

data class NumArray(val name: String, val values: IntArray)

Он объявляет data-класс NumArray с двумя полями. Это всего одна строка кода, но в результате ты получишь класс с уже реализованными геттерами, сеттерами и функциями equals(), hash(), toString(). Тебе не придется писать их самому, и это сильно облегчает жизнь.

Но есть одна проблема: если ты создашь два одинаковых объекта этого класса и попробуешь их сравнить, то получишь отрицательный ответ:

val n1 = NumArray("1", intArrayOf(1,2,3,4))
val n2 = NumArray("1", intArrayOf(1,2,3,4))
val result = n1==n2
println("result=$result")

Получается, что автоматически генерируемый метод equals() не работает? На самом деле это не так. Дело в том, что в JVM есть баг, который приводит к тому, что сравнение массивов и коллекций происходит по-разному. Коллекции сравниваются структурно, то есть поэлементно, а при сравнении массивов верный ответ будет только в том случае, если это действительно один и тот же массив, а не два с одинаковым набором элементов.

Поэтому вместо массивов лучше использовать списки:

data class NumList(val name: String, val values: List<Int>))
 

Parallelism vs concurrency

Concurrent Coroutines — Concurrency is not Parallelism — одна из лучших статей для тех, кто хочет разобраться, что такое короутины Kotlin. Вместо того чтобы рассказывать о стейт-машинах и математических алгоритмах, автор говорит о том, что надо просто осознать разницу между понятиями concurrency и parallelism.

Роб Пайк, один из разработчиков Unix, Plan 9 и языка Go, говорит об этих понятиях так: concurrency — это когда ты имеешь дело со множеством вещей одновременно, а parallelism — это когда ты делаешь множество вещей одновременно. Продемонстрируем это утверждение кодом:

fun main() = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = async { doSomethingUsefulOne() }
        val two = async { doSomethingUsefulTwo() }
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")    
}

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L)
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L)
    return 29
}

Здесь происходит запуск двух короутин, каждая из которых засыпает на одну секунду, а затем возвращает определенное число. Основная короутина при этом дожидается завершения обеих дочерних короутин и печатает на экран сумму двух возвращенных чисел.

Приложение заканчивает свою работу ровно за одну секунду, что абсолютно логично. Но есть один важный нюанс: оно работает в одном потоке. Когда одна из дочерних короутин блокируется (в данном случае с помощью delay, а в реальном приложении из-за ожидания данных из сети или с диска), основная короутина продолжает свою работу. Это и есть тот случай, когда приложение «имеет дело» с множеством вещей, а не делает их одновременно.

Но мы можем исправить код так, чтобы он действительно делал несколько вещей одновременно:

val time = measureTimeMillis {
    val one = async(Dispatchers.Default) { doSomethingUsefulOne() }
    val two = async(Dispatchers.Default) { doSomethingUsefulTwo() }
    println("The answer is ${one.await() + two.await()}")
}

Такой код запускает каждую короутину в отдельном потоке (Dispatchers.Main — основной поток приложения, Dispatchers.Default — один из фоновых), и да, теперь приложение делает несколько вещей одновременно.

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

 

Трюки с короутинами Kotlin

Advanced Kotlin Coroutines tips and tricks — еще одна статья о короутинах, в этот раз с советами по использованию.

1. Проблемы с Java API. Возьмем следующий пример:

runBlocking(Dispatchers.IO) {
    withTimeout(1000) {
        val socket = ServerSocket(42)
        socket.accept()
    }
}

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

Но этого не произойдет, потому что socket.accept() заблокирует поток короутины, до тех пор пока кто-нибудь действительно не подключится.

Обойти эту проблему можно с помощью suspendCancellableCoroutine. Создадим небольшую функцию-помощник:

public suspend inline fun <T : Closeable?, R> T.useCancellably(
        crossinline block: (T) -> R
): R = suspendCancellableCoroutine { cont ->
    cont.invokeOnCancellation { this?.close() }
    cont.resume(use(block))
}

И слегка изменим наш пример:

runBlocking(Dispatchers.IO) {
    withTimeout(1000) {
        val socket = ServerSocket(42)
        socket.useCancellably { it.accept() }
    }
}

Теперь все работает как надо.

2. launch vs. async. Это два самых используемых короутин-билдера в Kotlin. И тот и другой порождают новую короутину, но работают немного по-разному:

  • любое необработанное исключение в блоке launch будет воспринято как необработанное вообще и уронит приложение; исключение в блоке async можно обработать за пределами этого блока;
  • launch не позволит короутинам-родителям завершиться, пока короутины-потомки, запущенные с помощью launch, не завершатся;
  • async позволяет вернуть значение, launch — нет.
 

Удобочитаемый logcat

How To Customize Logcat Appearance in Android Studio — короткая заметка о том, как раскрасить вывод logcat в Android Studio для более удобного чтения. Для этого достаточно перейти в настройки, набрать в поиске logcat, и ты увидишь список уровней логирования от Debug до Assert. Выбирай один из них, отключай галочку Inherit values from и в опциях Foregraund, Background и других выбирай нужные цвета.

Автор статьи предлагает следующую схему цветов:

  • ASSERT: #bb2b2f;
  • DEBUG: #1194d6;
  • ERROR: #db332f;
  • INFO: #0c890d;
  • VERBOSE: #a8a8a8;
  • WARNING: #bb7000.

Также он создал цветовую схему на базе тем Default и Dracula.

 

Инструменты

  • frida-snippets — набор скриптов Frida для Android, iOS и Windows;
  • MagiskFrida — скрипт для запуска сервера Frida при загрузке с помощью Magisk;
  • ADBHoney — ханипот, эмулирующий доступный на 5555-м порте демон ADB;
  • ish — приложение для запуска Linux-окружения в iOS (использует эмуляцию x86 и трансляцию системных вызовов Linux → XNU);
  • androix-migration — скрипт для автоматической миграции с support-библиотек на AndroidX, по словам автора, работает лучше, чем аналогичный конвертер в Android Studio;
  • autoplay — плагин Gradle для автоматической публикации приложений в Google Play.
 

Библиотеки

  • AndroidVeil — библиотека для создания и анимации скелетов интерфейса, который пользователь видит до окончания загрузки данных;
  • MultiLamp — подсвечивает несколько View одновременно, может быть полезна для создания визардов;
  • JustifiedTextView — TextView, который выравнивает текст по ширине экрана (официально Android поддерживает эту функцию с Android 8);
  • PrettyStateView — позволяет одной строкой кода добавить отображение состояния к любому View: загрузка, ошибка, пусто и так далее;
  • CookieBar2 — окно с сообщением в верхней или нижней части экрана;
  • BezierSeekBar — настраиваемый SeekBar;
  • NomtekUtills — библиотека для управления тулбаром и статусбаром;
  • RandomGenKt — Kotlin-порт библиотеки для генерации случайных инстансов любых классов;
  • simplegraph — библиотека для отображения графиков;
  • slidetoact — виджет Slide to Unlock;
  • ElasticViews — позволяет добавить к любому View «эластичную анимацию кликов»;
  • spectrum — библиотека для транскодинга изображений;
  • AutoDSL — генератор простых DSL (Domain Specific Language) с помощью аннотаций;
  • librootjava — библиотека автора SuperSU для запуска кода Java и Kotlin с правами root;
  • ModernAndroidPreferences — позволяет создавать окна настроек с использованием специального DSL и Kotlin;
  • MotionLayoutCarousel — приложение-пример, которое демонстрирует карусель из элементов интерфейса с помощью MotionLayout.

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

Check Also

Задачи на собеседованиях. Задачи от компании Abbyy

Вопросы на собеседовании в стиле «почему крышка от канализационного люка круглая?» — это с…