新版本ColorOS使用dexdump脱壳遇到的问题及解决

问题

最近在更新后的ColorOS上使用frida-dexdump进行脱壳时,发现使用原有脚本脱出来的dex文件全都是名为com.coloros.phonemanager-idleoptimize的系统进程。

1
2
3
4
INFO:frida-dexdump:[+] Starting dump to 'F:\apks\top2000-4262\com.coloros.phonemanager-idleoptimize'...
INFO:frida-dexdump:[+] DexMd5=1d1ffe171610319311dda1f25b808f5f, SavePath=F:\apks\top2000-4262\com.coloros.phonemanager-idleoptimize\classes.dex, DexSize=0x6850b4
INFO:frida-dexdump:[+] DexMd5=d047d1f63deeed86ec6d3ab61c38a840, SavePath=F:\apks\top2000-4262\com.coloros.phonemanager-idleoptimize\classes2.dex, DexSize=0x128f000
INFO:frida-dexdump:[+] DexMd5=a2827813aa1c63baccb7ec0660dce01b, SavePath=F:\apks\top2000-4262\com.coloros.phonemanager-idleoptimize\classes3.dex, DexSize=0x691044

尝试

顾名思义,该进程属于系统自带的手机管家,用于优化系统资源与释放内存空间。在网上搜了半天也没有找到碰到一样问题的人,一开始觉得既然如之前的博客所述,frida-dexdump的原理是运行加壳应用,并利用frida来dump出当前正在运行的dex格式文件。那可能是系统更新后自带的进程造成了干扰,把该进程强制关闭应该就好。于是通过adb输入了以下指令:

1
adb shell pm uninstall -k --user 0 com.coloros.phonemanager

重启后,结果倒是不会输出该系统进程了,但是又换了一个com.android.statementservice,脱出来的dex文件全变成了Android的系统服务,这就让人有点难以琢磨了。

1
2
3
4
INFO:frida-dexdump:[+] Starting dump to 'F:\apks\top2000-4262\com.android.statementservice'...
INFO:frida-dexdump:[+] DexMd5=1d1ffe171610319311dda1f25b808f5f, SavePath=F:\apks\top2000-4262\com.android.statementservice\classes.dex, DexSize=0x6850b4
INFO:frida-dexdump:[+] DexMd5=d047d1f63deeed86ec6d3ab61c38a840, SavePath=F:\apks\top2000-4262\com.android.statementservice\classes2.dex, DexSize=0x128f000
INFO:frida-dexdump:[+] DexMd5=a2827813aa1c63baccb7ec0660dce01b, SavePath=F:\apks\top2000-4262\com.android.statementservice\classes3.dex, DexSize=0x691044

在这之后又尝试了更新frida版本等多种处理方法,都没有什么作用,去frida-dexdump的GitHub上看也已经很久都没有更新了,看来这个问题还只能自己来解决。经过反复观看frida的输出日志,终于是发现了一丝端倪:每次脱不同应用时,脱出来的dex文件Md5编码以及size好像都不太一样,是不是说明了每次脱出来的文件其实是对的,只是输出对应进程时出错了呢?

1
2
3
4
5
6
7
8
9
def get_package_name(self):
try:
pid = self._session._impl.pid
for process in self._device.enumerate_processes():
if process.pid == pid:
return process.name
return "dexdump.unnamed.{}".format(pid)
except:
return "dexdump.unnamed"

改正

接着又去研究了一下frida-dexdump的源码,发现其输出文件夹的名称,是在get_package_name方法中通过进程的pid去进行查询得到的,于是使用’frida-ps -Ua’指令debug了一下系统当前的进程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
INFO:Agent:DexDumpAgent<Connection(pid=Session(pid=31748), connected:True), attached=True>: Attach.

PID Name Identifier
----- ----------------- -------------------------
30782 App Market com.heytap.market
32133 Breeno Shortcuts com.coloros.shortcuts
30213 Clock com.coloros.alarmclock
1101 DingTalk com.alibaba.android.rimet
30738 Game Center com.nearme.gamecenter
29788 Magisk com.topjohnwu.magisk
2359 QQ com.tencent.mobileqq
24653 SIM Toolkit com.android.stk
24653 SIM Toolkit com.android.stk
30943 Snapchat com.snapchat.android
3289 WhatsApp com.whatsapp
3359 WhatsApp Business com.whatsapp.w4b
31748 学习强国 cn.xuexi.android
13933 山西银行 com.csii.jincheng

对照后发现与DexDumpAgent所启动后的进程是一致的,但在最后输出时所用的pid还是启动前的,并未更新的31748,被android.statementservice进程所使用。所以才会反复输出该进程名的dex文件夹。

知道问题后就好办了,首先尝试修改了frida-dexdump的源码逻辑,将查询pid并确定输出目录名的过程放在_resume后:

1
2
3
4
5
6
7
8
self.connection = SessionConnection(self._device, self._session)
self.agent = DexDumpAgent(self.connection)
self._resume()
self.package_name = self.get_package_name()
if not self.output:
self.output = os.path.join(os.getcwd(), self.package_name.replace(":", "-"))
os.makedirs(self.output, exist_ok=True)
# self._resume()

这里由于并接触不到_resume的内部源码,也只是多次实验后的尝试。结果又有一点新问题,获取到的中文应用的进程名字也是中文,而不是应用对应的包名,不利于我们脱壳之后的重打包工作,于是经过对于frida接口的一番查询之后,尝试使用process.identifier,即当前运行应用的包名来进行查询,但不知道是版本问题还是其他原因,查询并不能正常进行。左思右想后采取了稍微折中的解决方法,拿到脱壳后的中文文件夹后,再使用Androgurad来对该应用的包名进行查询。

部分参考资料:

frida-dexdump源码:https://github.com/hluwa/frida-dexdump

之前写的使用说明文章:https://canonize.github.io/2023/03/31/shell-apk/

一些其他的博客:

https://blog.csdn.net/qq_40644809/article/details/106814146

https://www.52pojie.cn/thread-1572220-1-1.html

https://blog.csdn.net/weixin_45746055/article/details/119761343

现有Android ICC静态分析工具总结

Android应用程序中的组件之间的交互是非常复杂的,因此,对于安全性的保证也变得十分重要。在当前的安全环境下,Android应用程序的污点分析和ICC静态分析是非常重要的技术。因此,本篇博客将介绍目前主流的Android ICC静态分析工作。

1. FlowDroid

FlowDroid是第一个高度精确的Android应用静态污点分析框架,它采用了全局分析的方式,对应用程序中所有组件进行分析。FlowDroid的敏感性依赖于额外的污点分析,这也导致了cg中方法调用之间数据传输跟踪不精确的问题。

2. Amandroid

Amandroid继承了FlowDroid,采用了组件级模型+ICC来替代整个应用程序级模型。它通过dummyMain来捕获Android允许的所有可能的生命周期方法调用序列,并在此基础上拓展了组件间的控制流。但是,它无法捕捉组件间通过intent.putExtra()传播的数据流,需要对android特有的数据类型进行建模,并跨组件边界地进行数据流分析。

3. Epicc

Epicc介绍了一种推断ICC规格的新技术,旨在解决应用程序中Intent目的地的问题。它通过按需进行的流敏感分析,在一大批市场应用中证明了其可行性。但是,它无法完全解决ICC的上下文问题。

4. IC3

IC3基于Epicc的基础上,介绍了复合MVC常数传播问题,并开发了第一个通用的求解器。它通过健全的字符串分析,提供了一个有效的可扩展性和精度的权衡,可被灵活地应用于设计Android中的新型组件间分析。

5. IccTA

IccTA将FlowDroid和IC3结合起来,试图识别敏感的组件间和应用间信息流。它是第一个试图通过代码无缝连接Android组件的方法,以执行基于ICC的静态污点分析,用于健全和精确的ICC链接和泄漏的检测。

6. StoryDroid

StoryDroid针对每一条控制流转移信息进一步分析,判断其来源是activity本身还是fragment还是内部类,从而得到拥有更详细信息的ATG。它通过两种方式来收集fragment,并利用启发式规则对业务内部的逻辑进行判别与总结。

7. RAICC

RAICC关注于应用中的非典型ICC(AICC),通过对AICC做预处理,使得现有的ICC分析工具可以分析AICC,让CG更完整。

8. ICCBot

ICCBot通过将一个完整的ICC过程从Intent的角度划分为创建、属性修改与发送,并建立其参数间的依赖关系,最后使用逆向的数据流分析得到该ICC的实际发起组件以及方法调用的轨迹。

代码依赖

实际上,以上提到的分析工具间在代码上都存在着密切的联系,可以用以下思维导图来诠释:

Workflow

总结

以上是目前主流的Android ICC静态分析工作。这些工具的发展能够大大提高Android应用程序的安全性,但是,由于Android应用程序非常复杂,仍有许多挑战需要克服,如对组件之间的交互进行准确建模等问题。期待未来的工具能够更加准确地分析Android应用程序的安全性,为用户提供更加安全的使用环境。

根据 XML 文件,计算组件在布局中的位置

在 Android 开发中,布局文件是一种常见的 XML 文件类型,用于描述应用程序界面的布局结构。布局文件中包含了许多组件,如文本框、按钮、图像等。通过计算它们在布局中的位置,可以在空间上反映出部分的组件特性。

本文将以 Python 语言和 xml.etree.ElementTree 库为基础,演示如何读取和解析 XML 文件,并计算组件的位置。

读取和解析 XML 文件

首先,需要读取和解析 XML 文件。在 Python 中,使用 xml.etree.ElementTree 库来实现这个目标。以下是一个示例 XML 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<Button
android:id="@+id/button1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 1" />

<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Button 2" />

</LinearLayout>

在这个示例中,定义了一个名为 LinearLayout 的 XML 根元素,并在其中包含了两个名为 Button 的子元素。下面以这个示例为基础,演示如何计算这两个按钮在布局中的位置。

以下是读取和解析 XML 文件的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import xml.etree.ElementTree as ET

# 加载 XML 文件
tree = ET.parse('layout.xml')

# 获取 XML 根元素
root = tree.getroot()

# 获取子元素列表
children = list(root)

# 遍历子元素
for child in children:
# 获取元素标签名
tag = child.tag
# 获取元素属性
attributes = child.attrib
# 获取元素布局宽度
width = attributes.get('android:layout_width')
# 获取元素布局高度
height = attributes.get('android:layout_height')
# 获取元素顶部垂直坐标
top = attributes.get('android:layout_marginTop')
# 获取元素左侧水平坐标
left = attributes.get('android:layout_marginLeft')
# 输出元素信息
print(tag, width, height, top, left)

在这个代码中,首先使用 ET.parse() 方法加载 XML 文件,并使用 tree.getroot() 方法获取 XML 根元素。然后,使用 list(root) 方法获取根元素的子元素列表,并使用 for 循环遍历每个子元素。在循环中,使用 child.tag 获取元素标签名,使用 child.attrib 获取元素属性,并使用 attributes.get() 方法获取元素布局宽度、高度、顶部垂直坐标和左侧水平坐标。最后,输出元素信息。

计算组件位置

在获取元素信息后,可以根据元素的布局属性,计算组件在布局中的位置。具体来说,可以使用以下公式计算组件的左上角坐标:

$$组件左上角坐标 = (左侧水平坐标, 顶部垂直坐标)$$

在示例代码中,可以使用以下代码计算两个按钮的左上角坐标:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 遍历子元素
for child in children:
# 获取元素属性
attributes = child.attrib
# 获取元素顶部垂直坐标
top = attributes.get('android:layout_marginTop')
# 获取元素左侧水平坐标
left = attributes.get('android:layout_marginLeft')
# 计算组件左上角坐标
x = int(left[:-2])
y = int(top[:-2])
# 输出组件位置
print(child.tag, '位置:', '(', x, ',', y, ')')

在这个代码中,使用 attributes.get() 方法获取元素的顶部垂直坐标和左侧水平坐标,并使用切片操作去掉字符串末尾的单位 dp。然后,将字符串转换为整数,并将计算的左上角坐标 (x, y) 输出到控制台。

总结

本文介绍了如何根据 XML 文件,计算组件在布局中的位置。使用 Python 语言和 xml.etree.ElementTree 库,演示了如何读取和解析 XML 文件,并根据元素的布局属性,计算组件在布局中的位置。

一些思考

值得注意的是,xml中的android:layout_gravity属性和其他属性(例如android:gravity属性)也可以影响 widget 在布局中的位置,因此仅根据 android:layout_marginLeft 等简单的约束属性计算 widget 坐标其实并不够准确。

如果要准确地计算每个 widget 在布局中的坐标,需要使用更复杂的算法,而不仅仅是根据几个简单的约束属性计算坐标。

主流存在的一些python库,例如 AndroidViewClient 和 uiautomator。可以解析布局文件,并提供一些工具来处理布局中的 widget。但是,这些库的主要目的是与 Android 设备进行交互,而不是仅处理布局文件。

单从解析xml文件来计算组件在布局中的位置来说,在python中目前还是一个较复杂和麻烦的事情,缺乏系统性的分析API。

参考资料

详解layout_marginTop:https://www.cnblogs.com/LinQingYang/p/11875681.html

xml.etree.ElementTree库官方文档:https://docs.python.org/3/library/xml.etree.elementtree.html

使用BERT进行简单的文本分类

Word2Vec的一些不足

在上一篇文章中,了解了如何使用word2vec进行文本相似度识别。然而,在实践中发现,word2vec的表现并不尽如人意,原因有很多:

一方面,word2vec的处理单位是单词,碰到短语时的核心处理逻辑还是进行分词后求平均,这样子的做法虽然高效,但在碰到较长的文本时,识别效果下降得非常快。

另一方面,在使用Google预训练的词向量模型时,出现了词汇表缺失的情况。很多常用词一旦变换了不太常见的形态,就因为出现频率下降而被优化掉了,这种情况在英文文本中受单词多态的影响极为明显。

虽然也有尝试各种方法,比如词干提取,迁移学习等来改善这些问题,但总感觉达不到满意的识别率。网上搜到的一些教程也都是比较老的了。跟ai方向的同学交流了下,他们说现在大家都是用GPT来处理文本了,于是自己也去了解了一下如何使用这些前沿的NLP技术。

尝试BERT

看了几篇介绍预训练语义模型的文章后,最终是把目标锁定在了「无监督预训练+下游任务微调」的BERT上。试着用BERT处理现在面临的认证相关文本识别问题。BERT是基于Transformer架构的预训练语言模型,可以用于各种NLP任务,如文本分类、命名实体识别等。

在本文中,将介绍如何使用BERT进行文本分类。具体来说,将使用BERT预训练模型bert-base-chinese,并在最后增加一个分类器,用于输入文本的分类。

安装transformer库

与上篇文章中提到的gensim库不同,将使用transformer库。在PyTorch中安装transformer库非常简单,只需要输入以下命令:

1
pip3 install transformers

文本分类

目标任务是对输入文本进行分类,判断其是否与认证相关,将使用bert-base-chinese模型进行分类。

由于目前只有一个包含100多个认证关键词的字典,多分类的精度肯定指望不上,因此尝试先做一个二分类的问题,即输入文本究竟是否认证相关,后续再进行划分。

但最后发现由于训练样本实在太小,内部种类差异大,且相当于只有正样本等多重原因,在经过反复的训练后,分类器模型的表现还是很差,不管碰到什么文本,都倾向于将其判断为认证相关。

代码实现

下面是使用BERT进行文本分类的代码实现,其中使用了transformer库和PyTorch框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import torch
from transformers import BertTokenizer, BertForSequenceClassification, BertConfig

class AuthClassifier:
def __init__(self, lang='en', model_path=None):
self.lang = lang
if self.lang == 'en':
# 加载英文 BERT 预训练模型和分词器
self.tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
config = BertConfig.from_pretrained('bert-base-uncased', num_labels=2)
self.model = BertForSequenceClassification(config)
keyword_file = "en/keyword.txt"
elif self.lang == 'zh':
# 加载中文 BERT 预训练模型和分词器
self.tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
config = BertConfig.from_pretrained('bert-base-chinese', num_labels=2)
self.model = BertForSequenceClassification(config)
keyword_file = "zh/keyword.txt"
else:
raise ValueError("Unsupported language: %s" % self.lang)

# 从外部文件读取认证相关的关键词列表
with open(keyword_file, 'r', encoding='utf-8') as f:
self.keywords = set([line.strip() for line in f])

# 加载或微调模型
if model_path is not None:
self.model = BertForSequenceClassification.from_pretrained(model_path)
# else:
# train(keyword_file)

def predict(self, text):
# 对输入文本进行分词和编码
tokens = self.tokenizer.encode_plus(text, max_length=128, truncation=True, padding='max_length', add_special_tokens=True, return_attention_mask=True, return_tensors='pt')

# 使用模型进行预测
with torch.no_grad():
outputs = self.model(tokens['input_ids'], attention_mask=tokens['attention_mask'])
logits = outputs[0]

# 获取预测结果
# probs = torch.softmax(logits, dim=1)
# return probs
_, predicted = torch.max(logits, dim=1)

# 判断文本是否包含认证相关的关键词
contains_keyword = any(keyword in text.lower() for keyword in self.keywords)

# 根据预测结果和关键词判断文本是否与认证相关
if predicted.item() == 1 or contains_keyword:
return True
else:
return False

在以上代码中,首先使用from_pretrained方法加载了bert-base-chinese模型和tokenizer。然后,对输入文本进行分词,并将其转换为BERT输入格式。最后,进行包含关系的判断,并打印出预测结果。

总结

本文介绍了如何使用最新的NLP技术BERT进行文本分类。通过使用BERT预训练模型和transformer库,可以轻松地对输入文本进行分类。但由于训练样本较少,分类器的表现还有待改进。下一篇文章中将计划以聚类的方法解决这个问题。

参考资料:

通俗理解BERT:https://blog.csdn.net/v_JULY_v/article/details/127411638

Github项目主页:https://github.com/huggingface/transformers

文本分类任务实例:https://colab.research.google.com/github/huggingface/notebooks/blob/main/examples/text_classification.ipynb#scrollTo=5o4rUteaIrI_

更新Word2Vec模型的多种尝试

Word2Vec是一种广泛使用的自然语言处理技术,它可以将文本转换为向量形式,并且可以在向量空间中对这些向量进行操作,例如计算相似性、聚类等。在这篇文章中,将讲述更新Word2Vec模型的多种尝试。

Word2Vec简介

Word2Vec是一种由Google于2013年发布的自然语言处理工具包。它可以将文本中的单词转换为向量形式,并且可以在向量空间中对这些向量进行操作。Word2Vec包括两种模型:CBOW和Skip-gram。

CBOW(Continuous Bag-of-Words)模型会尝试根据上下文单词来预测中心单词。Skip-gram模型则相反,它会尝试根据中心单词来预测上下文单词。在实践中,Skip-gram模型通常比CBOW模型更好。

训练Word2Vec模型

要训练Word2Vec模型,需要一个大型的文本语料库。可以使用Python中的gensim库来训练Word2Vec模型。首先,需要将文本分成单词,然后将单词列表传递给gensim.models.Word2Vec类的构造函数。

以下是一个简单的例子:

1
2
3
4
from gensim.models import Word2Vec

sentences = [["cat", "say", "meow"], ["dog", "say", "woof"]]
model = Word2Vec(sentences, min_count=1)

上面的代码将创建一个Word2Vec模型,用于训练由两个句子组成的语料库。min_count参数设置单词出现的最小次数。如果一个单词在语料库中出现的次数小于这个值,那么这个单词将被忽略。

可以使用以下代码来查看单词向量:

1
2
vector = model.wv["cat"]
print(vector)

也可以使用以下代码来查找与给定单词最相似的单词:

1
2
similar_words = model.wv.most_similar("cat")
print(similar_words)

更新Word2Vec模型

在某些情况下,希望在不重新训练整个Word2Vec模型的情况下更新它。gensim库提供了两种更新Word2Vec模型的方法。

第一种方法是使用gensim.models.Word2Vec类的build_vocab()方法来构建新的单词表。然后,可以使用train()方法来更新模型。以下是一个简单的例子:

1
2
model.build_vocab([["bird", "fly", "high"]], update=True)
model.train([["bird", "fly", "high"]], total_examples=1, epochs=1)

上面的代码将向模型添加一个新的句子,然后更新模型。注意,这里需要设置update参数为True。

第二种方法是使用gensim.models.Word2Vec类的build_vocab()方法来构建新的单词表。然后,就可以使用gensim.models.KeyedVectors类的add_vectors()方法来添加新的单词向量。以下是一个简单的例子:

1
2
3
model.build_vocab([["bird", "fly", "high"]], update=True)
new_vector = [1, 2, 3]
model.wv.add_vectors(["bird"], [new_vector])

上面的代码将添加一个新的单词向量,然后更新模型。

Google 预训练的 Word2Vec 模型

Google 发布了一款基于 Google News 数据集的预训练 Word2Vec 模型,包含了 300 维向量,覆盖了 300 万个单词和短语。该模型可从此链接下载,二进制文件(GoogleNews-vectors-negative300.bin)解压后大小为 3.4 GB。

在 Python 代码中使用预训练模型需要使用 gensim 库中的 KeyedVectors 类:

1
2
3
4
from gensim.models import KeyedVectors

filename = 'GoogleNews-vectors-negative300.bin'
model = KeyedVectors.load_word2vec_format(filename, binary=True)

使用预训练好的模型可以快速构建起权威的语料库,但在深入使用之后,经常会碰到词汇表缺失的问题,连”forgot”这种常用词,都不在词汇表中,无法进行匹配。

这是因为为了保证效率,Google 预训练的 Word2Vec 模型中并不会保留全部词汇,而是选择性的存储高频词汇,若需要将其用到不同的领域之中,需要对模型的词汇进行更新与微调。

一开始先尝试了手动地往Google 预训练的 Word2Vec 模型中补充缺失的词向量,但遇到了许多错误,查询资料后才发现:

预训练模型的一个缺点是,由于缺少隐藏权重、词汇频率和二叉树,无法继续训练,因此无法在预训练模型上进行迁移学习。

后来又顺着逆向的思维来看,既然预训练模型缺少信息,无法更新,那反过来可不可以用预训练模型来更新自己新建的模型呢?于是尝试使用 intersect_word2vec_format 方法替换自己模型中的词向量与预训练模型中的词向量:

1
your_word2vec_model.intersect_word2vec_format('GoogleNews-vectors-negative300.bin', lockf=1.0, binary=True)

虽然此方法不是迁移学习,但它非常类似。如果有一个小的自定义数据集,并想使用 Google 的预训练模型进行迁移学习,可以使用以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from gensim.models import Word2Vec

sentences = [["坏", "机器人"], ["好", "人类"], ["是的", "这", "是", "Word2Vec", "模型"]]

# size 选项需要设置为 300,以与 Google 的预训练模型相同

word2vec_model = Word2Vec(size=300, window=5, min_count=1, workers=2)

word2vec_model.build_vocab(sentences)

# 将单词向量指定为 Google 预训练模型中的向量和上面定义的句子

# lockf 必须设置为 1.0 才能允许继续训练

word2vec_model.intersect_word2vec_format('./word2vec/GoogleNews-vectors-negative300.bin', lockf=1.0, binary=True)

# 使用自己的数据继续训练

word2vec_model.train(sentences, total_examples=3, epochs=5)

此代码将词向量分配给 Google 预训练模型中的词汇表和上面定义的句子,然后使用自己的数据继续训练。 size 选项需要设置为 300,以与 Google 的预训练模型相同。 lockf 选项需要设置为 1.0 才能允许继续训练。

因为我使用 gensim是 4.0.1 版本的 Word2Vec 模型时,还遇到以下错误:

1
IndexError: index 0 is out of bounds for axis 0 with size 0

在插入向量之前,还需要手动设定一下lockf 值:

1
model.wv.vectors_lockf = np.ones(len(model.wv), dtype=REAL)

结论

在这篇文章中,尝试了多种训练和更新Word2Vec模型的方法。虽然最后以新训练模型成功补上了缺失的词汇,并利用Google News的预训练模型进行了迁移学习,但还是达不到词汇量与准确度上的两全其美。

查资料的过程中发现了Doc2vec,说是支持在线训练,并能自动推断未知词汇,倒是能满足需求的样子,下一步可能会去看看它的使用。

参考资料:

Gensin教程:https://rutumulkar.com/blog/2015/word2vec/

Google预训练模型代码:https://code.google.com/archive/p/word2vec/

更新W2V模型:https://phdstatsphys.wordpress.com/2018/12/27/word2vec-how-to-train-and-update-it/

frontmatter

论文地址:What do all these Buttons do? Statically Mining Android User Interfaces at Scale (arxiv.org)

开源地址:https://bit.ly/3knQHc9

概述

本文提出了一个可以高精度大规模自动挖掘Android应用程序的用户界面模型和行为的工具。给定一个应用程序,Frontmatter静态地提取其中所有声明的屏幕、用户界面元素、它们的文本和图形功能,以及通过与它们交互调用的Android API。

相关工作

作者发现虽然Gator对回调方法建模,以及窗口转换图的分析是上下文敏感的,但对于窗口内容的重建是上下文不敏感的,因此其精度不足以提取具有精确标签分配的UI模型。而Backstage经常无法重建应用程序的完整UI层次结构,缺少许多UI元素和标签。这与Frontmatter形成了鲜明对比,后者能重建应用程序的完整GUI模型。

技术实现

在Soot框架、Flowdroid和Boomerang的基础上,对Android UI进行精确的指向性分析。

CG构建

1. View对象处理

由于不存在对象分配点,SPARK算法无法处理如下图所示的强制转换语句,导致漏报,因此作者在每个findViewById唤起后使用新的expression statement,替换掉原有的cast statement。

图片

2. 异步调用处理

作者插桩了handleMessage与sendMessage以模拟handler中异步消息的发送与接收。并建模了AsyncTask中的四种交互来代替异步的execute调用:

* onPreExecute(),执行之前在UI线程上调用;
* 主函数doInBackground(Params…),在后台线程之后调用;
* 在UI线程上调用以在ProgressUpdate上显示进度(progress…);
* onPostExecute(Result),在后台计算完成发布结果后在UI线程上调用。

3. UI回调处理

由于Flowdroid未考虑在当前activity之外定义的监听器,因此缺少一些执行的代码。并且,识别的回调随后若在dummy main中被调用,其回调对象的构造函数中所需的大多数参数都会被设置为null。因此作者通过在特定监听器之后,立即注入其相应的回调invoke语句来修补代码。

图片

4. 适配器处理

在Android中,Adapter对象提供了一种特殊类型的回调,应该单独处理。因此,为了保留上下文,作者为Adapter::View getView(int position,View convertView,ViewGroup parent)方法中创建的UI元素进行建模。并在使用setAdapter方法调用的语句之后立即注入该适配器的getView调用。

图片

通过以上4步优化处理,Frontmatter在FlowDroid最初构建的cg上扩展了平均25%的边。

UI模型

主要借助Boomerang完成,在构建GUI层次结构的同时,Frontmatter还分析了应用程序行为,即Android框架对用户与某些UI元素的交互所触发的反应。为此,Frontmatter收集了所有Android API,并将这些API附加到每个UI元素的回调方法调用上。

此外,Frontmatter会考虑上下文并修剪不可行的边。它从回调开始沿着调用图行走,只获取适当的边,通过检查if和switch语句的条件,是否包含使用getId或getItemId方法初始化的id常量或变量。实现了同一事件监听器下,路径敏感的UI回调分析。

实验

在AndroZoo中518个应用上进行了测试,Frontmatter为403/518个应用生成了模型,占所有应用程序的78%,而Gator为385/518个应用生成成功,占74%

使用frida-dexdump脱壳apk

Android apk是Android应用程序的安装包,它包含了应用程序的代码、资源、签名等信息。为了保护应用程序的版权和安全,很多开发者会对apk进行加壳处理,即在原始的apk中嵌入一个壳程序,使得反编译工具无法直接获取到原始的代码和资源。特别是在小米市场中下载的国内top应用,加壳率高达60%,这为我们静态分析工具正常解析apk带来了很大的困难,因此在参考了相关资料后,尝试使用Frida框架动态地对apk进行了脱壳处理,即去除壳程序,还原原始的apk。

Frida框架是一个跨平台的动态代码注入工具,它可以在运行时动态注入代码到目标进程中,从而实现Hook和调试的功能。利用Frida框架,我们可以Hook libart.so中的OpenMemory方法,拦截加载的Dex文件,并将其从内存中导出到指定目录。这种方法可以适用于大部分基于Dex加密或隐藏的加壳工具。

此处借助了Github上大佬编写的脚本frida-dexdump,它是一款基于Frida框架开发的脱壳工具,可以在运行时动态注入代码到目标进程中,从而拦截加载的Dex文件,并将其从内存中导出到指定目录。这种方法可以适用于大部分基于Dex加密或隐藏的加壳工具。

环境准备

  • 一台已经Root的Android手机或模拟器
  • 一台安装了Python和Frida-tools的电脑
  • frida-dexdump脚本

使用步骤

  1. 在电脑上安装frida环境,包括frida-tools和frida-dexdump,可以使用pip命令进行安装。此处一定要注意frida与frida-tools版本的对应关系.

    1
    2
    3
    pip install frida-dexdump
    pip install frida==15.1.17
    pip install frida-tools==10.5.4
  2. 在电脑上通过adb命令在/data/local/tmp目录下放置frida-server-15.1.17-android-x86文件(根据自己的架构选择,我用的是frida 15),并给予可执行权限。之后启动frida-server,并查看是否连接成功。

    1
    2
    3
    4
    5
    6
    7
    8
    adb shell
    su
    cd /data/local/tmp
    chmod +x frida-server-15.1.17-android-x86
    ./frida-server-15.1.17-android-x86 &
    exit

    frida-ps -U
  3. 在电脑上运行frida-dexdump脚本,选择要脱壳的apk进程,等待脱壳完成。我这里用的脚本是从服务器上批量下载apk进行脱壳,并自动重打包。由于是同组其他同学写的,这里只放一部分关键代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    os.system("adb install " + localpath)     # 安装apk
    try:
    print('[*] Start to unpack ' + packagename)
    # 这是之前版本的老命令,参考frida-dexdump更新日志后,替换成了新版本的命令
    # fridacmd = "frida-dexdump -n " + packagename + " -f -s 4"
    fridacmd = "frida-dexdump --debug -U -d -f " + packagename
    print(fridacmd)
    # os.system(fridacmd)
    # frida-dexdump在碰到某些应用时仍可能出现运行错误,为防止脚本卡死,设置了3分钟的脱壳用时上限
    subprocess.run(args=fridacmd,timeout=180,shell=True)
    except Exception as e:
    print(str(e))
    fail_wf.write(apkpath)
    fail_wf.write('\n')
    os.system("adb uninstall " + packagename)
    continue
  4. 在手机或模拟器上查看/data/data/包名目录下的dex文件,如果有多个dex文件,可以使用dex2jar工具将其转换为jar文件,并使用jd-gui等工具查看源码。此处注意重打包后,查看源码时,需要关闭加载dex文件的checksum检查,否则会导致加载失败。

注意事项

  • 这种方法需要Root权限和Frida框架支持,可能会影响系统稳定性和安全性。
  • 这种方法可能不适用于一些复杂或特殊的加壳工具,例如动态加载或解密dex的加壳工具。
  • 这种方法可能会触发一些应用程序的反调试或反Hook机制,导致应用程序崩溃或无法正常运行。

部分参考资料:

frida-dexdump地址:https://github.com/hluwa/frida-dexdump

作者写的使用说明:https://mp.weixin.qq.com/s/x8_aa762wpsvA4nhSLoppQ

一些其他的博客:

https://www.jianshu.com/p/82775b65f4c5

https://www.cnblogs.com/gezifeiyang/p/16207042.html

https://zhuanlan.zhihu.com/p/413642920

modified-ICCBot——FragmentPageAdapter建模

背景

在使用基于Soot框架的开源ICC分析工具————ICCBot的过程中,发现虽然其架构支持对于fragment相关ICC的分析,但所建模的种类有限。

举个例子,如下图,通过FragmentPageAdapter滑动启动fragment,这一在标签化设计的应用中常用的ICC,ICCBot并检测不到。

1
2
3
4
5
6
7
8
9
10
11
// 做项目时碰到的一个例子
// RegisterAuthenticActivity通过beginTransaction直接启动LegalRegAuthFragment✔,ICCBot能检测
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_reg_auth, LegalRegAuthFragment.getInstance(extras)).commit();

// LoginActivity通过FragmentPageAdapter滑动启动UserLoginFragment❌,ICCBot无法检测
UserLoginFragment userLogin = new UserLoginFragment();
this.mFragments.add(userLogin);
this.mAdapter = new BaseSimpleViewPagerAdapter(getSupportFragmentManager(), this.titles, this.mFragments);
this.view_pager.setAdapter(this.mAdapter);
this.tablayout.setViewPager(this.view_pager);
this.tablayout.setCurrentTab(0);

本文主要以FragmentPageAdapter为例,讲述如何以模块化的方式拓展ICCBot可识别的ICC种类。

ICCBot识别流程分析

Workflow

ICCBot对于动态启动的Fragment,通过获取每个组件FragmentManager中的信息,建模与对intent类似的FragmentTransaction,构建Object Summary。

  • 创建:FragmentManager().beginTransaction() <=> new Intent() 创建Intent
  • 修改:FragmentManager().add/replace() <=> Intent.set..() 更新Intent
  • 提交:FragmentManager().commit() <=> startActivity(Intent) 发送Intent

模块化拓展FragmentPagerAdapter

因此,如果想要在在Fragment分析中加入对FragmentPagerAdapter的模块化识别,首先需要在src/main/java/client/obj/target/fragment/FragmentAnalyzerHelper.java里的getTypeofUnit()中加入对特定启动语句setAdapter的判断isSetAdapter();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 1.增加一条对Unit的类型判断isSetAdapter()
// setAdapter(Landroid/support/v4/view/PagerAdapter;)V

public boolean isSetAdapter(Unit u) {
// look for invocations of ViewPager.setAdapter

InvokeExpr invExpr = SootUtils.getInvokeExp(u);
if (invExpr == null)
return false;
// check whether setAdapter method is called
if (!invExpr.getMethod().getName().equals("setAdapter")
|| invExpr.getArgCount() != 1)
return false;

return true;
}

捕捉到该关键语句后,再在src/main/java/client/obj/unitHnadler/fragment目录下新建对应的SetAdapterHandler类, 并将该情况补充到FragmentAnalyzerHelper的getUnitHandler()中。

1
2
3
4
5
6
7
8
9
10
11
public String getTypeofUnit(SootMethod m, Unit unit) {
if (isSetAdapter(unit)) {
return "SetAdapter";
}
}

public UnitHandler getUnitHandler(Unit unit) {
if (isSetAdapter(unit)) {
return new SetAdapterHandler();
}
}

接下来,在SetAdapterHandler类中编写我们代码的主题内容,参照了目录下其它handler的书写规范,首先覆写了handleSingleObject(),在其内通过调用analyzeMethodForViewPagers(),实现了自己针对setAdapter语句的数据流分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 2.通过数据流分析FragmentPagerAdapter的初始化方法,来绑定所启动的相应fragment

protected void analyzeMethodForViewPagers() {
// We need at least one fragment base class
if (scSupportViewPager == null && scAndroidXViewPager == null)
return;
// We need at least one class with a method to register a fragment
if (scFragmentStatePagerAdapter == null && scAndroidXFragmentStatePagerAdapter == null
&& scFragmentPagerAdapter == null && scAndroidXFragmentPagerAdapter == null)
return;

// get argument
Value pa = SootUtils.getInvokeExp(unit).getArg(0);
if (!(pa.getType() instanceof RefType))
return;
RefType rt = (RefType) pa.getType();

// check whether argument is of type FragmentStatePagerAdapter
if (!safeIsType(pa, scFragmentStatePagerAdapter) && !safeIsType(pa, scAndroidXFragmentStatePagerAdapter)
&& !safeIsType(pa, scFragmentPagerAdapter) && !safeIsType(pa, scAndroidXFragmentPagerAdapter))
return;

// now analyze getItem() to find possible Fragments
SootMethod getItem = rt.getSootClass().getMethodUnsafe("android.support.v4.app.Fragment getItem(int)");
if (getItem == null)
getItem = rt.getSootClass().getMethodUnsafe("androidx.fragment.app.Fragment getItem(int)");
if (getItem == null || !getItem.isConcrete())
return;

Body b = getItem.retrieveActiveBody();
if (b == null)
return;

// iterate and add any returned Fragment classes
for (Unit getItemUnit : b.getUnits()) {
if (getItemUnit instanceof ReturnStmt) {
ReturnStmt rs = (ReturnStmt) getItemUnit;
Value rv = rs.getOp();
Type type = rv.getType();
if (type instanceof RefType) {
SootClass rtClass = ((RefType) type).getSootClass();
if (rv instanceof Local && (rtClass.getName().startsWith("android.")
|| rtClass.getName().startsWith("androidx.")))
analyzeFragmentCandidates(rs, getItem, (Local) rv);
else
checkAndAddFragment(rtClass);
}
}
}

}

获取了其启动的目标fragment后,补充到getSetDestinationList中,同时将记录语句信息到getSendFragment2Start里,最后在/src/main/java/client/obj/target/fragment/FragmentAnalyzer.java的generateATGInfo方法里生成对应的跳转信息。

1
2
3
4
5
6
// 3.若该Unit的类型判断为SetAdapter,则加入发送fragment的列表并记录目标组件

protected void checkAndAddFragment(SootClass fragmentClass) {
((FragmentSummaryModel)objectModel).getSetDestinationList().add(fragmentClass.getName());
((FragmentSummaryModel)objectModel).getSendFragment2Start().add(unit);
}

ATG中生成的信息:

1
2
3
<node method="LoginActivity initView"
type="setAdapter"
unit="$r7 = virtualinvoke $r1.&lt;android.support.v4.view.PagerAdapter: setAdapter()&gt;()"/>

完整的代码仍然是放在modified-ICCBot项目上:

https://github.com/Canonize/modified-ICCBot/commit/520758f8276d801104834b35d9b5e49d226913fc

部分参考资料:

原版ICCBot论文:https://hanada31.github.io/pdf/icse22_iccbot.pdf

远程仓库地址:https://github.com/Canonize/modified-ICCBot

Android官方文档:https://developer.android.com/reference/androidx/fragment/app/FragmentPagerAdapter

FragmentPagerAdapter原理详解:https://www.jianshu.com/p/d86e31dcc97b

modified-ICCBot——项目依赖升级

背景

在对移动应用进行安全分析的过程中,常常会需要用到静态分析技术,在不运行程序代码的情况下,通过对程序代码静态扫描的形式,实现对应用程序的分析,分析手段包括词法分析、语法分析、控制流分析等。

由于最近在做的课题需要自动化地对应用中跨组件通信(ICC)进行分析,参考了相关资料后,决定以基于Soot框架的开源ICC分析工具————ICCBot入手,进行自定义的修改与拓充。

本文主要讲述如何对ICCBot的项目依赖进行升级与改进。

项目依赖分析与修改

原版的ICCBot由中国科学院软件研究所的燕季薇博士维护。

原版开源地址:https://github.com/hanada31/ICCBot

简单来说,ICCBot是一个基于Soot框架,利用控制流与数据流分析对应用中ICC进行解析的工具,但其项目依赖中所用的Soot版本,以及用来生成CG的Flowdroid版本都年久失修,导致在实际使用的过程中会出现很多老问题。
因此需要对其项目依赖进行以下改良:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<!-- ICCBot版本号是1.0 -->
<modelVersion>4.0.0</modelVersion>
<groupId>ICCBot</groupId>
<artifactId>ICCBot</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<!-- 用于解析命令行参数的包,没啥要改的 -->
<dependencies>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.4</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
  <!-- 这部分是原项目中打包好的Flowdroid jar包,解包后可发现版本仍停留在2.8 -->
<!-- <dependency>
<groupId>soot-infoflow-dummy</groupId>
<artifactId>soot-infoflow-dummy</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/soot-infoflow-dummy.jar</systemPath>
</dependency> -->

<!-- 替换为最新的Flowdroid 2.11.0 ,此处是自己build后打包,带最新Soot 4.4.0依赖的jar包,仍放在项目lib目录下 -->
<dependency>
<groupId>soot-infoflow-cmd-jar-with-dependencies</groupId>
<artifactId>soot-infoflow-cmd-jar-with-dependencies</artifactId>
<version>2.11.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/soot-infoflow-cmd-jar-with-dependencies.jar</systemPath>
</dependency>

此处就出现了一个问题,由于原版ICCBot的Soot依赖是从远程soot-infoflows(即FlowDroid)的Maven仓库中进行获取,然而最新版FlowDroid并未在远程Maven仓库上进行同步。

因此如果按原始方法构建项目依赖的话,会导致所用的Flowdorid与Soot版本不同步(远程的Soot未更新)。需要自己手动地添加新版本Soot依赖,此处采用将其与FlowDroid合并打包的方式添加。

FlowDroid 2.10 :https://github.com/secure-software-engineering/FlowDroid/releases/tag/v2.10

选择soot-infoflow-cmd-jar-with-dependencies.jar,即带Soot依赖的版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<!-- 原版的Soot依赖,版本还停留在FlowDroid 2.8 中所用的Soot 4.3.0-SNAPSHOT, 将其合并到了上一条依赖中-->
<!-- <dependency>
<groupId>de.tud.sse</groupId>
<artifactId>soot-infoflow</artifactId>
<version>2.8</version>
</dependency> -->

<!-- https://mvnrepository.com/artifact/com.microsoft.z3/java-jar -->
<dependency>
<groupId>z3</groupId>
<artifactId>z3</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/com.microsoft.z3_linux.jar</systemPath>
</dependency>
<!-- 原版所用的apktool版本,也一样换成了最新的2.7-->
<dependency>
<groupId>apktool</groupId>
<artifactId>apktool</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/apktool_2.7.0.jar</systemPath>
</dependency>
<dependency>
<groupId>gator</groupId>
<artifactId>gator</artifactId>
<version>0.0.1-SNAPSHOT</version>
<scope>system</scope>
<systemPath>${project.basedir}/lib/sootandroid-1.0-SNAPSHOT-all.jar</systemPath>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.75</version>
</dependency>

<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
</dependencies>
<distributionManagement>
<snapshotRepository>
<id>ossrh</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
</distributionManagement>
<build>
<sourceDirectory>src</sourceDirectory>
<plugins>
<plugin>
<groupId>com.jolira</groupId>
<artifactId>onejar-maven-plugin</artifactId>
<version>1.4.4</version>
<executions>
<execution>
<configuration>
<mainClass>main.java.MainClass</mainClass>
<attachToBuild>true</attachToBuild>
<classifier>onejar</classifier>
<filename>ICCBot.jar</filename>
</configuration>
<goals>
<goal>one-jar</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

新版本适配

修改后还需要替换原版ICCBot中,调起的FlowDroid分析接口:

src/main/java/client/cg/CgConstructor.java

1
2
3
//由原版ICCBot自构的runInfoflow_dummy()方法,改为新版本FlowDroid中功能相同的constructCallgraph()方法
//setupApplication.runInfoflow_dummy();
setupApplication.constructCallgraph();

修改后运行还是会存在不少报错,依旧是类似的适配思路:

首先参照报错的信息,定位到具体的代码,并对其所调用的新旧Soot/FlowDroid版本API,一一结合Github上的更新记录进行了对比与修改。

以此在保留代码原有功能的基础上进行了接口的升级。主要集中在Manifest、Call Graph等功能模块

具体的修改可参考:https://github.com/Canonize/modified-ICCBot/commit/d5c43afb2f6ffdb56e4ff0abb7cc61d778917fc5

新项目依赖构成

新项目开源地址:https://github.com/Canonize/modified-ICCBot

新版Flowdroid/IccTA :
modified-ICCBot/lib/soot-infoflow-cmd-jar-with-dependencies.jar

ICCBot <- 新版Flowdroid/IccTA 部分 :modified-ICCBot/src/main/java/client/cg/

部分参考资料:

原版ICCBot论文:https://hanada31.github.io/pdf/icse22_iccbot.pdf

远程FlowDroid及Soot依赖版本信息:https://mvnrepository.com/artifact/de.tud.sse/soot-infoflow/2.8

FlowDroid版本更新:https://github.com/Canonize/FlowDroid/commits/develop

Soot版本更新:https://github.com/soot-oss/soot/commits/develop

论文阅读:《A Systematic Study of the Consistency of Two-Factor Authentication User Journeys on Top-Ranked Websites》

论文:A Systematic Study of the Consistency of Two-Factor Authentication User Journeys on Top-Ranked Websites

本文对web端2FA间用户体验的一致性进行系统性研究。

2FA是一种在过去几年才开始在网站中得到广泛采用的技术,因此并不是最初网站设计的一部分。目前也不存在2FA相关的通用指南或是最佳实践。

挑战与方法

为了解决这一挑战,作者设计了一种方法,对85个网站上现有的2FA user journey,进行开放和轴向地编码,得出了22个比较因素。这些因素描述了用户从登录/注册期间发现提供2FA支持,到用户了解可用的第二因素选项及其设置过程,再到所选2FA选项的使用和停用的过程。基于这些因素进行比较,确定了常见的2FA设计模式及差异,并强调它们对于用户体验的影响。

自动化爬虫工具需要先验知识的引导,且无法处理2FA过程中所需利用到的额外设备。因此本文采取两名研究人员手动地进行用户旅程探索

图片

作者将2FA用户旅程的探索分为了五个步骤,并分别进行比较因素的提取:

  1. 第一步是在网站上发现2FA支持;(是否有提示,是否强制,是否在常见位置等)
  2. 下一步是网站如何介绍与解释2FA;(是否有描述信息,是否有额外链接)
  3. 接着探索了不同的2FA设置选项以及网站对成功设置的反馈;(是否有关于2FA步骤与因子的引导,是否有默认或强制开启的选项,是否通知恢复机制等)
  4. 然后,检查网站上2FA的使用情况。重新登录并观察网站如何提示身份验证,以及它是否实现了设备记忆等;(是否提供记住设备功能,用户在未预先设置过的情况下登录,是否有第二因子可选列表,还是内部自动选取第二因子)
  5. 最后,探讨了网站设置中的2FA停用过程以及网站如何传达这些更改。(停用时是否有解释,验证,通知信息等)
    图片

具体来说,作者应用紧急编码中的知识,特别是基于基础理论的开放和轴向编码,这些编码技术通常应用于文本内容的定性数据分析。为了沿用这些既定的方法,作者将录屏得到的2FA用户旅程视为了半结构化的访谈

两名研究人员分别对一组记录的用户旅程进行了评估,并将观察到的旅程划分为有意义的部分,他们为这些部分分配了概念(即代码)。随后是轴向编码,两位研究人员通过归纳和演绎将这些概念组合成类别。在对比较因素列表达成一致后,研究人员讨论了每个网站如何匹配每个比较因素(例如,完全匹配、部分匹配或完全匹配)。

实验结论

实验数据集来源于2FA Directory,对其中属于同一域名下的子域名进行了合并,并选取最具代表性的域名展开分析(例如cloud.google.com和mail.google.com等都是采用google账号登录,选取mail.google.com即可)。并排除了一部分无法创建账号的网站类别(银行、政府等)

作者使用海明距离比较网站间相似性,香农熵计算因子一致性。

图片

结果表明,尽管2FA没有一个总体的设计模式。但其设计空间被聚集在具有非常相似模式的网站群中,其中一些受到顶级网站的青睐,另一些则受到不太流行网站的青睐。

图片

此外,几乎所有网站都同意的2FA设计方面是:它是一个可选功能,如何被调用和描述,以及应该能在帐户设置中找到它。数据集中仅六个网站强制要求2FA,其中大多数属于加密货币类别。

图片

相比之下,在设置和使用2FA的关键步骤中,这些网站所实施的策略存在着不一致性,例如支持的2FA技术不同、设备记忆选项的不一致、或向用户提供不同程度的反馈

  • 三种基本策略之间存在着几乎均等的分歧:“只提供一种2FA选项”、“提供多种2FA选项,但一次只能激活一种”和“提供多个2FA选项且同时可以激活多种2FA”
  • 在支持多个2FA选项的网站中,有一半在向用户提供其他选项之前,只会提供特定的2FA选项。例如,只有在提供电话号码后,用户才能设置安全密钥或TOTP作为替代。
  • 只有一个网站在登录期间,会提前询问用户希望使用哪个2FA选项。绝大多数网站都使用内部指标来确定应使用哪个2FA选项登录,用户只能通过“use a different method”或“do you have difficulties”菜单来选择另一个2FA选项。

图片

  • 四分之三的网站提供了恢复选项,其中大多数网站还向用户解释设置恢复选项的重要性或忽略设置恢复选项所带来的风险。这些网站中首选的恢复选项是可打印的一次性代码。此外,网站在强制设置恢复选项方面非常一致。几乎四分之三的具有恢复选项的网站都会敦促用户设置恢复,只有六个网站在2FA设置期间强制执行此操作。
  • 数据集中超过一半的网站不支持设备记忆,即用户无法在未来登录时明确选择跳过第二因素身份验证。对于支持这一功能的网站,我们发现它们不仅以不同的方式描述它,而且它们的记忆逻辑也不同。几乎三分之二的网站需要用户选择使用这一功能,五分之一的网站要求用户明确选择不记住设备,另外五分之一网站在不询问用户的情况下自动记忆设备cookie。
  • 数据集中除五个网站外,其他所有网站都支持停用2FA。然而,只有少数网站向用户传达停用2FA的风险(例如,更容易的账户劫持)。此外,只有大约一半的网站在停用前验证用户身份、通知用户停用、或在网站设置中显示成功消息。