我有什么
我有一个包含敏感数据的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
。
分析
我对应用程序进行了一些分析,发现了一些论坛中的一些被篡改的应用程序。这些显然是具有格式错误的版本和应用程序名称的应用程序。有些甚至有轻微的UI更改。我尝试安装这些应用程序,但无法安装“包已损坏”的消息。
我尝试在这些应用上运行keytool来检查他们的签名,就像这样,
keytool -list -printcert -jarfile TAMPERED_APP.apk
但我一直都有,
keytool error: java.lang.SecurityException: SHA1 digest error for classes.dex
我终于根植了我的设备,安装了Xposed Framework并禁用了“App Signature Verification”,然后才能安装这些应用程序。我发现该应用程序没有被阻止,因为我的签名检查方法总是返回“false”。这意味着,它始终在apk中找到原始签名。
更多分析
我花了更多时间,并且能够使用ApkTool解包APK。在META-INF
文件夹中,我发现CERT.RSA
只包含我的原始签名而没有其他签名。我打开了MANIFEST.MF
文件,发现所有条目的SHA1都与我原来的APK清单文件不同。
这显然意味着应用程序已被修改并由另一个签名进行签名(因此MANIFEST.MF
中的更改)但我的CERTIFICATE.RSA
只有我的原始签名,因为我的应用程序始终获得原始SHA来自PackageManager。
问题
应用程序如何重新签名但新签名未存储在CERTIFICATE.RSA中?
为什么我的原始证书仍然存在于CERTIFICATE.RSA中?
如果应用已停用,那么应该有多个签名?但这是真的。
在这种情况下,如何检测应用程序是否已被篡改?有没有办法可以自己计算应用程序的SHA而不是查询PackageManager?
编辑#1:使用DexGuard对整个代码进行模糊处理。因此,篡改检测逻辑混乱的可能性相当小。