Threat Research

An Android Package is no Longer a ZIP

By Axelle Apvrille | August 23, 2018

Over the past few years, I have been giving workshops on Android reverse engineering - my next one will be an advanced session at Virus Bulletin in October. As most other researchers on Android, I typically start off with a slide explaining that an Android Package (APK) is just a ZIP. Since Android 7.0, however, this is no longer true.

The Issue with Android/HiddenMiner

Everything started when I analyzed a sample of Android/HiddenMiner.A!tr. This malware poses as a Play Store update app, when it actually mines Monero in the background on your smartphone. After some static analysis, I wanted to check a few things and take a few screenshots running the sample in an Android emulator. And I got this error message:

# adb install hiddenminer.apk 
adb: failed to install hiddenminer.apk: Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: Failed to collect certificates from /data/app/vmdl1273641092.tmp/base.apk using APK Signature Scheme v2: SHA-256 digest of contents did not verify]

This led me to read more on APK Signature Scheme v2. Version 2 introduces a new APK signing mechanism, starting in Android 7.0 (Nougat). Since that version, the Android application package's format has changed, and now differs slightly from a normal ZIP file.

The APK Format: a Modified ZIP File

The modification consists in adding a special block in the ZIP file, called the APK Signing Block. This block is not specified in the ZIP format. Consequently, an APK is, strictly speaking, no longer a ZIP file. Interestingly, this causes issues to the 010Editor.

Parsing a sample of Android/HiddenMiner with the ZIPAdv template in 010Editor fails with the error message "Unknozn ZIP tag encountered. Template stopped"

The format of a standard ZIP file is illustrated as follows.

ZIP file format, explained by Ange Albertini

Normally, you start parsing the file from the end. You locate a block named the End of Central Directory (EOCD), based on its magic "PK0506". This block gives you the offset to the first Central Directory header. You have one of these per file in the ZIP file. Each of these Central Directory headers contains information, such as the compressed and uncompressed size of the file, its name etc. They also provide an offset to a Local File Header, which contains the actual (compressed) file data. I am simplifying a bit, because the ZIP format is actually quite complex, but this is the general idea.

Format of an APK since Android 7.0

With the APK Signature Scheme v2, a new block squeezes between the last Local File Header and the first Central Directory header. This block is called the "APK Signing Block", and it is meant to improve the authenticity of the APK. As Google describes it, this block is meant to be accessed from the first Central Directory header. Just before that header, we should find a magic "APK Sig Block 42", which is there to help identify the APK Signing Block. Then, just before this, we find the size of the block, and before that the block information, and before that, again the size of the block.

The Format of an APK File Since Android 7.0

The method used to parse the block is awkward (in my opinion). I wonder why Google did not simply create a tag for the APK Signing Block using a magic block like "PK0708". Consequently, parsers (like 010editor's ZIP template), which read the file top down, fail to correctly understand its structure and bump into an unknown block. If we edit the ZIP template to print the location where it fails, it shows offset 3279124. We will see later on that this is exactly the offset of the APK Signing Block.

662a663
>         Printf("zip end locator encountered\n");
666c667,668
<       Warning( "Unknown ZIP tag encountered. Template stopped." );
---
>         Printf("Location of unknown tag: %lu", FTell());
>       Warning( "Unknown ZIP tag encountered. Template stopped." );
ZIP template shows the parser fails on offset 3279124

The good thing with Google's modification to the ZIP, however, is that it occurs once all files have been uncompressed. So, tools like zip continue to work - and 010Editor only fails at the end of its parsing.

Parsing the APK Signing Block

I wanted to exactly see what is in the APK Signing Block. So, I wrote a parser for it. The usage is extremely simple: provide as the argument the name of the APK.

$ python parse_apk.py ./original/31747B02.vat

The parser parses the APK the way Google expects it to be done, and displays the various structures: End of Central Directory, Central Directory header, Local File header, and of course, the APK Signing Block. The offset of the APK Signing Block is 3279124.

APK Parser - supports APK Signing Block v2

The format of an APK Signing Block is specified here. In the case of the Android/HiddenMiner malware, we recover a correct block. In particular, we spot:

  • A digest of the local file headers, file data, central directory headers and EOCD. The digest uses SHA-256 and is 26a718dd28c7aa7a957f11f9ae9059af5558aabfb815bf37df3274019349dd27
  • A certificate. The malware author(s) used an Android Debug certificate.
  • A signature signing the important parts of the APK Signing Block itself. The signature algorithm is RSASSA-PKCS1-v1_5

Understanding the Error Message for Android/HiddenMiner

Remember the error message at APK installation was "INSTALL_PARSE_FAILED_NO_CERTIFICATES: Failed to collect certificates from /data/app/vmdl1273641092.tmp/base.apk using APK Signature Scheme v2: SHA-256 digest of contents did not verify". As we can recover the signing certificate without any problem, and it is a self-signed certificate (see below), there is no issue with collecting certificates as the message says.

Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number: 1 (0x1)
    Signature Algorithm: sha1WithRSAEncryption
        Issuer: CN=Android Debug, O=Android, C=US
        Validity
            Not Before: Oct 11 09:33:34 2017 GMT
            Not After : Oct  4 09:33:34 2047 GMT
        Subject: CN=Android Debug, O=Android, C=US
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (1024 bit)
...

Rather, the important problem is at the end of the message: "SHA-256 digest of contents did not verify". This means that the integrity of local file headers, file data, central directory headers, and EOCD are compromised. This is why the APK fails to install. The malware author(s) got something wrong at this point, and fortunately, end-users with Android 7.0 and above will be protected.

The malware installs on older versions of Android though - this is a side-effect of backward compatibility of the APK format. Fortunately, Fortinet customers are protected by the anti-virus signature we issued ;)

Finally, if you look closely at the APKs, you'll notice a comment in the EOCD.

Android/HiddenMiner includes a base64 encoded comment in its package

This is a base64 encoded string which decodes to {"site_id":"1014","tracker_id":"1af52uqtwiklpecd"}. It is clearly a marker added by the malware author(s) to track malicious samples they distribute.

-- the Crypto Girl

IOCs:

1c24c3ad27027e79add11d124b1366ae577f9c92cd3302bd26869825c90bf377
1f3d53ceb57367ae137cad2afac8b429a44c4df8c6202c0330d125981ea9652f
3039b2ff2e1edb522ffadaeaed8b0cee1519cfa56fabe7ce6f0f6a50816d026d
7fbf758feaf4d992b16b26ac582a4bdcfc1a36b6f29b52fc713a2b8537f54202
385171217bc94a3d56d497033adaf31be0cb83dcb7e5794b968aff187507f4a9
419629e1644b0179f0ae837fe3f8d80c6e490a59838e485eeda048bf8df176d2
6fb8cb3fb549c3531cc5661f6ec5d30c628cba6b1b72719be10823d85cae16e4
975a12756ca4f5e428704f7c553fd2b2ccc12f7965dd61c80bec7bcba08c1b37
b40e2eef49edb271bba2e5ad15c773e6ebdf4bfe5822ad93ddfe20847b8f9d67
b924a8ec7cfc1d5ddd9828467d7fc583fa6b35f441170d171c7a084ffd1799ad
bf9c41ee9d4a718f6b6958ec2e935395e79882b0ebee545e2c84277dba70a657
e62c034516f28a01abd1014d5d9caa7e103ae42c4d38419c39bc9846538747fa