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 this case, Malwarelytics can be configured to terminate the app.

Configuration

This feature can be configured during the Malwarelytics initialization phase:

val raspConfig = RaspConfig.Builder()
    .repackage(
        RepackageDetectionConfig.Builder()
            .action(DetectionConfig)
            .signatureHash(Collection<String>)
            .build()
    )
    // configuration of other RASP features
    .build()
Method Description
action(DetectionConfig) specifies the automatic behavior of repackaging detection feature. Defaults to DetectionConfig.Exit().
signatureHash(Collection<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.

Available values of DetectionConfig:

Value Description
NoAction indicates that repackaging will not be automatically detected. A manual check is still possible but works only if signatureHash is set.
Notify indicates that repackaging will be automatically detected and observers will be notified. Works only if signatureHash is set.
Exit(
exitUrl:String?)
indicates that repackaging will be automatically detected and the app will be terminated when a repackaging is automatically detected. Works only if signatureHash is set.

Repackaging detection defaults to DetectionConfig.Exit(null).

List of available parameters for some config values:

Parameter Description
exitUrl:String? defines the URL to be opened when the app is terminated because of the automatic 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). Afterward, the app is built and signed with this key. The signed app is then uploaded to the 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 the code and resources that are needed for a specific target device are downloaded.

Signing an AAB using Play App Signing follows these steps:

  1. The developer creates a keystore with an upload key (done only once).
  2. The app bundle is built and signed with the upload key.
  3. Opt in to Play App Signing (done only once).
  4. The signed app bundle is uploaded to Google Play.
  5. Google Play Store re-signs it with a release key.
  6. 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, the 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.

Besides distributing production apps, Google Play offers several kinds of testing distributions:

We recommend performing Internal testing on the Google Play Console to test your repackaging configuration.

The testing cycle goes as follows:

  1. Build the app and sign it with the upload key.
  2. Create an Internal testing release (go to Testing > Internal testing in the Google Play Console).
  3. If you haven’t set up internal testing before, add some testers (email addresses have to be provided).
  4. 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.
  5. Testers have to open the link. Upon accepting the testing invitation they are provided with a Google Play link for installing the app.
  6. Testers can now install a version of the app signed with the release key.
  7. Test the app. If Malwarelytics detects repackaging, the app certificate is not included in the RaspConfig.signatureHash list. In this 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

The 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.

Last updated on Nov 21, 2023 (12:06) View product
Search

1.2.x

Malwarelytics for Android