被篡改的 Android 应用程序具有原始签名

Ari*_*Roy 7 android certificate signature jarsigner apk

我拥有的

我有一个包含一些敏感数据的 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) }
    }
Run Code Online (Sandbox Code Playgroud)

在这里,getRealAppSignature()返回我在应用程序上签名的实际签名。

观察

  • 我已经看到这种方法有时效果很好。每当发现被篡改的应用程序时,此方法都会返回 true 并且用户被阻止使用该应用程序,并且我会收到一个事件通知。我发现很多用户都以这种方式被屏蔽了。

  • 我在论坛和其他社交媒体网站上看到了许多其他案例,人们正在使用我的应用程序,即使他们的应用程序被篡改。我观察到我的许多应用程序的版本名称和应用程序名称格式不正确,许多用户都在使用它。就像,如果我的原始应用程序版本是2.2那么他们将创建一个格式错误的版本2.2-TERMINATOR

分析

我对这些应用程序进行了一些分析,并从一些论坛中发现了一些被篡改的应用程序。这些显然是修改过的应用程序,具有格式错误的版本和应用程序名称。有些甚至对 UI 进行了细微的更改。我尝试安装这些应用程序,但无法安装它们并显示“程序包已损坏”消息。

我试图在这些应用程序上运行 keytool 来检查它们的签名,就像这样,

keytool -list -printcert -jarfile TAMPERED_APP.apk
Run Code Online (Sandbox Code Playgroud)

但我总是得到,

keytool error: java.lang.SecurityException: SHA1 digest error for classes.dex
Run Code Online (Sandbox Code Playgroud)

我终于扎根了我的设备,安装了 Xposed 框架并禁用了“应用程序签名验证”,然后就可以安装这些应用程序了。我发现该应用程序没有被阻止,因为我的签名检查方法总是返回“false”。这意味着,它总是在 apk 中找到原始签名。

更多分析

我花了更多时间,并且能够使用 ApkTool 解压缩 APK。在META-INF文件夹内,我发现CERT.RSA只包含我的原始签名,没有其他签名。我打开MANIFEST.MF文件,发现所有条目的 SHA1 与我原来的 APK 清单文件不同。

这显然意味着该应用程序已被修改并由另一个签名签名(因此更改了MANIFEST.MF),但我CERTIFICATE.RSA只有我的原始签名,因此我的应用程序始终从 PackageManager 获得原始 SHA。

问题

  • 应用程序如何重新签名但新签名未存储在 CERTIFICATE.RSA 中?

  • 为什么我的原始证书仍然存在于 CERTIFICATE.RSA 中?

  • 如果应用程序被辞职,那么应该存在多个签名吗?但这里确实如此。

  • 在这种情况下,如何检测应用程序已被篡改?有什么办法可以自己计算应用程序的 SHA 而不是查询 PackageManager?

编辑 #1:使用 DexGuard 混淆了整个代码。因此,篡改检测逻辑混乱的可能性要小得多。