《(Un)protected Broadcasts in Android 9 and 10》
- https://www.blackhat.com/asia-21/briefings/schedule/index.html#unprotected-broadcasts-in-android–and–22378
- http://i.blackhat.com/asia-21/Thursday-Handouts/as-21-Johnson-Unprotected-Broadcasts-In-Android-9-and-10.pdf
- http://i.blackhat.com/asia-21/Thursday-Handouts/as-21-Johnson-Unprotected-Broadcasts-In-Android-9-and-10-wp.pdf
Android应用有四大组件,Activity,Service,Content Provider,Broadcast Receiver
定义一个广播接收器的方式如下,该广播接收器接收的Action为"android.intent.action.BOOT_COMPLETED"
,当系统内部在广播带有这个Action的广播,就会被这个广播接收器给捕获到
<receiver android:name=".NfcBootCompletedReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
发送广播有两种方式:隐式发送和显式发送,前者只发送带有Action的广播,后者指定接收该广播的组件,当然Action也可以不指定,靠广播接收器内部判断也可以
Intent intent = new Intent("android.intent.action.BOOT_COMPLETED");
sendBroadcast(intent);
Intent intent = new Intent("android.intent.action.BOOT_COMPLETED");
intent.setClassName("com.android.nfc", "com.android.nfc.NfcBootCompletedReceiver");
sendBroadcast(intent);
要注意的是:这里举例的Action是系统启动时会广播的开机广播,属于被保护的广播,闲杂人等不能随便发送
<protected-broadcast android:name="android.intent.action.BOOT_COMPLETED"/>
有权限发送含有被保护的的广播判断规则如下,只有当前调用者的UID为ROOT_UID
,SYSTEM_UID
,PHONE_UID
,BLUETOOTH_UID
,NFC_UID
和SE_UID
或者callerApp.persistent
标志位被设置为true
解释下callerApp.persistent
标志位,它定义在AndroidManifest文件里,用于保证应用持续运行,只有系统应用的这个属性会生效,第三方应用利用这个属性保活是没有用的
<application
android:persistent="true|false">
在Android系统里存在两百多个自带的应用,一般来说就存在/system/app
和/system/priv-app
下面,记住他们都是系统应用即可
所有系统应用,都可以申请广播保护,如下在系统应用里定义广播即可受到保护,不被第三方应用随便发送
<protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
<protected-broadcast android:name="android.intent.action.BOOT_COMPLETED"/>
<protected-broadcast android:name="android.intent.action.LOCALE_CHANGED"/>
整个漏洞的核心就是这一张图,扫描/system/priv-app
的时候,会添加SCAN_AS_SYSTEM
和SCAN_AS_PRIVILEGED
两个标志位,而扫描/system/app
的时候,只添加SCAN_AS_SYSTEM
标签,在进行广播保护逻辑判断的时候,将没有SCAN_AS_PRIVILEGED
标志位的系统应用内需要被保护的广播直接置为空
换句话来说,现在所有/system/app
下的系统应用声明的保护广播,全都不被保护了,第三方也可以去发送这些被保护的广播
在Android 10里,作者搜索到了三个应用共十个被保护的广播
有想跟着分析的同学可以从这里下载对应的版本
- https://dl.google.com/dl/android/aosp/flame-qq2a.200405.005-factory-e6617692.zip
额外插一句,Path Finder也可以批量扫这种,想不到写了个扫描引擎额外捡了很多功能
/home/wnagzihxa1n/UnpackROM/SystemApps/app/PresencePolling/PresencePolling.apk
==> <protected-broadcast android:name="android.provider.rcs.eab.EAB_NEW_CONTACT_INSERTED"/>
==> <protected-broadcast android:name="android.provider.rcs.eab.EAB_DATABASE_RESET"/>
==> <protected-broadcast android:name="com.android.service.ims.presence.capability_polling_retry"/>
==> <protected-broadcast android:name="com.android.service.ims.presence.periodical_capability_discovery"/>
不过奇怪的是我只扫描出了一个应用,手动查看了固件,也并不存在另外两个应用uceShimService.apk
和SSRestartDetector.apk
,这个地方我需要再仔细想想原因
所以接下来以Slides描述为主
第一个漏洞是谷歌原生的应用,它最后可以操作联系人列表,我简单看了一下,这种攻击结果也没什么太大的意思
第二个漏洞来自小米,包名com.qualcomm.qti.perfdump
,受保护的广播定义如下
<protected-broadcast android:name="android.perfdump.action.EXT_EXEC_SHELL"/>
广播接收器StaticReceiver
会启动一个服务ExtRequestService
,这个服务会从Intent里取出一个字段"shellCommand"
作为命令行参数进行执行
public int onStartCommand(Intent intent, int flags, int startId) {
...
String shellCommand = intent.getStringExtra("shellCommand");
if(shellCommand != null) {
String trimedShellCommand = shellCommand.trim();
this.processShellRequest(this, trimedShellCommand);
return 2;
}
...
}
public void processShellRequest(Context context, String shellCommand) {
new Thread(new Runnable() {
@Override
public void run() {
Process shellProcess = Runtime.getRuntime().exec(new String[]{"sh", "-c", shellCommand});
...
}
}
}
第三个漏洞还是来自小米,包名com.qualcomm.qti.qmmi
,不过这个漏洞没什么意思,只能泄露当前设备硬件信息,不多说了
手上刚好有个Samsung的固件,顺带扫描了一下,以下仅打印出部分被保护的广播
app/FilterProvider/FilterProvider.apk
==> <protected-broadcast android:name="com.samsung.android.provider.filterprovider.PACKAGE_ADDED"/>
app/MotionPanoramaViewer/MotionPanoramaViewer.apk
==> <protected-broadcast android:name="android.intent.action.MEDIA_MOUNTED"/>
app/SelfMotionPanoramaViewer/SelfMotionPanoramaViewer.apk
==> <protected-broadcast android:name="android.intent.action.MEDIA_MOUNTED"/>
app/MdecService/MdecService.apk
==> <protected-broadcast android:name="com.samsung.android.mdecservice.SMS_SENT"/>
app/DRParser/DRParser.apk
==> <protected-broadcast android:name="android.provider.Telephony.SECRET_CODE"/>
app/EmergencyModeService/EmergencyModeService.apk
==> <protected-broadcast android:name="com.samsung.intent.action.EMERGENCY_STATE_CHANGED"/>
修复方式是把漏洞代码挪个地方
- https://android.googlesource.com/platform/frameworks/base/+/860fd4b6a2a4fe5d681bc07f2567fdc84f0d1580
漏洞代码会主动把没有SCAN_AS_PRIVILEGED
标志位的应用声明的保护广播全部移除
private static void applyPolicy(PackageParser.Package pkg, final @ParseFlags int parseFlags, final @ScanFlags int scanFlags, PackageParser.Package platformPkg) {
if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
...
} else {
...
}
if ((scanFlags & SCAN_AS_PRIVILEGED) == 0) {
// clear protected broadcasts
pkg.protectedBroadcasts = null;
...
修复后的代码将没有SCAN_AS_SYSTEM
标志位的应用声明的保护广播全部移除,因为没有SCAN_AS_SYSTEM
标志位就等于不是系统应用,那声明保护广播是没有意义的
private static void applyPolicy(PackageParser.Package pkg, final @ParseFlags int parseFlags, final @ScanFlags int scanFlags, PackageParser.Package platformPkg) {
if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
...
} else {
// clear protected broadcasts
pkg.protectedBroadcasts = null;
...
}