SystemUI As EvilPiP: 针对现代移动设备的劫持攻击
2024年4月19日,奇安信天工实验室安全研究员程为民,出席国际顶级信息安全会议BlackHat ASIA 2024,发表 《SystemUI As EvilPiP: The Hijacking Attacks on Modern Mobile Devices》 议题演讲。议题披露了在SystemUI下隐藏了六年之久的新型攻击面,以及SystemServer中难以修复的设计缺陷。 一、Preface Activity Hijack Attack(AHA)是一项古老的UI攻击技术。大约在十年前,利用这种技术的银行木马与间谍软件开始在Android4.0平台上泛滥,这些劫持软件可以精确监控用户的行为,并以几乎无感知的方式劫持用户正在浏览的内容。由于在早期Android平台上利用这种技术无需任何权限和额外的用户交互,其成为了地下产业最喜爱的攻击手段之一。 但近年来,AHA逐渐失去了它的光彩。由于Google持续发布针对这类技术的缓解方案与限制策略,劫持软件的攻击成本被不断提高。2016年,Google更新了SELinux策略,完全禁止了应用对procfs的访问,并限制了大部分可以泄露应用运行状态的API,自此,无感知与零权限的劫持攻击(基于AHA)成为了历史。2017年,更加严格的LMKD机制与后台执行限制开始杀死处于后台的闲置进程。2019年,Google发布了BAL限制,从后台启动活动的行为被禁止,AHA技术彻底死亡。 在这些安全策略的保护下,AHA不再是低成本的移动端攻击方案。攻击者或许会通过诱导用户开启需要复杂交互才能使用的特权以在高版本设备上实现AHA攻击,但这距离精准劫持还很遥远,更何况手机厂商会在这些特权被授予前警告用户不要轻易相信第三方软件,所以AHA毫无疑问被地下产业抛弃了,甚至在2019年之后,再也没有论文或会议提到这类技术。劫持软件的时代结束了吗? 这份研究将证明Google的安全策略并非不可突破,零权限且无感知的劫持攻击仍有可能出现在高版本Android设备上。 六年前,Android引入了一个新的系统特性。同时也引入了一个潜在的攻击面。本研究将披露攻击面下多个未公开漏洞的细节,任何应用都可以利用漏洞间接攻击SystemUI,并以零权限突破BAL限制。接着,研究将深入SystemServer,同时分析其中潜藏多年的安全问题与设计缺陷,最终利用这些缺陷以侧信道方式来泄露任意一个应用的运行状态,绕过LMKD与后台执行限制,获得长期监控与稳定运行的能力。 在最后,研究将组合这些绕过方案,以武器化一个可以绕过自2014年以来Google发布的所有安全策略的劫持软件。这或许是七年来唯一一个从正面突破安全策略与防御机制,在 Android Q+ 设备上达成零权限且无需额外用户交互的劫持软件(基于AHA)。 二、Introduction 在高版本设备上实现UI劫持攻击之前,有必要知道它在早期Android设备上是怎么运作的。虽然"Preface"章节简要谈到了限制劫持攻击的几种安全策略,同时也提到了绕过策略的可行方案,但如果要理解本研究针对关键组件的分析以及完整利用链的原理,那么通过传统劫持链条来理解安全策略是有必要的。 2.1 Chain Of AHA-based Hijackware 如图为传统劫持软件的大致攻击链条。首先链条将启动Service组件以便进程长时间驻留在后台,接着组件内的代码将不间断获取目标的运行状态,以此来判断其是否来到前台。一旦目标到达前台,也就意味着用户目前正在浏览目标应用,当时机合适时,程序会通过一个带有NEW_TASK标记的Intent对象从后台启动Activity以覆盖用户正在浏览的页面(这一步骤正是AHA),最终达到UI劫持的目的。 可见传统劫持链条相当简单,没有任何一步是多余的,且链条中的所有关键操作在早期Android平台上无需申请任何权限。毫无疑问,简短且有效的攻击链条允许攻击者很好地混淆或隐藏恶意代码,且这种UI覆盖攻击不易被用户察觉。如果Google没有对这类攻击方法采取措施,恐怕直到现在地下产业的开发者仍会采用这种方案攻击用户设备。 在了解攻击链条后,下面将正式进入到关键步骤的技术细节以及安全策略的分析部分。 2.2 Leaking Running State 在API22之前,攻击者可以通过滥用ActivityManager下的接口来泄露第三方应用的运行状态。如下图,getRunningTasks与getRunningAppProcesses函数可以获取到详细的第三方应用信息,其中getRunningTasks接口甚至能够获取到目标任务栈顶的Activity信息,早期的劫持软件正是以此实现高精度的劫持攻击。 在API22之后,这些API全部被Google标记为Deprecated且做了相关限制,目前在API33上调用这些接口将只能返回调用者本身的相关信息。但是在API26之前,攻击者仍可以通过procfs以侧信道方式泄露敏感信息。 如上图,以API19的Android设备为例,以用户u0_a57身份列出/proc目录下的内容,随机选中一个进程并访问其oom_score_adj,显然非特权用户依然有权限浏览第三方进程信息,即使Google对敏感API进行了限制,攻击者仍可以通过procfs获取第三方进程的优先级,以此判断其是否存在于前台。 随后在2017年,Google更新了SELinux策略,彻底禁止了任意应用通过procfs访问第三方应用数据(类似hidepid=2保护)。自此之后劫持软件不得不通过 PACKAGE_USAGE_STATS 权限与 UsageStatsManager 来实现精确劫持,但该权限的开启需要复杂的用户交互,诱导用户开启这种权限并非一件易事,况且许多手机厂商(比如MIUI)会在开启这类权限前强制警告用户可能的安全风险,且警告页面会强制持续10秒。所以在"Preface"章节才会称零权限与无感知的劫持攻击成为了历史。 2.3 Activity Hijack Attack 要实现精准UI劫持,泄露第三方应用的运行状态固然重要,但AHA技术是整个攻击链条的核心,一旦AHA技术不再起作用,整个链条也就无法运行。 在API29之前,AHA仍可以被利用。攻击者会通过调用startActivity启动一个指向Activity且携带NEW_TASK标记的Intent对象以实现AHA攻击。如AOSP框架代码中对于该标记的描述,携带此标记时启动Activity会让系统创建一个新的任务栈(如果这个Activity不包含在任何现有任务栈中),接着这个Activity将立即出现在用户视野内,覆盖屏幕上原本的内容。那么为什么会发生这种情况? 为什么启动新任务栈可以让Activity覆盖屏幕上的内容? 任务栈可以看作装载Activity的容器,任何应用在启动时都将至少创建一个任务栈(假设应用拥有UI)。根据官方文档"Task and back-stack“的描述,应用内启动的Activity都将进入对应的任务栈内,且任务栈可以容纳任意数量的Activity。 在用户视野内,用户将首先看到任务栈中的栈顶活动,而任务栈可以被分为前台任务栈与后台任务栈,后台任务栈不被用户可见,且后台中可以同时存在多个任务栈。前台任务栈为用户可见,但大多数情况下有且仅有一个任务栈存在于前台,用户一次只能与一个前台任务栈进行交互(不考虑分屏或其它情况)。 在了解过任务栈相关的概念后,AHA技术就很好理解了。以API15的AOSP框架代码为例,startActivity函数被调用后,系统将进入ActivityStack#startActivityUncheckedLocked函数,接着代码将判断传入的Intent是否携带NEW_TASK标记,携带标记时系统会将Intent指向的Activity的所在任务栈移动到前台,而由于前台仅允许存在一个任务栈,所以之前存在于前台的第三方应用任务栈将被压入后台,并被新的任务栈顶替。 不难得出结论,AHA的本质事实上就是对前台任务栈的抢占,在合适的时机抢占前台,就能悄无声息地劫持用户的屏幕。事实上早在2013年,由北京航空航天大学与其他相关机构发表的论文《Hijacking Activity Technology Analysis and Research in Android System》(10.1007/978-3-662-43908-1_6)就曾提到过利用这种方法实现AHA攻击。 但自从API29,谷歌开始发布相关策略来阻止这类攻击。下文称该策略为BAL限制(Background Activity Launch restriction)。根据官方文档”Restriction on starting activities“对该限制的描述,任何处于后台的应用都无法启动Activity,除非该应用能够满足一项或多项豁免条件。然而这些条件都十分苛刻,几乎没有任何后台应用可以在不持有危险权限时满足任何一条。如果Activity启动的流程被中断,就不可能创建新任务栈来劫持屏幕内容,所以在API29之后,AHA技术被宣告死亡,目前基于AHA技术的所有相关PoC在API29及以上Android版本都无法正常运行。...