Android: социальный мониторинг и советы от Microsoft

Сегодня в выпуске: защита приложения с помощью проверки цифровой подписи в нативной библиотеке, анализ приложения «Социальный мониторинг», рассказ о том, как новые версии Android экономят заряд батареи, статья о вредности System.exit(), советы, как сократить размер приложения и оптимизировать использование батареи, реализация обновления приложения через само приложение, надежный способ показать клавиатуру, а также подборка плагинов Android Studio, инструментов пентестера и библиотек для разработчика.

Почитать

Защита приложения с помощью проверки цифровой подписи

Yet Another Tamper Detection in Android — статья о том, как защитить приложение с помощью проверки цифровой подписи в нативной библиотеке.

Любое приложение для Android имеет цифровую подпись, с помощью которой можно подтвердить его авторство. Проблема лишь в том, что цифровая подпись сверяется только во время обновления приложения (подписи не совпадают — обновить нельзя), но не его первой установки. Это значит, что взломщик может разобрать любое приложение, взломать его или внедрить новую функциональность, а затем собрать с использованием своего ключа. А пользователь спокойно его установит, при необходимости удалив оригинальную версию приложения.

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

Недостаток этого подхода в том, что для сверки контрольной суммы используются стандартные API Android (packageManager.getPackageInfo), ориентируясь по которым взломщик может найти код сверки контрольной суммы и просто вырежет его из приложения.

Автор статьи предлагает разместить данный код в написанной на языке С библиотеке. Вместо API Android эта библиотека использует собственные средства сверки цифровой подписи (в частности, код из библиотек libzip и mbed TLS). Также в библиотеке применен ряд средств защиты от реверса, таких как собственные реализации функций libc и позаимствованный из OpenSSL способ определить, что код библиотеки изменен.

Последний работает так: при сборке в секции text (содержит код) и rodata (содержит константы, включая хеш сертификата) вставляются специальные маркеры, которые помечают начало и конец секции. Далее вычисляется HMAC для данных между этими маркерами и записывается в секцию данных. Во время вызова функции сверки цифровых подписей библиотека проверяет собственную целостность с помощью HMAC.

Код проекта опубликован на GitHub.

Анализ приложения «Социальный мониторинг»

JaDX decompile of com.askgps.personaltrackercore — декомпилированные исходники приложения «Социальный мониторинг», созданного по заказу мэрии Москвы за 180 миллионов рублей. Приложение получило в среднем одну звезду и было удалено из Google Play меньше чем через сутки после публикации.

Изучая исходники, можно заметить, что приложение не применяет никакой, даже самой простой обфускации и передает данные на сервер мэрии Москвы в открытом виде (голый HTTP, без шифрования), а в качестве метода аутентификации использует IMEI (зная IMEI смартфона жертвы, злоумышленник может «отправить» его на другой край страны, сменить фотку и другие сведения).

Для распознавания лиц приложение использует эстонский сервис identix.one, на который, судя по всему, разработчики залили фотографии всех жителей РФ. Это значит, что, имея на руках токен сервиса, выдранный из приложения, злоумышленник мог бы использовать сервис для идентификации людей по фотографии. К счастью, все API уже закрыты.

Примерно через неделю свой аналог «Социального мониторинга» выпустило уже Минкомсвязи. Госуслуги СТОП коронавирус (исходники) оказалось более качественным и таких зияющих дыр уже не содержало. Что, впрочем, не помешало ему получить среднюю оценку в 1,6 звезды.

Запрашиваемые приложением разрешения

Как новые версии Android экономят заряд батареи

App Standby Buckets In Android — небольшая заметка о функции App Standby Buckets, появившейся в Android 9.

App Standby Buckets — новая энергосберегающая функция, расширяющая и дополняющая механизм Doze, работающий с Android 6. Идея на этот раз состоит в том, чтобы разделить все установленные на смартфоне приложения на категории в зависимости от того, насколько часто они используются.

Основных категорий пять:

  • Active — приложение используется в данный момент или использовалось совсем недавно;
  • Working set — часто используемые приложения;
  • Frequent — регулярно используемые приложения, но не обязательно каждый день;
  • Rare — редко используемые приложения;
  • Never — приложение установлено, но ни разу не запускалось.

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

В зависимости от группы система применяет к приложениям различные ограничения, включая ограничения на запуск фоновых задач (Jobs), срабатывание таймеров (Alarm), доступность сетевых функций и push-уведомлений (Firebase Cloud Messaging — FCM):

Группа Jobs Alarms Сеть FCM
Active Без ограничений Без ограничений Без ограничений Без ограничений
Working set Задержка до 2 часов Задержка до 6 минут Без ограничений Без ограничений
Frequent Задержка до 8 часов Задержка до 30 минут Без ограничений 10 в день
Rare Задержка до 24 часов Задержка до 2 часов Задержка до 24 часов 5 в день

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

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

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

Почему не стоит использовать System.exit()

A cautionary tale on Android: do not call System.exit() — небольшая статья о том, почему не стоит использовать System.exit() в своем приложении.

Документация Android говорит, что метод System.exit() (и его эквивалент в Kotlin: exitProcess()) делает следующее: завершает текущую виртуальную машину и сообщает системе, корректным ли было завершение (0 — все нормально, больше нуля — произошло что-то плохое).

Так как в Android каждое приложение исполняется в собственной виртуальной машине, можно предположить, что System.exit(), по сути, приводит к полному уничтожению приложения. Это действительно так, но есть один нюанс: если в момент вызова у приложения были другие активности в состоянии paused, то они останутся в стеке активностей. Это, в свою очередь, приведет к тому, что после вызова System.exit() будет уничтожена текущая активность и само приложение, а затем система вернет управление предыдущей активности приложения. Но так как приложение уже мертво, система перезапустит его, чтобы показать активность.

Что происходит после вызова System.exit()

Решить эту проблему в большинстве случаев можно, используя метод finishAffinity(), который приводит к корректному завершению текущей активности приложения, а также всех активностей с тем же значением affinity. Так как по умолчанию Android назначает всем активностям приложения одинаковый affinity, равный имени пакета приложения, это приведет к закрытию всех активностей.

Советы, как уменьшить размер приложения

App size reduction at Microsoft SwiftKey — сборник заметок разработчиков клавиатуры SwiftKey (Microsoft) о том, как сократить размер скачиваемого и установленного приложения.

1. Установленное приложение весит намного больше скачиваемого потому, что во время установки Android сохраняет в памяти устройства не только сам APK-файл, но и некоторые извлеченные из него компоненты: верифицированный файл DEX (VDEX — Verified DEX) и нативные библиотеки. Также через некоторое время виртуальная машина создает файл ODEX — оптимизированную версию файла DEX, пропущенную через AOT-компилятор (некоторые части байт-кода заменяются на машинные инструкции). Причем начиная с Android 9 этот файл может быть получен сразу из Google Play.

В целях тестирования файл ODEX можно создать принудительно:

adb shell cmd package compile -m speed-profile -f имя.пакета

2. В случае со SwiftKey помогли следующие флаги ProGuard: repackageclasses, renamesourcefileattribute и allowaccessmodification. Но появился побочный эффект: еще большее запутывание стек-трейсов в системе анализа сбоев.

3. Компилятор R8 (в новых версиях Android Studio включен по умолчанию) сократил размер установленного приложения на 1,3 Мбайт, но замедлил время его старта.

4. По умолчанию при сборке приложения среда разработки сжимает нативные библиотеки. При установке приложения на устройство Android распаковывает библиотеки в специальный каталог с данными приложения. Это приводит к дополнительному расходу пространства устройства. Чтобы поменять такое поведение и заставить Android использовать библиотеки прямо из пакета, следует внести два изменения в проект.

В файл build.gradle добавить такие строки:

android { 
    aaptOptions { 
        noCompress 'so' 
        ...
    } 
    ...
}

В AndroidManifest.xml — такие:

<application android:extractNativeLibs="false" ...>

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

5. По умолчанию при обфускации/минимизации приложения среда разработки удалит все неиспользуемые ресурсы. Также ты можешь удалить их с помощью опции Android Studio: Refactor -> Remove Unused Resources.

6. Используемое приложением место можно сократить, преобразовав изображения в формат WebP (клик правой клавишей на папке drawable, затем пункт Convert to WebP). Экономия составит примерно 25%. Еще большего выигрыша можно достигнуть, заменив растровые изображения на векторные. Однако в этом случае автоматически преобразовать их не получится.

7. При сборке среда разработки помещает все используемые приложением строки в файл resources.arsc. Туда попадают строки на всех языках, включая строки из используемых в проекте библиотек. Проблема здесь в том, что, если библиотека переведена на большее количество языков, чем само приложение, строки на этих языках все равно попадут в пакет. Избавиться от них можно, перечислив используемые приложением языки в конфиге Gradle:

android { 
    defaultConfig { 
        resConfigs "en", "es" 
    } 
}

Теперь в приложение попадут строки только на английском и испанском.

8. Хороший выигрыш в размере загружаемого приложения даст использование App Bundle. Новые версии Android Studio по умолчанию предлагают собирать приложение в App Bundle вместо классического APK. Bundle затем можно залить в Google Play, и он автоматически будет разбит на несколько APK для разных платформ, включая отдельные дополнительные APK для разных языков и регионов. В этом случае итоговый размер пакета, который загружает на смартфон пользователь, обычно становится намного меньше.

Все эти техники позволили сократить размер пакета SwiftKey на 50% (с 27,6 до 14,3 Мбайт), а занимаемое место после установки — на 40% (с 81,5 до 48 Мбайт).

Советы, как измерить потребление батареи

Android Battery Testing at Microsoft YourPhone — еще один материал разработчиков из Microsoft. На этот раз авторство принадлежит команде приложения YourPhone (того, что позволяет управлять телефоном из Windows), а статья посвящена измерению потребления батареи.

Большая часть статьи — вода, но в конце есть мякотка — скрипт для запуска тестирования батареи и часть кода для парсинга результатов работы скрипта. Код скрипта:

## Эмулируем отключение смартфона от источника питания
adb shell dumpsys unplug
## Сбрасываем статистику использования батареи
adb shell dumpsys batterystats --reset
## Запускаем тесты
...
## Останавливаем тесты
## Получаем статистику работы батареи (вывод этой команды надо сохранить)
adb shell dumpsys batterystats
## Отключаем эмуляцию отключения от источника питания
adb shell dumpsys batterystats reset

Далее результат работы команды dumpsys batterystats reset можно пропарсить, чтобы получить сводные данные об использовании батареи. В статье приведен фрагмент приложения на C#, которое выводит такой результат:

Total Usage: 62.1mAh
Cpu Usage: 1.21mAh
Wifi Usage: 60.9mAh
Wakelock Usage: 0mAh
Bluetooth Usage: 0mAh

Разумеется, его можно переписать на любом другом языке.

Правильный способ показать клавиатуру

Showing the Android Keyboard Reliably — статья разработчиков Square о том, как показать клавиатуру и не получить проблем.

Суть проблемы: в Android есть способ форсировать показ клавиатуры без необходимости дожидаться, пока пользователь кликнет на поле ввода:

val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT)

Он работает, но только в том случае, если поле ввода (в данном случае — editText) будет иметь фокус в момент вызова метода showSoftInput(). Это ограничение можно обойти, передав методу showSoftInput() флаг InputMethodManager.SHOW_FORCED. Но тогда клавиатура не будет спрятана автоматически и останется на экране, например, если пользователь свернет приложение.

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

Код функции-расширения, которая корректно показывает клавиатуру во всех случаях:

fun View.focusAndShowKeyboard() {
   fun View.showTheKeyboardNow() {
       if (isFocused) {
           post {
               val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
               imm.showSoftInput(this, InputMethodManager.SHOW_IMPLICIT)
           }
       }
   }

   requestFocus()
   if (hasWindowFocus()) {
       showTheKeyboardNow()
   } else {
       viewTreeObserver.addOnWindowFocusChangeListener(
           object : ViewTreeObserver.OnWindowFocusChangeListener {
               override fun onWindowFocusChanged(hasFocus: Boolean) {
                   if (hasFocus) {
                       this@focusAndShowKeyboard.showTheKeyboardNow()
                       viewTreeObserver.removeOnWindowFocusChangeListener(this)
                   }
               }
           })
   }
}

Реализация In-App Updates

In-App Updates: Getting Started — большой туториал по реализации In-App Updates — функции, с помощью которой приложение может принудительно обновить себя или вывести уведомление об обновлении.

Функция реализована в библиотеке Google Play Core и предлагает два способа обновления приложения: immediate и flexible. Первый принудительный: приложение автоматически запускает обновление, а пользователю остается только наблюдать. Второй позволяет проверить наличие обновления и вывести сообщение/уведомление/кнопку, нажав на которое пользователь может самостоятельно запустить процесс обновления.

И в том и в другом случае проверить, есть ли новая версия, и запустить обновление очень просто. Например, принудительно обновить можно всего в несколько строчек:

private const val REQUEST_UPDATE = 100

fun checkForUpdate(activity: Activity) {
    val appUpdateManager = AppUpdateManagerFactory.create(getAppContext())
    val appUpdateInfo = appUpdateManager.appUpdateInfo
    appUpdateInfo.addOnSuccessListener {
        handleImmediateUpdate(activity, appUpdateManager, appUpdateInfo)
    }
}

private fun handleImmediateUpdate(activity: Activity, manager: AppUpdateManager, info: Task<AppUpdateInfo>) {
    if ((info.result.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE ||
                    info.result.updateAvailability() == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) &&
            info.result.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) {

        manager.startUpdateFlowForResult(info.result, AppUpdateType.IMMEDIATE, activity, REQUEST_UPDATE)
    }
}

Все, что делает этот код, — проверяет, есть ли обновление (UpdateAvailability.UPDATE_AVAILABLE), подходит ли оно для принудительной установки (AppUpdateType.IMMEDIATE), и, если да, устанавливает его.

Flexible-обновления работают примерно так же с тем исключением, что обновление можно запустить в фоне, а затем попросить пользователя перезапустить приложение.

Подборка полезных плагинов для Android Studio

The Top 20 Android Studio Plugins — обзор двадцати плагинов Android Studio на все случаи жизни. Наиболее интересные экземпляры:

  • Rainbow Brackets — раскрашивает парные скобки в разные цвета, упрощая поиск закрывающей/открывающей скобки;
  • ADB Idea — плагин, позволяющий быстро выполнить команды ADB: убить приложение, отозвать полномочия;
  • ADB Wifi — плагин для отладки смартфона без USB по Wi-Fi;
  • Gradle Killer — убивает процесс Gradle одной кнопкой (полезно для быстрой остановки сборки или освобождения памяти);
  • Kotlin Fill Class — позволяет быстро создать класс с дефолтными свойствами;
  • TabNine — плагин автодополнения на основе нейросети, обученной на коде GitHub;
  • SQLScout — плагин для управления базами SQLite в режиме реального времени;
  • Material Design Icon Generator — плагин для генерации иконок в стиле MD.
Результат работы плагина Rainbow Brackets

Инструменты

  • XploitSPY — исходники мощного трояна с панелью управления в комплекте (код основан на проекте L3MON);
  • Aind — образ Docker, позволяющий запускать приложения для Android в Linux без использования эмулятора (основан на Anbox);
  • FridaAndroidTracer — скрипт, формирующий отчет о приложении и его активности;
  • Runtime Mobile Security — веб-интерфейс для Frida.

Библиотеки

  • WindowInsetsAnimation — пример кода для работы с новым API клавиатуры в Android 11;
  • AndColorPicker — быстрый настраиваемый компонент для выбора цвета;
  • Chip-navigation-bar — очередная панель навигации в нижней части окна;
  • PowerPermission — удобная обертка для работы с разрешениями;
  • Venom — библиотека для быстрого убийства приложения в целях тестирования;
  • Android-lints — несколько кастомных lint-правил для проверки качества кода;
  • Blitz — TextView, показывающий прошедшее время («пять минут назад»).
Евгений Зобнин: Редактор рубрики X-Mobile. По совместительству сисадмин. Большой фанат Linux, Plan 9, гаджетов и древних видеоигр.
Похожие материалы