type
status
date
slug
summary
tags
category
icon
password
参考0x00 引言0x01 攻击实施1. 悬浮窗权限申请2. 悬浮窗服务3. 欺骗点击4. 劫持记录5. 测信道击键推断6. 攻击时机0x02 防护1. 系统应用屏蔽第三方悬浮窗2. 普通应用隐藏第三方悬浮窗
参考
- https://github.com/NoahS96/Cloak-And-Dagger 中包含了一些Android覆盖攻击的示例代码
- https://github.com/LLeavesG/Android-Overlay-Hijack 中是我实现的简单demo
0x00 引言
下面的内容引用自参考1
点击劫持
Tapjacking
,是一种欺骗用户进行点击的攻击技术,可存在于任何操作系统和浏览器之中,尽管原理简单,对于普通用户危害却极大,是一种容易忽视的安全威胁。Android中的点击劫持原理如图1所示,当出现重要的、需要进行用户确认的安全对话框时,申请悬浮窗权限的恶意APP在受害APP之上进行部分覆盖,显示一个虚假的界面,隐藏了与安全相关的重要提示信息,但并未覆盖正常APP原有的按钮,诱骗用户进行错误地点击操作,点击事件结果最终传递到受害APP,造成严重的安全后果。0x01 攻击实施
1. 悬浮窗权限申请
在Android系统当中,要能够在其他APP的界面之上显示,恶意APP首先需要申请悬浮窗权限
SYSTEM_ALERT_WINDOW
。该权限允许普通APP使用WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
标志位,在其他APP之上显示需付出。按照Android的设计,该权限仅供很少的APP使用,当Target SDK 大于等于23时,还需要主动请求悬浮窗权限并经过用户手动同意(这也意味着APP Target SDK < 23时是默认开启的)曾有一段时间通过GooglePlay上安装的APP,若申请
SYSTEM_ALERT_WINDOW
权限,在安装后均默认开启。该权限也经常被现实中的恶意APP滥用(参考4)。在具有悬浮窗权限之后,恶意APP还必须寻找一个劫持的时机,判断当前显示的Activity或者Fragment是否是重要的安全确认对话框,仅该时机实施点击劫持,否则达不到应有的攻击效果。当
Build.VERSION.
SDK_INT
>= Build.VERSION_CODES.
M
时需要动态申请,即会弹出一个页面,需要用户手动选择第三方App并且授予对应的悬浮窗权限。2. 悬浮窗服务
悬浮窗以服务的方式进行实现,主要为了保活和监控劫持时机,这就需要组件有独立的生命周期,那就需要
Service
来实现需要注意其中的几个参数:
FLAG_NOT_TOUCH_MODAL
参数如果被设置,那么就意味着用户的触摸事件会被悬浮窗劫持,即可以在悬浮窗上接收到点击的坐标位置,那么也能进行对应的绘图,但是这样触摸事件就不会被传递到下层的其他App,仅能记录用户点击,也就是无法实现欺骗(但是如果完全伪造界面全覆盖就有可能欺骗),一般用于窃取用户输入数据等。
FLAG_NOT_TOUCHABLE
参数如果被设置,悬浮窗仅悬浮在应用界面,而无法触摸也。意味着用户的点击会被传递到下层,但是无法记录点击事件,即可以欺骗无法窃取。
FLAG_WATCH_OUTSIDE_TOUCH
参数可用于监听用户在对话框之外的点击,可用于虚假对话框的退出时机。
其中下面的代码用于调整悬浮窗大小,下面的是铺满全屏的意思。
3. 欺骗点击
恶意APP能够主动控制劫持的时机,可以主动请求敏感操作,在显示用户确认对话框的同时,直接在上悬浮一个虚假的对话框。Android系统之前修复的一系列漏洞,均属于这种类型:
- CVE-2020-0306:蓝牙发现请求确认框覆盖
- CVE-2020-0394:蓝牙配对对话框覆盖
- CVE-2020-0015:证书安装对话框覆盖
- CVE-2021-0314:卸载确认对话框覆盖
- CVE-2021-0487 : 日历调试对话框覆盖
在声明
REQUEST_DELETE_PACKAGES
权限的情况下,通过Intent传递参数请求卸载App,在这个过程中系统会弹窗要求确认,在一些低版本Android(例如8)中该弹窗能够成功被覆盖显示出误导内容,欺骗用户点击OK确认卸载。需要注意覆盖的内容仅为标题和文字,确认和取消按钮不被覆盖,即下面的按钮还是属于PackageInstaller
应用的。在悬浮窗
service
中添加MotionEvent.ACTION_OUTSIDE
事件的处理,即点击到悬浮窗外部事件,此时需要移除悬浮窗windowManager.removeView(button)
,以使得悬浮窗在用户进行点击操作后与弹窗一起消失,防止被发现。当用户点击OK时,其实已经卸载了恶意APP请求卸载的package,而此时恶意APP也已经监听到了ACTION_OUTSIDE
事件,于是主动退出。在整个欺骗过程中,用户难以察觉自己其实已经确认了一次卸载操作。除了”主动请求、主动劫持“这种恶意APP可以主动控制劫持时机的情况,恶意APP还可以监听用户确认对话框出现的其他时机,例如有特定的广播事件、特定的通知,在时机出现的时候进行劫持。例如,在重要对话框出现时,将出现一个通知,恶意APP可以监听通知该通知的出现,通过实现一个NotificationListenerService
,捕捉特定的通知,并启动服务,在原有对话框之上悬浮一个欺骗的对话框 漏洞案例:CVE-2020-0394 CVE-2020-0394 即为这种情况,当蓝牙配对发生时,蓝牙APP会发送一个通知,用户点击通知以后就会出现蓝牙配对对话框,供用户确认或取消
4. 劫持记录
下面的代码则是自定义的悬浮窗布局,可以设置为拦截后在悬浮窗绘图,实现信息窃取等攻击。
效果可以见
SekaiCTF2024 Hijacker赛题
,可以在设备装入恶意App,并且授予任何权限,模拟用户点击输入PIN,要求窃取PIN时就可以使用上面的代码,设置六个不同颜色的框来记录按键顺序,即可读取用户输入的PIN码。当然这里的场景是不出网截图,真实攻击场景是记录左边直接传回,不需要绘图以免被发现。还有一个问题就是点击事件不能传递到下层,这也会暴露。5. 测信道击键推断
- Android为了安全引入了一个标志
FLAG_WINDOW_IS_OBSCURED
来保护用户点击事件的安全性。每次点击时,接收点击事件的控件会收到一个MotionEvent
对象,该对象存储了相关信息。在MotionEvent
对象中添加了该标志(简称为“遮挡标志”)。如果点击事件在到达最终目的地(例如“确定”按钮)之前经过了一个不同的覆盖层,则该标志会被设置为true
。该机制本用于确定在点击控件上层有无覆盖层,依此对用户点击事件进行安全处理。但该机制能够被用于一种测信道攻击,达到推断按键的目的。
FLAG_WATCH_OUTSIDE_TOUCH
标志允许覆盖层接收到任何点击事件的通知,即使点击发生在应用程序之外。为了安全起见,如果点击发生在覆盖层所属的应用程序之外,点击事件的精确坐标会被设置为(0,0)
。只有当点击发生在应用程序内部时,才会提供精确的坐标。这种机制防止攻击者通过坐标推断用户点击的位置,从而保护用户隐私和安全
攻击者在底层按一定的顺序添加透明覆盖层,这些覆盖层不拦截点击事件直接将事件传递到底层。即通过添加
TYPE_SYSTEM_ALERT
、FLAG_NOT_FOCUSABLE
、FLAG_NOT_TOUCHABLE
和FLAG_WATCH_OUTSIDE_TOUCH
标志。这些标志确保每个覆盖层不会拦截用户的点击(即,当用户点击键盘的键时,点击会到达键盘)。在四个按键的情况下,按1234的顺序创建覆盖层,使得视图堆栈中的覆盖层保持这个顺序(覆盖层#1在栈底,覆盖层#4在栈顶)。当点击不同按键时由于
FLAG_WATCH_OUTSIDE_TOUCH
标志,每个覆盖层都会接收到一个MotionEvent
对象,但这些点击事件不包含用户实际点击位置的信息。对于每个覆盖层,obscured
标志的设置取决于用户是否点击了其上方的覆盖层。例如,如果用户点击覆盖层#4,所有接收到的事件的obscured
标志都设置为0,即传递给覆盖层#1,#2,#3的事件的obscured
标志将设置为1。从而推断出用户点击了第四个按键同理可以推断出其他按键的点击
在实际攻击场景中,如下图所示的应用中,为每一个键添加一个覆盖层,覆盖层按照特定的顺序创建,并组织成堆栈:覆盖层#0(左上角)在堆栈底部,而覆盖层#42(右下角)在堆栈顶部。
6. 攻击时机
上文讲了很多,但是一直没讲到在哪个时间点实施攻击。
- 在主动的情况下,例如主动发起卸载App的请求时的情况下,攻击实施时机是可控的,不需要做其他的感知行为。
- 在被动的情况下,由多种方法实现感知,例如:
- 通过获取顶层App的信息来判断是否可以进行攻击,但是这种感知手段在高版本Android系统中显然不可行。
- 还有上文提到的
NotificationListenerService
,在高版本Android中显然也不可能实现。 context-aware
感知:创建一个全屏的不透明覆盖层,捕捉所有用户的点击。在屏幕上创建一个特定位置的“洞”,攻击者希望用户点击这个位置。或通过创建多个覆盖层来形成一个“洞”,洞周围的覆盖层捕捉所有点击,而洞上的覆盖层允许点击通过。由于只有一个“洞”,用户的点击只有一种方式情况能穿透到最底层的App。或者在设置FLAG_WATCH_OUTSIDE_TOUCH
的情况下,如果事件的坐标被设置为(0,0),则表示用户点击了“洞”(否则,事件的坐标会被设置为实际值)。通过这些信息来判断用户是否点击了某些位置,进而确定是否实施下一步的攻击。
到这里其实还有一种很危险的机制导致的攻击,就是无障碍服务。在用户授权无障碍服务后即可实现任意键盘记录和任意点击。
在恶意应用申请使用无障碍服务时会向用户声明危险性和其他信息,但是通过覆盖攻击能够覆盖其中的一些内容,神不知鬼不觉就使得用户进行了授权。如下图中的红框中的内容都能被覆盖实施攻击。在较新版本的 Android 上,如果覆盖处于活动状态,则无法配置辅助功能设置,或者 Android 在进入辅助功能设置页面时会自动禁用任何覆盖,因此不再赘述。
0x02 防护
1. 系统应用屏蔽第三方悬浮窗
Android 系统应用可以申请
HIDE_NON_SYSTEM_OVERLAY_WINDOWS
权限(该权限保护等级为signature|installer
即第三方App无法申请),同时需要在App设置标志位SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS
,设置后第三方App即使拥有SYSTEM_ALERT_WINDOW
权限,也无法进行覆盖。Android中的应用大多采用这种方法进行修复。可以尝试打开悬浮窗后再打开设置App就会发现,悬浮窗神奇不见了,其实就是因为设置App是系统App,为了安全申请了权限并且设置了对应标志位。
2. 普通应用隐藏第三方悬浮窗
为了让开发者能够更好地控制用户在与开发者的应用互动时会看到什么内容,Android 12 引入了隐藏由具有
SYSTEM_ALERT_WINDOW
权限的应用绘制的叠加窗口的功能。声明 HIDE_OVERLAY_WINDOWS
权限后,应用可以调用 setHideOverlayWindows()
以指明当应用自己的窗口可见时所有 TYPE_APPLICATION_OVERLAY
类型的窗口都应隐藏。在显示敏感屏幕(如交易确认流程)时,应用可能会选择这样做。显示 TYPE_APPLICATION_OVERLAY
类型窗口的应用应考虑可能更适合其用例的替代方案,如画中画或气泡。Android 12将默认开启setFilterTouchesWhenObscured
为true
,在有其他APP覆盖的情况下,自动阻止APP接受输入事件。除了下面的特例:
- 被自己覆盖:APP被自己的窗体覆盖
- 被受信任窗体覆盖:包括辅助服务窗体、输入法窗体和Assistant窗体
- 可见度不高的窗体:安卓认为这样的窗体难以展现欺骗内容。
也可以针对重要对话框的View,传入
true
到如下方法public
void setFilterTouchesWhenObscured (boolean enabled)
或者设置View的布局文件属性
android:filterTouchesWhenObscured
为true
,均可以防止点击劫持攻击。此时,如果防御对话框被其他恶意窗体(包括toast、dialog或window)覆盖时,所有的输入事件将会被过滤,防御对话框不再会对点击事件进行响应,也就防止了点击劫持。另外一种方法是重写View的onFilterTouchEventForSecurity
方法,在该方法中可以检测其他APP覆盖的情况- 作者:LLeaves
- 链接:https://lleavesg.top//article/Android-overlay-hijack
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章