1. 本app内部使用的activity一定要设置为非公开
不准备对外公开的activity一定要设置为非公开,以防止被人非法调用
- <activity
- android:name=".PrivateActivity"
- android:label="@string/app_name"
- android:exported="false" />
- <activity
- android:name=".PrivateActivity"
- android:label="@string/app_name"
- android:exported="false" />
同时,一定要注意的是, 非公开的Activity不能设置intent-filter
因为,如果假设在同一机器上,有另外一个app有同样的intent-filter的话, 调用该Activity的intent会唤醒android的选择画面, 让你选择使用那个app接受该intent。这样就会事实上绕过了非公开的设置。
2. 不要指定taskAffinity
Android中的activity全都归属于task管理 , 简单说来task是一种stack的数据结构, 先入后出。
一般来说, 如果不指明归属于什么task, 同一个app内部的所有Activity都会存续在一个task中,task的名字就是app的packageName。
因为在同一个andorid设备中,不会有两个同packageName的app存在,所以能保证Activity不被攻击。
但是如果你指明taskAffinity,比如如下
- [html]
- <application android:icon="@drawable/icon" android:label="@string/app_name">
- <activity android:name=".Activity1"
- android:taskAffinity="com.winuxxan.task"
- android:label="@string/app_name">
- </activity>
- <activity android:name=".Activity2">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
- <application android:icon="@drawable/icon" android:label="@string/app_name">
- <activity android:name=".Activity1"
- android:taskAffinity="com.winuxxan.task"
- android:label="@string/app_name">
- </activity>
- <activity android:name=".Activity2">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
那此时,恶意软件中的Activity如果也声明为同样的taskAffinity,那他的Activity就会启动到你的task中,就会有机会拿到你的intent
3. 不要指定LaunchMode(默认standard模式)
Android中Activity的LaunchMode分成 以下四种
Standard: 这种方式打开的Activity不会被当作rootActivity,会生成一个新的Activity的instance,会和打开者在同一个task内
singleTop: 和standard基本一样,唯一的区别在于如果当前task第一个Activity就是该Activity的话,就不会生成新的instance
singleTask:系统会创建一个新task(如果没有启动应用)和一个activity新实例在新task根部,然后,如果activity实例已经存在单独的task中,系统会调用已经存在activity的 onNewIntent()方法,而不是存在新实例,仅有一个activity实例同时存在。
singleInstance: 和singleTask相似,除了系统不会让其他的activities运行在所有持有的task实例中,这个activity是独立的,并且task中的成员只有它,任何其他activities运行这个activity都将打开一个独立的task。
所有发送给root Activity(根Activiy)的intent都会在android中留下履历。所以一般来说严禁用singleTask或者singleInstance来启动画面。
然而,即使用了standard来打开画面,也可能会出问题,比如如果调用者的Activity是用singleInstance模式打开,即使用standard模式打开被调用Activity,因为调用者的Activitytask是不能有其他task的, 所以android会被迫生成一个新的task,并且把被调用者塞进去,最后被调用者就成了rootActivity。
程序如下:
- AndroidManifest.xml
- [html]
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.jssec.android.activity.privateactivity"
- android:versionCode="1"
- android:versionName="1.0" >
- <uses-sdk android:minSdkVersion="8" />
- <application
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name" >
- <!—root Activity以”singleInstance”模式启动 -->
- <!—不设置taskAffinity-->
- <activity
- android:name=".PrivateUserActivity"
- android:label="@string/app_name"
- android:launchMode="singleInstance" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <!-- 非公開Activity -->
- <!—启动模式为”standard” -->
- <!—不设置taskAffinity-->
- <activity
- android:name=".PrivateActivity"
- android:label="@string/app_name"
- android:exported="false" />
- </application>
- </manifest>
- <?xml version="1.0" encoding="utf-8"?>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.jssec.android.activity.privateactivity"
- android:versionCode="1"
- android:versionName="1.0" >
- <uses-sdk android:minSdkVersion="8" />
- <application
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name" >
- <!—root Activity以”singleInstance”模式启动 -->
- <!—不设置taskAffinity-->
- <activity
- android:name=".PrivateUserActivity"
- android:label="@string/app_name"
- android:launchMode="singleInstance" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- <!-- 非公開Activity -->
- <!—启动模式为”standard” -->
- <!—不设置taskAffinity-->
- <activity
- android:name=".PrivateActivity"
- android:label="@string/app_name"
- android:exported="false" />
- </application>
- </manifest>
非公开Activity的代码如下:
- [java]
- package org.jssec.android.activity.privateactivity;
- import android.app.Activity;
- import android.content.Intent;
- import android.os.Bundle;
- import android.view.View;
- import android.widget.Toast;
- public class PrivateActivity extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.private_activity);
- String param = getIntent().getStringExtra("PARAM");
- Toast.makeText(this, String.format("「%s」取得。", param),
- Toast.LENGTH_LONG).show();
- }
- public void onReturnResultClick(View view) {
- Intent intent = new Intent();
- intent.putExtra("RESULT", 机密数据");
- setResult(RESULT_OK, intent);
- finish();
- }
- }
- package org.jssec.android.activity.privateactivity;
- import android.app.Activity;
- import android.content.Intent;
- import android.os.Bundle;
- import android.view.View;
- import android.widget.Toast;
- public class PrivateActivity extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.private_activity);
- String param = getIntent().getStringExtra("PARAM");
- Toast.makeText(this, String.format("「%s」取得。", param),
- Toast.LENGTH_LONG).show();
- }
- public void onReturnResultClick(View view) {
- Intent intent = new Intent();
- intent.putExtra("RESULT", 机密数据");
- setResult(RESULT_OK, intent);
- finish();
- }
- }
调用非公开Activity者,以standard模式打开
- [java]
- package org.jssec.android.activity.privateactivity;
- import android.app.Activity;
- import android.content.Intent;
- import android.os.Bundle;
- import android.view.View;
- import android.widget.Toast;
- public class PrivateUserActivity extends Activity {
- private static final int REQUEST_CODE = 1;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.user_activity);
- }
- public void onUseActivityClick(View view) {
- // 用standard模式启动非公开Activity
- Intent intent = new Intent();
- intent.setClass(this, PrivateActivity.class);
- intent.putExtra("PARAM", "机密数据");
- startActivityForResult(intent, REQUEST_CODE);
- }
- @Override
- public void onActivityResult(int requestCode, int resultCode,
- Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (resultCode != RESULT_OK)
- return;
- switch (requestCode) {
- case REQUEST_CODE:
- String result = data.getStringExtra("RESULT");
- break;
- }
- }
- }
- package org.jssec.android.activity.privateactivity;
- import android.app.Activity;
- import android.content.Intent;
- import android.os.Bundle;
- import android.view.View;
- import android.widget.Toast;
- public class PrivateUserActivity extends Activity {
- private static final int REQUEST_CODE = 1;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.user_activity);
- }
- public void onUseActivityClick(View view) {
- // 用standard模式启动非公开Activity
- Intent intent = new Intent();
- intent.setClass(this, PrivateActivity.class);
- intent.putExtra("PARAM", "机密数据");
- startActivityForResult(intent, REQUEST_CODE);
- }
- @Override
- public void onActivityResult(int requestCode, int resultCode,
- Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (resultCode != RESULT_OK)
- return;
- switch (requestCode) {
- case REQUEST_CODE:
- String result = data.getStringExtra("RESULT");
- break;
- }
- }
- }
就算上面的Activity的lauchMode设置完善了, 在打开intent的时候还是能指定打开模式。
比如在intent中指明用FLAG_ACTIVITY_NEW_TASK模式的话,发现该activity不存在的话,就会强制新建一个task。如果同时设置了FLAG_ACTIVITY_MULTIPLE_TASK+ FLAG_ACTIVITY_NEW_TASK,就无论如何都会生成新的task,该Activity就会变成rootActiviy,并且intent会被留成履历
Activity中数据的传递都依靠intent, 很容易被攻击, 所以 就算同一个app内部传递数据, 最好还是要加密, 加密算法很多
6. 明确ActivityName发送Intent
明确Activity发送Intent,能够避免被恶意软件截取。
同一app内部的发送
- [java]
- Intent intent = new Intent(this, PictureActivity.class);
- intent.putExtra("BARCODE", barcode);
- startActivity(intent);
- Intent intent = new Intent(this, PictureActivity.class);
- intent.putExtra("BARCODE", barcode);
- startActivity(intent);
- 不同app内部的发送
- [java]
- Intent intent = new Intent();
- intent.setClassName(
- "org.jssec.android.activity.publicactivity",
- "org.jssec.android.activity.publicactivity.PublicActivity");
- startActivity(intent);
- Intent intent = new Intent();
- intent.setClassName(
- "org.jssec.android.activity.publicactivity",
- "org.jssec.android.activity.publicactivity.PublicActivity");
- startActivity(intent);
但是,要注意的是!
不是指明了packageName和ActivityName就能避免所有的问题,
如果有一个恶意软件故意做成和你发送目标同packageName, 同ActivityName, 此时的intent就会被截取
7. 跨app接受Intent时,要明确对方的身份
接受到别的app发来的intent时,要能确定对方的身份。
一个好方法是比对对方的app的hashcode。
当前,前提是调用者要用startActivityForResult(),因为只有这个方法,被调用者才能得到调用者的packageName
代码如下:
被调用的Activity
- [java]
- package org.jssec.android.activity.exclusiveactivity;
- import org.jssec.android.shared.PkgCertWhitelists;
- import org.jssec.android.shared.Utils;
- import android.app.Activity;
- import android.content.Context;
- import android.content.Intent;
- import android.os.Bundle;
- import android.view.View;
- import android.widget.Toast;
- public class ExclusiveActivity extends Activity {
- // hashcode的白名单
- private static PkgCertWhitelists sWhitelists = null;
- private static void buildWhitelists(Context context) {
- boolean isdebug = Utils.isDebuggable(context);
- sWhitelists = new PkgCertWhitelists();
- sWhitelists
- .add("org.jssec.android.activity.exclusiveuser", isdebug ?
- "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"
- :
- "1F039BB5 7861C27A 3916C778 8E78CE00 690B3974 3EB8259F E2627B8D 4C0EC35A");
- }
- private static boolean checkPartner(Context context, String pkgname) {
- if (sWhitelists == null)
- buildWhitelists(context);
- return sWhitelists.test(context, pkgname);
- }
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- // check白名单
- if (!checkPartner(this, getCallingPackage())) {
- Toast.makeText(this, "不是白名单内部的。", Toast.LENGTH_LONG).show();
- finish();
- return;
- }
- }
- public void onReturnResultClick(View view) {
- Intent intent = new Intent();
- intent.putExtra("RESULT", "机密数据");
- setResult(RESULT_OK, intent);
- finish();
- }
- }
- package org.jssec.android.activity.exclusiveactivity;
- import org.jssec.android.shared.PkgCertWhitelists;
- import org.jssec.android.shared.Utils;
- import android.app.Activity;
- import android.content.Context;
- import android.content.Intent;
- import android.os.Bundle;
- import android.view.View;
- import android.widget.Toast;
- public class ExclusiveActivity extends Activity {
- // hashcode的白名单
- private static PkgCertWhitelists sWhitelists = null;
- private static void buildWhitelists(Context context) {
- boolean isdebug = Utils.isDebuggable(context);
- sWhitelists = new PkgCertWhitelists();
- sWhitelists
- .add("org.jssec.android.activity.exclusiveuser", isdebug ?
- "0EFB7236 328348A9 89718BAD DF57F544 D5CCB4AE B9DB34BC 1E29DD26 F77C8255"
- :
- "1F039BB5 7861C27A 3916C778 8E78CE00 690B3974 3EB8259F E2627B8D 4C0EC35A");
- }
- private static boolean checkPartner(Context context, String pkgname) {
- if (sWhitelists == null)
- buildWhitelists(context);
- return sWhitelists.test(context, pkgname);
- }
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- // check白名单
- if (!checkPartner(this, getCallingPackage())) {
- Toast.makeText(this, "不是白名单内部的。", Toast.LENGTH_LONG).show();
- finish();
- return;
- }
- }
- public void onReturnResultClick(View view) {
- Intent intent = new Intent();
- intent.putExtra("RESULT", "机密数据");
- setResult(RESULT_OK, intent);
- finish();
- }
- } [java]
- PkgCertWhitelists.java
- [java]
- package org.jssec.android.shared;
- import java.util.HashMap;
- import java.util.Map;
- import android.content.Context;
- public class PkgCertWhitelists {
- private Map<String, String> mWhitelists = new HashMap<String, String>();
- public boolean add(String pkgname, String sha256) {
- if (pkgname == null)
- return false;
- if (sha256 == null)
- return false;
- sha256 = sha256.replaceAll(" ", "");
- if (sha256.length() != 64)
- return false;
- sha256 = sha256.toUpperCase();
- if (sha256.replaceAll("[0-9A-F]+", "").length() != 0)
- return false;
- mWhitelists.put(pkgname, sha256);
- return true;
- }
- public boolean test(Context ctx, String pkgname) {
- String correctHash = mWhitelists.get(pkgname);
- return PkgCert.test(ctx, pkgname, correctHash);
- }
- }
- package org.jssec.android.shared;
- import java.util.HashMap;
- import java.util.Map;
- import android.content.Context;
- public class PkgCertWhitelists {
- private Map<String, String> mWhitelists = new HashMap<String, String>();
- public boolean add(String pkgname, String sha256) {
- if (pkgname == null)
- return false;
- if (sha256 == null)
- return false;
- sha256 = sha256.replaceAll(" ", "");
- if (sha256.length() != 64)
- return false;
- sha256 = sha256.toUpperCase();
- if (sha256.replaceAll("[0-9A-F]+", "").length() != 0)
- return false;
- mWhitelists.put(pkgname, sha256);
- return true;
- }
- public boolean test(Context ctx, String pkgname) {
- String correctHash = mWhitelists.get(pkgname);
- return PkgCert.test(ctx, pkgname, correctHash);
- }
- }
- PkgCert.java
- [java]
- package org.jssec.android.shared;
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
- import android.content.Context;
- import android.content.pm.PackageInfo;
- import android.content.pm.PackageManager;
- import android.content.pm.PackageManager.NameNotFoundException;
- import android.content.pm.Signature;
- public class PkgCert {
- public static boolean test(Context ctx, String pkgname,
- String correctHash) {
- if (correctHash == null)
- return false;
- correctHash = correctHash.replaceAll(" ", "");
- return correctHash.equals(hash(ctx, pkgname));
- }
- public static String hash(Context ctx, String pkgname) {
- if (pkgname == null)
- return null;
- try {
- PackageManager pm = ctx.getPackageManager();
- PackageInfo pkginfo = pm.getPackageInfo(pkgname,
- PackageManager.GET_SIGNATURES);
- if (pkginfo.signatures.length != 1)
- return null;
- Signature sig = pkginfo.signatures[0];
- byte[] cert = sig.toByteArray();
- byte[] sha256 = computeSha256(cert);
- return byte2hex(sha256);
- } catch (NameNotFoundException e) {
- return null;
- }
- }
- private static byte[] computeSha256(byte[] data) {
- try {
- return MessageDigest.getInstance("SHA-256").digest(data);
- } catch (NoSuchAlgorithmException e) {
- return null;
- }
- }
- private static String byte2hex(byte[] data) {
- if (data == null)
- return null;
- final StringBuilder hexadecimal = new StringBuilder();
- for (final byte b : data) {
- hexadecimal.append(String.format("%02X", b));
- }
- return hexadecimal.toString();
- }
- }
- package org.jssec.android.shared;
- import java.security.MessageDigest;
- import java.security.NoSuchAlgorithmException;
- import android.content.Context;
- import android.content.pm.PackageInfo;
- import android.content.pm.PackageManager;
- import android.content.pm.PackageManager.NameNotFoundException;
- import android.content.pm.Signature;
- public class PkgCert {
- public static boolean test(Context ctx, String pkgname,
- String correctHash) {
- if (correctHash == null)
- return false;
- correctHash = correctHash.replaceAll(" ", "");
- return correctHash.equals(hash(ctx, pkgname));
- }
- public static String hash(Context ctx, String pkgname) {
- if (pkgname == null)
- return null;
- try {
- PackageManager pm = ctx.getPackageManager();
- PackageInfo pkginfo = pm.getPackageInfo(pkgname,
- PackageManager.GET_SIGNATURES);
- if (pkginfo.signatures.length != 1)
- return null;
- Signature sig = pkginfo.signatures[0];
- byte[] cert = sig.toByteArray();
- byte[] sha256 = computeSha256(cert);
- return byte2hex(sha256);
- } catch (NameNotFoundException e) {
- return null;
- }
- }
- private static byte[] computeSha256(byte[] data) {
- try {
- return MessageDigest.getInstance("SHA-256").digest(data);
- } catch (NoSuchAlgorithmException e) {
- return null;
- }
- }
- private static String byte2hex(byte[] data) {
- if (data == null)
- return null;
- final StringBuilder hexadecimal = new StringBuilder();
- for (final byte b : data) {
- hexadecimal.append(String.format("%02X", b));
- }
- return hexadecimal.toString();
- }
- }
8. 所有根Activity中的intent都能被所有app共享
所有的app,只要按照如下样子,就能取出这台手机上所有task上所有根Activity接受到的intent
- AndroidManifest.xml
- [html]
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.jssec.android.intent.maliciousactivity"
- android:versionCode="1"
- android:versionName="1.0" >
- <uses-sdk
- android:minSdkVersion="8"
- android:targetSdkVersion="15" />
- <application
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name"
- android:theme="@style/AppTheme" >
- <activity
- android:name=".MaliciousActivity"
- android:label="@string/title_activity_main" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
- <uses-permission android:name="android.permission.GET_TASKS" />
- </manifest>
- <manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="org.jssec.android.intent.maliciousactivity"
- android:versionCode="1"
- android:versionName="1.0" >
- <uses-sdk
- android:minSdkVersion="8"
- android:targetSdkVersion="15" />
- <application
- android:icon="@drawable/ic_launcher"
- android:label="@string/app_name"
- android:theme="@style/AppTheme" >
- <activity
- android:name=".MaliciousActivity"
- android:label="@string/title_activity_main" >
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </activity>
- </application>
- <uses-permission android:name="android.permission.GET_TASKS" />
- </manifest>
- MaliciousActivity.java
- [java]
- package org.jssec.android.intent.maliciousactivity;
- import java.util.List;
- import android.app.Activity;
- import android.app.ActivityManager;
- import android.content.Intent;
- import android.os.Bundle;
- import android.util.Log;
- public class MaliciousActivity extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.malicious_activity);
- ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
- List<ActivityManager.RecentTaskInfo> list = activityManager
- .getRecentTasks(100, ActivityManager.RECENT_WITH_EXCLUDED);
- for (ActivityManager.RecentTaskInfo r : list) {
- Intent intent = r.baseIntent;
- Log.v("baseIntent", intent.toString());
- }
- }
- }
- package org.jssec.android.intent.maliciousactivity;
- import java.util.List;
- import android.app.Activity;
- import android.app.ActivityManager;
- import android.content.Intent;
- import android.os.Bundle;
- import android.util.Log;
- public class MaliciousActivity extends Activity {
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.malicious_activity);
- ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
- List<ActivityManager.RecentTaskInfo> list = activityManager
- .getRecentTasks(100, ActivityManager.RECENT_WITH_EXCLUDED);
- for (ActivityManager.RecentTaskInfo r : list) {
- Intent intent = r.baseIntent;
- Log.v("baseIntent", intent.toString());
- }
- }
- }
如果像如下代码,那Intent中发送的数据就会被自动写入LogCat
- [java]
- Uri uri = Uri.parse("mailto:test@gmail.com");
- Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
- startActivity(intent);
- Uri uri = Uri.parse("mailto:test@gmail.com");
- Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
- startActivity(intent);
- 如果像如下,就能避免
- [java]
- Uri uri = Uri.parse("mailto:");
- Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
- intent.putExtra(Intent.EXTRA_EMAIL, new String[] {"test@gmail.com"});
- startActivity(intent);
- Uri uri = Uri.parse("mailto:");
- Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
- intent.putExtra(Intent.EXTRA_EMAIL, new String[] {"test@gmail.com"});
- startActivity(intent);