本页目录

论文精读:Do Not Give A Dog Bread Every Time He Wags His Tail: Stealing Passwords through Content Queries (CONQUER) Attack (NDSS 2023)

相关链接:

1.Introduction

论文提出了一种攻击方法:CONQUER(Content Queries),利用Android系统辅助模式的findAccessibilityNodeInfosByText(text)API来查询密码输入框节点包含的文本,从而逐位枚举出用户输入的密码。

2.Background

Android Accessibility Service

Android系统提供的辅助功能,本意是帮助残障人士使用手机,但是也可以被恶意应用利用。其中提供的一个API是findAccessibilityNodeInfosByText(text),该API会返回一系列AccessibilityNodeInfo对象,代表包含text的UI组件。

关于Accessibility Service的攻击与防御

被动攻击:被动地嗅探无障碍事件,并收集泄露的凭证;被动攻击往往对环境要求较高,一般性受限。

主动攻击:攻击者主动与系统或应用程序交互,甚至诱导用户在攻击者控制的环境下输入敏感信息。这类攻击通常涉及更多的操作,如劫持输入输出通道,或用精心构造的组件覆盖在输入框的上层;通常更易被用户察觉。

3.Observation and Threat Model

关键发现

Android并没有阻止findAccessibilityNodeInfosByText(text)搜索密码输入框,也没有向用户发出警报。攻击者可以检查API返回的列表是否包含密码输入框来查询text是否在用户的密码中。

威胁模型和假设

攻击目标是窃取密码;假设用户已经安装了恶意软件,并已经授予其无障碍服务的权限;用户使用最新安卓系统(旧版存在一些更易实现的攻击手段);假设用户关闭了“使密码可见”(否则可以通过无障碍事件直接访问到密码)。

4.Overview of CONQUER Attack

攻击流程

1.

恶意软件注册无障碍服务来获取目标事件;(事件由输入密码触发)

2.

用户输入密码时恶意软件将收到通知,并能够确定相关事件;

3.

当恶意软件确定用户输入了密码的特定字符,将会尽可能枚举可能的组合来推断该字符;

4.

用户点击“登录”时恶意软件能够感知到,并知晓此时用户输入已结束。

挑战和解决

区别密码和描述

无障碍服务提供了一些内容描述标签,查询结果同样会考虑这些标签,因此可能会与密码字符混淆在一起;解决方案是论文提出的惰性查询(Lazy Queries)算法。

绕过防御机制

某些应用程序通过阻止与密码输入相关的辅助功能事件来防御攻击,使得攻击者无法被动接收密码输入的通知;解决方案是只需找到密码输入框,然后主动监控长度的变化来确定用户是否输入了密码。

区分大小写

前文提到的API在查询时不区分大小写;解决方案是利用其他的侧信道来重放用户的行为(如大小写切换),例如时间维度上输入一个大写字母后往往需要花费更多时间输入下一个字符。

5.Detail Design of CONQUER

惰性查询算法

这里举一个例子来解释,考虑输入的密码是tendollar,有干扰的描述是enter

1.

首先得到描述的字符集是,记为

2.

输入密码时在补集中查找,显然密码的前三位ten都找不到;

3.

再输入第四位d时,命中;此时遍历中的字符回推上一位,也就是查询edndrdtd,找到nd

4.

重复此过程,最终找到tend

注:仍需考虑一些罕见情况(如密码是描述的子串),此处略。

主动查询以绕过防御

首先找到密码输入框并获取视图ID,然后通过findAccessibilityNodeInfosByViewId(id)找到,然后对这个节点(假设是n)重复调用n.getText().length()来判断用户输入。

侧信道

基于时间的侧信道 - 检测大小写切换

1.

每个人的输入习惯都有很大差别,因此很难设定一个绝对的时间间隙作为标准;因此研究使用相对指标z score,如果向量代表所有按键的时间间隔,那么z score,即“与均值相差几个标准差”。然后进一步定义了一个z score的阈值,使得大小写切换(case switches)真阳性率和非大小写切换(non case switches)真阳性率的乘积最大。

2.

考虑密码是dot#COM#C之间的切换间隔会干扰到期望得到的最一般情况下的输入间隔,因此计算z score时只包括字母输入的间隔,以进一步提高稳定性。

基于状态机的侧信道 - 恢复用户行为

img

总体而言本节介绍一个利用有限自动机来恢复包含大小写切换的用户输入的方法。

基于时间维度的检测仍然有一些功能无法实现,例如无法区分是使用CapsLock还是Shift做切换。自动机设计了三种状态:

小写状态(Lowercase State):默认状态

切换状态(Switching State):单击Shift,下一个输入字符为大写,但输入完成后立即变为小写状态

大写状态(Uppercase State):双击Shift/按下CapsLock,后续输入的字符均为大写

要注意的是,状态机的初始状态不确定,检测到大小写切换后可能转移的状态也不唯一,为此会给每一种可能的状态都创建一个副本(类似树形增长)。论文在此处用数学方法证明了随着大小写切换被检测到的次数增加,总的可能性增长规律符合斐波那契数列;不过此处想表达的中心思想是:尽管总的可能性呈指数增长,但前人关于密码习惯的调查表明通常大写字母是很少的,也就是其实并不会很大。

上图中的红色箭头标识一个键盘切换(keyboard switches)(有些复杂的密码可能需要在字母键盘和符合键盘等等之间反复切换),每次检测到键盘切换后,会为相同的当前密码创建三个不同状态的副本以覆盖所有的可能性。

论文在Rockyou数据集上观察到,对于99.65%的用户密码,大小写切换和键盘切换的次数都很少。此外,对于那些包含字母的密码,其中有99.55%都符合以下三个简单模式:

全是小写字母

全是大写字母

仅首字母是大写的

这些观察到的结果也被用于改善攻击的效率。

6.Evaluation

略。

7.Discussion

源码级别的根因分析

论文参考了findAccessibilityNodeInfosByText的请求处理器Android源码:

private void findAccessibilityNodeInfosByTextUiThread(Message message) {
    final int flags = message.arg1;
    // ...
    // ...
    final int accessibilityViewId = args.argi1;
    final int virtualDescendantId = args.argi2;
    // ...
    List<AccessibilityNodeInfo> infos = null;
    try {
        // ...
        final View root = findViewByAccessibilityId(accessibilityViewId);
        if (root != null && isShown(root)) {
            AccessibilityNodeProvider provider = root.getAccessibilityNodeProvider();
            if (provider != null) {
                infos = provider.findAccessibilityNodeInfosByText(text, virtualDescendantId);
            } else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
                ArrayList<View> foundViews = mTempArrayList;
                foundViews.clear();
                root.findViewsWithText(foundViews, text,
                        View.FIND_VIEWS_WITH_TEXT |
                        View.FIND_VIEWS_WITH_CONTENT_DESCRIPTION |
                        View.FIND_VIEWS_WITH_ACCESSIBILITY_NODE_PROVIDERS);
                // ...
            }
        }
    } finally {
        // ...
    }
}

默认情况下ViewgetAccessibilityNodeProvider()会返回null,此时会调用findViewsWithText()。对于View,这个函数是仅在描述标签中搜索的,因此不会受到本文提出的攻击;然而其子类TextView重写了这个函数,使得其可以搜索自身的文本,并且TextView并没有重写getAccessibilityNodeProvider()(所以会进入else if逻辑),因此TextView和所有TextView的子类都会收到CONQUER攻击的影响。

可能的对策

系统级:一种选择是在API中强制进行安全检查,不允许一个password节点被搜索,但由于存在自定义密码输入框,这种限制通常有遗漏;另一种是使得此API仅搜索内容描述标签,然而这种限制可能会阻碍无障碍的部分功能。

应用级:开发人员不使用基于TextView的类作为密码字段。或者,如果密码字段是从TextView直接或间接继承的,则应该重写findViewsWithText()以确保不会在密码文本中搜索;或重写getAccessibilityNodeProvider()让其返回一个自定义的基于AccessibilityNodeProvider类的对象。

一些局限

意外输入错误、特殊的打字习惯、使用密码管理器自动填写密码等。

8.Related Work

Android无障碍服务利用

基于无障碍服务的密码窃取攻击

Android无障碍服务防御

基于侧信道的按键推断

9.Conclusion

论文提出了一种基于内容查询的密码窃取攻击,并克服了区分密码和描述、区分大小写等难点。