protected-broadcast 的一些细节

★ 1. 引言

注:本文中提及的“广播(Broadcast)”,“广播事件”和“Action”的意思大致相同。发送广播(sendBroadcast)也是发送一个指定的action给BroadcastReceiver。在本文中不严格区分“广播”和“Action”,除非有地方特别说明。

对于 android 系统应用来说,运用protected-broadcast是基本的安全要求。
顾名思义,protected-broadcast是保护广播事件(Action)不被滥用的。相对的,如果一个action是不受protected-broadcast保护,并且使用此action的组件(称之为MyReceiver1)没有system或者signature权限保护的话,这时任何app都可以发送此action的广播给MyReceiver1

对于 Android 系统应用来说,用protected-broadcast保护action不被滥用,可以不用声明system或者signature权限去保护receiver。当然,从安全的角度来说,给每一个组件设置适当的system或者signature权限是更好的。

你如果看过 Android 源码,一定在frameworks\base\core\res中的AndroidManifest.xml文件中看到过这样的代码:

    <protected-broadcast android:name="android.intent.action.SCREEN_OFF" />    <protected-broadcast android:name="android.intent.action.SCREEN_ON" />    <protected-broadcast android:name="android.intent.action.USER_PRESENT" />    <protected-broadcast android:name="android.intent.action.TIME_SET" />    <protected-broadcast android:name="android.intent.action.TIME_TICK" />    <protected-broadcast android:name="android.intent.action.TIMEZONE_CHANGED" />    <protected-broadcast android:name="android.intent.action.DATE_CHANGED" />    <protected-broadcast android:name="android.intent.action.PRE_BOOT_COMPLETED" />    <protected-broadcast android:name="android.intent.action.BOOT_COMPLETED" />    <protected-broadcast android:name="android.intent.action.PACKAGE_INSTALL" />    <protected-broadcast android:name="android.intent.action.PACKAGE_ADDED" />(略)

本文就是针对protected-broadcast做一些细节方面的介绍,包括:

  • (1)protected-broadcast的适用范围:哪些应用可以使用这个特性?有系统签名的非系统app,能够使用这个特性吗?

  • (2)Android 系统是如何阻止第三方app发送被 protected-broadcast 保护的广播(Action)的?

  • (3)protected-broadcast 是对Activity(startActivity)、Receiver(sendBroadcast)、Service(startService)都有效吗?

  • (4)如何判断一个action是否是受保护的?

接下来一一说明。

★ 2. protected-broadcast的适用范围:哪些应用可以使用这个特性?有系统签名的非系统app,能够使用这个特性吗?

对于这个问题,我们可以从PackageParser.java入手,从PackageParser解析apk里的AndroidManifest.xml开始。

PackageParser.java 在 frameworks\base\core\java\android\content\pm目录中。

PackageParser.java中定义的常量TAG_PROTECTED_BROADCAST

private static final String TAG_PROTECTED_BROADCAST = "protected-broadcast";

PackageParser的内部类Package中的变量protectedBroadcasts

public ArrayList protectedBroadcasts;

解析 apk 中的 AndroidManifest.xml 的代码在 PackageParser.java 中的parseBaseApkCommon()

   private Package parseBaseApkCommon(Package pkg, Set acceptedTags, Resources res,            XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException,            IOException {            //(略)            } else if (tagName.equals(TAG_PROTECTED_BROADCAST)) {                // 解析标签                // 获取 AndroidManifestProtectedBroadcast 的属性值                // 这里 res 是 Resources res, sa 是 TypedArray sa。                sa = res.obtainAttributes(parser,                        com.android.internal.R.styleable.AndroidManifestProtectedBroadcast);                // getNonResourceString()确保protected broadcast 的name是从xml中得到的,而不能是引用strings.xml中的字符串。                // protected broadcast 的name在编译之后不能改变,除非重新编译。                // AndroidManifestProtectedBroadcast_name是属性值的索引,其值为0。                // name 为获取到的受保护的action名。                String name = sa.getNonResourceString(                        com.android.internal.R.styleable.AndroidManifestProtectedBroadcast_name);                sa.recycle();                if (name != null && (flags & PARSE_IS_SYSTEM) != 0) {                    // PARSE_IS_SYSTEM表明只在解析系统app时,protected broadcast才会有效                    // pkg 是内部类 Package 的实例                    if (pkg.protectedBroadcasts == null) {                        pkg.protectedBroadcasts = new ArrayList();                    }                    if (!pkg.protectedBroadcasts.contains(name)) {                        // 所有的受保护的action都放到protectedBroadcasts                        pkg.protectedBroadcasts.add(name.intern());                    }                }                // 此标签解析完毕,跳到END_TAG:'/>' 或者 ''                XmlUtils.skipCurrentTag(parser);            }            略}

(flags & PARSE_IS_SYSTEM) != 0这个判断可知,可以使用标签的app只能是系统app如果不是系统app,那么此标签将被忽略

哪些app是系统app呢?
有两类:
* 一类是uid为 android.uid.systemandroid.uid.phoneandroid.uid.logandroid.uid.nfcandroid.uid.bluetoothandroid.uid.shell的app是系统app。

  • 另一类是指定目录中的app是系统app,“指定目录”包括/vendor/overlay/system/framework/system/priv-app/system/app/vendor/app/oem/app

解析这两类apk的时候,用到了PARSE_IS_SYSTEM标志位。

关于系统app的更多细节,可以参考 《Android 权限的一些细节》 。

替换系统app(replace)或者升级系统app(upgrade)时,也用到了PARSE_IS_SYSTEM标志位,如果系统app在新版本中增加了也会被处理。

如果安装一个跟系统签名一样的app,不是替换系统app,也不是升级系统app的话,就不会用到PARSE_IS_SYSTEM标志来解析apk,所以在这种情况下,有系统签名但不是系统app,不能使用特性

★ 3. Android 系统是如何阻止第三方app发送protected-broadcast保护的广播(Action)的?

分两个部分:

  • 第一部分:受保护的广播是如何保存到PackageManagerService(简称为PMS)的,即protected-broadcast从系统app到PMS。

  • 第二部分:什么时候检查广播(action)是不是受保护的。

♦ 3.1 受保护的广播是如何保存到PMS的

PackageManagerService.java

Android 7.1.1中大致的流程如下:

-> PackageManagerService()构造方法-> scanDirTracedLI()-> scanDirLI(File dir, ...) {// 为了与Android 8.0代码做对比,这里列出一些细节    final File[] files = dir.listFiles();    for (File file : files) {        scanPackageTracedLI(); // 这里是串行处理,目录dir中的apk是一个接一个解析的    }}-> scanPackageTracedLI((File scanFile, ...)-> scanPackageLI(File scanFile, ...)    -> pp.parsePackage(scanFile, parseFlags);-> scanPackageLI(PackageParser.Package pkg, File scanFile, ...)-> scanPackageInternalLI()-> scanPackageLI(PackageParser.Package pkg, final int policyFlags, ...)-> scanPackageDirtyLI() {   if (pkg.protectedBroadcasts != null) {        N = pkg.protectedBroadcasts.size();        for (i=0; i

Android 8.0中大致的流程如下:

-> PackageManagerService()构造方法-> scanDirTracedLI()-> scanDirLI(File dir, ...) {// 这里采用了并行处理,7.1.1中是串行处理的    final File[] files = dir.listFiles();    ParallelPackageParser parallelPackageParser = new ParallelPackageParser()    for (File file : files) {        // 将解析apk的任务交给多个线程来处理        parallelPackageParser.submit(file, parseFlags);        fileCount++;    }    for (; fileCount > 0; fileCount--) {        // take()取出解析apk的结果,如果没有解析完,则等待        // 哪个apk先解析完,就先处理哪个apk(scanPackageLI())        ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();        Throwable throwable = parseResult.throwable;        if (throwable == null) {// 解析apk无异常            scanPackageLI(parseResult.pkg, parseResult.scanFile, ...);        }    }    parallelPackageParser.close();}-> scanPackageLI(PackageParser.Package pkg, File scanFile, ...)-> scanPackageInternalLI()-> scanPackageLI(File scanFile, int parseFlags, ...)    -> parsePackage() // 这又解析了一次?不知道为什么,没有用之前解析好的(useCaches为false)-> scanPackageLI(PackageParser.Package pkg, final int policyFlags, ...)-> scanPackageDirtyLI()-> commitPackageSettings() {    if (pkg.protectedBroadcasts != null) {        N = pkg.protectedBroadcasts.size();        for (i=0; i

保存到PMS后,PMS提供了一个接口isProtectedBroadcast()供其他应用调用,目前只是AMS在调用。

从下面的代码中可以看到,mProtectedBroadcasts中的action是受保护的,除此之外,某些action名字是受保护的或者说是被禁止的,第三方app不能乱发,例如,以android.net.netmon.lingerExpired开头的action。

    @Override    public boolean isProtectedBroadcast(String actionName) {        synchronized (mPackages) {            if (mProtectedBroadcasts.contains(actionName)) {                return true;            } else if (actionName != null) {                if (actionName.startsWith("android.net.netmon.lingerExpired")                        || actionName.startsWith("com.android.server.sip.SipWakeupTimer")                        || actionName.startsWith("com.android.internal.telephony.data-reconnect")                        || actionName.startsWith("android.net.netmon.launchCaptivePortalApp")) {                    return true;                }            }        }        return false;    }

调用PMS的isProtectedBroadcast()是通过IPackageManager接口。

IPackageManager.aidl    boolean isProtectedBroadcast(String actionName);

♦ 3.2 检查广播(action)是不是受保护的

这是在 ActivityManagerService (简称AMS)中执行的,最终调用的是PMS的接口isProtectedBroadcast()

ActivityManagerService.java
大致的调用过程如下:

-> App中调用sendBroadcast()-> sendBroadcast() @ ContextImpl-> broadcastIntent() @ ActivityManagerService-> broadcastIntentLocked() {    final String action = intent.getAction();    isProtectedBroadcast = AppGlobals.getPackageManager().isProtectedBroadcast(action);    if (!isCallerSystem) {// 如果调用者不是系统app,即调用sendBroadcast()的不是系统app        if (isProtectedBroadcast) {//action是被保护的广播,那么将抛出异常            String msg = "Permission Denial: not allowed to send broadcast "                    + action + " from pid="                    + callingPid + ", uid=" + callingUid;            throw new SecurityException(msg);        }    }

由上面可知,如果调用sendBroadcast()的不是系统app,并且广播action是受保护的,那么将抛出SecurityException异常。

★ 4. protected-broadcast是对Activity(startActivity)、Receiver(sendBroadcast)、Service(startService)都有效吗?

从上面的分析来看,不是都有效。
而且从名字protected-broadcast直观的来看(猜),也是只保护广播action的,不保护 startActivity 和 startService 的action。
都是action,待遇怎么差那么多呢?

★ 5. 如何判断一个action是否是受保护的?

如果对apk进行渗透测试,经常会遇到Receiver组件暴露的问题,但是Receiver接收的action是否受保护呢?这里提供一种方法来判断action是不是受保护的。代码如下:

package com.galian.mylib;import android.content.pm.IPackageManager;import android.os.RemoteException;import android.util.Log;import java.lang.reflect.InvocationTargetException;import java.lang.reflect.Method;public class Utils {    private static final String TAG = "Utils";    public static boolean isProtectedBroatcast(String action) {        Class<?> cls = null;        Method method = null;        boolean ret = false;        try {            cls = Class.forName("android.app.ActivityThread");            method = cls.getMethod("getPackageManager", null);            if (!method.isAccessible()) {                method.setAccessible(true);            }            IPackageManager iPackageManager = (IPackageManager) method.invoke(cls, null);            ret = iPackageManager.isProtectedBroadcast(action);            Log.d(TAG, "action: " + action + " is " + (ret?"protected.":" not protected."));        } catch ...        //(catch 代码块略)        return ret;    }}

要想使上面的代码编译通过,还需要framework生成的jar包framework-classes-full-debug.jar
在 build.gradle中引用 framework-classes-full-debug.jar 包:

dependencies {    //compile fileTree(dir: 'libs', include: ['*.jar']) // 一定要注释掉,否则会发生method数超过65536的问题    provided files('libs/framework-classes-full-debug.jar')    ...}

我用Android 8.0 代码编译生成的 framework-classes-full-debug.jar,放到了github,地址为:https://github.com/galian123/Samples/blob/master/mylib/libs/framework-classes-full-debug.jar。
整个工程也传到了github,地址:https://github.com/galian123/Samples。

更多相关文章

  1. Gears Android WIFI/基站定位源代码分析
  2. 安卓学习(初)第三章(2)(《第一行代码》)
  3. Android 2.3禁止系统弹出应用程序强制退出对话框
  4. 《第一行代码Android》阅读笔记
  5. 白话开发——Android Studio代码调试技巧篇
  6. Android NDK r5 windows系统上安装与使用【1】
  7. Android系统回顾(三):UI之GridLayout布局

随机推荐

  1. android 开发真机测试,无法安装调试解决
  2. Android(安卓)Push Notifications using
  3. [Android] 简单的状态机实现
  4. download android-4.0 source code
  5. 自定义PopupWindow动画效果
  6. CTS/GTS 常见问题汇总
  7. android日常学习3-23 实现打字游戏
  8. 2020.05.11-Python中调用zip包中的Python
  9. 如何做android技术面试
  10. java.lang.RuntimeException: Unable to