https://blog.csdn.net/u013553529/article/details/78409382
protected-broadcast 的一些细节
★ 1. 引言
注:本文中提及的“广播(Broadcast)”,“广播事件”和“Action”的意思大致相同。发送广播(sendBroadcast)也是发送一个指定的action给BroadcastReceiver。在本文中不严格区分“广播”和“Action”,除非有地方特别说明。
对于 android 系统应用来说,运用protected-broadcast
是基本的安全要求。
顾名思义,protected-broadcast
是保护广播事件(Action)不被滥用的。相对的,如果一个action是不受protected-broadcast
保护,并且使用此action的<receiver>
组件(称之为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" />
(略)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
本文就是针对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";
- 1
PackageParser的内部类Package
中的变量protectedBroadcasts
:
public ArrayList<String> protectedBroadcasts;
- 1
解析 apk 中的 AndroidManifest.xml 的代码在 PackageParser.java
中的parseBaseApkCommon()
。
private Package parseBaseApkCommon(Package pkg, Set<String> acceptedTags, Resources res,
XmlResourceParser parser, int flags, String[] outError) throws XmlPullParserException,
IOException {
//(略)
} else if (tagName.equals(TAG_PROTECTED_BROADCAST)) {
// 解析<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<String>();
}
if (!pkg.protectedBroadcasts.contains(name)) {
// 所有的受保护的action都放到protectedBroadcasts
pkg.protectedBroadcasts.add(name.intern());
}
}
// 此标签<protected-broadcast>解析完毕,跳到END_TAG:'/>' 或者 '</protected-broadcast>'
XmlUtils.skipCurrentTag(parser);
}
略
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
从(flags & PARSE_IS_SYSTEM) != 0
这个判断可知,可以使用<protected-broadcast>
标签的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在新版本中增加了<protected-broadcast>
也会被处理。
如果安装一个跟系统签名一样的app,不是替换系统app,也不是升级系统app的话,就不会用到PARSE_IS_SYSTEM
标志来解析apk,所以在这种情况下,有系统签名但不是系统app,不能使用<protected-broadcast>
特性。
★ 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<N; i++) {
mProtectedBroadcasts.add(pkg.protectedBroadcasts.get(i));
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
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<N; i++) {
mProtectedBroadcasts.add(pkg.protectedBroadcasts.get(i));
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
<protected-broadcast>
保存到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;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
调用PMS的isProtectedBroadcast()
是通过IPackageManager
接口。
IPackageManager.aidl
boolean isProtectedBroadcast(String actionName);
- 1
- 2
- 3
♦ 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);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
由上面可知,如果调用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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
要想使上面的代码编译通过,还需要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')
...
}
- 1
- 2
- 3
- 4
- 5
我用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。