7

再论CVE-2014-7911安卓序列化漏洞 | WooYun知识库

 6 years ago
source link:
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

0x00 回顾


CVE-2014-7911是一个非常值得安卓安全研究者学习的漏洞,其漏洞成因和漏洞利用涉及java序列化、安卓Binder IPC通信、ROP、Stack Pivot、heap Spray及多方面的知识。本文上篇http://drops.wooyun.org/mobile/6082对漏洞成因及Crash POC进行了分析。本篇结合retme、secauo等大牛们的已有exp,站在大牛们的肩膀上,续写漏洞利用部分,最终目标是利用这个漏洞以system权限执行代码。由于水平有限,请各位大牛批评指正。

首先回顾一下。前面提到,利用java反射和Binder进程间通信机制,向system_server传入一不可序列化的恶意对象,由于java.io.ObjectInputStream并未校验该输入的对象实例是否是实际可序列化的,因此当该对象实例被ObjectInputStream反序列化时,将发生类型混淆,对象的Field被视为由本地代码处理的指针,使攻击者获得控制权。如下,

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, 其中放置提权代码以及大量指向该提权代码的地址。这涉及到两个问题。

  1. 如何向sysetem_server的dalvik-heap空间传入可控字符串?
  2. 如何在dalvik-heap中布局这些可控字符串,才能在每次漏洞利用时都稳定执行代码?

对于第一个问题,我们知道system_server向android系统提供绝大多数的系统服务,通过这些服务的一些特定方法可以向system_server传入String,同时system_server把这些String存储在Dalvik-heap中,在GC之前都不会销毁。例如,下面android.content.Context中的registerReceiver方法

public Intent registerReceiver (BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler)

其中broadcastPermission为String类型,调用该方法后,String Buffer将常驻system_server进程空间。具体调用链见

ContextWrapper.registerReceiver->ContextImpl.registerReceiver->ContextImpl.registerReceiverInternal->ActivityManagerProxy.registerReceiver->ActivityManagerService.registerReceiver

该调用链表明可从某个app的Context通过binder IPC跨进程调用system_server的ActivityManagerService.registerReceiver方法,注意ActivityManagerService常驻system_server进程空间。我们再看看ActivityManagerService的registerReceiver方法

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方法分配内存时都会不一样,这就需要构造一种特殊的堆喷射布局,包含递减的指针值,如图。

image

如图,每一个在堆中分配的内存块(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。

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函数之后

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寄存器、以及在栈上布置数据带来便利。利用

ROPgadget --thumb --binary libwebviewchromium.so

可找到如下Gadget

Gadget1

为Stack Pivot作准备

in libwebviewchromium.so

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

或者

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

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

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布局如图。

image

最后,构造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文件的解析,就可以得知所加载基础模块的基址。如图,

image

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

0x04 修复


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

  1. 检查反序列化的类是否仍然满足序列化的需求;
  2. 检查反序列化的类的类型是否与stream中所持有的类型信息 (enum, serializable, externalizable)一致;
  3. 在某些情形下,延迟类的静态初始化,直到对序列化流的内容检查完成。

0x05 参考

1.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

3.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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK