CVE-2015-3864漏洞利用分析(exploit_from_google)
前言
接下來要學習安卓的漏洞利用相關的知識了,網上搜了搜,有大神推薦 stagefright
系列的漏洞。於是開幹,本文分析的是 google
的 exploit
. 本文介紹的漏洞是 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
前四個字節為box
的size
第二個四字節為
box
的type
,box type
有ftyp,moov,trak
等等好多種,moov
是containerbox
,包含mvhd
、trak
等box
還有一些要註意的點。
box
中存儲數據采用大端字節序存儲
當size
box
- 當
size
為1 時,表示這是一個large box
,在type
域後面的8 字節
作為該box
的長度。
下面來看兩個實例。
實例一
size
域為00000014
,所以該box
長度為0x14
字節。type
域為66 74 79 70
所以type
為fytp
- 剩下的一些信息是一些與多媒體播放相關的一些信息。與漏洞利用無關,就不說了。
實例二
size
域為1,表示從該box
開頭偏移8字節開始的8字節為size
字段, 所以該box
的大小為0xFFFFFFFFFFFFFF88
type
為tx3g
現在我們對該文件的格式已經有了一個大概的了解,這對於漏洞利用來說還不夠,接下來我們要去看具體的解析該文件格式的代碼是怎麽實現的。
解析文件的具體代碼位於 MPEG4Extractor.cpp
中的 MPEG4Extractor::parseChunk
函數裏面。
該函數中的 chunk
對應的就是 box
, 函數最開始先解析 type
和 size
.
// 開始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_size
是 uint64_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
中, mPssh
在 MPEG4Extractor::~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
分配 ABuffer
到 buffer
,chunk_data_size
在 box
的 size
域指定,註意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
中,然後會溢出到下一個 region
的 MPEG4DataSource
內存塊。使用 cyclic
可以在程序 crash
時,計算 buffer
和 MPEG4DataSource
之間的距離。
第 13
行,調用了 memory_leak
函數, 該函數通過使用 pssh
來分配任意大小的內存,在這裏分配的是 alloc_size
,即 0x20
. 因為MPEG4DataSource
的大小為 0x20
,就保證內存的分配會在同一個 run
中分配。這些這樣這裏分配了 4
個 0x20
的內存塊,我認為是用來清理之前可能使用內存時,產生的內存碎片,確保後面內存分配按照我們的順序進行分配。此時內存關系
| pssh | - | pssh |
第 17
到 25
行,清理內存後,開始分配 avcC
和 hvcC
, 大小也是 0x20
, 然後在第 25
行又進行了內存碎片清理,原因在於我們在分配 avcC
和 hvcC
時,會使用到 new ABuffer(chunk_data_size)
,這個臨時的緩沖區,這個會在函數返回時被釋放(請看智能指針相關知識)
同時多分配了幾個 pssh
確保可以把 avcC
和 hvcC
包圍在中間。所以現在的內存關系是
| pssh | - | pssh | pssh | avcC | hvcC | pssh |
然後是 第 29
行, 再次分配 hvcC
,不過這次的大小 為 alloc_size * 2
, 觸發 hvcC
的釋放,而且確保不會占用 剛剛釋放的 內存.(jemalloc中 相同大小的內存在同一個run中分配)
| pssh | - | pssh | pssh | avcC | .... | pssh |
接下來構造 stbl
用 MPEG4DataSource
占據剛剛空出來的 內存。
| 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_pivot
的 gadget
.
關於堆噴射
在看雪上大佬們進行了討論
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)