没羽@阿里移动安全,更多安全类技术干货,请访问阿里聚安全博客


这是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的内存写漏洞。这个内存写漏洞的产生需要满足以下条件:

  • 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()方法,该方法代码为:

如果根据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],接口定义为:

其服务端代码为:

当客户端调用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_t channelMask)),走到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


没羽@阿里移动安全,更多安全类技术干货,请访问阿里聚安全博客

Android提权漏洞CVE-2014-7920&CVE-2014-7921分析的更多相关文章

  1. android提权漏洞CVE-2010-EASY修复【转】

    本文转载自: http://blog.csdn.net/lhj0711010212/article/details/9351131 android提权漏洞CVE-2010-EASY修复   linux ...

  2. Android提权漏洞CVE-2014-7920、CVE-2014-7921

  3. Linux Kernel ‘perf’ Utility 本地提权漏洞

    漏洞名称: Linux Kernel ‘perf’ Utility 本地提权漏洞 CNNVD编号: CNNVD-201309-050 发布时间: 2013-09-09 更新时间: 2013-09-09 ...

  4. Linux Kernel ‘kvm_set_memory_region()’函数本地提权漏洞

    漏洞名称: Linux Kernel ‘kvm_set_memory_region()’函数本地提权漏洞 CNNVD编号: CNNVD-201306-343 发布时间: 2013-06-20 更新时间 ...

  5. Intel产品AMT本地及远程提权漏洞(CVE-2017-5689)复现 【转载自freebuf.com】

    零.绪论: 1.鸣谢freebuf的文章,主要是学习这个漏洞,文章地址: Intel产品AMT本地及远程提权漏洞(CVE-2017-5689)复现 2.在shadon上找了多个该漏洞尝试复现失败(评论 ...

  6. 基于RedHat发行的Apache Tomcat本地提权漏洞

    描述 Tomcat最近总想搞一些大新闻,一个月都没到,Tomcat又爆出漏洞.2016年10月11日,网上爆出Tomcat本地提权漏洞,漏洞编号为CVE-2016-5425.此次受到影响的主要是基于R ...

  7. 2019-10-16,sudo提权漏洞(CVE-2019-14287)实现

    sudo是linux系统命令,让普通账号以root身份执行某些命令,比如,安装软件,查看某些配置文件,关机,重启等,如果普通用户需要使用sudo需要修改配置文件,/etc/sudoers,将sudo使 ...

  8. sudo 提权漏洞(CVE-2019-14287)复现 (10.16 第二十二天)

    sudo是Linux系统命令,让普通账号以root身份去执行某些命令,比,安装软件.查看某些配置文件.关机.重启等操作,如果普通账号需要使用sudo需要修改配置文件/etc/sudoers,将sudo ...

  9. sudo 提权漏洞(CVE-2019-14287)复现

    (该文参考网络他人资料,仅为学习,不许用于非法用途) 一.环境 1.sudo版本小于1.8.28的Linux系统 2.sudo 是Linux系统命令,让普通账号以root身份去执行某些命令,比如:安装 ...

随机推荐

  1. H5(三)

    Canvas(画布)   基本内容     简单来说,HTML5提供的新元素<canvas>     Canvas在HTML页面提供画布的功能       在画布中绘制各种图形     C ...

  2. 迭代器模式/iterator模式/对象行为型模式

    意图 又名:游标(Cursor): 提供一种方法顺序访问一个聚合对象中各个元素,而又不暴露该对象的内部表示. 动机 一个聚合对象,提供访问元素的方法,而有不暴露它的内部结构.如list,将对列表的访问 ...

  3. Python-匿名函数

    lambda 函数是一种快速定义单行的最小函数,可以用在任何需要函数的地方   常规版本: def fun(x,y) return x*y lambda版本: r = lambda x,y:x*y p ...

  4. sweetAlert

    SweetAlert2是一款功能强大的纯Js模态消息对话框插件.SweetAlert2用于替代浏览器默认的弹出对话框,它提供各种参数和方法,支持嵌入图片,背景,HTML标签等,并提供5种内置的情景类, ...

  5. 【iOS atomic、nonatomic、assign、copy、retain、weak、strong】的定义和区别详解

    一.atomic与nonatomic 1.相同点 都是为对象添加get和set方法 2.不同点 atomic为get方法加了一把安全锁(及原子锁),使得方法get线程安全,执行效率慢 nonatomi ...

  6. 使用SHFB(Sandcastle Help File Builder)建立MSDN风格的代码文档

    使用SHFB(Sandcastle Help File Builder)建立MSDN风格的代码文档 下载地址:http://sandcastle.codeplex.com/ 下载地址2:http:// ...

  7. mysql5.7绿色版安装与配置

    1,找到zip archive包下载,官方地址如下: http://dev.mysql.com/downloads/mysql/ http://dev.mysql.com/downloads/file ...

  8. 我理想中的父母(The Ideal Parents In My Heart)

    Parents are the first teachers in children's life, and people all know the great importance exactly ...

  9. time的用法

    线程计时器(System.Threading.Timer) System.Windows.Threading.DispatcherTimer tRecorderTimer; if (tRecorder ...

  10. Navicat备份远程Oracle数据库到本地

    公司的数据库是本地的,我只能在公司连,回家就不能跑项目了,一跑就报SQLException,所以希望可以把数据库复制到我的本地来. 因为一直在用Navicat操作数据库,这里就分享一下用Navicat ...