protected-broadcast 的一些细节
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.system
,android.uid.phone
,android.uid.log
,android.uid.nfc
,android.uid.bluetooth
,android.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。
更多相关文章
- Gears Android WIFI/基站定位源代码分析
- 安卓学习(初)第三章(2)(《第一行代码》)
- Android 2.3禁止系统弹出应用程序强制退出对话框
- 《第一行代码Android》阅读笔记
- 白话开发——Android Studio代码调试技巧篇
- Android NDK r5 windows系统上安装与使用【1】
- Android系统回顾(三):UI之GridLayout布局