What I Have
I have an Android app with some sensitive data. I want to make sure that the client app calls the server only if it has not been tampered with in any way. Basically, I want to check the integrity of the Android app.
What I Have Done
For that, I have implemented a method that can check if the signature is same as the signature I have signed the APK with. Here is a code sample,
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) }
}
Here, getRealAppSignature()
returns my actual signature with which I have signed the app.
Observations
I have seen this method to work well sometimes. Whenever a tampered app is discovered, this method returns true and the user is blocked from using the app and I am notified by an event. I have found many users being blocked in this way.
I have seen many other cases being reported in forums and other social media sites, that people are using my app even if they are having tampered apps. I have observed many of my apps having malformed version names and application names being used by many users. Like, if my original apps version is
2.2
then they will create a malformed version2.2-TERMINATOR
.
Analysis
I did some analysis on the apps and found some tampered apps from some forums. Those are clearly modded apps having malformed version and application names. Some even have minor UI changes. I tried to install those apps, but they can't be installed with the message "Package is corrupted".
I tried to run keytool on those apps to check their signature, like this,
keytool -list -printcert -jarfile TAMPERED_APP.apk
but I always got,
keytool error: java.lang.SecurityException: SHA1 digest error for classes.dex
I finally rooted my device, installed Xposed Framework and disabled "App Signature Verification" and then was able to install those apps. What I found was that the app was not getting blocked as my signature checking method always returned "false". That means, it always found the original signature inside the apk.
More Analysis
I spend some more time and was able to unpackage the APK using ApkTool. Inside the META-INF
folder I found the CERT.RSA
to only contain my original signature and no other signature. I opened the MANIFEST.MF
file and found that all the entries have different SHA1 than my original APKs manifest file.
It clearly means that the app was modified and was signed by another signature (hence the change in MANIFEST.MF
) but my CERTIFICATE.RSA
only had my original signature because of which my app always got the original SHA from PackageManager.
Questions
How is the app being re-signed but the new signature is not stored in CERTIFICATE.RSA?
Why is my original certificate still present in CERTIFICATE.RSA?
If the app was resigned, then multiple signatures should be present? But that is true here.
In this situation, how do I detect that the app has been tampered with? Is there any way I can compute the SHA of the app myself instead of querying the PackageManager?
EDIT #1: The entire code is obfuscated using DexGuard. So, the chance of the tamper detection logic getting messed up is pretty less.