1. 程式人生 > >CVE-2015-3864漏洞利用分析(exploit_from_google)

CVE-2015-3864漏洞利用分析(exploit_from_google)

內存布局 labs dep ase printf ram onf gad 之間

前言

接下來要學習安卓的漏洞利用相關的知識了,網上搜了搜,有大神推薦 stagefright 系列的漏洞。於是開幹,本文分析的是 googleexploit. 本文介紹的漏洞是 CVE-2015-3864 , 在 google的博客上也有對該 exploit 的研究。

我之前下載下來了:

pdf版本 的鏈接:在這裏

exploit 的鏈接: https://www.exploit-db.com/exploits/38226/

分析環境:

Android 5.1 nexus4 

正文

這個漏洞是一個文件格式相關漏洞,是由 mediaserver 在處理 MPEG4 文件時所產生的漏洞,漏洞的代碼位於 libstagefright.so

這個庫裏面。

要理解並且利用 文件格式 類漏洞,我們就必須要非常清楚的了解目標文件的具體格式規範。

Part 1 文件格式學習

先來一張總體的格式圖
技術分享圖片

mp4 文件由 box 組成,圖中那些 free, stsc等都是box, box 裏也可以包含 box ,這種 box 就叫 containerbox .

  • 每個 box 前四個字節為 boxsize

  • 第二個四字節為 boxtypebox typeftyp,moov,trak 等等好多種,moovcontainerbox ,包含 mvhdtrakbox

還有一些要註意的點。

  • box 中存儲數據采用大端字節序存儲
    size
    域為 0時,表示這是文件最後一個 box
  • size 為1 時,表示這是一個 large box ,在 type 域後面的 8 字節 作為該 box 的長度。

下面來看兩個實例。

實例一

技術分享圖片

  • size 域為 00000014,所以該 box長度為 0x14 字節。
  • type 域為 66 74 79 70 所以 typefytp
  • 剩下的一些信息是一些與多媒體播放相關的一些信息。與漏洞利用無關,就不說了。

實例二

技術分享圖片

  • size 域為1,表示從該 box 開頭偏移8字節開始的8字節為 size 字段, 所以該 box 的大小為 0xFFFFFFFFFFFFFF88
  • typetx3g

現在我們對該文件的格式已經有了一個大概的了解,這對於漏洞利用來說還不夠,接下來我們要去看具體的解析該文件格式的代碼是怎麽實現的。

解析文件的具體代碼位於 MPEG4Extractor.cpp 中的 MPEG4Extractor::parseChunk 函數裏面。
該函數中的 chunk 對應的就是 box, 函數最開始先解析 typesize .

    // 開始4字節為 box 大小, 後面緊跟的 4 字節為 box type
    
    uint64_t chunk_size = ntohl(hdr[0]);
    uint32_t chunk_type = ntohl(hdr[1]); //大端序轉換
    off64_t data_offset = *offset + 8;  // 找到 box 數據區的偏移

    // 如果size區為1, 那麽後面8字節作為size
    if (chunk_size == 1) {
        if (mDataSource->readAt(*offset + 8, &chunk_size, 8) < 8) {
            return ERROR_IO;
        }
        chunk_size = ntoh64(chunk_size);
        data_offset += 8;

        if (chunk_size < 16) {
            // The smallest valid chunk is 16 bytes long in this case.
            return ERROR_MALFORMED;
        }
    } else if (chunk_size < 8) {
        // The smallest valid chunk is 8 bytes long.
        return ERROR_MALFORMED;
    }

通過註釋和代碼,我們知道對於 size 的處理和前面所述是一致的。然後就會根據不同的 chunk_type ,進入不同的邏輯,
技術分享圖片

如果 box 中還包含 子 box 就會遞歸調用該函數進行解析。

Part 2 漏洞分析

CVE-2015-3864 漏洞產生的原因是,在處理 tx3g box時,對於獲取的 size 字段處理不當,導致分配內存時出現整數溢出,進而造成了堆溢出。

技術分享圖片

size 為之前所解析的所有 tx3g box 的長度總和。chunk_size 為當前要處理的 tx3g box 的長度。然後 size + chunk_size 計算要分配的內存大小。 chunk_sizeuint64_t 類型的,chunk_size 我們在文件格式中我們所能控制的最大大小為 0xFFFFFFFFFFFFFFFF ( 看 part1 實例二 ) ,也是 64 位,但是我們還有一個 size 為可以控制,這樣一相加,就會造成 整數溢出 , 導致分配小內存。而我們的 數據大小則遠遠大於分配的內存大小,進而造成堆溢出

Part 3 漏洞利用

概述

現在我們已經擁有了堆溢出的能力,如果是在 ptmalloc 中,可以修改下一個堆塊的元數據來觸發 crash ,甚至可能完成漏洞利用。不過從 android 5開始,安卓已經開始使用 jemalloc 作為默認的堆分配器。

jemalloc 中,小內存分配采用 regions 進行分配, region 之間是沒有 元數據 的 (具體可以去網上搜 jemalloc 的分析的文章),所以 在 ctf 中常見的通過修改 堆塊元數據 的漏洞利用方法在這裏是沒法用了。

不過所有事情都有兩面性。region 間是直接相鄰的,那我就可以很方便的修改相鄰內存塊的數據。 如果我們在 tx3g 對應內存塊的後面放置一個含有關鍵數據結構的內存塊,比如一個對象,在 含有虛函數 的類的 對象開始4字節(32位下),會存放一個 虛表指針 .

對象 調用 虛函數 時會從 虛表指針 指向的位置的 某個偏移(不同函數,偏移不同) 處取到相應的函數指針,然後跳過去執行。

如果我們修改對象的虛表指針,我們就有可能在程序調用虛函數時,控制程序的流程。

一些重要的 chunk_type(box type)

tx3g box

上一節提到,我們可以修改對象的虛表指針,以求能夠控制程序的跳轉。那我們就需要找到一個能夠在解析 box 數據能時分配的對象。

MPEG4DataSource 就是這樣一個類。

技術分享圖片

可以看到該對象繼承自 DataSource, 同時還有幾個虛函數。

我們可以在ida中看看虛表的構成。

技術分享圖片

可以看到 readAt 方法在虛表的第7項,也就是虛表偏移 0x1c 處。同時MPEG4DataSource在我這的大小為 0x20 .再看一下漏洞位置的代碼。

技術分享圖片
可以看到如果當前解析的 tx3g box 不是第一個tx3g box(即size>0),會先調用 memcpy , 把之前所有 tx3g box中的數據拷貝到剛剛分配的內存。

如果我們先構造一個 tx3g ,其中包含的數據大於 0x20, 然後在構造一個 tx3g 構造大小使得 size+chunk_size = 0x20, 然後通過 memcpy 就可以覆蓋 MPEG4DataSource 的虛表了。exploit 中就是這樣幹的。

pssh box

看看代碼

技術分享圖片
劃線位置說明了 pssh 的結構。

    pssh 的結構
    開始8字節 表示 該 box 的性質
    
    00 00 00 40 70 73 73 68
    size: 0x40, 
    type: pssh :
    + 0xc 開始 16字節 為 pssh.uuid
    + 0x1c開始4字節為 pssh.datalen
    + 0x20 開始為 pssh.data
    可以查看 代碼,搜索關鍵字: FOURCC(‘p‘, ‘s‘, ‘s‘, ‘h‘)

這裏先分配 pssh.datalen 大小的內存,然後把 pssh.data 拷貝到剛剛分配的內存。完了之後會把 分配到的 PsshInfo 結構體增加到 類屬性值 Vector<PsshInfo> mPssh 中, mPsshMPEG4Extractor::~MPEG4Extractor()中才會被釋放。

技術分享圖片

所以在解析完 MPEG4格式前,通過 pssh 分配的內存會一直在內存中。

avcC box 和 hvcC box
這兩個 box 的處理基本一致,以 avcC 為例進行介紹。解析代碼如下

        case FOURCC(‘a‘, ‘v‘, ‘c‘, ‘C‘):
        {
            // 這是一塊臨時分配, buffer 為智能指針,在 函數返回時相應內存會被釋放。
            sp<ABuffer> buffer = new ABuffer(chunk_data_size);
            if (mDataSource->readAt(
                        data_offset, buffer->data(), chunk_data_size) < chunk_data_size) {
                return ERROR_IO;
            }
            // 在這裏,會釋放掉原來那個,新分配內存來容納新的數據。
            // 因此我們有了一個 分配,釋放 內存能力
            // setData 中會釋放掉原來的buf, 新分配一個 chunk_data_size

            mLastTrack->meta->setData(
                    kKeyAVCC, kTypeAVCC, buffer->data(), chunk_data_size);

            *offset += chunk_size;
            break;
        }

首先根據 chunk_data_size 分配 ABufferbufferchunk_data_sizeboxsize 域指定,註意buffer是一個智能指針,在這裏,它會在函數返回時釋放。

ABuffer 中是直接調用的 malloc 分配的內存。
技術分享圖片
接下來讀取數據到 buffer->data(), 最後調用 mLastTrack->meta->setData 保存數據到 meta, 在 setData 內部會先釋放掉之前的內存,然後分配的內存,存放該數據,此時分配內存的大小還是chunk_data_size, 我們可控。

技術分享圖片

hvcC 的處理方式基本一樣。所以通過這兩個 box 我們可以 分配指定大小的內存,並且可以隨時釋放前面分配的那個內存塊 。我們需要使用這個來布局tx3g內存塊 和 MPEG4DataSource內存塊。

修改對象虛表指針

下面結合exploit 和上一節的那幾個關鍵 box ,分析通過布局內存,使得我們可以修改 MPEG4DataSource 的虛表指針。
為了便於說明,取了 exploit 中的用於 修改對象虛表指針的相關代碼進行解析 ( 我調試過程做了部分修改 )
技術分享圖片

首先看到第7,8行,構造了第一個 tx3g box, 大小為 0x3a8, 後面在觸發漏洞時,會先把這部分數據拷貝到分配到的小內存buffer中,然後會溢出到下一個 regionMPEG4DataSource內存塊。使用 cyclic 可以在程序 crash 時,計算 bufferMPEG4DataSource 之間的距離。

13 行,調用了 memory_leak 函數, 該函數通過使用 pssh 來分配任意大小的內存,在這裏分配的是 alloc_size ,即 0x20. 因為MPEG4DataSource 的大小為 0x20 ,就保證內存的分配會在同一個 run 中分配。這些這樣這裏分配了 40x20 的內存塊,我認為是用來清理之前可能使用內存時,產生的內存碎片,確保後面內存分配按照我們的順序進行分配。此時內存關系

| pssh | - | pssh |

1725 行,清理內存後,開始分配 avcChvcC, 大小也是 0x20, 然後在第 25 行又進行了內存碎片清理,原因在於我們在分配 avcChvcC時,會使用到 new ABuffer(chunk_data_size),這個臨時的緩沖區,這個會在函數返回時被釋放(請看智能指針相關知識)

技術分享圖片
同時多分配了幾個 pssh 確保可以把 avcChvcC包圍在中間。所以現在的內存關系是

| pssh | - | pssh | pssh | avcC | hvcC | pssh |

然後是 第 29 行, 再次分配 hvcC ,不過這次的大小 為 alloc_size * 2, 觸發 hvcC 的釋放,而且確保不會占用 剛剛釋放的 內存.(jemalloc中 相同大小的內存在同一個run中分配)

| pssh | - | pssh | pssh | avcC | .... | pssh |

接下來構造 stblMPEG4DataSource 占據剛剛空出來的 內存。

| pssh | - | pssh | pssh | avcC | MPEG4DataSource | pssh |

接下來, 第 38 行用同樣的手法分配釋放 avcC

| pssh | - | pssh | pssh | .... | MPEG4DataSource | pssh |

然後使用整數溢出,計算得到第二個 tx3g 的長度值,使得最後分配到的內存大小為0x20, 用來占據剛剛空閑的 avcC 的 內存塊,於是現在的內存布局,就會變成這樣。

| pssh | - | pssh | pssh | tx3g | MPEG4DataSource | pssh |

然後在

技術分享圖片
就會溢出修改了 MPEG4DataSource 的虛表指針。然後在下面的 readAt 函數調用出會 crash.

我測試時得好幾次才能成功一次,估計和內存碎片相關。

Thread 10 received signal SIGSEGV, Segmentation fault.
0xb66b57cc in android::MPEG4Extractor::parseChunk (this=this@entry=0xb74e2138, offset=offset@entry=0xb550ca98, depth=depth@entry=0x2) at frameworks/av/media/libstagefright/MPEG4Extractor.cpp:1905
1905                if ((size_t)(mDataSource->readAt(*offset, buffer + size, chunk_size))
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ registers ]────
$r0   : 0xb74e27b8  →  0x61616169 ("iaaa"?)
$r1   : 0xb74e2bb8  →  0x00000000
$r2   : 0x61616169 ("iaaa"?)
$r3   : 0x00000000
$r4   : 0xb550c590  →  0x00000428
$r5   : 0xfffffbf8
$r6   : 0xb550c580  →  0xb74e5c98  →  0x28040000
$r7   : 0xb550c570  →  0xfffffbf8
$r8   : 0xb74e2138  →  0xb6749f18  →  0xb66b2841  →  <android::MPEG4Extractor::~MPEG4Extractor()+1> ldr r3,  [pc,  #188]    ; (0xb66b2900 <android::MPEG4Extractor::~MPEG4Extractor()+192>)
$r9   : 0x74783367 ("g3xt"?)
$r10  : 0xb550ca98  →  0x01000a98
$r11  : 0xb74e2790  →  0x28040000
$r12  : 0x00000000
$sp   : 0xb550c530  →  0xb74e2bb8  →  0x00000000
$lr   : 0xb66b57bd  →  <android::MPEG4Extractor::parseChunk(long+0> ldr r1,  [r4,  #0]
$pc   : 0xb66b57cc  →  <android::MPEG4Extractor::parseChunk(long+0> ldr r6,  [r2,  #28]
$cpsr : [THUMB fast interrupt overflow carry ZERO negative]
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
$r0   : 0x00000000
$r1   : 0xb74e2bb8  →  0x00000000
$r2   : 0x61616169 ("iaaa"?)
$r3   : 0x00000000
$r4   : 0xb550c590  →  0x00000428
$r5   : 0xfffffbf8
$r6   : 0xb550c580  →  0xb74e5c98  →  0x28040000
$r7   : 0xb550c570  →  0xfffffbf8
$r8   : 0xb74e2138  →  0xb6749f18  →  0xb66b2841  →  <android::MPEG4Extractor::~MPEG4Extractor()+1> ldr r3,  [pc,  #188]    ; (0xb66b2900 <android::MPEG4Extractor::~MPEG4Extractor()+192>)
$r9   : 0x74783367 ("g3xt"?)
$r10  : 0xb550ca98  →  0x01000a98
$r11  : 0xb74e2790  →  0x28040000
$r12  : 0x00000000
$sp   : 0xb550c530  →  0xb74e2bb8  →  0x00000000
$lr   : 0xb66b57bd  →  <android::MPEG4Extractor::parseChunk(long+0> ldr r1,  [r4,  #0]
$pc   : 0xb66b57cc  →  <android::MPEG4Extractor::parseChunk(long+0> ldr r6,  [r2,  #28]
$cpsr : [THUMB fast interrupt overflow carry ZERO negative]

可以看到斷在了<android::MPEG4Extractor::parseChunk(long+0> ldr r6, [r2, #28],去 ida 裏面找到對應的位置。

技術分享圖片
r2存放的就是虛表指針,可以確定成功修改了 虛函數表指針。

技術分享圖片

偏移也符合預期。

堆噴射

上面我們已經成功修改了MPEG4DataSource 的虛表指針,並在虛函數調用時觸發了 crash .

我們現在能夠修改對象的 虛表指針,並且能夠觸發虛函數調用。我們需要在一個可預測的內存地址精準的布置我們的數據,然後把虛表指針修改到這裏,在 exploit 中使用了

spray_size = 0x100000
spray_count = 0x10

sample_table(heap_spray(spray_size) * spray_count)

來進行堆噴射

heap_spray 函數 就是使用 pssh 來噴射的內存。每次分配 0x100 頁,共分配了 0x10 次。 exploit 作者在 博客中寫道,這樣就可以在可預測的內存地址中定位到特定數據。在這裏就是 用於 stack_pivotgadget.

關於堆噴射

在看雪上大佬們進行了討論

https://bbs.pediy.com/thread-222893-1.htm

最後

這個 exploit 寫的確實強悍,提示我在進行漏洞利用時,要關註各種可能分配內存的地方,靈活的使用代碼中的內存分配,來布局內存。 同時研究一個漏洞要把相關知識給補齊。對於這個漏洞就是 MPEG4 的文件格式和 相關的處理代碼了。

一些tips:

  • 使用 gef + gdb-multiarch 來調試 , pwndbg 我用著非常卡, gef 就不會
  • 調試過程盡量使用腳本減少重復工作量。

使用的一些腳本。

使用 gdbserver attach mediaserver 並轉發端口的腳本

adb root
adb forward tcp:1234 tcp:1234
a=`adb shell "ps | grep mediaserver" |  awk ‘{printf $2}‘`
echo $a
adb shell "gdbserver --attach :1234 $a"

gdb 的調試腳本

set arch armv5
gef-remote 127.0.0.1:1234
set solib-search-path debug_so/
directory android-5.1.0_r3/
gef config context.layout "regs -source"
set logging file log.txt
set logging on
break frameworks/av/media/libstagefright/MPEG4Extractor.cpp:1897 
break frameworks/av/media/libstagefright/MPEG4Extractor.cpp:1630
break frameworks/av/media/libstagefright/MPEG4Extractor.cpp:1647
break frameworks/av/media/libstagefright/MPEG4Extractor.cpp:884
commands 1
p chunk_size
p buffer
c
end

commands 2
p buffer

end

commands 3
p buffer
c
end

commands 4
hexdump dword  mDataSource 0x4
c
end

參考:

https://census-labs.com/media/shadow-infiltrate-2017.pdf

https://googleprojectzero.blogspot.hk/

http://blog.csdn.net/zhuweigangzwg/article/details/17222951

CVE-2015-3864漏洞利用分析(exploit_from_google)