Android提權漏洞CVE-2014-7920&CVE-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]披露過程如下:
2016年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”中,介面類為IAudioPolicyService[6][7],其中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()收到請求,然後執行下面的代碼:
繼續調用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()方法實現,該方法調用:
我們來看AudioOutputDescriptor:: changeRefCount()[10]方法的代碼:
當2個if語句都為假時,mRefCount[stream] += delta;語句將被執行。
此時如果索引stream可被控制,那麼mRefCount記憶體的相對位移記憶體將可被修改為加delta。恰巧stream為介面參數之一,也沒校正,在AudioPolicyManagerBase:: startOutput()中傳入的delta為1,也就是說這裡存在一個遞增1的記憶體寫漏洞。這個記憶體寫漏洞的產生需要滿足以下條件:
lisDuplicated()為False:幸運的是預設情況大部分output不是duplicated的。
l(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()方法,該方法代碼為:
如果根據isStreamActive()傳回值判斷mRefCount[stream]是否為0,需要滿足2個條件:
lmRefCount[stream] != 0;
lns2ms(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],介面定義為:
其服務端代碼為:
當用戶端調用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-7920-7921_update,編譯環境: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):
3)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,代碼如下:
其中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為:
然後調用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:
gadget2運行時直接返回dev(R0)的值,即audio_hw_device對象的地址。
3)設定write gadget
修改audio_hw_device. get_input_buffer_size為libcamera_client.so: 0x208f0+1,記為gadget3,利用代碼如下:
我們再來看看AudioFlinger::getInputBufferSize()方法,其中:
看gadget3,寫資料調用介面getInputBufferSize(address, 0, value)(該介面定義為getInputBufferSize(uint32_tsampleRate, audio_format_t format, audio_channel_mask_tchannelMask)),走到get_input_buffer_size(dev, config)時,R0為dev, R1為&config,gadget3執行如下:
至此我們將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()函數參數
看利用代碼:
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:
3)寫入call gadget
調用gadget3修改audio_hw_device. get_input_buffer_size指標的值,使用指向,libstagefright.so: 0x5EF88+1,記為gadget4。
gadget4:
3.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/android-privilege-escalation-to.html
[2]https://github.com/laginimaineb/cve-2014-7920-7921
[3]http://androidxref.com/4.3_r2.1/xref/frameworks/av/media/mediaserver/main_mediaserver.cpp#40
[4]http://androidxref.com/4.3_r2.1/xref/frameworks/av/services/audioflinger/AudioFlinger.cpp
[5]http://androidxref.com/4.3_r2.1/xref/frameworks/av/services/audioflinger/AudioPolicyService.cpp
[6]http://androidxref.com/4.3_r2.1/xref/frameworks/av/include/media/IAudioPolicyService.h
[7]http://androidxref.com/4.3_r2.1/xref/frameworks/av/media/libmedia/IAudioPolicyService.cpp
[8]http://androidxref.com/4.3_r2.1/xref/frameworks/av/media/libmedia/IAudioPolicyService.cpp#384
[9]http://androidxref.com/4.3_r2.1/xref/frameworks/av/services/audioflinger/AudioPolicyService.cpp#234
[10]http://androidxref.com/4.3_r2.1/xref/hardware/libhardware_legacy/audio/AudioPolicyManagerBase.cpp#3083
[11]http://androidxref.com/4.3_r2.1/xref/frameworks/av/media/libmedia/IAudioFlinger.cpp#346
[12]https://github.com/Vinc3nt4H/cve-2014-7920-7921_update