# 再论CVE-2014-7911安卓序列化漏洞

Author: 乌云历史资料库 (@wooyun_archive)
Published: 2015-11-11T08:11:00Z
Canonical: https://wepostx.com/topics/1170

> 乌云历史资料归档
>
> **原始作者：** 小荷才露尖尖角
> **原始编号：** superkieran-wooyundrops:881
> **原始发布时间：** 2015-11-11 16:11
> **声明：内容仅用于技术研究和个人使用，版权归 wooyun.org。**

---

# 0x00 回顾

CVE-2014-7911是一个非常值得安卓安全研究者学习的漏洞，其漏洞成因和漏洞利用涉及java序列化、安卓Binder IPC通信、ROP、Stack Pivot、heap Spray及多方面的知识。本文上篇[http://drops.wooyun.org/mobile/6082](http://drops.wooyun.org/mobile/6082)对漏洞成因及Crash POC进行了分析。本篇结合retme、secauo等大牛们的已有exp，站在大牛们的肩膀上，续写漏洞利用部分，最终目标是利用这个漏洞以system权限执行代码。由于水平有限，请各位大牛批评指正。
首先回顾一下。前面提到，利用java反射和Binder进程间通信机制，向system_server传入一不可序列化的恶意对象，由于java.io.ObjectInputStream并未校验该输入的对象实例是否是实际可序列化的，因此当该对象实例被ObjectInputStream反序列化时，将发生类型混淆，对象的Field被视为由本地代码处理的指针，使攻击者获得控制权。如下，

```text
if(*(*(mOrgue+4))==1){ refs = *(mOrgue+4) r2 = *(*(*(refs+8))+12) blx r2 ----->获取控制权 }
```
其中mOrgue是攻击者可控的，经过层层指针的解引用，最终以system_server(uid=1000)的权限跳转到攻击者可控的地址执行代码，从而实现从普通用户到system用户的提权。

# 0x01 Dalvik-heap Spray

为了使上述blx r2这条指令可靠稳定地跳转到执行攻击者可控的代码，需要使用堆喷射技术，在system_server内存空间的dalvik-heap中预先布置大量的Spray Buffer, 其中放置提权代码以及大量指向该提权代码的地址。这涉及到两个问题。

- 如何向sysetem_server的dalvik-heap空间传入可控字符串？

- 如何在dalvik-heap中布局这些可控字符串，才能在每次漏洞利用时都稳定执行代码？
对于第一个问题，我们知道system_server向android系统提供绝大多数的系统服务，通过这些服务的一些特定方法可以向system_server传入String，同时system_server把这些String存储在Dalvik-heap中，在GC之前都不会销毁。例如，下面android.content.Context中的registerReceiver方法

```text
public Intent registerReceiver (BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler)
```
其中broadcastPermission为String类型，调用该方法后，String Buffer将常驻system_server进程空间。具体调用链见

```text
ContextWrapper.registerReceiver->ContextImpl.registerReceiver->ContextImpl.registerReceiverInternal->ActivityManagerProxy.registerReceiver->ActivityManagerService.registerReceiver
```
该调用链表明可从某个app的Context通过binder IPC跨进程调用system_server的ActivityManagerService.registerReceiver方法，注意ActivityManagerService常驻system_server进程空间。我们再看看ActivityManagerService的registerReceiver方法

```text
public Intent registerReceiver(IApplicationThread caller, String callerPackage, IIntentReceiver receiver, IntentFilter filter, String permission, int userId) { enforceNotIsolatedCaller("registerReceiver"); int callingUid; int callingPid; synchronized(this) { ...... ReceiverList rl = (ReceiverList)mRegisteredReceivers.get(receiver.asBinder()); ...... BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage, permission, callingUid, userId); //在Dalvik-heap中分配内存 rl.add(bf); ...... return sticky; } }
```
注意上面的new将在system_server进程的Dalvik-heap堆中分配内存，传入的String Buffer即permission将常驻system_server进程空间。这样，通过调用某些系统Api，第一个传入字符串的问题就解决了。
下面探讨第二个问题：如何在dalvik-heap中布局这些可控字符串，才能在每次漏洞利用时都能稳定执行代码？
根据前面的叙述，攻击者可控的mOrgue需要指向一个可读的内存区域，简单地让其指向传入registerReceiver方法permission参数String Buffer所属的地址区域并在String Buffer中布置ROP Gadget即可达到这个目的，但system_server在其dalvik-heap中分配String Buffer的偏移地址却是未知的，mOrgue未必能命中（指向）堆块中为String Buffer分配的内存。为了提高命中率，需要在dalvik-heap中分配大量的String Buffer，这就是Heap Spray(堆喷射)技术，反复调用registerReceiver方法分配大量的String Buffer即可完成Heap Spray。但是，String Buffer的地址在每次调用registerReceiver方法分配内存时都会不一样，这就需要构造一种特殊的堆喷射布局，包含递减的指针值，如图。

![原文图片](/media/2016/06/700ae21e0a00d29cd4944300f2ab7995)

如图，每一个在堆中分配的内存块(chunk)，都包含Relative Address Chunk和Gadget_buffer两部分，目标是使可控的STATIC_ADDRESS（即mOrgue）位于Relative Address Chunk，并且使其存放的内容[STATIC_ADDRESS]=GADGET_BUFFER（即Gadget_buffer的地址）。简单的思路就是在每个chunk的relative Address Chunk中都放入GADGET_BUFFER，然而由于GADGET_BUFFER在每个chunk中都不一样，而且也无法在跨进程传入system_server之前提前知晓，因此该思路并不可行。
注意，GADGET_BUFFER = 堆底地址 + Gadget_buffer_offset(即Gadget_Buffer相对于堆底的偏移)。当STATIC_ADDRESS=堆底地址时，GADGET_BUFFER = STATIC_ADRRESS+Gadget_buffer_offset；考虑到四字节对齐，一般情况下，STATIC_ADDRESS=堆底地址+4N（N=1,2,...），此时GADGET_BUFFER = STATIC_ADDRESS + Gadget_buffer_offset - 4N。因此，在每一个Chunk的Relative Address Chunk区域按地址增长方向，依次在内存中填入STATIC_ADDRESS+Gadget_buffer_offset、STATIC_ADDRESS+Gadget_buffer_offset-4、...、STATIC_ADDRESS+Gadget_buffer_offset-4N。这样，给定一个STATIC_ADDRESS，只要能落入system_server在dalvik heap分配的Relative Addresses Chunk的地址范围(为了提高这个可能性，需要满足1.每一个Chunk的Relative Address Chunk比Gadget Buffer大很多；2.分配大量这样的Chunk），就总是存在[STATIC_ADDRESS]=GADGET_BUFFER，并满足[STATIC_ADDRESS+4N]=GADGET_BUFFER-4N（这个条件将在后面布置Gadget时用到）。
按照这样的布局，回过来再看汇编代码，布置Gadget_Buffer。

```text
ldr r4, [r0, #4] # r0=STATIC_ADDRESS-->r4=[STATIC_ADDRESS+4]=GADGET_BUFFER-4 mov r6, r1 mov r0, r4 # r0=GADGET_BUFFER-4 blx <android_atomic_dec ()>
```
调用android_atomic_dec函数之后

```text
cmp r0, #1 # r0 = [GADGET_BUFFER-4] bne.n d1ea ldr r0, [r4, #8] # r0 = [GADGET_BUFFER-4+8] = [GADGET_BUFFER+4] mov r1, r6 ldr r3, [r0, #0] # r3 =[[GADGET_BUFFER+4]] = [STATIC_ADDRESS+12] = GADGET_BUFFER-12 ldr r2, [r3, #12] # r2 = [GADGET_BUFFER -12 +12] = [GADGET_BUFFER] blx r2
```
首先，为了进入blx r2这条分支，r0必须等于1，也就是[GADGET_BUFFER-4]=1；其次，[GADGET_BUFFER+4]必须为一个合法可读的地址，为了方便之后的布局，我们令[GADGET_BUFFER+4]=STATIC_ADDRESS+12，因此r3 = [STATIC_ADDRESS+12]=GADGET_BUFFER-12,接下来r2=[r3+12]=[GADGET_BUFFER-12+12]=[GADGET_BUFFER]，程序将跳转到GADGET_BUFFER这个地址存放的内容执行，因此在这里就可以布置ROP Gadget1的地址了。至此，通过一种特殊布局的堆喷射，第二个代码稳定执行的问题也迎刃而解。

# 0x02 ROP Chain

由于Android使用了DEP，因此Dalvik-heap上的内存不能用来执行，这就必须使用ROP技术，使PC跳转到一系列合法指令序列（Gadget），并由这些Gadget“拼凑”而成shellcode。这里我们将使用ROP Gadget调用system函数执行代码。
使用ROPGadget这个工具，在zygote加载的基础模块（如libc.so、libwebviewchromium.so、libdvm.so）上进行搜索，把arm code当做thumb code来搜索，可以增加更多的候选指令序列。
为了调用system函数，需要控制r0寄存器，指向我们预先布置的命令行字符串作为参数。这里需要使用Stack Pivot技术，将栈顶指针SP指向控制的Dalvik-heap堆中的数据，这将为控制PC寄存器、以及在栈上布置数据带来便利。利用

```text
ROPgadget --thumb --binary libwebviewchromium.so
```
可找到如下Gadget

## Gadget1
为Stack Pivot作准备
in libwebviewchromium.so

```text
70a93c: 682f ldr r7, [r5, #0] #r5=STATIC_ADDRESS, r7=[STATIC_ADDRESS]=GADGET_BUFFER 70a93e: 4628 mov r0, r5 #r0=STATIC_ADDRESS 70a940: 68b9 ldr r1, [r7, #8] #r1=[GADGET_BUFFER+8] 70a942: 4788 blx r1
```
或者

```text
4fed02: 4628 mov r0, r5 #r5 = STATIC_ADDRESS 4fed04: 682f ldr r7, [r5, #0] #r7 = [STATIC_ADDRESS] = GADGET_BUFFER 4fed06: f8d4 8048 ldr.w r8, [r4, #72] ; 0x48 4fed0a: 68b9 ldr r1, [r7, #8] #r1 = [GADGET_BUFFER+8] 4fed0c: 4788 blx r1
```
因此，GADGET_BUFFER+8这个地址需要指向第二个Gadget

## Gadget2
Stack Pivot
in libdvm.so

```text
664c4: f107 0708 add.w r7, r7, #8 #r7=r7+8=GADGET_BUFFER+8 664c8: 46bd mov sp, r7 #sp=GADGET_BUFFER+8 664ca: bdb0 pop {r4, r5, r7, pc} # r4=[GADGET_BUFFER+8],r5=[GADGET_BUFFER+12],r7=[GADGET_BUFFER+16],pc=[GADGET_BUFFER+20], sp=GADGET_BUFFER+24
```
可以看到，将SP指向堆中可控的数据后，后面就可以控制PC。这里，我们提前将system函数的地址写入[GADGET_BUFFER+12]。为什么要通过Gadget1的过渡才能来到Gadget2，事实上这是不得已而为之，使用ROPGadget搜遍/system/lib下的基础模块grep "mov sp,r"，只发现mov sp,r7，因此只能采取这种过渡的方式。
接下来，在GADGET_BUFFER+20这个地址填入Gadget3的地址

## Gadget3
in libwebviewchromium.so

```text
30c4b8: 4668 mov r0, sp #r0=GADGET_BUFFER+24 30c4ba: 47a8 blx r5 #r5=[GADGET_BUFFER+12]=system_addr
```
因此，提前将system函数的参数放入r0指向的GADGET_BUFFER+24即可，最终将以system_server的权限执行任意代码。
最终的chunk布局如图。

![原文图片](/media/2016/06/49e06e5ec319cd0d4679dfd84405995a)

最后，构造ROP Chain还需要考虑一个细节，ARM有两种模式Thumb和ARM模式，我们使用的Gadgets均为Thumb模式，因此其地址的最低位均需要加1。

# 0x03 ASLR

Android 自4.1始开始启用ASLR（地址随机化），任何程序自身的的地址空间在每一次运行时都将发生变化。但在Android中，攻击程序、system_server皆由zygote进程fork而来，因此攻击程序与system_server共享同样的基础模块和dalvik-heap。只要在使用dalvik heapspray和构建ROP Gadget时，只使用libc、libdvm这些基础模块，就无需考虑地址随机化的问题。通过对攻击程序自身/proc/<pid>/maps文件的解析，就可以得知所加载基础模块的基址。如图，

![原文图片](/media/2016/06/e7e856d498d16ff1017899b93bc9df18)

根据上述Gadgets构建的POC见[https://github.com/heeeeen/CVE-2014-7911poc](https://github.com/heeeeen/CVE-2014-7911poc) ，执行完毕后，将以system用户的权限在/data目录下生成一个pwned.txt文件。

# 0x04 修复

见[https://android.googlesource.com/platform/libcore/+/738c833d38d41f8f76eb7e77ab39add82b1ae1e2%5E%21/#F0](https://android.googlesource.com/platform/libcore/+/738c833d38d41f8f76eb7e77ab39add82b1ae1e2%5E%21/#F0)，涉及与反序列化相关的 ObjectInputStream.java、ObjectStreamClass.java、ObjectStreamConstants.java、SerializationTest.java等文件。主要加了三种检查：

- 检查反序列化的类是否仍然满足序列化的需求；

- 检查反序列化的类的类型是否与stream中所持有的类型信息 (enum, serializable, externalizable)一致；

- 在某些情形下，延迟类的静态初始化，直到对序列化流的内容检查完成。

# 0x05 参考
1.[http://researchcenter.paloaltonetworks.com/2015/01/cve-2014-7911-deep-dive-analysis-android-system-service-vulnerability-exploitation](http://researchcenter.paloaltonetworks.com/2015/01/cve-2014-7911-deep-dive-analysis-android-system-service-vulnerability-exploitation)
2.[https://github.com/retme7/CVE-2014-7911_poc](https://github.com/retme7/CVE-2014-7911_poc)
3.[https://github.com/retme7/My-Slides/blob/master/xKungfooSH%40retme.pdf](https://github.com/retme7/My-Slides/blob/master/xKungfooSH%40retme.pdf)
4.[http://secauo.com/CVE-2014-7911-Detailed-Analysis-Of-Android-Local-Privilege-Escalation-To-System-Vulnerability.html](http://secauo.com/CVE-2014-7911-Detailed-Analysis-Of-Android-Local-Privilege-Escalation-To-System-Vulnerability.html)

## Replies
