Перейти к содержанию

Отсутствие проверки целостности приложения

Критичность: СРЕДНЯЯ
Способ обнаружения: IAST

Описание

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

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

Проблема

Отсутствие проверки целостности приводит к следующим рискам:

  1. Обход клиентской логики и платных функций. Атакующий правит байткод и снимает проверки лицензий, подписки, рекламы, флагов feature-toggle.
  2. Внедрение вредоносного кода. Перепакованное приложение со встроенным шпионским/мошенническим кодом распространяется через сторонние магазины и фишинговые сайты под видом оригинала.
  3. Компрометация защитных механизмов. Снятие или подмена проверок на root, отладчик, SSL Pinning открывает путь к перехвату трафика и краже данных.
  4. Репутационный и финансовый ущерб. Модифицированные сборки наносят ущерб пользователям и бренду владельца приложения.

Информация

Проверка подписи на устройстве — это мера, повышающая стоимость атаки, а не абсолютная защита: решительный атакующий может найти и нейтрализовать саму проверку. Эффективность достигается многоступенчатой реализацией (в том числе в нативном коде) и серверной верификацией. Для комплексной оценки доверенности среды используйте Play Integrity API.

Рекомендации

Чтобы усложнить модификацию кода приложения, разработчики могут воспользоваться механизмом проверки подписи приложения во время его работы. С помощью объекта класса PackageManager можно получить хеш сертификата, которым было подписано приложение, и сравнить его с эталонным проверочным значением. Если значения совпадают, приложение не было переподписано.

Используйте актуальные API в зависимости от версии ОС:

  • API 28+ (Android 9): метод PackageManager.hasSigningCertificate() — напрямую сверяет хеш/сертификат подписи с эталонным байтовым массивом (самый удобный способ).
  • Получение списка сертификатов: флаг PackageManager.GET_SIGNING_CERTIFICATES (API 28+) и поле PackageInfo.signingInfo.

Устаревший API: GET_SIGNATURES / PackageInfo.signatures

Флаг PackageManager.GET_SIGNATURES и поле PackageInfo.signatures помечены как deprecated с API 28. Кроме того, GET_SIGNATURES исторически уязвим к атаке Janus/подмене и не учитывает ротацию ключей (signing key rotation, APK Signature Scheme v3). На API 28+ используйте GET_SIGNING_CERTIFICATES/signingInfo; обращение к GET_SIGNATURES допустимо только как fallback для API ниже 28.

Пример кода проверки:

public boolean checkSign(String crt) {

    PackageManager pm = getPackageManager();
    String sign = crt.replace(":", "");
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        // API 28+: актуальный способ, корректно работает в том числе при ротации ключей
        return pm.hasSigningCertificate(getPackageName(), hexToByte(sign), PackageManager.CERT_INPUT_SHA256);
    } else {
        try {
            // Fallback для API < 28: GET_SIGNATURES помечен deprecated, использовать только здесь
            @SuppressWarnings("deprecation")
            Signature signature = pm.getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES).signatures[0];
            byte[] pkgSign = MessageDigest.getInstance("SHA-256").digest(signature.toByteArray());
            return Arrays.equals(hexToByte(sign), pkgSign);

        } catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }
    return false;
}

Для получения хеша сертификата подписи релизной версии приложения можно использовать утилиту keytool:

keytool -list -v -keystore sign_key.jks -alias key0 -storepass 123456 -keypass 123456

здесь sign_key.jks — файл с ключом подписи, key0 — имя алиаса, storepass и keypass — пароли хранилища и ключа соответственно.

Некоторые замечания:

Не стоит делать единственный метод с названием checkSign() или подобным, так как злоумышленник все-равно будет менять код — ему ничего не стоит заставить данный метод возвращать нужное значение. Проверку подписи лучше всего организовать многоступенчато в разных местах приложения, в том числе и в нативном коде.

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

Ссылки

  1. Подделка подписи Android-приложения и её проверка | OTUS

  2. GitHub—DimaKoz/stunning-signature: Native Signature Verification For Android (with example)

  3. Simple Android signature check. Please note: This was created in 2013, not actively maintained and may not be compatible with the latest Android versions. It's not particularly difficult for an attacker to decompile an .apk, find this tamper check, replace the APP_SIGNATURE with theirs and rebuild (or use method hooking to return true from validateAppSignature()). It'll make the task of running the .apk unsigned or with edited code slightly more time-consuming and hopefully reduce the effectiveness of automated attacker. But it's not bulletproof.

  4. OWASP MASTG — Testing Code Quality and Build Settings (Android)

  5. OWASP MASVS — MASVS-RESILIENCE (защита от подмены кода)

К началу