zoukankan      html  css  js  c++  java
  • 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的内存写漏洞。这个内存写漏洞的产生需要满足以下条件:

    • 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


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

     

  • 相关阅读:
    python3 基础数据类型之列表及其操作方法
    python3 逻辑运算符
    python3 内置函数
    python3 装饰器
    python3 变量作用域
    python3 参数以及函数的传参
    python3 函数基础
    洛谷P3379倍增LCA
    洛谷P3375KMP字符串匹配
    洛谷P2613有理数取余
  • 原文地址:https://www.cnblogs.com/alisecurity/p/5358105.html
Copyright © 2011-2022 走看看