Android privilege escalation to mediaserver from zero permissions (CVE-2014-7920 + CVE-2014-7921)
墙外通道:http://bits-please.blogspot.com/2016/01/android-privilege-escalation-to.html
In this blog post we'll go over two vulnerabilities I discovered which, when combined, enable arbitrary code execution within the "mediaserver" process from any context, requiring no permissions whatsoever.
HOW BAD IS IT?
The first vulnerability (CVE-2014-7921) was present in all Android version from 4.0.3 onwards. The second vulnerability (CVE-2014-7920) was present in all Android versions from 2.2 (!). Also, these vulnerabilities are not vendor specific and were present in all Android devices. Since the first vulnerability is only needed to bypass ASLR, and ASLR is only present (in a meaningful form) from Android 4.1 onwards, this means that these vulnerabilities allow code execution within "mediaserver" on any Android device starting from version 2.2.
Although I reported both vulnerabilities in mid October 2014, they were unfortunately only fixed much later (see "Timeline" for full description, below) - in Android version 5.1! This means that there are many devices out there which are still vulnerable to these issues, so please take care.
You can find the actual patches here. The patches were pushed to AOSP five months after the vulnerabilities were reported.
That said, the Android security team was very pleasant to work with, and with other vulnerabilities I reported later on, were much more responsive and managed to solve the issues within a shorter time-frame.
WHERE ARE WE AT?
Continuing our journey of getting from zero permissions to TrustZone code execution; after recently completing the task of getting to TrustZone from the Linux kernel, and after finding a way to gain code execution within the Linux kernel, we are left with the final step of gaining the privileges needed in order to execute our kernel exploit.
As mentioned in the previous blog post in the series, in order to exploit the kernel vulnerability in the "qseecom" driver, an attacker must only satisfy one of the following conditions:
- Gain execution within one of "mediaserver", "drmserver", "surfaceflinger" or "keystore"
- Run within a process with the "system", "drm" or "keystore" user-ID
- Run within a process with the "drmrpc" group-ID

In this blog post, we'll gain code execution within the "mediaserver" process, thus completing our journey from zero permissions to TrustZone kernel code execution.
DIVING IN
As it's name suggests, the "mediaserver" process is in charge of all media-related tasks. In order to serve different media-related requests, the process exposes a large set of features in the form of four different services:
- "media.audio_policy" - Enables manipulation of different audio related policies, such as the volumes of different audio streams
- "media.audio_flinger" - Main configuration endpoint for media-related tasks, such as recording audio, muting the phone, etc.
- "media.camera" - Allows interaction with the device's cameras.
- "media.player" - Allows the playback of many different media formats (for example, by using the "stagefright" library).

As you've probably seen, lately there's been a lot of focus on the "media.player" service (ala Stagefright) , especially focusing on different media-parsing libraries which are utilised by it. However, in this post we'll cover two vulnerabilities in a different service - the "media.audio_policy" service.
Usually, when registering an Android service, the actual implementation of the service is provided in the Java programming language. This means that finding memory corruption vulnerabilities is more difficult, since those would only present themselves in unique circumstances (using a native "JNI" call from Java code, delegating a feature to a native library, etc.).
However, in the case of the "mediaserver" process, all of the services housed within the process are implemented in the C++ programming language, making the prospect of finding memory corruptions much more viable.
Actually, implementing a service is quite a hard task to fulfil in a secure manner - recall when we previously discussed kernel vulnerabilities? Well, in order to prevent accidental access to user-provided data, the kernel uses a coding convention in which user-provided pointers are marked as "tainted". However, for interaction between userspace services, there is no such feature. This means that implementers of a service must always pay attention to the origin of the processed data, and can't trust it at all.
LET'S GET DOWN TO BUSINESS
Here's the game plan - first of all, we'll need to look for a memory corruption vulnerability in the audio policy service. Then, we'll need to find a way to reliably exploit this vulnerability. This is usually made difficult by the presence of ASLR.
For those of you who haven't encountered ASLR (Address Space Layout Randomization) yet, you should definitely check this link for Android-specific information (and this link to see the problems still present in Android's ASLR implementation).
Now, without any further ado, let's take a look at the functionality exposed by the audio policy service. Unsurprisingly, we'll start at the "main" function of the "mediaserver" process:

Looks straight-forward enough. However, looking deeper reveals that while both "AudioPolicyService" and "AudioFlinger" register themselves as the handlers for commands directed at the "media.audio_policy" and "media.audio_flinger" services respectively, they actually acts as a façades for the real concrete implementation, which is provided after going through several layers of abstraction.
The end result is that the actual implementation for most functionality provided by "AudioPolicyService" and some of the functionality provided by "AudioFlinger" are in fact handled by a single class - "AudioPolicyManagerBase". As a result, this is the class we're going to be focusing on from now on.
LIMITED WRITE PRIMITIVE
Whenever a user would like to start an output stream on a particular output device (such as the front or back speakers), he may do so by calling the "startOutput" function, provided by the audio policy service.

This function receives three arguments:
- The output descriptor - this must be a device (such as the front or back speakers).
- The type of stream for which the output should be opened (should be one of the predefined stream types).
- The session ID - this should be a number corresponding to a previously opened session.
Initially, the function verifies the "output" parameter by fetching the AudioOutputDescriptor object corresponding to the given output device. This means that this argument must, in fact, be valid.
But what about the other two arguments? Well, peering a little further reveals the following call:

Doesn't seem too shady, but let's just make sure the stream argument is safely handled:

Oh.
So - as we can see above, the function uses the "stream" argument as an index into an array (of 32-bit values) within the AudioOutputDescriptor object - and both reads and writes to that address, without ever sanitizing the stream number. We're off to a good start already!
In reality, there are only a handful of valid stream_type values (it is in fact an enumerated type), so adding appropriate validation is an easy as checking that the given argument is within the enumerations minimal and maximal values:

Regardless - there are still some constraints we need to figure out. First and foremost, in order to avoid unnecessary side-effects, we would like to choose an output descriptor which is not "duplicated" (so as not to execute the first block). Luckily, this is easy - most output descriptors are in fact not duplicated by default.
Moving on, when would the second block in this function execute? Well, since the "delta" argument is always 1, this means we'll enter the block iff (int)mRefCount[stream] + 1 is negative. Meaning, if the value pointed to is larger than or equal to 0x7FFFFFFF (since we're dealing with a 32-bit system).
If that were to happen, the actual value would be logged to the system log (an info leak!), and would then be zeroed out before returning from the function. Although this is a nice info-leak, it has two obvious downsides (and another one which I won't cover in this post):
- Reading the leaked value requires the READ_LOGS permission (and we originally stated we would like to start with zero permissions)
- The value being read is corrupted - this could be troublesome for quite a few exploitation techniques.
But all is definitely not lost; we can still create a much stronger primitive using this vulnerability. Assuming the second if block is not executed, we arrive at the function's end:

So the target value is incremented by one; a limited write primitive. Note that the final log statement is not actually included in a release build (since ALOGV is an empty macro is those builds).
Putting this all together, we get a write primitive allowing us to increment the value at mRefCount[stream] by one, so long as it is not larger than or equal to 0x7FFFFFFF.
I SPY WITH MY LITTLE EYE
Now that we have a write primitive, let's look for a read primitive. Also, since our write primitive is relative to an AudioOutputDescriptor object, which is dynamically allocated (and is therefore located in a rather unpredictable location in the heap), it would be much more convenient if we were able to find such a primitive which is also relative to an AudioOutputDescriptor object.
Pouring over the AudioPolicyManagerBase's methods once more, reveals a very tempting target; the AudioPolicyManagerBase::isStreamActive method. This method allows a user to query a given stream in order to check if it was active in any of the output descriptors within the user-supplied time-frame:

So once again - this method performs no validation at all on the given "stream" argument. Perhaps the validation is delegated to the internal AudioOutputDescriptor::isStreamActive method?

Nope - lucky once again!
So, once more we access the mRefCount member of the AudioOutputDescriptor using the "stream" argument as a index (while performing no validation whatsoever). As we can see, there are two cases in which this function would return true:
- If mRefCount[stream] != 0
- Otherwise, if the time difference between the current system time and the value of mStopTime[stream] is less than the user-supplied argument - inPastMs.
Since we would like to use this vulnerability as an easy read primitive, we would first seek to eliminate side-effects. This is crucial as it would make the actual exploit much easier to build (and much more modular).
However, simply passing in the argument "inPastMs" with the value 0x80000000 (i.e., INT_MIN), would cause the last if statement to always evaluate to false (since there are no integers smaller than INT_MIN).
This leaves us with a simple and "clean" (albeit somewhat weak) read primitive: the function isStreamActive will return true iff the value at mRefCount[stream] is not zero. Since the stream argument is fully controlled by us, we can use it to "scan" the memory relative to the AudioOutputDescriptor object, and to gage whether or not it contains a zero DWORD.
THERMAL VISION
At this point you might be wondering - how can you even call this a read-primitive? After all, the only possible information we can learn using this vulnerability is whether or not the value at a given address is zero. Glad you (kind-of) asked!
In fact, this is more than enough for us to find our way around the heap. Instead of thinking in terms of "heap" and "memory", let's use our imagination.
You're a secret agent out on a mission. You're standing behind a closed door, leading to the room you need to enter. So what do you naturally do? Turn on your thermal vision goggles, of course. The goggles present you with the following image:

So it's safe - we can see it's only a dog.
Let's look at the image again - did we really need all the heat information? For example, what if we only had information if a given pixel is "hot" or not?

Still definitely recognizable.
This is because the outline of the dog allowed us to create a "heat signature", which we could then use to identify dogs using our thermal goggles.
So what about heaps and memory? Let's say that when a value in memory is non-zero, it is "hot", and otherwise, that memory location is "cold". Now - using our read-primitive, we can create a form of thermal vision goggles, just like the pair we imagined a minute ago.
All that remains to be seen is whether or not we can create good "heat signatures" for objects we are interested in.
First, looking at a histogram of a full memory dump of the heap in the mediaserver process, reveals that the value zero is by far the most common:

Moreover, typical heap objects appear to have many zeros within them, leading to some interesting repeatable patterns. Here is a heat-map generated from the aforementioned heap dump:

Now - looking at the binary heat-map we can see there are still many interesting patterns we can use to try and "understand" which objects we are observing:

So now that you're (hopefully) convinced, we can move on to building an actual exploit!
BUILDING AN EXPLOIT
As we've established above, we now have two tools in our belt:
- We can increment any value, so long as it's lower than 0x7FFFFFFF
- We can inspect a memory location in order to check if it contains the value zero or not
In order to take this one step further, it would be nice if we were able to find an object that has a very "distinct" heat-signature, and which also contains pointers to functions which we can call (using regular API calls), and to which we can pass controllable arguments.
Searching around for a bit, reveals a prime candidate for exploitation - audio_hw_device. This is a structure holding many function pointers for the implementations of each of the functions provided by an actual audio hardware device (it is part of the audio hardware abstraction layer). Moreover, these function pointers can also be triggered at ease simply by calling different parts of the audio policy service and audio flinger APIs.

However, what makes this object especially interesting is its structure - it begins with a header with a fixed length initialized with non-zero values. Then, it contains a large block of "reserved" values, which are initialized to zero, followed by a large block of function pointers, of whom only the second one is initialized to zero.
This means audio_hw_device objects have quite a unique heat signature:

So we can easily find these objects, great! Now what?
Let's sketch a game-plan:
- Search for a audio_hw_device using its heat signature
- Create a "stronger" read primitive (using the existing primitives)
- Create a "stronger" write primitive (again, using the existing primitives)
- Using the new primitives, hijack a function to execute arbitrary code
We've already seen how we can search for a audio_hw_device by using the heat signature mentioned above, but what about creating new primitives?
HARDER BETTER FASTER STRONGER (PRIMITIVES)
In order to do so, we would like to hijack a function within the audio_hw_device structure with the following properties:
- We can easily trigger a call to this function by invoking external API calls
- The function's return value is returned to the user
- The arguments to this function are completely user-controlled
Reading through the different API calls once more, we arrive at the perfect candidate; AudioFlinger::getInputBufferSize:

As you can see, an audio_config structure is populated using the user-provided values, and is then passed on to the audio hardware device's implementation - get_input_buffer_size.
This means that if we find our audio_hw_device, we can modify the get_input_buffer_size function pointer to point to any gadget we would like to execute - and whichever value we return from that gadget, will be simply returned to the user.
CREATING THE PRIMITIVES
First of all, we would like to find out the real memory address of the audio_hw_device structure. This is useful in case we would like to pass a pointer to a location within this object at a later stage of the exploit.
This is quite easily accomplished by using our weak write primitive in order to increment the value of the get_input_buffer_size function pointer so that it will point to a "BX LR" gadget - i.e., instead of performing any operation, the function will simply return.
Since the first argument provided to the function is a pointer the audio_hw_device structure itself, this means it will be stored in register R0 (according to the ARM calling convention), so upon executing our crafted return statement, the value returned will be the value of R0, namely, the pointer to the audio_hw_device.

Now that we have the address of the audio_hw_device, we would like to also read an address within one of the loaded libraries. This is necessary so that we'll be able to calculate the absolute location of other loaded libraries and gadgets within them.
However, as we've seen before, the audio_hw_device structure contains many function pointers - all of whom point to the text segment of one of the loaded libraries. This means that reading any of these function pointers is sufficient for us to learn the location of the loaded libraries.
Moreover, since the get_input_buffer_size function receives the audio_hw_device as its first argument, we can search for any gadget which reads into R0 a value from R0 at an offset which falls within the function pointer block range, and returns. There are many such gadgets, so we can simply chose one:

At this point, we know the location of the audio_hw_device and of the loaded libraries. All that's left is to create an arbitrary write primitive.
As we've since before, three user-controlled values are inserted into a structure and passed as the second argument (R1) to get_input_buffer_size. We can now use this to our advantage; we'll pass in values corresponding to our wanted write address and value as the first two arguments to the function. These will get packed into the first two values in the audio_config structure.
Now, we'll search for a unique gadget which unpacks these two values from R1, writes our crafted value into the wanted location and returns.
While this seems like a lot to ask for, after searching through a multitude of gadgets, there appears to be a gadget (in libcamera_client.so) which does just this:

This means we can now increment the get_input_buffer_size function pointer to point to this gadget, and by passing the wanted values to the AudioFlinger::getInputBufferSize function, we can now write any 32-bit value to any absolute address within the mediaplayer process's virtual address space.
WE HAVE LIFT-OFF
Now that we have all the primitives we need, we just need to put the pieces together. We'll create an exploit which calls the "system" function within the "mediaserver" process.
Once we have the addresses of the audio_hw_device and the library addresses, along with an arbitrary write primitive, we can prepare the arguments to our "system" function call anywhere within mediaserver's virtual address space.
A good scratch pad candidate would be the "reserved" block within the audio_hw_device, since we already know its absolute memory location (because we leaked the address of the audio_hw_device), and we also know that overwriting that area won't have any negative side-effects. Using our write primitive, we can now write the path we would like to call "system" on to the "reserved" block, along with the address of the "system" function itself (which we can calculate since we leaked the library load address).
Now, we can use our write primitive to change the get_input_buffer_size function pointer one final time - this time we would like to point it at a gadget which would unpack the function address and argument we have written into the reserved block, and would execute the function using this argument. This gadget was found in libstagefright.so:

So... This is it; we now have code execution within the "mediaserver" process. Here's a small diagram recapping our total exploit flow:

FULL EXPLOIT CODE
As always; I'd like to provide the full exploit code we have covered in this blog post. You can get it here:
https://github.com/laginimaineb/cve-2014-7920-7921
The gadgets were collected for Android version 4.3, but can obviously be adjusted to whichever Android version you would like to run the exploit against (up to Android 5.1).
I highly recommend that you download and look at the exploit's source code - there are many nuances I did not cover in the blog post (for brevity's sake) and the each stage of the exploit is heavily documented.
Have fun!
TIMELINE
- 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
Android privilege escalation to mediaserver from zero permissions (CVE-2014-7920 + CVE-2014-7921)的更多相关文章
- [EXP]Memu Play 6.0.7 - Privilege Escalation
# Exploit Title: Memu Play - Privilege Escalation (PoC) # Date: // # Author: Alejandra Sánchez # Ven ...
- Android linux kernel privilege escalation vulnerability and exploit (CVE-2014-4322)
In this blog post we'll go over a Linux kernel privilege escalation vulnerability I discovered which ...
- OSCP Learning Notes - Privilege Escalation
Privilege Escalation Download the Basic-pentesting vitualmation from the following website: https:// ...
- karottc A Simple linux-virus Analysis、Linux Kernel <= 2.6.37 - Local Privilege Escalation、CVE-2010-4258、CVE-2010-3849、CVE-2010-3850
catalog . 程序功能概述 . 感染文件 . 前置知识 . 获取ROOT权限: Linux Kernel <= - Local Privilege Escalation 1. 程序功能概述 ...
- Basic Linux Privilege Escalation
(Linux) privilege escalation is all about: Collect - Enumeration, more enumeration and some more enu ...
- Linux/Unix System Level Attack、Privilege Escalation(undone)
目录 . How To Start A System Level Attack . Remote Access Attack . Local Access Attack . After Get Roo ...
- FreeBSD Intel SYSRET Kernel Privilege Escalation Exploit
/* * FreeBSD 9.0 Intel SYSRET Kernel Privilege Escalation exploit * Author by CurcolHekerLink * * Th ...
- [EXP]Microsoft Windows - DfMarshal Unsafe Unmarshaling Privilege Escalation
Windows: DfMarshal Unsafe Unmarshaling Elevation of Privilege (Master) Platform: Windows (not tested ...
- CVE-2014-4014 Linux Kernel Local Privilege Escalation PoC
/** * CVE-2014-4014 Linux Kernel Local Privilege Escalation PoC * * Vitaly Nikolenko * http://ha ...
随机推荐
- day 5,格式化输出,for,while, break,continue,列表
本节内容: 1,格式化输出 2,数据类型 3,for 循环 4,while 循环 5,列表 pycharm的简单使用,设置pycharm自动生成日期和计算机用户名 ctrl+d复制一行 1,格式化输出 ...
- zeromq学习记录(六)C语言示例
考虑到官方的示例c语言是最多的 官方未使用C++语言演示的例子就使用VC编译C语言例子 记录在此 /************************************************** ...
- 深入理解JVM(三)垃圾收集器和内存分配策略
3.1 关于垃圾收集和内存分配 垃圾收集和内存分配主要针对的区域是Java虚拟机中的堆和方法区: 3.2 如何判断对象是否“存活”(存活判定算法) 垃圾收集器在回收对象前判断其是否“存活”的两个算法: ...
- Java 装箱和拆箱
1.装箱机制 基础类型引用到其包装类型,这样就可以调用其各种方法. 例如,我们声明: Integer a = 1; 其在编译过程中会自动解释成: Integer a = Integer.valueOf ...
- 过滤器和拦截器filter和Interceptor的区别
1.创建一个Filter过滤器只需两个步骤 创建Filter处理类 web.xml文件中配置Filter 2.Servlet中的过滤器Filter是实现了javax.servlet.Filter接口的 ...
- Hadoop 系列文章(一) Hadoop 的安装,以及 Standalone Operation 的启动模式测试
以前都是玩 java,没搞过 hadoop,所以以此系列文章来记录下学习过程 安装的文件版本.操作系统说明 centos-6.5-x86_64 [bamboo@hadoop-senior opt]$ ...
- Python之旅Day1 数据类型初识(数字|字符串|列表|数据运算) 编码 表达式(if...else|for|while)
初识PYTHON Python(英国发音:/ˈpaɪθən/ 美国发音:/ˈpaɪθɑːn/), 是一种面向对象的解释型计算机程序设计语言,由荷兰人Guido van Rossum(吉多·范罗苏姆)于 ...
- bash编程-cut、printf
1. cut cut命令用于从行中截取字符串. SYNOPSIS cut OPTION... [FILE]... ** OPTION** -d CHAR:以指定的字符为分隔符(不要用空格作为分隔符): ...
- Repository 简化实现多条件查询
Repository 在做查询的时候,如果查询条件多的话,linq查询表达式会写的很复杂,比如: public IQueryable<Student> Get(int id, string ...
- 背水一战 Windows 10 (79) - 自定义控件: Layout 系统, 控件模板, 事件处理
[源码下载] 背水一战 Windows 10 (79) - 自定义控件: Layout 系统, 控件模板, 事件处理 作者:webabcd 介绍背水一战 Windows 10 之 控件(自定义控件) ...