關於libStagefright系列漏洞分析
0x00 前言
文章對應著CVE-2015-{1538,1539,3824,3826,3827,3828,3829}7個CVE,具體對映關係目前不明。此次安全漏洞號稱影響“95%”安卓手機的安全。透過跟進此次漏洞的攻擊面來看,這種說法毫不誇張,外界報導的關於一個彩信就直接打下機器的說法也是可信的。但這也僅僅是眾多攻擊面中的一條而已。
0x01 攻擊面分析
libStagefright預設會被mediaserver使用,也就是說,如果惡意的影片檔案有機會被mediaserver處理到,該漏洞就有機會觸發,舉例:
如檔案管理app,如果影片被存放在sdcard,那麼開啟檔案管理app,下拉選單到露出影片,就會觸發縮圖解析,漏洞觸發。
相簿app,點選本地圖片會出現縮圖,如果影片在sdcard,或者download目錄,這時候也會觸發。
微信同樣受到影響。透過微信傳送的影片,點選也會導致media server崩潰。此外,收到的影片即使使用者不點選,後面在微信中傳送圖片時,也會造成前面gallery,檔案管理器同樣的效果,也會觸發縮圖過程並溢位。
在最新版的Chrome43版中開啟一個video連結(mp4),無需點選自動觸發。
開機同樣是一個觸發點,mediaprovider會掃描sd卡里的所有檔案,並且嘗試去解析,恩開機自啟動
media framework的架構如下:基本上採用了android的media框架來開發的程式都會受到影響。
看到這裡,想說的是,外界所謂的那些,關閉彩信功能保平安也就尋求個心理安慰吧。從根源上看大部分(有一個例外)都和整數計算的上溢/下溢相關,因為這個問題,間接導致了後續的記憶體破壞等相關的安全問題。
1.1. 程式碼分析
1.1.1. No1 heap 讀越界
#!c
1. status_t MPEG4Extractor::parseChunk(off64_t *offset, int depth) {
2. uint32_t hdr[2];
3. uint64_t chunk_size = ntohl(hdr[0]);
4. uint32_t chunk_type = ntohl(hdr[1]);
5.
6. switch(chunk_type) {
只有下面幾種chunk_type才會觸發分支parse3GPPMetaData:
#!c
1. case FOURCC('t', 'i', 't', 'l'):
2. case FOURCC('p', 'e', 'r', 'f'):
3. case FOURCC('a', 'u', 't', 'h'):
4. case FOURCC('g', 'n', 'r', 'e'):
5. case FOURCC('a', 'l', 'b', 'm'):
6. case FOURCC('y', 'r', 'r', 'c'):
7. {
8. *offset += chunk_size;
9.
10. status_t err = <span style="color: #ff0000;">parse3GPPMetaData</span>(data_offset, chunk_data_size, depth);
11.
12. if (err != OK) {
13. return err;
14. }
15.
16. break;
17. }
以上parse3GPPMetaData會觸發兩個3gp格式的漏洞。 第一個setCString heap讀越界,首先從檔案中offset讀size資料到buffer。
#!c
status_t MPEG4Extractor::parse3GPPMetaData(off64_t offset, size_t size, int depth) {
1. if (size < 4) {
2. return ERROR_MALFORMED;
3. }
4.
5. uint8_t *buffer = new (std::nothrow) uint8_t[size];
6. if (buffer == NULL) {
7. return ERROR_MALFORMED;
8. }
9. <span style="color: #ff0000;">if (mDataSource->readAt(</span>
10. <span style="color: #ff0000;">offset, buffer, size) != (ssize_t)size) {</span>
11. delete[] buffer;
12. buffer = NULL;
13.
14. return ERROR_IO;
15. }
然後,這個類似strcpy,所以就是 mFileMetaData->setCString(metadataKey, (const char *)buffer + 6);
https://android.googlesource.com/platform/frameworks/av/+/android-5.1.1_r8/media/libstagefright/MetaData.cpp
#!c
1. bool MetaData::setCString(uint32_t key, const char *value) {
2. return setData(key, TYPE_C_STRING, value, <span style="color: #ff0000;">strlen</span>(value) + 1);
3. }
1. bool MetaData::setData(
2. uint32_t key, uint32_t type, const void *data, size_t size) {
3. bool overwrote_existing = true;
4.
5. ssize_t i = mItems.indexOfKey(key);
6. if (i < 0) {
7. typed_data item;
8. i = mItems.add(key, item);
9.
10. overwrote_existing = false;
11. }
12.
13. typed_data &item = mItems.editValueAt(i);
14.
15. item.setData(type, data, size);
16.
17. return overwrote_existing;
18. }
注意到size是動態的,所以這裡一般不會溢位,但會出現讀越界。
#!c
1. void MetaData::typed_data::setData(
2. uint32_t type, const void *data, size_t size) {
3. clear();
4.
5. mType = type;
6. <span style="color: #ff0000;">allocateStorage</span>(size);
7. memcpy(storage(), data, size);
8. }
讀到的內容被儲存到一個metadata中,或許可以洩漏(例如title, artist這些資訊)
1.1.2. No2 heap 越界寫
第二個是under flow,如果size<6,那麼len16會很大,會對buffer(還是剛才的heap)後面很大一片記憶體作bswap_16操作,寫的內容不太可控
#!c
1. if (metadataKey > 0) {
2. bool isUTF8 = true; // Common case
3. char16_t *framedata = NULL;
4. int len16 = 0; // Number of UTF-16 characters
5.
6. // smallest possible valid UTF-16 string w BOM: 0xfe 0xff 0x00 0x00
7. if (size - 6 >= 4) {
8. len16 = ((size - 6) / 2) - 1; // don't include 0x0000 terminator
9. framedata = (char16_t *)(buffer + 6);
10. if (0xfffe == *framedata) {
11. // endianness marker (BOM) doesn't match host endianness
12. for (int i = 0; i < len16; i++) {
13. framedata[i] = <span style="color: #ff0000;">bswap_16</span>(framedata[i]);
14. }
15. // BOM is now swapped to 0xfeff, we will execute next block too
}
根據前面的計算,這裡的size就是chunk_data_size
,代表這個chunk中除去header外的data size
。計算方式如下:
off64_t data_offset = *offset + 8;
在解析header過程中自然標記data開始的offset
#!c
off64_t chunk_data_size = *offset + chunk_size – data_offset;
所以chunk_size<14
且>8
即可。Chunk_size
來自檔案tag前面4位元組。
1.1.3. No3 heap overflow
然後是mpeg tx3g tag
的,chunk_size
是uint
,與size
之和溢位,導致實際分配比size
小的記憶體。後面的memcpy heap overflow
,寫入的data應該是可控的。
將trak修改為tx3g,然後前面的改為ffff
1.1.4. No4 heap 越界讀
出現在covr這個tag處理時,chunk_data_size
小於kSkipBytesOfDataBox
時,setData
會讀過buffer
的邊界。由於setData
會分配記憶體,但多半分配失敗,所以可能也會導致向地址為0的記憶體寫入。
1.1.5. No5 heap 越界寫
當chunk_data_size=SIZE_MAX
時,+1導致分配0長度的記憶體,後面的readAt
會邊讀檔案邊寫入buffer,在讀到檔案結束之前已經導致了heap write
越界。由於覆蓋資料來自檔案,所以內容與長度都是可控的。
1.1.6. No6 Integer overflow
處理stsc
這種tag
時,呼叫了setSampleToChunkParams
方法
#!c
1. case FOURCC('s', 't', 's', 'c'):
2. {
3. status_t err =
4. mLastTrack->sampleTable->setSampleToChunkParams(
5. data_offset, chunk_data_size);
6.
7. *offset += chunk_size;
8.
9. if (err != OK) {
10. return err;
11. }
12.
13. break;
14. }
這個方法內有integer overflow
,主要是迴圈過程中,在計算類似mSampleToChunkEntries[i].startChunk
的時候,內部實際上是按照i*sizeof(SampleToChunkEntry)+ offset(startChunk)
來計算的,這裡可能overflow
,但從這裡看不一定造成記憶體破壞,可能會干擾執行邏輯。
https://android.googlesource.com/platform/frameworks/av/+/android-5.1.1_r8/media/libstagefright/SampleTable.cpp
#!c
1. mSampleToChunkEntries =
2. new SampleToChunkEntry[mNumSampleToChunkOffsets];
3.
4. for (uint32_t i = 0; i < mNumSampleToChunkOffsets; ++i) {
5. uint8_t buffer[12];
6. if (mDataSource->readAt(
7. mSampleToChunkOffset + 8 + i * 12, buffer, sizeof(buffer))
8. != (ssize_t)sizeof(buffer)) {
9. return ERROR_IO;
10. }
11.
12. CHECK(U32_AT(buffer) >= 1); // chunk index is 1 based in the spec.
13.
14. // We want the chunk index to be 0-based.
15. mSampleToChunkEntries[i].startChunk = U32_AT(buffer) - 1;
16. mSampleToChunkEntries[i].samplesPerChunk = U32_AT(&buffer[4]);
17. mSampleToChunkEntries[i].chunkDesc = U32_AT(&buffer[8]);
18. }
所以補丁增加了校驗
#!c
+ if (SIZE_MAX / sizeof(SampleToChunkEntry) <= mNumSampleToChunkOffsets)
+ return ERROR_OUT_OF_RANGE;
1.1.7. No7 parseESDescriptor Integer overflow
這裡的主要問題是隻在開始檢查了size>=3,然後就-2,–,後面又繼續幾次-2,-length都沒法保證不溢位。
#!c
1. status_t ESDS::parseESDescriptor(size_t offset, size_t size) {
2. if (size < 3) {
3. return ERROR_MALFORMED;
4. }
5.
6. offset += 2; // skip ES_ID
7. size -= 2;
8.
9. unsigned streamDependenceFlag = mData[offset] & 0x80;
10. unsigned URL_Flag = mData[offset] & 0x40;
11. unsigned OCRstreamFlag = mData[offset] & 0x20;
12.
13. ++offset;
14. --size;
15.
16. if (streamDependenceFlag) {
17. offset += 2;
18. <span style="color: #ff0000;">size -= 2;</span>
19. }
20.
21. if (URL_Flag) {
22. if (offset >= size) {
23. return ERROR_MALFORMED;
24. }
25. unsigned URLlength = mData[offset];
26. offset += URLlength + 1;
27. <span style="color: #ff0000;">size -= URLlength + 1;</span>
28. }
29.
30. if (OCRstreamFlag) {
31. offset += 2;
32.<span style="color: #ff0000;"> size -= 2;</span>
33.
34. if ((offset >= size || mData[offset] != kTag_DecoderConfigDescriptor)
35. && offset - 2 < size
36. && mData[offset - 2] == kTag_DecoderConfigDescriptor) {
37. // Content found "in the wild" had OCRstreamFlag set but was
38. // missing OCR_ES_Id, the decoder config descriptor immediately
39. // followed instead.
40. offset -= 2;
41. size += 2;
42.
43. ALOGW("Found malformed 'esds' atom, ignoring missing OCR_ES_Id.");
44. }
45. }
46.
47. if (offset >= size) {
48. return ERROR_MALFORMED;
49. }
50.
51. uint8_t tag;
52. size_t sub_offset, sub_size;
53. status_t err = <span style="color: #ff0000;">skipDescriptorHeader</span>(
54. offset, size, &tag, &sub_offset, &sub_size);
雖然沒直接看到溢位的size造成影響,但可能會造成開發者未預料到的邏輯。
#!c
1. do {
2. if (size == 0) {
3. return ERROR_MALFORMED;
4. }
5.
6. uint8_t x = mData[offset++];
7. --size;
8.
9. *data_size = (*data_size << 7) | (x & 0x7f);
10. more = (x & 0x80) != 0;
11. }
12. while (more);
1.1.8. No8 SampleTable Integer overflow
https://android.googlesource.com/platform/frameworks/av/+/android-5.1.1_r8/media/libstagefright/SampleTable.cpp
32位uint相乘,然後將結果轉化為64位uint
#!c
1. uint32_t mTimeToSampleCount;
2. mTimeToSampleCount = U32_AT(&header[4][4]);
3. uint64\_t allocSize = mTimeToSampleCount * 2 * sizeof(uint32\_t);
4. if (allocSize > SIZE_MAX) {
5. return ERROR\_OUT\_OF_RANGE;
6. }
7.
這裡存在溢位問題,雖然未看到直接的影響,但可能造成後面的檢查誤判。修復方法如下:
#!c
- uint64_t allocSize = mTimeToSampleCount * 2 * sizeof(uint32_t);
+ uint64_t allocSize = mTimeToSampleCount * 2 * (uint64_t)sizeof(uint32_t);
1.2. 總結
前面1-8個漏洞有相似之處。
No1 : 一段資料被計算strlen
,然後分配記憶體並strcpy
,但這段資料並非一定以’\0’
結束,所以導致讀越界
No2-5:都是tag
前的4位元組size沒有校驗,可以任意取值,導致一系列的size
計算問題。如下圖所示,所有tag4
位元組前面都有4位元組的size
:
No6-8:都是integer overflow
,但沒有看到直接記憶體破壞的錯誤。可能會造成資料異常等。
1.3. POC
1到5這5個漏洞的觸發路徑非常明瞭。都是在parseChunk遇到某種特殊tag時,分支處理邏輯出現問題。所以構造poc只需要修改對應的tag即可。
特別是2-4,都是tag前的4位元組size出現問題。只需要調整對應的size。
以下的POC是針對no3,將其中一個trak tag
修改為tx3g
,然後將前面的size修改為4個FF
#!c
07-28 20:16:10.888: I/DEBUG(247): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
07-28 20:16:10.888: I/DEBUG(247): Build fingerprint: 'Xiaomi/cancro/cancro:4.4.4/KTU84P/4.8.22:user/release-keys'
07-28 20:16:10.888: I/DEBUG(247): Revision: '0'
<span style="color: #ff0000;">07-28 20:16:10.888: I/DEBUG(247): pid: 10928, tid: 10945, name: Binder_4 >>> /system/bin/mediaserver <<<
07-28 20:16:10.888: I/DEBUG(247): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 00000004</span>
07-28 20:16:10.978: I/DEBUG(247): r0 00000000 r1 63707274 r2 b187a6e8 r3 00000000
07-28 20:16:10.978: I/DEBUG(247): AM write failure (32 / Broken pipe)
07-28 20:16:10.978: I/DEBUG(247): r4 b187a6f8 r5 00000000 r6 b8100ed0 r7 b187aa28
07-28 20:16:10.978: I/DEBUG(247): r8 74783367 r9 b66dc904 sl 000000a9 fp 00000000
07-28 20:16:10.978: I/DEBUG(247): ip b66267b7 sp b187a690 lr b6df08df pc b664346e cpsr 60010030
07-28 20:16:10.978: I/DEBUG(247): d0 0000000000000000 d1 0000000000000000
07-28 20:16:10.978: I/DEBUG(247): d2 0000000000000000 d3 0000000000000000
07-28 20:16:10.978: I/DEBUG(247): d4 3fd1cb8765719d59 d5 bebbbb3f58eabe9c
07-28 20:16:10.978: I/DEBUG(247): d6 3e66376972bea4d0 d7 3ecccccd3ecccccd
07-28 20:16:10.978: I/DEBUG(247): d8 0000000000000000 d9 0000000000000000
07-28 20:16:10.978: I/DEBUG(247): d10 0000000000000000 d11 0000000000000000
07-28 20:16:10.978: I/DEBUG(247): d12 0000000000000000 d13 0000000000000000
07-28 20:16:10.978: I/DEBUG(247): d14 0000000000000000 d15 0000000000000000
07-28 20:16:10.978: I/DEBUG(247): d16 3930373039303032 d17 2e37343932373154
07-28 20:16:10.978: I/DEBUG(247): d18 006900640065006d d19 004d0049002e0061
07-28 20:16:10.978: I/DEBUG(247): d20 0061006900640065 d21 00790061006c0050
07-28 20:16:10.978: I/DEBUG(247): d22 006c004300720065 d23 0074006e00650069
07-28 20:16:10.978: I/DEBUG(247): d24 3f77ff86776369e9 d25 bf77ff86919d591e
07-28 20:16:10.978: I/DEBUG(247): d26 3fe0000000000000 d27 4000000000000000
07-28 20:16:10.978: I/DEBUG(247): d28 3ffe542fa9d0152a d29 bfbcb8765719d592
07-28 20:16:10.978: I/DEBUG(247): d30 3ff0000000000000 d31 3fd1cb8765719d59
07-28 20:16:10.978: I/DEBUG(247): scr 20000010
07-28 20:16:10.978: I/DEBUG(247): backtrace:
07-28 20:16:10.978: I/DEBUG(247): <span style="color: #ff0000;">#00 pc 0006846e /system/lib/libstagefright.so
(android::MPEG4Extractor::parseChunk(long long*, int)+4345)</span>
07-28 20:16:10.978: I/DEBUG(247): #01 pc 000675fb /system/lib/libstagefright.so (android::MPEG4Extractor::parseChunk(long long*, int)+646)
07-28 20:16:10.978: I/DEBUG(247): #02 pc 00068a8b /system/lib/libstagefright.so (android::MPEG4Extractor::readMetaData()+46)
07-28 20:16:10.978: I/DEBUG(247): #03 pc 00068d31 /system/lib/libstagefright.so (android::MPEG4Extractor::countTracks()+4)
07-28 20:16:10.978: I/DEBUG(247): #04 pc 00092077 /system/lib/libstagefright.so
(android::ExtendedUtils::MediaExtractor_CreateIfNeeded(android::sp<android::MediaExtractor>, android::sp<android::DataSource> const&, char const*)+206)
07-28 20:16:10.978: I/DEBUG(247): #05 pc 00075a43 /system/lib/libstagefright.so
(android::MediaExtractor::Create(android::sp<android::DataSource> const&, char const*)+566)
07-28 20:16:10.978: I/DEBUG(247): #06 pc 0005a00b /system/lib/libstagefright.so
(android::AwesomePlayer::setDataSource_l(android::sp<android::DataSource> const&)+10)
07-28 20:16:10.978: I/DEBUG(247): #07 pc 0005b519 /system/lib/libstagefright.so (android::AwesomePlayer::setDataSource(int, long long, long long)+136)
07-28 20:16:10.978: I/DEBUG(247): #08 pc 00034319 /system/lib/libmediaplayerservice.so (android::MediaPlayerService::Client::setDataSource(int, long long, long long)+196)
07-28 20:16:10.978: I/DEBUG(247): <span style="color: #ff0000;">#09 pc 00059b2d /system/lib/libmedia.so (android::BnMediaPlayer::onTransact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+332)</span>
07-28 20:16:10.978: I/DEBUG(247):<span style="color: #ff0000;"> #10 pc 00019225 /system/lib/libbinder.so (android::BBinder::transact(unsigned int, android::Parcel const&, android::Parcel*, unsigned int)+60)</span>
07-28 20:16:10.978: I/DEBUG(247): #11 pc 0001d799 /system/lib/libbinder.so (android::IPCThreadState::executeCommand(int)+508)
07-28 20:16:10.978: I/DEBUG(247): #12 pc 0001db17 /system/lib/libbinder.so (android::IPCThreadState::getAndExecuteCommand()+38)
07-28 20:16:10.978: I/DEBUG(247): #13 pc 0001db8d /system/lib/libbinder.so (android::IPCThreadState::joinThreadPool(bool)+48)
07-28 20:16:10.978: I/DEBUG(247): #14 pc 000219f5 /system/lib/libbinder.so
07-28 20:16:10.978: I/DEBUG(247): #15 pc 0000ea5d /system/lib/libutils.so (android::Thread::_threadLoop(void*)+216)
07-28 20:16:10.978: I/DEBUG(247): #16 pc 0000e58f /system/lib/libutils.so
07-28 20:16:10.978: I/DEBUG(247): #17 pc 0000d248 /system/lib/libc.so (__thread_entry+72)
07-28 20:16:10.978: I/DEBUG(247): #18 pc 0000d3e0 /system/lib/libc.so (pthread_create+240)
從trace
看出,這裡是透過binder
來呼叫media server
提供的介面,進而對影片處理解析過程崩潰。所以溢位在media server
程式。
1.4. 防護
由於media
是安卓中非常核心的一個服務(雖然許可權不高),大量的功能都涉及到這個服務。如果僅僅stop media
來停止這個服務,手機基本無法使用,例如無法顯示出桌面。
service media /system/bin/mediaserver class main user media group audio camera inet net_bt net_bt_admin net_bw_acct drmrpc mediadrm qcom_diag ioprio rt 4
在簡訊app中,透過設定(可在小米,華為等手機中找到這個選項)可以關閉彩信自動下載,降低風險。
但這樣無法防止例如sdcard根目錄, 下載目錄, bluetooth這些目錄被透過各種渠道傳送過來的惡意影片(瀏覽器自動下載,usb複製,bluetooth,微信等),當使用者一旦開啟檔案瀏覽或相簿app,甚至在瀏覽器中直接訪問影片也會被攻擊。
所以大家開心的等補丁吧!
相關文章
- 關於OpenSSL“心臟出血”漏洞的分析2020-08-19
- 網站漏洞修復服務商關於越權漏洞分析2022-07-15網站
- 關於Ghostscript SAFER沙箱繞過漏洞的分析2018-08-31
- Discuz! X系列遠端程式碼執行漏洞分析2020-08-19
- 關於PHPMailer漏洞情況的通報2017-09-04PHPAI
- 如何掃描網站漏洞 針對於海洋CMS的漏洞檢查分析2019-08-24網站
- mysql 關於exists 和in分析2018-07-15MySql
- java RMI相關反序列化漏洞整合分析2020-08-19Java
- Web Components 系列(五)—— 關於 Templates2022-02-11Web
- 關於TCP/IP協議漏洞的安全措施2022-07-15TCP協議
- 關於漏洞挖掘理論的讀書筆記2016-09-20筆記
- 關於系統分析設計2008-02-29
- 【漏洞分析】KaoyaSwap 安全事件分析2022-08-28事件
- 【面試系列】番外:關於糯米麵試2019-02-16面試
- 白繕大師關於buffer cache系列2013-03-29
- BlueKeep 漏洞利用分析2019-09-20
- XSS漏洞分析2017-11-27
- iOS 8.1.2 越獄過程詳解及相關漏洞分析2020-08-19iOS
- Element原始碼分析系列9-Switch(開關)2018-08-29原始碼
- 漏洞分析 | Dubbo2.7.7反序列化漏洞繞過分析2020-07-02
- Tomcat系列漏洞復現2024-07-22Tomcat
- 關於類似於awr的效能分析報告2013-11-07
- 關於創業教育的案例分析2020-12-24創業
- 關於session leak的問題分析2015-12-23Session
- 關於mysql連線慢的分析.2008-07-23MySql
- 關於製造業的流程分析2006-02-28
- 原始碼分析系列1:HashMap原始碼分析(基於JDK1.8)2021-09-09原始碼HashMapJDK
- Web Components 系列(二)—— 關於 Custom Elements2022-02-08Web
- Internet Explorer漏洞分析系列(一)——CVE-2012-18762021-02-24
- PfSense命令注入漏洞分析2020-08-19
- SSRF漏洞簡單分析2020-07-16
- JSON劫持漏洞分析2018-05-17JSON
- 從exp入手分析漏洞2016-07-26
- tp5漏洞分析2024-06-30
- 關於 Alpine Docker 映象漏洞 CVE-2019-50212019-05-12Docker
- 美國政府關於JAVA安全漏洞的宣告你怎麼看2013-04-28Java
- 關於redis單執行緒的分析2020-04-16Redis執行緒
- 關於ObservableCollection的更新與不更新分析2023-05-06