Just An Application

July 13, 2013

The Great Android Security Hole Of ’08 ? – Part Three: dalvik.system.PathClassLoader

When an Android application is launched an instance of dalvik.system.PathClassLoader is created for use as the application’s ClassLoader.

The dalvik.system.PathClassLoader constructor is passed a ‘path’ which includes the application’s APK.

1.0 PathClassLoader

Class: dalvik.system.PathClassLoader

Source: $(ANDROID_SRC)/libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java

1.1 The Constructor

    public PathClassLoader(String dexPath, ClassLoader parent) 
    {
        super(dexPath, null, null, parent);
    }

The PathClassLoader constructor simply invokes the constructor of the super class which is BaseDexClassLoader.

2.0 BaseDexClassLoader

Class: dalvik.system.BaseDexClassLoader

Source: $(ANDROID_SRC)/libcore/libdvm/src/main/java/dalvik/system/BaseDexClassLoader.java

2.1 The Constructor

    public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) 
    {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }

The BaseDexClassLoader constructor creates an instance of DexPathList which it will use when attempting to load classes.

It passes the DexPathList constructor itself plus its dexPath, libraryPath and optimizedDirectory arguments.

3.0 DexPathList

Class: dalvik.system.DexPathList

Source: $(ANDROID_SRC)/libcore/libdvm/src/main/java/dalvik/system/DexPathList.java

3.1 The Constructor

    public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory)

The constructor calls the method makeDexElements to create an array of Elements.

An Element represents either a directory, a Dex file, or a ZIP file which was specified in the dexPath argument.

3.2 makeDexElements

    private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory)

The method iterates over the elements in the files argument.

If the name of the File ends with one of

  • .apk

  • .jar

  • .zip

it invokes the loadDexFile method passing it the File and the optimizedDirectory argument.

3.3 loadDexFile

    private static DexFile loadDexFile(File file, File optimizedDirectory)

If the optimizedDirectory argument is null then the method creates a DexFile instance passing the File argument to the constructor.

4.0 DexFile

Class: dalvik.system.DexFile

Source: $(ANDROID_SRC)/libcore/libdvm/src/main/java/dalvik/system/DexFile.java

Source: $(ANDROID_SRC)/dalvik/vm/native/dalvik_system_DexFile.cpp

4.1 DexFile(File)

    public DexFile(File file) throws IOException {
        this(file.getPath());
    }

4.2 DexFile(String)

    public DexFile(String fileName) throws IOException

This constructor calls the method openDexFile passing it the fileName argument, null, and 0.

4.3 openDexFile

    native private static int openDexFile(
                                  String sourceName, 
                                  String outputName,
                                  int    flags) 
                              throws 
                                 IOException;

If the sourceName argument does not end with the suffix “.dex”, openDexFile calls the function dvmJarFileOpen passing it the C++ equivalents of its sourceName and outputName arguments plus a pointer to a JarFile* on the stack and false.

5.0 JarFile

Source: $(ANDROID_SRC)/dalvik/vm/JarFile.cpp

5.1 dvmJarFileOpen

     int dvmJarFileOpen(const char* fileName, const char* odexOutputName, JarFile** ppJarFile, bool isBootstrap)

The function starts by calling the function dexZipOpenArchive to open the named file.

If this is successful it then calls the function dexZipFindEntry to find the classes.dex file within the ZIP file.

6.0 ZipArchive

Source: $(ANDROID_SRC)/dalvik/libdex/ZipArchive.cpp

6.1 dexZipOpenArchive

     int dexZipOpenArchive(const char* fileName, ZipArchive* pArchive)

The function attempts to open the named file using the open system call.

If this is successful it then calls dexZipPrepArchive passing it the file descriptor returned by the call to open
and the fileName and pArchive arguments.

6.2 dexZipPrepArchive

    int dexZipPrepArchive(int fd, const char* debugFileName, ZipArchive* pArchive)

The function dexZipPrepArchive starts by mapping the Central Directory of the ZIP file into memory using the function mapCentralDirectory.

It this is successful it then calls parseZipArchive

6.2 parseZipArchive

    static int parseZipArchive(ZipArchive* pArchive)

The parseZipArchive function begins by creating a linear hash table.

It then iterates over the File Headers in the Central Directory.

For each File Header it calls the function addtoHash to create an entry in the linear hash table using the file name from the file header as the key.

The entry allocated within the linear hash table contains the address of the file name field within the File Header and the length of the file name.

Given the address of the file name field it is possible to determine the address of the File Header itself within the Central Directory.

6.4 addToHash

    static void addToHash(ZipArchive* pArchive, const char* str, int strLen, unsigned int hash)
    {
        const int hashTableSize = pArchive->mHashTableSize;
        int ent = hash & (hashTableSize - 1);

        /*
         * We over-allocated the table, so we're guaranteed to find an empty slot.
         */
        while (pArchive->mHashTable[ent].name != NULL)
            ent = (ent + 1) & (hashTableSize-1);

        pArchive->mHashTable[ent].name = str;
        pArchive->mHashTable[ent].nameLen = strLen;
   }

6.5 dexZipFindEntry

    ZipEntry dexZipFindEntry(const ZipArchive* pArchive, const char* entryName)
    {
        int nameLen = strlen(entryName);
        unsigned int hash = computeHash(entryName, nameLen);
        const int hashTableSize = pArchive->mHashTableSize;
        int ent = hash & (hashTableSize-1);

        while (pArchive->mHashTable[ent].name != NULL) {
            if (pArchive->mHashTable[ent].nameLen == nameLen &&
                memcmp(pArchive->mHashTable[ent].name, entryName, nameLen) == 0)
            {
                /* match */
                return (ZipEntry)(long)(ent + kZipEntryAdj);
            }

            ent = (ent + 1) & (hashTableSize-1);
        }

        return NULL;
    }

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.

%d bloggers like this: