这是CVE-2021-39749的PoC,它允许在Android 12L Beta上无视其他应用的permission和exported设置启动其Activity
在Android 12L中,TaskFragmentOrganizer的访问(有意地)不再需要MANAGE_ACTIVITY_TASKS权限
使用此处提供的应用需要禁用Hidden API Checks,你可以通过adb shell settings put global hidden_api_policy 1来实现。这些不是安全边界,并且存在已知的基于应用的绕过方法
以下是修复此漏洞(以及原始报告中提到的几个相关问题)的提交:
startActivityInTaskFragment不再依赖Binder.getCallingUid()ResolverActivity现在启用了relinquishTaskIdentityTaskFragment的SurfaceControl不再提供ActivityRecord#appToken发送给TaskFragmentOrganizer现在基于uid而非pid你可以检出android-12.1.0_r4,还原前3个提交(或前2个,应用仍然能够关闭设备(通过启动ShutdownActivity),但“缩放并设置透明度”复选框将无法工作)
(如果你尝试还原该列表中的第一个提交,测试中会出现合并冲突,但你可以忽略这些)
Binder.getCallingUid()Binder.getCallingUid()方法返回发送当前正在处理的Binder事务的进程的uid。该uid存储在线程局部变量中。处理事务的代码可以调用Binder.clearCallingIdentity()将该变量设置为自身进程的uid,以指示在事务处理期间稍后调用的方法应针对自身(处理事务的代码)而非Binder事务的调用者进行权限检查
有时存在总是在Binder.clearCallingIdentity()之后调用的Binder.getCallingUid(),因此总是返回自身进程的uid。有时这是有意为之,例如在ActivityTaskManagerService#startDreamActivity中(尽管这是执行Process.myUid()或Os.getuid()的一种相当迂回的方式)
我为自己编写了一个(基于Soot的)静态分析工具,用于报告此类只能在Binder.clearCallingIdentity()之后发生的Binder.getCallingUid()调用(以及其他权限检查)。(我有处理Soot提供的Jimple/Shimple IR的自定义逻辑,尽管使用Soot可能有更好的方法,但这是我目前拥有的)
在Android 12L Beta中,该工具在ActivityStartController#startActivityInTaskFragment中发现了一个(注意:当时源代码不可用,因为Beta版本不是开源的,但Shimple通常可读,因此我也将Soot用作Java反编译器)
startActivityInTaskFragment作为静态分析报告的一部分,我获得了从onTransact()实现(Binder调用开始处)到startActivityInTaskFragment的调用层次结构:
IWindowOrganizerController的aidl生成代码中的onTransactWindowOrganizerController#applyTransaction(不带CallerInfo参数)WindowOrganizerController#applyTransaction(带CallerInfo参数)WindowOrganizerController#applyHierarchyOpActivityStartController#startActivityInTaskFragment我发现TaskFragmentOrganizer类中存在对applyTransaction的Binder调用,我决定将其用作比直接执行所有Binder调用更方便的包装器(两者都不是公共API,因此无论如何我都必须使用反射)
首先,方法“2.”调用enforceTaskPermission,在Android 12.0上检查仅signature级别的MANAGE_ACTIVITY_TASKS权限,我们无法获得该权限,然而在Android 12L上规则被放宽,某些事务可以在没有权限的情况下进行。事实证明,执行startActivityInTaskFragment所需的操作都不需要权限(如果事务关联了TaskFragmentOrganizer)
因此我们想要执行HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT。为此,我们必须将我们的TaskFragment注册在mLaunchTaskFragments中,否则将报告"Not allowed to operate with invalid fragment token"异常
我们可以通过HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT注册这样的TaskFragment,它会调用createTaskFragment()
(在PoC代码中,这些事务在SecondActivity中发送:HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT由initOrganizerAndFragment()发送,HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT由startActivityInOrganizer发送)
因此这允许我们调用startActivityInTaskFragment,并且此处启动的Activity的Intent被视为来自系统uid,但事实证明这本身并不能让我们做任何事情:系统启动的Activity不能进行URI授权,如果我们尝试启动另一个应用的Activity,我们将被canEmbedActivity检查阻止
canEmbedActivity让我们再次查看canEmbedActivity:如果taskFragment.getTask().effectiveUid是系统uid或与启动的应用的uid匹配,则允许嵌入。我们需要处于effectiveUid为系统的任务中
再回到createTaskFragment():仅当rootActivity.getUid() != ownerActivity.getUid()时才允许创建TaskFragment。这意味着我们的Activity需要位于其所在Task的返回栈底部
我们需要启动一个新的Task(通过Intent.FLAG_ACTIVITY_NEW_TASK),该Task将包含属于系统uid的Activity(因此Task#effectiveUid将被设置为AID_SYSTEM),然后该Activity将启动我们的Activity(在同一Task内)并finish()自身(因此我们的Activity将成为该任务的根Activity,允许我们使用createTaskFragment())