两年前写的东西,现在发一下。写了个最简单的fuzz脚本:基于adb的fuzzer,仅供学习,请勿作不良用途。
Android为用户提供的很多功能是通过系统服务实现的。截至Android 6.0,系统服务数量已经达到60多个。系统服务的底层是system_server(以下简称ss),我们知道,Android存在watchdog程序,当系统服务长时间不响应时,watchdog会杀死ss进程,从而导致Android系统的软重启。Android提供的系统服务底层是通过binder进程间通信机制实现的,binder的数据传递过程这不在详解。从Java层传递的数据,最终会在C/C++代码中被使用。不管是java还是c/c++,如果传递了一个null指针引用。将会导致程序的崩溃,Android系统因此也受此影响。本Fuzz测试旨在发现Android系统服务的代码实现中,由于没有null指针做好处理导致的系统奔溃。
Android对外提供的系统服务,根据提供的功能,实现了不同的函数,以供开发者调用。例如,开发者在上层调用发送短信的API,其底层同样是通过binder调用了短信的系统服务。这些可以通过binder调用的方法一般定义在.aidl文件,一些系统服务定义了多达数百个可调用的方法。这样,可通过binder调用测试的方法多大几千个。所以,通过Fuzz测试这些方法不失为一种好的测试手段。
在Android Binder机制实现进程间数据交换(不使用aidl实现)中,已经说明了如何利用binder调用系统服务实现发送短信的功能。除了编写本地App通过binder来调用系统服务之外。其实,Android已经给我们留下了“测试后门”。在shell中,通过service指令可以直接对系统服务进行测试,并支持对所有aidl文件中定义的方法的测试。
其中,service call SERVICE CODE 就是对aidl文件中定义的方法的测试。其中,SERVICE就是对应的service名,code就是在aidl文件中定义的方法,其数值根据定义的方法递增,从1开始。例如,在IAccessibilityManager.aidl文件中,定义了以下方法。
了解了以下原理之后,就可以编写Fuzz工具进行测试了。这里工具实现方式有两种,一是编写脚本调用的shell提供的service指令测试,而是编写本地App,通过调用binder来对系统服务进行测试。我们采用第二种,更加有利于我们学习Android系统服务及binder通信机制的相关知识。这里不给出具体的代码,后面会上传的github,只给出一些关键的地方。
获取所有的系统服务。
/**
* 获取系统中所有的服务名
* @return 服务名字符串数组
*/
public String[] ListService() {
String SM[]={};
try{
SM=(String [])Class.forName("android.os.ServiceManager").getMethod("listServices").invoke(null);
// Log.d(tag,"I find there are "+SM.length+" System Services");
for(int i=0;i< SM.length;i++){
// Log.d(tag,SM[i]);
}
}catch(Exception e){
e.printStackTrace();
Log.e(tag, e.toString());
}
return SM;
}
通过反射获取IBinder接口对象。
/**
* 使用反射机制获取服务的IBinder接口
* @param sername 服务名
* @return 返回服务的IBinder接口
* @throws Exception
*/
private static IBinder getIBinder(String sername) throws Exception{
Class smcls = Class.forName("android.os.ServiceManager");
Method mth = smcls.getMethod("getService", String.class);
return (IBinder) mth.invoke(null, sername);
}
通过binder获取接口名。
/**
* 获取接口名
* @param serHandle 服务的IBinder接口
* @return 返回接口名字符串
* @throws RemoteException
*/
private static String getInterfaceName(IBinder serHandle) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
serHandle.transact(INTERFACE_TRANSACTION, data, reply, 0);
String interfacename = reply.readString();
data.recycle();
reply.recycle();
return interfacename;
}
通过binder调用系统服务函数,进行测试。
void testNUll(String sername, int code) {
for (int i = 0; i < testcaseint.length; i++)
for (int j = 0; j < testcaseint.length; j++) {
try {
Log.d(tag, "-" + sername + "-" + code + "-arg1-" + testcaseint[i] + "-arg2-" + testcaseint[j]);
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
IBinder ib = getIBinder(sername);
String in = getInterfaceName(ib);
data.writeInterfaceToken(in);
data.writeInt(testcaseint[i]);
data.writeInt(testcaseint[j]);
// data.writeInt(0);
// data.writeInt(3);
ib.transact(code, data, reply, 0);
//可以读出reply中的数据
// Log.d(tag, "-" + sername + "-" + code + "-" + "reply is \n" + reply.readString());
reply.readException();
data.recycle();
reply.recycle();
} catch (Exception e) {
}
}
}
单次调用binder测试系统服务中定义的方法流程如下。
整个Fuzz工具的架构如下:
还有一个问题,要实现整个Fuzz的自动化,还需要自动化监控系统重启事件。
Android系统开机完成之后,会发出一个BOOT_COMPLETED 系统广播 ,所以通过检测此广播可以监控系统是否发生了重启,从而确定漏洞位置,即哪一个系统服务对应的哪一个方法触发。除此之外,BOOT_COMPLETED 广播是由ActivityManagerService的finishBooting方法发出的,所有可以通过Hook此API完成监测。鉴于Xposed的Hook框架可以很容易的帮助我们做到这点。