Android漏洞检测——模糊测试
前言
Android在目前的市场上占有率很高,用户数量庞大,而在该平台下的应用程序开发成本低,开发难度低,发布容易,缺少监管和审查,导致大量低质量App流入市场,这些App由于开发者缺乏安全编程技能或缺乏测试和审查,可能存在着一些严重的漏洞,对用户的隐私以及财产安全造成巨大风险。因此,移动应用尤其是Android平台下的应用的开发应该对此引起高度重视。
Android常见漏洞
-
越权绕过:没有对调用activity的组件进行权限验证,就会造成验证的安全问题
-
钓鱼欺诈:启动一个activity时,加入标志位FLAG_ACTIVITY_NEW_TASK,能够使被启动的activity立马呈现给用户,可用于钓鱼欺诈
-
拒绝服务:本地组件启动时没有对Intent.getXXXExtra()获取或处理的数据进行异常捕获,从而导致攻击者可通过向受害者应用发送空数据、异常或者畸形数据来使该应用crash的目的
-
权限提升:当一个具有高权限的service是被导出的时,如果没对调用这个Service进行权限限制和调用者的身份验证时,那么恶意的app将具有调用高权限的service的能力来执行高权限行为等
-
权限泄露:主要存在于某些具有高权限操作的组件被导出,而系统没有进行严格的身份验证和权限控制而导致的其他应用可以利用该组件而产生越权操作的行为。
Android常见漏洞检测方法
-
静态分析
利用apktool、dex2jar、jd-gui、smali2dex等静态分析工具对应用进行反编译,并对反编译后的java文件、xml文件等文件静态扫描分析,通过关键词搜索等静态方式将具有安全隐患的代码进行摘录并存入到检测平台后台,为后续的安全检测报告提供数据依据。 -
动态分析
对应用软件安装、运行过程的行为监测和分析。检测的方式包括沙箱模型和虚拟机方式。虚拟机方式通过建立与Android手机终端软件运行环境几乎一样的虚拟执行环境,手机应用软件在其中独立运行,从外界观察应用程序的执行过程和动态,进而记录应用程序可能表现出来的恶意行为。 -
人工分析
专业安全人员对待检测应用,对其进行安装、运行和试用,通过在试用过程中,逐步掌握应用的特点,并通过专业经验,来圈定检测重点。人工专业检测在涵盖基础检测和深度检测的全部检测项的同时,兼顾侧重点检测,给予应用更全面、更专业、更贴合应用的量身打造的检测服务。
模糊测试
-
简介
模糊测试(Fuzzing),是一种通过向目标系统提供非预期的输入并监视异常结果来发现软件漏洞的方法。我个人理解,这是一种随机或伴随机的测试方法,与Monkey等随机测试工具有异曲同工之妙,只是其关注点不同。 -
模糊测试的执行过程:
1.测试工具通过随机或是半随机的方式生成大量数据;
2.测试工具将生成的数据发送给被测试的系统(输入);
3.测试工具检测被测系统的状态(如是否能够响应,响应是否正确等);
4.根据被测系统的状态判断是否存在潜在的安全漏洞
模糊测试工具IntentFuzzer
本文将介绍一种模糊测试工具,IntentFuzzer。
主界面 应用列表 测试组件列表-
简介
这个工具是针对Intent的Fuzzer。它通常可以发现能够导致系统崩溃的bug,部分安全漏洞,以及设备、应用程序或者是定制平台的运行中的问题。该工具能够针对一个简单组件或者是所有安装组件进行fuzz测试。它也适用于BroadcastReceiver,但针对Service只有较少的覆盖,Service通常更加广泛地应用Binder接口而不是针对IPC的Intent。原版的工具只能针对一个Activity进行fuzz测试,一次不能针对所有的Activity进行测试。另外,也能应用这个接口来启动Instrumentation,虽然列出了ContentProvider,但是它们不是一个基于Intent的IPC机制,因此并不能应用该工具进行fuzz测试。MindMac在此基础上进行了一些修改,使其能够针对一个应用的一个简单组件或者是所有组件进行fuzz测试,同时具有区分系统应用和非系统应用的能力。MindMac修改后的版本仅针对Activity、BroadcastReceiver、Service。 -
原理
列举出系统上所有公开的、能够从应用获取到的Activity、BroadcastReceiver、Service、Instrumentation、ContentProvider。工具将通过Intent尝试启动所有可以获取到的组件,从而触发某些难以发掘的漏洞。触发一般有两类漏洞,一类是拒绝服务,一类的权限提升。拒绝服务危害性比较低,更多的只是影响应用服务质量;而权限提升将使得没有该权限的应用可以通过Intent触发拥有该权限的应用,从而帮助其完成越权行为。如果该工具能够轻易从外部启动特定应用的内部组件,尤其是有较高权限的组件时,很可能在此处发现漏洞。 -
功能
对某个组件或某个应用的某类组件发起Fuzz,分为Null Fuzz和Serialize
Fuzz,即在Intent中不携带参数和携带序列化对象参数,然后尝试使用该Intent启动Activity、BroadcastReceiver、Service。 -
代码
获取所有应用及其组件
public static List<AppInfo> getPackageInfo(Context context, int type){
List<AppInfo> pkgInfoList = new ArrayList<AppInfo>();
List<PackageInfo> packages = context.getPackageManager().getInstalledPackages(
// PackageManager.GET_DISABLED_COMPONENTS |
PackageManager.GET_ACTIVITIES
| PackageManager.GET_RECEIVERS
| PackageManager.GET_INSTRUMENTATION
| PackageManager.GET_SERVICES);
for(int i=0;i<packages.size();i++) {
PackageInfo packageInfo = packages.get(i);
if (type == SYSTEM_APPS) {
if((packageInfo.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 1) {
pkgInfoList.add(fillAppInfo(packageInfo, context));
}
}else if(type == NONSYSTEM_APPS){
if((packageInfo.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) {
pkgInfoList.add(fillAppInfo(packageInfo, context));
}
}else {
pkgInfoList.add(fillAppInfo(packageInfo, context));
}
}
构建Intent
private void initView(){
typeSpinner = (Spinner) findViewById(R.id.type_select);
cmpListView = (ListView) findViewById(R.id.cmp_listview);
fuzzAllNullBtn = (Button) findViewById(R.id.fuzz_all_null);
fuzzAllSeBtn = (Button) findViewById(R.id.fuzz_all_se);
cmpListView.setOnItemClickListener(new OnItemClickListener(){
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
ComponentName toSend = null;
Intent intent = new Intent();
String className = cmpAdapter.getItem(position).toString();
for (ComponentName cmpName : components) {
if (cmpName.getClassName().equals(className)) {
toSend = cmpName;
break;
}
}
intent.setComponent(toSend);
if (sendIntentByType(intent, currentType)) {
Toast.makeText(FuzzerActivity.this, "Sent Null " + intent, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(FuzzerActivity.this, "Send " + intent + " Failed!", Toast.LENGTH_LONG).show();
}
}
});
cmpListView.setOnItemLongClickListener(new OnItemLongClickListener(){
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
// TODO Auto-generated method stub
ComponentName toSend = null;
Intent intent = new Intent();
String className = cmpAdapter.getItem(position).toString();
for (ComponentName cmpName : components) {
if (cmpName.getClassName().equals(className)) {
toSend = cmpName;
break;
}
}
intent.setComponent(toSend);
intent.putExtra("test", new SerializableTest());
if (sendIntentByType(intent, currentType)) {
Toast.makeText(FuzzerActivity.this, "Sent Serializeable " + intent, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(FuzzerActivity.this, "Send " + intent + " Failed!", Toast.LENGTH_LONG).show();
}
return true;
}
});
fuzzAllNullBtn.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
for(ComponentName cmpName : components){
Intent intent = new Intent();
intent.setComponent(cmpName);
if (sendIntentByType(intent, currentType)) {
Toast.makeText(FuzzerActivity.this, "Sent Null " + intent, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(FuzzerActivity.this, R.string.send_faild, Toast.LENGTH_LONG).show();
}
}
}
});
fuzzAllSeBtn.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View v) {
for(ComponentName cmpName : components){
Intent intent = new Intent();
intent.setComponent(cmpName);
intent.putExtra("test", new SerializableTest());
if (sendIntentByType(intent, currentType)) {
Toast.makeText(FuzzerActivity.this, "Sent Serializeable " + intent, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(FuzzerActivity.this, R.string.send_faild, Toast.LENGTH_LONG).show();
}
}
}
});
}
发送请求
private boolean sendIntentByType(Intent intent, String type) {
try {
switch (ipcNamesToTypes.get(type)) {
case Utils.ACTIVITIES:
startActivity(intent);
return true;
case Utils.RECEIVERS:
sendBroadcast(intent);
return true;
case Utils.SERVICES:
startService(intent);
return true;
default:
return true;
}
} catch (Exception e) {
//e.printStackTrace();
return false;
}
}