安卓安全小分隊發現Android新漏洞

    幾天前,Bluebox Security剛曝出了Android存在安全漏洞。小分隊立刻就掌握了其技術細節。最近幾天經過對Android的研究,小分隊又發現了一個類似的漏洞。攻擊者可以對原apk進行修改,但不修改其原apk的簽名。只是原理跟Bluebox Security曝的漏洞不太一樣,但效果是一樣的。

 

這次我們講講技術細節:

1.在講這個漏洞之前,首先需要搞明白java裏short類型轉int類型的問題。要理解這個漏洞,必須明白這個技術點。

public class JavaTest {
    public static void main(String[] args) {
        short a = (short)0xFFFF;
        int b;
        b = a;
        System.out.println(b);
        b = a & 0xFFFF;
        System.out.println(b);
    }
}


如果你能很清楚的瞭解上述代碼中兩次打印變量b的值有何不同,以及爲何不同的話,這部分就可以先跳過了。否則還是要先弄清楚再往下看。

 

2. Zip文件格式

    在每個Zip文件中都有一個Central directory,Central directory中的每一項是一個File header。這個File header的結構對應到Android代碼的類就是ZipEntry。File header結構中有一個偏移量指向local file header,local file header後面就緊跟着file data。接下來我們詳細看一下local file header的結構。        

        local file header signature     4 bytes  (0x04034b50)
        version needed to extract       2 bytes
        general purpose bit flag        2 bytes
        compression method              2 bytes
        last mod file time              2 bytes
        last mod file date              2 bytes
        crc-32                          4 bytes
        compressed size                 4 bytes
        uncompressed size               4 bytes
        file name length                2 bytes
        extra field length              2 bytes
        file name (variable size)
        extra field (variable size)

可以看到,除最後2個域以外,local file header的其他域都是定長的。而這兩個變長域的長度是由file name length和extra field length所確定。再次說明,緊跟在extra field後面的就是文件的數據file data了。

 

3. Android如何進行apk校驗

Android在進行apk文件校驗時,會調到ZipFile的public InputStream getInputStream(ZipEntry entry)函數。這函數中,有這麼一段:

        RAFStream rafstrm = new RAFStream(raf, entry.mLocalHeaderRelOffset + 28);
        DataInputStream is = new DataInputStream(rafstrm);
        int localExtraLenOrWhatever = Short.reverseBytes(is.readShort());
        is.close();
 
        // Skip the name and this "extra" data or whatever it is:
        rafstrm.skip(entry.nameLength + localExtraLenOrWhatever);
        rafstrm.mLength = rafstrm.mOffset + entry.compressedSize;
        if (entry.compressionMethod == ZipEntry.DEFLATED) {
            int bufSize = Math.max(1024, (int)Math.min(entry.getSize(), 65535L));
            return new ZipInflaterInputStream(rafstrm, new Inflater(true), bufSize, entry);
        } else {
            return rafstrm;
        }

注意:上述代碼中紅色部分。localExtraLenOrWhatever就是local file header結構中的extra field length。回想一下我們第一部分將的技術點,如果這裏的extra filed length的大小是大於2^15,會怎麼樣?

沒錯,localExtraLenOrWhatever將會是負值。因此接下來,rafstrm.skip(entry.nameLength + localExtraLenOrWhatever); 這句將無法真正跳過變長域file name (variable size) 和extra field (variable size)。反而有可能呢會跳到file name (variable size)中,甚至file name (variable size)之前。當然爲了攻擊方便,我們還是期望它跳到file name (variable size)中。

 

4. 如何實施攻擊

   要改變一個apk的行爲,顯然攻擊的目標就是apk裏的classes.dex文件。對於classes.dex文件在apk文件中的local file header結構,其file name (variable size)域的內容肯定就是“classes.dex”了。注意,這裏的後綴名dex,正好和dex文件開頭的三個字節完全相同(不理解的,參見dex文件格式)。

    a) 利用這一點,從file name (variable size)域“classex.dex”的“.”之後開始我們可以寫入一個完整的dex文件。這個dex文件必須是原apk裏的classes.dex文件。只有這樣才能繞過簽名驗證

    b) 修改extra field length,使之爲0xFFFD。因爲這個值剛好爲-3。根據漏洞,rafstrm.skip(entry.nameLength + localExtraLenOrWhatever); 這句就會跳到file name (variable size)域中的“.”之後。也就是一個dex文件的開始,這裏必須是原dex文件內容。

    c) 修改local file header之後的file data數據。在這裏寫入帶有攻擊代碼的classes.dex內容。

    d) 以上的修改會帶來apk文件一些結構上的調整,比如擴充extra field域,調整file data大小等。

具體攻擊模型,如下圖:

    

 

5. 總結

   總的來說該攻擊手段,首先利用了Android在簽名驗證過程中,對Zip文件相應16位域的讀取時,沒有考慮到大於2^15的情況。(因爲java的int , short, long都是有符號數,而不像C/C++裏有無符號數)。

   其次利用了Zip文件中的local file header結構的extra field域來存放原classes.dex。但這個域的大小最多隻能是2^16-1,因此被攻擊的Apk裏的classes.dex大小必須在64K以內。否則,就無法對其進行攻擊。這算是這種攻擊方式的一個限制。

   最後還有一個問題補充說明:之所以這種攻擊方式能成功,還在於在運行時,系統抽取的是hacked classes.dex,而在簽名校驗時,驗證的是extra域裏的classes.dex。前者是在libdex.so中實現,後者在Java層實現。是由Java層跟Native層不一致導致。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章