wnagzihxa1n

wnagzihxa1n

iOS/Android Security

© 2021

CVE-2020-0391 Android9和Android10系统应用广播保护机制失效漏洞

《(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_UIDSYSTEM_UIDPHONE_UIDBLUETOOTH_UIDNFC_UIDSE_UID或者callerApp.persistent标志位被设置为true

IMAGE

解释下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_SYSTEMSCAN_AS_PRIVILEGED两个标志位,而扫描/system/app的时候,只添加SCAN_AS_SYSTEM标签,在进行广播保护逻辑判断的时候,将没有SCAN_AS_PRIVILEGED标志位的系统应用内需要被保护的广播直接置为空

IMAGE

换句话来说,现在所有/system/app下的系统应用声明的保护广播,全都不被保护了,第三方也可以去发送这些被保护的广播

在Android 10里,作者搜索到了三个应用共十个被保护的广播

IMAGE

有想跟着分析的同学可以从这里下载对应的版本

  • 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.apkSSRestartDetector.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;
        
        ...
    }