Android 安全测试
文章Android Security Tools对1~4的使用有介绍,下面主要分析其源码实现。
View Codepublic void onCreate(Bundle savedInstanceState) { ... Map<String, PackageInfo> packages = new HashMap<String, PackageInfo>(); PackageManager pm = getPackageManager(); List<PackageInfo> l = pm.getInstalledPackages(0xFFFFFFFF); ...
函数getInstalledPackages(int)参数有如下取值:
flags | Value |
GET_ACTIVITIES | 0x00000001 |
GET_GIDS | 0x00000100 |
GET_CONFIGURATIONS | 0x00004000 |
GET_INSTRUMENTATION | 0x00000010 |
GET_PERMISSIONS | 0x00001000 |
GET_PROVIDERS | 0x00000008 |
GET_RECEIVERS | 0x00000002 |
GET_SERVICES | 0x00000004 |
GET_SIGNATURES | 0x00000040 |
GET_UNINSTALLED_PACKAGES | 0x00002000 |
为oxFFFFFFFF是由于Android此函数实现对flags取“&”,不能包括GET_META_DATA,可取512或14111。详见ApplicationPackageManager.java
View Code@SuppressWarnings("unchecked") @Override public List<PackageInfo> getInstalledPackages(int flags) { return getInstalledPackages(flags, mContext.getUserId()); } /** @hide */ @Override public List<PackageInfo> getInstalledPackages(int flags, int userId) { try { ParceledListSlice<PackageInfo> slice = mPM.getInstalledPackages(flags, userId); return slice.getList(); } catch (RemoteException e) { throw new RuntimeException("Package manager has died", e); } }
View Code@Override public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId) { final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0; enforceCrossUserPermission(Binder.getCallingUid(), userId, true, "get installed packages"); // writer synchronized (mPackages) { ArrayList<PackageInfo> list; if (listUninstalled) { list = new ArrayList<PackageInfo>(mSettings.mPackages.size()); for (PackageSetting ps : mSettings.mPackages.values()) { PackageInfo pi; if (ps.pkg != null) { pi = generatePackageInfo(ps.pkg, flags, userId); } else { pi = generatePackageInfoFromSettingsLPw(ps.name, flags, userId); } if (pi != null) { list.add(pi); } } } else { list = new ArrayList<PackageInfo>(mPackages.size()); for (PackageParser.Package p : mPackages.values()) { PackageInfo pi = generatePackageInfo(p, flags, userId); if (pi != null) { list.add(pi); } } } return new ParceledListSlice<PackageInfo>(list); } } PackageInfo generatePackageInfo(PackageParser.Package p, int flags, int userId) { if (!sUserManager.exists(userId)) return null; PackageInfo pi; final PackageSetting ps = (PackageSetting) p.mExtras; if (ps == null) { return null; } final GrantedPermissions gp = ps.sharedUser != null ? ps.sharedUser : ps; final PackageUserState state = ps.readUserState(userId); return PackageParser.generatePackageInfo(p, gp.gids, flags, ps.firstInstallTime, ps.lastUpdateTime, gp.grantedPermissions, state, userId); } private PackageInfo generatePackageInfoFromSettingsLPw(String packageName, int flags, int userId) { if (!sUserManager.exists(userId)) return null; PackageSetting ps = mSettings.mPackages.get(packageName); if (ps != null) { PackageParser.Package pkg = ps.pkg; if (pkg == null) { if ((flags & PackageManager.GET_UNINSTALLED_PACKAGES) == 0) { return null; } pkg = new PackageParser.Package(packageName); pkg.applicationInfo.packageName = packageName; pkg.applicationInfo.flags = ps.pkgFlags | ApplicationInfo.FLAG_IS_DATA_ONLY; pkg.applicationInfo.publicSourceDir = ps.resourcePathString; pkg.applicationInfo.sourceDir = ps.codePathString; pkg.applicationInfo.dataDir = getDataPathForPackage(packageName, 0).getPath(); pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString; } // pkg.mSetEnabled = ps.getEnabled(userId); // pkg.mSetStopped = ps.getStopped(userId); return generatePackageInfo(pkg, flags, userId); } return null; }
View Codepublic static PackageInfo generatePackageInfo(PackageParser.Package p, int gids[], int flags, long firstInstallTime, long lastUpdateTime, HashSet<String> grantedPermissions, PackageUserState state, int userId) { if (!checkUseInstalled(flags, state)) { return null; } PackageInfo pi = new PackageInfo(); pi.packageName = p.packageName; pi.versionCode = p.mVersionCode; pi.versionName = p.mVersionName; pi.sharedUserId = p.mSharedUserId; pi.sharedUserLabel = p.mSharedUserLabel; pi.applicationInfo = generateApplicationInfo(p, flags, state, userId); pi.installLocation = p.installLocation; if ((pi.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) != 0 || (pi.applicationInfo.flags&ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { pi.requiredForAllUsers = p.mRequiredForAllUsers; } pi.restrictedAccountType = p.mRestrictedAccountType; pi.requiredAccountType = p.mRequiredAccountType; pi.firstInstallTime = firstInstallTime; pi.lastUpdateTime = lastUpdateTime; if ((flags&PackageManager.GET_GIDS) != 0) { pi.gids = gids; } if ((flags&PackageManager.GET_CONFIGURATIONS) != 0) { int N = p.configPreferences.size(); if (N > 0) { pi.configPreferences = new ConfigurationInfo[N]; p.configPreferences.toArray(pi.configPreferences); } N = p.reqFeatures != null ? p.reqFeatures.size() : 0; if (N > 0) { pi.reqFeatures = new FeatureInfo[N]; p.reqFeatures.toArray(pi.reqFeatures); } } if ((flags&PackageManager.GET_ACTIVITIES) != 0) { int N = p.activities.size(); if (N > 0) { if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { pi.activities = new ActivityInfo[N]; } else { int num = 0; for (int i=0; i<N; i++) { if (p.activities.get(i).info.enabled) num++; } pi.activities = new ActivityInfo[num]; } for (int i=0, j=0; i<N; i++) { final Activity activity = p.activities.get(i); if (activity.info.enabled || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { pi.activities[j++] = generateActivityInfo(p.activities.get(i), flags, state, userId); } } } } if ((flags&PackageManager.GET_RECEIVERS) != 0) { int N = p.receivers.size(); if (N > 0) { if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { pi.receivers = new ActivityInfo[N]; } else { int num = 0; for (int i=0; i<N; i++) { if (p.receivers.get(i).info.enabled) num++; } pi.receivers = new ActivityInfo[num]; } for (int i=0, j=0; i<N; i++) { final Activity activity = p.receivers.get(i); if (activity.info.enabled || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { pi.receivers[j++] = generateActivityInfo(p.receivers.get(i), flags, state, userId); } } } } if ((flags&PackageManager.GET_SERVICES) != 0) { int N = p.services.size(); if (N > 0) { if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { pi.services = new ServiceInfo[N]; } else { int num = 0; for (int i=0; i<N; i++) { if (p.services.get(i).info.enabled) num++; } pi.services = new ServiceInfo[num]; } for (int i=0, j=0; i<N; i++) { final Service service = p.services.get(i); if (service.info.enabled || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { pi.services[j++] = generateServiceInfo(p.services.get(i), flags, state, userId); } } } } if ((flags&PackageManager.GET_PROVIDERS) != 0) { int N = p.providers.size(); if (N > 0) { if ((flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { pi.providers = new ProviderInfo[N]; } else { int num = 0; for (int i=0; i<N; i++) { if (p.providers.get(i).info.enabled) num++; } pi.providers = new ProviderInfo[num]; } for (int i=0, j=0; i<N; i++) { final Provider provider = p.providers.get(i); if (provider.info.enabled || (flags&PackageManager.GET_DISABLED_COMPONENTS) != 0) { pi.providers[j++] = generateProviderInfo(p.providers.get(i), flags, state, userId); } } } } if ((flags&PackageManager.GET_INSTRUMENTATION) != 0) { int N = p.instrumentation.size(); if (N > 0) { pi.instrumentation = new InstrumentationInfo[N]; for (int i=0; i<N; i++) { pi.instrumentation[i] = generateInstrumentationInfo( p.instrumentation.get(i), flags); } } } if ((flags&PackageManager.GET_PERMISSIONS) != 0) { int N = p.permissions.size(); if (N > 0) { pi.permissions = new PermissionInfo[N]; for (int i=0; i<N; i++) { pi.permissions[i] = generatePermissionInfo(p.permissions.get(i), flags); } } N = p.requestedPermissions.size(); if (N > 0) { pi.requestedPermissions = new String[N]; pi.requestedPermissionsFlags = new int[N]; for (int i=0; i<N; i++) { final String perm = p.requestedPermissions.get(i); pi.requestedPermissions[i] = perm; if (p.requestedPermissionsRequired.get(i)) { pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_REQUIRED; } if (grantedPermissions != null && grantedPermissions.contains(perm)) { pi.requestedPermissionsFlags[i] |= PackageInfo.REQUESTED_PERMISSION_GRANTED; } } } } if ((flags&PackageManager.GET_SIGNATURES) != 0) { int N = (p.mSignatures != null) ? p.mSignatures.length : 0; if (N > 0) { pi.signatures = new Signature[N]; System.arraycopy(p.mSignatures, 0, pi.signatures, 0, N); } } return pi; }
回到此apk的源文件ViewPackage.java
View Code@Override protected void onCreate(Bundle savedInstanceState) { ... Intent startingIntent = this.getIntent(); try { ArrayList<String> toShow = startingIntent.getExtras() .getStringArrayList("pkgs"); /* * Allow starting of activities when we are in single package view * mode, and give package count when not exactly one. */ if (toShow.size() != 1) mOut.append("Received " + toShow.size() + " package names\n"); else setupActionStuff(pm.getPackageInfo(toShow.get(0), 0xFFFFFFFF)); for (String cur : toShow) { mOut.append(describePackage(packages.get(cur))); } ... void initControls() { ... mStartButton.setOnClickListener(new OnClickListener() { public void onClick(View v) { startCurrentlySelected(); } }); ... /** * Populates the activity selection spinner and activates start button if * needed. */ public void setupActionStuff(PackageInfo p) { showActivityUI(false); mActivitiesList.clear(); mPkgName = null; // enable system and manifest views as this is an individual package mSystemViewButton.setVisibility(View.VISIBLE); try { getPackageManager().getPackageGids( "com.isecpartners.android.manifestexplorer"); mManifestButton.setVisibility(View.VISIBLE); } catch (NameNotFoundException e) { mManifestButton.setVisibility(View.GONE); } if (null == p || null == p.activities) return; mPkgName = p.packageName; for (ActivityInfo ai : p.activities) if (ai.exported) mActivitiesList.add(ai.name); ArrayAdapter<String> AA = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_dropdown_item, mActivitiesList); mActivities.setAdapter(AA); if (mActivities.getCount() > 0) showActivityUI(true); } /** * Looks at the spinner for the currently selected Activity, then starts it. */ public void startCurrentlySelected() { Intent i = new Intent(); i.setComponent(new ComponentName(mPkgName, mActivities .getSelectedItem().toString())); startActivity(i); }
IntentSniffer.java
View Code// classes that have broadcast actions, this list is incomplete (a start). // These classes are not all visible to SDK, so loading them at runtime for // reflection on the actual device. public String[] actionHarboringClasses = { "android.content.Intent", "android.bluetooth.BluetoothIntent", "android.bluetooth.BluetoothA2dp", // Application specific, and would be dangerous to load (as their // static initializer might run etc.) // "com.android.mms.transaction.MessageStatusReceiver", // "com.android.mms.transaction.SmsReceiverService", // "com.android.internal.telephony.gsm.stk.AppInterface", "com.android.internal.location.GpsLocationProvider", "com.android.internal.telephony.TelephonyIntents", "android.provider.Telephony.Intents", "android.proivder.Contacts.Intents", "com.android.mms.util.RateController", "android.media.AudioManager", "android.net.wifi.WifiManager", "android.telephony.TelephonyManager", "android.appwidget.AppWidgetManager", "android.net.ConnectivityManager" }; /* * Grabs the recent tasks from the ActivityManager. */ protected void updateWithRecents() { ActivityManager am = (ActivityManager) getSystemService(ACTIVITY_SERVICE); List<RecentTaskInfo> rti = am.getRecentTasks(1000, ActivityManager.RECENT_WITH_EXCLUDED); StringBuffer log = new StringBuffer(); int count = 0; for (RecentTaskInfo c : rti) { count++; Intent cur = c.baseIntent; log.append("received: " + rti.toString() + "\n"); receiveIntent(RECENT_SOURCE, cur); Log.d(TAG, "recent intent added: " + cur.toString() + cur.hashCode()); } } protected void updateKnownCategories() { Set<String> l = new HashSet<String>(); // reflect across whatever Intent class is installed on the user's // system to grab all static "CATEGORY" fields for (Field f : Intent.class.getFields()) { // the Intent class has a bunch of constants named "CATEGORY_*", // grabbing em...cheap eh. if (f.getName().startsWith("CATEGORY") && f.getType() == String.class) { try { l.add((String) f.get(null)); } catch (IllegalAccessException e) { // should "adjust" protection level of field and retry. // but I believe there is no need right now. Maybe a // future constant will be private on some platform. Log.d(TAG, "Access error on: " + f.getName()); } } } mKnownCategories = l.toArray(new String[l.size()]); saveKnownCategories(); } protected void updateKnownActions() { Set<String> l = new HashSet<String>(); List<Class> classes = new ArrayList<Class>(); for (String cur : actionHarboringClasses) try { classes.add(Class.forName(cur)); } catch (ClassNotFoundException cne) { Log.e(TAG, "missing class " + cne.getMessage()); } for (Class c : classes) for (Field f : c.getFields()) { // actions constants tend to start or end with ACTION if ((f.getName().startsWith("ACTION") || f.getName().endsWith( "ACTION")) && f.getType() == String.class) { try { l.add((String) f.get(null)); } catch (Exception e) { // should "adjust" protection level of field and retry. Log.d(TAG, "Access error on: " + c.getName() + " : " + f.getName() + "" + e.getMessage()); } } } this.mNumReflected = l.size(); findMoreActions(l); mKnownBroadcastActions = l.toArray(new String[l.size()]); saveKnownActions(); } protected void findMoreActions(Set<String> l) { // walk the XML registrations for every package on the system looking // for places where broadcast receivers are registered. for (PackageInfo pi : getPackageManager().getInstalledPackages( PackageManager.GET_DISABLED_COMPONENTS)) { try { XmlResourceParser x = createPackageContext(pi.packageName, 0) .getAssets().openXmlResourceParser( "AndroidManifest.xml"); int eventType = x.getEventType(); // looking for receiver tag, containing action tag(s), contains // a name attribute has a value that is the known action. int state = 0; // 0 out, 1 in receiver String n; while (eventType != XmlPullParser.END_DOCUMENT) { switch (eventType) { case XmlPullParser.START_TAG: n = x.getName(); n = (null == n) ? "" : n.toLowerCase(); if (n.equals("receiver")) state = 1; else if (1 == state && n.equals("action")) for (int i = 0; i < x.getAttributeCount(); i++) if (x.getAttributeName(i).equalsIgnoreCase( "name")) l.add(x.getAttributeValue(i)); break; case XmlPullParser.END_TAG: n = x.getName(); n = (null == n) ? "" : n.toLowerCase(); if (n.equals("receiver")) state = 0; break; } eventType = x.nextToken(); } } catch (IOException ioe) { Log.e(TAG, "IOException opening package: " + pi.packageName); } catch (NameNotFoundException name) { Log.e(TAG, "NameNotFoundException opening package: " + pi.packageName); } catch (XmlPullParserException e) { Log.e(TAG, "Exception reading manifest XML for package: " + pi.packageName); } } mNumDug = l.size() - mNumReflected; }
IntentFuzzer.java
View Code /** * For any type, provide the registered instances based on what the package * manager has on file. Only provide exported components. * * @param type * IPC requested, activity, broadcast, etc. * @return */ protected ArrayList<ComponentName> getExportedComponents(IPCType type) { ArrayList<ComponentName> found = new ArrayList<ComponentName>(); PackageManager pm = getPackageManager(); for (PackageInfo pi : pm .getInstalledPackages(PackageManager.GET_DISABLED_COMPONENTS | PackageManager.GET_ACTIVITIES | PackageManager.GET_RECEIVERS | PackageManager.GET_INSTRUMENTATION | PackageManager.GET_PROVIDERS | PackageManager.GET_SERVICES)) { PackageItemInfo items[] = null; switch (type) { case ACTIVITIES: items = pi.activities; break; case BROADCASTS: items = pi.receivers; break; case SERVICES: items = pi.services; break; case PROVIDERS: items = pi.providers; break; case INSTRUMENTATIONS: items = pi.instrumentation; } if (items != null) for (PackageItemInfo pii : items) found.add(new ComponentName(pi.packageName, pii.name)); } return found; } protected int fuzzAllBroadcasts(List<ComponentName> comps) { int count = 0; for (int i = 0; i < comps.size(); i++) { Intent in = new Intent(); in.setComponent(comps.get(i)); sendBroadcast(in); count++; } return count; } protected int fuzzAllServices(List<ComponentName> comps) { int count = 0; for (int i = 0; i < comps.size(); i++) { Intent in = new Intent(); in.setComponent(comps.get(i)); try { startService(in); } catch (Exception e) { mOut.append("Can't launch " + comps.get(i) + "" + e.getMessage() + "\n"); } count++; } return count; } protected boolean sendIntentByType(Intent i, IPCType t) { try { switch (t) { case ACTIVITIES: startActivity(i); return true; case BROADCASTS: sendBroadcast(i); return true; case SERVICES: startService(i); // stopping these might be nice too return true; case PROVIDERS: // uh - providers don't use Intents...what am I doing... Toast.makeText(this, "Proivders don't use Intents, ignore this setting.", Toast.LENGTH_SHORT).show(); return false; case INSTRUMENTATIONS: Toast .makeText( this, "Instrumentations aren't Intent based... starting Instrumentation.", Toast.LENGTH_SHORT).show(); startInstrumentation(i.getComponent(), null, null); // not // intent based you could fuzz these params, if anyone cared. return true; } } catch (Exception e) { return false; } return false; } protected Intent fuzzBroadcast(ComponentName toTest) { Intent i = new Intent(); i.setComponent(toTest); sendBroadcast(i); return i; }
5.drozer(原名Mercury)用作Android平台上动态分析的安全测试框架,其源码由Python编写,它能安装在Windows & Linux上,详见其用户指南,其研发公司MWR InfoSecurity还提供了Android 安全研究。
6.androguard用Python编写,目前只能在Linux上安装、使用,它安装时需要较多第三方库的支持,幻灯片Android-OEM-applications-insecurity-and-backdoors-without-permission用此工具作了分析。
7.DroidBox用Python编写,目前只能运行在Linux和Mac上,它用作对Android应用动态分析。
8.查看apk签名:
jarsigner -verify -verbose -certs apk_name.apk
9.Apk相关文件中如congfig、未加密的db中无工号、用户个人数据和系统数据、万能码之类内容、账户密码和敏感数据不要明文存储
10.Apk网络传输时不能泄露数据、不能有影响安全的开发端口,测试工具参考文章Android工具tcpdump和Nmap
11.Apk运行时Logcat输出不必要的信息
12.APK相关文件权限other组权限为---或--x,比如不含w权限:
-rw-rw--w- :data/data/com.snda.cloudary/shared_prefs/UserInfo.xml …该文件的这个权限不安全
13.Santoku
14.用至少两种杀毒软件对发布产品进行查杀
15.禁用私有加密算法和业界已知不安全加密算法,需解决兼容或标准协议规定的除外。目前较安全的加密算法为:
3DES 128及以上
AES 128及以上
RSA 1024及以上
SHA2 256及以上
HMAC-SHA2 128及以上
其它可见幻灯片Android软件安全攻防研究现状,Android漏洞信息库,Security Tips。
16.APK禁止申请不必要权限