Just An Application

July 15, 2013

The Great Android Security Hole Of ’08 ? – Part Six: The Gory Details — The APK Verification Considered Ineffective Edition

As we have seen, verification of the files within an APK occurs during the execution of the PackageParser method collectCertificates.

This method constructs a JarFile instance using the JarFile(File) constructor.

It then calls the JarFile method entries to obtain an Enumeration which it uses to iterate over all the files in the APK.

It is the entries method that is the issue, or rather, what it returns.

1.0 The JarFile entries Method

Class: java.util.jar.JarFile

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

    @Override
    public Enumeration entries() {
        class JarFileEnumerator implements Enumeration<JarEntry> {
            Enumeration<? extends ZipEntry> ze;

            JarFile jf;

            JarFileEnumerator(Enumeration<? extends ZipEntry> zenum, JarFile jf) {
                ze = zenum;
                this.jf = jf;
            }

            public boolean hasMoreElements() {
                return ze.hasMoreElements();
            }

            public JarEntry nextElement() {
                JarEntry je = new JarEntry(ze.nextElement());
                je.parentJar = jf;
                return je;
            }
        }
        return new JarFileEnumerator(super.entries(), this);
    }

It turns out that this method is effectively just wrapping the entries method of the super class java.util.zip.ZipFile.

2.0 The ZipFile entries Method

Class: java.util.zip.ZipFile

Source: $(ANDROID_SRC)/libcore/luni/src/main/java/java/util/zip/ZipFile.java

    public Enumeration<? extends ZipEntry> entries() {
        checkNotClosed();
        final Iterator<ZipEntry> iterator = entries.values().iterator();

        return new Enumeration<ZipEntry>() {
            public boolean hasMoreElements() {
                checkNotClosed();
                return iterator.hasNext();
            }

            public ZipEntry nextElement() {
                checkNotClosed();
                return iterator.next();
            }
        };

This method is in turn wrapping the values held in the entries instance variable.

3.0 The ZipFile entries Instance Variable

The entries instance variable is declared like this

    private final LinkedHashMap<String, ZipEntry> entries = new LinkedHashMap<String, ZipEntry>();

It is populated by the readCentralDirectory method which is invoked by the ZipFile(File,int) constructor.

4.0 ZipFile readCentralDirectory

The readCentralDirectory method starts, as you might expect, by scanning backwards through the file looking for the End of Central Directory record.

        ...

        // Scan back, looking for the End Of Central Directory field. If the zip file doesn't
        // have an overall comment (unrelated to any per-entry comments), we'll hit the EOCD
        // on the first try.
        // No need to synchronize raf here -- we only do this when we first open the zip file.
        long scanOffset = raf.length() - ENDHDR;
        if (scanOffset < 0) {
            throw new ZipException("File too short to be a zip file: " + raf.length());
        }

        long stopOffset = scanOffset - 65536;
        if (stopOffset < 0) {
            stopOffset = 0;
        }

        final int ENDHEADERMAGIC = 0x06054b50;
        while (true) {
            raf.seek(scanOffset);
            if (Integer.reverseBytes(raf.readInt()) == ENDHEADERMAGIC) {
                break;
            }

            scanOffset--;
            if (scanOffset < stopOffset) {
                throw new ZipException("EOCD not found; not a zip file?");
            }
        }
        
        ...

Once it has found it, it reads the fields it is interested in and does a basic sanity check.

        ...

        // Read the End Of Central Directory. We could use ENDHDR instead of the magic number 18,
        // but we don't actually need all the header.
        byte[] eocd = new byte[18];
        raf.readFully(eocd);

        // Pull out the information we need.
        BufferIterator it = HeapBufferIterator.iterator(eocd, 0, eocd.length, ByteOrder.LITTLE_ENDIAN);
        int diskNumber = it.readShort() & 0xffff;
        int diskWithCentralDir = it.readShort() & 0xffff;
        int numEntries = it.readShort() & 0xffff;
        int totalNumEntries = it.readShort() & 0xffff;
        it.skip(4); // Ignore centralDirSize.
        long centralDirOffset = ((long) it.readInt()) & 0xffffffffL;

        if (numEntries != totalNumEntries || diskNumber != 0 || diskWithCentralDir != 0) {
            throw new ZipException("spanned archives not supported");
        }
        
        ...

It then seeks to the start of the Central Directory and reads the File Headers and adds them to the entries LinkedHashMap.

        ...

        RAFStream rafStream = new RAFStream(raf, centralDirOffset);
        BufferedInputStream bufferedStream = new BufferedInputStream(rafStream, 4096);
        byte[] hdrBuf = new byte[CENHDR]; // Reuse the same buffer for each entry.
        for (int i = 0; i < numEntries; ++i) {
            ZipEntry newEntry = new ZipEntry(hdrBuf, bufferedStream);
            entries.put(newEntry.getName(), newEntry);
        }
        
        ...

It is the line in red that is the problem.

No check is made to see whether the ZIP file contains multiple files with the same name.

As a result, in the unlikely event of a ZIP file containing N files of the same name, then the JarFile entries method will only return the N'th. The preceding N-1 files will silently disappear.

5.0 Implications For The Verification Of An APK

If an APK just happens to have two files of the same name in it, then only the second one which gets verified, which is a bit unfortunate really if those files are called classes.dex, as we shall see.


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.

Advertisements

1 Comment »

  1. […] function parseZipArchive is the equivalent of the java.util.ZipFile readCentralDirectory […]

    Pingback by The Great Android Security Hole Of ’08 ? – Part Seven: The Gory Details — The Loading Classes From The ‘Wrong’ classes.dex Edition | Just An Application — July 17, 2013 @ 12:21 pm


RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

%d bloggers like this: