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 version 2.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.
Related
I am trying to customize Android for a media device. We got the firmware from the manufacturer and it's based on Android 7 with minor modifications.
One of the things we would like to do is to restrict installation of apps on the device to certain apps only. We won't install any app store like Google Play on the device. We will build the firmware and install all apps onto the devices at our workshops and deliver to the customers. In future, we may want to install more apps on the devices via OTA or some mechanisms.
We would like to disallow customers sideloading other apps via USB port. We created a file (eg., whitelisted_apps.txt) that has the list of all approved app names, such as -
com.mycompany.android.app1
com.mycompany.android.app2
com.ourpartner.android.appx
We tweaked the PackageInstaller app of AOSP so that when a *.APK file is opened via the file browser and when the methods in PackageInstallerActivity.java are called, it will compare the name of the app to be installed against those in whitelisted_apps.txt and if the name is not found, disallow installation of the new app. It's working.
Now, we want to improve it further because whitelisted_apps.txt can be manipulated. Some people suggest using sqlite to keep the list. We are not sure if it will be the best solution.
Some suggest using certificates and signing and we think it's a better solution than others. We will sign all the apps we want to install on the device with our key. When a *.APK file is sideloaded, the PackageInstaller will get the signature of the APK and compare against ours. If they match, the app can be sideloaded.
We followed this excellent resource: SignatureCheck.java. It's working with the hardcoded APP_SIGNATURE. We have this currently:
public boolean validateAppSignature() throws NameNotFoundException {
boolean sigMatch = false;
String APP_SIGNATURE = "123456784658923C4192E61B16999";
PackageInfo packageInfo3 = mPm.getPackageInfo(
getPackageName(), PackageManager.GET_SIGNATURES);
try {
for (Signature signature : packageInfo3.signatures) {
// SHA1 the signature
String sha1 = getSHA1(signature.toByteArray());
Log.i(TAG, "got this SHA1 of the signature ... "+sha1);
// check if it matches hardcoded value
sigMatch = APP_SIGNATURE.equals(sha1);
if (sigMatch){
return sigMatch;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
Just that we don't know how to do that in a real world situation. When we build, install apps or release with OTA, we sign those apps with our private key and then on the device (PackageInstaller app), we hardcode our certificate's SHA1 signature as APP_SIGNATURE so that we can compare? Any other ideas?
Thank you so much.
Seems like you're trying way too hard to partially shut down sideloading. Use a Device Owner to completely turn off installation, and just preload the apps you want. Or make it so the only app that can install is your own downloader that only looks at your server.
I'm trying check my app's release signature at runtime to prevent tampering. However, what I've been reading is that I need to log the signature during a run so that I can hard code it into my app for runtime comparison. But I see two problems with this:
1) The signature obtained while debugging (reading the log) will be different than the release signature.
2) If I get the release signature and then put it into my code for runtime comparison, won't that change the signature for the next release build... hence a chicken and egg problem?
What am I missing? Do static final strings not change the signature? Likewise, if I log release output does the log code not change the signature? What does and doesn't impact the release signature?
If compute the signature over bytes that include the signature itself then yes, you will have chicken and egg problem. But you don't have to: compute the signature over everything but the signature. That's in fact how signatures generated by jarsigner are done.
But what I think you really want, since apk files are already signed, is to check if the signer information matches your own. And that can be done by comparing the package's signer certificate with your own certificate.
You can see an adopted example from Android Security Cookbook that performs such check here.
If I want to change your apk here are the steps:
Open your apk with a an unzip program
Get one or two resources out of it ( ex: class files).
Make changes to it ( de-copmile, change, recompile)
Put it back in the APK.
Sign it
Publish it
I will have some problem At step 5, as I don't have your private key and your signature, I should create my own and sign it with my key. Then I should hope that no one checks the sign before installing the app ( which no one does! ).
So my tampered app has two differences with your app, changed resources AND changed sign certificate.
If you want to check the changed resources, ( signature over bytes) you will have the chicken and egg problem.
The easiest way to stop me is to hard code your app signature and check if it is changed at run time.
The sample code is at https://gist.github.com/scottyab/b849701972d57cf9562e
A simple method for this check:
private boolean validateAppSignature() {
APP_SIGN= "f10e2821bbbea527ea02200352313bc059445190";
PackageInfo packageInfo = this.getPackageManager().getPackageInfo(
getPackageName(), PackageManager.GET_SIGNATURES);
for (Signature signature : packageInfo.signatures) {
// SHA256 the signature
String AppSign = DigestUtils.sha256Hex(signature.toByteArray());
return APP_SIGN.equalsIgnoreCase(AppSign);
}
return false;
}
Of course this is not a 100% solution even the obfuscated code can be changed. So I may find a way to by pass this check. But you make tampering harder for me :)
I've created an android application, and also a paid key-application that can open some features in the regular application.
I tried to use this code to check if the paid key is installed:
protected static boolean isProInstalled(Context context) {
PackageManager manager = context.getPackageManager();
if (manager.checkSignatures(context.getPackageName(), "com.myapps.appkey")
== PackageManager.SIGNATURE_MATCH) {
//Pro key installed, and signatures match
return true;
}
return false;
}
It worked on my phone when I installed the two APKs that I exported from Eclipse (I think I exported them. Maybe it was directly with "run"/"debug". I can't remember). But when I uploaded them to Google Play - I got a message from a user that said that he bought the key but the features are still blocked.
Is there something I do wrong? What are those signatures anyway? Does it have anything with the alias and keystore when I export the APKs?
The debug key is the default used when you run from the IDE, so while testing, they both were using the same key. But you signed them with different keys for the store, so the signatures won't match.
As #NobuGames mentioned in the comments, at this point since you already published both apps, you can update the free one to check for the key hash of the paid app using a hard-coded string. This theoretically might make it easier for someone to make a premium-unlocked pirate version of your app, although if they are digging into your source code that far, I suspect they would have succeeded anyway. Think of that as a good problem to have (popular enough app for pirates to spend that much time hacking on yours).
I'm trying to create my own version of Gesture Builder. In eclipse I selected android project from existing code and I renamed the project and package name to new gesture. Then I added in android:fadeOffset = "1000" in create gesture xml(so that I can create gestures for letters like t and f) and in AndroidManifest.xml I set the version name to NewGestures and I set a different icon but when I try to run it I get this error message:
"Re-installation failed due to different application signatures. You must perform a full uninstall of the application. WARNING: This will remove the application data! Do you want to uninstall?"
From what I've seen online I need to match the signature used originally on Gesture Builder, but I've no idea how to do this on eclipse, shouldn't the signature have been handled properly when I created from existing code? Any help would be very much appreciated. I just need this app working so I can get a gestures library for a different application I'm working on for college.
This message concerns the application signature. This happens when you are trying to install an application on your device while an application of the same package name is already installed, but signed with a different certificate (see details here).
For example:
You have exported and installed your application using your Google Play keystore (so using your actual developer's certificate), and now you are running/debugging it from Eclipse, implicitely using the debug certificate (which is different)
You have runned/debugged your application from Eclipse at home on this device, and now your are running it/debugging it from Eclipse with another computer (which is using a different implicit debug certificate)
etc
Normally, below the error message, you have a button that allows uninstalling/reinstalling. If not, just uninstall your app manually and everything will be fine again.
versionName:
The version number shown to users. This attribute can be set as a raw
string or as a reference to a string resource. The string has no other
purpose than to be displayed to users.
package:
The package name serves as a unique identifier for the application.
The package name declared in your manifest.xml is what makes your application unique. Therefore, there can not be two application installed with the same package name at the same time. If you try this, your error occurs.
We integrated Facebook login on our Kindle Fire android app. It works without any problem most of the time. But occasionally for some users, when they try to register using facebook login, it fails with the error "APp is misconfigured for facebook login". We checked the hash key, package name and all that, they all are correct. As i said, it works for 95% of the users. For those users it fails, it fails repeatedly. Anybody from facebook can help us resolve this issue? Appreciated. (BTW, we use the same facebook app for our google play version of the android app also with a different hash key, we never had this problem for our google play app. We got the hash key for Kindle from Amazon.) Since it doesnt fail in any of our devices, and fails only on some random user's devices, we couldnt get any debug messages.
PS: I have already read the thread App is misconfigured for Facebook login: Android Facebook integration issue . I am a new user, i couldnt ask this question over there.
We just had the same problem with one of our apps on the Amazon appstore. In our case we realized the problem only happened if these three conditions were true:
Kindle Fire HD
Facebook App installed and user logged in
User also logged into facebook via Settings -> My Account -> Manage Social Accounts
That may explain why in your case it only happens in 5% of the cases.
As far as we could tell, Amazon resigns the .apk, which breaks the Facebook Android App Key Hash check.
The solution involved:
Obtaining the Amazon .apk of our app (not the one we submitted, but the one distributed by the Amazon appstore)
Extract the signing certificate from the .apk file
Base64 encode the SHA digest of the encoded certificate
Add the resulting Base64 key hash to our Facebook App settings
This fixed the problem.
Getting the .apk proved tricky. Applications reside in the /data/app folder of the device's filesystem. However, this directory is protected to prevent listing it, so unless you know the name of the file you're looking for, you're out of luck. You can of course, root the device. Alternatively you can try your blind luck by doing adb pull /data/app/<app-id><suffix>.apk where suffix is either an empty string or -1, -2, etc, until you succeed. E.g.:
$ adb pull /data/app/com.example.game.apk
remote object '/data/app/com.example.game.apk' does not exist
$ adb pull /data/app/com.example.game-1.apk
remote object '/data/app/com.example.game-1.apk' does not exist
$ adb pull /data/app/com.example.game-2.apk
3658 KB/s (1085140 bytes in 0.289s)
If this approach fails, rooting might be the only option.
Once you have the .apk file, you can use the code below to obtain the key hash. Save as Main.java, compile with javac Main.java and run with java Main <APK>, e.g.:
$ javac Main.java
$ java Main com.example.game-1.apk
com.example.game-1.apk: 478uEnKQV+fMQT8Dy4AKvHkYibo=
Adding 478uEnKQV+fMQT8Dy4AKvHkYibo= to the key hashes of our Facebook App settings then fixes the problem. I'm curious if other people find the same hash we got (which would mean all Amazon games are resigned with the same key). In our case, the hash started with wwYPegrz....
Here's the code:
import java.security.MessageDigest;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import sun.misc.BASE64Encoder;
public class Main {
public static void main(String[] args) throws Exception {
for (String jarFilename : args)
extractHash(jarFilename);
}
private static void extractHash(String jarFilename) throws Exception {
BASE64Encoder base64 = new BASE64Encoder();
MessageDigest sha1 = MessageDigest.getInstance("SHA");
Set<Certificate> certificates = new HashSet<Certificate>();
JarFile jarFile = new JarFile(jarFilename);
for (JarEntry jarEntry : Collections.list(jarFile.entries())) {
jarFile.getInputStream(jarEntry).skip(Long.MAX_VALUE);
Certificate[] certs = jarEntry.getCertificates();
if (certs == null)
continue;
certificates.addAll(Arrays.asList(certs));
}
System.out.printf("%s:", jarFilename);
for (Certificate cert : certificates) {
byte[] digest = sha1.digest(cert.getEncoded());
System.out.printf(" %s", base64.encode(digest));
}
if (certificates.isEmpty())
System.out.printf(" NOT SIGNED!");
System.out.println();
jarFile.close();
}
}
#Blanka's answer is technically correct, however I found an easier way: Simply go to Amazon developer console and copy the value I have highlighted in the red rectangle:
The solution from Blanka works.
However, here's another solution easier to do if you can reproduce the issue on a Kindle Fire.
Retrieve from LogCat the authentication request sent by Facebook:
03-13 15:21:19.360: D/WebCore(26863): *-* Total load time: 1535.31 ms, thread time: 287.00 ms for
https://m.facebook.com/dialog/oauth?android_key=XXXXXXXXXXXXX&calling_package_key=<app_package_id>
&client_id=YYYYYYYYYYYY&display=touch&redirect_uri=fbconnect%3A%2F%2Fsuccess&scope=email%
2Cpublish_stream&type=user_agent&_rdr
android_key parameter is the Hash Key of your app.
You need to add this key on the Facebook Dashboad.
Note: Be careful, the encoding format of your hash may in the https request be different that the one needed by Facebook.
I think the problem with hashkey, I also faced same issue. I resolved this by downloaded openssl and generated hash. Try with following answer https://stackoverflow.com/a/14826036/1258999
Discovered an even easier way to deal with this on Kindle or any other device. If you have the FB app installed (in my case I didn't try other sign in paths but may work too?), and the login you're using is listed as a developer in the FB app at developer.facebook.com for the app in question, the hash will appear in the error message it gives you in the app itself. Says something to the effect of "Hash key xxxxxxxxxxxxxxxxx was not recognized. Manage your hash keys in the developer portal for app id yyyyyyyyyyyy".
Sorry if the text isn't exactly right, i didn't screenshot it prior to fixing the problem myself, but that's the gist of it.