Repackaging Detection
When a production application is modified by a third party, its code signature becomes invalid. Android only accepts applications with a valid signature. However, it is possible to repackage the modified application and re-sign it with a different signature. The modified app can then be installed on a device because it has a valid signature (even though this signature differs from the original).
Malwarelytics for Android can detect that an application has been modified and re-signed with a different signing certificate. In such case, Malwarelytics can be configured to terminate the app.
Configuration
This feature can be configured during the Malwarelytics initialization phase:
val raspConfig = RaspConfig.Builder()
.signatureHash(String) // SHA-1 of signing certificate(s)
.checkRepackaging(Boolean)
.exitOnRepackaging(Boolean)
.exitOnRepackagingUrl(String)
// configuration of other RASP features
.build()
Method | Description |
---|---|
signatureHash(String) |
SHA-1 of signing certificate(s). One or more values can be set. A lowercase hex value without any byte separators is expected. No default value is set. |
checkRepackaging(Boolean) |
indicates whether repackaging should be detected automatically. Defaults to true but works only if signatureHash is set. |
exitOnRepackaging(Boolean) |
indicates whether the app should be terminated when repackaging is automatically detected. Defaults to true but works only if signatureHash is set. |
exitOnRepackagingUrl(String) |
defines a URL to be opened when the app is terminated because of repackaging detection. Defaults to null . |
To properly configure repackaging detection, you need to provide the SHA-1 hash of the signing certificate. There are several ways this hash can be obtained, as described in the next section.
Obtaining Signature Hash
The following documentation only addresses publishing to the official Android store, Google Play.
There are two basic ways Android apps can be signed and published: legacy APK signing and Play App Signing.
Legacy APK Signing
From the very beginning, apps on the Android platform were published as signed APKs. This method is still used for signing development builds and existing apps.
To sign the app, the developer first needs to create a keystore with a release key (this step is only done once). Afterwards, the app is built and signed with this key. The signed app is then uploaded to Google Play Store and is distributed as is.
In this case, the signature hash can be obtained either from the keystore or from a signed APK.
Obtaining Hash from Keystore
Use this command on Linux or Mac to get the hash. You need to know the path to the keystore and the key alias.
keytool -list -v -keystore ${SOME_KEYSTORE} -alias ${MY_ALIAS} | grep "SHA1" | sed "s/.*(SHA1): //" | sed "s/://g" | tr "[A-Z]" "[a-z]"
Obtaining Hash from Signed APK
apksigner verify --print-certs ${SOME_APK} | grep "SHA-1" | sed "s/.*: //"
Play App Signing
Effective August 2021, all new apps have to be published in the Android App Bundle (AAB) format and signed using Play App Signing. This process was designed to allow splitting apps into parts. The parts are then distributed so that only code and resources that are needed for a specific target device are downloaded.
Signing an AAB using Play App Signing follows these steps:
- Developer creates a keystore with an upload key (done only once).
- App bundle is built and signed with the upload key.
- Opt in to Play App Signing (done only once).
- Signed app bundle is uploaded to Google Play.
- Google Play Store re-signs it with a release key.
- Google Play Store distributes APKs generated from the app bundle suited to each individual device.
The signature hash can be obtained either from Google Play Console or from a signed APK that was downloaded from the Play Store and installed on a device.
Obtaining Hash from Google Play Console
Go to the Google Play Console and then to Setup > App integrity. Find a section called App signing key certificate.
Copy the value of SHA-1 certificate fingerprint
and remove colons from the string. If you are using Linux or a Mac, you can copy the hash value and then run the following command to remove the colons:
pbpaste | sed "s/://g" | tr "[A-Z]" "[a-z]"
Obtaining Hash from Device
When Play App Signing is used, obtaining the correct app signature from the app on a device is a bit more complicated. It’s necessary to get an APK with the right signature – the app has to be installed from the Google Play Store. You can’t use development builds anymore as they are signed with the upload key.
To get the signature hash, you have to pull the APK from an Android device first. Find the APK location on the device:
adb shell pm list packages -f | grep "$PACKAGE$" | sed "s/package://" | sed "s/=$PACKAGE//"
Pull the APK from the device:
adb pull "$APK_LOCATION"
Obtain the signature hash in the right format:
apksigner verify --print-certs ${SOME_APK} | grep "SHA-1" | sed "s/.*: //"
Testing Correct Repackaging Config
The complexity of repackaging config testing depends on the signing method used.
When the legacy APK signing is used, correct signature can be tested locally without uploading the app to the Play Store.
When Play App Signing is used, a development cycle that includes uploading to the Google Play store is necessary.
Testing with Play App Signing
To test repackaging configuration when Play App Signing is used, distributing an app build through Google Play is a necessary step.
Beside distributing production apps, Google Play offers several kinds of testing distributions:
- Open testing – anyone can join as a tester on Google Play
- Closed testing – testing with specific users
- Internal testing – for initial quality assurance check by a list of invited testers
We recommend performing Internal testing on the Google Play Console to test your repackaging configuration.
The testing cycle goes as follows:
- Build the app and sign it with the upload key.
- Create an Internal testing release (go to Testing > Internal testing in the Google Play Console).
- If you haven’t set up internal testing before, add some testers (email addresses have to be provided).
- Send your internal testers a link to join the testing, unless they have already joined. You can copy the link in the How testers join your test section on the page.
- Testers have to open the link. Upon accepting the testing invitation they are provided with a Google Play link for installing the app.
- Testers can now install a version of the app signed with the release key.
- Test the app. If Malwarelytics detects repackaging, the app certificate is not included in the
RaspConfig.signatureHash
list. In such case do not release to production. Obtain the correct signature hash, update the app and perform another testing cycle until you are sure that your app works flawlessly.
Usage
After initialization, the repackaging detection feature can be accessed via RaspManager
. This can be used to register an observer or to trigger a manual repackaging detection check.
Registering an Observer
Repackaging detection can trigger a certain action. To achieve that, an observer needs to be configured and registered.
Observer configuration:
val raspObserver = object : RaspObserver {
override fun onRepackagingDetected(repackagingResult: RepackagingResult) {
// handle repackaging detection
}
// handle detection of other RASP features
}
The observer can be registered in RaspManager
. When it is no longer needed, it can be unregistered again.
raspManager.registerRaspObserver(raspObserver)
raspManager.unregisterRaspObserver(raspObserver)
Triggering a Manual Check
Repackaging detection check can be triggered manually in RaspManager
by calling the isAppRepackaged()
method. A simple boolean answer is given.
val repackagingResult = raspManager.isAppRepackaged()
More information on general RASP feature configuration and usage can be found in this overview.