Just An Application

July 13, 2013

The Great Android Security Hole Of ’08 ? – Part Two: Signed JARs

1.0 Structure

An Android APK is a signed JAR and a signed JAR is a good old-fashioned ZIP file.

If you list the contents of an APK using unzip you will see something like this

    Archive:  GoodAppBadApp.apk
      Length     Date   Time    Name
     --------    ----   ----    ----
          624  07-10-13 21:11   res/layout/activity_main.xml
          464  07-10-13 21:11   res/menu/activity_main.xml
         1588  07-10-13 21:11   AndroidManifest.xml
         2504  07-10-13 21:11   resources.arsc
          409  07-10-13 21:11   res/drawable-hdpi/ic_action_search.png
         4129  07-10-13 21:11   res/drawable-hdpi/ic_launcher.png
         1756  07-10-13 21:11   res/drawable-ldpi/ic_launcher.png
          311  07-10-13 21:11   res/drawable-mdpi/ic_action_search.png
         2654  07-10-13 21:11   res/drawable-mdpi/ic_launcher.png
          491  07-10-13 21:11   res/drawable-xhdpi/ic_action_search.png
         5456  07-10-13 21:11   res/drawable-xhdpi/ic_launcher.png
       366112  07-10-13 21:11   classes.dex
         1034  07-10-13 21:11   META-INF/MANIFEST.MF
         1087  07-10-13 21:11   META-INF/CERT.SF
          776  07-10-13 21:11   META-INF/CERT.RSA
     --------                   -------
       389395                   15 files

It is the presence of the manifest file

    META-INF/MANIFEST.MF

which make this ZIP file a JAR, and it is the presence of the ‘signature’ files

    META-INF/CERT.SF
    META-INF/CERT.RSA

that makes it a signed JAR.

2.0 The Meta Files

2.1 MANIFEST.MF

This is what’s in the MANIFEST.MF file contained in the APK shown in the example above

    Manifest-Version: 1.0
    Created-By: 1.0 (Android)

    Name: res/drawable-xhdpi/ic_launcher.png
    SHA1-Digest: TJE1tg63y88xLdIALKGpuLmgh0s=

    Name: res/drawable-mdpi/ic_action_search.png
    SHA1-Digest: XjltJdEB3tvakTn9CN7KwdaNT68=

    Name: AndroidManifest.xml
    SHA1-Digest: iXwTXVvLX3gY+ByGhybEJvIxP08=

    Name: res/drawable-mdpi/ic_launcher.png
    SHA1-Digest: aM73uFWfPOOvq5Kk9Ffd3cWm0OQ=

    Name: res/drawable-hdpi/ic_launcher.png
    SHA1-Digest: ioH2V9g4FYKkqpDHk7jPryMKtcE=

    Name: res/layout/activity_main.xml
    SHA1-Digest: zd1eNopMfZ7CmMk8GjyPGPpYloc=

    Name: resources.arsc
    SHA1-Digest: 7nYq90PW0OZVg8ytyjBEcwQhYiA=

    Name: classes.dex
    SHA1-Digest: b80KFg5gdqlORweY5LrUS87tbVU=

    Name: res/drawable-hdpi/ic_action_search.png
    SHA1-Digest: rOWnNxWCWKwkicf+FpYGMg1fX2Y=

    Name: res/drawable-xhdpi/ic_action_search.png
    SHA1-Digest: WzSVDDIHZpn0cFeuxgKRJete4TU=

    Name: res/menu/activity_main.xml
    SHA1-Digest: TfxVPQP5IRVlJun7A7PdOqdkp+I=

    Name: res/drawable-ldpi/ic_launcher.png
    SHA1-Digest: r6Mdl54h2qEvVnqgfsgxU4CysiI=

As you can see there is a section for each file in the APK with the exception of those contained in the META-INF directory.

The SHA1-Digest attribute specifies the SHA-1[1] digest of the named file. The value is Base64 encoded.

2.2 CERT.SF

The CERT.SF file is a ‘signature file’. This is odd because technically it does not contain any signatures in the cryptographic sense, but there it is.

Signature files are identified by the suffix .SF

This is what’s in the CERT.SF file contained in the APK shown in the example above

    Signature-Version: 1.0
    Created-By: 1.0 (Android)
    SHA1-Digest-Manifest: uy1/gV4Q91cFNXDPKFWnIkdp7Hs=

    Name: res/drawable-xhdpi/ic_launcher.png
    SHA1-Digest: uQhdAiwpzle4OMU4KiZ8rHZZW0s=

    Name: res/drawable-mdpi/ic_action_search.png
    SHA1-Digest: jAGFiT7RUDOYcmBQqk2FP01Xtb0=

    Name: AndroidManifest.xml
    SHA1-Digest: app7CkR5QClO2DngK2SwqyoYT/4=

    Name: res/drawable-mdpi/ic_launcher.png
    SHA1-Digest: 7AYLMxFKX2g7vudLLZTXlC9DWHI=

    Name: res/drawable-hdpi/ic_launcher.png
    SHA1-Digest: diYsvf3zRlU+JNDApc7+KLVhhk0=

    Name: res/layout/activity_main.xml
    SHA1-Digest: tK4fMdQKbbzD/KoE5awGKM7o++Y=

    Name: resources.arsc
    SHA1-Digest: mMTrtN8w3SByhMHUQ19nJ3OT3wo=

    Name: res/drawable-hdpi/ic_action_search.png
    SHA1-Digest: WvsoZfcfGbz7vtTCwzXLh3VcxFw=

    Name: classes.dex
    SHA1-Digest: /okgNUxVNi1erXMkC7ftetu9S9U=

    Name: res/drawable-xhdpi/ic_action_search.png
    SHA1-Digest: 772ITFXOlprwo44bOLvLMa3Vdeg=

    Name: res/menu/activity_main.xml
    SHA1-Digest: K1PTOAgvzmJK47MHIsJIWgVr/04=

    Name: res/drawable-ldpi/ic_launcher.png
    SHA1-Digest: zC+Z/B5ib1ntZmz9D80Rj3muYqE=

The SHA1-Digest-Manifest in the first section specifies the SHA-1 digest of the manifest file.

In the following sections the SHA1-Digest attribute specifies the SHA-1 digest of the corresponding section in the manifest file.

2.3 CERT.RSA

The CERT.RSA file is the ‘signed signature file’.

The CERT.RSA file contained in the APK shown in the example above is a binary file, so this is a representation of its contents produced using the ubiquitous dumpasn1.


   0  772: SEQUENCE {
   4    9:   OBJECT IDENTIFIER signedData (1 2 840 113549 1 7 2)
  15  757:   [0] {
  19  753:     SEQUENCE {
  23    1:       INTEGER 1
  26   11:       SET {
  28    9:         SEQUENCE {
  30    5:           OBJECT IDENTIFIER sha1 (1 3 14 3 2 26)
  37    0:           NULL
         :           }
         :         }
  39   11:       SEQUENCE {
  41    9:         OBJECT IDENTIFIER data (1 2 840 113549 1 7 1)
         :         }
  52  489:       [0] {
  56  485:         SEQUENCE {
  60  334:           SEQUENCE {
  64    3:             [0] {
  66    1:               INTEGER 2
         :               }
  69    4:             INTEGER 1322302491
  75   13:             SEQUENCE {
  77    9:               OBJECT IDENTIFIER
         :                 sha1withRSAEncryption (1 2 840 113549 1 1 5)
  88    0:               NULL
         :               }
  90   55:             SEQUENCE {
  92   11:               SET {
  94    9:                 SEQUENCE {
  96    3:                   OBJECT IDENTIFIER countryName (2 5 4 6)
 101    2:                   PrintableString 'US'
         :                   }
         :                 }
 105   16:               SET {
 107   14:                 SEQUENCE {
 109    3:                   OBJECT IDENTIFIER organizationName (2 5 4 10)
 114    7:                   PrintableString 'Android'
         :                   }
         :                 }
 123   22:               SET {
 125   20:                 SEQUENCE {
 127    3:                   OBJECT IDENTIFIER commonName (2 5 4 3)
 132   13:                   PrintableString 'Android Debug'
         :                   }
         :                 }
         :               }
 147   30:             SEQUENCE {
 149   13:               UTCTime 26/11/2011 10:14:51 GMT
 164   13:               UTCTime 18/11/2041 10:14:51 GMT
         :               }
 179   55:             SEQUENCE {
 181   11:               SET {
 183    9:                 SEQUENCE {
 185    3:                   OBJECT IDENTIFIER countryName (2 5 4 6)
 190    2:                   PrintableString 'US'
         :                   }
         :                 }
 194   16:               SET {
 196   14:                 SEQUENCE {
 198    3:                   OBJECT IDENTIFIER organizationName (2 5 4 10)
 203    7:                   PrintableString 'Android'
         :                   }
         :                 }
 212   22:               SET {
 214   20:                 SEQUENCE {
 216    3:                   OBJECT IDENTIFIER commonName (2 5 4 3)
 221   13:                   PrintableString 'Android Debug'
         :                   }
         :                 }
         :               }
 236  159:             SEQUENCE {
 239   13:               SEQUENCE {
 241    9:                 OBJECT IDENTIFIER
         :                   rsaEncryption (1 2 840 113549 1 1 1)
 252    0:                 NULL
         :                 }
 254  141:               BIT STRING, encapsulates {
 258  137:                 SEQUENCE {
 261  129:                   INTEGER
         :                   00 8A 0A 1D 53 48 FC 26 6D BF 65 57 04 8B 0B E9
         :                   58 EE E7 11 CE A8 A2 01 72 7B 34 C1 31 60 B4 64
         :                   FC D3 0D 1E 94 BE B3 5B 55 7F 23 97 B9 3D 6F E8
         :                   17 17 FE CA 50 EB F3 EA 4B 7C F6 F9 B6 6E E7 5D
         :                   0D 64 79 00 8F 12 1A 76 09 C1 32 4F 28 E8 27 94
         :                   34 F5 C8 50 C3 0D E9 2E FF A2 97 71 6C 18 7F 7C
         :                   8A 78 EA 37 50 20 8D DA 6C F2 3F 65 85 67 6A 7B
         :                   35 BF 5A 8D 53 DE AD 9D 50 70 30 B4 0D A4 60 D4
         :                           [ Another 1 bytes skipped ]
 393    3:                   INTEGER 65537
         :                   }
         :                 }
         :               }
         :             }
 398   13:           SEQUENCE {
 400    9:             OBJECT IDENTIFIER
         :               sha1withRSAEncryption (1 2 840 113549 1 1 5)
 411    0:             NULL
         :             }
 413  129:           BIT STRING
         :             2E 77 85 3F F4 AF EE 1E 81 9B 8B BB E2 9C AA 95
         :             BE 38 1E 20 EA E3 63 BE 2B A7 F2 AB 8A F4 F5 27
         :             35 F6 7C 67 CF E5 07 82 D0 B5 D8 00 77 CC 9A 8B
         :             86 DD 4C DD 41 88 2E C5 B4 B9 7E E9 C4 B2 B9 FA
         :             31 A0 60 6F DA 8D D1 F9 FA F6 94 08 92 C2 58 D6
         :             BF 93 1E 4D B8 DF DD D3 C1 11 89 14 22 C8 8B 85
         :             98 04 7A F5 00 D5 1D F2 E0 54 42 82 46 2D FE 2D
         :             C3 AB 2B C6 BC E7 07 A2 B8 CD 04 26 01 F1 EB E5
         :           }
         :         }
 545  228:       SET {
 548  225:         SEQUENCE {
 551    1:           INTEGER 1
 554   63:           SEQUENCE {
 556   55:             SEQUENCE {
 558   11:               SET {
 560    9:                 SEQUENCE {
 562    3:                   OBJECT IDENTIFIER countryName (2 5 4 6)
 567    2:                   PrintableString 'US'
         :                   }
         :                 }
 571   16:               SET {
 573   14:                 SEQUENCE {
 575    3:                   OBJECT IDENTIFIER organizationName (2 5 4 10)
 580    7:                   PrintableString 'Android'
         :                   }
         :                 }
 589   22:               SET {
 591   20:                 SEQUENCE {
 593    3:                   OBJECT IDENTIFIER commonName (2 5 4 3)
 598   13:                   PrintableString 'Android Debug'
         :                   }
         :                 }
         :               }
 613    4:             INTEGER 1322302491
         :             }
 619    9:           SEQUENCE {
 621    5:             OBJECT IDENTIFIER sha1 (1 3 14 3 2 26)
 628    0:             NULL
         :             }
 630   13:           SEQUENCE {
 632    9:             OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
 643    0:             NULL
         :             }
 645  128:           OCTET STRING
         :             22 52 11 23 F5 AA 38 2E 28 FD 96 1E 2C AB 96 FC
         :             E7 85 95 54 A0 F7 14 49 93 F4 19 A7 F0 E5 CC B9
         :             FF 82 B6 8D 84 A2 3A DD F6 CA 6A 4F 3A 38 B4 EA
         :             69 23 BF B1 29 5F C2 49 48 34 63 35 84 59 C6 69
         :             0C E3 F1 D3 90 B5 02 F5 0D 79 CC B4 B0 25 0C BD
         :             38 4F B2 FC D1 D7 D4 11 84 EA A1 66 31 F0 08 DA
         :             0F F6 94 23 96 5C 83 96 C9 DA CA C0 FC 60 84 C2
         :             54 21 DA CA E4 C4 D0 7D FD 98 C4 91 8A 2C 5D E4
         :           }
         :         }
         :       }
         :     }
         :   }

If you are well-versed in the arcana of ASN.1 and the myriad security standards you will recognize this as ‘signed data’ as specified by PKCS#7. If you aren’t, you won’t, but that’s what it is nonetheless.

It is the SHA-1 digest of the CERT.SF file which has been encrypted using the private key corresponding to the public key contained in the accompanying certificate.

There is a bit more to it than that, but that is basically it.

3.0 Signed JAR Verification In Theory

To make life more exciting there can in fact be multiple signature files and signed signature files in a signed JAR but in what follows I am going to assume we are attempting to verify the APK shown in the example above in which there is just the one signature/signed signature file pair.

3.1 Decide Whether You ‘Trust’ The Signer Of The JAR

The signer of the JAR is the entity identified by the certificate which accompanies the signed data in the signed signature file (CERT.RSA).

The decision to ‘trust’ that entity is often made by going up the chain of certificates looking for one which is defined to be a ‘trust root’.

If the certificate that accompanies the signed data in the signed signature file is self-signed or was not signed directly or indirectly by a certificate defined to be a ‘trust root’ then the JAR should be rejected.

3.2 Verify The Signature File

Compute the digest of the signature file (CERT.SF) and use the signature in the signed signature file (CERT.SF) to verify it. If verification fails then reject the JAR.

3.3 Verify The Manifest File

If the expected digest of the manifest file is specified in the signature file compute the digest of the manifest file. If the expected and computed digests do not match then reject the JAR.

Alternatively, for each file in the JAR other than those in the META-INF directory find the corresponding section in the manifest.

If there is no corresponding section reject the JAR.

Otherwise find the expected digest of the section in the signature file.

If there is no expected digest fo the section in the signature file reject the JAR.

Otherwise compute the hash of the section in the manifest. If the computed and expected digests do not match reject the JAR.

3.4 Verify The Remaining Files In The JAR

For each file in the JAR that has not already been verified check that its digest has been specified in the manifest file.

If it has not then reject the JAR.

If the expected digest is specified in the manifest file then compute the actual digest. If the expected and computed digests do not match then reject the JAR.

If all the files in the JAR are successfully verified then accept the JAR.

4.0 Signed JAR Verification In Practice

The Java class java.util.JarFile supports the piecemeal implicit verification of files in signed JARs

4.1 java.util.JarFile

Source: $(ANDROID_SRC)/libcore/luni/src/main/java/java/util/jar/JarFile.java

4.1.1 Constructing A JarFile

4.1.1.1 JarFile(File)

    public JarFile(File file) throws IOException {
        this(file, true);
    }

4.1.1.2 JarFile(File, boolean)

    public JarFile(File file, boolean verify) throws IOException

Passing this constructor a File argument which represents a signed JAR and a verify argument of true will result in a JarFile instance which supports the verification of individual files within the underlying signed JAR.

4.2 The Verification Of Individual Files Within A Signed JAR Using A JarFile Instance

Given a JarFile instance initialized to verify an underlying signed JAR as descibed above there is surprisingly no way to explicitly verify the entire contents of the signed JAR.

Nor is there any way to explicitly verify an individual file within the signed JAR.

The only way to verify an individual file is to read the entire contents of the file. Doing this will implicitly verify the file but only at the point when all the contents have been read.

Since verification of a file involves computing the digest of the contents it is obviously necessary to read the entire contents before being able to verify it.

What is extremely problematic is that there is nothing whatsoever in the API documentation that explains that this is what happens and more particularly that it is not safe to act on anything in the contents of a file in a signed JAR, despite the fact that the contents are being made available via the API, until the entire file has been read.

4.2.1 The ‘Verification’ API

4.2.1.1 getInputStream

     public InputStream getInputStream(ZipEntry ze) throws IOException

The getInputStream method returns an InputStream which can be used to read the contents of the file represented by the JarEntry argument.

If the JarFile instance was constructed with a signed JAR and it is being verified then the method returns a an instance of the inner class JarFileInputStream with an associated JarVerifier.VerifyEntry instance.

4.2.1.2 The JarFileInputStream read Methods

The read methods read from the underlying stream. They then write the bytes read to the associated verifier.

If the call results in all of the file data having been read then verify method of the associated verifier is called. This will throw a SecurityException if the verification fails.

5.0 Signed JAR Verification During The Installation Of An APK By The Android PackageManagerService

When the PackageManagerService finds an APK while scanning a directory it calls the method scanPackageLI.

This triggers the verification of the APK as a signed JAR as follows.

5.1 PackageManagerService

Source: $(ANDROID_SRC)/frameworks/base/services/java/com/android/server/pm/PackageManagerService.java

5.1.1 scanPackageLI

    private PackageParser.Package scanPackageLI(
                                      File       scanFile, 
                                      int        parseFlags, 
                                      int        scanMode, 
                                      long       currentTime, 
                                      UserHandle user)

After determining whether it already knows about the APK, in which case it may return, scanPackageLI calls the method collectCertificatesLI.

5.1.2 collectCertificatesLI

private boolean collectCertificatesLI(
                    PackageParser         pp, 
                    PackageSetting        ps,
                    PackageParser.Package pkg, 
                    File                  srcFile, 
                    int                   parseFlags)

The method calls collectCertificates on the PackageParser argument to do the actual work.

5.2 PackageParser

Class: android.content.pm.PackageParser

Source: $(ANDROID_SRC)/frameworks/base/core/java/android/content/pm/PackageParser.java

5.2.1 collectCertificates

    public boolean collectCertificates(Package pkg, int flags)

The method begins by creating a JarFile using the JarFile(File) constructor.

It then calls the method entries on the JarFile instance.

It then iterates over these entries. For each entry it calls the method loadCertificates.

5.2.2 loadCertificates

    private Certificate[] loadCertificates(JarFile jarFile, JarEntry je, byte[] readBuffer)

The method begins by calling getInputStream on the JarFile argument passing it the JarEntry argument.

It then calls read repeatedly on the InputStream, ignoring what it is actually read, until the entire contents of the file have been read.

As we have seen it is the call to the read method which results in the last of the data being returned which causes the verification of that file.

Notes

  1. The official SHA standard is here (This is a PDF document).


Copyright (c) 2013 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

Blog at WordPress.com.