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方法。
修复前代码中下:
...
private static final String KEY_CALLER_IDENTITY = "pendingIntent";
... private void addAccount(String accountType) {
Bundle addAccountOptions = new Bundle();
mPendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(), 0);
addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);
addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS, Utils.hasMultipleUsers(this));
AccountManager.get(this).addAccount(
accountType,
null, /* authTokenType */
null, /* requiredFeatures */
addAccountOptions,
null,
mCallback,
null /* handler */);
mAddAccountCalled = true;
}
修复后代码例如以下
...
private static final String KEY_CALLER_IDENTITY = "pendingIntent";
private static final String SHOULD_NOT_RESOLVE = "SHOULDN'T RESOLVE!";
... private void addAccount(String accountType) { Bundle addAccountOptions = new Bundle(); /*
* The identityIntent is for the purposes of establishing the identity
* of the caller and isn't intended for launching activities, services
* or broadcasts.
*
* Unfortunately for legacy reasons we still need to support this. But
* we can cripple the intent so that 3rd party authenticators can't
* fill in addressing information and launch arbitrary actions.
*/
Intent identityIntent = new Intent();
identityIntent.setComponent(new ComponentName(SHOULD_NOT_RESOLVE, SHOULD_NOT_RESOLVE));
identityIntent.setAction(SHOULD_NOT_RESOLVE);
identityIntent.addCategory(SHOULD_NOT_RESOLVE); mPendingIntent = PendingIntent.getBroadcast(this, 0, identityIntent, 0);
addAccountOptions.putParcelable(KEY_CALLER_IDENTITY, mPendingIntent);
addAccountOptions.putBoolean(EXTRA_HAS_MULTIPLE_USERS, Utils.hasMultipleUsers(this));
AccountManager.get(this).addAccountAsUser(
accountType,
null, /* authTokenType */
null, /* requiredFeatures */
addAccountOptions,
null,
mCallback,
null /* handler */,
mUserHandle);
mAddAccountCalled = true;
}
mPenddingIntent的作用主要是作为身份识别用的。
通过前后对照。修复方案就是把放入mPendingIntent的intent。由原来简单的new Intent()改为事先经过一系列填充的identityIntent。这样做,就能够防止第三方的Authenticator(主要是针对木马)进行二次填充。后面会具体介绍。
注意PendingIntent.getBroadcast调用的參加中,在修复前传入的是一个"空"的Intent对象,这对后面的分析很关键。
PeddingIntent的实现原理
通过上面代码对照分析。假设你已经对PeddingIntent的实现细节比較清楚的话,那么这节的内容能够跳过。在PenddingIntent.java源文件里,有这么一段说明:
/**
* ...
* ...
* <p>By giving a PendingIntent to another application,
* you are granting it the right to perform the operation you have specified
* as if the other application was yourself (with the same permissions and
* identity). As such, you should be careful about how you build the PendingIntent:
* almost always, for example, the base Intent you supply should have the component
* name explicitly set to one of your own components, to ensure it is ultimately
* sent there and nowhere else.
*
* <p>A PendingIntent itself is simply a reference to a token maintained by
* the system describing the original data used to retrieve it. This means
* that, even if its owning application's process is killed, the
* PendingIntent itself will remain usable from other processes that
* have been given it. If the creating application later re-retrieves the
* same kind of PendingIntent (same operation, same Intent action, data,
* categories, and components, and same flags), it will receive a PendingIntent
* representing the same token if that is still valid, and can thus call
* {@link #cancel} to remove it.
* ...
* ...
*/
简单来说。就是指PenddingIntent对象能够按预先指定的动作进行触发。当这个对象传递(通过binder)到其它进程(不同uid的用户),其它进程利用这个PenddingInten对象,能够原进程的身份权限运行指定的触发动作。这有点相似于Linux上suid或guid的效果。另外,因为触发的动作是由系统进程运行的,因此哪怕原进程已经不存在了,PenddingIntent对象上的触发动作依旧有效。
PeddingIntent是一个Parcelable对象。包括了一个叫名mTarget成员,类型是。这个字段事实上是个BinerProxy对象,真正的实现逻辑在PenddingIntentRecored.java。从源代码分析可知。PendingIntent.getBroadcast终于调用的是ActivityManagerService中的getIntentSender方法。关键代码例如以下:
public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle options, int userId) { enforceNotIsolatedCaller("getIntentSender");
...
...
synchronized(this) {
int callingUid = Binder.getCallingUid();
int origUserId = userId;
userId = handleIncomingUser(Binder.getCallingPid(), callingUid, userId,
type == ActivityManager.INTENT_SENDER_BROADCAST, false,
"getIntentSender", null);
...
... return getIntentSenderLocked(type, packageName, callingUid, userId, token, resultWho, requestCode, intents, resolvedTypes, flags, options); } catch (RemoteException e) {
throw new SecurityException(e);
}
}
}
IIntentSender getIntentSenderLocked(int type, String packageName, int callingUid, int userId, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, Bundle options) { if (DEBUG_MU)
Slog.v(TAG_MU, "getIntentSenderLocked(): uid=" + callingUid);
ActivityRecord activity = null; ...
... PendingIntentRecord.Key key = new PendingIntentRecord.Key(type, packageName, activity, resultWho, requestCode, intents, resolvedTypes, flags, options, userId); //依据调用者的信息,生成PendingIntentRecord.Key对象 WeakReference<PendingIntentRecord> ref;
ref = mIntentSenderRecords.get(key);
PendingIntentRecord rec = ref != null ? ref.get() : null;
...
... rec = new PendingIntentRecord(this, key, callingUid); //最后生成PendingIntentRecord对象
mIntentSenderRecords.put(key, rec.ref); //保存
...
return rec; //并返回
}
总结一下这个过程。就是AMS会把生成PenddingIntent的进程(Caller)信息保存到PendingIntentRecord.Key。并为其维护一个PendingIntentRecord对象,这个对象是一个BinderStub。
PendingIntent提供了一系列的send方法进行动作触发。终于是调用PendingIntentRecord的send方法,我们直接分析这里的代码:
public int send(int code, Intent intent, String resolvedType,
IIntentReceiver finishedReceiver, String requiredPermission) {
return sendInner(code, intent, resolvedType, finishedReceiver,
requiredPermission, null, null, 0, 0, 0, null);
}
跟进去:
int sendInner(int code, Intent intent, String resolvedType,
IIntentReceiver finishedReceiver, String requiredPermission,
IBinder resultTo, String resultWho, int requestCode,
int flagsMask, int flagsValues, Bundle options) { synchronized(owner) {
if (!canceled) {
sent = true;
if ((key.flags&PendingIntent.FLAG_ONE_SHOT) != 0) {
owner.cancelIntentSenderLocked(this, true);
canceled = true;
}
Intent finalIntent = key.requestIntent != null
? new Intent(key.requestIntent) : new Intent();
if (intent != null) {
int changes = finalIntent.fillIn(intent, key.flags); //用传进来的intent进行填充finalIntent
if ((changes&Intent.FILL_IN_DATA) == 0) {
resolvedType = key.requestResolvedType;
}
} else {
resolvedType = key.requestResolvedType;
} ...
... switch (key.type) {
...
case ActivityManager.INTENT_SENDER_BROADCAST:
try {
// If a completion callback has been requested, require
// that the broadcast be delivered synchronously
owner.broadcastIntentInPackage(key.packageName, uid,
finalIntent, resolvedType,
finishedReceiver, code, null, null,
requiredPermission, (finishedReceiver != null), false, userId);
sendFinish = false;
} catch (RuntimeException e) {
Slog.w(ActivityManagerService.TAG,
"Unable to send startActivity intent", e);
}
break;
...
} ... return 0;
}
}
return ActivityManager.START_CANCELED;
针对该漏洞我们仅仅分析broadcast这个分支的逻辑就可以。这里发现。会用send传进来的intent对finalIntent进行填充。通过前面的代码分析得到。这里的finalInent是一个“空”的intent。即mAction, mData,mType等等全为null,这使得差点儿能够任意指定finalIntent的内容。见fillIn的代码:
public int fillIn(Intent other, int flags) {
int changes = 0;
if (other.mAction != null
&& (mAction == null || (flags&FILL_IN_ACTION) != 0)) {
mAction = other.mAction;
changes |= FILL_IN_ACTION;
}
if ((other.mData != null || other.mType != null)
&& ((mData == null && mType == null)
|| (flags&FILL_IN_DATA) != 0)) {
mData = other.mData;
mType = other.mType;
changes |= FILL_IN_DATA;
}
if (other.mCategories != null
&& (mCategories == null || (flags&FILL_IN_CATEGORIES) != 0)) {
if (other.mCategories != null) {
mCategories = new ArraySet<String>(other.mCategories);
}
changes |= FILL_IN_CATEGORIES;
}
if (other.mPackage != null
&& (mPackage == null || (flags&FILL_IN_PACKAGE) != 0)) {
// Only do this if mSelector is not set.
if (mSelector == null) {
mPackage = other.mPackage;
changes |= FILL_IN_PACKAGE;
}
}
// Selector is special: it can only be set if explicitly allowed,
// for the same reason as the component name.
if (other.mSelector != null && (flags&FILL_IN_SELECTOR) != 0) {
if (mPackage == null) {
mSelector = new Intent(other.mSelector);
mPackage = null;
changes |= FILL_IN_SELECTOR;
}
}
if (other.mClipData != null
&& (mClipData == null || (flags&FILL_IN_CLIP_DATA) != 0)) {
mClipData = other.mClipData;
changes |= FILL_IN_CLIP_DATA;
}
// Component is special: it can -only- be set if explicitly allowed,
// since otherwise the sender could force the intent somewhere the
// originator didn't intend.
if (other.mComponent != null && (flags&FILL_IN_COMPONENT) != 0) {
mComponent = other.mComponent;
changes |= FILL_IN_COMPONENT;
}
mFlags |= other.mFlags;
if (other.mSourceBounds != null
&& (mSourceBounds == null || (flags&FILL_IN_SOURCE_BOUNDS) != 0)) {
mSourceBounds = new Rect(other.mSourceBounds);
changes |= FILL_IN_SOURCE_BOUNDS;
}
if (mExtras == null) {
if (other.mExtras != null) {
mExtras = new Bundle(other.mExtras);
}
} else if (other.mExtras != null) {
try {
Bundle newb = new Bundle(other.mExtras);
newb.putAll(mExtras);
mExtras = newb;
} catch (RuntimeException e) {
// Modifying the extras can cause us to unparcel the contents
// of the bundle, and if we do this in the system process that
// may fail. We really should handle this (i.e., the Bundle
// impl shouldn't be on top of a plain map), but for now just
// ignore it and keep the original contents. :(
Log.w("Intent", "Failure filling in extras", e);
}
}
return changes;
}
从上面代码得知,我们能够任意指定除了mComponent之外的全部字段,这已经能够满足大部分的使用情景了。
漏洞利用和危害
有了前面分析,漏洞复用代码就很easy了。这里一个是发送系统开机广播的样例:
// the exploit of broadcastAnyWhere
final String KEY_CALLER_IDENTITY = "pendingIntent";
PendingIntent pendingintent = options.getParcelable(KEY_CALLER_IDENTITY);
Intent intent_for_broadcast = new Intent("android.intent.action.BOOT_COMPLETED");
intent_for_broadcast.putExtra("info", "I am bad boy"); try {
pendingintent.send(mContext, 0, intent_for_broadcast);
} catch (CanceledException e) {
e.printStackTrace();
}
事实上可利用的广播实在太多了。再比方:
- 发送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)漏洞具体分析的更多相关文章
- Android LaunchAnyWhere (Google Bug 7699048)漏洞具体解释及防御措施
開始 近日,Google修复一个组件安全的漏洞LaunchAnyWhere(Google Bug 7699048). 这个漏洞属于Intend Based提取漏洞,攻击者利用这个漏洞,能够突破了应用间 ...
- Google发布SSLv3漏洞简要分析报告
今天上午,Google发布了一份关于SSLv3漏洞的简要分析报告.根据Google的说法,该漏洞贯穿于所有的SSLv3版本中,利用该漏洞,黑客可以通过中间人攻击等类似的方式(只要劫持到的数据加密两端均 ...
- Android电话拨打权限绕过漏洞(CVE-2013-6272)分析
原文:http://blogs.360.cn/360mobile/2014/07/08/cve-2013-6272/ 1. CVE-2013-6272漏洞背景 CVE-2013-6272是一个安卓平台 ...
- broadAnywhere:Broadcast组件权限绕过漏洞(Bug: 17356824)
原创内容,转载请注明出处 http://retme.net/index.php/2014/11/14/broadAnywhere-bug-17356824.html Lolipop源代码已经放出有些日 ...
- Android“寄生兽”漏洞技术分析
一.关于app的缓存代码 安卓的应用程序apk文件是zip压缩格式的文件,apk文件中包含的classes.dex文件相当于app的可执行文件,当app运行后系统会对classes.dex进行优化,生 ...
- CVE-2015-3864漏洞利用分析(exploit_from_google)
title: CVE-2015-3864漏洞利用分析(exploit_from_google) author: hac425 tags: CVE-2015-3864 文件格式漏洞 categories ...
- Android:你不知道的 WebView 使用漏洞
前言 如今非常多App里都内置了Web网页(Hyprid App),比方说非常多电商平台.淘宝.京东.聚划算等等.例如以下图 上述功能是由 Android的WebView 实现的.可是 WebView ...
- 蓝牙App漏洞系列分析之一CVE-2017-0601
蓝牙App漏洞系列分析之一CVE-2017-0601 0x01 概要 2017年5月的 Android 安全公告修复了我们提交的一个蓝牙提权中危漏洞,这个漏洞尽管简单,但比较有意思,能够使本地恶意 A ...
- 蓝牙App漏洞系列分析之二CVE-2017-0639
蓝牙App漏洞系列分析之二CVE-2017-0639 0x01 漏洞简介 Android本月的安全公告,修复了我们发现的另一个蓝牙App信息泄露漏洞,该漏洞允许攻击者获取 bluetooth用户所拥有 ...
随机推荐
- 【Python-2.7】删除空格
有时我们在编程过程中,需要去除字符串两边的空格,可以用如下函数解决问题: rstrip():去除字符串右边的空格: lstrip():去除字符串左边的空格: strip():去除字符串两边的空格. 示 ...
- android studio使用中碰到Failure [INSTALL_FAILED_OLDER_SDK] 问题
第一次使用Android studio开发.直接新建一个默认项目运行出现:Failure [INSTALL_FAILED_OLDER_SDK] , 网上很多人说修改build.gradle中的mins ...
- magento category Ids Name
如何获取产品的分类的名称 和ids 1.对于产品的分类ids 的获取 $this->getProduct()->getCategoryIds() 2.对应产品的分类的Name 的 ...
- 6.11 将分割数据转换为多值IN列表
问题 已经有了分隔数据,想要将其转换为WHERE子句IN列表中的项目.考虑下面的字符串: 7654,7698,7782,7788 要将该字符串用在WHERE子句中,但是下面的SQL语句是错误的,因为E ...
- Codeforces_761_E_(dfs)
E. Dasha and Puzzle time limit per test 2 seconds memory limit per test 256 megabytes input standard ...
- CAD如何设置系统变量
主要用到函数说明: MxDrawXCustomFunction::Mx_SetSysVar 设置系统变量.详细说明如下: 参数 说明 CString sVarName 系统变量名 Value 需要设置 ...
- BZOJ1013 + BZOJ1923 + POJ1830 (高斯消元)
三个题放在一起写了 主要是搞搞模板 在这里简述一下怎么写高斯消元 就和代数里学的加减消元学的一样 把矩阵化为上三角形形式 然后进行回代 同时枚举当前要消元的未知数和当前化简到哪一行了 然后从这一行往后 ...
- php切换版本之后 redis 安装使用
一:redis安装Download, extract and compile Redis with: $ wget http://download.redis.io/releases/redis-3. ...
- tf idf公式及sklearn中TfidfVectorizer
在文本挖掘预处理之向量化与Hash Trick中我们讲到在文本挖掘的预处理中,向量化之后一般都伴随着TF-IDF的处理,那么什么是TF-IDF,为什么一般我们要加这一步预处理呢?这里就对TF-IDF的 ...
- JPA @MappedSuperclass注解
该注解只能引用于类上,使用该注解的类将不是一个完整的类,不会映射到数据库的表中,但是该类的属性会映射到其子类的数据库字段中 @MappedSuperclass注解使用在父类上面,是用来标识父类的作用 ...