近日,安全专家发现两个安卓漏洞,若两者结合使用,就可以在mediaserver进程中执行任意代码,且没有任何权限要求。
漏洞影响
第一个漏洞(CVE-2014-7921)影响Android 4.0.3以上版本,第二个漏洞(CVE-2014-7920)影响Android 2.2以上版本,且二者均不会局限于特定厂商,而是影响所有安卓设备。前一个漏洞只需要让ASLR保护,而该机制应用于Android 4.1以上版本,因此这些漏洞可以在Android 2.2以上版本的设备中的mediaserver进程中执行任意代码。
尽管这两个漏洞在2014年10月中旬曝光,却是在Android 5.1版本中最终修复,这就意味着仍然有很多安卓设备受到影响。
补丁获取链接: googlesource
漏洞利用
Mediaserver进程负责所有与媒体相关的任务,为了服务与不同的媒体相关类请求,该进程公开使用大量功能,主要分为以下四类:
1、media.audio_policy:处理音频相关信息,例如不同音频流的数据卷; 2、media.audio_flinger:媒体相关任务的主要配置端,例如录制音频,将手机设为静音状态; 3、media.camera:允许与设备的相机进行交互; 4、media.player:允许多种不同媒体格式的回放。例如使用stagefright库。
图一 Mediaserver的功能组成
最近,很多安全人员都比较关注media.player服务,尤其是该服务所使用的媒体解析库。但是本文所分析的漏洞却在另一个服务——media.audio_policy服务中。
通常,当用户注册安卓服务时,这些服务都是使用Java语言实现的。这就意味着,内存损坏漏洞的发现变得更加困难,因为这些漏洞需要特殊的环境(例如,使用本地化JNI调用,删除本地库中的功能等)。
Mediaserver进程是个例外,其中的所有服务都是使用C++语言实现的,在该进程中发现内存损坏漏洞就容易得多。
实际上,以安全的方式实现服务是一个相当艰巨的任务。以内核漏洞为例,为了防止对用户提供的数据的意外访问,内核会使用一种编码规范,将用户提供的指针标记为“tainted”,但是在用户空间服务的交互中并不支持该功能,因此,实现进程时即使是已处理过的数据也是不能相信的。
漏洞分析过程
首先,应该需要media.audio_policy服务中的一个内存损坏漏洞,其次,要了解可靠利用该漏洞的方法,ASLR机制的使用让该过程变得艰难。
下面,开始从mediaserver进程中的主函数分析media.audio_policy中的两大服务, AudioPolicyService和AudioFlinger都是Native层的系统服务,通过图二中的代码启动。
图二 mediaserver进程中的主函数
实际上,AudioPolicyService中的大部分功能和AudioFlinger中的部分功能都继承于同一个类——AudioPolicyManagerBase。那么接下来的重点便是AudioPolicyManagerBase类的分析。
当用户想要在特定的输出设备上启动输出流(以扬声器为例)时,则需要调用media.audio_policy服务中的startOutput函数,该函数包含三个参数:
1、输出描述符:须为一个具体设备(例如扬声器) 2、输出流的预定义类型 3、已创建的会话ID
startOutput函数函数通过抓取AudioOutputDescriptor对象与给定的输出设备比较,来验证“output”参数。这就意味着,该参数必须有效。接下来验证以下“stream”参数的安全性。
图三 changeRefCount()函数对“stream”参数的处理
如图三可以看出,changeRefCount()函数函数将“stream”参数作为AudioOutputDescriptor对象中的数组索引,可同时读取和写入到该地址,却没有过滤输出流的值!
实际上,有效的stream_type值是一个枚举类型,增加适当验证的方法也十分简单,只需要判断给定的值是否在最大值和最小值之间即可。
图四 有效的stream_type值
攻击者选择使用未重复的输出描述符即可跳过第一个if分支,那么怎么才能执行第二个分支呢?由于“delta”参数默认为1,也就是说“(int)mRefCount[stream]”值需要大于或等于0x7FFFFFFF(INT_MAX)。并且,实际的值会被记录到系统日志中,进而造成信息泄露。但是这个信息泄露漏洞存在两个明显的缺点:
1、攻击者要读取泄露的数据需要拥有READ_LOGS权限 2、读取的值是损坏的
但是任何已定义的值都不会丢失,攻击者可利用该漏洞创建更加强壮的初始值。假设第二个if分支也没有执行,那么久直接跳到了函数结尾。目标值将递增,这是一个受限的写入原语,且最后的日志语句并不包含在可释放的build中。结合来看,攻击者拥有了写入原语,可以使“mRefCount[stream]”值在不大于0x7FFFFFFF的情况下逐一递增。
接下来就是获得读取原语。由于之前的写入原语是与AudioOutputDescriptor对象相关的,那么如果读取原语也是这样,那么后续操作会方便许多。
重新检查AudioPolicyManagerBase中的方法,发现AudioPolicyManagerBase::isStreamActive方法可以做为目标。该方法允许用户查询输出流类型,以便检查该类型是否在用户提供的时间框架中可用:
图五 isStreamActive方法
同样的,该方法也没有验证“stream”参数,检查内部的AudioOutputDescriptor::isStreamActive方法是否代执行了参数验证,发现同样没有。
图六 isStreamActive方法
攻击者可以通过使用“stream”参数作为索引访问AudioOutputDescriptor对象中的mRefCount成员。如图六,有两种方式使isStreamActive方法返回true:
1、mRefCount[stream] != 0 2、若给定的时间不同于当前系统时间,且“mStopTime[stream]”参数的值小于用户提供的值减去“inPastMs”参数值。
至此,攻击者就获取了读取原语:“mRefCount[stream]”值为零时,isStreamActive函数会返回true,攻击者可通过控制“stream”参数,扫描AudioOutputDescriptor对象对应的内存,来确定是否存在零值。
现在,攻击者已经拥有了两项权限:
1、增加任意小于0x7FFFFFFF的值 2、检查内存位置是否包含零值
接下来就是找到利用向量:包含函数指针且可传递可控参数的对象。通过搜索发现audio_hw_device结构体,其中包含了很多用于音频硬件设备的功能实现的指针,另外,只需要调用media.audio_policy 服务和media.audio_flinger API的不同部分就可以触发这些函数指针。最重要的是它的结构,它包含一个非零的固定长度的数据头,紧接着就是大量的保留值,这些值被初始化为零,最后是大量的函数指针。
将以上内容结合起来,攻击者就可以调用mediaserver进程中的系统函数。利用audio_hw_device对象的其绝对内存位置,使用写入原语就可以将系统函数的调用路径和系统函数的路径写入audio_hw_device对象的保留区域。然后再使用写入原语更改get_input_buffer_size函数指针——将其指向打开函数地址的小工具(libstagefright库中)和已写入保留区域的参数,使用该参数执行函数。这样,攻击者就可以在mediaserver进程中执行任意代码了。