1. 本app內部使用的activity一定要設置為非公開
不准備對外公開的activity一定要設置為非公開,以防止被人非法調用
[html]
<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;
}
}
}
4. 發給Activity的intent不要設定為FLAG_ACTIVITY_NEW_TASK
就算上面的Activity的lauchMode設置完善了, 在打開intent的時候還是能指定打開模式。
比如在intent中指明用FLAG_ACTIVITY_NEW_TASK模式的話,發現該activity不存在的話,就會強制新建一個task。如果同時設置了FLAG_ACTIVITY_MULTIPLE_TASK+ FLAG_ACTIVITY_NEW_TASK,就無論如何都會生成新的task,該Activity就會變成rootActiviy,並且intent會被留成履歷
5. 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());
}
}
}
9. Intent數據遺漏到LogCat的可能性
如果像如下代碼,那Intent中發送的數據就會被自動寫入LogCat
[java]
Uri uri = Uri.parse("mailto:
[email protected]");
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
startActivity(intent);
Uri uri = Uri.parse("mailto:
[email protected]");
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[] {"
[email protected]"});
startActivity(intent);
Uri uri = Uri.parse("mailto:");
Intent intent = new Intent(Intent.ACTION_SENDTO, uri);
intent.putExtra(Intent.EXTRA_EMAIL, new String[] {"
[email protected]"});
startActivity(intent);
#以上#