前言
StrandHogg是一个比较古老的漏洞了,但是通过它,我们可以了解Android的activity的启动模式以及属性。
activity任务栈
任务栈显而易见是一种栈结构,遵循后进先出的原则,这些 Activity 按照每个 Activity 打开的顺序排列在一个返回堆栈中。例如,电子邮件应用可能有一个 Activity 来显示新邮件列表。当用户选择一封邮件时,系统会打开一个新的 Activity 来显示该邮件。这个新的 Activity 会添加到返回堆栈中。如果用户按返回按钮,这个新的 Activity 即会完成并从堆栈中退出。
可以使用
adb shell dumpsys activity activities
命令查看堆栈。(该命令使用后会有大量内容产生,查找Running activities。linux下可以使用grep直接筛选)
作为系统的默认行为,每次启动一个 Activity,都会创建这个 Activity 的实例,这样就会重复创建。因此,Android 提供了启动模式(launchMode)来改变系统的默认行为。launchMode属性指定了activity如何与任务关联。可以为launchMode
属性指定 4 种不同的启动模式:
standard(默认模式)
默认值。系统在启动该 Activity 的任务中创建 Activity 的新实例,并将 intent 传送给该实例。Activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以拥有多个实例。
singleTop
如果当前任务的顶部已存在 Activity 的实例,则系统会通过调用其 onNewIntent()
方法来将 intent 转送给该实例,而不是创建 Activity 的新实例。Activity 可以多次实例化,每个实例可以属于不同的任务,一个任务可以拥有多个实例(但前提是返回堆栈顶部的 Activity 不是该 Activity 的现有实例)。
举个例子来说,假如任务的返回堆栈为根activity A 以及Activity B和位于栈顶的Activity C。此时的堆栈为A_B_C(c在顶部)。如果C的启动模式为standard,则如果收到以c类型activity为目标的intent。则会启动该类的新实例,变为A_B_C_C。但是如果是singleTop,则C会通过onNewIntent()接收intent,因为他位于堆栈顶部,所以还会是A_B_C,但是如果不是位于顶部,则还会添加新的实例。即使启动模式为singleTop。
singleTask
系统会创建新任务,并实例化新任务的根 Activity。但是,如果另外的任务中已存在该 Activity 的实例,则系统会通过调用其 onNewIntent()
方法将 intent 转送到该现有实例,而不是创建新实例。Activity 一次只能有一个实例存在。要注意的是,虽然activity在新任务中启动,但是返回仍然会返回到上一个activity。
singleInstance
与 "singleTask"
相似,唯一不同的是系统不会将任何其他 Activity 启动到包含该实例的任务中。该 Activity 始终是其任务唯一的成员;由该 Activity 启动的任何 Activity 都会在其他的任务中打开。
任务相关性
任务相关性(TaskAffinity)作用为了区分不同的任务栈,这个参数标识了一个 Activity 进入的任务栈的名字。在不指定这个参数的默认情况下,Activity 进入任务栈的名字就是应用的包名。taskAffinity在两种情况下发挥作用。
当启动 Activity 的 intent 包含 FLAG_ACTIVITY_NEW_TASK
标记时
默认情况下,新 Activity 会启动到调用 startActivity()
的 Activity 的任务中。它会被推送到调用方 Activity 所在的返回堆栈中。但是,如果传递给 startActivity()
的 intent 包含 FLAG_ACTIVITY_NEW_TASK
标记,则系统会寻找其他任务来容纳新 Activity。通常会是一个新任务,但也可能不是。如果已存在与新 Activity 具有相同亲和性的现有任务,则会将 Activity 启动到该任务中。如果不存在,则会启动一个新任务。
如果此标记导致 Activity 启动一个新任务,而用户按下主屏幕按钮离开该任务,则必须为用户提供某种方式来返回到该任务。有些实体(例如通知管理器)总是在外部任务中启动 Activity,而不在它们自己的任务中启动,因此它们总是将 FLAG_ACTIVITY_NEW_TASK
添加到传递给 startActivity()
的 intent 中。如果Activity可以由外部实体调用,则该实体可以使用此标记。
allowTaskReparenting 属性的作用是 Activity 的迁移,从一个任务栈迁移到另一个任务栈,一旦和activity有相关性的任务进入前台运行,则activity就可以从启动的任务转移到该任务。而StrandHogg就是利用了该属性。
StandHogg原理
StandHogg从本质上来说,还是UI欺骗。利用过程是这样的,首先设置一个恶意app作为poc,设置allowTaskReparenting为true,同时将TaskAffinity设为待攻击app的包名。然后打开这个攻击Poc,然后直接按home键返回桌面,此时需要查看一下任务栈,看taskAffinity是不是设置成功,之后打开待攻击app,可以发现显示在最前面的还是poc界面,此时劫持完成,我们要做的就是将界面伪造为待攻击界面。即可窃取隐私信息。
总结一下,StandHogg就是利用了 Android Activity 启动模式的一个细微特性,allowTaskReparenting为true,并且有TaskAffinity指定包名。
POC代码:StandHogg
StandHogg2.0(cve-2020-0096)
相较于StandHogg,StandHogg2.0则是利用了FLAG_ACTIVITY_NEW_TASK标志和startActivity这个API。startActivities方法的功能是一次启动多个Activity,传入一个Intent数组,Android会解析每个Intent,并逐个启动它们。而该方法传入特定的参数时,会将当前应用的Activity放入其它应用的任务栈中,并且覆盖在该应用原本应该显示的Activity的上层,也就是Activity劫持。
恶意应用可向startActivities方法传入精心构造的Intent数组:奇数位置(假定从1开始)为被攻击的Activity(属于被攻击应用),并且为他们添加FLAG_ACTIVITY_NEW_TASK标志,偶数位置放置仿冒的Activity(属于恶意应用),最末位放置恶意应用伪装的符合恶意应用主题的正常Activity,用于迷惑用户这是一个很普通的应用。
构造
Intent[] intents = new Intent[]{受害界面1 仿冒界面1 受害界面2 仿冒界面2 .。。。。。。 伪造的正常界面}
startActivities(intents);
原因就是使用startActivities启动界面的时候,在有漏洞的代码下,默认认为第一个Activity的启动者是当前应用,而第二个Activity的启动者是第一个Activity所属的那个应用,以此类推。在启动奇数位置的受害界面时,使用了FLAG_ACTIVITY_NEW_TASK标志,这样就会将Activity放入新的任务栈中,并且默认该任务栈的名字和那个应用的包名相同,而启动偶数位置的仿冒界面时,由于当前Activity的启动者默认是上一个Activity所属的那个应用,也就是受害应用,所以仿冒界面会尝试将自己放入受害应用的任务栈中,而只要受害应用的那个受害界面没有配置singleTask或者singleInstance启动模式,Android就会将仿冒界面加入到受害应用的任务栈中,并且仿冒界面位于受害界面之上。在用户真正启动受害应用的时候,由于受害应用的任务栈已存在,所以会直接显示任务栈顶部的界面,也就看到了仿冒界面。
修改后的代码,会判断当前要启动的Activity和上一个启动的Activity是否是同一个应用,如果是才会设置启动者为上一个应用,否则都认为是当前应用启动的,并且创建一个新的任务栈(添加FLAG_ACTIVITY_NEW_TASK标志)。
所以可以构造Poc:
Intent[] Intents = new Intent[4];
Intents[0] = new Intent();
Intents[0].setClassName("pkg1","pkg1.clz1");
Intents[1] = new Intent(MainActivity.this,ActivityB.class);
Intents[2] = new Intent();
Intents[2].setClassName("pkg2","pkg2.clz2");
Intents[3] = new Intent(MainActivity.this,ActivityC.class);
Intents[0].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Intents[2].addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivities(Intents);