public AccountManagerFuture<Bundle> addAccount(final String accountType,
final String authTokenType, final String[] requiredFeatures,
final Bundle addAccountOptions,
final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
if (accountType == null) throw new IllegalArgumentException("accountType is null");
final Bundle optionsIn = new Bundle();
if (addAccountOptions != null) {
optionsIn.putAll(addAccountOptions);
}
optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
return new AmsTask(activity, handler, callback) {
public void doWork() throws RemoteException {
mService.addAccount(mResponse, accountType, authTokenType,
requiredFeatures, activity != null, optionsIn);
}
}.start();
}
最后代码是通过一个AmsTask调用addAccount的,而AmsTask本身是一个异步任务,mRespone是一个Binder对象,当AuthenticationService指定Intent后,就是把intent保存到这个respone对象里,最后看看这个Respone的源码:
/** Handles the responses from the AccountManager */
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
} else if (bundle.getBoolean("retry")) {
try {
doWork();
} catch (RemoteException e) {
// this will only happen if the system process is dead, which means
// we will be dying ourselves
}
} else {
set(bundle);
}
}
public void onError(int code, String message) {
if (code == ERROR_CODE_CANCELED || code == ERROR_CODE_USER_RESTRICTED) {
// the authenticator indicated that this request was canceled, do so now
cancel(true /* mayInterruptIfRunning */);
return;
}
setException(convertErrorToException(code, message));
}
}
从代码可见,Respone直接帮我们startActivity了。
关于System用户
在Android中,可以说System用户拥有相当高的权限,通过阅读源码可以发,所有permissoin的地方都是直接放行System用户的,见代码ActivityManagerService.checkComponentPermission
/**
* This can be called with or without the global lock held.
*/
int checkComponentPermission(String permission, int pid, int uid,
int owningUid, boolean exported) {
// We might be performing an operation on behalf of an indirect binder
// invocation, e.g. via {@link #openContentUri}. Check and adjust the
// client identity accordingly before proceeding.
Identity tlsIdentity = sCallerIdentity.get();
if (tlsIdentity != null) {
Slog.d(TAG, "checkComponentPermission() adjusting {pid,uid} to {"
+ tlsIdentity.pid + "," + tlsIdentity.uid + "}");
uid = tlsIdentity.uid;
pid = tlsIdentity.pid;
}
if (pid == MY_PID) {
return PackageManager.PERMISSION_GRANTED;
}
漏洞利用POC, 见https://github.com/retme7/launchAnyWhere_poc_by_retme_bug_7699048
结合FakeID漏洞,实现InjectAnyWhere
FakeID漏洞详解见我之前的博客《Android FakeID(Google Bug 13678484) 漏洞详解》。
FakeID漏洞利用的关键让目标进程运行Webview控件,加载Flash控件达到程序注入的目的。通过LaunchAnyWhere漏洞,我们可以直接打开目标进程的Webview。
FakeID和LaunchAnyWhere完美结合的做法是在一个APK中同时集成两个漏洞利用,大致过程如下:
EvilApp首先令Setting产生一AddAccount请求,并把accountType指定EvilApp本身处理;
EvilApp创建Intent,并把Intent指向攻击应用Webview的Activity(如微信的com.tencent.mm.plugin.webview.ui.tools.ContactQZoneWebView),url指向某个有flash元素的网页;
由于FakeID,WebView则会加载EvilApp中so,注入完毕;
在so中再直接加载EvilApp.apk文件,实现Java注入,这部分更详细的内容,见我之前的博客《攻击的Android注入术》;
漏洞修复
这个漏洞在4.4上已经修复,看看修复的代码,可以找到防御的思路:
public void onResult(Bundle result) {
mNumResults++;
- if (result != null && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) {
+ Intent intent = null;
+ if (result != null
+ && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
+ /*
+ * The Authenticator API allows third party authenticators to
+ * supply arbitrary intents to other apps that they can run,
+ * this can be very bad when those apps are in the system like
+ * the System Settings.
+ */
+ PackageManager pm = mContext.getPackageManager();
+ ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
+ int targetUid = resolveInfo.activityInfo.applicationInfo.uid;
+ int authenticatorUid = Binder.getCallingUid();
+ if (PackageManager.SIGNATURE_MATCH !=
+ pm.checkSignatures(authenticatorUid, targetUid)) {
+ throw new SecurityException(
+ "Activity to be started with KEY_INTENT must " +
+ "share Authenticator's signatures");
+ }
+ }
+ if (result != null
+ && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) {
String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME);
String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
@@ -2223,6 +2276,7 @@
super(looper);
}
由于 Resopne是一个Binder对象,因此当onResult被回调时,可以通过Binder.getCallingUid()获取authenticatorUid,如果targetUid跟authenticatorUid不相同,则直接对AuthenticationService抛异常。