Что у меня есть
У меня есть приложение для Android с некоторыми конфиденциальными данными. Я хочу убедиться, что клиентское приложение вызывает сервер только в том случае, если оно не было каким-либо образом изменено. По сути, я хочу проверить целостность приложения для Android.
Что я сделал
Для этого я реализовал метод, который может проверить, совпадает ли подпись с подписью, которой я подписал APK. Вот пример кода,
fun isApkSignatureBroken(): Boolean {
val packageInfo = context.packageManager.getPackageInfo(context.packageName, GET_SIGNATURES)
val signatures = packageInfo.signatures
if (signatures == null || signatures.isEmpty()) {
return true
}
return signatures
.map { it.toByteArray() }
.map { hash.getSha1(it) }
.none { it.equals(getRealAppSignature(), true) }
}
Здесь getRealAppSignature()
возвращает мою фактическую подпись, которой я подписал приложение.
Наблюдения
Я видел, что этот метод иногда работает хорошо. Всякий раз, когда обнаруживается поддельное приложение, этот метод возвращает значение true, и пользователь блокируется от использования приложения, и я уведомляюсь о событии. Я обнаружил, что многие пользователи были заблокированы таким образом.
Я видел много других случаев, о которых сообщалось на форумах и других сайтах социальных сетей, когда люди используют мое приложение, даже если у них есть поддельные приложения. Я заметил, что многие из моих приложений имеют искаженные имена версий и имена приложений, используемые многими пользователями. Например, если моя исходная версия приложения
2.2
, то они создадут искаженную версию2.2-TERMINATOR
.
Анализ
Я провел некоторый анализ приложений и нашел некоторые поддельные приложения на некоторых форумах. Это явно модифицированные приложения с неверными версиями и именами приложений. Некоторые даже имеют незначительные изменения пользовательского интерфейса. Я пытался установить эти приложения, но они не могут быть установлены с сообщением «Пакет поврежден».
Я попытался запустить keytool в этих приложениях, чтобы проверить их подпись, вот так:
keytool -list -printcert -jarfile TAMPERED_APP.apk
но я всегда получал,
keytool error: java.lang.SecurityException: SHA1 digest error for classes.dex
Наконец-то я рутировал свое устройство, установил Xposed Framework и отключил «Проверку подписи приложения», а затем смог установить эти приложения. Я обнаружил, что приложение не блокировалось, поскольку мой метод проверки подписи всегда возвращал «ложь». Это означает, что он всегда находил оригинальную подпись внутри apk.
Дополнительный анализ
Я потратил еще немного времени и смог распаковать APK с помощью ApkTool. Внутри папки META-INF
я обнаружил, что CERT.RSA
содержит только мою оригинальную подпись и никакой другой подписи. Я открыл файл MANIFEST.MF
и обнаружил, что все записи имеют другой SHA1, чем исходный файл манифеста APK.
Это явно означает, что приложение было изменено и подписано другой подписью (отсюда и изменение в MANIFEST.MF
), но у моего CERTIFICATE.RSA
была только моя исходная подпись, из-за которой мое приложение всегда получало оригинальный SHA от PackageManager.
Вопросы
Как приложение переподписывается, но новая подпись не сохраняется в CERTIFICATE.RSA?
Почему мой исходный сертификат все еще присутствует в CERTIFICATE.RSA?
Если приложение было уволено, то должны присутствовать несколько подписей? Но это верно здесь.
Как в этой ситуации определить, что приложение было взломано? Есть ли способ, которым я могу вычислить SHA приложения самостоятельно, вместо того, чтобы запрашивать PackageManager?
РЕДАКТИРОВАНИЕ №1: весь код запутан с помощью DexGuard. Таким образом, вероятность того, что логика обнаружения несанкционированного доступа испортится, значительно меньше.