Перейти к основному содержанию
Блог

Мобильные

Целостность приложения и обфускация: R8 и Dart --obfuscate

Мобильная экспертиза проверяет защиту приложения от модификации и реверс-инжиниринга. Как настроить R8, обфускацию Dart и проверку целостности.

· 7 мин. чтения

Фото: Profpcde, CC0 · Wikimedia Commons

Мобильное приложение целиком попадает на устройство пользователя. Значит, любой может скачать APK или IPA, открыть его, изменить и собрать заново. Поэтому экспертиза по кибербезопасности проверяет в мобильном приложении не только обмен данными с сервером, но и то, насколько защищён сам бинарный файл. В OWASP Mobile Top 10 (2024) это называется M7: Insufficient Binary Protections.

При экспертизе мобильного приложения нашего собственного продукта (Flutter, Android и iOS) в этой области было несколько находок высокого уровня: отсутствовала проверка целостности (tamper), а код был недостаточно запутан. Ниже — практическое описание того, как мы их закрыли.

Две разные защиты: обфускация и целостность

Эти понятия часто смешивают, но они решают разные задачи:

  • Обфускация затрудняет чтение кода. Имена классов, методов и переменных заменяются короткими бессмысленными именами, неиспользуемый код удаляется. Цель — увеличить время, необходимое для реверс-инжиниринга.
  • Проверка целостности — при запуске приложение убеждается, что его не изменили. Если кто-то распаковал APK, изменил код и переподписал его другим ключом, приложение должно это обнаружить и прекратить работу.

Обфускация — не средство хранения секретов. Она лишь замедляет анализ. Поэтому обе меры применяются вместе.

Android: сжатие кода с помощью R8

На Android слой Java/Kotlin сжимает и обфусцирует компилятор R8. В Flutter-проекте для release-сборки он обычно включён, но лучше указать это явно в android/app/build.gradle (или build.gradle.kts) — экспертиза проверяет именно release-сборку:

android {
    buildTypes {
        release {
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

В proguard-rules.pro оставляйте только действительно нужные правила -keep. Сохранение целого пакета через -keep class com.example.** { *; } фактически отключает обфускацию — это частая ошибка.

Flutter: сборка Dart-кода с --obfuscate

Основная логика Flutter-приложения написана на Dart, и R8 на неё не влияет. Для Dart-кода нужен отдельный флаг:

flutter build apk --release --obfuscate --split-debug-info=build/symbols
flutter build ipa --release --obfuscate --split-debug-info=build/symbols

--split-debug-info выносит символы за пределы приложения, в отдельную папку. Не публикуйте её, но сохраните: именно эти файлы нужны, чтобы прочитать stack trace из crash-отчётов через flutter symbolize.

Обфускация не шифрует строковые литералы. Если адреса API, ключи и секреты лежат в коде обычными строками, они видны командой strings и после обфускации.

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

Проверка целостности (tamper)

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

Это можно написать вручную, но мы использовали готовую RASP-библиотеку (Runtime Application Self-Protection) — freeRASP. Она одновременно обнаруживает:

  • изменение подписи (tamper) и установку из неофициального источника;
  • устройство с root или jailbreak;
  • эмулятор;
  • hook-инструменты вроде Frida и подключение отладчика.

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

Важный момент: хеш подписи берите от release-ключа. Если приложение распространяется через Google Play App Signing, версия в магазине переподписывается ключом Google. В этом случае добавьте в список и хеш сертификата «App signing key» из Play Console, иначе копия, установленная из магазина, будет считаться «изменённой».

Подтверждение на стороне сервера

Любую проверку на устройстве человек с достаточным временем и знаниями может обойти. Поэтому для важных операций полезно запрашивать подтверждение и на сервере: Play Integrity API для Android, App Attest для iOS. Так сервер сам проверяет, что запрос пришёл от подлинной, неизменённой копии приложения. Это не обязательно для каждого проекта, но для приложений, работающих с финансовыми или персональными данными, стоит рассмотреть.

Чек-лист перед экспертизой

  1. В release-сборке включён R8 (isMinifyEnabled = true), лишних правил -keep нет.
  2. Dart-код собран с --obfuscate --split-debug-info, символы хранятся в безопасном месте.
  3. Откройте APK в jadx или аналогичном инструменте и убедитесь, что имена классов действительно запутаны.
  4. Проверьте через strings, что в бинарном файле не осталось ключей, паролей и внутренних адресов.
  5. Проверка подписи (tamper) работает: переподпишите приложение другим ключом, установите и убедитесь, что появляется блокирующий экран.
  6. Обнаружение root/jailbreak, эмулятора и hook проверено на тестовом устройстве.
  7. Файл, сдаваемый на экспертизу, — это именно release-файл, собранный с этими настройками.

Эти меры не делают приложение невзламываемым. Они существенно удорожают реверс-инжиниринг и модификацию — и экспертиза проверяет именно это: наличие и работоспособность слоёв защиты.

Сканирование

Хотите узнать, в каком состоянии бинарная защита вашего приложения? Отправьте APK или IPA на сканирование. ИИ-агент Claude и наш специалист проверят обфускацию, контроль подписи и меры RASP и подскажут, что нужно закрыть до экспертизы.