网络安全的机器学习秘籍-全-
网络安全的机器学习秘籍(全)
原文:
annas-archive.org/md5/ccb0ce37f50c14ac9195f73f7edac92e
译者:飞龙
序言
当前的网络威胁是每个组织面临的关键问题之一。本书使用了多种 Python 库,如 TensorFlow、Keras、scikit-learn 等,揭示了网络安全研究人员面临的常见及不常见的挑战。
本书将帮助读者实施智能解决方案,以应对现有的网络安全挑战,并构建前沿的实现方案,以满足日益复杂的组织需求。通过本书的学习,读者将能够使用机器学习(ML)算法,通过基于实例的方法,遏制网络安全威胁。
本书适合的人群
本书面向网络安全专业人员和安全研究人员,帮助他们通过实现机器学习算法和技术,提升计算机安全技能。基于实例的本书也适合那些希望将智能技术引入网络安全领域的数据科学家和机器学习开发人员。本书要求具备 Python 的基础知识,并且熟悉网络安全的基本概念。
本书的内容
第一章,网络安全的机器学习,介绍了用于网络安全的机器学习基本技术。
第二章,基于机器学习的恶意软件检测,展示了如何对样本进行静态和动态分析。你还将学习如何应对网络安全领域中机器学习面临的重要挑战,如类别不平衡和假阳性率(FPR)约束。
第三章,高级恶意软件检测,涵盖了恶意软件分析的更高级概念。我们还将讨论如何处理混淆和打包的恶意软件,如何扩展 N-gram 特征的收集,以及如何使用深度学习来检测甚至创造恶意软件。
第四章,社交工程中的机器学习,解释了如何使用机器学习构建一个 Twitter 钓鱼机器人。你还将学习如何使用深度学习来让目标说出你希望他们说的任何话。本章还会讲解一个谎言检测周期,并展示如何训练递归神经网络(RNN),使其能够生成与训练数据集中的评论相似的新评论。
第五章,使用机器学习进行渗透测试,涵盖了广泛的机器学习技术,用于渗透测试和安全防护措施。它还涵盖了一些更专业的主题,如去匿名化 Tor 流量、通过击键动态识别未授权访问,以及检测恶意网址。
第六章,自动入侵检测,介绍了使用机器学习设计和实现几种入侵检测系统。它还讨论了依赖示例、成本敏感、极度不平衡且具有挑战性的信用卡欺诈问题。
第七章,利用机器学习保护和攻击数据,涵盖了如何利用机器学习保护和攻击数据的相关方案。它还讨论了如何应用机器学习来攻击硬件安全,利用人工智能攻击 物理不可克隆函数(PUFs)。
第八章,安全与隐私的人工智能,解释了如何使用 TensorFlow Federated 框架中的联邦学习模型。它还包括加密计算基础的操作步骤,并展示了如何使用 Keras 和 TensorFlow Privacy 实现并训练一个差分隐私的深度神经网络来处理 MNIST 数据集。
附录 提供了创建基础设施以应对网络安全数据上机器学习挑战的指南。本章还提供了使用虚拟 Python 环境的指南,允许你在不同的 Python 项目上无缝工作,避免包冲突。
如何最大化利用本书
你需要具备基本的 Python 知识和网络安全知识。
下载示例代码文件
你可以从你的账户下载本书的示例代码文件,访问 www.packt.com。如果你是在其他地方购买的本书,你可以访问 www.packtpub.com/support 并注册,直接将文件通过电子邮件发送给你。
你可以按照以下步骤下载代码文件:
-
登录或注册 www.packt.com。
-
选择 "支持" 标签。
-
点击 "代码下载"。
-
在搜索框中输入书名并按照屏幕上的指示操作。
文件下载完成后,请确保使用最新版本的工具解压缩或提取文件夹:
-
适用于 Windows 的 WinRAR/7-Zip
-
适用于 Mac 的 Zipeg/iZip/UnRarX
-
适用于 Linux 的 7-Zip/PeaZip
本书的代码包也托管在 GitHub 上,链接为 github.com/PacktPublishing/Machine-Learning-for-Cybersecurity-Cookbook
。如果代码有更新,GitHub 上的现有仓库也会更新。
我们还提供了来自我们丰富书籍和视频目录中的其他代码包,访问 github.com/PacktPublishing/
查看!
下载彩色图像
我们还提供了一个 PDF 文件,其中包含本书中使用的截图/图表的彩色图像。你可以在这里下载: static.packt-cdn.com/downloads/9781789614671_ColorImages.pdf
。
使用的约定
本书中使用了多种文本约定。
CodeInText
:指示文本中的代码词、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 账号名。例如:“将标签附加到X_outliers
。”
一段代码的设置如下:
from sklearn.model_selection import train_test_split
import pandas as pd
任何命令行输入或输出都如下所示:
pip install sklearn pandas
粗体:表示新术语、重要词汇或您在屏幕上看到的词汇。例如,菜单或对话框中的词汇以这种方式出现在文本中。示例:“超参数调整的最基本方法叫做网格搜索。”
警告或重要提示以这种方式呈现。提示和技巧以这种方式呈现。
章节
本书中有几个常见的标题(准备工作,如何做...,原理...,还有更多...,和另见)。
为了清晰地说明如何完成一个教程,使用以下这些章节:
准备工作
本节介绍您在执行教程时的预期,并描述了设置所需软件或任何必要的初始设置。
如何做…
本节包含完成教程所需的步骤。
它是如何工作的…
本节通常包含对上一节的详细解释。
还有更多…
本节包含关于教程的更多信息,帮助您更好地理解该教程。
另见
本节提供了与教程相关的其他有用信息链接。
联系我们
我们始终欢迎读者的反馈。
一般反馈:如果您对本书的任何部分有疑问,请在邮件主题中提到书名,并通过customercare@packtpub.com
联系我们。
勘误:尽管我们已尽力确保内容的准确性,但错误仍然可能发生。如果您在本书中发现了错误,我们非常感激您能向我们报告。请访问www.packt.com/submit-errata,选择您的书籍,点击勘误提交表单链接,并填写相关细节。
盗版:如果您在互联网上发现任何我们作品的非法复制品,我们非常感激您能提供该材料的网址或网站名称。请通过copyright@packt.com
与我们联系,并提供该材料的链接。
如果您有意成为作者:如果您在某个领域有专业知识,并且有意写书或为书籍贡献内容,请访问authors.packtpub.com。
评论
请留下评论。在您阅读并使用本书后,为什么不在购买该书的网站上留下评论呢?潜在的读者可以通过查看并利用您的公正意见来做出购买决定,我们在 Packt 能了解您对我们产品的看法,作者也可以看到您对他们书籍的反馈。谢谢!
欲了解更多关于 Packt 的信息,请访问packt.com。
第一章:网络安全中的机器学习
在本章中,我们将介绍机器学习的基本技术。我们将在全书中使用这些技术来解决有趣的网络安全问题。我们将涵盖基础算法,如聚类和梯度提升树,并解决常见的数据挑战,如数据不平衡和假阳性约束。在网络安全领域,机器学习实践者处于一个独特且令人兴奋的位置,能够利用大量数据并在不断发展的环境中创造解决方案。
本章涵盖以下内容:
-
训练-测试分割你的数据
-
标准化你的数据
-
使用主成分分析(PCA)总结大型数据
-
使用马尔可夫链生成文本
-
使用 scikit-learn 进行聚类
-
训练 XGBoost 分类器
-
使用 statsmodels 分析时间序列
-
使用 Isolation Forest 进行异常检测
-
使用哈希向量器和 tf-idf 与 scikit-learn 进行自然语言处理(NLP)
-
使用 scikit-optimize 进行超参数调整
技术要求
在本章中,我们将使用以下内容:
-
scikit-learn
-
Markovify
-
XGBoost
-
statsmodels
安装说明和代码可以在github.com/PacktPublishing/Machine-Learning-for-Cybersecurity-Cookbook/tree/master/Chapter01
找到。
训练-测试分割你的数据
在机器学习中,我们的目标是创建一个能够执行从未被明确教过的任务的程序。我们实现这一目标的方法是利用我们收集的数据来训练或拟合一个数学或统计模型。用于拟合模型的数据称为训练数据。训练得到的模型随后用于预测未来的、以前未见过的数据。通过这种方式,程序能够在没有人工干预的情况下处理新情况。
对于机器学习实践者来说,一个主要的挑战是过拟合的风险——即创建一个在训练数据上表现良好,但无法对新的、从未见过的数据进行推广的模型。为了应对过拟合问题,机器学习实践者会预留出一部分数据,称为测试数据,并仅用于评估训练模型的性能,而不是将其包含在训练数据集中。精心预留测试集是训练网络安全分类器的关键,在这里,过拟合是一个无处不在的危险。一个小小的疏忽,例如仅使用来自某一地区的良性数据,可能导致分类器效果差。
有多种方法可以验证模型性能,比如交叉验证。为了简化,我们将主要关注训练-测试分割。
准备工作
本教程的准备工作包括在pip
中安装 scikit-learn 和pandas
包。安装命令如下:
pip install sklearn pandas
此外,我们还提供了north_korea_missile_test_database.csv
数据集,以供本教程使用。
如何操作……
以下步骤演示了如何将一个数据集(包含特征X
和标签y
)拆分为训练集和测试集:
- 首先导入
train_test_split
模块和pandas
库,并将特征读取到X
中,将标签读取到y
中:
from sklearn.model_selection import train_test_split
import pandas as pd
df = pd.read_csv("north_korea_missile_test_database.csv")
y = df["Missile Name"]
X = df.drop("Missile Name", axis=1)
- 接下来,随机将数据集及其标签拆分为一个训练集(占原始数据集的 80%)和一个测试集(占原始数据集的 20%):
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=31
)
- 我们再次应用
train_test_split
方法,以获得一个验证集,X_val
和y_val
:
X_train, X_val, y_train, y_val = train_test_split(
X_train, y_train, test_size=0.25, random_state=31
)
- 我们最终得到了一个训练集,占原始数据的 60%,一个验证集占 20%,一个测试集占 20%。
以下截图显示了输出结果:
它是如何工作的……
我们从读取数据集开始,数据集包含朝鲜的历史和持续的导弹实验。我们的目标是根据剩余特征(如设施和发射时间)预测导弹类型。这是步骤 1 的内容。在步骤 2 中,我们应用 scikit-learn 的train_test_split
方法将X
和y
细分为一个训练集X_train
和y_train
,以及一个测试集X_test
和y_test
。test_size = 0.2
参数表示测试集占原始数据的 20%,其余部分放入训练集中。random_state
参数允许我们复现相同的随机生成的拆分。接下来,关于步骤 3,需要注意的是,在实际应用中,我们通常希望比较几个不同的模型。使用测试集选择最佳模型的危险在于,我们可能会过度拟合测试集。这类似于数据钓鱼的统计学错误。为了应对这一危险,我们创建了一个额外的数据集,称为验证集。我们在训练集上训练模型,使用验证集进行比较,最后使用测试集来获得我们选择的模型的准确性能指标。因此,在步骤 3 中,我们选择参数,使得从数学角度来看,最终结果包含 60%的训练集,20%的验证集和 20%的测试集。最后,我们通过使用len
函数来计算数组的长度,来仔细检查我们的假设(步骤 4)。
标准化你的数据
对于许多机器学习算法,性能对特征的相对尺度非常敏感。因此,通常需要对特征进行标准化。标准化特征意味着将其所有值平移,使其均值为 0,并对其进行缩放,使其方差为 1。
标准化在某些情况下非常有用,特别是在处理文件的 PE 头信息时。PE 头信息包含极大的数值(例如,SizeOfInitializedData
字段),也包含非常小的数值(例如,节区的数量)。对于某些机器学习模型,如神经网络,特征间的巨大差异会降低模型的表现。
准备工作
本示例的准备工作包括在 pip
中安装 scikit-learn
和 pandas
包。请执行以下步骤:
pip install sklearn pandas
此外,你将在本仓库中找到一个名为 file_pe_headers.csv
的数据集,供本示例使用。
如何实现...
在接下来的步骤中,我们使用 scikit-learn 的 StandardScaler
方法来标准化数据:
- 首先,导入所需的库并收集数据集
X
:
import pandas as pd
data = pd.read_csv("file_pe_headers.csv", sep=",")
X = data.drop(["Name", "Malware"], axis=1).to_numpy()
数据集 X
如下所示:
- 接下来,使用
StandardScaler
实例对X
进行标准化:
from sklearn.preprocessing import StandardScaler
X_standardized = StandardScaler().fit_transform(X)
标准化后的数据集如下所示:
工作原理...
我们从读取数据集开始(步骤 1),该数据集包含一组 PE 文件的 PE 头信息。不同的列差异很大,有些列的数据量达到数十万文件,而有些则只有个位数。因此,某些模型,如神经网络,在处理这些非标准化数据时表现不佳。在步骤 2 中,我们实例化了StandardScaler()
,然后应用.fit_transform(X)
对 X
进行重新缩放。最终,我们获得了一个重新缩放的数据集,其中的列(对应特征)的均值为 0,方差为 1。
使用主成分分析(PCA)对大数据进行总结
假设你想要构建一个预测模型,预测某人在 45 岁时的预期净资产。需要考虑的变量有很多:智商、当前净资产、婚姻状况、身高、地理位置、健康状况、教育背景、职业状态、年龄等等,甚至可能包括 LinkedIn 连接数量或 SAT 分数等变量。
拥有如此多特征的难题是多方面的。首先,数据量庞大,这将导致高存储成本和计算时间。其次,拥有庞大的特征空间时,模型准确性依赖于大量的数据。也就是说,信号与噪声之间的区别变得更加困难。因此,在处理像这样的高维数据时,我们通常会采用降维技术,例如 PCA。关于该主题的更多信息,请参考 en.wikipedia.org/wiki/Principal_component_analysis
。
PCA 使我们能够将原始特征转换为较少的新的特征,这些特征是由原始特征组成的,并且具有最大的解释能力。此外,由于新特征是旧特征的线性组合,这使得我们能够对数据进行匿名化处理,这在处理例如金融信息时非常方便。
准备工作
这个实例的准备工作包括安装scikit-learn
和pandas
包,可以使用pip
安装。命令如下:
pip install sklearn pandas
此外,我们将使用与前一个实例相同的数据集,malware_pe_headers.csv
。
如何操作...
在本节中,我们将演示如何在数据上使用 PCA 的一个实例:
- 首先导入必要的库并读取数据集:
from sklearn.decomposition import PCA
import pandas as pd
data = pd.read_csv("file_pe_headers.csv", sep=",")
X = data.drop(["Name", "Malware"], axis=1).to_numpy()
- 在应用 PCA 之前,先标准化数据集:
from sklearn.preprocessing import StandardScaler
X_standardized = StandardScaler().fit_transform(X)
- 实例化一个
PCA
实例,并使用它来降低我们数据的维度:
pca = PCA()
pca.fit_transform(X_standardized)
- 评估降维的效果:
print(pca.explained_variance_ratio_)
以下截图显示了输出结果:
它是如何工作的...
我们首先读取数据集并进行标准化,方法参照标准化数据的步骤(步骤 1 和 2)。 (在应用 PCA 之前,必须使用标准化数据)。接下来,我们实例化一个新的 PCA 转换器,并使用它来进行转换学习(fit)并将转换应用于数据集,使用fit_transform
(步骤 3)。在步骤 4 中,我们分析我们的转换。特别需要注意的是,pca.explained_variance_ratio_
的元素表示在每个方向上所占的方差比例。总和为 1,表示如果我们考虑数据所在的整个空间,所有的方差都已经被解释。然而,通过仅选择前几个方向,我们就能解释大部分的方差,同时减少维度。在我们的例子中,前 40 个方向就解释了 90%的方差:
sum(pca.explained_variance_ratio_[0:40])
这将产生以下输出:
0.9068522354673663
这意味着我们可以将特征的数量从 78 减少到 40,同时保留 90%的方差。这意味着 PE 头部的许多特征是高度相关的,这是可以理解的,因为这些特征并非设计为独立的。
使用马尔可夫链生成文本
马尔可夫链是简单的随机模型,其中一个系统可以处于多个状态之一。要知道系统下一个状态的概率分布,只需知道系统当前的状态即可。这与一种系统不同,在这种系统中,后续状态的概率分布可能依赖于系统的过去历史。这个简化假设使得马尔可夫链能够轻松应用于许多领域,并且效果出奇的好。
在这个实例中,我们将使用马尔可夫链生成虚假评论,这对渗透测试评论系统的垃圾信息检测器非常有用。在后续的实例中,您将把技术从马尔可夫链升级到 RNN。
准备工作
本食谱的准备工作包括在pip
中安装markovify
和pandas
包。命令如下:
pip install markovify pandas
此外,本章的代码库中包含一个 CSV 数据集,airport_reviews.csv
,它应与本章的代码放在一起。
如何操作…
让我们通过执行以下步骤来看看如何使用马尔可夫链生成文本:
- 从导入
markovify
库和我们希望模仿风格的文本文件开始:
import markovify
import pandas as pd
df = pd.read_csv("airport_reviews.csv")
作为示例,我选择了一组机场评论作为我的文本:
"The airport is certainly tiny! ..."
- 接下来,将单独的评论合并成一个大的文本字符串,并使用机场评论文本构建一个马尔可夫链模型:
from itertools import chain
N = 100
review_subset = df["content"][0:N]
text = "".join(chain.from_iterable(review_subset))
markov_chain_model = markovify.Text(text)
在幕后,库会根据文本计算过渡词的概率。
- 使用马尔可夫链模型生成五个句子:
for i in range(5):
print(markov_chain_model.make_sentence())
- 由于我们使用的是机场评论,执行前面的代码后,我们将得到以下输出:
On the positive side it's a clean airport transfer from A to C gates and outgoing gates is truly enormous - but why when we arrived at about 7.30 am for our connecting flight to Venice on TAROM.
The only really bother: you may have to wait in a polite manner.
Why not have bus after a short wait to check-in there were a lots of shops and less seating.
Very inefficient and hostile airport. This is one of the time easy to access at low price from city center by train.
The distance between the incoming gates and ending with dirty and always blocked by never ending roadworks.
令人惊讶的逼真!尽管这些评论需要筛选出最好的。
- 生成
3
个句子,每个句子的长度不超过140
个字符:
for i in range(3):
print(markov_chain_model.make_short_sentence(140))
使用我们的示例,我们将看到以下输出:
However airport staff member told us that we were put on a connecting code share flight.
Confusing in the check-in agent was friendly.
I am definitely not keen on coming to the lack of staff . Lack of staff . Lack of staff at boarding pass at check-in.
它是如何工作的…
我们从导入 Markovify 库开始,这个库用于马尔可夫链计算,并读取文本,这将为我们的马尔可夫模型提供信息(步骤 1)。在步骤 2 中,我们使用文本创建马尔可夫链模型。以下是文本对象初始化代码中的相关片段:
class Text(object):
reject_pat = re.compile(r"(^')|('$)|\s'|'\s|[\"(\(\)\[\])]")
def __init__(self, input_text, state_size=2, chain=None, parsed_sentences=None, retain_original=True, well_formed=True, reject_reg=''):
"""
input_text: A string.
state_size: An integer, indicating the number of words in the model's state.
chain: A trained markovify.Chain instance for this text, if pre-processed.
parsed_sentences: A list of lists, where each outer list is a "run"
of the process (e.g. a single sentence), and each inner list
contains the steps (e.g. words) in the run. If you want to simulate
an infinite process, you can come very close by passing just one, very
long run.
retain_original: Indicates whether to keep the original corpus.
well_formed: Indicates whether sentences should be well-formed, preventing
unmatched quotes, parenthesis by default, or a custom regular expression
can be provided.
reject_reg: If well_formed is True, this can be provided to override the
standard rejection pattern.
"""
最重要的参数是state_size = 2
,这意味着马尔可夫链将计算连续单词对之间的转换。为了生成更逼真的句子,可以增加该参数,但代价是句子看起来不那么原始。接下来,我们应用训练好的马尔可夫链生成一些示例句子(步骤 3 和 4)。我们可以清楚地看到,马尔可夫链捕捉到了文本的语气和风格。最后,在步骤 5 中,我们使用我们的马尔可夫链生成一些模仿机场评论风格的推文
。
使用 scikit-learn 进行聚类
聚类是一类无监督机器学习算法,其中数据的部分被基于相似性进行分组。例如,聚类可能由在 n 维欧几里得空间中紧密相邻的数据组成。聚类在网络安全中很有用,可以用来区分正常和异常的网络活动,并帮助将恶意软件分类为不同的家族。
准备工作
本食谱的准备工作包括在pip
中安装scikit-learn
、pandas
和plotly
包。命令如下:
pip install sklearn plotly pandas
此外,仓库中为本食谱提供了一个名为file_pe_header.csv
的数据集。
如何操作…
在接下来的步骤中,我们将看到 scikit-learn 的 K-means 聚类算法在玩具 PE 恶意软件分类上的演示:
- 首先导入并绘制数据集:
import pandas as pd
import plotly.express as px
df = pd.read_csv("file_pe_headers.csv", sep=",")
fig = px.scatter_3d(
df,
x="SuspiciousImportFunctions",
y="SectionsLength",
z="SuspiciousNameSection",
color="Malware",
)
fig.show()
以下截图显示了输出:
- 提取特征和目标标签:
y = df["Malware"]
X = df.drop(["Name", "Malware"], axis=1).to_numpy()
- 接下来,导入 scikit-learn 的聚类模块,并将 K-means 模型(包含两个聚类)拟合到数据:
from sklearn.cluster import KMeans
estimator = KMeans(n_clusters=len(set(y)))
estimator.fit(X)
- 使用我们训练好的算法预测聚类:
y_pred = estimator.predict(X)
df["pred"] = y_pred
df["pred"] = df["pred"].astype("category")
- 为了查看算法的表现,绘制算法的聚类结果:
fig = px.scatter_3d(
df,
x="SuspiciousImportFunctions",
y="SectionsLength",
z="SuspiciousNameSection",
color="pred",
)
fig.show()
以下截图显示了输出:
结果虽然不完美,但我们可以看到聚类算法捕捉到了数据集中的大部分结构。
它是如何工作的...
我们首先从一组样本中导入 PE 头部信息的数据集(步骤 1)。该数据集包含两类 PE 文件:恶意软件和良性文件。然后,我们使用 plotly 创建一个漂亮的交互式 3D 图(步骤 1)。接着,我们准备好将数据集用于机器学习。具体来说,在步骤 2 中,我们将X
设为特征,将 y 设为数据集的类别。由于数据集有两个类别,我们的目标是将数据分成两个组,以便与样本分类相匹配。我们使用 K-means 算法(步骤 3),有关此算法的更多信息,请参阅:en.wikipedia.org/wiki/K-means_clustering
。在经过充分训练的聚类算法下,我们准备好对测试集进行预测。我们应用聚类算法来预测每个样本应该属于哪个聚类(步骤 4)。在步骤 5 中观察结果时,我们发现聚类捕捉到了大量的潜在信息,因为它能够很好地拟合数据。
训练 XGBoost 分类器
梯度提升被广泛认为是解决一般机器学习问题时最可靠、最准确的算法。我们将在未来的食谱中利用 XGBoost 来创建恶意软件检测器。
准备开始
本食谱的准备工作包括在pip
中安装 scikit-learn、pandas
和xgboost
包。安装命令如下:
pip install sklearn xgboost pandas
此外,仓库中提供了名为file_pe_header.csv
的数据集,供本食谱使用。
如何实现...
在接下来的步骤中,我们将演示如何实例化、训练和测试 XGBoost 分类器:
- 开始读取数据:
import pandas as pd
df = pd.read_csv("file_pe_headers.csv", sep=",")
y = df["Malware"]
X = df.drop(["Name", "Malware"], axis=1).to_numpy()
- 接下来,进行训练-测试数据集划分:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3)
- 创建一个 XGBoost 模型实例,并在训练集上训练它:
from xgboost import XGBClassifier
XGB_model_instance = XGBClassifier()
XGB_model_instance.fit(X_train, y_train)
- 最后,评估它在测试集上的表现:
from sklearn.metrics import accuracy_score
y_test_pred = XGB_model_instance.predict(X_test)
accuracy = accuracy_score(y_test, y_test_pred)
print("Accuracy: %.2f%%" % (accuracy * 100))
以下截图显示了输出:
它是如何工作的...
我们首先读取数据(步骤 1)。然后,我们创建一个训练-测试分割(步骤 2)。接着,我们实例化一个带有默认参数的 XGBoost 分类器,并将其拟合到训练集(步骤 3)。最后,在步骤 4 中,我们使用 XGBoost 分类器对测试集进行预测。然后,我们计算 XGBoost 模型预测的准确性。
使用 statsmodels 分析时间序列
时间序列是指在连续的时间点上获取的数值序列。例如,股市每分钟的价格构成了一个时间序列。在网络安全领域,时间序列分析对于预测网络攻击非常有用,例如内部员工窃取数据,或一群黑客在为下一次攻击做准备时的行为模式。
让我们看看使用时间序列进行预测的几种技术。
准备就绪
本示例的准备工作包括在 pip
中安装 matplotlib
、statsmodels
和 scipy
包。安装命令如下:
pip install matplotlib statsmodels scipy
如何操作...
在接下来的步骤中,我们展示了几种使用时间序列数据进行预测的方法:
- 首先生成一个时间序列:
from random import random
time_series = [2 * x + random() for x in range(1, 100)]
- 绘制你的数据:
%matplotlib inline
import matplotlib.pyplot as plt
plt.plot(time_series)
plt.show()
以下截图展示了输出结果:
-
我们可以使用多种技术来预测时间序列的后续值:
- 自回归(AR):
from statsmodels.tsa.ar_model import AR
model = AR(time_series)
model_fit = model.fit()
y = model_fit.predict(len(time_series), len(time_series))
-
- 移动平均(MA):
from statsmodels.tsa.arima_model import ARMA
model = ARMA(time_series, order=(0, 1))
model_fit = model.fit(disp=False)
y = model_fit.predict(len(time_series), len(time_series))
-
- 简单指数平滑(SES):
from statsmodels.tsa.holtwinters import SimpleExpSmoothing
model = SimpleExpSmoothing(time_series)
model_fit = model.fit()
y = model_fit.predict(len(time_series), len(time_series))
结果预测如下:
它是如何工作的...
在第一步中,我们生成了一个简单的时间序列。该序列由一条线上的值组成,并添加了一些噪声。接下来,在第 2 步中我们绘制了时间序列。你可以看到它非常接近一条直线,而且对时间点 处的时间序列值做出的合理预测是
。为了创建时间序列值的预测,我们考虑了三种不同的方案(第 3 步)来预测时间序列的未来值。在自回归模型中,基本思想是时间序列在时间 t 的值是该时间序列在之前时刻值的线性函数。更准确地说,有一些常数
,以及一个数字
,使得:
作为一个假设的例子, 可能是 3,意味着可以通过知道时间序列的最后 3 个值来轻松计算其值。
在移动平均模型中,时间序列被建模为围绕均值波动。更准确地说,设 是一组独立同分布的正态变量,且设
是常数。那么,时间序列可以通过以下公式建模:
因此,它在预测我们生成的嘈杂线性时间序列时表现较差。
最后,在简单指数平滑中,我们提出一个平滑参数,。然后,我们模型的估计值
是根据以下公式计算得出的:
换句话说,我们跟踪一个估计值,,并使用当前的时间序列值
略微调整它。调整的强度由
参数控制。
使用隔离森林进行异常检测
异常检测是识别数据集中不符合预期模式的事件。在应用中,这些事件可能至关重要。例如,它们可能是网络入侵或欺诈的发生。我们将利用隔离森林来检测此类异常。隔离森林依赖于一个观察结果:隔离异常值很容易,而描述一个正常数据点则更为困难。
准备工作
该配方的准备工作包括在pip
中安装matplotlib
、pandas
和scipy
包。命令如下:
pip install matplotlib pandas scipy
如何做到这一点...
在接下来的步骤中,我们演示如何应用隔离森林算法来检测异常:
- 导入所需的库并设置随机种子:
import numpy as np
import pandas as pd
random_seed = np.random.RandomState(12)
- 生成一组正常观测数据,用作训练数据:
X_train = 0.5 * random_seed.randn(500, 2)
X_train = np.r_[X_train + 3, X_train]
X_train = pd.DataFrame(X_train, columns=["x", "y"])
- 生成一个测试集,仍然由正常观测数据组成:
X_test = 0.5 * random_seed.randn(500, 2)
X_test = np.r_[X_test + 3, X_test]
X_test = pd.DataFrame(X_test, columns=["x", "y"])
- 生成一组异常观测数据。这些数据来自与正常观测数据不同的分布:
X_outliers = random_seed.uniform(low=-5, high=5, size=(50, 2))
X_outliers = pd.DataFrame(X_outliers, columns=["x", "y"])
- 让我们看看我们生成的数据:
%matplotlib inline
import matplotlib.pyplot as plt
p1 = plt.scatter(X_train.x, X_train.y, c="white", s=50, edgecolor="black")
p2 = plt.scatter(X_test.x, X_test.y, c="green", s=50, edgecolor="black")
p3 = plt.scatter(X_outliers.x, X_outliers.y, c="blue", s=50, edgecolor="black")
plt.xlim((-6, 6))
plt.ylim((-6, 6))
plt.legend(
[p1, p2, p3],
["training set", "normal testing set", "anomalous testing set"],
loc="lower right",
)
plt.show()
以下截图显示了输出结果:
- 现在在我们的训练数据上训练一个隔离森林模型:
from sklearn.ensemble import IsolationForest
clf = IsolationForest()
clf.fit(X_train)
y_pred_train = clf.predict(X_train)
y_pred_test = clf.predict(X_test)
y_pred_outliers = clf.predict(X_outliers)
- 让我们看看算法的表现。将标签附加到
X_outliers
:
X_outliers = X_outliers.assign(pred=y_pred_outliers)
X_outliers.head()
以下是输出结果:
x | y | pred | |
---|---|---|---|
0 | 3.947504 | 2.891003 | 1 |
1 | 0.413976 | -2.025841 | -1 |
2 | -2.644476 | -3.480783 | -1 |
3 | -0.518212 | -3.386443 | -1 |
4 | 2.977669 | 2.215355 | 1 |
- 让我们绘制隔离森林预测的异常值,看看它捕获了多少:
p1 = plt.scatter(X_train.x, X_train.y, c="white", s=50, edgecolor="black")
p2 = plt.scatter(
X_outliers.loc[X_outliers.pred == -1, ["x"]],
X_outliers.loc[X_outliers.pred == -1, ["y"]],
c="blue",
s=50,
edgecolor="black",
)
p3 = plt.scatter(
X_outliers.loc[X_outliers.pred == 1, ["x"]],
X_outliers.loc[X_outliers.pred == 1, ["y"]],
c="red",
s=50,
edgecolor="black",
)
plt.xlim((-6, 6))
plt.ylim((-6, 6))
plt.legend(
[p1, p2, p3],
["training observations", "detected outliers", "incorrectly labeled outliers"],
loc="lower right",
)
plt.show()
以下截图显示了输出结果:
- 现在让我们看看它在正常测试数据上的表现。将预测标签附加到
X_test
:
X_test = X_test.assign(pred=y_pred_test)
X_test.head()
以下是输出结果:
x | y | pred | |
---|---|---|---|
0 | 3.944575 | 3.866919 | -1 |
1 | 2.984853 | 3.142150 | 1 |
2 | 3.501735 | 2.168262 | 1 |
3 | 2.906300 | 3.233826 | 1 |
4 | 3.273225 | 3.261790 | 1 |
- 现在让我们绘制结果,看看我们的分类器是否正确地标记了正常的测试数据:
p1 = plt.scatter(X_train.x, X_train.y, c="white", s=50, edgecolor="black")
p2 = plt.scatter(
X_test.loc[X_test.pred == 1, ["x"]],
X_test.loc[X_test.pred == 1, ["y"]],
c="blue",
s=50,
edgecolor="black",
)
p3 = plt.scatter(
X_test.loc[X_test.pred == -1, ["x"]],
X_test.loc[X_test.pred == -1, ["y"]],
c="red",
s=50,
edgecolor="black",
)
plt.xlim((-6, 6))
plt.ylim((-6, 6))
plt.legend(
[p1, p2, p3],
[
"training observations",
"correctly labeled test observations",
"incorrectly labeled test observations",
],
loc="lower right",
)
plt.show()
以下截图显示了输出结果:
显然,我们的 Isolation Forest 模型在捕捉异常点方面表现得相当不错。尽管存在一些假阴性(正常点被错误分类为异常点),但通过调整模型的参数,我们或许能减少这些问题。
它是如何工作的……
第一步简单地加载必要的库,这些库将使我们能够快速、轻松地操作数据。在步骤 2 和 3 中,我们生成一个由正常观察值组成的训练集和测试集。这些数据具有相同的分布。而在步骤 4 中,我们通过创建异常值来生成其余的测试集。这个异常数据集的分布与训练数据和其余的测试数据不同。绘制我们的数据时,我们看到一些异常点与正常点看起来无法区分(步骤 5)。这保证了由于数据的性质,我们的分类器将有相当大比例的误分类,在评估其性能时,我们必须记住这一点。在步骤 6 中,我们使用默认参数拟合一个 Isolation Forest 实例到训练数据。
请注意,算法并未接收到任何关于异常数据的信息。我们使用训练好的 Isolation Forest 实例来预测测试数据是正常的还是异常的,类似地,也预测异常数据是正常的还是异常的。为了检查算法的表现,我们将预测标签附加到 X_outliers
(步骤 7),然后绘制 Isolation Forest 实例在异常值上的预测(步骤 8)。我们看到它能够捕捉到大部分的异常值。那些被错误标记的异常值与正常观察值无法区分。接下来,在步骤 9,我们将预测标签附加到 X_test
,为分析做准备,然后绘制 Isolation Forest 实例在正常测试数据上的预测(步骤 10)。我们看到它正确地标记了大多数正常观察值。与此同时,也有相当数量的正常观察值被错误分类(用红色显示)。
根据我们愿意容忍多少误报,我们可能需要对分类器进行微调,以减少假阳性的数量。
使用哈希向量化器和 tf-idf 以及 scikit-learn 进行自然语言处理
我们在数据科学中常常发现,我们希望分析的对象是文本。例如,它们可能是推文、文章或网络日志。由于我们的算法需要数值输入,我们必须找到一种方法将这些文本转化为数值特征。为此,我们使用了一系列技术。
一个词元是文本的一个单位。例如,我们可以指定我们的词元是单词、句子或字符。计数向量化器接受文本输入,然后输出一个包含文本词元计数的向量。哈希向量化器是计数向量化器的一种变体,旨在以更快和更可扩展的方式实现,但牺牲了可解释性和哈希冲突。尽管它很有用,仅仅获取文档语料库中出现的单词计数可能会误导。原因是,通常,像the和a这样的不重要词(称为停用词)频繁出现,因此信息含量较低。正因如此,我们通常会为词语赋予不同的权重以抵消这个问题。主要的技术是tf-idf,即词频-逆文档频率。其主要思想是我们考虑某个词出现的次数,但根据它在多少个文档中出现过来进行折扣。
在网络安全领域,文本数据无处不在;事件日志、对话记录以及函数名列表只是其中的一些例子。因此,能够处理此类数据至关重要,这是你在本食谱中将要学习的内容。
准备工作
该食谱的准备工作包括在pip
中安装 scikit-learn 包。安装命令如下:
pip install sklearn
此外,包含#Anonops
IRC 频道中对话摘录的日志文件anonops_short.log
也包含在本章的代码库中。
如何进行…
在接下来的步骤中,我们将把一组文本数据转换为数值形式,以便于机器学习算法使用:
- 首先,导入一个文本数据集:
with open("anonops_short.txt", encoding="utf8") as f:
anonops_chat_logs = f.readlines()
- 接下来,使用哈希向量化器计算文本中的单词数量,然后使用 tf-idf 进行加权:
from sklearn.feature_extraction.text import HashingVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
my_vector = HashingVectorizer(input="content", ngram_range=(1, 2))
X_train_counts = my_vector.fit_transform(anonops_chat_logs,)
tf_transformer = TfidfTransformer(use_idf=True,).fit(X_train_counts)
X_train_tf = tf_transformer.transform(X_train_counts)
- 最终结果是一个稀疏矩阵,每一行是一个表示文本之一的向量:
X_train_tf
<180830 x 1048576 sparse matrix of type <class 'numpy.float64'>' with 3158166 stored elements in Compressed Sparse Row format> print(X_train_tf)
以下是输出结果:
它是如何工作的...
我们从加载#Anonops 文本数据集开始(第 1 步)。Anonops IRC 频道与匿名黑客活动组织有关。特别地,聊天参与者曾在过去通过 Anonops 计划和宣布他们未来的目标。因此,一个经过精心设计的机器学习系统,通过对这类数据进行训练,可以预测网络攻击。在第 2 步中,我们实例化了一个哈希向量化器。哈希向量化器为我们提供了文本中 1-gram 和 2-gram 的计数,换句话说,就是单个单词和相邻的两个单词(词元)。然后,我们应用了一个 tf-idf 转换器,为哈希向量化器提供的计数赋予适当的权重。我们的最终结果是一个大型稀疏矩阵,表示文本中 1-gram 和 2-gram 的出现次数,并根据重要性加权。最后,我们检查了在 Scipy 中展示的稀疏矩阵前端。
使用 scikit-optimize 进行超参数调优
在机器学习中,超参数是指在训练过程开始之前就已设定的参数。例如,梯度提升模型的学习率选择和多层感知机的隐藏层大小都是超参数的例子。与之对比,其他参数的值是通过训练过程中学习得出的。超参数选择非常重要,因为它可能对模型的表现产生巨大影响。
最基本的超参数调优方法叫做网格搜索。在这种方法中,你为每个超参数指定一组潜在的值,然后尝试所有的组合,直到找到最佳的组合。这个暴力法虽然全面,但计算量大。也有更为复杂的方法。在这个食谱中,你将学习如何使用scikit-optimize
进行超参数的贝叶斯优化。与基本的网格搜索不同,在贝叶斯优化中,并不是尝试所有参数值,而是从指定的分布中抽取一个固定数量的参数设置。更多细节请参见scikit-optimize.github.io/notebooks/bayesian-optimization.html
。
准备开始
这个食谱的准备工作包括安装特定版本的scikit-learn
,安装xgboost
,以及通过pip
安装scikit-optimize
。相关命令如下:
pip install scikit-learn==0.20.3 xgboost scikit-optimize pandas
如何实现...
在接下来的步骤中,你将加载标准的wine
数据集,并使用贝叶斯优化来调优 XGBoost 模型的超参数:
- 从 scikit-learn 加载
wine
数据集:
from sklearn import datasets
wine_dataset = datasets.load_wine()
X = wine_dataset.data
y = wine_dataset.target
- 导入 XGBoost 和分层 K 折交叉验证:
import xgboost as xgb
from sklearn.model_selection import StratifiedKFold
- 从
scikit-optimize
导入BayesSearchCV
并指定要测试的参数设置数量:
from skopt import BayesSearchCV
n_iterations = 50
- 指定你的估计器。在这种情况下,我们选择 XGBoost,并将其设置为能够执行多类别分类:
estimator = xgb.XGBClassifier(
n_jobs=-1,
objective="multi:softmax",
eval_metric="merror",
verbosity=0,
num_class=len(set(y)),
)
- 指定参数搜索空间:
search_space = {
"learning_rate": (0.01, 1.0, "log-uniform"),
"min_child_weight": (0, 10),
"max_depth": (1, 50),
"max_delta_step": (0, 10),
"subsample": (0.01, 1.0, "uniform"),
"colsample_bytree": (0.01, 1.0, "log-uniform"),
"colsample_bylevel": (0.01, 1.0, "log-uniform"),
"reg_lambda": (1e-9, 1000, "log-uniform"),
"reg_alpha": (1e-9, 1.0, "log-uniform"),
"gamma": (1e-9, 0.5, "log-uniform"),
"min_child_weight": (0, 5),
"n_estimators": (5, 5000),
"scale_pos_weight": (1e-6, 500, "log-uniform"),
}
- 指定要执行的交叉验证类型:
cv = StratifiedKFold(n_splits=3, shuffle=True)
- 使用你定义的设置来定义
BayesSearchCV
:
bayes_cv_tuner = BayesSearchCV(
estimator=estimator,
search_spaces=search_space,
scoring="accuracy",
cv=cv,
n_jobs=-1,
n_iter=n_iterations,
verbose=0,
refit=True,
)
- 定义一个
callback
函数来输出参数搜索的进度:
import pandas as pd
import numpy as np
def print_status(optimal_result):
"""Shows the best parameters found and accuracy attained of the search so far."""
models_tested = pd.DataFrame(bayes_cv_tuner.cv_results_)
best_parameters_so_far = pd.Series(bayes_cv_tuner.best_params_)
print(
"Model #{}\nBest accuracy so far: {}\nBest parameters so far: {}\n".format(
len(models_tested),
np.round(bayes_cv_tuner.best_score_, 3),
bayes_cv_tuner.best_params_,
)
)
clf_type = bayes_cv_tuner.estimator.__class__.__name__
models_tested.to_csv(clf_type + "_cv_results_summary.csv")
- 执行参数搜索:
result = bayes_cv_tuner.fit(X, y, callback=print_status)
如你所见,以下显示了输出结果:
Model #1
Best accuracy so far: 0.972
Best parameters so far: {'colsample_bylevel': 0.019767840658391753, 'colsample_bytree': 0.5812505808116454, 'gamma': 1.7784704701058755e-05, 'learning_rate': 0.9050859661329937, 'max_delta_step': 3, 'max_depth': 42, 'min_child_weight': 1, 'n_estimators': 2334, 'reg_alpha': 0.02886003776717955, 'reg_lambda': 0.0008507166793122457, 'scale_pos_weight': 4.801764874750116e-05, 'subsample': 0.7188797743009225}
Model #2
Best accuracy so far: 0.972
Best parameters so far: {'colsample_bylevel': 0.019767840658391753, 'colsample_bytree': 0.5812505808116454, 'gamma': 1.7784704701058755e-05, 'learning_rate': 0.9050859661329937, 'max_delta_step': 3, 'max_depth': 42, 'min_child_weight': 1, 'n_estimators': 2334, 'reg_alpha': 0.02886003776717955, 'reg_lambda': 0.0008507166793122457, 'scale_pos_weight': 4.801764874750116e-05, 'subsample': 0.7188797743009225}
<snip>
Model #50
Best accuracy so far: 0.989
Best parameters so far: {'colsample_bylevel': 0.013417868502558758, 'colsample_bytree': 0.463490250419848, 'gamma': 2.2823050161337873e-06, 'learning_rate': 0.34006478878384533, 'max_delta_step': 9, 'max_depth': 41, 'min_child_weight': 0, 'n_estimators': 1951, 'reg_alpha': 1.8321791726476395e-08, 'reg_lambda': 13.098734837402576, 'scale_pos_weight': 0.6188077759379964, 'subsample': 0.7970035272497132}
如何实现...
在步骤 1 和 2 中,我们导入了一个标准数据集——wine
数据集,以及分类所需的库。接下来的步骤更为有趣,我们指定了想要进行超参数搜索的时间,具体来说,就是指定我们希望尝试多少种参数组合。搜索时间越长,结果通常越好,但也有过拟合和延长计算时间的风险。在步骤 4 中,我们选择 XGBoost 作为模型,然后指定类别数量、问题类型和评估指标。这个部分将取决于问题的类型。例如,对于回归问题,我们可能会设置eval_metric = 'rmse'
,并且一同去掉num_class
。
除了 XGBoost 之外,超参数优化器还可以选择其他模型。在下一步(第 5 步)中,我们指定了每个参数的概率分布,这些参数将会被探索。这也是使用BayesSearchCV
优于简单网格搜索的一个优势,因为它允许你更智能地探索参数空间。接下来,我们指定交叉验证方案(第 6 步)。由于我们正在进行分类问题,因此指定分层折叠是合理的。然而,对于回归问题,StratifiedKFold
应该被替换为KFold
。
还要注意,为了测量结果的准确性,较大的划分数更为理想,尽管这会带来计算上的开销。在第 7 步中,你可以看到一些可以更改的额外设置。特别地,n_jobs
允许你并行化任务。输出的详细程度以及评分方法也可以进行调整。为了监控搜索过程和我们超参数调优的性能,我们在第 8 步定义了一个回调函数,用于打印出进度。网格搜索的结果也会保存在 CSV 文件中。最后,我们运行超参数搜索(第 9 步)。输出结果让我们能够观察每次超参数搜索迭代的参数和性能。
在本书中,我们将避免调整分类器的超参数。原因部分是为了简洁,部分原因是因为在这里进行超参数调优会是过早优化,因为从最终用户的角度来看,算法的性能并没有特定的要求或目标。既然我们已经展示了如何进行调优,你可以轻松地将这个方法应用于当前的任务。
另一个需要记住的用于超参数调优的著名库是hyperopt
。
第二章:基于机器学习的恶意软件检测
在本章中,我们将开始认真地将数据科学应用于网络安全。我们将从学习如何对样本进行静态和动态分析开始。在此基础上,我们将学习如何对样本进行特征化,以便构建一个具有信息量的特征的数据集。本章的亮点是学习如何使用我们学到的特征化技能构建静态恶意软件检测器。最后,您将学习如何解决网络安全领域中常见的机器学习挑战,如类别不平衡和假阳性率(FPR)限制。
本章涵盖以下内容:
-
恶意软件静态分析
-
恶意软件动态分析
-
使用机器学习检测文件类型
-
测量两个字符串之间的相似度
-
测量两个文件之间的相似度
-
提取 N-gram
-
选择最佳 N-gram
-
构建静态恶意软件检测器
-
解决类别不平衡问题
-
处理类型 I 和类型 II 错误
技术要求
在本章中,我们将使用以下工具:
-
YARA
-
pefile
-
PyGitHub
-
Cuckoo 沙箱
-
自然语言工具包(NLTK)
-
imbalanced-learn
代码和数据集可以在 github.com/PacktPublishing/Machine-Learning-for-Cybersecurity-Cookbook/tree/master/Chapter02
找到。
恶意软件静态分析
在静态分析中,我们在不执行样本的情况下进行检查。通过这种方式可以获得大量信息,从文件名称到更复杂的信息,如专用的 YARA 签名。我们将介绍通过静态分析样本可以获取的各种特征。尽管静态分析强大且方便,但它并不是万能的,主要是因为软件可能被混淆。因此,我们将在后续章节中使用动态分析和其他技术。
计算样本的哈希值
不深入探讨哈希的复杂性,哈希本质上是一个简短且唯一的字符串签名。例如,我们可以对文件的字节序列进行哈希处理,从而得到该文件的唯一代码。这使我们能够快速比较两个文件,查看它们是否相同。
市面上有许多哈希算法,因此我们将重点介绍最重要的几种,即 SHA256 和 MD5。需要注意的是,MD5 因哈希碰撞(即两个不同的对象具有相同的哈希值)而存在已知的漏洞,因此使用时需要小心。在本教程中,我们将使用一个可执行文件,并计算它的 MD5 和 SHA256 哈希值。
准备工作
本教程的准备工作包括下载一个测试文件,即来自 www.python.org/ftp/python/3.7.2/python-3.7.2-amd64.exe
的 Python 可执行文件。
如何操作...
在以下步骤中,我们将展示如何获取文件的哈希值:
- 首先,导入库并选择您希望计算哈希的文件:
import sys
import hashlib
filename = "python-3.7.2-amd64.exe"
- 实例化 MD5 和 SHA256 对象,并指定我们将读取的块的大小:
BUF_SIZE = 65536
md5 = hashlib.md5()
sha256 = hashlib.sha256()
- 然后,我们将文件以 64 KB 为块进行读取,并增量构建哈希值:
with open(filename, "rb") as f:
while True:
data = f.read(BUF_SIZE)
if not data:
break
md5.update(data)
sha256.update(data)
- 最后,输出计算结果的哈希值:
print("MD5: {0}".format(md5.hexdigest()))
print("SHA256: {0}".format(sha256.hexdigest()))
这将产生以下输出:
MD5: ff258093f0b3953c886192dec9f52763
SHA256: 0fe2a696f5a3e481fed795ef6896ed99157bcef273ef3c4a96f2905cbdb3aa13
如何工作…
本节将解释前面章节中提供的步骤:
-
在步骤 1 中,我们导入了
hashlib
,一个用于哈希计算的标准 Python 库。我们还指定了我们将要计算哈希的文件——在这个例子中,文件是python-3.7.2-amd64.exe
。 -
在步骤 2 中,我们实例化一个
md5
对象和一个sha256
对象,并指定我们将读取的块的大小。 -
在步骤 3 中,我们使用
.update(data)
方法。这个方法允许我们增量计算哈希,因为它计算的是连接字符串的哈希。换句话说,hash.update(a)
后跟hash.update(b)
等同于hash.update(a+b)
。 -
在步骤 4 中,我们以十六进制数字的形式输出哈希值。
我们还可以验证我们的计算结果是否与其他来源提供的哈希计算一致,比如 VirusTotal 和官方 Python 网站。MD5 哈希值显示在 Python 网页上(www.python.org/downloads/release/python-372/
):
可以通过将文件上传到 VirusTotal(www.virustotal.com/gui/home
)来计算 SHA256 哈希值:
YARA
YARA 是一种计算机语言,允许安全专家方便地指定规则,然后用该规则对所有符合条件的样本进行分类。一个最小的规则包含一个名称和一个条件,例如,以下内容:
rule my_rule_name { condition: false }
这个规则不会匹配任何文件。相反,下面的规则将匹配每个样本:
Rule my_rule_name { condition: true }
一个更有用的例子是匹配任何大于 100 KB 的文件:
Rule over_100kb { condition: filesize > 100KB }
另一个例子是检查某个特定文件是否为 PDF 文件。为此,我们需要检查文件的魔术数字是否与 PDF 文件的魔术数字匹配。魔术数字是文件开头的一段字节序列,表示文件类型。在 PDF 文件的情况下,该序列为25 50 44 46
:
rule is_a_pdf {
strings:
$pdf_magic = {25 50 44 46}
condition:
$pdf_magic at 0
}
现在,让我们看看如何将规则应用于文件。
准备工作
本教程的准备工作包括在设备上安装 YARA。安装说明可以在yara.readthedocs.io/en/stable/
找到。对于 Windows,您需要下载 YARA 的可执行文件。
如何操作……
在以下步骤中,我们将向您展示如何创建 YARA 规则并对文件进行测试:
- 将您的规则(如下面所示)复制到文本文件中并命名为
rules.yara
:
rule is_a_pdf
{
strings:
$pdf_magic = {25 50 44 46}
condition:
$pdf_magic at 0
}
rule dummy_rule1
{
condition:
false
}
rule dummy_rule2
{
condition:
true
}
- 接下来,选择一个你希望用规则进行检查的文件。称之为
target_file
。在终端中执行Yara rules.yara target_file
,如下所示:
Yara rule.yara PythonBrochure
结果应该如下所示:
is_a_pdf target_file
dummy_rule2 target_rule
工作原理...
如你所见,在步骤 1中,我们复制了几条 YARA 规则。第一条规则检查文件的魔术数字,看看它们是否与 PDF 文件的魔术数字匹配。其他两条规则是简单的规则——一条匹配所有文件,另一条不匹配任何文件。然后,在步骤 2中,我们使用 YARA 程序将这些规则应用到目标文件上。通过打印输出,我们看到文件匹配了一些规则,但没有匹配其他规则,这符合有效的 YARA 规则集的预期。
检查 PE 头
便携式可执行文件 (PE) 是一种常见的 Windows 文件类型。PE 文件包括.exe
、.dll
和.sys
文件。所有 PE 文件都有一个 PE 头,这是代码的一个头部部分,指示 Windows 如何解析随后的代码。PE 头中的字段通常作为特征被用于恶意软件的检测。为了方便提取 PE 头的众多值,我们将使用pefile
Python 模块。在本食谱中,我们将解析一个文件的 PE 头,然后打印出其中一些重要部分。
准备开始
本食谱的准备工作包括在pip
中安装pefile
包。在你的 Python 环境的终端中运行以下命令:
pip install pefile
此外,从www.python.org/ftp/python/3.7.2/python-3.7.2-amd64.exe
下载测试文件 Python 可执行文件。
如何操作...
在接下来的步骤中,我们将解析一个文件的 PE 头,并打印出其中一些重要部分:
- 导入 PE 文件并使用它来解析你希望检查的文件的 PE 头:
import pefile
desired_file = "python-3.7.2-amd64.exe"
pe = pefile.PE(desired_file)
- 列出 PE 文件的导入项:
for entry in pe.DIRECTORY_ENTRY_IMPORT:
print(entry.dll)
for imp in entry.imports:
print("\t", hex(imp.address), imp.name)
这里显示了一小部分输出:
- 列出 PE 文件的各个部分:
for section in pe.sections:
print(
section.Name,
hex(section.VirtualAddress),
hex(section.Misc_VirtualSize),
section.SizeOfRawData,
)
之前代码的输出结果如下:
- 打印解析信息的完整转储:
print(pe.dump_info())
这里显示了一小部分输出:
工作原理...
我们从步骤 1开始,导入了pefile
库,并指定了我们将要分析的文件。在这个例子中,文件是python-3.7.2-amd64.exe
,但同样也可以轻松分析任何其他 PE 文件。接着,我们继续检查文件所导入的 DLL,以了解文件可能使用的方法,在步骤 2中进行分析。DLL 可以回答这个问题,因为 DLL 是代码库,其他应用程序可能会调用它。例如,USER32.dll
是一个包含 Windows USER 的库,它是 Microsoft Windows 操作系统的一部分,提供用于构建用户界面的核心功能。该组件允许其他应用程序利用窗口管理、消息传递、输入处理和标准控件等功能。因此,从逻辑上讲,如果我们看到一个文件导入了像GetCursorPos
这样的函数,那么它很可能是在查看光标的位置。在步骤 3中,我们打印了 PE 文件的各个部分。它们为程序的不同部分提供了逻辑和物理的分隔,因此为分析人员提供了有关程序的宝贵信息。最后,我们打印了文件的所有解析后的 PE 头部信息,为后续用于特征工程做好准备(步骤 4)。
提取 PE 头部特征
在本节中,我们将从 PE 头部提取特征,用于构建恶意/良性
样本分类器。我们将继续使用pefile
Python 模块。
准备开始
本配方的准备工作包括在pip
中安装pefile
包。命令如下:
pip install pefile
此外,良性和恶意文件已经提供给你,位于根目录下的PE Samples Dataset
文件夹中。将所有名为Benign PE Samples*.7z
的压缩包解压到名为Benign PE Samples
的文件夹中。将所有名为Malicious PE Samples*.7z
的压缩包解压到名为Malicious PE Samples
的文件夹中。
如何操作...
在接下来的步骤中,我们将收集 PE 头部的显著部分:
- 导入
pefile
和用于列举样本的模块:
import pefile
from os import listdir
from os.path import isfile, join
directories = ["Benign PE Samples", "Malicious PE Samples"]
- 我们定义了一个函数来收集文件的节名称,并对其进行预处理以提高可读性和标准化:
def get_section_names(pe):
"""Gets a list of section names from a PE file."""
list_of_section_names = []
for sec in pe.sections:
normalized_name = sec.Name.decode().replace("\x00", "").lower()
list_of_section_names.append(normalized_name)
return list_of_section_names
- 我们定义了一个便捷函数来预处理和标准化我们的导入信息:
def preprocess_imports(list_of_DLLs):
"""Normalize the naming of the imports of a PE file."""
return [x.decode().split(".")[0].lower() for x in list_of_DLLs]
- 然后,我们定义一个函数,使用
pefile
收集文件中的导入信息:
def get_imports(pe):
"""Get a list of the imports of a PE file."""
list_of_imports = []
for entry in pe.DIRECTORY_ENTRY_IMPORT:
list_of_imports.append(entry.dll)
return preprocess_imports(list_of_imports)
- 最后,我们准备迭代所有文件,并创建列表来存储我们的特征:
imports_corpus = []
num_sections = []
section_names = []
for dataset_path in directories:
samples = [f for f in listdir(dataset_path) if isfile(join(dataset_path, f))]
for file in samples:
file_path = dataset_path + "/" + file
try:
- 除了收集前述特征外,我们还会收集文件的节数:
pe = pefile.PE(file_path)
imports = get_imports(pe)
n_sections = len(pe.sections)
sec_names = get_section_names(pe)
imports_corpus.append(imports)
num_sections.append(n_sections)
section_names.append(sec_names)
- 如果无法解析文件的 PE 头部,我们定义了一个 try-catch 语句:
except Exception as e:
print(e)
print("Unable to obtain imports from " + file_path)
它是如何工作的...
如你所见,在步骤 1中,我们导入了pefile
模块来列举样本。完成之后,我们定义了一个便捷函数,正如你在步骤 2中看到的那样。这样做的原因是它经常使用不同的大小写(大写/小写)导入。这会导致相同的导入看起来像是不同的导入。
在预处理导入项后,我们定义另一个函数,将文件的所有导入项收集到一个列表中。我们还将定义一个函数,用来收集文件的各个部分的名称,以便规范化这些名称,如.text
、.rsrc
和.reloc
,同时包含文件的不同部分(步骤 3)。然后,文件将在我们的文件夹中进行枚举,空列表将被创建,用于存放我们将要提取的特征。预定义的函数将收集导入项(步骤 4)、部分名称以及每个文件的部分数量(步骤 5 和 6)。最后,将定义一个 try-catch 语句块,以防某个文件的 PE 头无法解析(步骤 7)。这种情况可能由于多种原因发生。一个原因是文件本身并不是 PE 文件。另一个原因是其 PE 头部故意或无意地被篡改。
恶意软件动态分析
与静态分析不同,动态分析是一种恶意软件分析技术,其中专家执行样本,然后在样本运行时研究其行为。动态分析相对于静态分析的主要优势是,它允许通过观察样本的行为来绕过混淆,而不是试图解读样本的内容和行为。由于恶意软件本质上是不安全的,研究人员会选择在虚拟机(VM)中执行样本。这被称为沙箱化。
准备工作
在虚拟机中自动化样本分析的最突出工具之一是 Cuckoo 沙箱。Cuckoo 沙箱的初始安装非常简单;只需运行以下命令:
pip install -U cuckoo
您必须确保还拥有可以由您的机器控制的虚拟机。配置沙箱可能具有挑战性,但可以通过cuckoo.sh/docs/
的说明来完成。
现在我们展示如何利用 Cuckoo 沙箱来获取样本的动态分析。
如何做...
一旦您的 Cuckoo 沙箱设置完成,并且运行了 Web 界面,按照以下步骤收集样本的运行时信息:
- 打开您的 Web 界面(默认位置是
127.0.0.1:8000
),点击提交文件进行分析,并选择您希望分析的样本:
- 以下屏幕将自动出现。在其中,选择您希望对样本执行的分析类型:
- 点击分析以在沙箱中分析样本。结果应如下所示:
- 接下来,打开您分析的样本的报告:
- 选择行为分析标签:
显示的 API 调用顺序、注册表键更改和其他事件均可用作分类器的输入。
它是如何工作的...
从概念上讲,获取动态分析结果包括在允许分析员收集运行时信息的环境中运行样本。Cuckoo Sandbox 是一个灵活的框架,带有预构建模块来完成这一任务。我们从打开 Cuckoo Sandbox 的 Web 门户开始了我们的操作流程(步骤 1)。命令行界面(CLI)也可用。我们继续提交一个样本并选择希望执行的分析类型(步骤 2和步骤 3)。这些步骤也可以通过 Cuckoo CLI 来执行。接着,我们查看了分析报告(步骤 4)。此时你可以看到 Cuckoo Sandbox 的许多模块如何反映在最终的分析结果中。例如,如果安装并使用了一个捕获流量的模块,那么报告中会包含网络标签中捕获的数据。我们继续将视角集中在行为分析(步骤 5)上,特别是观察 API 调用的顺序。API 调用基本上是操作系统执行的操作。这个顺序构成了一个极好的特征集,我们将利用它在未来的操作中检测恶意软件。最后,值得注意的是,在生产环境中,可能有必要创建一个定制的沙箱,配备自定义的数据收集模块,并搭载反虚拟机检测软件,以促进成功的分析。
使用机器学习检测文件类型
黑客常用的技巧之一就是通过混淆文件类型来悄悄将恶意文件渗透到安全系统中。例如,一个(恶意的)PowerShell 脚本通常应该有.ps1
的扩展名。系统管理员可以通过阻止所有带有.ps1
扩展名的文件执行,来防止 PowerShell 脚本的执行。然而,狡猾的黑客可以删除或更改扩展名,使得文件的身份变得模糊。只有通过检查文件的内容,才能将其与普通文本文件区分开来。由于实际原因,人类不可能检查系统中的所有文本文件。因此,采用自动化方法就显得尤为重要。在本章中,我们将展示如何利用机器学习来检测未知文件的文件类型。我们的第一步是策划一个数据集。
从 GitHub 抓取特定类型的文件
为了策划数据集,我们将从 GitHub 上抓取我们感兴趣的特定文件类型。
准备工作
准备工作包括通过运行以下命令,在pip
中安装PyGitHub
包:
pip install PyGitHub
此外,你还需要 GitHub 账户凭证。
如何做到这一点...
在接下来的步骤中,我们将整理一个数据集,并利用它创建一个分类器来确定文件类型。为了演示,我们将展示如何通过抓取 GitHub 获取 PowerShell 脚本、Python 脚本和 JavaScript 文件的集合。通过这种方式获得的示例集合可以在随附的仓库中找到,文件名为PowerShellSamples.7z
、PythonSamples.7z
和JavascriptSamples.7z
。首先,我们将编写用于抓取 JavaScript 文件的代码:
- 首先导入
PyGitHub
库,以便能够调用 GitHub API。我们还导入了base64
模块,以便解码base64
编码的文件:
import os
from github import Github
import base64
- 我们必须提供我们的凭证,然后指定一个查询——在这种情况下,查询 JavaScript——来选择我们的仓库:
username = "your_github_username"
password = "your_password"
target_dir = "/path/to/JavascriptSamples/"
g = Github(username, password)
repositories = g.search_repositories(query='language:javascript')
n = 5
i = 0
- 我们遍历符合条件的仓库:
for repo in repositories:
repo_name = repo.name
target_dir_of_repo = target_dir+"\\"+repo_name
print(repo_name)
try:
- 我们为每个匹配我们搜索标准的仓库创建一个目录,然后读取其内容:
os.mkdir(target_dir_of_repo)
i += 1
contents = repo.get_contents("")
- 我们将所有仓库的目录添加到队列中,以便列出这些目录中包含的所有文件:
while len(contents) > 1:
file_content = contents.pop(0)
if file_content.type == "dir":
contents.extend(repo.get_contents(file_content.path))
else:
- 如果我们发现一个非目录文件,我们检查它的扩展名是否为
.js
:
st = str(file_content)
filename = st.split("\"")[1].split("\"")[0]
extension = filename.split(".")[-1]
if extension == "js":
- 如果扩展名是
.js
,我们将文件的副本写出:
file_contents = repo.get_contents(file_content.path)
file_data = base64.b64decode(file_contents.content)
filename = filename.split("/")[-1]
file_out = open(target_dir_of_repo+"/"+filename, "wb")
file_out.write(file_data)
except:
pass
if i==n:
break
-
一旦完成,方便的方法是将所有 JavaScript 文件移到一个文件夹中。
要获取 PowerShell 示例,请运行相同的代码,并修改以下内容:
target_dir = "/path/to/JavascriptSamples/"
repositories = g.search_repositories(query='language:javascript')
接下来:
target_dir = "/path/to/PowerShellSamples/"
repositories = g.search_repositories(query='language:powershell').
同样,对于 Python 文件,我们进行如下操作:
target_dir = "/path/to/PythonSamples/"
repositories = g.search_repositories(query='language:python').
工作原理……
我们从导入PyGitHub
库开始,进行步骤 1,这样我们就能方便地调用 GitHub API。这些 API 将允许我们抓取并探索仓库的世界。我们还导入了base64
模块,以解码我们从 GitHub 下载的base64
编码的文件。请注意,GitHub 对普通用户的 API 调用次数有限制。因此,如果你尝试在短时间内下载太多文件,你的脚本可能无法获取所有文件。我们的下一步是向 GitHub 提供我们的凭证(步骤 2),并指定我们正在寻找具有 JavaScript 语言的仓库,使用query='language:javascript'
命令。我们列举出所有符合我们搜索条件的 JavaScript 仓库,如果符合条件,我们继续在这些仓库中查找以.js
结尾的文件,并创建本地副本(步骤 3 到 6)。由于这些文件是base64
编码的,我们确保在步骤 7 中将它们解码为纯文本。最后,我们展示如何调整脚本,以便抓取其他类型的文件,比如 Python 和 PowerShell 文件(步骤 8)。
按文件类型分类
现在我们已经有了一个数据集,我们希望训练一个分类器。由于相关文件是脚本文件,我们将问题作为一个自然语言处理(NLP)问题来处理。
准备工作
这个步骤的准备工作包括在pip
中安装scikit-learn
包。安装说明如下:
pip install sklearn
此外,我们还为您提供了JavascriptSamples.7z
、PythonSamples.7z
和PowerShellSamples.7z
压缩包中每种文件类型的样本,以防您希望补充自己的数据集。将其解压到不同的文件夹中,以便按照以下步骤操作。
如何实现...
以下代码可在github.com/PacktPublishing/Machine-Learning-for-Cybersecurity-Cookbook/blob/master/Chapter02/Classifying%20Files%20by%20Type/File%20Type%20Classifier.ipynb
找到。我们利用这些数据构建分类器,以预测文件为 JavaScript、Python 或 PowerShell:
- 首先导入必要的库并指定我们将用于训练和测试的样本的路径:
import os
from sklearn.feature_extraction.text import HashingVectorizer, TfidfTransformer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, confusion_matrix
from sklearn.pipeline import Pipeline
javascript_path = "/path/to/JavascriptSamples/"
python_path = "/path/to/PythonSamples/"
powershell_path = "/path/to/PowerShellSamples/"
- 接下来,我们读取所有文件类型。同时,我们创建一个标签数组,分别代表 JavaScript、Python 和 PowerShell 脚本,分别为-1、0 和 1:
corpus = []
labels = []
file_types_and_labels = [(javascript_path, -1), (python_path, 0), (powershell_path, 1)]
for files_path, label in file_types_and_labels:
files = os.listdir(files_path)
for file in files:
file_path = files_path + "/" + file
try:
with open(file_path, "r") as myfile:
data = myfile.read().replace("\n", "")
except:
pass
data = str(data)
corpus.append(data)
labels.append(label)
- 我们继续创建训练-测试分割和管道,该管道将对文件执行基本的自然语言处理,然后使用随机森林分类器:
X_train, X_test, y_train, y_test = train_test_split(
corpus, labels, test_size=0.33, random_state=11
)
text_clf = Pipeline(
[
("vect", HashingVectorizer(input="content", ngram_range=(1, 3))),
("tfidf", TfidfTransformer(use_idf=True,)),
("rf", RandomForestClassifier(class_weight="balanced")),
]
)
- 我们将管道拟合到训练数据上,然后在测试数据上进行预测。最后,我们打印出准确度和混淆矩阵:
text_clf.fit(X_train, y_train)
y_test_pred = text_clf.predict(X_test)
print(accuracy_score(y_test, y_test_pred))
print(confusion_matrix(y_test, y_test_pred))
这将导致以下输出:
工作原理...
利用我们在从 GitHub 抓取特定类型文件配方中建立的数据集,我们将文件放置在不同的目录中,根据其文件类型,并指定路径以准备构建我们的分类器(步骤 1)。本配方的代码假设"JavascriptSamples"
目录和其他目录包含样本,并且没有子目录。我们将所有文件读入一个语料库,并记录它们的标签(步骤 2)。我们对数据进行训练-测试分割,并准备一个管道,该管道将对文件执行基本的自然语言处理,然后使用随机森林分类器(步骤 3)。这里选择的分类器是为了说明目的,而不是为了暗示对于这类数据的最佳分类器选择。最后,我们执行创建机器学习分类器过程中的基本但重要的步骤,包括将管道拟合到训练数据上,然后通过测量其在测试集上的准确性和混淆矩阵来评估其性能(步骤 4)。
测量两个字符串之间的相似度
要检查两个文件是否相同,我们使用标准的加密哈希函数,例如 SHA256 和 MD5。然而,有时我们也想知道两个文件在多大程度上相似。为此,我们使用相似性哈希算法。在这里我们将演示的是ssdeep
。
首先,让我们看看如何使用ssdeep
比较两个字符串。这对于检测文本或脚本中的篡改以及抄袭非常有用。
准备工作
本食谱的准备工作包括在pip
中安装ssdeep
包。安装过程稍微复杂,并且在 Windows 上并不总是能成功。安装说明可以在python-ssdeep.readthedocs.io/en/latest/installation.html
找到。
如果你只有一台 Windows 机器,并且安装ssdeep
没有成功,那么一种可能的解决方法是通过运行ssdeep
在 Ubuntu 虚拟机上,然后在pip
中安装它,使用以下命令:
pip install ssdeep
如何操作...
- 首先导入
ssdeep
库并创建三个字符串:
import ssdeep
str1 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
str2 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore Magna aliqua."
str3 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore aliqua."
str4 = "Something completely different from the other strings."
- 对字符串进行哈希:
hash1 = ssdeep.hash(str1)
hash2 = ssdeep.hash(str2)
hash3 = ssdeep.hash(str3)
hash4 = ssdeep.hash(str4)
作为参考,
hash1 是u'3:f4oo8MRwRJFGW1gC6uWv6MQ2MFSl+JuBF8BSnJi:f4kPvtHMCMubyFtQ'
,
hash2 是u'3:f4oo8MRwRJFGW1gC6uWv6MQ2MFSl+JuBF8BS+EFECJi:f4kPvtHMCMubyFIsJQ'
,
hash3 是u'3:f4oo8MRwRJFGW1gC6uWv6MQ2MFSl+JuBF8BS6:f4kPvtHMCMubyF0
,并且
hash4 是u'3:60QKZ+4CDTfDaRFKYLVL:ywKDC2mVL'
。
- 接下来,我们查看这些字符串的相似度分数:
ssdeep.compare(hash1, hash1)
ssdeep.compare(hash1, hash2)
ssdeep.compare(hash1, hash3)
ssdeep.compare(hash1, hash4)
数值结果如下:
100
39
37
0
它是如何工作的...
ssdeep
的基本思路是结合多个传统哈希,这些哈希的边界由输入的上下文决定。这些哈希集合可以用来识别已知文件的修改版本,即使它们已经通过插入、修改或删除进行了更改。
在本食谱中,我们首先创建了四个测试字符串,作为一个玩具示例,来说明字符串的变化如何影响其相似度度量(步骤 1)。第一个字符串str1
仅仅是 Lorem Ipsum 的第一句。第二个字符串str2
在“magna”中的m
字母大小写上有所不同。第三个字符串str3
完全缺少了“magna”这个词。最后,第四个字符串是完全不同的字符串。我们的下一步,步骤 2,是使用相似度哈希库ssdeep
对这些字符串进行哈希处理。请注意,相似的字符串具有明显相似的相似度哈希。这与传统哈希形成鲜明对比,在传统哈希中,即使是一个小小的修改也会产生完全不同的哈希。接下来,我们通过ssdeep
(步骤 3)得出这些字符串之间的相似度分数。特别注意,ssdeep
的相似度分数是一个介于 0 和 100 之间的整数,100 表示完全相同,0 表示完全不同。两个完全相同的字符串的相似度分数是 100。改变一个字母的大小写会显著降低相似度分数至 39,因为字符串相对较短。删除一个单词将相似度分数降低到 37。两个完全不同的字符串的相似度为 0。
虽然还有其他一些在某些情况下更好的模糊哈希可供选择,但由于ssdeep
的速度和它作为事实标准的地位,它仍然是首选。
测量两个文件之间的相似度
现在,我们将了解如何应用ssdeep
来衡量两个二进制文件之间的相似性。这个概念有很多应用,尤其是在聚类中将相似性度量作为距离。
准备工作
本示例的准备工作包括通过pip
安装ssdeep
包。安装过程有点复杂,且在 Windows 上并不总是有效。可以参考python-ssdeep.readthedocs.io/en/latest/installation.html
上的说明。
如果你只有一台 Windows 机器且无法运行,那么一个可能的解决方案是在 Ubuntu 虚拟机上通过安装pip
并使用以下命令运行ssdeep
:
pip install ssdeep
此外,请从www.python.org/ftp/python/3.7.2/python-3.7.2-amd64.exe
下载测试文件,如 Python 可执行文件。
如何操作...
在下面的示例中,我们篡改一个二进制文件。然后我们将其与原文件进行比较,发现ssdeep
认为这两个文件高度相似但并不完全相同:
- 首先,我们下载 Python 的最新版本,
python-3.7.2-amd64.exe
。我将创建一个副本,将其重命名为python-3.7.2-amd64-fake.exe
,并在末尾添加一个空字节:
truncate -s +1 python-3.7.2-amd64-fake.exe
- 使用
hexdump
,我可以通过查看操作前后的文件来验证操作是否成功:
hexdump -C python-3.7.2-amd64.exe |tail -5
这将产生以下输出:
018ee0f0 e3 af d6 e9 05 3f b7 15 a1 c7 2a 5f b6 ae 71 1f |.....?....*_..q.|
018ee100 6f 46 62 1c 4f 74 f5 f5 a1 e6 91 b7 fe 90 06 3e |oFb.Ot.........>|
018ee110 de 57 a6 e1 83 4c 13 0d b1 4a 3d e5 04 82 5e 35 |.W...L...J=...⁵|
018ee120 ff b2 e8 60 2d e0 db 24 c1 3d 8b 47 b3 00 00 00 |...`-..$.=.G....|
也可以使用以下命令通过第二个文件进行验证:
hexdump -C python-3.7.2-amd64-fake.exe |tail -5
这将产生以下输出:
018ee100 6f 46 62 1c 4f 74 f5 f5 a1 e6 91 b7 fe 90 06 3e |oFb.Ot.........>|
018ee110 de 57 a6 e1 83 4c 13 0d b1 4a 3d e5 04 82 5e 35 |.W...L...J=...⁵|
018ee120 ff b2 e8 60 2d e0 db 24 c1 3d 8b 47 b3 00 00 00 |...`-..$.=.G....|
018ee130 00 |.|
018ee131
- 现在,我将使用
ssdeep
对两个文件进行哈希,并比较结果:
import ssdeep
hash1 = ssdeep.hash_from_file("python-3.7.2-amd64.exe")
hash2 = ssdeep.hash_from_file("python-3.7.2-amd64-fake.exe")
ssdeep.compare(hash1, hash2)
上述代码的输出是99
。
它是如何工作的...
这个场景模拟了篡改文件后,利用相似性哈希检测篡改的存在,并衡量差异的大小。我们从一个标准的 Python 可执行文件开始,然后通过在末尾添加一个空字节进行篡改(步骤 1)。在现实中,黑客可能会在合法程序中插入恶意代码。我们通过hexdump
双重检查篡改是否成功,并检查篡改的性质(步骤 2)。然后,我们对原始文件和篡改后的文件进行相似性计算,观察到发生了微小的变化(步骤 3)。仅使用标准哈希,我们将无法知道这两个文件之间的关系,除了得出它们不是同一个文件的结论。知道如何比较文件使我们能够在机器学习算法中对恶意软件和良性文件进行聚类,并将它们分组为家族。
提取 N-gram
在文本的标准定量分析中,N-gram 是由 N 个标记(例如,单词或字符)组成的序列。例如,给定文本The quick brown fox jumped over the lazy dog,如果我们的标记是单词,则 1-gram 是the,quick,brown,fox,jumped,over,the,lazy和dog。2-gram 是the quick,quick brown,brown fox,依此类推。3-gram 是the quick brown,quick brown fox,brown fox jumped,依此类推。就像文本的局部统计信息使我们能够构建马尔可夫链以进行统计预测和从语料库生成文本一样,N-gram 使我们能够建模语料库的局部统计特性。我们的最终目标是利用 N-gram 的计数帮助我们预测样本是恶意的还是良性的。在本食谱中,我们演示了如何从样本中提取 N-gram 计数。
准备工作
本食谱的准备工作包括在pip
中安装nltk
包,安装说明如下:
pip install nltk
此外,下载一个测试文件,例如来自www.python.org/ftp/python/3.7.2/python-3.7.2-amd64.exe
的 Python 可执行文件。
如何做到这一点...
在接下来的步骤中,我们将枚举一个示例文件的所有 4-gram,并选择其中 50 个最频繁的:
- 我们首先导入
collections
库以方便计数,并从nltk
库导入ngrams
库以简化 N-gram 的提取:
import collections
from nltk import ngrams
- 我们指定要分析的文件:
file_to_analyze = "python-3.7.2-amd64.exe"
- 我们定义了一个便捷函数来读取文件的字节:
def read_file(file_path):
"""Reads in the binary sequence of a binary file."""
with open(file_path, "rb") as binary_file:
data = binary_file.read()
return data
- 我们编写一个便捷函数来处理字节序列并获取 N-gram:
def byte_sequence_to_Ngrams(byte_sequence, N):
"""Creates a list of N-grams from a byte sequence."""
Ngrams = ngrams(byte_sequence, N)
return list(Ngrams)
- 我们编写一个函数来处理文件并获取其 N-gram 的计数:
def binary_file_to_Ngram_counts(file, N):
"""Takes a binary file and outputs the N-grams counts of its binary sequence."""
filebyte_sequence = read_file(file)
file_Ngrams = byte_sequence_to_Ngrams(filebyte_sequence, N)
return collections.Counter(file_Ngrams)
- 我们指定所需的值为 N=4,并获取文件中所有 4-gram 的计数:
extracted_Ngrams = binary_file_to_Ngram_counts(file_to_analyze, 4)
- 我们列出了文件中最常见的 10 个 4-gram:
print(extracted_Ngrams.most_common(10))
结果如下:
[((0, 0, 0, 0), 24201), ((139, 240, 133, 246), 1920), ((32, 116, 111, 32), 1791), ((255, 255, 255, 255), 1663), ((108, 101, 100, 32), 1522), ((100, 32, 116, 111), 1519), ((97, 105, 108, 101), 1513), ((105, 108, 101, 100), 1513), ((70, 97, 105, 108), 1505), ((101, 100, 32, 116), 1503)]
它是如何工作的...
在文献和工业界中已经确定,最常见的 N-gram 也是恶意软件分类算法中最具信息量的。因此,在本教程中,我们将编写函数来提取文件的 N-gram。我们首先导入一些有助于提取 N-gram 的库(第 1 步)。特别地,我们导入collections
库和nltk
中的ngrams
库。collections
库允许我们将 N-gram 列表转换为 N-gram 的频次计数,而ngrams
库则允许我们获取有序字节列表并得到 N-gram 列表。我们指定要分析的文件,并编写一个函数来读取给定文件的所有字节(第 2 步和第 3 步)。在开始提取之前,我们定义几个便利函数。特别地,我们编写一个函数来获取文件的字节序列并输出其 N-gram 列表(第 4 步),并编写一个函数来获取文件并输出其 N-gram 计数(第 5 步)。现在我们准备传入文件并提取其 N-gram。我们这样做以提取文件的 4-gram 计数(第 6 步),然后展示其中最常见的 10 个及其计数(第 7 步)。我们看到一些 N-gram 序列,如(0,0,0,0)和(255,255,255,255),可能不太有信息量。因此,我们将在下一个教程中利用特征选择方法去除这些不太有信息量的 N-gram。
选择最佳 N-gram
不同 N-gram 的数量随着 N 的增大呈指数增长。即使对于一个固定的小 N,如 N=3,也有256x256x256=16,777,216种可能的 N-gram。这意味着 N-gram 特征的数量巨大,几乎不可能实际使用。因此,我们必须选择一个较小的 N-gram 子集,这些子集将对我们的分类器最有价值。在这一部分中,我们展示了三种选择最具信息量 N-gram 的不同方法。
准备工作
本教程的准备工作包括在pip
中安装scikit-learn
和nltk
包。安装说明如下:
pip install sklearn nltk
此外,善意和恶意文件已在仓库根目录下的PE Samples Dataset
文件夹中提供。将所有名为Benign PE Samples*.7z
的压缩包解压到名为Benign PE Samples
的文件夹中。将所有名为Malicious PE Samples*.7z
的压缩包解压到名为Malicious PE Samples
的文件夹中。
如何实现...
在接下来的步骤中,我们展示了三种选择最具信息量 N-gram 的方法。本教程假设已包括前一个教程中的binaryFileToNgramCounts(file, N)
和所有其他辅助函数:
- 首先指定包含我们样本的文件夹,指定我们的
N
,并导入模块以枚举文件:
from os import listdir
from os.path import isfile, join
directories = ["Benign PE Samples", "Malicious PE Samples"]
N = 2
- 接下来,我们从所有文件中统计所有的 N-gram:
Ngram_counts_all_files = collections.Counter([])
for dataset_path in directories:
all_samples = [f for f in listdir(dataset_path) if isfile(join(dataset_path, f))]
for sample in all_samples:
file_path = join(dataset_path, sample)
Ngram_counts_all_files += binary_file_to_Ngram_counts(file_path, N)
- 我们将
K1=1000
个最常见的 N-gram 收集到一个列表中:
K1 = 1000
K1_most_frequent_Ngrams = Ngram_counts_all_files.most_common(K1)
K1_most_frequent_Ngrams_list = [x[0] for x in K1_most_frequent_Ngrams]
- 一个辅助方法
featurize_sample
将用于获取一个样本并输出其字节序列中最常见 N-gram 的出现次数:
def featurize_sample(sample, K1_most_frequent_Ngrams_list):
"""Takes a sample and produces a feature vector.
The features are the counts of the K1 N-grams we've selected.
"""
K1 = len(K1_most_frequent_Ngrams_list)
feature_vector = K1 * [0]
file_Ngrams = binary_file_to_Ngram_counts(sample, N)
for i in range(K1):
feature_vector[i] = file_Ngrams[K1_most_frequent_Ngrams_list[i]]
return feature_vector
- 我们遍历目录,并使用前面的
featurize_sample
函数来特征化我们的样本。同时,我们创建一组标签:
directories_with_labels = [("Benign PE Samples", 0), ("Malicious PE Samples", 1)]
X = []
y = []
for dataset_path, label in directories_with_labels:
all_samples = [f for f in listdir(dataset_path) if isfile(join(dataset_path, f))]
for sample in all_samples:
file_path = join(dataset_path, sample)
X.append(featurize_sample(file_path, K1_most_frequent_Ngrams_list))
y.append(label)
- 我们导入将用于特征选择的库,并指定希望缩小到多少个特征:
from sklearn.feature_selection import SelectKBest, mutual_info_classif, chi2
K2 = 10
- 我们对 N-gram 进行三种类型的特征选择:
- 频率—选择最常见的 N-gram:
X = np.asarray(X)
X_top_K2_freq = X[:,:K2]
- 互信息—通过互信息算法选择排名最高的 N-gram:
mi_selector = SelectKBest(mutual_info_classif, k=K2)
X_top_K2_mi = mi_selector.fit_transform(X, y)
- 卡方—通过卡方算法选择排名最高的 N-gram:
chi2_selector = SelectKBest(chi2, k=K2)
X_top_K2_ch2 = chi2_selector.fit_transform(X, y)
它是如何工作的……
与之前的方案不同,在那里我们分析了单个文件的 N-gram,而在这个方案中,我们会查看大量文件,以了解哪些 N-gram 是最具信息量的特征。我们首先指定包含样本的文件夹,N 的值,并导入一些模块来列举文件(步骤 1)。接下来,我们统计数据集中的所有文件的所有 N-gram(步骤 2)。这使我们能够找到全局最常见的 N-gram。在这些 N-gram 中,我们筛选出 K1=1000
个最常见的(步骤 3)。然后,我们引入一个辅助方法 featurizeSample
,用于提取样本并输出其字节序列中 K1 个最常见 N-gram 的出现次数(步骤 4)。接下来,我们遍历文件目录,并使用之前的 featurizeSample
函数来特征化样本,同时记录它们的标签,标记为恶意或良性(步骤 5)。标签的重要性在于,评估某个 N-gram 是否具有信息量,取决于能否基于其区分恶意和良性类别。
我们导入 SelectKBest
库,通过评分函数选择最佳特征,以及两种评分函数:互信息和卡方(步骤 6)。最后,我们应用三种不同的特征选择方案来选择最佳的 N-gram,并将这些知识应用于转换我们的特征(步骤 7)。在第一种方法中,我们简单地选择 K2 个最常见的 N-gram。请注意,这种选择方法在文献中经常推荐,因为它不需要标签或复杂的计算,较为简单。在第二种方法中,我们使用互信息来缩小 K2 个特征,而在第三种方法中,我们使用卡方来进行选择。
构建静态恶意软件检测器
在本节中,我们将看到如何将之前讨论的方案组合起来,构建一个恶意软件检测器。我们的恶意软件检测器将同时采用从 PE 头部提取的特征以及从 N-gram 派生的特征。
准备工作
本方案的准备工作包括在 pip
中安装 scikit-learn
、nltk
和 pefile
包。安装说明如下:
pip install sklearn nltk pefile
另外,在存储库根目录的"PE Samples Dataset"
文件夹中已为您提供了良性和恶意文件。请将名为"Benign PE Samples*.7z"
的所有存档解压到名为"Benign PE Samples"
的文件夹中。将名为"Malicious PE Samples*.7z"
的所有存档解压到名为"Malicious PE Samples"
的文件夹中。
如何做...
在接下来的步骤中,我们将展示一个完整的工作流程,我们将从原始样本开始,对其进行特征提取,将结果向量化,将它们组合在一起,最后训练和测试分类器:
- 首先,列举我们的样本并分配它们的标签:
import os
from os import listdir
directories_with_labels = [("Benign PE Samples", 0), ("Malicious PE Samples", 1)]
list_of_samples = []
labels = []
for dataset_path, label in directories_with_labels:
samples = [f for f in listdir(dataset_path)]
for sample in samples:
file_path = os.path.join(dataset_path, sample)
list_of_samples.append(file_path)
labels.append(label)
- 我们执行分层的训练测试分离:
from sklearn.model_selection import train_test_split
samples_train, samples_test, labels_train, labels_test = train_test_split(
list_of_samples, labels, test_size=0.3, stratify=labels, random_state=11
)
- 我们引入之前章节中的便捷函数,以获取特征:
import collection
from nltk import ngrams
import numpy as np
import pefile
def read_file(file_path):
"""Reads in the binary sequence of a binary file."""
with open(file_path, "rb") as binary_file:
data = binary_file.read()
return data
def byte_sequence_to_Ngrams(byte_sequence, N):
"""Creates a list of N-grams from a byte sequence."""
Ngrams = ngrams(byte_sequence, N)
return list(Ngrams)
def binary_file_to_Ngram_counts(file, N):
"""Takes a binary file and outputs the N-grams counts of its binary sequence."""
filebyte_sequence = read_file(file)
file_Ngrams = byte_sequence_to_Ngrams(filebyte_sequence, N)
return collections.Counter(file_Ngrams)
def get_NGram_features_from_sample(sample, K1_most_frequent_Ngrams_list):
"""Takes a sample and produces a feature vector.
The features are the counts of the K1 N-grams we've selected.
"""
K1 = len(K1_most_frequent_Ngrams_list)
feature_vector = K1 * [0]
file_Ngrams = binary_file_to_Ngram_counts(sample, N)
for i in range(K1):
feature_vector[i] = file_Ngrams[K1_most_frequent_Ngrams_list[i]]
return feature_vector
def preprocess_imports(list_of_DLLs):
"""Normalize the naming of the imports of a PE file."""
temp = [x.decode().split(".")[0].lower() for x in list_of_DLLs]
return " ".join(temp)
def get_imports(pe):
"""Get a list of the imports of a PE file."""
list_of_imports = []
for entry in pe.DIRECTORY_ENTRY_IMPORT:
list_of_imports.append(entry.dll)
return preprocess_imports(list_of_imports)
def get_section_names(pe):
"""Gets a list of section names from a PE file."""
list_of_section_names = []
for sec in pe.sections:
normalized_name = sec.Name.decode().replace("\x00", "").lower()
list_of_section_names.append(normalized_name)
return "".join(list_of_section_names)
- 我们选择前 100 个最常见的二元组作为我们的特征:
N = 2
Ngram_counts_all = collections.Counter([])
for sample in samples_train:
Ngram_counts_all += binary_file_to_Ngram_counts(sample, N)
K1 = 100
K1_most_frequent_Ngrams = Ngram_counts_all.most_common(K1)
K1_most_frequent_Ngrams_list = [x[0] for x in K1_most_frequent_Ngrams]
- 我们提取每个样本中的 N-gram 计数、节名称、导入以及节的数量,跳过无法解析 PE 头部的样本:
imports_corpus_train = []
num_sections_train = []
section_names_train = []
Ngram_features_list_train = []
y_train = []
for i in range(len(samples_train)):
sample = samples_train[i]
try:
NGram_features = get_NGram_features_from_sample(
sample, K1_most_frequent_Ngrams_list
)
pe = pefile.PE(sample)
imports = get_imports(pe)
n_sections = len(pe.sections)
sec_names = get_section_names(pe)
imports_corpus_train.append(imports)
num_sections_train.append(n_sections)
section_names_train.append(sec_names)
Ngram_features_list_train.append(NGram_features)
y_train.append(labels_train[i])
except Exception as e:
print(sample + ":")
print(e)
- 我们使用哈希向量化器,然后使用
tfidf
将导入和节名称(均为文本特征)转换为数值形式:
from sklearn.feature_extraction.text import HashingVectorizer, TfidfTransformer
from sklearn.pipeline import Pipeline
imports_featurizer = Pipeline(
[
("vect", HashingVectorizer(input="content", ngram_range=(1, 2))),
("tfidf", TfidfTransformer(use_idf=True,)),
]
)
section_names_featurizer = Pipeline(
[
("vect", HashingVectorizer(input="content", ngram_range=(1, 2))),
("tfidf", TfidfTransformer(use_idf=True,)),
]
)
imports_corpus_train_transformed = imports_featurizer.fit_transform(
imports_corpus_train
)
section_names_train_transformed = section_names_featurizer.fit_transform(
section_names_train
)
- 我们将向量化的特征合并为单个数组:
from scipy.sparse import hstack, csr_matrix
X_train = hstack(
[
Ngram_features_list_train,
imports_corpus_train_transformed,
section_names_train_transformed,
csr_matrix(num_sections_train).transpose(),
]
)
- 我们在训练集上训练了一个随机森林分类器,并打印出其得分:
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier(n_estimators=100)
clf = clf.fit(X_train, y_train)
- 我们收集测试集的特征,就像我们对训练集所做的一样:
imports_corpus_test = []
num_sections_test = []
section_names_test = []
Ngram_features_list_test = []
y_test = []
for i in range(len(samples_test)):
file = samples_test[i]
try:
NGram_features = get_NGram_features_from_sample(
sample, K1_most_frequent_Ngrams_list
)
pe = pefile.PE(file)
imports = get_imports(pe)
n_sections = len(pe.sections)
sec_names = get_section_names(pe)
imports_corpus_test.append(imports)
num_sections_test.append(n_sections)
section_names_test.append(sec_names)
Ngram_features_list_test.append(NGram_features)
y_test.append(labels_test[i])
except Exception as e:
print(sample + ":")
print(e)
- 我们将先前训练过的转换器应用于向量化文本特征,然后在生成的测试集上测试我们的分类器:
imports_corpus_test_transformed = imports_featurizer.transform(imports_corpus_test)
section_names_test_transformed = section_names_featurizer.transform(section_names_test)
X_test = hstack(
[
Ngram_features_list_test,
imports_corpus_test_transformed,
section_names_test_transformed,
csr_matrix(num_sections_test).transpose(),
]
)
print(clf.score(X_test, y_test))
我们的分类器得分如下:
0.8859649122807017
工作原理...
本节中有几个值得注意的新想法。我们首先列举我们的样本并为它们分配其相应的标签(步骤 1)。由于我们的数据集不平衡,使用分层的训练测试分离是合理的(步骤 2)。在分层的训练测试分离中,创建一个训练集和测试集,其中每个类的比例与原始集中的比例相同。这确保了训练集不会由于偶然事件而只包含一个类。接下来,我们加载将用于对样本进行特征提取的函数。我们像以前的方法一样使用我们的特征提取技术来计算最佳的 N-gram 特征(步骤 4),然后遍历所有文件以提取所有特征(步骤 5)。然后,我们使用基本的自然语言处理方法对先前获取的 PE 头部特征,如节名称和导入,进行向量化(步骤 6)。
获得了所有这些不同的特征后,我们现在可以将它们合并在一起,这一步通过使用scipy
的 hstack 来完成,将不同的特征合并为一个大的稀疏scipy
数组(步骤 7)。接下来,我们继续训练一个使用默认参数的随机森林分类器(步骤 8),然后对我们的测试集重复提取过程(步骤 9)。在步骤 10 中,我们最终测试我们的训练好的分类器,并获得一个有前景的起始分数。总的来说,这个配方为一个恶意软件分类器提供了基础,可以扩展成一个强大的解决方案。
解决类别不平衡问题
在将机器学习应用于网络安全时,我们经常面对严重不平衡的数据集。例如,获取大量正常样本可能比收集恶意样本容易得多。反过来,你可能在一个因法律原因而禁止保存正常样本的企业工作。在这两种情况下,你的数据集都会严重偏向于某一类。因此,旨在最大化准确度的简单机器学习方法将导致一个几乎将所有样本预测为来自过度代表类的分类器。有几种技术可以用来解决类别不平衡的问题。
准备工作
本配方的准备工作包括安装scikit-learn
和imbalanced-learn
的 pip 包。安装说明如下:
pip install sklearn imbalanced-learn
如何操作...
在接下来的步骤中,我们将演示几种处理不平衡数据的方法:
- 首先加载训练和测试数据,导入决策树,以及我们将用来评估性能的一些库:
from sklearn import tree
from sklearn.metrics import balanced_accuracy_score
import numpy as np
import scipy.sparse
import collections
X_train = scipy.sparse.load_npz("training_data.npz")
y_train = np.load("training_labels.npy")
X_test = scipy.sparse.load_npz("test_data.npz")
y_test = np.load("test_labels.npy")
- 训练和测试一个简单的决策树分类器:
dt = tree.DecisionTreeClassifier()
dt.fit(X_train, y_train)
dt_pred = dt.predict(X_test)
print(collections.Counter(dt_pred))
print(balanced_accuracy_score(y_test, dt_pred))
这将产生以下输出:
Counter({0: 121, 1: 10})
0.8333333333333333
接下来,我们测试几种提高性能的技术。
- 加权:我们将分类器的类别权重设置为
"balanced"
,并训练和测试这个新分类器:
dt_weighted = tree.DecisionTreeClassifier(class_weight="balanced")
dt_weighted.fit(X_train, y_train)
dt_weighted_pred = dt_weighted.predict(X_test)
print(collections.Counter(dt_weighted_pred))
print(balanced_accuracy_score(y_test, dt_weighted_pred))
这将产生以下输出:
Counter({0: 114, 1: 17})
0.9913793103448276
- 对小类别进行上采样:我们从类别 0 和类别 1 中提取所有测试样本:
from sklearn.utils import resample
X_train_np = X_train.toarray()
class_0_indices = [i for i, x in enumerate(y_train == 0) if x]
class_1_indices = [i for i, x in enumerate(y_train == 1) if x]
size_class_0 = sum(y_train == 0)
X_train_class_0 = X_train_np[class_0_indices, :]
y_train_class_0 = [0] * size_class_0
X_train_class_1 = X_train_np[class_1_indices, :]
- 我们对类别 1 的元素进行有放回的上采样,直到类别 1 和类别 0 的样本数量相等:
X_train_class_1_resampled = resample(
X_train_class_1, replace=True, n_samples=size_class_0
)
y_train_class_1_resampled = [1] * size_class_0
- 我们将新上采样的样本合并成一个训练集:
X_train_resampled = np.concatenate([X_train_class_0, X_train_class_1_resampled])
y_train_resampled = y_train_class_0 + y_train_class_1_resampled
- 我们在上采样的训练集上训练并测试一个随机森林分类器:
from scipy import sparse
X_train_resampled = sparse.csr_matrix(X_train_resampled)
dt_resampled = tree.DecisionTreeClassifier()
dt_resampled.fit(X_train_resampled, y_train_resampled)
dt_resampled_pred = dt_resampled.predict(X_test)
print(collections.Counter(dt_resampled_pred))
print(balanced_accuracy_score(y_test, dt_resampled_pred))
这将产生以下输出:
Counter({0: 114, 1: 17})
0.9913793103448276
- 对大类别进行下采样:我们执行与上采样相似的步骤,只不过这次我们对大类别进行下采样,直到它与小类别的样本数量相等:
X_train_np = X_train.toarray()
class_0_indices = [i for i, x in enumerate(y_train == 0) if x]
class_1_indices = [i for i, x in enumerate(y_train == 1) if x]
size_class_1 = sum(y_train == 1)
X_train_class_1 = X_train_np[class_1_indices, :]
y_train_class_1 = [1] * size_class_1
X_train_class_0 = X_train_np[class_0_indices, :]
X_train_class_0_downsampled = resample(
X_train_class_0, replace=False, n_samples=size_class_1
)
y_train_class_0_downsampled = [0] * size_class_1
- 我们从下采样数据中创建一个新的训练集:
X_train_downsampled = np.concatenate([X_train_class_1, X_train_class_0_downsampled])
y_train_downsampled = y_train_class_1 + y_train_class_0_downsampled
- 我们在这个数据集上训练一个随机森林分类器:
X_train_downsampled = sparse.csr_matrix(X_train_downsampled)
dt_downsampled = tree.DecisionTreeClassifier()
dt_downsampled.fit(X_train_downsampled, y_train_downsampled)
dt_downsampled_pred = dt_downsampled.predict(X_test)
print(collections.Counter(dt_downsampled_pred))
print(balanced_accuracy_score(y_test, dt_downsampled_pred))
这将产生以下输出:
Counter({0: 100, 1: 31})
0.9310344827586207
- 包含内置平衡采样器的分类器:我们使用
imbalanced-learn
包中的分类器,这些分类器在训练估计器之前会对数据子集进行重采样:
from imblearn.ensemble import BalancedBaggingClassifier
balanced_clf = BalancedBaggingClassifier(
base_estimator=tree.DecisionTreeClassifier(),
sampling_strategy="auto",
replacement=True,
)
balanced_clf.fit(X_train, y_train)
balanced_clf_pred = balanced_clf.predict(X_test)
print(collections.Counter(balanced_clf_pred))
print(balanced_accuracy_score(y_test, balanced_clf_pred))
这将产生以下输出:
Counter({0: 113, 1: 18})
0.9494252873563218
如何实现…
我们首先加载一个预定义的数据集(第 1 步),使用scipy.sparse.load_npz
加载函数来加载之前保存的稀疏矩阵。下一步是对我们的数据训练一个基本的决策树模型(第 2 步)。为了评估性能,我们使用平衡准确度分数,这是一种常用于处理不平衡数据集分类问题的衡量标准。根据定义,平衡准确度是对每个类别召回率的平均值。最好的值是 1,而最差的值是 0。
在接下来的步骤中,我们采用不同的技术来解决类别不平衡问题。我们的第一个方法是利用类别权重来调整决策树,以适应不平衡的数据集(第 3 步)。平衡模式使用* y 的值自动调整权重,该权重与输入数据中类别的频率成反比,公式为n_samples / (n_classes * np.bincount(y))*。在第 4 到第 7 步中,我们使用上采样来处理类别不平衡。这是通过随机复制少数类的观察值,以加强少数类的信号。
有几种方法可以做到这一点,但最常见的做法是像我们之前那样进行有放回的重采样。上采样的两个主要问题是,它会增加数据集的大小,并且由于多次在相同样本上训练,它可能导致过拟合。在第 8 到第 10 步中,我们进行了主要类别的下采样。这意味着我们不会使用所有样本,而是使用足够的样本来平衡类别。
这个技术的主要问题在于我们被迫使用一个较小的训练集。我们最终的方案,也是最复杂的方案,是使用一个包括内部平衡采样器的分类器,即来自imbalanced-learn
的BalancedBaggingClassifier
(第 11 步)。总体来看,我们发现每一种应对类别不平衡的方法都提高了平衡准确度分数。
处理类型 I 和类型 II 错误
在许多机器学习的情境中,一种错误可能比另一种更重要。例如,在一个多层防御系统中,要求某一层具有低假警报(低假阳性)率可能是合理的,虽然这会牺牲一定的检测率。在这一部分中,我们提供了一种确保假阳性率(FPR)不超过预定限制的方法,具体通过使用阈值化来实现。
准备工作
准备这一方法需要在pip
中安装scikit-learn
和xgboost
。安装说明如下:
pip install sklearn xgboost
如何做到这一点……
在接下来的步骤中,我们将加载一个数据集,训练一个分类器,然后调整阈值以满足假阳性率的约束:
- 我们加载一个数据集并指定所需的假阳性率(FPR)为 1%或更低:
import numpy as np
from scipy import sparse
import scipy
X_train = scipy.sparse.load_npz("training_data.npz")
y_train = np.load("training_labels.npy")
X_test = scipy.sparse.load_npz("test_data.npz")
y_test = np.load("test_labels.npy")
desired_FPR = 0.01
- 我们编写方法来计算
FPR
和TPR
:
from sklearn.metrics import confusion_matrix
def FPR(y_true, y_pred):
"""Calculate the False Positive Rate."""
CM = confusion_matrix(y_true, y_pred)
TN = CM[0][0]
FP = CM[0][1]
return FP / (FP + TN)
def TPR(y_true, y_pred):
"""Calculate the True Positive Rate."""
CM = confusion_matrix(y_true, y_pred)
TP = CM[1][1]
FN = CM[1][0]
return TP / (TP + FN)
- 我们编写一个方法,通过阈值化将概率向量转换为布尔向量:
def perform_thresholding(vector, threshold):
"""Threshold a vector."""
return [0 if x >= threshold else 1 for x in vector]
- 我们训练一个 XGBoost 模型并计算训练数据的概率预测:
from xgboost import XGBClassifier
clf = XGBClassifier()
clf.fit(X_train, y_train)
clf_pred_prob = clf.predict_proba(X_train)
- 让我们检查一下我们的预测概率向量:
print("Probabilities look like so:")
print(clf_pred_prob[0:5])
print()
这将产生以下输出:
Probabilities look like so:
[[0.9972162 0.0027838 ]
[0.9985584 0.0014416 ]
[0.9979202 0.00207978]
[0.96858877 0.03141126]
[0.91427565 0.08572436]]
- 我们遍历 1000 个不同的阈值,计算每个阈值的 FPR,当满足
FPR<=desiredFPR
时,我们选择那个阈值:
M = 1000
print("Fitting threshold:")
for t in reversed(range(M)):
scaled_threshold = float(t) / M
thresholded_prediction = perform_thresholding(clf_pred_prob[:, 0], scaled_threshold)
print(t, FPR(y_train, thresholded_prediction), TPR(y_train, thresholded_prediction))
if FPR(y_train, thresholded_prediction) <= desired_FPR:
print()
print("Selected threshold: ")
print(scaled_threshold)
break
这将产生以下输出:
Fitting threshold:
999 1.0 1.0
998 0.6727272727272727 1.0
997 0.4590909090909091 1.0
996 0.33181818181818185 1.0
<snip>
649 0.05454545454545454 1.0
648 0.004545454545454545 0.7857142857142857
Selected threshold: 0.648
工作原理……
我们通过加载一个先前已特征化的数据集并指定一个 1%的期望 FPR 约束来开始这个过程(步骤 1)。实际使用的值高度依赖于具体情况和所考虑的文件类型。这里有几点需要考虑:如果文件是极为常见,但很少是恶意的,例如 PDF 文件,那么期望的 FPR 必须设置得非常低,例如 0.01%。
如果系统得到了额外系统的支持,这些系统可以在没有人工干预的情况下再次验证其判定结果,那么较高的 FPR 可能不会有害。最后,客户可能会有一个偏好,这将建议一个推荐值。我们在步骤 2 中定义了一对便捷函数,用于计算 FPR 和 TPR——这些函数非常实用且可重复使用。我们定义的另一个便捷函数是一个函数,它将接受我们的阈值,并用它对一个数值向量进行阈值处理(步骤 3)。
在步骤 4 中,我们在训练数据上训练模型,并在训练集上确定预测概率。你可以在步骤 5 中看到这些预测结果。当有大量数据集时,使用验证集来确定适当的阈值将减少过拟合的可能性。最后,我们计算将来分类时要使用的阈值,以确保满足 FPR 约束(步骤 6)。
第三章:高级恶意软件检测
在本章中,我们将涵盖更高级的恶意软件分析概念。在上一章中,我们讨论了恶意软件分类的一般方法。这里,我们将讨论更具体的方法和前沿技术,特别是我们将讨论如何处理混淆和打包的恶意软件,如何扩大 N-gram 特征的收集规模,以及如何使用深度学习来检测甚至创建恶意软件。
本章包括以下内容:
-
检测混淆的 JavaScript
-
特征化 PDF 文件
-
使用哈希图算法快速提取 N-grams
-
构建动态恶意软件分类器
-
MalConv – 用于恶意 PE 检测的端到端深度学习
-
使用打包器
-
组装一个打包的示例数据集
-
构建打包器分类器
-
MalGAN – 创建规避型恶意软件
-
跟踪恶意软件漂移
技术要求
以下是本章的技术前提:
-
Keras
-
TensorFlow
-
XGBoost
-
UPX
-
Statsmodels
代码和数据集可以在github.com/PacktPublishing/Machine-Learning-for-Cybersecurity-Cookbook/tree/master/Chapter03
找到。
检测混淆的 JavaScript
在本节中,我们将看到如何使用机器学习来检测 JavaScript 文件是否被混淆。这样做可以创建一个二进制特征,标识文件是否被混淆,以用于良性/恶性分类,也可以作为解混淆脚本的前置步骤。
准备工作
本教程的准备工作包括在pip
中安装scikit-learn
包。命令如下:
pip install sklearn
此外,混淆和非混淆的 JavaScript 文件已经在仓库中提供。请将JavascriptSamplesNotObfuscated.7z
解压到名为JavaScript Samples
的文件夹中,将JavascriptSamplesObfuscated.7z
解压到名为JavaScript Samples Obfuscated
的文件夹中。
如何实现...
在接下来的步骤中,我们将演示如何使用二分类器来检测混淆的 JavaScript 文件:
- 首先导入我们将用来处理 JavaScript 内容、准备数据集、分类以及衡量分类器性能所需的库:
import os
from sklearn.feature_extraction.text import HashingVectorizer, TfidfTransformer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, confusion_matrix
from sklearn.pipeline import Pipeline
- 我们指定了混淆和非混淆 JavaScript 文件的路径,并为这两种文件分配了不同的标签:
js_path = "path\\to\\JavascriptSamples"
obfuscated_js_path = "path\\to\\ObfuscatedJavascriptSamples"
corpus = []
labels = []
file_types_and_labels = [(js_path, 0), (obfuscated_js_path, 1)]
- 然后,我们将文件读入语料库并准备标签:
for files_path, label in file_types_and_labels:
files = os.listdir(files_path)
for file in files:
file_path = files_path + "/" + file
try:
with open(file_path, "r") as myfile:
data = myfile.read().replace("\n", "")
data = str(data)
corpus.append(data)
labels.append(label)
except:
pass
- 我们将数据集分为训练集和测试集,并准备一个管道来执行基本的 NLP,随后是一个随机森林分类器:
X_train, X_test, y_train, y_test = train_test_split(
corpus, labels, test_size=0.33, random_state=42
)
text_clf = Pipeline(
[
("vect", HashingVectorizer(input="content", ngram_range=(1, 3))),
("tfidf", TfidfTransformer(use_idf=True,)),
("rf", RandomForestClassifier(class_weight="balanced")),
]
)
- 最后,我们将我们的管道拟合到训练数据,预测测试数据,然后输出结果:
text_clf.fit(X_train, y_train)
y_test_pred = text_clf.predict(X_test)
print(accuracy_score(y_test, y_test_pred))
print(confusion_matrix(y_test, y_test_pred))
准确率和混淆矩阵如下所示:
它是如何工作的...
我们首先导入标准的 Python 库以分析文件并设置机器学习管道(第 1 步)。在第 2 步和第 3 步中,我们将非混淆和混淆的 JavaScript 文件收集到数组中,并为它们分配相应的标签。这是我们二分类问题的准备工作。请注意,生成这个分类器的主要挑战是如何获取一个大而有用的数据集。解决这个难题的思路包括收集大量 JavaScript 样本,然后使用不同的工具对这些样本进行混淆。这样,分类器就有可能避免过拟合某一种混淆方式。收集完数据后,我们将其分为训练集和测试集(第 4 步)。此外,我们设置一个管道来应用自然语言处理(NLP)方法处理 JavaScript 代码,然后训练一个分类器(第 4 步)。最后,我们在第 5 步中评估分类器的性能。你会注意到,除了构建合适数据集的挑战之外,这个教程与我们用于检测文件类型的教程类似。
对 PDF 文件进行特征提取
在本节中,我们将学习如何对 PDF 文件进行特征提取,以便用于机器学习。我们将使用的工具是由Didier Stevens设计的PDFiD
Python 脚本(blog.didierstevens.com/
)。Stevens 选择了 20 个常见的特征,这些特征通常出现在恶意文件中,包括 PDF 文件是否包含 JavaScript 或启动自动操作。在文件中发现这些特征是可疑的,因此它们的出现可能表明存在恶意行为。
本质上,该工具扫描 PDF 文件,并统计每个约 20 个特征的出现次数。工具运行的输出如下所示:
PDFiD 0.2.5 PythonBrochure.pdf
PDF Header: %PDF-1.6
obj 1096
endobj 1095
stream 1061
endstream 1061
xref 0
trailer 0
startxref 2
/Page 32
/Encrypt 0
/ObjStm 43
/JS 0
/JavaScript 0
/AA 1
/OpenAction 0
/AcroForm 1
/JBIG2Decode 0
/RichMedia 0
/Launch 0
/EmbeddedFile 0
/XFA 0
/URI 0
/Colors > 2²⁴ 0
准备工作
这个教程所需的文件位于仓库中pdfid
和PDFSamples
文件夹内。
如何操作...
在接下来的步骤中,你将使用PDFiD
脚本对一批 PDF 文件进行特征提取:
-
下载工具并将所有相关代码与特征提取 PDF 的
Files.ipynb
放在同一目录下。 -
导入 IPython 的
io
模块,以便捕获外部脚本的输出:
from IPython.utils import io
- 定义一个函数来对 PDF 进行特征提取:
def PDF_to_FV(file_path):
"""Featurize a PDF file using pdfid."""
- 运行
pdfid
工具并捕获操作的输出:
with io.capture_output() as captured:
%run -i pdfid $file_path
out = captured.stdout
- 接下来,解析输出,使其成为一个数值向量:
out1 = out.split("\n")[2:-2]
return [int(x.split()[-1]) for x in out1]
- 导入
listdir
以列举文件夹中的文件,并指定存放 PDF 集合的目录:
from os import listdir
PDFs_path = "PDFSamples\\"
- 遍历目录中的每个文件,对其进行特征提取,然后将所有特征向量收集到
X
中:
X = []
files = listdir(PDFs_path)
for file in files:
file_path = PDFs_path + file
X.append(PDF_to_FV(file_path))
工作原理…
我们通过下载PDFiD
工具并将我们的 PDF 文件放在一个方便的位置进行分析(步骤 1)来开始我们的准备工作。请注意,该工具是免费且易于使用。接着,我们导入非常有用的 IPython 的io
模块,以便捕获外部程序PDFiD
的结果(步骤 2)。在接下来的步骤中,步骤 3和步骤 5,我们定义了一个将 PDF 文件转换为特征向量的函数 PDF to FV。特别地,它利用了PDFiD
工具,然后将其输出解析成方便的形式。当我们在PDFSamples\PythonBrochure.pdf
文件上运行时,我们的函数输出以下向量:
[1096, 1095, 1061, 1061, 0, 0, 2, 32, 0, 43, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0]
现在我们能够对单个 PDF 文件进行特征化,为什么不对所有的 PDF 文件进行特征化,以便使其适用于机器学习(步骤 6和步骤 7)。特别是在步骤 6中,我们提供包含我们想要进行特征化的 PDF 文件的路径,在步骤 7中,我们执行文件的实际特征化。
使用哈希-gram 算法快速提取 N-gram
在本节中,我们演示了一种快速和内存高效提取最频繁 N-gram 的技术。这使得处理大量 N-gram 带来的挑战变得更加容易。这种技术称为Hash-Grams,并依赖于在提取时对 N-gram 进行哈希处理。N-gram 的一个特性是它们遵循幂律,这确保了哈希碰撞对所获得特征的质量几乎没有影响。
准备工作
准备这个步骤涉及在pip
中安装nltk
。命令如下:
pip install nltk
另外,在存储库的根目录中为您提供了良性和恶意文件,将所有名为Benign PE Samples*.7z
的存档解压缩到名为Benign PE Samples
的文件夹中,并将所有名为Malicious PE Samples*.7z
的存档解压缩到名为Malicious PE Samples
的文件夹中。
如何操作...
在接下来的步骤中,我们将演示哈希-gram 算法的工作原理:
- 首先指定包含我们样本的文件夹,参数 N,并导入一个用于哈希处理和从字符串中提取 N-gram 的库:
from os import listdir
from nltk import ngrams
import hashlib
directories = ["Benign PE Samples", "Malicious PE Samples"]
N = 2
- 我们创建一个函数来读取文件的字节并将其转换为 N-gram:
def read_file(file_path):
"""Reads in the binary sequence of a binary file."""
with open(file_path, "rb") as binary_file:
data = binary_file.read()
return data
def byte_sequence_to_Ngrams(byte_sequence, N):
"""Creates a list of N-grams from a byte sequence."""
return ngrams(byte_sequence, N)
- 现在,我们将要对 N-gram 进行哈希处理:
def hash_input(inp):
"""Compute the MD5 hash of an input."""
return int(hashlib.md5(inp).hexdigest(), 16)
def make_ngram_hashable(Ngram):
"""Convert N-gram into bytes to be hashable."""
return bytes(Ngram)
hash_file_Ngrams_into_dictionary
函数接受一个 N-gram,对其进行哈希处理,然后增加字典中哈希的计数。减少模块 B(%B)确保字典中的键不超过B
个:
def hash_file_Ngrams_into_dictionary(file_Ngrams, T):
"""Hashes N-grams in a list and then keeps track of the counts in a dictionary."""
for Ngram in file_Ngrams:
hashable_Ngram = make_ngram_hashable(Ngram)
hashed_and_reduced = hash_input(hashable_Ngram) % B
T[hashed_and_reduced] = T.get(hashed_and_reduced, 0) + 1
- 我们为 B 指定一个值,即小于 2¹⁶ 的最大素数,并创建一个空字典:
B = 65521
T = {}
- 我们遍历我们的文件并计算它们的哈希 N-gram:
for dataset_path in directories:
samples = [f for f in listdir(dataset_path)]
for file in samples:
file_path = dataset_path + "/" + file
file_byte_sequence = read_file(file_path)
file_Ngrams = byte_sequence_to_Ngrams(file_byte_sequence, N)
hash_file_Ngrams_into_dictionary(file_Ngrams, T)
- 我们使用
heapq
选择最频繁的K1=1000
:
K1 = 1000
import heapq
K1_most_common_Ngrams_Using_Hash_Grams = heapq.nlargest(K1, T)
- 一旦选择了哈希后的 N-grams,这些将构成特征集。为了特征化一个样本,我们遍历它的 N-grams,进行哈希并取模,如果结果是选中的哈希后的 N-grams 之一,则在该索引位置增加特征向量的值:
def featurize_sample(file, K1_most_common_Ngrams_Using_Hash_Grams):
"""Takes a sample and produces a feature vector.
The features are the counts of the K1 N-grams we've selected.
"""
K1 = len(K1_most_common_Ngrams_Using_Hash_Grams)
fv = K1 * [0]
file_byte_sequence = read_file(file_path)
file_Ngrams = byte_sequence_to_Ngrams(file_byte_sequence, N)
for Ngram in file_Ngrams:
hashable_Ngram = make_ngram_hashable(Ngram)
hashed_and_reduced = hash_input(hashable_Ngram) % B
if hashed_and_reduced in K1_most_common_Ngrams_Using_Hash_Grams:
index = K1_most_common_Ngrams_Using_Hash_Grams.index(hashed_and_reduced)
fv[index] += 1
return fv
- 最后,我们对数据集进行特征化:
X = []
for dataset_path in directories:
samples = [f for f in listdir(dataset_path)]
for file in samples:
file_path = dataset_path + "/" + file
X.append(featurize_sample(file_path, K1_most_common_Ngrams_Using_Hash_Grams))
工作原理…
哈希 N-gram 配方的初始步骤与普通的 N-gram 提取类似。首先,我们通过指定包含样本的文件夹以及我们的 N 值(如 N-grams)来进行准备。此外,我们导入一个哈希库,这与普通的 N-gram 提取操作不同(步骤 1)。继续我们的准备工作,我们定义一个函数来读取文件的所有字节(与读取其内容不同),并将这些字节转化为 N-grams(步骤 2)。我们定义一个函数来计算 N-gram 的 MD5 哈希,并将结果以十六进制数返回。此外,我们还定义一个函数,将 N-gram 转换为其字节构成,以便能够进行哈希处理(步骤 3)。
接下来,我们定义一个函数来遍历文件的哈希 N-grams,将其对 B 取模,然后增加字典中对应哈希值的计数(步骤 4)。参数 B 控制字典中不同键的数量上限。通过哈希,我们能够随机化计数 N-grams 的桶。现在,在我们即将运行函数时,是时候指定 B 的值了。我们选择 B 的值为小于 2¹⁶ 的最大素数(步骤 5)。
通常选择一个素数,以确保哈希碰撞的数量最小。我们现在遍历文件目录,并对每个文件应用我们之前定义的函数(步骤 6)。结果是一个包含哈希 N-grams 计数的大字典,T。这个字典不大,我们很容易从中选择最常见的 K1 个哈希后的 N-grams(步骤 7)。通过这样做,选择到最频繁的 N-grams 的概率很高,尽管由于哈希碰撞,可能会多于 K1 个。此时,我们得到了特征集,即通过哈希映射到我们选择的 K1 个哈希 N-grams 的 N-grams。接下来,我们对数据集进行特征化(步骤 8 和 步骤 9)。特别地,我们遍历文件,计算它们的 N-grams。如果一个 N-gram 的哈希值是 K1 个选中值之一,我们认为它是一个频繁的 N-gram,并将其作为特征集的一部分。
需要注意的是,哈希 N-grams 算法并不总是更快,但当所考虑的数据集较大时,通常会更快。在许多情况下,当使用朴素的方法提取 N-grams 导致内存错误时,哈希 N-grams 能够成功终止。
另见
关于哈希 N-gram 算法的更多细节,请参见www.edwardraff.com/publications/hash-grams-faster.pdf
.
构建一个动态恶意软件分类器
在某些情况下,能够基于行为检测恶意软件具有相当大的优势。特别是,当恶意软件在动态环境中进行分析时,隐藏其意图变得更加困难。因此,基于动态信息的分类器比基于静态信息的分类器更准确。在本节中,我们提供了一个动态恶意软件分类器的教程。我们使用的数据集来自 VirusShare 仓库中的 Android 应用程序。动态分析由 Johannes Thon 在多台 LG Nexus 5 设备(Android API 23)上进行,(在 LG Nexus 5 设备农场(API 23)上动态分析了 4000 多个恶意应用,goorax 在 LG Nexus 5 设备农场(API 23)上动态分析了 4300 多个良性应用,原始内容未修改,使用的是 CC BY 许可证)。
我们的方法是使用 N-gram 对 API 调用序列进行处理。
准备工作
该教程的准备工作包括在pip
中安装scikit-learn
、nltk
和xgboost
。命令如下:
pip install sklearn nltk xgboost
此外,您可以在仓库中找到良性和恶意的动态分析文件。将所有名为DA Logs Benign*.7z
的压缩包解压到一个名为DA Logs Benign
的文件夹中,将所有名为DA Logs Malware*.7z
的压缩包解压到一个名为DA Logs Malicious
的文件夹中。
如何操作…
在接下来的步骤中,我们展示了如何根据观察到的 API 调用序列检测恶意软件。
- 我们的日志是 JSON 格式,因此我们首先导入 JSON 库。
import numpy as np
import os
import json
directories_with_labels = [("DA Logs Benign", 0), ("DA Logs Malware", 1)]
- 编写一个函数来解析 JSON 日志:
def get_API_class_method_type_from_log(log):
"""Parses out API calls from behavioral logs."""
API_data_sequence = []
with open(log) as log_file:
json_log = json.load(log_file)
api_calls_array = "[" + json_log["api_calls"] + "]"
- 我们选择提取 API 调用的类、方法和类型:
api_calls = json.loads(api_calls_array)
for api_call in api_calls:
data = api_call["class"] + ":" + api_call["method"] + ":" + api_call["type"]
API_data_sequence.append(data)
return API_data_sequence
- 我们将日志读取到一个语料库中,并收集它们的标签:
data_corpus = []
labels = []
for directory, label in directories_with_labels:
logs = os.listdir(directory)
for log_path in logs:
file_path = directory + "/" + log_path
try:
data_corpus.append(get_API_class_method_type_from_log(file_path))
labels.append(label)
except:
pass
- 现在,让我们看看我们语料库中的数据是什么样的:
print(data_corpus[0])
['android.os.SystemProperties:get:content', 'android.os.SystemProperties:get:content', 'android.os.SystemProperties:get:content', 'android.os.SystemProperties:get:content', 'android.os.SystemProperties:get:content', 'android.os.SystemProperties:get:content', 'android.os.SystemProperties:get:content', 'android.os.SystemProperties:get:content', 'android.os.SystemProperties:get:content', 'android.os.SystemProperties:get:content', 'android.os.SystemProperties:get:content', 'android.os.SystemProperties:get:content', 'android.app.ContextImpl:registerReceiver:binder', 'android.app.ContextImpl:registerReceiver:binder', 'android.os.SystemProperties:get:content', 'android.os.SystemProperties:get:content']
- 我们继续进行训练集和测试集的划分:
from sklearn.model_selection import train_test_split
corpus_train, corpus_test, y_train, y_test = train_test_split(
data_corpus, labels, test_size=0.2, random_state=11
)
- 我们的方法是使用 N-gram,所以我们加载我们的 N-gram 提取函数,并根据当前数据格式进行轻微修改:
import collections
from nltk import ngrams
import numpy as np
def read_file(file_path):
"""Reads in the binary sequence of a binary file."""
with open(file_path, "rb") as binary_file:
data = binary_file.read()
return data
def text_to_Ngrams(text, n):
"""Produces a list of N-grams from a text."""
Ngrams = ngrams(text, n)
return list(Ngrams)
def get_Ngram_counts(text, N):
"""Get a frequency count of N-grams in a text."""
Ngrams = text_to_Ngrams(text, N)
return collections.Counter(Ngrams)
- 我们指定 N=4 并收集所有 N-gram:
N = 4
total_Ngram_count = collections.Counter([])
for file in corpus_train:
total_Ngram_count += get_Ngram_counts(file, N)
- 接下来,我们将范围缩小到
K1 = 3000
个最常见的 N-gram:
K1 = 3000
K1_most_frequent_Ngrams = total_Ngram_count.most_common(K1)
K1_most_frequent_Ngrams_list = [x[0] for x in K1_most_frequent_Ngrams]
[('java.lang.reflect.Method:invoke:reflection', 'java.lang.reflect.Method:invoke:reflection', 'java.lang.reflect.Method:invoke:reflection', 'java.lang.reflect.Method:invoke:reflection'),
('java.io.FileInputStream:read:runtime', 'java.io.FileInputStream:read:runtime', 'java.io.FileInputStream:read:runtime', 'java.io.FileInputStream:read:runtime'),
<snip>
('android.os.SystemProperties:get:content', 'android.os.SystemProperties:get:content', 'android.os.SystemProperties:get:content', 'javax.crypto.spec.SecretKeySpec:javax.crypto.spec.SecretKeySpec:crypto')
- 然后我们编写一个方法,将样本转换为 N-gram 计数的向量:
def featurize_sample(file, Ngrams_list):
"""Takes a sample and produces a feature vector.
The features are the counts of the K1 N-grams we've selected.
"""
K1 = len(Ngrams_list)
feature_vector = K1 * [0]
fileNgrams = get_Ngram_counts(file, N)
for i in range(K1):
feature_vector[i] = fileNgrams[Ngrams_list[i]]
return feature_vector
- 我们应用这个函数来提取我们的训练和测试样本特征:
X_train = []
for sample in corpus_train:
X_train.append(featurize_sample(sample, K1_most_frequent_Ngrams_list))
X_train = np.asarray(X_train)
X_test = []
for sample in corpus_test:
X_test.append(featurize_sample(sample, K1_most_frequent_Ngrams_list))
X_test = np.asarray(X_test)
- 我们使用互信息来进一步缩小 K1=3000 个最常见的 N-gram,筛选出 K2=500 个最有信息量的 N-gram。然后,我们设置一个管道,接着运行 XGBoost 分类器:
from sklearn.feature_selection import SelectKBest, mutual_info_classif
from sklearn.pipeline import Pipeline
from xgboost import XGBClassifier
K2 = 500
mi_pipeline = Pipeline(
[
("mutual_information", SelectKBest(mutual_info_classif, k=K2)),
("xgb", XGBClassifier()),
]
)
- 我们训练我们的管道并评估其在训练集和测试集上的准确性:
mi_pipeline.fit(X_train, y_train)
print("Training accuracy:")
print(mi_pipeline.score(X_train, y_train))
print("Testing accuracy:")
print(mi_pipeline.score(X_test, y_test))
以下输出给出了我们的训练和测试准确率:
Training accuracy:
0.8149428743235118
Testing accuracy:
0.8033674082982561
它是如何工作的……
在本食谱中,我们执行了一项令人兴奋的任务,即基于样本的运行时行为对恶意软件和良性样本进行分类。我们的前三个步骤是定义一个函数来读取和解析包含样本运行时行为信息的 JSON 日志。顺便说一句,JSON 是一个非常有用的文件格式,当你的数据可能包含可变数量的属性时非常适用。我们战略性地选择提取 API 调用的类别、方法和内容。还可以使用其他特征,如 API 调用发生的时间和调用的参数。权衡是数据集将更大,这些特征可能会导致性能下降或过拟合。建议调查选择其他特征来为分类器提供更多信息。
定义了我们的函数后,我们继续执行解析并将所有解析的数据收集到一个地方(步骤 4)。在步骤 5中,我们查看我们的语料库。我们看到了构成我们数据的 API 调用的四元组样本。接下来是进行训练-测试分割的标准步骤。在步骤 7和步骤 8中,我们加载我们的 N-gram 提取函数,并使用这些函数从我们的数据集中提取 N-gram。这些提取方法类似于用于二进制文件的方法,但针对手头的文本格式进行了调整。最初,我们收集了 K1=3000 个最常见的 N-gram,以减少计算负载。通过增加 K1 和稍后的 K2 数量,我们可以期望分类器的准确性得到提高,但内存和计算需求也会增加(步骤 9)。在步骤 10中,我们定义了一个函数,将样本特征化为它们的 N-gram 特征向量,然后,在步骤 11中,我们应用这个函数来特征化我们的训练和测试样本。我们希望进一步缩小特征集。我们选择使用互信息从K1=3000
个最常见的 N-gram 中选择 K2=500 个最具信息量的 N-gram(步骤 12)——正如在选择最佳 N-gram 的食谱中所讨论的那样,有很多选项。
例如,另一种选择是使用卡方检验。此外,除了 XGBoost 之外,还可以选择其他分类器。最后,我们看到获得的准确度表明,使用 N-gram 对 API 调用序列进行处理的方法是有前景的。
MalConv – 用于恶意 PE 检测的端到端深度学习
静态恶意软件检测领域的一项新发展是使用深度学习进行端到端机器学习的恶意软件检测。在这种情况下,我们完全跳过所有特征工程;我们无需了解 PE 头或其他可能表明 PE 恶意软件的特征。我们只需将原始字节流输入到我们的神经网络中并进行训练。这个想法最早在arxiv.org/pdf/1710.09435.pdf
中提出。这个架构被称为MalConv,如下图所示:
准备就绪
本食谱的准备工作涉及在pip
中安装多个包,具体包括keras
、tensorflow
和tqdm
。安装命令如下:
pip install keras tensorflow tqdm
此外,良性和恶意文件已提供给你,在仓库根目录下的PE Samples Dataset
文件夹中。将所有名为Benign PE Samples*.7z
的压缩包提取到名为Benign PE Samples
的文件夹中,并将所有名为Malicious PE Samples*.7z
的压缩包提取到名为Malicious PE Samples
的文件夹中。
如何操作...
在本食谱中,我们详细讲解了如何在原始 PE 文件上训练 MalConv:
- 我们导入
numpy
进行向量运算,并导入tqdm
来跟踪循环中的进度:
import numpy as np
from tqdm import tqdm
- 定义一个函数将字节嵌入为向量:
def embed_bytes(byte):
binary_string = "{0:08b}".format(byte)
vec = np.zeros(8)
for i in range(8):
if binary_string[i] == "1":
vec[i] = float(1) / 16
else:
vec[i] = -float(1) / 16
return vec
- 读取原始 PE 样本的位置,并创建它们的标签列表:
import os
from os import listdir
directories_with_labels = [("Benign PE Samples", 0), ("Malicious PE Samples", 1)]
list_of_samples = []
labels = []
for dataset_path, label in directories_with_labels:
samples = [f for f in listdir(dataset_path)]
for file in samples:
file_path = os.path.join(dataset_path, file)
list_of_samples.append(file_path)
labels.append(label)
- 定义一个便捷函数来读取文件的字节序列:
def read_file(file_path):
"""Read the binary sequence of a file."""
with open(file_path, "rb") as binary_file:
return binary_file.read()
- 设置每个样本读取的最大字节长度
maxSize
,将所有样本的字节嵌入,并将结果收集到 X 中:
max_size = 15000
num_samples = len(list_of_samples)
X = np.zeros((num_samples, 8, max_size))
Y = np.asarray(labels)
file_num = 0
for file in tqdm(list_of_samples):
sample_byte_sequence = read_file(file)
for i in range(min(max_size, len(sample_byte_sequence))):
X[file_num, :, i] = embed_bytes(sample_byte_sequence[i])
file_num += 1
- 准备优化器:
from keras import optimizers
my_opt = optimizers.SGD(lr=0.01, decay=1e-5, nesterov=True)
- 利用 Keras 函数式 API 设置深度神经网络架构:
from keras import Input
from keras.layers import Conv1D, Activation, multiply, GlobalMaxPool1D, Dense
from keras import Model
inputs = Input(shape=(8, maxSize))
conv1 = Conv1D(kernel_size=(128), filters=32, strides=(128), padding='same')(inputs)
conv2 = Conv1D(kernel_size=(128), filters=32, strides=(128), padding='same')(inputs)
a = Activation('sigmoid', name='sigmoid')(conv2)
mul = multiply([conv1, a])
b = Activation('relu', name='relu')(mul)
p = GlobalMaxPool1D()(b)
d = Dense(16)(p)
predictions = Dense(1, activation='sigmoid')(d)
model = Model(inputs=inputs, outputs=predictions)
- 编译模型并选择批次大小:
model.compile(optimizer=my_opt, loss="binary_crossentropy", metrics=["acc"])
batch_size = 16
num_batches = int(num_samples / batch_size)
- 在批次上训练模型:
for batch_num in tqdm(range(num_batches)):
batch = X[batch_num * batch_size : (batch_num + 1) * batch_size]
model.train_on_batch(
batch, Y[batch_num * batch_size : (batch_num + 1) * batch_size]
)
它是如何工作的…
我们首先导入numpy
和tqdm
(步骤 1),tqdm
是一个可以通过显示百分比进度条来跟踪循环进度的包。作为将文件的原始字节输入到我们的深度神经网络的一部分,我们使用一种简单的字节嵌入方式,将字节嵌入到一个 8 维空间中,其中每个字节的位对应于向量的一个坐标(步骤 2)。一个值为 1 的位表示对应的坐标被设置为 1/16,而位值为 0 对应的坐标则为-1/16。例如,10010001 被嵌入为向量(1/16, -1/16, -1/16, 1/16, -1/16, -1/16, -1/16, 1/16)。也可以采用其他嵌入方式,例如那些与神经网络一起训练的方式。
MalConv 架构做出了一个简单但计算上快速的选择。在 步骤 3 中,我们列出我们的样本及其标签,在 步骤 4 中,我们定义了一个函数来读取文件的字节。注意 rb
设置替代了 r
,以便将文件读取为字节序列。在 步骤 5 中,我们使用 tqdm
来跟踪循环的进度。对于每个文件,我们读取字节序列并将每个字节嵌入到一个 8 维空间中。然后,我们将这些字节聚集到 X 中。如果字节数超过 maxSize=15000
,则停止。如果字节数小于 maxSize
,则假定字节为 0。maxSize
参数控制每个文件读取多少字节,可以根据内存容量、可用计算量和样本大小进行调整。在接下来的步骤(步骤 6 和 步骤 7)中,我们定义了一个标准优化器,即随机梯度下降,并选择了相应的参数,同时定义了与 MalConv 非常接近的神经网络架构。注意,我们在这里使用了 Keras 函数式 API,它使我们能够在模型中创建复杂的输入输出关系。
最后,需要注意的是,更好的架构和参数选择是一个开放的研究领域。接下来,我们可以自由选择批次大小并开始训练(步骤 8 和 步骤 9)。批次大小是一个重要的参数,它会影响学习过程的速度和稳定性。为了我们的目的,我们做出了一个简单的选择。我们一次输入一个批次,并训练我们的神经网络。
应对打包恶意软件
打包是可执行文件的压缩或加密,与普通压缩不同的是,它通常在运行时内存中解压,而不是在执行前解压到磁盘上。打包工具给分析人员带来了混淆挑战。
例如,VMProtect 这种打包工具通过在具有独特架构的虚拟环境中执行,来保护其内容免受分析人员的窥视,这使得分析该软件成为一个巨大的挑战。
Amber 是一个用于绕过安全产品和防护措施的反射型 PE 打包工具。它可以将常规编译的 PE 文件打包成反射型有效载荷,这些有效载荷可以像 shellcode 一样自我加载和执行。它实现了隐蔽的内存中有效载荷部署,可用于绕过杀毒软件、防火墙、IDS、IPS 产品以及应用程序白名单的防护措施。最常用的打包工具是 UPX。
由于打包会混淆代码,它往往会导致机器学习分类器性能的下降。通过确定用于打包可执行文件的打包工具,我们可以使用相同的打包工具来解包代码,即恢复到原始的、未混淆的版本。然后,杀毒软件和机器学习检测是否为恶意文件就变得更加简单。
使用打包工具
在本食谱中,我们将展示如何获取打包器(即 UPX)并使用它。拥有一组打包器的目的是,首先,进行数据增强,如本食谱后续内容所述;其次,在确定打包器后,可以解包已打包的样本。
准备工作
以下方法不需要额外的包。你可以在本书的Packers
文件夹中找到upx.exe
。
如何操作...
在本食谱中,你将使用 UPX 打包器来打包一个文件:
- 从
github.com/upx/upx/releases/
下载并解压最新版本的 UPX
- 通过运行
upx.exe
和foofile.exe
,对你希望打包的文件执行upx.exe
。成功打包的结果如下所示:
该文件仍然是可执行文件,这与压缩文件不同,压缩文件会变成 ZIP 格式。
它是如何工作的…
如你所见,使用打包器非常简单。大多数打包器的一个优点是它们除了混淆文件内容外,还能减少文件的大小。许多黑客会使用自制的打包器。这些打包器的优势在于它们难以解包。从检测恶意文件的角度来看,使用自制打包器打包的文件是高度可疑的。
组装一个打包的样本数据集
组装一个打包器分类器数据集的一个明显方法是收集已经打包并且标注了打包信息的样本。另一种有效的方法是收集一个大型文件数据集,然后自己进行打包。
准备工作
以下方法不需要额外的包。你可以在本书的Packers
文件夹中找到upx.exe
。
如何操作...
在本食谱中,你将使用 UPX 来打包一个文件夹中的文件。
-
将
upx.exe
放入目录A
,并将一组样本放入目录B
,该目录位于A
中。对于这个例子,B
是良性 PE 样本 UPX。 -
列出目录
B
中的文件:
import os
files_path = "Benign PE Samples UPX/"
files = os.listdir(files_path)
file_paths = [files_path+x for x in files]
- 对
B
中的每个文件运行upx
:
from subprocess import Popen, PIPE
cmd = "upx.exe"
for path in file_paths:
cmd2 = cmd+" \""+path+"\""
res = Popen(cmd2, stdout=PIPE).communicate()
print(res)
- 每当在打包时发生错误,删除原始样本:
if "error" in str(res[0]):
print(path)
os.remove(path)
它是如何工作的…
前两个步骤是为运行 UPX 打包器做准备。在第 3 步中,我们使用子进程在 Python 中调用外部命令,即 UPX。当我们打包样本时(第 4 步),每当发生错误时,我们会删除该样本,因为它无法成功打包。这确保了我们的目录中只有打包的样本,从而我们可以将干净且有序的数据输入到分类器中。
构建打包器的分类器
在组装了标注数据后,这些数据由按打包器标注的目录中的打包样本组成,我们准备训练一个分类器来判断样本是否被打包,如果是的话,使用了哪种打包器。
准备工作
本食谱的准备工作包括在 pip
中安装 scikit-learn
和 nltk
。命令如下:
pip install sklearn nltk
此外,仓库中已为您提供了打包和非打包文件。在这个食谱中,使用了三种类型的样本:未打包、UPX 打包和 Amber 打包。从仓库根目录中的 PE Samples Dataset
中提取所有名为 Benign PE Samples*.7z
的归档文件到名为 Benign PE Samples
的文件夹,提取 Benign PE Samples UPX.7z
到名为 Benign PE Samples UPX
的文件夹,并提取 Benign PE Samples Amber.7z
到名为 Benign PE Samples Amber
的文件夹。
如何操作……
在这个食谱中,您将构建一个分类器来确定用于打包文件的打包器:
- 读取要分析的文件名称及其对应的标签,标签对应所使用的打包器:
import os
from os import listdir
directories_with_labels = [
("Benign PE Samples", 0),
("Benign PE Samples UPX", 1),
("Benign PE Samples Amber", 2),
]
list_of_samples = []
labels = []
for dataset_path, label in directories_with_labels:
samples = [f for f in listdir(dataset_path)]
for file in samples:
file_path = os.path.join(dataset_path, file)
list_of_samples.append(file_path)
labels.append(label)
- 创建训练测试集划分:
from sklearn.model_selection import train_test_split
samples_train, samples_test, labels_train, labels_test = train_test_split(
list_of_samples, labels, test_size=0.3, stratify=labels, random_state=11
)
- 定义提取 N-gram 所需的导入:
import collections
from nltk import ngrams
import numpy as np
- 定义用于提取 N-gram 的函数:
def read_file(file_path):
"""Reads in the binary sequence of a binary file."""
with open(file_path, "rb") as binary_file:
data = binary_file.read()
return data
def byte_sequence_to_Ngrams(byte_sequence, N):
"""Creates a list of N-grams from a byte sequence."""
Ngrams = ngrams(byte_sequence, N)
return list(Ngrams)
def extract_Ngram_counts(file, N):
"""Takes a binary file and outputs the N-grams counts of its binary sequence."""
filebyte_sequence = read_file(file)
file_Ngrams = byte_sequence_to_Ngrams(filebyte_sequence, N)
return collections.Counter(file_Ngrams)
def featurize_sample(sample, K1_most_frequent_Ngrams_list):
"""Takes a sample and produces a feature vector.
The features are the counts of the K1 N-grams we've selected.
"""
K1 = len(K1_most_frequent_Ngrams_list)
feature_vector = K1 * [0]
file_Ngrams = extract_Ngram_counts(sample, N)
for i in range(K1):
feature_vector[i] = file_Ngrams[K1_most_frequent_Ngrams_list[i]]
return feature_vector
- 遍历数据,选择您希望用作特征的 N-gram:
N = 2
total_Ngram_count = collections.Counter([])
for file in samples_train:
total_Ngram_count += extract_Ngram_counts(file, N)
K1 = 100
K1_most_common_Ngrams = total_Ngram_count.most_common(K1)
K1_most_common_Ngrams_list = [x[0] for x in K1_most_common_Ngrams]
- 对训练集进行特征化:
Ngram_features_list_train = []
y_train = []
for i in range(len(samples_train)):
file = samples_train[i]
NGram_features = featurize_sample(file, K1_most_common_Ngrams_list)
Ngram_features_list_train.append(NGram_features)
y_train.append(labels_train[i])
X_train = Ngram_features_list_train
- 在训练数据上训练随机森林模型:
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier(n_estimators=100)
clf = clf.fit(X_train, y_train)
- 对测试集进行特征化:
Ngram_features_list_test = []
y_test = []
for i in range(len(samples_test)):
file = samples_test[i]
NGram_features = featurize_sample(file, K1_most_common_Ngrams_list)
Ngram_features_list_test.append(NGram_features)
y_test.append(labels_test[i])
X_test = Ngram_features_list_test
- 利用训练好的分类器在测试集上进行预测,并使用混淆矩阵评估性能:
y_pred = clf.predict(X_test)
from sklearn.metrics import confusion_matrix
confusion_matrix(y_test, y_pred)
输出结果如下:
它是如何工作的……
我们从简单的步骤开始,将数据和标签组织成数组(第 1 步)。特别地,我们读取样本并为其分配相应的标签,标签对应它们被打包的打包器。在 第 2 步 中,我们进行训练测试集划分。现在我们准备好对数据进行特征化,因此我们导入提取 N-gram 所需的库,并定义我们的 N-gram 函数(第 3 步 和 第 4 步),这些内容将在其他食谱中讨论,并且,我们简化地选择 N=2 和 K1=100 个最常见的 N-gram 作为特征,进行数据的特征化(第 5 步 和 第 6 步)。不同的 N 值和选择最有信息量的 N-gram 的其他方法可以获得更好的结果,但同时也会增加对计算资源的需求。特征化数据后,我们进行训练测试集划分(第 7 步),然后在数据上训练一个随机森林分类器(一个简单的首选)(第 8 步)。根据 第 9 步 中的混淆矩阵,我们可以看到,机器学习分类器在此类问题上表现得非常准确。
MalGAN – 创建规避型恶意软件
使用生成对抗网络(GANs),我们可以创建对抗性恶意软件样本,用于训练和改进我们的检测方法,并在对手之前识别出漏洞。这里的代码基于j40903272/MalConv-keras。对抗性恶意软件样本是通过向恶意软件样本中添加一小段经过精心计算的字节序列进行修改的,选择这些字节序列是为了欺骗用于分类样本的神经网络(在此情况下是 MalConv)。
做好准备
本食谱的准备工作包括在pip
中安装pandas
、keras
、tensorflow
和scikit-learn
包。安装命令如下:
pip install pandas keras tensorflow sklearn
MalGan
的相关代码和资源文件已经包含在本书的仓库中,在MalGan
目录下。此外,收集一批 PE 样本,然后将它们的路径放入文件的第一列:
"MalGAN_input/samplesIn.csv"
在第二列中,输入这些样本的判定(1 为良性,0 为恶性)。
如何执行...
在这个食谱中,你将学习如何创建对抗性恶意软件:
- 开始时导入 MalGAN 代码以及一些实用库。
import os
import pandas as pd
from keras.models import load_model
import MalGAN_utils
import MalGAN_gen_adv_examples
- 指定输入和输出路径:
save_path = "MalGAN_output"
model_path = "MalGAN_input/malconv.h5"
log_path = "MalGAN_output/adversarial_log.csv"
pad_percent = 0.1
threshold = 0.6
step_size = 0.01
limit = 0.
input_samples = "MalGAN_input/samplesIn.csv"
- 设置是否希望使用 GPU 进行对抗样本生成:
MalGAN_utils.limit_gpu_memory(limit)
- 将包含样本名称和标签的 csv 文件读入数据框:
df = pd.read_csv(input_samples, header=None)
fn_list = df[0].values
- 加载预计算的 MalConv 模型:
model = load_model(model_path)
- 使用快速梯度步进法(FGSM)生成对抗性恶意软件:
adv_samples, log = MalGAN_gen_adv_examples.gen_adv_samples(model, fn_list, pad_percent, step_size, threshold)
- 保存结果日志并将样本写入磁盘:
log.save(log_path)
for fn, adv in zip(fn_list, adv_samples):
_fn = fn.split('/')[-1]
dst = os.path.join(save_path, _fn)
print(dst)
with open(dst, 'wb') as f:
f.write(adv)
它是如何工作的…
我们首先导入将要使用的所有 MalGAN 代码(步骤 1)。我们必须指定一些参数(步骤 2),现在进行说明。savePath
参数是保存对抗性示例的位置。modelPath
变量是 MalConv 预计算权重的路径。logPath
参数是记录应用快速梯度符号法(FGSM)到样本的相关数据的位置。例如,日志文件可能如下所示:
文件名 | 原始分数 | 文件长度 | 填充长度 | 损失 | 预测分数 |
---|---|---|---|---|---|
0778...b916 | 0.001140 | 235 | 23 | 1 | 0.912 |
请注意,原始分数接近0
,表明 MalConv 认为原始样本是恶意的。在选择要使用哪些字节进行填充后,最终的预测分数接近1
,表明修改后的样本现在被认为是良性的。padPercent
参数确定向样本末尾附加多少字节。threshold
参数确定神经网络应对对抗样本的良性做出多大程度的确定才将其写入磁盘。stepSize
是 FGSM 中的一个参数。到目前为止,参数就讲解完了。我们还有一个选择要做,就是是否使用 CPU 或 GPU(步骤 3)。为了简单起见,本食谱选择使用 CPU。显然,使用 GPU 可以加速计算。这里的limit
参数表示在计算中使用多少 GPU,默认设置为0
。在下一步,步骤 4,我们读取由inputSamples
参数指定的.csv
文件。此输入日志的格式如下:
2b5137a1658c...8 | 1 |
---|---|
0778a070b28...6 | 0 |
在这里,第一列给出了样本的路径,第二列提供了标签(1
表示良性,0
表示恶意)。我们现在加载预计算的 MalGAN 模型(第 5 步),生成对抗恶意软件样本(第 6 步),然后将其保存到磁盘(第 7 步)。
跟踪恶意软件漂移
恶意软件的分布时刻在变化。不仅新样本不断发布,还有新类型的病毒出现。例如,隐私挖矿者是近年来随着加密货币出现的新型恶意软件,直到加密货币出现之前它是未知的。有趣的是,从机器学习的角度来看,恶意软件的种类和分布不仅在进化,它们的定义也在变化,这种现象被称为概念漂移。更具体地说,十五年前的病毒很可能已经无法在当前使用的系统中执行,因此它不会对用户造成危害,因而不再算作恶意软件。
通过跟踪恶意软件的漂移,甚至预测它,组织可以更好地将资源投入到正确的防御类型中,从而为未来的威胁建立免疫。
准备中
准备这道菜需要在pip
中安装matplotlib
、statsmodels
和scipy
包。命令如下:
pip install matplotlib statsmodels scipy
如何操作……
在这道菜中,你将使用时间序列回归来预测基于历史数据的恶意软件分布:
- 收集你感兴趣领域内恶意软件分布的历史数据:
month0 = {"Trojan": 24, "CryptoMiner": 11, "Other": 36, "Worm": 29}
month1 = {"Trojan": 28, "CryptoMiner": 25, "Other": 22, "Worm": 25}
month2 = {"Trojan": 18, "CryptoMiner": 36, "Other": 41, "Worm": 5}
month3 = {"CryptoMiner": 18, "Trojan": 33, "Other": 44, "Worm": 5}
months = [month0, month1, month2, month3]
- 将数据转换为每种恶意软件类别的独立时间序列:
trojan_time_series = []
crypto_miner_time_series = []
worm_time_series = []
other_time_series = []
for month in months:
trojan_time_series.append(month["Trojan"])
crypto_miner_time_series.append(month["CryptoMiner"])
worm_time_series.append(month["Worm"])
other_time_series.append(month["Other"])
以下图表展示了木马的时间序列:
以下图表展示了加密矿工的时间序列:
以下图表展示了蠕虫的时间序列:
以下图表展示了其他类型恶意软件的时间序列:
- 从
statsmodels
导入移动平均:
from statsmodels.tsa.arima_model import ARMA
- 使用移动平均法基于时间序列预测下个月的分布。
ts_model = ARMA(trojan_time_series, order=(0, 1))
model_fit_to_data = ts_model.fit(disp=True)
y_Trojan = model_fit_to_data.predict(len(trojan_time_series), len(trojan_time_series))
print("Trojan prediction for following month: " + str(y_Trojan[0]) + "%")
木马的结果如下:
Trojan prediction for following month: 21.699999876315772%
我们对加密矿工使用相同的方法:
ts_model = ARMA(crypto_miner_time_series, order=(0, 1))
model_fit_to_data = ts_model.fit(disp=True)
y_CryptoMiner = model_fit_to_data.predict(
len(crypto_miner_time_series), len(crypto_miner_time_series)
)
print("CryptoMiner prediction for following month: " + str(y_CryptoMiner[0]) + "%")
我们得到以下预测结果:
CryptoMiner prediction for following month: 24.09999979660618%
对于蠕虫,使用以下代码:
ts_model = ARMA(worm_time_series, order=(0, 1))
model_fit_to_data = ts_model.fit(disp=True)
y_Worm = model_fit_to_data.predict(len(worm_time_series), len(worm_time_series))
print("Worm prediction for following month: " + str(y_Worm[0]) + "%")
我们得到以下预测结果:
Worm prediction for following month: 14.666665384131406%
对于其他类型的恶意软件,我们使用以下代码:
ts_model = ARMA(other_time_series, order=(0, 1))
model_fit_to_data = ts_model.fit(disp=True)
y_Other = model_fit_to_data.predict(len(other_time_series), len(other_time_series))
print("Other prediction for following month: " + str(y_Other[0]) + "%")
我们得到以下预测结果:
Other prediction for following month: 27.400000645620793%
它是如何工作的……
为了教学目的,我们生成了一个玩具数据集,表示每种类型的恶意软件在时间上的百分比(步骤 1)。通过更多的历史数据,这样的数据集可以指示你在安全领域应该将资源投入到何处。我们将数据收集在一个地方并生成可视化图表(步骤 2)。我们希望进行简单的预测,因此我们导入 ARMA 模型,ARMA 代表自回归滑动平均模型,它是滑动平均模型的一个推广。为了简化起见,我们将 ARMA 专门化为移动平均(MA)。在步骤 4中,我们使用 MA 来预测恶意软件百分比在下一个时间段内的变化情况。随着数据集的增大,尝试不同的模型并创建考虑时间因素的训练-测试集是明智的做法。这将帮助你找到最具解释性的模型,换句话说,找到最能准确预测时间的模型。
第四章:用于社会工程学的机器学习
有许多酷炫的机器学习(ML)应用,它们在社会工程学中尤其突出。机器学习使得自动化钓鱼攻击取得了巨大的成功,正如我们将通过 Twitter 钓鱼机器人教程所学到的那样。它还被用于生成假冒但逼真的视频,同时也能发现这些视频是假的。机器学习提供了语音传输、谎言检测等多种实用工具,这些工具将出现在本章的教程中,旨在提升你的社会工程学技能。
本章涵盖以下内容:
-
Twitter 钓鱼机器人
-
声音伪装
-
用于 开源情报(OSINT)的语音识别
-
面部识别
-
深度伪造
-
深度伪造识别
-
使用机器学习进行谎言检测
-
个性分析
-
社会化映射工具
-
训练虚假评论生成器
-
生成虚假评论
-
假新闻
技术要求
在本章中,我们将使用以下内容:
-
Markovify
-
Twitter 开发者账号
-
Tweepy
-
PyTorch
-
OpenCV
-
Keras
-
TensorFlow
-
IBM 的 Watson
代码和数据集可以在 github.com/PacktPublishing/Machine-Learning-for-Cybersecurity-Cookbook/tree/master/Chapter04
找到。
Twitter 钓鱼机器人
在本教程中,我们将使用机器学习来构建一个 Twitter 钓鱼机器人。该机器人将利用人工智能模拟目标的推文,从而为自己的推文创造有趣且吸引人的内容。此外,这些推文将包含嵌入的链接,导致目标点击这些钓鱼链接。当然,我们不会将此机器人用于恶意目的,我们的链接将是虚拟链接。链接本身将被模糊化,直到目标点击之后才会发现实际隐藏的内容。
实验结果表明,这种攻击形式具有较高的成功率,通过模拟这种攻击,你可以测试并提高客户或组织的安全防护能力。
准备工作
本教程的准备工作包括在 pip
中安装 tweepy
和 markovify
包。具体步骤如下:
pip install tweepy markovify
此外,你还需要在 Twitter 上设置一个开发者账号。过程相对简单,且账号创建是免费的。
如何操作...
在接下来的步骤中,我们将展示如何利用机器学习创建一个 Twitter 钓鱼机器人:
-
设置 Twitter 开发者账号。
-
创建一个新应用并获取你的消费者 API 密钥、访问令牌和访问令牌密钥。
-
导入
tweepy
库并填写你的凭证,以便访问 Twitter API:
import json
import tweepy
CONSUMER_API_KEY = "fill me in"
CONSUMER_API_SECRET_KEY = "fill me in"
ACCESS_TOKEN = "fill me in"
ACCESS_TOKEN_SECRET = "fill me in"
auth = tweepy.OAuthHandler(CONSUMER_API_KEY, CONSUMER_API_SECRET_KEY)
auth.set_access_token(ACCESS_TOKEN, ACCESS_TOKEN_SECRET)
api = tweepy.API(
auth, wait_on_rate_limit=True, wait_on_rate_limit_notify=True, compression=True
)
- 我们选择一个目标用户或要模仿的人物。在本例中,我选择了一位活跃在 Twitter 上的科技领域知名人物:
user_id = "elonmusk"
- 收集用户最近的
count = 200
条推文:
count = 200
user_tweets = api.user_timeline(screen_name=user_id, count=count, tweet_mode="extended")
- 将用户的所有推文收集成一大段文本:
tweet_corpus = []
for tweet in user_tweets:
tweet_corpus.append(tweet.full_text)
tweets_text = ". ".join(tweet_corpus)
- 现在我们开始处理文本。我们定义一个函数,将找到的任何 URL 实例替换为新的 URL:
import re
def replace_URLs(string, new_URL):
"""Replaces all URLs in a string with a custom URL."""
modified_string = re.sub(
"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\), ]|(?:%[0-9a-fA-F][0-9a-fA-F]))+",
" " + new_URL + " ",
string,
)
return modified_string
- 创建一个网络钓鱼链接并将其插入到推文中。在我们的情况下,我们使用了一个 URL 缩短服务来隐藏链接的目的地,其实只是 Google。
phishing_link = "https://urlzs.com/u8ZB"
processed_tweets_text = replace_URLs(tweets_text, phishing_link)
- 在处理文本后训练一个马尔科夫模型并生成推文:
import markovify
markov_model = markovify.Text(processed_tweets_text)
- 生成包含网络钓鱼链接的所需数量的推文:
num_phishing_tweets_desired = 5
num_phishing_tweets_so_far = 0
generated_tweets = []
while num_phishing_tweets_so_far < num_phishing_tweets_desired:
tweet = markov_model.make_short_sentence(140)
if phishing_link in tweet and tweet not in generated_tweets:
generated_tweets.append(tweet)
num_phishing_tweets_so_far += 1
我们将看到以下输出:
- 发布你的推特并定位到用户、用户的关注者或用户的朋友。例如,这段代码获取用户的朋友:
user = api.get_user(user_id)
for friend in user.friends():
print(friend.screen_name)
我们将看到的输出如下:
wonderofscience
SpaceComCC
AFSpace
Liv_Boeree
shivon
Teslarati
neiltyson
SciGuySpace
wlopwangling
Berger_SN
pewdiepie
CathieDWood
lexfridman
ccsakuras
4thFromOurStar
TheOnion
BBCScienceNews
sciencemagazine
NatureNews
TheStoicEmperor
工作原理...
在步骤 1 和 2 中,您需要访问 Twitter 开发者网页以创建您的 API 账户,这是免费的。要通过 Python 访问 Twitter API,我们使用tweepy
库(步骤 3)。我们的目标是从目标 Twitter 用户的推文中学习,以便我们的推文具有相同的风格和主题。这样的推文然后成为任何对相同主题和风格感兴趣的人可能点击的诱饵。我们选择模仿埃隆·马斯克的风格来发布我们的推文(步骤 4)。接着我们收集埃隆发布的最后 200 条推文(步骤 5 和 6)。一般来说,您能够获取的用户推文越多,模型就越有说服力。然而,考虑时间和相关性也很重要,即用户更有可能点击及时和相关的推文,而不是与老旧话题有关的推文。
我们定义一个处理文本的函数,使所有的 URL 都替换为所需的 URL(步骤 7),然后将其应用于我们的文本(步骤 8)。在处理推文的这个阶段,有很大的创意空间。例如,我们可以自定义@
屏幕名称,使其与我们的目标更相关。在步骤 9 和 10 中,我们对处理后的推文训练了一个马尔科夫模型,然后生成了几条包含网络钓鱼链接的推文。最后,在步骤 11 中,请记住,使机器人更有效的其他修改包括选择发送推文的最佳时间,例如一天、一周、一个月或其他(例如与事件相关的时机),或在推文中添加带有链接的照片。
语音模仿
通过神经网络的语音风格迁移新技术,现在越来越容易逼真地模仿目标的声音。在本节中,我们将向你展示如何使用深度学习制作一段目标说出你希望他们说的内容的录音,例如,利用目标的声音进行社会工程学攻击,或者一个更有趣的例子,用奥巴马的声音来唱碧昂丝的歌曲。我们选择了mazzzystar/randomCNN-voice-transfer
中的架构,它可以快速获得高质量的结果。特别地,模型不需要在大量录音数据集上进行预训练。
在本书的附带代码中,你将找到两个版本的语音转移神经网络代码,一个是 GPU 版本,一个是 CPU 版本。我们在这里描述的是 CPU 版本,虽然 GPU 版本非常相似。
准备工作
本方案的准备工作包括在pip
中安装pytorch
和librosa
。安装步骤如下:
pip install torch librosa
同时,将两个文件放入voice_impersonation_input
文件夹中。一个文件是你希望目标表达的信息的音频录音,另一个文件是你希望目标使用的语音。
如何操作...
在接下来的步骤中,我们提供了一个将一个说话者的声音转移到另一个说话者录音的方案。代码结构分为三部分:CPU 上的语音模仿(主程序)、模型和工具。我们将讨论如何运行主程序,并解释它的功能。每当涉及到代码的其他部分时,我们将提供一个高层次的解释,但为了简洁起见,省略细节。
以下代码可以在Voice Impersonation.ipynb
中找到:
- 导入 PyTorch 工具、神经网络模型和用于一些基本计算的
math
模块:
import math
from torch.autograd import Variable
from voice_impersonation_utils import *
from voice_impersonation_model import *
- 指定我们希望在
style_file
中使用的声音和我们希望用该声音表达的音频content_file
:
input_files = "voice_impersonation_input/"
content_file = input_files + "male_voice.wav"
style_file = input_files + "Eleanor_Roosevelt.wav"
- 我们提取内容和风格文件的频谱,并将其转换为 PyTorch 张量:
audio_content, sampling_rate = wav2spectrum(content_file)
audio_style, sampling_rate = wav2spectrum(style_file)
audio_content_torch = torch.from_numpy(audio_content)[None, None, :, :]
audio_style_torch = torch.from_numpy(audio_style)[None, None, :, :]
- 我们实例化一个随机 CNN 模型并将其设置为
eval
模式:
voice_impersonation_model = RandomCNN()
voice_impersonation_model.eval()
- 我们为即将进行的神经网络训练准备张量,并选择 Adam 优化器和学习率:
audio_content_variable = Variable(audio_content_torch, requires_grad=False).float()
audio_style_variable = Variable(audio_style_torch, requires_grad=False).float()
audio_content = voice_impersonation_model(audio_content_variable)
audio_style = voice_impersonation_model(audio_style_variable)
learning_rate = 0.003
audio_G_var = Variable(
torch.randn(audio_content_torch.shape) * 1e-3, requires_grad=True
)
opt = torch.optim.Adam([audio_G_var])
- 我们指定
style
和content
参数,以及我们希望训练模型的时间长度:
style_param = 1
content_param = 5e2
num_epochs = 500
print_frequency = 50
- 我们训练我们的模型:
for epoch in range(1, num_epochs + 1):
opt.zero_grad()
audio_G = voice_impersonation_model(audio_G_var)
content_loss = content_param * compute_content_loss(audio_content, audio_G)
style_loss = style_param * compute_layer_style_loss(audio_style, audio_G)
loss = content_loss + style_loss
loss.backward()
opt.step()
- 我们打印训练过程中的进度,指定输出文件的名称,最后将神经网络的输出谱转换成音频文件:
if epoch % print_frequency == 0:
print("epoch: "+str(epoch))
print("content loss: "+str(content_loss.item()))
print("style loss: "+str(style_loss.item()))
print("loss: "+str(loss.item()))
gen_spectrum = audio_G_var.cpu().data.numpy().squeeze()
output_audio_name = "Eleanor_saying_there_was_a_change_now.wav"
spectrum2wav(gen_spectrum, sampling_rate, output_audio_name)
我们计算的最终结果可以在名为Eleanor_saying_there_was_a_change_now.wav
的音频文件中看到。
它是如何工作的…
我们首先导入 PyTorch 神经网络模型,并引入math
库进行一些基本计算(步骤 1)。更有趣的是,在步骤 2 中,我们指定了内容和风格音频。在内容文件中,你可以说出任何你希望表达的短语,例如,没有机器学习,你做不了网络安全。然后,在风格文件中,你选择一个人的声音录音,例如,名人如埃隆·马斯克的录音。最终的语音模仿结果是,埃隆·马斯克说了没有机器学习,你做不了网络安全。步骤 3、4 和 5 涉及一些准备工作,将数据准备好并输入我们的模型,然后实例化一个随机 CNN 模型及其优化器。该模型的主要特点是它使用 2D 卷积层而不是 1D 层来处理音频频谱图,并且在时间轴上计算grams
。将模型设置为评估模式(与训练模式对比)会影响某些层的行为,比如 dropout 和 batch norm,它们在训练和测试时的使用方式不同。在接下来的步骤(步骤 6)中,我们定义了style
和content
参数,这些参数为风格和内容赋予相对的权重。特别是,它们决定了最终音频在多大程度上继承了来自相应文件的风格和内容。现在我们准备好训练模型,在步骤 7 中通过进行前向传播和反向传播来训练它。我们在步骤 8 中监控训练进度,最后输出一个音频文件到磁盘,该文件使用风格文件中的风格发音内容文件中的内容。你可以在本书的代码库中找到这个文件。
面向 OSINT 的语音识别
故事是这样的,渗透测试员当时正在对 FBI 时任局长詹姆斯·科米进行情报收集。通过听科米的录音,渗透测试员注意到科米提到自己有几个社交媒体账户,包括一个推特账户。然而,当时没有人知道他的任何账户。
通过彻底的调查,渗透测试员最终发现了科米的秘密推特账户,屏幕名称为 Reinhold Niebuhr。这个方案的目标是帮助渗透测试员自动化并加速筛选大量关于目标的音频/视频镜头,以寻找关键词。具体来说,我们使用机器学习将语音转换为文本,收集这些文本,然后搜索感兴趣的关键词。
准备工作
本方案的准备工作包括在 pip
中安装 speechrecognition
包。安装说明如下:
pip install speechrecognition
此外,收集一些你想要识别的语音音频文件。
如何操作...
在接下来的步骤中,我们展示了如何使用语音识别库将语音录音转换为文本,然后在这些文本中搜索所需的关键词:
- 导入语音识别库,并选择一系列我们希望转换为文本的音频文件。同时,创建一个关键词列表,您希望在这些音频文件中自动检测到这些关键词:
import speech_recognition
list_of_audio_files = ["Eleanor_Roosevelt.wav", "Comey.wav"]
keywords = ["Twitter", "Linkedin", "Facebook", "Instagram", "password", "FBI"]
- 定义一个使用 Google 语音识别 API 将音频文件转换为文本的函数:
def transcribe_audio_file_to_text(audio_file):
"""Takes an audio file and produces a text transcription."""
recognizer = speech_recognition.Recognizer()
with speech_recognition.AudioFile(audio_file) as audio_source:
audio = recognizer.record(audio_source)
return recognizer.recognize_google(audio)
- 将音频文件转换为文本,并创建一个字典来记录文本来自哪个音频文件:
audio_corpus = {}
for audio_file in list_of_audio_files:
audio_corpus[transcribe_audio_file_to_text(audio_file)] = audio_file
print(audio_corpus)
语料库输出如下所示:
{"I'm very glad to be able to take part in this celebration dim sum Direct on human rights day": 'Eleanor_Roosevelt.wav', "have you met read recently that I'm on Twitter I am not a tweeter I am there to listen to read especially what's being said about the FBI and its mission": 'Comey.wav'}
- 在语料库中搜索关键词,并打印出包含这些关键词的音频文件:
for keyword in keywords:
for transcription in audio_corpus:
if keyword in transcription:
print(
"keyword "
+ keyword
+ " found in audio "
+ '"'
+ audio_corpus[transcription]
+ '"'
)
我们的运行已检测到关键词Twitter
:
keyword Twitter found in audio "Comey.wav"
keyword FBI found in audio "Comey.wav"
工作原理…
我们首先导入语音识别库,并选择一系列我们希望转换为文本的音频文件。同时,我们创建一个我们希望在这些音频文件中自动检测的关键词列表(步骤 1)。检测这些关键词的发音可以通过词干提取或词形还原技术使其更加稳健,这有效地涵盖了具有相同含义的关键词变体。例如,如果该方法得到适当实施,Twitter、Twitted 和 Tweet 都将被检测到。在步骤 2 中,我们指定将使用 Google 的语音识别 API 来转录音频。也可以使用其他语音识别服务,例如 pocketsphinx。现在我们已经准备好转录我们的音频文件,这将在步骤 3 中完成。现在我们已经将音频转换为文本,接下来的操作就顺利多了。只需搜索感兴趣的关键词(步骤 4)。当语料库和文本规模变大时,另一个可能有用的优化是打印出找到关键词的句子,以便更容易理解上下文。
面部识别
面部识别系统是一种用于识别或验证图像或视频中的人脸的技术。在对目标或潜在目标进行 OSINT 时,面部识别系统可能是无价的。在这个教程中,您将学习如何使用成熟的face_recognition
Python 库。
准备就绪
本教程的准备工作包括通过pip
安装face_recognition
和 OpenCV 库。安装说明如下:
pip install face_recognition opencv-python
此外,您还需要一张个体的肖像以及一系列您希望在其中搜索该个体的图片。
操作方法…
在接下来的步骤中,您将训练face_recognition
库,在一系列图片中找到并标记给定的个体:
- 开始时导入
face_recognition
库:
import face_recognition
- 首先加载该个体的标记肖像,并对其进行 OSINT 分析:
known_image = face_recognition.load_image_file("trump_official_portrait.jpg")
该个体的面部必须清晰可见:
- 接下来,加载一张
unknown
图片,您希望在其中自动检测该个体的面部:
unknown_image = face_recognition.load_image_file("trump_and_others.jpg")
该截图中展示的是正在被搜索的个体的面部:
- 编码该个体的面部:
trump_encoding = face_recognition.face_encodings(known_image)[0]
- 编码未知图像中所有个人的面部:
unknown_faces = face_recognition.face_encodings(unknown_image)
- 寻找个人脸部的图像:
matches = face_recognition.compare_faces(unknown_faces, trump_encoding)
print(matches)
结果如下:
[False, False, False, True]
- 加载未知图像中所有脸部的位置,并将匹配位置保存到一个变量中:
face_locations = face_recognition.face_locations(unknown_image)
trump_face_location = face_locations[3]
- 将未知图像读入
cv2
:
import cv2
unknown_image_cv2 = cv2.imread("trump_and_others.jpg")
- 在未知图像上画一个矩形,用于匹配脸部位置:
(top, right, bottom, left) = trump_face_location
cv2.rectangle(unknown_image_cv2, (left, top), (right, bottom), (0, 0, 255), 2)
- 给矩形标上标签:
cv2.rectangle(unknown_image_cv2, (left, bottom - 35), (right, bottom), (0, 0, 255), cv2.FILLED)
font = cv2.FONT_HERSHEY_DUPLEX
cv2.putText(unknown_image_cv2, "Trump", (left + 6, bottom - 6), font, 1.0, (255, 255, 255), 1)
- 显示带有标记矩形的图像:
cv2.namedWindow('image', cv2.WINDOW_NORMAL)
cv2.imshow('image',unknown_image_cv2)
cv2.waitKey(0)
cv2.destroyAllWindows()
下面的屏幕截图显示我们已经成功地输出了结果:
自动化这个搜索和标注过程是很简单的。
如何运作…
首先导入人脸识别库(步骤 1)。在接下来的步骤中,我们加载目标图像,该图像位于我们的渗透测试图像集合中。接下来,准备一个我们想要扫描其目标脸部存在的示例图像(步骤 3)。对所有发现的脸部进行编码(步骤 4 和 5),然后搜索目标脸部(步骤 6)。为了方便起见,我们打印出寻找与目标脸部匹配的结果。在步骤 7-10 中,我们希望展示我们已经找到了匹配。为此,我们加载了我们扫描的图像。然后,我们在分类器检测到目标脸部的地方画一个矩形和一个标签。查看第 11 步的结果,我们看到了巨大的成功。我们成功地进行了检测。
顺便提一下,face_recognition
工具背后的技术是深度学习,作为推论,使用 GPU 可以加速人脸搜索过程。
Deepfake
Deepfake 是利用神经网络对视频或图像进行处理,将一些内容叠加到其中,并使结果看起来逼真的技术。例如,这种技术可以获取艾丽斯说她支持某一运动的视频,并用鲍勃替换艾丽斯,创造出一个看起来逼真的鲍勃支持该运动的视频。显然,这种技术对我们对视频和图像的信任有深远的影响,同时也为社会工程师提供了一个有用的工具。
在这个配方中,我们使用了 Deepfake 变种,将一个目标的面部图像逼真地叠加到另一个目标面部图像上。该配方是 GitHub 仓库 wuhuikai/FaceSwap
中代码的重构和简化版本。
准备工作
此配方的准备工作包括在 pip
中安装 opencv
、dlib
和 scipy
。操作指南如下:
pip install opencv-python dlib scipy
此外,您将需要两张图像;一张是个人的肖像,另一张是包含脸部的图像。前者的面部将被转移到后者上。在 deepfake_input
文件夹中为您提供了一个样本。
如何做…
在以下步骤中,我们提供了一个将图像中一个人的面部替换为另一个人的面部的配方。代码分为五个部分:Deepfake.ipynb
(主要部分),deepfake_config
配置文件,deepfake_face_detection
,deepfake_face_points_detection
,以及deepfake_face_swap
。另外,还包括一个模型文件夹。
以下代码可以在Deepfake.ipynb
中找到:
- 导入
opencv
以进行图像操作,并导入关联代码中需要的面部交换方法:
import os
import cv2
import numpy as np
from deepfake_face_detection import select_face
from deepfake_face_swap import (
warp_image_2d,
warp_image_3d,
mask_from_points,
apply_mask,
correct_colours,
transformation_from_points,
ProcessFace,
)
- 在
content_image
中指定包含我们希望使用的面部的图像,并在target_image
中指定我们希望将面部转移到的图像。最后,指定您希望在哪里创建结果:
content_image = "deepfake_input/author.jpg"
target_image = "deepfake_input/gymnast.jpg"
result_image_path = "deepfake_results/author_gymnast.jpg"
在运行示例中,源图像是作者的面部照片:
目标图像是一个体操运动员在表演中的照片:
- 将图像读入
opencv
,然后提取源面部和目标面部:
content_img = cv2.imread(content_image)
destination_img = cv2.imread(target_image)
content_img_points, content_img_shape, content_img_face = select_face(content_img)
destination_img_points, destination_img_shape, destination_img_face = select_face(
destination_img
)
- 计算源面部的变换版本:
result_image = ProcessFace(
content_img_points, content_img_face, destination_img_points, destination_img_face
)
- 将变换后的面部绘制到目标图像中,并将文件写入磁盘:
x, y, w, h = destination_img_shape
destination_img_copy = destination_img.copy()
destination_img_copy[y : y + h, x : x + w] = result_image
result_image = destination_img_copy
cv2.imwrite(result_image_path, result_image)
本示例中deepfake
操作的最终结果是一个体操运动员的身体和作者的面部:
通过逐帧应用该方法,它可以扩展到视频中。
它是如何工作的…
像往常一样,首先导入适当的库(第 1 步)。在第 2 步中,指定样式图像和内容图像。这里,内容是目标图像,而样式是需要绘制的面部。在第 3 步中,如果图像中有多个面部,将会显示一个屏幕,询问您希望使用哪一张面部。下一步是进行计算,以确定如何绘制叠加的面部(第 4 步)。完成这一步后,我们可以在第 5 步绘制并显示deepfake
叠加的面部。显然,这种实现还有改进的空间,但也做得还不错。
深度伪造识别
随着深度伪造和类似图像伪造技术的出现,越来越难以区分伪造媒体和真实媒体。幸运的是,正如神经网络可以合成伪造的媒体一样,它们也可以检测伪造的媒体。在这个配方中,我们将利用深度神经网络来检测伪造图像。这个配方使用了MesoNet
架构,该架构可以在 GitHub 仓库DariusAf/MesoNet
中找到。
准备就绪
本配方的准备工作包括在pip
中安装keras
、tensorflow
和pillow
。安装说明如下:
pip install keras tensorflow pillow
此外,我们还为您提供了一组伪造和真实的图像,保存在mesonet_test_images
文件夹中,您可以在其中添加更多图像。
如何实现...
在接下来的步骤中,我们提供了一个检测图像是否由深度伪造技术生成的操作步骤。代码分为四个部分:Deepfake Recognition.ipynb
(主文件),定义 MesoNet 分类器的 mesonet_classifiers.py
文件,包含训练权重的 mesonet_weights
文件夹,以及包含测试图像的 mesonet_test_images
文件夹。
以下代码可以在 Deepfake Recognition.ipynb
中找到:
- 从
keras
导入 MesoNet 神经网络和图像数据生成器:
from mesonet_classifiers import *
from keras.preprocessing.image import ImageDataGenerator
- 实例化 MesoNet 并加载其权重:
MesoNet_classifier = Meso4()
MesoNet_classifier.load("mesonet_weights/Meso4_DF")
- 创建一个图像数据生成器来读取目录中的图像,并指定存放未知图像的路径:
image_data_generator = ImageDataGenerator(rescale=1.0 / 255)
data_generator = image_data_generator.flow_from_directory(
"", classes=["mesonet_test_images"]
)
以下是输出结果:
Found 3 images belonging to 1 classes.
- 定义一个字典,将数字标签转换为文本标签,
"real"
和"fake"
:
num_to_label = {1: "real", 0: "fake"}
在我们的示例中,我们将三张图片放入文件夹中,其中一张是真实的,另外两张是伪造的:
你能分辨出哪些是真,哪些是假吗?
- 运行 MesoNet 会显示以下输出:
X, y = data_generator.next()
probabilistic_predictions = MesoNet_classifier.predict(X)
predictions = [num_to_label[round(x[0])] for x in probabilistic_predictions]
print(predictions)
以下是输出结果:
['real', 'fake', 'fake']
它是如何工作的...
和大多数操作步骤一样,我们首先导入必要的库。然后在步骤 2 中加载 MesoNet 模型,即加载其结构和预训练权重。为了清晰起见,架构可以在 MesoNet_classifiers
文件中找到,结构如下:
x = Input(shape = (IMGWIDTH, IMGWIDTH, 3))
x1 = Conv2D(8, (3, 3), padding='same', activation = 'relu')(x)
x1 = BatchNormalization()(x1)
x1 = MaxPooling2D(pool_size=(2, 2), padding='same')(x1)
x2 = Conv2D(8, (5, 5), padding='same', activation = 'relu')(x1)
x2 = BatchNormalization()(x2)
x2 = MaxPooling2D(pool_size=(2, 2), padding='same')(x2)
x3 = Conv2D(16, (5, 5), padding='same', activation = 'relu')(x2)
x3 = BatchNormalization()(x3)
x3 = MaxPooling2D(pool_size=(2, 2), padding='same')(x3)
x4 = Conv2D(16, (5, 5), padding='same', activation = 'relu')(x3)
x4 = BatchNormalization()(x4)
x4 = MaxPooling2D(pool_size=(4, 4), padding='same')(x4)
y = Flatten()(x4)
y = Dropout(0.5)(y)
y = Dense(16)(y)
y = LeakyReLU(alpha=0.1)(y)
y = Dropout(0.5)(y)
y = Dense(1, activation = 'sigmoid')(y)
在步骤 3 中,我们定义并使用了一个 ImageDataGenerator
,这是一个方便的 keras
对象,允许我们在一个地方执行图像处理——在此例中,用于重新缩放和归一化像素的数值。很难判断标签 0
和 1
代表什么。因此,为了便于阅读,我们定义了一个字典将 0 和 1 转换为单词 real
和 fake
(步骤 4)。最后,在步骤 5 中,我们看到 MesoNet 模型能够正确预测测试图像的标签。
使用机器学习进行谎言检测
在进行社会工程学情报收集时,能够判断一个人是在说真话还是在说谎是至关重要的。为此,机器学习可以为我们提供帮助。通过分析视频中的微表情和声音质量,机器学习系统可以帮助识别不诚实的行为者。在本教程中,我们将运行一个谎言检测流程,使用稍作修改的《Lie To Me》——一个结合面部和声音识别的谎言检测系统。
准备中
准备此操作步骤需要在 pip
中安装多个包。包的列表可以在 requirements.txt
文件中找到。要一次性安装所有这些包,可以运行以下命令:
pip install -r requirements.txt
你需要一个带有音频的视频文件进行分析。
如何操作...
在接下来的步骤中,我们提供了一个分析视频中谎言行为的操作步骤:
- 运行 Lie To Me 应用:
Python application.py
-
打开 Lie To Me 门户,方法是访问指定的 IP 地址,例如
127.0.0.1:5000
,在浏览器中输入该地址。 -
点击 上传 并选择一个你想分析的视频:
- 分析完成后,您将注意到以下内容。
以下截图展示了眨眼分析图表中的变化:
以下截图展示了微表情分析图表中的变化:
以下截图展示了语音能量分析图表中的变化:
以下截图展示了语音音高分析图表中的变化:
以下截图展示了语音音高轮廓分析图表中的变化:
以下截图展示了元音持续时间分析图表中的变化:
- 最后,点击结果将显示视频中检测到的谎言分析:
它是如何工作的…
在第 1 步中,我们使用 Python 运行 "Lie To Me" 应用程序。我们进入应用程序的门户并上传候选视频(步骤 2 和 3)。视频分析完成后,"Lie To Me" 应用程序会显示几个探索性屏幕(步骤 4)。这些屏幕代表了可能表明撒谎的特征。最后,在步骤 5 中,我们看到一个屏幕,显示视频中是否包含撒谎的人,如果有,谎言是何时说的,且撒谎次数是多少。
个性分析
了解目标的个性类型和沟通风格大大增加了影响力的潜力。因此,个性分析是社会工程师工具箱中的一项有用工具。在本食谱中,我们将利用 IBM Watson 的 Personality Insights API 来分析目标的推文,从而获取个性报告。
准备工作
本食谱的准备工作包括在 pip
中安装 IBM Watson 包。具体步骤如下:
pip install ibm-watson
此外,您需要注册一个 Watson Personality Insights 账户。
如何操作...
在接下来的步骤中,我们设置一个 API 调用来分析推文作者的个性:
-
注册一个 Watson Personality Insights 账户,过程简单且免费。
-
导入 Watson 的 Python 库并记录今天的日期:
from ibm_watson import PersonalityInsightsV3
from datetime import date
v = str(date.today())
api_key = "fill me in"
- 指定您的 API 密钥,该密钥在第 1 步中已获得,并声明 Personality Insights 实例:
personality_insights_service = PersonalityInsightsV3(version=v, iam_apikey=api_key)
- 创建一个文本文件,例如一组推文:
tweets_file = "ElonMuskTweets.txt"
- 在文本文件上调用 Personality Insights API:
with open(tweets_file) as input_file:
profile = personality_insights_service.profile(
input_file.read(),
"application/json",
raw_scores=False,
consumption_preferences=True,
).get_result()
- 最后,打印出个性分析报告:
import json
print(json.dumps(profile, indent=2))
{ "word_count": 2463, "processed_language": "en", "personality": [ { "trait_id": "big5_openness", "name": "Openness", "category": "personality", "percentile": 0.7417085532819794, "significant": true, "children": [ { "trait_id": "facet_adventurousness", "name": "Adventurousness", "category": "personality", "percentile": 0.9589655282562557, "significant": true }, { "trait_id": "facet_artistic_interests", "name": "Artistic interests", "category": "personality", "percentile": 0.44854779978198406, "significant": true }, { "trait_id": "facet_emotionality", "name": "Emotionality", "category": "personality", "percentile": 0.0533351337262023, "significant": true },
<snip>
"consumption_preference_id": "consumption_preferences_books_financial_investing", "name": "Likely to read financial investment books", "score": 0.0 }, { "consumption_preference_id": "consumption_preferences_books_autobiographies", "name": "Likely to read autobiographical books", "score": 1.0 } ] }, { "consumption_preference_category_id": "consumption_preferences_volunteering", "name": "Volunteering Preferences", "consumption_preferences": [ { "consumption_preference_id": "consumption_preferences_volunteer", "name": "Likely to volunteer for social causes", "score": 0.0 } ] } ], "warnings": [] }
它是如何工作的…
首先注册 Watson 人格洞察账户。该服务有不同的层次,对 API 调用速率有不同的限制和不同的价格,但最低层次易于设置,免费,并且足够用于此方法。我们将今天的日期保存到一个变量中,并导入 IBM Watson 库(第 2 步)。通过指定最新日期,我们确保将使用最新版本的 Watson。在下一步中,我们使用我们的 API 密钥实例化 IBM Watson 人格洞察。
在第 4 步中,我们必须整理目标生成的文本数据集。利用 Twitter 鱼叉式网络钓鱼机器人的方法收集用户的推文可能会有所帮助。在第 5 步中,我们对我们的文本集运行人格洞察应用程序,其中包括埃隆·马斯克最近的推文。我们选择将人格概况显示为 JSON。也可以显示为其他格式,如 CSV,详细信息可以在人格洞察的 API 文档中找到。最后,在第 6 步中,我们打印人格概况的一个小片段。正如您所看到的,它甚至提供可操作的见解,比如目标愿意自愿参与的可能性有多大。
Social Mapper
Social Mapper 是一款 OSINT 工具,可以利用面部识别技术相关联目标的众多社交媒体资料。它会自动在流行的社交媒体网站上搜索目标的姓名和照片,轻松地找到用户的社交媒体资料,然后将结果输出到报告中,供您进一步调查使用。
Social Mapper 的最大优势在于通过将姓名搜索与图像识别相结合,而不仅仅是姓名搜索,可以消除误报,节省社交工程师宝贵的时间。
Social Mapper 目前支持 LinkedIn、Facebook、Twitter、Google Plus、Instagram、VKontakte、微博和豆瓣。
准备工作
对于此方法,建议您准备一个 Python 2.7 环境。Social Mapper 已经设计用于 Python 2.7,并且可能无法在其他 Python 环境中运行。安装的先决条件在 github.com/Greenwolf/social_mapper
中有详细说明。此外,您将需要在 Mac 或 Linux 机器上使用此方法。
如何操作...
在接下来的步骤中,我们提供了一个使用 Social Mapper 相关联个人社交媒体账户的方法:
-
按照 GitHub 页面上的说明在
github.com/Greenwolf/social_mapper
上安装 Social Mapper 及其先决条件。 -
将您目标的面部图像放入
Input, Examples/imagefolder/
中,文件名和目标的全名一致:
-
为您希望在其中搜索目标的社交媒体网站创建一次性账户。例如,创建一次性 Facebook、LinkedIn 和 Twitter 账户。
-
打开
social_mapper.py
文件,并填写你的临时账号凭证。例如,你可能只对 Twitter 感兴趣:
global linkedin_username
global linkedin_password
linkedin_username = ""
linkedin_password = ""
global facebook_username
global facebook_password
facebook_username = ""
facebook_password = ""
global twitter_username
global twitter_password
twitter_username = "FILL ME IN"
twitter_password = "FILL ME IN"
global instagram_username
global instagram_password
instagram_username = ""
instagram_password = ""
global google_username
global google_password
google_username = ""
google_password = ""
global vk_username
global vk_password
- 在终端中运行命令来搜索目标的社交媒体资料:
Python social_mapper.py -f imagefolder -I ./Input-Examples/imagefolder -m fast -tw
- 在
social_mapper/results-social-mapper.html
文件中检查输出:
对于每个目标个体,会增加一行,包含该个体的社交网络数据。
工作原理…
首先,在你的环境中准备 Social Mapper(步骤 1)。将目标的图片放置在输入目录(步骤 2)。图片必须以目标的全名命名,否则应用程序无法找到目标的账户。接下来,在步骤 3 中,为你希望在其上搜索目标的社交媒体网站创建临时账户,并将这些账户信息填入social_mapper.py
中的相应位置(步骤 4)。注意,你拥有的账户越多,通过 Social Mapper 能收集到的数据就越多。现在,你可以开始对目标进行搜索。在终端中运行命令来搜索目标的社交媒体资料(步骤 5)。你可以根据需要使用许多不同的参数和选项。例如,我们使用-tw
参数指定了 Twitter。然而,你也可以选择添加其他社交媒体网站,如 LinkedIn(-li
)或 Instagram(-ig
)。最后,在步骤 6 中,你会看到 Social Mapper 成功找到了比尔·盖茨的 Twitter 账户。
假评论生成器
社会工程学的重要部分是冒充。社会工程师可能希望假装代表一家目前不存在的公司或商业。通过创建一个个人资料并用可信的评论填充它,社会工程师可以为虚假的商业增加可信度。在本方案中,我们展示了如何训练一个 RNN,使其能够生成新的评论,类似于训练数据集中的评论。
训练一个假评论生成器
我们的第一步是训练模型。之后,我们将利用它来生成新的评论。
做好准备
本方案的准备工作包括在pip
中安装keras
和tensorflow
。安装步骤如下:
pip install keras tensorflow
如何操作…
在接下来的步骤中,我们提供了一个使用评论语料库训练循环神经网络(RNN)的方案:
- 收集你希望模仿的评论类型。更多内容请参见工作原理…部分的讨论:
with open("airport_reviews_short.csv", encoding="utf-8") as fp:
reviews_text = fp.read()
- 创建一个字典来将文本中的字符向量化:
chars_list = sorted(list(set(reviews_text)))
char_to_index_dict = {
character: chars_list.index(character) for character in chars_list
}
根据你的语料库包含的字符,字典可能是这样的:
{' ': 0, '!': 1, "'": 2, '(': 3, ')': 4, ',': 5, '-': 6, '.': 7, '/': 8, '2': 9, '5': 10, '<': 11, '>': 12, 'A': 13, 'B': 14, 'C': 15, 'D': 16, 'E': 17, 'F': 18, 'G': 19, 'H': 20, 'I': 21, 'J': 22, 'L': 23, 'M': 24, 'O': 25, 'R': 26, 'S': 27, 'T': 28, 'U': 29, 'W': 30, 'a': 31, 'b': 32, 'c': 33, 'd': 34, 'e': 35, 'f': 36, 'g': 37, 'h': 38, 'i': 39, 'j': 40, 'k': 41, 'l': 42, 'm': 43, 'n': 44, 'o': 45, 'p': 46, 'r': 47, 's': 48, 't': 49, 'u': 50, 'v': 51, 'w': 52, 'x': 53, 'y': 54}
- 构建一个 RNN 来学习并预测字符序列:
import keras
from keras import layers
max_length = 40
rnn = keras.models.Sequential()
rnn.add(
layers.LSTM(1024, input_shape=(max_length, len(chars_list)), return_sequences=True)
)
rnn.add(layers.LSTM(1024, input_shape=(max_length, len(chars_list))))
rnn.add(layers.Dense(len(chars_list), activation="softmax"))
- 选择一个优化器并编译模型:
optimizer = keras.optimizers.SGD(lr=0.01, decay=1e-6, nesterov=True)
rnn.compile(loss="categorical_crossentropy", optimizer=optimizer)
- 定义一个便捷的函数来将文本向量化:
import numpy as np
def text_to_vector(input_txt, max_length):
"""Reads in the text and vectorizes it.
X will consist of consecutive sequences of characters.
Y will consist of the next character.
"""
sentences = []
next_characters = []
for i in range(0, len(input_txt) - max_length):
sentences.append(input_txt[i : i + max_length])
next_characters.append(input_txt[i + max_length])
X = np.zeros((len(sentences), max_length, len(chars_list)))
y = np.zeros((len(sentences), len(chars_list)))
for i, sentence in enumerate(sentences):
for t, char in enumerate(sentence):
X[i, t, char_to_index_dict[char]] = 1
y[i, char_to_index_dict[next_characters[i]]] = 1
return [X, y]
- 向量化我们的示例输入文本,并分批训练模型:
X, y = text_to_vector(reviews_text, max_length)
rnn.fit(X, y, batch_size=256, epochs=1)
- 最后,保存模型的权重以供将来使用。
rnn.save_weights("weights.hdf5")
工作原理…
首先收集您想模仿的评论数据集(第 1 步)。一个实际的例子可能需要一个大型的评论语料库。有许多这样的数据集可用,比如 Yelp 评论数据集。继续到第 2 步,我们创建字符和数字之间的映射。这将允许我们对文本进行向量化。根据您的应用程序,您可能希望使用标准的 ASCII 码。但是,如果您只使用少量字符,那么这将不必要地减慢您的模型。我们继续声明一个 RNN 的架构来学习和预测字符序列(第 3 步)。我们使用了一个相对简单的架构。如下一节所示,它仍然提供令人信服的结果。有兴趣的读者可以自由尝试其他架构。接下来,我们声明一个(标准的)优化器(第 4 步),定义一个函数来接受文本,然后将其向量化,以便我们可以将其馈送到我们的神经网络中(第 5 步)。在第 5 步中,注意向量的形状如下:
-
X:(评论数量,
maxlen
,字符数量) -
Y:(评论数量,字符数量)
特别地,我们设置max_length=40
以简化计算,表示我们只考虑评论的前40
个字符。做好所有准备工作后,我们现在将我们的文本传入进行向量化,然后在此基础上训练我们的模型(第 6 步)。具体来说,我们的text_to_vector
函数接受文本并将其转换为向量化的句子,以及一个向量化的标签,即下一个字符。最后,我们保存我们模型的权重,这样我们将来就不必重新训练它了(第 7 步)。
生成虚假评论
训练完网络后,我们的下一步是利用它生成新的虚假评论。
准备就绪
准备工作包括在pip
中安装keras
和tensorflow
。具体指令如下:
pip install keras tensorflow
如何操作...
在以下步骤中,我们提供了一个使用先前训练过的 RNN 生成评论的方法:
- 我们将从导入
keras
开始:
import keras
from keras import layers
- 创建一个字符索引的字典或加载上一个配方中的字典:
char_indices = dict((char, chars.index(char)) for char in chars)
- 读入一个种子文本并声明神经网络接收的句子的
max_length
:
text = open("seed_text.txt").read()
max_length = 40
- 构建一个 RNN 模型并加载您预先训练的权重:
rnn = keras.models.Sequential()
rnn.add(
layers.LSTM(1024, input_shape=(max_length, len(chars_list)), return_sequences=True)
)
rnn.add(layers.LSTM(1024, input_shape=(max_length, len(chars_list))))
rnn.add(layers.Dense(len(chars_list), activation="softmax"))
rnn.load_weights("weights.hdf5")
optimizer = keras.optimizers.SGD(lr=0.01, decay=1e-6, nesterov=True)
rnn.compile(loss="categorical_crossentropy", optimizer=optimizer)
- 定义一个从概率向量中抽样的函数:
import numpy as np
def sample_next_char(preds):
"""Samples the subsequent character based on a probability distribution."""
return np.random.choice(chars_list, p=preds)
- 从初始种子文本生成随机评论:
import sys
start_index = np.random.randint(0, len(text) - max_length - 1)
generated_text = text[start_index : start_index + max_length]
sys.stdout.write(generated_text)
sentence_length = 1000
for i in range(sentence_length):
vec_so_far = np.zeros((1, max_length, len(chars_list)))
for t, char in enumerate(generated_text):
vec_so_far[0, t, char_to_index_dict[char]] = 1.0
preds = rnn.predict(vec_so_far)[0]
next_char = sample_next_char(preds)
generated_text += next_char
generated_text = generated_text[1:]
sys.stdout.write(next_char)
sys.stdout.flush()
print(generated_text)
这是代码运行的评论输出:
工作原理...
我们的初始步骤(步骤 1、2 和 4)是我们在训练阶段执行过的操作,我们在此处重新执行这些操作,以便让食谱保持自包含。在步骤 3 中,我们读取种子文本以初始化我们的 RNN。种子文本可以是任何包含已列出字符的文本,只要它的长度超过max_length
。现在,我们必须能够使用我们预训练、预加载并基于种子文本初始化的神经网络生成有趣的文本。为此,我们定义了一个方便的函数来采样神经网络将生成的后续字符(步骤 5)。从概率向量中进行采样可以确保 RNN 不会仅选择最可能的后续字符,避免生成重复的文本。还有一些更巧妙的采样方法,如使用温度参数和指数加权,但这一方法涵盖了基本原理。最后,在步骤 6 中,我们开始使用我们的神经网络生成文本。我们指定生成 1,000 个字符。调整这个参数将改变输出中的评论数量。
假新闻
假新闻是一种通过传统新闻媒体或在线社交媒体传播的虚假信息或宣传。像任何虚假信息运动一样,它的影响可能是毁灭性的。在本食谱中,您将加载一个真实与假新闻的数据集,并利用机器学习来判断一篇新闻故事是否是假新闻。
准备工作
本食谱的准备工作包括在pip
中安装pandas
和 scikit-learn。具体步骤如下:
pip install pandas sklearn
同时,提取fake_news_dataset.7z
。
如何做……
在接下来的步骤中,您将读取假新闻数据集,进行预处理,然后训练一个随机森林分类器来检测假新闻:
- 导入
pandas
并读取 CSV 文件fake_news_dataset.csv
:
import pandas as pd
columns = [
"text",
"language",
"thread_title",
"spam_score",
"replies_count",
"participants_count",
"likes",
"comments",
"shares",
"type",
]
df = pd.read_csv("fake_news_dataset.csv", usecols=columns)
- 通过聚焦于英文文章并删除缺失值的行来预处理数据集:
df = df[df["language"] == "english"]
df = df.dropna()
df = df.drop("language", axis=1
- 定义一个方便的函数,将分类特征转换为数字:
features = 0
feature_map = {}
def add_feature(name):
"""Adds a feature to the dictionary of features."""
if name not in feature_map:
global features
feature_map[name] = features
features += 1
- 将“
fake
”和“real
”特征转换为数字:
add_feature("fake")
add_feature("real")
- 定义一个函数,将所有标签转换为
real
或fake
:
def article_type(row):
"""Binarizes target into fake or real."""
if row["type"] == "fake":
return feature_map["fake"]
else:
return feature_map["real"]
- 将该函数应用于 DataFrame,将标签转换为 0 和 1:
df["type"] = df.apply(article_type, axis=1)
- 在 DataFrame 上创建训练-测试分割:
from sklearn.model_selection import train_test_split
df_train, df_test = train_test_split(df)
- 实例化两个 Tf-Idf 向量化器,一个用于文章的文本,另一个用于其标题:
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer_text = TfidfVectorizer()
vectorizer_title = TfidfVectorizer()
- 使用 Tf-Idf 向量化器拟合并转换文本和标题数据:
vectorized_text = vectorizer_text.fit_transform(df_train.pop("text").values)
vectorized_title = vectorizer_title.fit_transform(df_train.pop("thread_title").values
- 将 DataFrame 中剩余的数字字段转换为矩阵:
from scipy import sparse
spam_score_train = sparse.csr_matrix(df_train["spam_score"].values).transpose()
replies_count_train = sparse.csr_matrix(df_train["replies_count"].values).transpose()
participants_count_train = sparse.csr_matrix(
df_train["participants_count"].values
).transpose()
likes_train = sparse.csr_matrix(df_train["likes"].values).transpose()
comments_train = sparse.csr_matrix(df_train["comments"].values).transpose()
shares_train = sparse.csr_matrix(df_train["shares"].values).transpose()
- 将所有矩阵合并为一个特征矩阵,并创建一组标签:
from scipy.sparse import hstack
X_train = hstack(
[
vectorized_text,
vectorized_title,
spam_score_train,
replies_count_train,
participants_count_train,
likes_train,
comments_train,
shares_train,
]
)
y_train = df_train.pop("type").values
- 实例化一个随机森林分类器,并在训练数据上训练它:
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier()
clf.fit(X_train, y_train)
- 使用之前训练的 Tf-Idf 向量化器,将测试数据的文本和标题转换为数字形式:
vectorized_text_test = vectorizer_text.transform(df_test.pop("text").values)
vectorized_title_test = vectorizer_title.transform(df_test.pop("thread_title").values)
- 如之前所述,将所有数字特征合并为一个特征矩阵:
spam_score_test = sparse.csr_matrix(df_test["spam_score"].values).transpose()
replies_count_test = sparse.csr_matrix(df_test["replies_count"].values).transpose()
participants_count_test = sparse.csr_matrix(
df_test["participants_count"].values
).transpose()
likes_test = sparse.csr_matrix(df_test["likes"].values).transpose()
comments_test = sparse.csr_matrix(df_test["comments"].values).transpose()
shares_test = sparse.csr_matrix(df_test["shares"].values).transpose()
X_test = hstack(
[
vectorized_text_test,
vectorized_title_test,
spam_score_test,
replies_count_test,
participants_count_test,
likes_test,
comments_test,
shares_test,
]
)
y_test = df_test.pop("type").values
- 测试随机森林分类器:
clf.score(X_test, y_test)
以下是输出:
0.9977324263038548
它是如何工作的……
我们的初始步骤是导入虚假新闻数据集并进行基本的数据处理(步骤 1-6),例如将目标转换为数值类型。接下来,在步骤 7 中,我们对数据集进行训练-测试划分,为构建分类器做准备。由于我们处理的是文本数据,我们必须对其进行特征化。为此,在步骤 8 和 9 中,我们实例化了用于自然语言处理(NLP)的 Tf-Idf 向量化器,并对其进行了拟合。其他 NLP 方法在这里可能也会有所帮助。继续特征化,我们提取了数据框的数值特征(步骤 10 和 11)。完成数据集的特征化后,我们可以实例化一个基础分类器,并在数据集上进行拟合(步骤 12)。在步骤 13-15 中,我们对测试集重复这一过程并衡量我们的性能。观察到显著的性能表现。即使现在,提升分类器性能的可能步骤包括考虑文章来源、加入图片以及与其他事件进行更复杂的相关性分析。
第五章:使用机器学习的渗透测试
渗透测试,简称“渗透测试”,是一种授权的模拟网络攻击,旨在发现信息系统中的安全漏洞。在本章中,我们将涵盖一系列用于渗透测试和安全对策的机器学习技术。我们将从破解一个简单的验证码系统开始。接下来,我们将介绍使用深度学习、模糊测试和代码小工具自动发现软件漏洞。我们将展示如何增强 Metasploit,并讨论如何评估机器学习系统对抗性攻击的鲁棒性。最后,我们将探讨一些更专业的主题,如去匿名化 Tor 流量、通过键击动态识别未授权访问以及检测恶意 URL。
本章将涵盖以下内容:
-
验证码破丨解丨器
-
神经网络辅助模糊测试
-
DeepExploit
-
使用机器学习的 Web 服务器漏洞扫描器(GyoiThon)
-
使用机器学习去匿名化 Tor 流量
-
使用机器学习的物联网(IoT)设备类型识别
-
键击动态
-
恶意 URL 检测器
-
深度渗透
-
基于深度学习的自动软件漏洞检测系统(VulDeePecker)
技术要求
在本章中,我们将使用以下工具:
-
TensorFlow
-
Keras
-
OpenCV
-
Google API 客户端
-
Censys
-
NetworkX
-
Tldextract
-
dpkt
-
NumPy
-
SciPy
-
Xlib
-
Gensim
代码和数据集可以在github.com/PacktPublishing/Machine-Learning-for-Cybersecurity-Cookbook/tree/master/Chapter05
找到。
验证码破丨解丨器
验证码是一种旨在防止自动访问或抓取的系统。它通过提出旨在识别用户是人类还是程序的问题来实现这一点。你可能已经看到了许多类似的屏幕截图:
有时,要求插入一段代码,有时需要选择一些物体,例如在一系列图像中选择店面或交通信号灯,有时验证码是一个数学问题。在本章中,我们将破解一个简单的验证码系统,叫做“非常简单的验证码”:
尽管其简单性,非常简单的验证码仍然被广泛使用。最重要的是,它将说明如何破解其他更复杂的验证码系统。
第一步是处理 CAPTCHA 数据集,以便它适合机器学习。最简单的方法可能会失败。即,构建一个监督分类器,该分类器接收一个四字符的 CAPTCHA,并将其分类到 (26+10)⁴ = 1,679,616 个可能的类别中(26 个字母和 10 个数字,由于四个字符的组合,类别总数为上述幂的计算结果)。这种方法需要大量的数据和计算。相反,我们在单个字符上训练分类器,将 CAPTCHA 切割成单个字符,然后进行四次分类。这里同样有一个问题,那就是精确裁剪字符并不容易。通过使用 OpenCV 功能和额外的考虑,本食谱将解决这一挑战。
处理 CAPTCHA 数据集
在本食谱中,我们将执行创建 CAPTCHA 破丨解丨器的第一部分,即处理 CAPTCHA 数据集,使其适合训练机器学习模型。
准备工作
本食谱的准备工作包括在 pip
中安装一些软件包。安装说明如下:
pip install opencv-python imutils
此外,为了方便起见,已经包含了一些 CAPTCHA 数据集,存储在 captcha_images.7z
文件中。只需解压此存档到 captcha_images
文件夹中即可使用。
如何操作...
在接下来的步骤中,我们将处理一个 CAPTCHA 数据集,使其适合训练机器学习模型:
-
收集大量 CAPTCHA 数据。
-
我们的下一个目标是处理 CAPTCHA,指定 CAPTCHA 图像存储的位置,然后枚举指定文件夹中的所有 CAPTCHA:
import os
captcha_images_folder = "captcha_images"
captchas = [
os.path.join(captcha_images_folder, f) for f in os.listdir(captcha_images_folder)
]
- 定义一个函数,该函数将接受 CAPTCHA 图像并生成一个灰度版本,以及一个阈值化的(即黑白)版本的 CAPTCHA 图像:
import cv2
def preprocess_CAPTCHA(img):
"""Takes a CAPTCHA image and thresholds it."""
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
gray_with_border = cv2.copyMakeBorder(gray, 8, 8, 8, 8, cv2.BORDER_REPLICATE)
preprocessed = cv2.threshold(
gray_with_border, 0, 255, cv2.THRESH_BINARY_INV | cv2.THRESH_OTSU
)[1]
return gray_with_border, preprocessed
- 定义一个函数,该函数将接受 CAPTCHA 的路径,并使用该路径存储该 CAPTCHA 的文本标签:
def get_CAPTCHA_label(path_to_file):
"""Get the CAPTCHA text from the file name."""
filename = os.path.basename(path_to_file)
label = filename.split(".")[0]
return label
- 定义一个函数,该函数将接受 CAPTCHA 的轮廓,我们将计算这些轮廓,并确定它们的边界矩形,为将 CAPTCHA 切割成单个字符做准备:
def find_bounding_rectangles_of_contours(contours):
"""Determines the bounding rectangles of the contours of the cropped letters."""
letter_bounding_rectangles = []
for contour in contours:
(x, y, w, h) = cv2.boundingRect(contour)
if w / h > 1.25:
half_width = int(w / 2)
letter_bounding_rectangles.append((x, y, half_width, h))
letter_bounding_rectangles.append((x + half_width, y, half_width, h))
else:
letter_bounding_rectangles.append((x, y, w, h))
return letter_bounding_rectangles
- 定义一个函数,该函数将接受 CAPTCHA 的路径,将其作为图像读取,并使用我们已定义的函数进行预处理:
def CAPTCHA_to_gray_scale_and_bounding_rectangles(captcha_image_file):
"""Take a CAPTCHA and output a grayscale version as well as the bounding rectangles of its cropped letters."""
image = cv2.imread(captcha_image_file)
gray, preprocessed = preprocess_CAPTCHA(image)
contours = cv2.findContours(
preprocessed.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE
)
contours = contours[0]
letter_bounding_rectangles = find_bounding_rectangles_of_contours(contours)
letter_bounding_rectangles = sorted(letter_bounding_rectangles, key=lambda x: x[0])
return gray, letter_bounding_rectangles
- 定义另一个辅助函数,接受字母轮廓的边界矩形并从中生成字符图像:
def bounding_rectangle_to_letter_image(letter_bounding_box, grayscaled):
"""Obtains the letter defined by a bounding box."""
x, y, w, h = letter_bounding_box
letter_image = grayscaled[y - 2 : y + h + 2, x - 2 : x + w + 2]
return letter_image
- 定义最后一个辅助函数,执行 CAPTCHA 的裁剪,然后保存每个裁剪后的字符:
captcha_processing_output_folder = "extracted_letter_images"
character_counts = {}
def crop_bounding_rectangles_and_save_to_file(
letter_bounding_rectangles, gray, captcha_label
):
"""Saves the individual letters of a CAPTCHA."""
for letter_bounding_rectangle, current_letter in zip(
letter_bounding_rectangles, captcha_label
):
letter_image = bounding_rectangle_to_letter_image(
letter_bounding_rectangle, gray
)
save_path = os.path.join(captcha_processing_output_folder, current_letter)
if not os.path.exists(save_path):
os.makedirs(save_path)
character_count = character_counts.get(current_letter, 1)
p = os.path.join(save_path, str(character_count) + ".png")
cv2.imwrite(p, letter_image)
character_counts[current_letter] = character_count + 1
- 遍历所有的 CAPTCHA,进行预处理,找到字符轮廓,然后保存相应的字符:
import imutils
import numpy as np
for captcha_image_file in captchas:
captcha_label = get_CAPTCHA_label(captcha_image_file)
gray, letter_bounding_rectangles = CAPTCHA_to_gray_scale_and_bounding_rectangles(
captcha_image_file
)
if len(letter_bounding_rectangles) != 4:
continue
crop_bounding_rectangles_and_save_to_file(
letter_bounding_rectangles, gray, captcha_label
)
它是如何工作的……
我们的起点是收集大量 CAPTCHA(步骤 1)。您可以在 captcha_images.7z
中找到这些 CAPTCHA。或者,由于 Really Simple CAPTCHA 的代码可以在线获取,您可以修改它来生成大量的 CAPTCHA。其他方法包括使用机器人抓取 CAPTCHA。接下来,在 步骤 2 中,我们指定 CAPTCHA 图像的存储位置,并列举出指定文件夹中的所有 CAPTCHA。我们的目标是开始处理这些 CAPTCHA。在 步骤 3 中,我们定义一个函数,用来阈值化并将 CAPTCHA 图像转换为灰度图像。这样可以减少计算量,并且更容易确定一个字符的起始位置和下一个字符的结束位置。然后我们定义一个函数来获取 CAPTCHA 的标签(步骤 4)。接下来,为了准备处理,我们定义一个实用函数,获取 CAPTCHA 的轮廓,并利用这些轮廓来确定每个字符的边界矩形。一旦找到边界矩形,就可以轻松地裁剪字符,以便将其隔离出来(步骤 5)。然后,在 步骤 6 中,我们将到目前为止定义的函数组合成一个方便的函数。我们还定义了一个额外的函数,用来实际裁剪字符。将以上内容结合起来,在 步骤 8 中,我们编写一个函数来执行前面的步骤,然后保存结果中的隔离字符,并统计每个字符保存的数量。这对命名和统计都非常有帮助。现在我们可以开始裁剪了,所以,在 步骤 9 中,我们遍历所有的 CAPTCHA,并利用我们的实用函数裁剪单个字符。请注意,if
语句用于跳过裁剪错误的 CAPTCHA。
在本教程结束时,您的输出文件夹 extracted_letter_images
应该会有一个文件夹,包含大多数字母和数字,如下图所示:
并非所有的字符和数字都有表示,原因是 CAPTCHA 中不包含数字 1 和字母 I,因为这两者容易混淆。同理,数字 0 和字母 O 也存在相同问题。
在每个文件夹内,您将会有大量该字母或数字的实例,这些实例是从原始 CAPTCHA 中裁剪和处理出来的:
这结束了预处理步骤。
训练一个 CAPTCHA 解码神经网络
现在我们的数据已经处理得很整洁,可以训练一个神经网络来进行 CAPTCHA 预测。
准备工作
本教程的准备工作包括通过 pip 安装若干软件包。安装步骤如下:
pip install opencv-python imutils sklearn keras tensorflow
如何做到这一点...
在接下来的步骤中,我们将训练一个神经网络来解决 Really Simple CAPTCHA 的 CAPTCHA:
- 指定提取的字母图像所在的文件夹:
captcha_processing_output_folder = "extracted_letter_images"
- 导入 OpenCV 和 imutils 进行图像处理:
import cv2
import imutils
- 定义一个辅助函数,将图像调整为给定的大小:
def resize_image_to_dimensions(image, desired_width, desired_height):
"""Resizes an image to the desired dimensions."""
(h, w) = image.shape[:2]
if w > h:
image = imutils.resize(image, width=desired_width)
else:
image = imutils.resize(image, height=desired_height)
pad_width = int((desired_width - image.shape[1]) / 2.0)
pad_height = int((desired_height - image.shape[0]) / 2.0)
image_with_border = cv2.copyMakeBorder(
image, pad_height, pad_height, pad_width, pad_width, cv2.BORDER_REPLICATE
)
image_with_border_resized = cv2.resize(
image_with_border, (desired_width, desired_height)
)
return image_with_border_resized
- 准备读取图像:
def read_image(image_file_path):
"""Read in an image file."""
img = cv2.imread(image_file_path)
img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
img = resize_image_to_dimensions(img, 20, 20)
img = np.expand_dims(img, axis=2)
return img
- 读取每个字母图像并记录其标签:
import numpy as np
import os
from imutils import paths
images = []
labels = []
for image_file_path in imutils.paths.list_images(captcha_processing_output_folder):
image_file = read_image(image_file_path)
label = image_file_path.split(os.path.sep)[-2]
images.append(image_file)
labels.append(label)
- 归一化所有图像,即将像素值缩放到 0-1,并将标签转换为 NumPy 数组:
images = np.array(images, dtype="float") / 255.0
labels = np.array(labels)
- 创建训练集和测试集的划分:
from sklearn.model_selection import train_test_split
(X_train, X_test, y_train, y_test) = train_test_split(
images, labels, test_size=0.3, random_state=11
)
- 导入
LabelBinarizer
以编码标签:
from sklearn.preprocessing import LabelBinarizer
label_binarizer = LabelBinarizer().fit(y_train)
y_train = label_binarizer.transform(y_train)
y_test = label_binarizer.transform(y_test)
- 定义神经网络架构:
from keras.models import Sequential
from keras.layers.convolutional import Conv2D, MaxPooling2D
from keras.layers.core import Flatten, Dense
num_classes = 32
NN_model = Sequential()
NN_model.add(
Conv2D(20, (5, 5), padding="same", input_shape=(20, 20, 1), activation="relu")
)
NN_model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
NN_model.add(Conv2D(50, (5, 5), padding="same", activation="relu"))
NN_model.add(MaxPooling2D(pool_size=(2, 2), strides=(2, 2)))
NN_model.add(Flatten())
NN_model.add(Dense(512, activation="relu"))
NN_model.add(Dense(num_classes, activation="softmax"))
NN_model.compile(
loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"]
)
NN_model.summary()
- 将神经网络拟合到训练数据上:
NN_model.fit(
X_train,
y_train,
validation_data=(X_test, y_test),
batch_size=16,
epochs=5,
verbose=1,
)
- 选择一个你想破解的 CAPTCHA 实例:
CAPTCHA = "captcha_images\\NZH2.png"
-
我们将导入在上一节中用于处理图像的所有函数,即
find_bounding_rectangles_of_contours
、preprocess_CAPTCHA
、get_CAPTCHA_label
和CAPTCHA_to_grayscale_and_bounding_rectangles
。 -
按照我们在上一节中所做的方式处理 CAPTCHA 图像:
captcha_label = get_CAPTCHA_label(CAPTCHA)
gray, letter_bounding_rectangles = CAPTCHA_to_gray_scale_and_bounding_rectangles(
CAPTCHA
)
predictions = []
- 读取每个裁剪后的字母,并使用神经网络预测标签:
for letter_bounding_rectangle in letter_bounding_rectangles:
x, y, w, h = letter_bounding_rectangle
letter_image = gray[y - 2 : y + h + 2, x - 2 : x + w + 2]
letter_image = resize_image_to_dimensions(letter_image, 20, 20)
letter_image = np.expand_dims(letter_image, axis=2)
letter_image = np.expand_dims(letter_image, axis=0)
prediction = NN_model.predict(letter_image)
letter = label_binarizer.inverse_transform(prediction)[0]
predictions.append(letter)
- 输出预测结果:
predicted_captcha_text = "".join(predictions)
print("Predicted CAPTCHA text is: {}".format(predicted_captcha_text))
print("CAPTCHA text is: {}".format(CAPTCHA.split("\\")[-1].split(".")[0]))
Predicted CAPTCHA text is: NZH2
CAPTCHA text is: NZH2
它是如何工作的……
在上一节中我们已经完成了 CAPTCHA 的预处理,现在我们准备利用这些数据来训练一个 CAPTCHA 破丨解丨器。我们首先设置一个变量,指向从 CAPTCHA 中提取的所有单个字符的路径。然后我们导入将要使用的图像处理库(第 2 步),接着在第 3 步中定义一个调整图像大小的函数。这是一种相对标准的字符识别方法,可以加速训练并减少内存消耗。在第 4 步中,我们定义一个方便的函数,将文件读取为 NumPy 数组,用于训练;然后在第 5 步中,我们遍历所有字母并记录它们的标签。接下来,我们对所有图像进行归一化处理(第 6 步),这是另一个标准的计算机视觉技巧。现在我们创建训练集和测试集的划分,准备进行分类器拟合(第 7 步),然后使用标签二值化器对标签进行编码(第 8 步)。这是必要的,因为标签是字符,可能并非数值类型。在第 9 步中,我们定义神经网络的架构。所定义的架构是相对常见的,既具有精度又具备速度。在第 10 步中,我们将神经网络拟合到训练集上。其他参数可以增强网络的性能。现在,繁重的工作已经完成。接下来,我们展示 CAPTCHA 破丨解丨器如何工作。在第 11 步中,我们选择一个单例实例来展示 CAPTCHA 破丨解丨器的有效性。在第 12 到 14 步中,我们将图像通过我们的处理管道,生成对该 CAPTCHA 的预测文本。最后,我们验证预测是否正确(第 15 步)。
神经网络辅助模糊测试
Fuzz 测试是一种软件漏洞检测方法,其中将大量随机输入提供给程序,寻找会导致崩溃、信息泄露或其他意外行为的输入。在自动化模糊测试中,程序会生成这些输入。通常,自动化模糊测试器存在一个缺点,即它们倾向于重复尝试冗余的输入。为了解决这个问题,最近开发了基于 AI 的模糊测试器。在这个食谱中,我们将使用 She 等人开发的基于神经网络的模糊测试器 NEUZZ(见 arxiv.org/abs/1807.05620
)来发现软件中的未知漏洞。
准备工作
以下食谱要求使用 Ubuntu 16.04 或 18.04 虚拟机或物理机。在此设备上运行以下命令:
pip install keras
将 neuzz-modified.7z
解压到你选择的文件夹中。
如何操作...
在接下来的步骤中,我们提供了一个使用 NEUZZ 查找导致崩溃的输入的食谱,针对的是 readelf
Unix 工具:
- 使用以下命令构建 neuzz:
gcc -O3 -funroll-loops ./neuzz.c -o neuzz
如果你收到警告,没关系。
2. 安装 32 位二进制文件所需的库:
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install libc6:i386 libncurses5:i386 libstdc++6:i386 lib32z1
- 以 root 用户身份设置 CPU 缩放算法和核心转储通知:
cd /sys/devices/system/cpu
echo performance | tee cpu*/cpufreq/scaling_governor
echo core >/proc/sys/kernel/core_pattern
- 将
neuzz
、nn.py
和afl-showmap
复制到programs/readelf
:
cp /path_to_neuzz/neuzz /path_to_neuzz/programs/readelf
cp /path_to_neuzz/nn.py /path_to_neuzz/programs/readelf
cp /path_to_neuzz/afl-showmap /path_to_neuzz/programs/readelf
- 为所有文件提供可执行权限:
chmod +x /path_to_neuzz/programs/readelf/neuzz
chmod +x /path_to_neuzz/programs/readelf/nn.py
chmod +x /path_to_neuzz/programs/readelf/afl-showmap
chmod +x /path_to_neuzz/programs/readelf/readelf
- 打开终端以启动神经网络模块:
cd /path_to_neuzz/programs/readelf
python nn.py ./readelf -a
- 打开另一个终端并启动 NEUZZ:
./neuzz -i neuzz_in -o seeds -l 7507 ./readelf -a @@
下面是运行这些命令的一部分:
- 通过运行以下命令测试 NEUZZ 收集的崩溃:
./readelf -a crash/file_name
工作原理…
大多数流行的模糊测试工具在某些有限的情况下有效,但通常会陷入循环中。基于梯度的方法,如这里讨论的这种方法,虽然很有前景,但并不完全适用于这个问题,因为现实世界中的程序行为不一定是平滑的函数(例如,它们可能是不连续的)。NEUZZ 背后的理念是使用神经网络来逼近程序的行为作为一个平滑的函数。然后,可以应用梯度方法来提高模糊测试的效率。我们从编译 NEUZZ(步骤 1)开始。funroll-loops
标志会使编译器展开可以在编译时或进入循环时确定迭代次数的循环。这样,代码会变大,运行速度可能会更快,但不一定。继续设置 NEUZZ,我们加入 32 位支持(步骤 2)。我们设置 CPU 调节算法和核心转储通知(步骤 3);CPU 频率调节是一个让操作系统通过调整 CPU 频率来节省电力的设置。在接下来的两个步骤中,我们只需将文件放置在一个方便的位置,并允许执行权限。我们已经完成了 NEUZZ 的设置。现在,我们可以使用它来找到导致程序崩溃的输入。在步骤 6和步骤 7中,我们开始使用神经网络搜索崩溃。等待足够的时间,让步骤 6和步骤 7收集到足够的输入以使 readelf 工具崩溃后,我们执行其中一个输入(步骤 8),看看结果。果然,我们看到该输入导致 readelf 崩溃。
DeepExploit
DeepExploit是一个渗透测试工具,通过利用人工智能,将 Metasploit 提升到一个全新的水平。它的主要特点如下:
-
深度渗透:如果 DeepExploit 成功利用目标漏洞,它将自动执行漏洞利用到其他内部服务器。
-
学习:DeepExploit 是一个强化学习系统,类似于 AlphaGo。
使用 DeepExploit 来进行渗透测试可以大大增强你系统的安全性。在本教程中,我们将设置并运行 DeepExploit。
准备工作
现在,我们将引导你完成安装DeepExploit
的步骤:
-
下载并设置 Kali Linux。你可以在
www.offensive-security.com/kali-linux-vm-vmware-virtualbox-image-download/
找到虚拟机镜像。接下来的步骤都将在你的 Kali Linux 系统中进行。 -
通过在终端中运行以下命令来安装 Git:
sudo apt install git
- 通过运行以下命令来安装 Python:
sudo apt install python3-pip
- 克隆
git
仓库。在终端中,运行以下命令:
git clone https://github.com/emmanueltsukerman/machine_learning_security.git
- 打开
DeepExploit
目录:
在终端中,运行以下命令:
cd machine_learning_security/DeepExploit
- 安装
DeepExploit
的前置软件包。
在终端中,运行以下命令:
pip3 install -r requirements.txt
如何操作...
在本教程中,你将使用DeepExploit
来攻破一个受害者虚拟机。
- 下载
Metasploitable2
虚拟机镜像。
详细信息请参见metasploit.help.rapid7.com/docs/metasploitable-2
。
-
在虚拟机上运行一个
Metasploitable2
实例。 -
获取你的
Metasploitable2
的 IP 地址。 -
下一步是设置
DeepExploit
的配置。 -
在终端中运行
ifconfig
以获取 Kali Linux 的 IP 地址。编辑config.ini
(例如,使用vim
)并将[common]
下的server_host
设置为你的 Kali Linux IP。 -
将
config.ini
中的proxy_host
和proxy_port
的值设置为proxychains.conf
中的值。 -
在终端中,运行
cat /etc/proxychains.conf
并找到socks4
旁边的值:
...snip...
[ProxyList]
...snip...
socks4 127.0.0.1 9050
- 然后,将
config.ini
中proxy_host
和proxy_port
的值设置为这些值:
vim config.ini
...snip...
proxy_host : 127.0.0.1
proxy_port : 9050
-
在终端中运行
msfconsole
启动 Metasploit。 -
在 Metasploit 上启动 RPC 服务器。在指定的位置,输入你的 Kali Linux 的 IP 地址:
msf> load msgrpc ServerHost="kali linux ip" ServerPort=55553 User=test Pass=test1234.
你应该看到如下内容:
[*] MSGRPC Service: "kali linux ip":55553
[*] MSGRPC Username: test
[*] MSGRPC Password: test1234
[*] Successfully loaded plugin: msgrpc
- 在你的 Kali Linux 机器的终端中,运行
python3 DeepExploit.py -t "Metasploitable2 ip" -m train
来训练DeepExploit
。训练开始时应该如下所示:
每当DeepExploit
发现漏洞时,你会看到一个BINGO!!!
的通知,如下图所示:
培训结束时,学习内容会被保存。你可以在此处看到完成屏幕:
-
使用
DeepExploit
测试Metasploitable2
的漏洞。在终端中,运行python DeepExploit.py -t "Metasploitable2 ip" -m test
。 -
检查渗透测试的报告,如下所示:
firefox report/DeepExploit_test_report.html
我们会得到以下输出:
它的工作原理是…
本教程需要大量准备和配置。初步步骤是设置受害者虚拟机(步骤 1 和 2)。在 步骤 3 中,我们确定受害者虚拟机的 IP 地址。请注意,Metasploitable2
的凭据是 msfadmin/msfadmin
。您可以使用这些凭据登录,然后使用 ifconfig
获取 Metasploitable2
的 IP 地址。如果您在同一主机上使用 Kali Linux 虚拟机和 Metasploitable2
虚拟机,请确保两者能够通信。例如,将两个虚拟机都放在 Host-Only 适配器上,并从 Kali Linux 机器 ping Metasploitable2
机器。接下来,我们配置 DeepExploit
,以便我们可以针对受害者虚拟机进行攻击(步骤 4-8)。在 步骤 9 和 10 中,我们打开 Metasploit,Metasploit 被 DeepExploit
作为子模块使用。Metasploit 是一个主要的渗透测试框架。完成所有准备工作后,我们现在可以开始训练我们的模型。在 步骤 11 中,我们在 Metasploitable2
虚拟机上训练 DeepExploit
。该模型利用 异步演员批评算法(A3C
),这是 Google DeepMind 团队几年前发布的算法,因其超越了 深度 Q 网络
(DQN
)方法而闻名。接下来,我们测试我们的模型(步骤 12),并将其分析结果以报告的形式打印出来(步骤 13)。从长报告中可以看出,DeepExploit
发现了大量漏洞。从高层次来看,将强化学习应用于渗透测试表明,高效的自动化渗透测试即将到来。
使用机器学习的 Web 服务器漏洞扫描器(GyoiThon)
GyoiThon 是一款用于 Web 服务器的情报收集工具。它执行远程访问目标 Web 服务器,识别服务器上运行的产品,如 内容管理系统 (CMS)、Web 服务器软件、框架和编程语言。此外,它还可以使用 Metasploit 执行已识别产品的漏洞利用模块。
GyoiThon 的一些主要功能如下:
-
远程访问/完全自动化:GyoiThon 仅通过远程访问即可自动收集目标 Web 服务器的信息。您只需执行一次 GyoiThon 即可完成操作。
-
非破坏性测试:GyoiThon 仅通过正常访问即可收集目标 Web 服务器的信息。一个功能允许 GyoiThon 异常访问,如通过发送漏洞利用模块。
-
收集多样的信息:GyoiThon 拥有多种情报收集引擎,如网络爬虫、Google 自定义搜索 API、Censys、默认内容探索器以及云服务检查。通过使用字符串模式匹配和机器学习分析收集的信息,GyoiThon 可以识别目标 Web 服务器上运行的产品/版本/CVE 编号、HTML 注释/调试信息、登录页面及其他信息。
-
真实漏洞检查:GyoiThon 可以使用 Metasploit 执行漏洞模块,对已识别的产品进行攻击。因此,它可以确定目标 Web 服务器的实际漏洞。
准备工作
现在,您将按照步骤进行 GyoiThon 的安装和运行:
-
下载并设置 Kali Linux。您可以在
www.offensive-security.com/kali-linux-vm-vmware-virtualbox-image-download/
找到虚拟机镜像。接下来的步骤都将在您的 Kali Linux 机器上进行。 -
在终端中通过运行以下命令安装
git
:
sudo apt install git
- 在终端中运行以下命令安装
python
:
sudo apt install python3-pip
- 在 Linux 终端中,通过运行以下命令克隆 Git 仓库:
git clone https://github.com/gyoisamurai/GyoiThon.git
- 在终端中运行以下命令,打开
GyoiThon
目录:
cd GyoiThon
- 在终端中通过运行以下命令安装 DeepExploit 的先决条件:
pip3 install -r requirements.txt
- (可选)将
Gyoi_CveExplorerNVD
文件替换为本书仓库中提供的文件。在某些情况下,原始代码可能已失效,而本书仓库中的修改代码可能解决了这个问题。
如何操作...
在本食谱中,您将使用 DeepExploit 来攻陷一个受害者虚拟机:
-
下载
Metasploitable2
虚拟机镜像。 -
在虚拟机上运行一个
Metasploitable2
实例。 -
获取您的
Metasploitable2
的 IP 地址。 -
在您的 Kali Linux 机器上,您应该能够通过在浏览器中输入
Metasploitable2 的 ip 地址:80
来查看Metasploitable2
的网页实例:
-
在终端中运行
ifconfig
来获取 Kali Linux 的 IP 地址。通过编辑config.ini
(例如使用vim
),将proxy
设置为空,将server host
设置为您的Kali Linux IP
,将LHOST
设置为Metasploitable2 IP
,将LPORT
设置为80
。 -
打开主机文件,并通过输入
http:Metasploitable2 ip:80/
来添加Metasploitable2
的 web 服务器地址。 -
在 Kali Linux 机器的终端中,运行
python3 Gyoithon.py
开始攻击。 -
攻击结束后,检查位于报告文件夹中的渗透测试报告:
它是如何工作的…
步骤 1-3与 DeepExploit 中的配方没有区别,我们在其中准备了一个受害者虚拟机。Metasploitable2
的凭证是msfadmin/msfadmin
。你可以使用这些凭证登录,然后使用ifconfig
获取你的Metasploitable2
IP。如果你在同一主机上使用 Kali Linux 虚拟机和Metasploitable2
虚拟机,确保它们能够通信。例如,将这两个虚拟机设置为 Host-only 适配器,并从 Kali Linux 机器 ping 到 Metasploitable2 机器。接下来,我们通过检查能否访问受害者虚拟机的网页来验证环境是否已正确设置(步骤 4)。在步骤 5和步骤 6中,我们配置 GyoiThon 以准备进行渗透测试。完成环境设置后,我们就准备开始渗透测试。在步骤 7中,我们利用 GyoiThon 搜索漏洞。然后,我们输出检测到的漏洞的完整报告(步骤 8)。通过查看报告,我们可以看到 GyoiThon 能够找到大量漏洞。确定受害者主机的漏洞后,我们可以使用例如 Metasploit 等工具进行攻击。
使用机器学习去匿名化 Tor
Tor 是一款免费的开源软件,用于实现匿名通信。此外,只有使用 Tor 浏览器才能访问的网站存在,它们是暗网生态系统的一部分——这是指普通用户无法访问的互联网部分。在这个配方中,我们将通过收集足够的特征和信息来去匿名化 Tor 流量,从而识别匿名用户的活动。这个配方使用了conmarap/website-fingerprinting仓库。
准备工作
现在将引导你完成设置 Tor 和 Lynx 网页浏览器所需的步骤:
-
设置一个 Ubuntu 虚拟机。
-
在终端中通过运行以下命令安装
git
:
sudo apt install git
- 在终端中通过运行以下命令克隆代码仓库:
git clone https://github.com/conmarap/website-fingerprinting
- 在终端中通过运行以下命令安装
tor
和lynx
:
sudo apt install tor lynx
如何做到…
这个配方由三个部分组成。第一部分是收集 Tor 流量数据。第二部分是基于这些数据训练分类器。最后一部分是使用分类器来预测观察到的流量类型。
数据收集
以下步骤是数据收集时需要遵循的:
- 在
config.json
中列出你希望分类的流量类别:
- 在
website-fingerprinting
目录中,从一个类(比如duckduckgo.com
)收集额外的数据点,在终端中运行以下命令:
./pcaps/capture.sh duckduckgo.com
- 打开另一个终端,并运行以下命令:
torsocks lynx https://duckduckgo.com
此时,你的两个终端应该如下所示:
- 一旦浏览会话结束,按Q键两次结束捕获。
当足够的训练数据收集完毕后,我们就可以开始训练分类器。
训练
要在数据上训练分类器,请使用 Python 运行以下脚本:
python gather_and_train.py
结果是一个分类器文件:nb.dmp
。
预测
让我们使用分类器来预测所观察到的流量类型:
-
要预测新的流量实例,请收集
pcap
文件。 -
使用 Python 运行
predict.py
脚本,并将pcap
文件作为参数:
作者的聚类结果如下:
上述图表显示,尽管流量是匿名的,但特征的确能够区分不同类型的流量。
它是如何工作的...
我们通过创建一个我们希望分析的所有网站的目录(步骤 1)来开始构建我们的分类器。网站数量越多,目标访问这些网站的可能性越大。另一方面,网站越少,训练数据集所需的大小也越小。在步骤 2 到 步骤 4 中,我们执行收集分类器数据点所需的步骤。具体来说,我们通过访问步骤 1中定义的网站之一,然后捕获该访问的网络数据包。通过对不同的浏览会话重复这些步骤,我们能够构建一个强大的数据集。在步骤 5 中,我们在迄今为止收集的数据上训练分类器。现在我们已经准备好测试我们的分类器了。在步骤 6 中,我们访问一个网站并收集其pcap
文件,就像我们收集训练数据时一样。然后,我们使用分类器来分类这个访问(步骤 7)。我们看到,尽管用户使用了 Tor,它仍然正确地识别出了用户访问的网页。
总结来说,在本教程中,使用了 scikit-learn 编写了一个 k 最近邻分类器,用来分类 Tor 的pcap
文件。在实际情况中,流量数据往往不像干净数据那样,准确度在相同大小的真实数据集上可能会降低。然而,拥有大量资源的实体可以创建一个非常精确的分类器。这意味着,像这种方法完全有可能准确地攻破匿名用户。
使用机器学习进行物联网设备类型识别
随着物联网(IoT)的到来,任何目标的攻击面都呈指数级增加。随着新技术的出现,也伴随而来的是新的风险,而在物联网的案例中,一个组织面临的风险之一就是恶意物联网设备被接入组织的网络。因此,能够判断网络中是否新增了此类设备,并了解其性质,是至关重要的。在本教程中,我们将构建一个机器学习模型,以按类型分类网络中的物联网设备。
准备工作
本教程的准备工作包括在pip
中安装sklearn
、pandas
和xgboost
包。安装指令如下:
pip install pandas sklearn xgboost
数据集已通过iot_train.csv
和iot_test.csv
文件提供。
如何实现...
在接下来的步骤中,我们将训练并测试一个分类器,基于物联网(IoT)网络信息:
- 导入
pandas
和os
,并读取训练和测试数据:
import pandas as pd
import os
training_data = pd.read_csv("iot_devices_train.csv")
testing_data = pd.read_csv("iot_devices_test.csv")
数据包含 298 个特征,如下图所示:
- 创建训练和测试数据集,其中目标是设备类别:
X_train, y_train = (
training_data.loc[:, training_data.columns != "device_category"].values,
training_data["device_category"],
)
X_test, y_test = (
testing_data.loc[:, testing_data.columns != "device_category"].values,
testing_data["device_category"],
)
设备类别包括安全摄像头、电视、烟雾探测器、恒温器、水传感器、手表、婴儿监视器、运动传感器、灯具和插座。
- 将类别标签编码为数值形式:
from sklearn import preprocessing
le = preprocessing.LabelEncoder()
le.fit(training_data["device_category"].unique())
y_train_encoded = le.transform(y_train)
y_test_encoded = le.transform(y_test)
- 实例化一个
xgboost
分类器:
from xgboost import XGBClassifier
model = XGBClassifier()
- 训练并测试
xgboost
分类器:
model.fit(X_train, y_train_encoded)
model.score(X_test, y_test_encoded)
输出结果如下:
0.6622222222222223
如何运作…
本示例的一个重要动机是我们不能依赖 IP 地址作为设备的标识符,因为该值可能会被伪造。因此,我们希望分析流量的高层数据,即元数据和流量统计信息,而不是内容,以确定设备是否属于该网络。我们首先读取训练和测试数据集。接着我们对数据进行特征提取,并通过观察分类标签进行快速数据探索(第 2 步)。为了将这些数据输入到我们的分类器中,我们将分类标签转换为数值标签,用于训练我们的机器学习分类器(第 3 步)。在第 4 步和第 5 步中提取特征后,我们实例化、训练并测试一个xgboost
分类器,最终在测试集上获得了0.66
的评分。关联数据中有 10 类物联网设备。随机猜测 10 类设备的基线准确率为 0.1。这里训练的XGBoost
分类器达到了 0.66 的准确率,表明它确实是一个有前景的基于高层流量数据成功分类物联网设备的方法。
击键动态学
击键动态学,也叫打字生物特征识别,是通过一个人的打字方式来识别其身份的研究领域。一个重要的应用场景是识别使用给定凭证登录的用户,例如,谁正在以 root 用户登录?另一个应用场景是识别何时有不同的用户输入了一系列击键。在这个示例中,我们将展示如何使用基于机器学习的击键动态学算法。
准备工作
本示例需要在 Linux 虚拟机或实际机器上运行。准备工作如下:
- 在你的设备上安装
git
。
在终端中,运行以下命令:
sudo apt install git
- 克隆包含击键动态学算法代码的
git
仓库:
git clone https://github.com/emmanueltsukerman/keystroke_dynamics.git
如何操作…
在接下来的步骤中,我们将训练模型,基于两位用户的打字模式,然后使用该模型识别其中一位用户的打字模式。此示例应在 Linux 虚拟机或实际机器上运行:
- 运行
example.py
:
python example.py
- 通过选择选项 1 并输入文本来训练用户 1 的击键模式:
- 运行
example.py
并通过选择选项 1 来训练用户 2 的按键动态,然后让用户 2 输入以下文本:
-
运行
example.py
,这次选择选项 2。 -
让其中一个用户再次输入文本。算法将匹配该用户的键盘动态,并与训练数据中最相似的打字员进行匹配:
它是如何工作的…
按键动态分析利用用户在键盘上的打字节奏和速度来验证该用户的身份。我们首先设置一些基准。在步骤 1 和 步骤 2 中,我们设置按键动态系统以学习第一个用户的打字模式。然后我们对第二个用户执行相同操作(步骤 3)。这就建立了我们的正常用户及其打字模式。在步骤 4 和 步骤 5 中,我们利用经过训练的模型(在步骤 1-3 中训练)来确定当前用户是谁。如你所见,分类器输出相似度得分并预测当前用户是谁,依据的是它保存的用户目录。这使我们能够检测未经授权的用户,并能够简单地跟踪系统的使用情况。
恶意 URL 检测器
恶意 URL 每年造成数十亿美元的损失,原因包括托管垃圾邮件、恶意软件和漏洞攻击,以及窃取信息。传统上,对这些威胁的防御依赖于黑名单和白名单——分别是被认为恶意的 URL 列表和被认为安全的 URL 列表。然而,黑名单存在缺乏通用性的问题,且无法防御之前未见过的恶意 URL。为了解决这一问题,已经开发出了机器学习技术。在本教程中,我们将使用 Keras 运行一个基于字符级递归神经网络的恶意 URL 检测器。代码基于 github.com/chen0040/keras-malicious-url-detector
。
准备就绪
本教程的准备工作包括在 pip
中安装多个软件包。安装步骤如下:
pip install keras tensorflow sklearn pandas matplotlib
此外,克隆以下 git
仓库:
git clone https://github.com/emmanueltsukerman/keras-malicious-url-detector.git
如何操作…
- 训练双向 LSTM 模型:
python bidirectional_lstm_train.py
训练界面应如下所示:
- 测试分类器:
python bidirectional_lstm_predict.py
测试界面应如下所示:
最后,你可以在 reports
文件夹下看到结果:
它是如何工作的…
这是一个相对简单的方案,但它是构建更高效恶意 URL 检测器的良好起点。数据集由带有标签 0 和 1 的 URL 组成,取决于它们是恶意的还是良性的:
http://google.com,0
http://facebook.com,0
http://youtube.com,0
http://yahoo.com,0
http://baidu.com,0
http://wikipedia.org,0
http://qq.com,0
http://linkedin.com,0
http://live.com,0
http://twitter.com,0
http://amazon.com,0
http://taobao.com,0
http://blogspot.com,0
<snip>
http://360.cn,0
http://go.com,0
http://bbc.co.uk,0
http://xhamster.com,0
在步骤 1中,我们训练了一个双向 LSTM 模型。通过深入代码,你可以根据自己的需求调整网络。训练好模型后,重要的是评估其性能并进行一些合理性检查。我们在步骤 2中进行测试步骤,通过在 20 个随机选定的 URL 上展示分类器的结果来进行。一般来说,双向 LSTM 是一种循环神经网络架构,因其能够记住信息并分析从前到后的数据及从后到前的数据而展现出巨大的潜力。
Deep-pwning
Deep-pwning 是一个用于评估机器学习工具在对抗性攻击下鲁棒性的框架。数据科学界已经广泛认识到,单纯为分类图像而训练的深度神经网络等简单机器学习模型非常容易被欺骗。
下图展示了《解释与利用对抗样本》,I. J. Goodfellow 等人的研究:
网络安全是一个对抗性较强的领域,用于防御攻击者的机器学习模型应当具备抗对抗性攻击的鲁棒性。因此,不仅要报告通常的性能指标,如准确率、精确度和召回率,还需要有一定的对抗鲁棒性测量。deep-pwning 框架就是一个实现这一目标的简单工具包。
准备工作
准备这个食谱时,请按照以下步骤操作:
-
在你的设备上安装
git
。 -
使用以下命令通过 Git 下载或克隆该仓库:
git clone https://github.com/emmanueltsukerman/deep-pwning.git
- 安装该仓库的依赖项。
在终端中,进入仓库的根目录并运行以下命令:
pip install -r requirements.txt
如何操作…
在接下来的步骤中,你将利用 deep-pwning 对 MNIST 数字数据集上的 LeNet5 进行攻击:
- 从该目录向下,使用以下命令运行 MNIST 驱动程序:
python mnist_driver.py –restore_checkpoint
结果应该像这样显示:
它是如何工作的…
在步骤 1中,我们创建了一个包含对抗样本的大型数据集;即,创建了 150,000 个对抗样本,几乎所有这些样本都能够欺骗 LeNet5 对数字的识别。要检查这些对抗样本,可以像这样解压输出目录中的 pickle 文件:
在utils
目录下,有一个名为mnist_read_pickle.py
的文件,它将pickle
文件作为参数传入。运行它会显示一个对抗样本。以下图像让 LeNet5 误以为它看到的是数字 1:
deep-pwning 框架设计为模块化,因此用户可以插入并修改各个部分以满足自己的需求。例如,替换 MNIST 数据集和 LeNet5 架构。
基于深度学习的自动化软件漏洞检测系统
信息安全专家通常能够识别出潜在可利用的代码片段。然而,这项工作是繁重且成本高昂的,并且可能不足以确保程序的安全性。深度学习相较于传统机器学习的一个重要优势是可以自动发现特征。这使得我们能够减少对漏洞领域人类专家的依赖,并且能产生更有效的系统。在本教程中,我们将使用一个修改版的*VulDeePecker : *基于深度学习的漏洞检测系统 (arxiv.org/pdf/1801.01681.pdf
),自动检测 C/C++软件中的缓冲区错误漏洞和资源管理错误。
准备工作
本教程的准备工作包括安装pandas
、gensim
、keras
、tensorflow
和sklearn
等软件包,使用pip
进行安装。安装指令如下:
pip install pandas gensim keras tensorflow sklearn
此外,为了进行本教程操作,克隆 VulDeePecker 的代码库:
- 安装
git
,然后在终端运行以下命令:
git clone https://github.com/emmanueltsukerman/Deep-Learning-Based-System-for-Automatic-Detection-of-Software-Vulnerabilities.git
datasets
文件夹中有两个数据集,cwe119_cgd.7z
和cwe399_cgd.7z
。如果你想使用它们,请解压。
如何操作…
- 收集一个包含代码小片段的训练数据集,并将其放在
datasets
文件夹下。该文件夹中有两个数据集,它们的形式如下:
- 在你的数据集上训练和测试深度学习模型。
通过运行以下命令来完成此操作:
python vuldeepecker_train.py "path to dataset"
输出结果显示在以下截图中:
- 收集你希望进行预测的数据集,并将其放置在
datasets
文件夹下:
- 使用你训练好的模型通过运行以下命令来预测这些代码是否存在漏洞:
python vuldeepecker_predict.py "path to data" "path to model"
它是如何工作的…
为了让机器学习能够进行漏洞检测,你需要找到能够学习的软件程序表示方式。为此,我们使用代码小片段,并将它们转换为向量。代码小片段是一组语义相关的代码行。在步骤 1中,我们为训练收集这样的代码小片段。你可以看到三种代码小片段及其标签的图片。这里,标签为 1 表示存在漏洞,而标签为 0 表示没有漏洞。为了从所需程序中提取小片段,建议使用商业产品 Checkmarx 来提取程序切片,然后将它们组装成代码小片段。另一个数据集cwe-119
与缓冲区错误漏洞相关。接下来,我们在我们的漏洞数据集上训练一个深度学习模型(步骤 2)。所使用的深度学习模型是双向长短期记忆网络(BLSTM),其架构如下所示:
Bidirectional(LSTM(300), input_shape=(50, 50))
Dense(300)
LeakyReLU()
Dropout(0.5)
Dense(300)
LeakyReLU()
Dropout(0.5)
Dense(2, activation='softmax')
Adamax(lr=0.002)
'categorical_crossentropy'
请注意,训练阶段会自动将模型保存为[base-name-of-training-dataset]_model.h5
。现在我们准备寻找新的漏洞。所以,我们将测试集放入datasets
中(步骤 3),然后通过预测这个新数据集中的漏洞来使用我们的神经网络(步骤 4)。
第六章:自动入侵检测
入侵检测系统监控网络或一组系统,以发现恶意活动或政策违规行为。任何被捕获的恶意活动或违规行为都将被阻止或报告。在本章中,我们将设计并实现几个使用机器学习的入侵检测系统。我们将从检测垃圾邮件这一经典问题开始。然后我们将转向分类恶意 URL。接下来,我们将简要介绍如何捕获网络流量,以便解决更具挑战性的网络问题,如僵尸网络和 DDoS 检测。我们将构建一个针对内部威胁的分类器。最后,我们将解决信用卡欺诈这一依赖示例、成本敏感、极度不平衡且具有挑战性的问题。
本章包含以下内容:
-
使用机器学习进行垃圾邮件过滤
-
网络钓鱼 URL 检测
-
捕获网络流量
-
网络行为异常检测
-
僵尸网络流量检测
-
内部威胁检测的特征工程
-
使用异常检测进行内部威胁检测
-
检测 DDoS 攻击
-
信用卡欺诈检测
-
伪造银行票据检测
-
使用机器学习进行广告拦截
-
无线室内定位
技术要求
本章的技术前提如下:
-
Wireshark
-
PyShark
-
costcla
-
scikit-learn
-
pandas
-
NumPy
代码和数据集可以在github.com/PacktPublishing/Machine-Learning-for-Cybersecurity-Cookbook/tree/master/Chapter06
找到。
使用机器学习进行垃圾邮件过滤
垃圾邮件(不需要的邮件)约占全球电子邮件流量的 60%。除了自 1978 年首次垃圾邮件出现以来,垃圾邮件检测软件取得的进展外,任何拥有电子邮件帐户的人都知道,垃圾邮件仍然是一个费时且昂贵的问题。在这里,我们提供一个使用机器学习进行垃圾邮件-非垃圾邮件(ham)分类的配方。
准备就绪
为本配方的准备工作包括通过pip
安装scikit-learn
包。命令如下:
pip install sklearn
此外,将spamassassin-public-corpus.7z
解压到名为spamassassin-public-corpus
的文件夹中。
如何实现...
在以下步骤中,我们构建一个用于分类垃圾邮件和非垃圾邮件的分类器:
-
解压
spamassassin-public-corpus.7z
数据集。 -
指定
spam
和ham
目录的路径:
import os
spam_emails_path = os.path.join("spamassassin-public-corpus", "spam")
ham_emails_path = os.path.join("spamassassin-public-corpus", "ham")
labeled_file_directories = [(spam_emails_path, 0), (ham_emails_path, 1)]
- 为两个类别创建标签并将电子邮件读取到语料库中:
email_corpus = []
labels = []
for class_files, label in labeled_file_directories:
files = os.listdir(class_files)
for file in files:
file_path = os.path.join(class_files, file)
try:
with open(file_path, "r") as currentFile:
email_content = currentFile.read().replace("\n", "")
email_content = str(email_content)
email_corpus.append(email_content)
labels.append(label)
except:
pass
- 对数据集进行训练-测试拆分:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
email_corpus, labels, test_size=0.2, random_state=11
)
- 在训练数据上训练 NLP 管道:
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import HashingVectorizer, TfidfTransformer
from sklearn import tree
nlp_followed_by_dt = Pipeline(
[
("vect", HashingVectorizer(input="content", ngram_range=(1, 3))),
("tfidf", TfidfTransformer(use_idf=True,)),
("dt", tree.DecisionTreeClassifier(class_weight="balanced")),
]
)
nlp_followed_by_dt.fit(X_train, y_train)
- 在测试数据上评估分类器:
from sklearn.metrics import accuracy_score, confusion_matrix
y_test_pred = nlp_followed_by_dt.predict(X_test)
print(accuracy_score(y_test, y_test_pred))
print(confusion_matrix(y_test, y_test_pred))
以下是输出结果:
0.9761620977353993
[[291 7]
[ 13 528]]
它是如何工作的…
我们首先准备一个包含原始邮件的数据集(步骤 1),读者可以通过查看数据集来进行检查。在步骤 2中,我们指定垃圾邮件和正常邮件的路径,并为它们的目录分配标签。接着,我们将在步骤 3中读取所有邮件到一个数组中,并创建一个标签数组。接下来,我们将数据集进行训练-测试拆分(步骤 4),然后在步骤 5中为其拟合一个 NLP 管道。最后,在步骤 6中,我们测试我们的管道。我们发现准确率相当高。由于数据集相对平衡,因此无需使用特殊的评估指标来评估成功。
钓鱼 URL 检测
钓鱼网站是通过让你认为自己在一个合法网站上,从而试图获取你的账户密码或其他个人信息的网站。一些钓鱼 URL 与目标 URL 只有一个字符不同,这个字符特别选择以增加拼写错误的几率,而其他一些则利用其他渠道来生成流量。
这是一个钓鱼网站的示例,该网站通过让用户相信他们的电子邮件将被关闭,从而迫使用户提供电子邮件地址:
由于钓鱼是最成功的攻击方式之一,因此能够识别 URL 是否合法至关重要。在本食谱中,我们将构建一个机器学习模型来检测钓鱼 URL。
准备工作
本食谱的准备工作包括在pip
中安装scikit-learn
和pandas
。命令如下:
pip install sklearn pandas
此外,提取名为phishing-dataset.7z
的压缩文件。
如何进行...
在接下来的步骤中,我们将读取一个特征化的 URL 数据集,并对其进行分类器训练。
-
从本章节的目录下载钓鱼数据集。
-
使用
pandas
读取训练和测试数据:
import pandas as pd
import os
train_CSV = os.path.join("phishing-dataset", "train.csv")
test_CSV = os.path.join("phishing-dataset", "test.csv")
train_df = pd.read_csv(train_CSV)
test_df = pd.read_csv(test_CSV)
- 准备钓鱼网页的标签:
y_train = train_df.pop("target").values
y_test = test_df.pop("target").values
- 准备特征:
X_train = train_df.values
X_test = test_df.values
- 训练、测试和评估分类器:
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, confusion_matrix
clf = RandomForestClassifier()
clf.fit(X_train, y_train)
y_test_pred = clf.predict(X_test)
print(accuracy_score(y_test, y_test_pred))
print(confusion_matrix(y_test, y_test_pred))
以下是输出:
0.9820846905537459
[[343 4]
[ 7 260]]
如何工作...
我们首先下载数据集,然后将其读取到数据框中(步骤 1和步骤 2),以方便检查和操作。接下来,我们将数据集放入数组中,为机器学习做准备(步骤 3和步骤 4)。该数据集包含了数千个钓鱼 URL 的特征向量。这里列出了 30 个特征的名称和值:
属性 | 值 | 列名 |
---|---|---|
是否具有 IP 地址 | has_ip |
|
是否具有长 URL | long_url |
|
是否使用缩短服务 | short_service |
|
是否具有'@'符号 | has_at |
|
是否双斜杠重定向 | double_slash_redirect |
|
是否具有前后缀 | pref_suf |
|
是否具有子域名 | has_sub_domain |
|
SSL 最终状态 | ssl_state |
|
域名注册长度 | long_domain |
|
网站图标 | favicon |
|
是否为标准端口 | port |
|
是否使用 HTTPS 令牌 | https_token |
|
请求 URL | req_url |
|
异常 URL 锚点 | url_of_anchor |
|
标签中的链接 | tag_links |
|
SFH | SFH |
|
提交到邮件 | submit_to_email |
|
异常 URL | abnormal_url |
|
重定向 | redirect |
|
鼠标悬停 | mouseover |
|
右键点击 | right_click |
|
弹出窗口 | popup |
|
内嵌框架 | iframe |
|
域名年龄 | domain_age |
|
DNS 记录 | dns_record |
|
网络流量 | traffic |
|
页面排名 | page_rank |
|
Google 索引 | google_index |
|
指向页面的链接 | links_to_page |
|
统计报告 | stats_report |
|
结果 | target |
在 步骤 5 中,我们训练并测试一个随机森林分类器。准确度相当高,但根据数据集的平衡情况,可能需要考虑 FP 约束。有许多方法可以扩展此类检测器,例如添加其他特征并扩大数据集。由于大多数网站包含一些图片,图像分类器只是模型改进结果的一种方式。
捕获网络流量
捕获网络流量对于故障排除、分析、软件和通信协议开发非常重要。对于关注安全的个人来说,监控网络流量是检测恶意活动或政策违规的关键。在本教程中,我们将演示如何捕获和检查网络流量。
准备就绪
为了准备此教程,请遵循以下步骤:
- 安装
pyshark
:
pip install pyshark
- 安装
wireshark
。最新版本可以在www.wireshark.org/download.html
找到。
如何操作……
在接下来的步骤中,我们将使用一个名为 PyShark 的 Python 库,并结合 Wireshark 捕获和检查网络流量。
- 你必须将
tshark
添加到 PyShark 的配置路径中。Tshark 是 Wireshark 的命令行版本。为此,运行以下命令:
pip show pyshark
请注意数据包的位置。在该位置的 pyshark
目录中,找到文件 config.ini
。编辑 tshark_path
为 wireshark
安装文件夹内 tshark
的路径。同样,编辑 dumpcap_path
为 wireshark
安装文件夹内 dumpcap
的路径。
步骤 2 和 步骤 4 应在 Python 环境中执行。请注意,在当前版本中,pyshark
在 Jupyter notebook 中运行时可能存在一些 bug。
- 导入
pyshark
并指定捕获的持续时间:
import pyshark
capture_time = 20
- 指定文件名以输出捕获内容,
to
:
import datetime
start = datetime.datetime.now()
end = start+datetime.timedelta(seconds=capture_time)
file_name = "networkTrafficCatpureFrom"+str(start).replace(" ", "T")+"to"+str(end).replace(" ","T")+".pcap"
- 捕获网络流量:
cap = pyshark.LiveCapture(output_file=file_name)
cap.sniff(timeout=capture_time)
- 要检查捕获内容,请在 Wireshark 中打开
pcap
文件:
工作原理……
我们从配置 tshark
(Wireshark 的命令行版本)开始。完成配置后,可以通过pyshark
进行访问。我们导入pyshark
并指定网络捕获的持续时间(步骤 2)。由于捕获的网络流量数据可能非常庞大,因此控制持续时间很重要。接下来,我们指定输出捕获的名称,使其既唯一又容易理解(步骤 3),然后在步骤 4中,开始捕获流量。最后,在步骤 6中,我们使用 Wireshark 的图形界面来检查捕获的网络流量。在熟练的操作下,这样的网络流量有助于检测不安全的物联网设备、配置错误、异常事件、黑客攻击尝试,甚至数据泄露。
网络行为异常检测
网络行为异常检测(NBAD)是对网络进行连续监控,寻找不寻常的事件或趋势。理想情况下,NBAD 程序实时跟踪关键网络特征,并在检测到表明威胁的奇异事件或趋势时生成警报。在本教程中,我们将使用机器学习构建一个 NBAD。
使用的数据集是从著名的数据集 KDD 数据集中修改而来的子集,是测试和构建 IDS 系统的标准数据集。该数据集包含在军事网络环境中模拟的各种入侵。
准备工作
本教程的准备工作包括安装scikit-learn
、pandas
和matplotlib
。命令如下:
pip install sklearn pandas matplotlib
此外,解压kddcup_dataset.7z
档案。
如何做到这一点…
在接下来的步骤中,我们将利用孤立森林在 KDD 数据集中检测异常:
- 导入
pandas
并将数据集读取到数据框中:
import pandas as pd
kdd_df = pd.read_csv("kddcup_dataset.csv", index_col=None)
- 检查流量类型的比例:
y = kdd_df["label"].values
from collections import Counter
Counter(y).most_common()
将会观察到以下输出:
[('normal', 39247),
('back', 1098),
('apache2', 794),
('neptune', 93),
('phf', 2),
('portsweep', 2),
('saint', 1)]
- 将所有非正常观测值转换为单一类别:
def label_anomalous(text):
"""Binarize target labels into normal or anomalous."""
if text == "normal":
return 0
else:
return 1
kdd_df["label"] = kdd_df["label"].apply(label_anomalous)
- 获取异常数据与正常数据的比例。这是我们在孤立森林中使用的污染参数:
y = kdd_df["label"].values
counts = Counter(y).most_common()
contamination_parameter = counts[1][1] / (counts[0][1] + counts[1][1])
- 将所有类别特征转换为数值形式:
from sklearn.preprocessing import LabelEncoder
encodings_dictionary = dict()
for c in kdd_df.columns:
if kdd_df[c].dtype == "object":
encodings_dictionary[c] = LabelEncoder()
kdd_df[c] = encodings_dictionary[c].fit_transform(kdd_df[c])
- 将数据集分为正常和异常观测值:
kdd_df_normal = kdd_df[kdd_df["label"] == 0]
kdd_df_abnormal = kdd_df[kdd_df["label"] == 1]
y_normal = kdd_df_normal.pop("label").values
X_normal = kdd_df_normal.values
y_anomaly = kdd_df_abnormal.pop("label").values
X_anomaly = kdd_df_abnormal.values
- 将数据集分为训练集和测试集:
from sklearn.model_selection import train_test_split
X_normal_train, X_normal_test, y_normal_train, y_normal_test = train_test_split(
X_normal, y_normal, test_size=0.3, random_state=11
)
X_anomaly_train, X_anomaly_test, y_anomaly_train, y_anomaly_test = train_test_split(
X_anomaly, y_anomaly, test_size=0.3, random_state=11
)
import numpy as np
X_train = np.concatenate((X_normal_train, X_anomaly_train))
y_train = np.concatenate((y_normal_train, y_anomaly_train))
X_test = np.concatenate((X_normal_test, X_anomaly_test))
y_test = np.concatenate((y_normal_test, y_anomaly_test))
- 实例化并训练一个孤立森林分类器:
from sklearn.ensemble import IsolationForest
IF = IsolationForest(contamination=contamination_parameter)
IF.fit(X_train
- 对正常和异常观测值进行分类评分:
decisionScores_train_normal = IF.decision_function(X_normal_train)
decisionScores_train_anomaly = IF.decision_function(X_anomaly_train)
- 绘制正常数据集的分数:
import matplotlib.pyplot as plt
%matplotlib inline
plt.figure(figsize=(20, 10))
_ = plt.hist(decisionScores_train_normal, bins=50)
以下图表提供了输出结果:
- 类似地,绘制异常观测值的分数以进行可视化检查:
plt.figure(figsize=(20, 10))
_ = plt.hist(decisionScores_train_anomaly, bins=50)
以下图表提供了输出结果:
- 选择一个截断点,以将异常数据与正常数据分开:
cutoff = 0
- 在测试集上检查此截断点:
print(Counter(y_test))
print(Counter(y_test[cutoff > IF.decision_function(X_test)]))
以下是输出结果:
Counter({0: 11775, 1: 597})
Counter({1: 595, 0: 85})
它是如何工作的…
我们首先将KDD cup
数据集读取到数据框中。接下来,在步骤 2中,我们检查数据,发现大多数流量是正常的,正如预期的那样,但少量是异常的。显然,这个问题是高度不平衡的。因此,这个问题非常适合使用异常检测方法。在步骤 3和步骤 5中,我们将所有非正常流量转化为一个单一类别,即异常。
我们还确保计算异常值与正常观察值的比率(步骤 4),即污染参数。这是设置隔离森林灵敏度的参数之一。这个步骤是可选的,但可能会提高性能。我们在步骤 6中将数据集分为正常和异常观察值,同时将数据集分为正常和异常数据的训练集和测试集(步骤 7)。我们实例化一个隔离森林分类器,并设置其污染参数(步骤 8)。论文《Isolation Forest》中推荐使用的默认参数n_estimators
和max_samples
由 Liu 等人提出。在步骤 9和步骤 10中,我们使用隔离森林的决策函数为正常训练集提供分数,然后在图表中检查结果。在步骤 11中,我们类似地为异常训练集提供分数。
知道决策函数是衡量一个点描述简单程度的指标后,我们希望通过选择一个数值截断点,将简单点与复杂点分离开来,从而获得明显的区分。通过可视化检查,建议选择在步骤 12中选定的值。
最后,我们可以使用模型进行预测并评估其性能。在步骤 13中,我们看到模型能够捕捉到大量异常,而不会触发过多的假阳性(正常流量的实例),按比例而言。
僵尸网络流量检测
僵尸网络是由被攻击的互联网设备组成的网络。僵尸网络可用于执行分布式拒绝服务攻击(DDoS 攻击)、窃取数据、发送垃圾邮件,以及其他各种创意恶意用途。僵尸网络可以造成巨大的损害。例如,在写作时,快速搜索“僵尸网络”一词显示,3 天前,Electrum 僵尸网络窃取了 460 万美元的加密货币。在本教程中,我们构建一个分类器来检测僵尸网络流量。
使用的数据集是一个处理过的子集,名为CTU-13,包含了 2011 年捷克 CTU 大学捕获的僵尸网络流量。该数据集包含大量真实的僵尸网络流量,混合了正常流量和背景流量。
准备工作
本教程的准备工作包括在pip
中安装scikit-learn
。命令如下:
pip install sklearn
此外,解压CTU13Scenario1flowData.7z
。要反序列化CTU13Scenario1flowData.pickle
文件,需要使用 Python 2:
如何操作…
- 首先读取已序列化的数据:
import pickle
file = open('CTU13Scenario1flowData.pickle', 'rb')
botnet_dataset = pickle.load(file)
- 数据已分割为训练集和测试集,你只需将它们分别分配给各自的变量:
X_train, y_train, X_test, y_test = (
botnet_dataset[0],
botnet_dataset[1],
botnet_dataset[2],
botnet_dataset[3],
)
- 使用默认参数实例化决策树分类器:
from sklearn.tree import *
clf = DecisionTreeClassifier()
- 将分类器拟合到训练数据:
clf.fit(X_train, y_train)
- 在测试集上进行测试:
clf.score(X_test, y_test)
以下是输出:
0.9991001799640072
它是如何工作的…
我们从步骤 1开始,通过反序列化数据来加载它。数据集已经经过预处理以确保平衡,因此我们无需担心数据不平衡的问题。实际上,检测僵尸网络可能需要满足对假阳性率的限制。接下来,我们利用已经预定义的训练集-测试集划分来分割数据(步骤 2)。现在我们可以实例化分类器,将其拟合到数据上,然后进行测试(步骤 3和步骤 5)。从准确率来看,我们看到它相当高。由于数据集已经平衡,我们无需担心我们的指标会产生误导。一般来说,检测僵尸网络是一个具有挑战性的任务。僵尸网络的检测难度可以通过 GameOver Zeus 僵尸网络恶意软件包来说明。该僵尸网络最初在 2007 年被发现,运营了超过三年,最终导致约 7000 万美元的资金被盗,并导致 FBI 在 2010 年逮捕了 100 多名嫌疑人。直到 2012 年 3 月,微软才宣布它已经能够关闭该僵尸网络的大部分指挥与控制(C&C)服务器。
内部威胁检测
内部威胁是雇主面临的一个复杂且日益严峻的挑战。通常定义为员工采取的任何可能对组织造成危害的行为。这些行为可能包括未经授权的数据传输或资源破坏等。内部威胁可能以各种新颖形式表现出来,动机各异,从不满的员工破坏雇主声誉,到高级持续性威胁(APT)等。
卡内基梅隆大学软件工程研究所(CERT 项目)的内部风险数据库包含了最大的公开红队情景档案。该模拟通过将真实世界的内部风险案例与从防御公司秘密获得的实际中立客户结合而构建。该数据集代表了一家单一工程公司数月的流量,涵盖了互联网、电话、登录、文件夹和系统访问(dtaa.com)。模拟公司雇佣了数千名员工,每人每天平均执行 1,000 次登录活动。数据中描绘了几种威胁情景,例如泄密者、小偷和破坏者。该问题的一个显著特点是其非常低的信噪比,无论是表现为恶意用户的总数、频繁的计数,还是总体使用情况。
我们进行的分析基于 CERT 内部威胁情景(v.4.2),特别是因为它代表了一个密集的针形数据集,这意味着它具有高频次的攻击发生。
攻击的基本计划是,首先手工创建新特征,例如是否向外部人员发送了邮件或登录是否发生在非工作时间。接下来的想法是为每个用户提取一个多变量时间序列。这个时间序列将由一系列向量组成,每个向量构成一天中我们手工创建的特征发生的次数计数。因此,我们输入数据集的形状将如下所示:
(每天检查的用户数,每天检查的总特征数,时间序列中的天数)。
然后我们将展平每个用户的时间序列,并利用隔离森林来检测异常。
内部威胁检测的特征工程
通常情况下,当一个机器学习解决方案不依赖端到端的深度学习时,通过创建富有洞见和信息性的特征可以提高性能。在这个示例中,我们将为内部威胁检测构建几个有前途的新特征。
准备工作
准备工作包括在pip
中安装pandas
。命令如下:
pip install pandas
另外,从以下链接下载 CERT 内部威胁数据集:ftp://ftp.sei.cmu.edu/pub/cert-data/r4.2.tar.bz2。有关数据集的更多信息以及答案,请访问resources.sei.cmu.edu/library/asset-view.cfm?assetid=508099
。
如何做…
在接下来的步骤中,您将为 CERT 内部威胁数据集构建新特征:
- 导入
numpy
和pandas
,并指向下载数据的位置:
import numpy as np
import pandas as pd path_to_dataset = "./r42short/"
- 指定
.csv
文件及其要读取的列:
log_types = ["device", "email", "file", "logon", "http"]
log_fields_list = [
["date", "user", "activity"],
["date", "user", "to", "cc", "bcc"],
["date", "user", "filename"],
["date", "user", "activity"],
["date", "user", "url"],
]
- 我们将手工创建一些特征并对其进行编码,从而创建一个字典来跟踪这些特征。
features = 0
feature_map = {}
def add_feature(name):
"""Add a feature to a dictionary to be encoded."""
if name not in feature_map:
global features
feature_map[name] = features
features += 1
- 将要使用的特征添加到我们的字典中:
add_feature("Weekday_Logon_Normal")
add_feature("Weekday_Logon_After")
add_feature("Weekend_Logon")
add_feature("Logoff")
add_feature("Connect_Normal")
add_feature("Connect_After")
add_feature("Connect_Weekend")
add_feature("Disconnect")
add_feature("Email_In")
add_feature("Email_Out")
add_feature("File_exe")
add_feature("File_jpg")
add_feature("File_zip")
add_feature("File_txt")
add_feature("File_doc")
add_feature("File_pdf")
add_feature("File_other")
add_feature("url")
- 定义一个函数来记录被复制到可移动介质的文件类型:
def file_features(row):
"""Creates a feature recording the file extension of the file used."""
if row["filename"].endswith(".exe"):
return feature_map["File_exe"]
if row["filename"].endswith(".jpg"):
return feature_map["File_jpg"]
if row["filename"].endswith(".zip"):
return feature_map["File_zip"]
if row["filename"].endswith(".txt"):
return feature_map["File_txt"]
if row["filename"].endswith(".doc"):
return feature_map["File_doc"]
if row["filename"].endswith(".pdf"):
return feature_map["File_pdf"]
else:
return feature_map["File_other"]
- 定义一个函数来识别员工是否向非公司邮箱发送了邮件:
def email_features(row):
"""Creates a feature recording whether an email has been sent externally."""
outsider = False
if not pd.isnull(row["to"]):
for address in row["to"].split(";"):
if not address.endswith("dtaa.com"):
outsider = True
if not pd.isnull(row["cc"]):
for address in row["cc"].split(";"):
if not address.endswith("dtaa.com"):
outsider = True
if not pd.isnull(row["bcc"]):
for address in row["bcc"].split(";"):
if not address.endswith("dtaa.com"):
outsider = True
if outsider:
return feature_map["Email_Out"]
else:
return feature_map["Email_In"]
- 定义一个函数来记录员工是否在非工作时间使用可移动介质:
def device_features(row):
"""Creates a feature for whether the user has connected during normal hours or otherwise."""
if row["activity"] == "Connect":
if row["date"].weekday() < 5:
if row["date"].hour >= 8 and row["date"].hour < 17:
return feature_map["Connect_Normal"]
else:
return feature_map["Connect_After"]
else:
return feature_map["Connect_Weekend"]
else:
return feature_map["Disconnect"]
- 定义一个函数来记录员工是否在非工作时间登录到计算机:
def logon_features(row):
"""Creates a feature for whether the user logged in during normal hours or otherwise."""
if row["activity"] == "Logon":
if row["date"].weekday() < 5:
if row["date"].hour >= 8 and row["date"].hour < 17:
return feature_map["Weekday_Logon_Normal"]
else:
return feature_map["Weekday_Logon_After"]
else:
return feature_map["Weekend_Logon"]
else:
return feature_map["Logoff"]
- 我们不会利用员工访问的 URL 中包含的信息:
def http_features(row):
"""Encodes the URL visited."""
return feature_map["url"]
- 我们仅保留事件发生的日期,而不是完整的时间戳:
def date_to_day(row):
"""Converts a full datetime to date only."""
day_only = row["date"].date()
return day_only
- 我们循环遍历包含日志的
.csv
文件,并将它们读入 pandas 数据框中:
log_feature_functions = [
device_features,
email_features,
file_features,
logon_features,
http_features,
]
dfs = []
for i in range(len(log_types)):
log_type = log_types[i]
log_fields = log_fields_list[i]
log_feature_function = log_feature_functions[i]
df = pd.read_csv(
path_to_dataset + log_type + ".csv", usecols=log_fields, index_col=None
)
- 将
date
数据转换为pandas
时间戳:
date_format = "%m/%d/%Y %H:%M:%S"
df["date"] = pd.to_datetime(df["date"], format=date_format)
- 创建上述定义的新特征,然后丢弃除日期、用户和我们的新特征之外的所有特征:
new_feature = df.apply(log_feature_function, axis=1)
df["feature"] = new_feature
cols_to_keep = ["date", "user", "feature"]
df = df[cols_to_keep]
- 将日期转换为只有一天:
df["date"] = df.apply(date_to_day, axis=1)
dfs.append(df)
- 将所有数据框连接成一个,并按
date
排序:
joint = pd.concat(dfs)
joint = joint.sort_values(by="date")
工作原理...
首先导入pandas
和numpy
并创建一个指向数据集的变量(步骤 1)。CERT 提供了多个数据集。4.2 版本之所以与众不同,是因为它是一个密集的“针”数据集,意味着它比其他数据集有更高的内部威胁发生率。由于数据集非常庞大,因此在实验阶段,至少要对其进行过滤和下采样,以便更方便地处理数据,因此我们在步骤 2中这样做。在接下来的步骤中,我们将手动设计一些特征,认为这些特征有助于我们的分类器识别内部威胁。在步骤 3中,我们创建一个便捷的函数来编码特征,以便一个字典可以跟踪这些特征。我们在步骤 4中提供将要添加的特征名称。在步骤 5中,我们创建一个特征,用于跟踪复制到可移动介质中的文件类型。推测这可能表明数据泄露犯罪行为。在步骤 6中,我们创建一个特征,跟踪员工是否曾给外部实体发送过电子邮件。我们又创建了一个特征,用于跟踪员工是否在非工作时间使用过可移动存储设备(步骤 7)。
一个附加特征用于跟踪员工是否在非工作时间登录过设备(步骤 8)。为了简化处理,我们没有利用员工访问的 URL(步骤 9),尽管这些可能暗示恶意行为。
接下来,我们通过仅使用日期(步骤 10),而不是在特征化数据中使用完整的时间戳,来简化数据。在步骤 11中,我们将数据读取到一个 pandas 数据框中。然后,在步骤 12中,我们编辑当前的日期格式,以适应 pandas,并收集所有新的特征,同时丢弃旧的特征(步骤 13)。在步骤 14中,我们将数据转换为一个时间序列,其增量为单天。最后,在步骤 15中,我们将所有数据汇总到一个大型的已排序数据框中。我们现在已经完成了特征工程阶段的第一次迭代。为了提升性能并添加特征,你可以朝着多个方向努力。这些方向包括观察电子邮件文本中的负面情绪,并使用心理测量学分析个性。
使用异常检测来识别内部威胁
在设计出有前景的新特征后,我们的下一步是进行训练集和测试集划分,将数据处理成方便的时间序列形式,然后进行分类。我们的训练集和测试集将是数据集的两个时间半部分。通过这种方式,我们可以轻松确保训练输入的形状与测试输入的形状相同,从而避免在评估中作弊。
准备工作
这个配方的准备工作包括在pip
中安装scikit-learn
、pandas
和matplotlib
。命令如下:
pip install sklearn pandas matplotlib
在准备这个配方时,你需要加载前一个配方中的数据框(或者直接从前一个配方的结束部分继续)。
如何操作...
在接下来的步骤中,你将把特征化数据转换为时间序列集合,并使用孤立森林检测犯罪:
- 列出所有威胁行为者,为创建标签做准备:
threat_actors = [
"AAM0658",
"AJR0932",
"BDV0168",
<snip>
"MSO0222",
]
- 然后我们对日期进行索引:
start_date = joint["date"].iloc[0]
end_date = joint["date"].iloc[-1]
time_horizon = (end_date - start_date).days + 1
def date_to_index(date):
"""Indexes dates by counting the number of days since the starting date of the dataset."""
return (date - start_date).days
- 定义一个函数来提取给定用户的时间序列信息:
def extract_time_series_by_user(user_name, df):
"""Filters the dataframe down to a specific user."""
return df[df["user"] == user_name]
- 定义一个函数来向量化用户的时间序列信息:
def vectorize_user_time_series(user_name, df):
"""Convert the sequence of features of a user to a vector-valued time series."""
user_time_series = extract_time_series_by_user(user_name, df)
x = np.zeros((len(feature_map), time_horizon))
event_date_indices = user_time_series["date"].apply(date_to_index).to_numpy()
event_features = user_time_series["feature"].to_numpy()
for i in range(len(event_date_indices)):
x[event_features[i], event_date_indices[i]] += 1
return x
- 定义一个函数来向量化所有用户特征的时间序列:
def vectorize_dataset(df):
"""Takes the dataset and featurizes it."""
users = set(df["user"].values)
X = np.zeros((len(users), len(feature_map), time_horizon))
y = np.zeros((len(users)))
for index, user in enumerate(users):
x = vectorize_user_time_series(user, df)
X[index, :, :] = x
y[index] = int(user in threat_actors)
return X, y
- 向量化数据集:
X, y = vectorize_dataset(joint)
- 对向量化数据进行训练-测试集划分:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y)
- 重新调整向量化数据的形状:
X_train_reshaped = X_train.reshape(
[X_train.shape[0], X_train.shape[1] * X_train.shape[2]]
)
X_test_reshaped = X_test.reshape([X_test.shape[0], X_test.shape[1] * X_test.shape[2]])
- 将训练集和测试集数据拆分为威胁和非威胁子集:
X_train_normal = X_train_reshaped[y_train == 0, :]
X_train_threat = X_train_reshaped[y_train == 1, :]
X_test_normal = X_test_reshaped[y_test == 0, :]
X_test_threat = X_test_reshaped[y_test == 1, :]
- 定义并实例化孤立森林分类器:
from sklearn.ensemble import IsolationForest
contamination_parameter = 0.035
IF = IsolationForest(
n_estimators=100, max_samples=256, contamination=contamination_parameter
)
- 将孤立森林分类器拟合到训练数据:
IF.fit(X_train_reshaped)
- 绘制训练数据中正常子集的决策分数:
normal_scores = IF.decision_function(X_train_normal)
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8, 4), dpi=600, facecolor="w", edgecolor="k")
normal = plt.hist(normal_scores, 50, density=True)
plt.xlim((-0.2, 0.2))
plt.xlabel("Anomaly score")
plt.ylabel("Percentage")
plt.title("Distribution of anomaly score for non threats")
请查看以下截图:
- 对训练数据中的威胁行为者执行相同操作:
anomaly_scores = IF.decision_function(X_train_threat)
fig = plt.figure(figsize=(8, 4), dpi=600, facecolor="w", edgecolor="k")
anomaly = plt.hist(anomaly_scores, 50, density=True)
plt.xlim((-0.2, 0.2))
plt.xlabel("Anomaly score")
plt.ylabel("Percentage")
plt.title("Distribution of anomaly score for threats")
请查看以下截图:
- 选择一个截断分数:
cutoff = 0.12
- 观察截断在训练数据上的结果:
from collections import Counter
s = IF.decision_function(X_train_reshaped)
print(Counter(y_train[cutoff > s]))
以下是输出结果:
Counter({0.0: 155, 1.0: 23})
- 测量截断选择在测试集上的结果:
s = IF.decision_function(X_test_reshaped)
print(Counter(y_test[cutoff > s]))
以下是输出结果:
Counter({0.0: 46, 1.0: 8})
它是如何工作的…
在前面的食谱中完成特征工程阶段后,我们继续创建了一个模型。在第 1 步中,我们列出了所有的威胁行为者,为接下来的步骤做准备。在第 2 步中,我们为日期创建了索引,使得0
对应起始日期,1
对应第二天,依此类推。在随后的第 3 步和第 5 步中,我们定义了函数来读取整个数据集的时间序列,将其过滤到每个用户,然后为每个用户向量化时间序列。接着我们向量化了数据集(第 6 步),并进行了训练-测试集划分(第 7 步)。我们在第 8 步中重新调整了数据形状,以便能够将其输入到孤立森林分类器中。我们进一步将数据拆分为良性和威胁子集(第 9 步),以便调整我们的参数。在第 10 步中,我们实例化了孤立森林分类器,然后在第 11 步中将其拟合到数据上。对于我们的污染参数,我们使用了一个值,对应威胁与良性行为者的比例。
在接下来的三步(第 12 步 - 第 14 步)中,我们检查了孤立森林在良性和威胁行为者上的决策分数,并通过检查得出结论,截断值 0.12 能够检测到大比例的威胁行为者,而不会标记太多良性行为者。最后,在第 15 步和第 16 步中评估我们的性能时,我们发现有一些假阳性,但也检测到了相当数量的内部威胁。由于比例不是很高,因此分类器对于告知分析员潜在威胁非常有帮助。
检测 DDoS
DDoS,即分布式拒绝服务攻击,是一种攻击方式,其中来自不同来源的流量洪水般涌向目标,导致服务中断。DDoS 攻击有许多类型,通常分为三大类:应用层攻击、协议攻击和流量攻击。目前,很多 DDoS 防御仍是手动进行的。通过识别并阻止特定的 IP 地址或域名来进行防御。随着 DDoS 攻击的机器人变得越来越复杂,这种方法正在逐渐过时。机器学习为此提供了一个有前景的自动化解决方案。
我们将使用的数据集是对 CSE-CIC-IDS2018、CICIDS2017 和 CIC DoS 数据集(2017 年)的子采样。它由 80%的正常流量和 20%的 DDoS 流量组成,以代表更为现实的正常流量与 DDoS 流量的比例。
准备工作
本配方的准备工作包括在pip
中安装几个包,分别是scikit-learn
和pandas
。命令如下:
pip install sklearn pandas
为了准备这个配方,提取归档文件ddos_dataset.7z
。
如何实现…
在接下来的步骤中,我们将训练一个随机森林分类器来检测 DDoS 流量:
- 导入
pandas
并指定您将在代码中读取的列的数据类型:
import pandas as pd
features = [
"Fwd Seg Size Min",
"Init Bwd Win Byts",
"Init Fwd Win Byts",
"Fwd Seg Size Min",
"Fwd Pkt Len Mean",
"Fwd Seg Size Avg",
"Label",
"Timestamp",
]
dtypes = {
"Fwd Pkt Len Mean": "float",
"Fwd Seg Size Avg": "float",
"Init Fwd Win Byts": "int",
"Init Bwd Win Byts": "int",
"Fwd Seg Size Min": "int",
"Label": "str",
}
date_columns = ["Timestamp"]
- 读取包含数据集的
.csv
文件:
df = pd.read_csv("ddos_dataset.csv", usecols=features, dtype=dtypes,parse_dates=date_columns,index_col=None)
- 按日期对数据进行排序:
df2 = df.sort_values("Timestamp")
- 删除日期列,因为它不再需要:
df3 = df2.drop(columns=["Timestamp"])
- 将数据分割为训练集和测试集,分别由数据的前 80%和后 20%组成:
l = len(df3.index)
train_df = df3.head(int(l * 0.8))
test_df = df3.tail(int(l * 0.2))
- 准备标签:
y_train = train_df.pop("Label").values
y_test = test_df.pop("Label").values
- 准备特征向量:
X_train = train_df.values
X_test = test_df.values
- 导入并实例化一个随机森林分类器:
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier(n_estimators=50)
- 将随机森林模型拟合到训练数据,并在测试数据上进行评分:
clf.fit(X_train, y_train)
clf.score(X_test, y_test)
以下是输出结果:
0.83262
它是如何工作的…
由于数据集较大,即便是导入它也会消耗大量计算资源。因此,我们在步骤 1中通过指定我们认为最有前景的特征子集以及记录它们的数据类型来开始操作,这样我们就不需要后续再转换它们。然后,在步骤 2中,我们将数据读取到数据框中。在步骤 3和4中,我们按日期对数据进行排序,因为问题要求能够预测未来的事件,并且随后删除日期列,因为我们不再需要它。在接下来的两步中,我们进行训练-测试数据集划分,同时考虑到时间进展。然后,我们在步骤 8和9中实例化、拟合并测试一个随机森林分类器。根据应用场景,得到的准确性是一个不错的起点。为了提升性能,一个有前景的方向是考虑源 IP 和目的 IP。直观来说,连接的来源应该对它是否属于 DDoS 攻击有重要影响。
信用卡欺诈检测
信用卡公司必须监控欺诈交易,以避免客户被错误收费。这样的数据非常不平衡,我们在本章中将使用的数据集,其中欺诈交易仅占总交易的 0.172%。它只包含数字输入变量,这些变量是通过 PCA 变换得到的,还有Time和Amount特征。Time特征包含每笔交易与数据集中第一笔交易之间经过的秒数。Amount特征是交易金额,这是我们将在成本敏感学习中使用的一个特征。Class特征是响应参数,在欺诈情况下,其值为1
,否则为0
。
那么什么是示例相关的、成本敏感的学习呢?考虑每种分类类型相关的成本。如果程序没有识别出欺诈交易,钱就会被浪费,并且持卡人必须获得全额退款。如果程序认为支付是欺诈性的,交易将被停止。在这种情况下,由于需要联系持卡人,且卡需要被替换(如果交易被正确标记为欺诈),或者重新激活(如果交易实际上是合法的),就会产生行政成本。为了简化起见,假设行政成本总是相同的。如果系统认为交易是有效的,那么交易将自动接受,并且不会收取费用。这就导致了每种预测场景相关的以下成本:
欺诈 y = 1 | 良性 y = 0 | |
---|---|---|
预测的欺诈 y_pred = 1 | 真阳性成本 = 行政成本 | 假阳性成本 = 行政成本 |
预测的良性 y_pred = 0 | 假阴性成本 = 交易金额 | 真阴性成本 = $0 |
与大多数场景不同,我们的兴趣在于最小化总成本,基于上述考虑,而不是准确性、精确度或召回率。
准备工作
为了准备这个配方,需要在pip
中安装scikit-learn
、pandas
和matplotlib
,以及一个名为costcla
的新包。命令如下:
pip install sklearn pandas matplotlib costcla
为了准备这个配方,从www.kaggle.com/mlg-ulb/creditcardfraud/downloads/creditcardfraud.zip/3
下载信用卡交易数据集(开放数据库许可证)。
如何操作……
在接下来的步骤中,我们将使用costcla
库在信用卡交易数据上构建一个示例相关的、成本敏感的分类器:
- 导入
pandas
并将与交易相关的数据读取到数据框中:
import pandas as pd
fraud_df = pd.read_csv("FinancialFraudDB.csv", index_col=None)
- 为
假
正例和假
负例设置成本:
card_replacement_cost = 5
customer_freeze_cost = 3
- 定义一个对应于图表的成本矩阵:
import numpy as np
cost_matrix = np.zeros((len(fraud_df.index), 4))
cost_matrix[:, 0] = customer_freeze_cost * np.ones(len(fraud_df.index))
cost_matrix[:, 1] = fraud_df["Amount"].values
cost_matrix[:, 2] = card_replacement_cost * np.ones(len(fraud_df.index))
- 创建标签和特征矩阵:
y = fraud_df.pop("Class").values
X = fraud_df.values
- 创建训练集和测试集的划分:
from sklearn.model_selection import train_test_split
sets = train_test_split(X, y, cost_matrix, test_size=0.25, random_state=11)
X_train, X_test, y_train, y_test, cost_matrix_train, cost_matrix_test = sets
- 导入决策树,将其拟合到训练数据上,然后在测试集上进行预测:
from sklearn import tree
y_pred_test_dt = tree.DecisionTreeClassifier().fit(X_train, y_train).predict(X_test)
- 导入成本敏感决策树,将其拟合到训练数据上,然后在测试集上进行预测:
from costcla.models import CostSensitiveDecisionTreeClassifier
y_pred_test_csdt = CostSensitiveDecisionTreeClassifier().fit(X_train, y_train, cost_matrix_train).predict(X_test)
- 计算两个模型的节省得分:
from costcla.metrics import savings_score
print(savings_score(y_test, y_pred_test_dt, cost_matrix_test))
print(savings_score(y_test, y_pred_test_csdt, cost_matrix_test))
以下是输出结果:
0.5231523713991505 0.5994028394464614
它是如何工作的…
第一步是简单地加载数据。在第 2 步中,我们根据更换信用卡的预期成本设置了行政成本。此外,我们还估算了冻结客户银行操作直到所有交易被验证的商业成本。在实践中,你应该获取一个准确的数字,适合具体的信用卡公司或业务用例。使用我们定义的参数,在第 3 步中,我们定义了一个成本矩阵,考虑了更换信用卡的行政成本、冻结客户带来的业务中断等因素。在第 4 步和第 5 步中,我们进行数据的训练-测试拆分。接下来,我们想看看这个依赖于示例的、成本敏感的分类器与普通分类器相比表现如何。为此,我们实例化一个简单的分类器,进行训练,并在第 6 步中使用它对测试集进行预测,然后在第 7 步中使用costcla
库中的成本敏感随机森林模型进行相同的操作。最后,在第 8 步中,我们利用costcla
中的savings_score
函数,基于成本矩阵计算使用y_pred
对y_true
的节省成本。数字越高,节省的成本越大。因此,我们可以看到,成本敏感的随机森林模型优于普通模型。
伪造钞票检测
伪造货币是未经国家或政府合法批准的货币,通常是为了模仿合法货币并欺骗其使用者。在这个教程中,你将训练一个机器学习分类器来区分真实和伪造的钞票。
准备中
本教程的准备工作包括在pip
中安装scikit-learn
和pandas
。命令如下:
pip install sklearn pandas
在准备本教程时,从 UCI 机器学习库下载钞票鉴定数据集:archive.ics.uci.edu/ml/datasets/banknote+authentication
。
如何操作...
在接下来的步骤中,你将下载一个标注过的伪造和合法钞票数据集,并构建一个分类器来检测伪造货币:
-
获取真实和伪造的钞票标注数据集。
-
使用
pandas
读取钞票数据集:
import pandas as pd
df = pd.read_csv("data_banknote_authentication.txt", header=None)
df.columns = ["0", "1", "2", "3", "label"]
以下是输出结果:
feature 1 feature 2 feature 3 feature 4 label
0 3.62160 8.6661 -2.8073 -0.44699 0
1 4.54590 8.1674 -2.4586 -1.46210 0
2 3.86600 -2.6383 1.9242 0.10645 0
3 3.45660 9.5228 -4.0112 -3.59440 0
4 0.32924 -4.4552 4.5718 -0.98880 0
- 创建训练-测试拆分:
from sklearn.model_selection import train_test_split
df_train, df_test = train_test_split(df)
- 将特征和标签收集到数组中:
y_train = df_train.pop("label").values
X_train = df_train.values
y_test = df_test.pop("label").values
X_test = df_test.values
- 实例化一个随机森林分类器:
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier()
- 训练并测试分类器:
clf.fit(X_train, y_train)
print(clf.score(X_test, y_test))
以下是输出结果:
0.9825072886297376
它是如何工作的...
解决伪造问题的最大潜力在于获取大量的图像数据集并使用深度学习技术。然而,在数据集相对较小的情况下(如本案例所示),特征工程是必不可少的。我们通过加载并读取数据集到 pandas 中来开始解决问题(步骤 1 和 步骤 2)。在这个数据集的情况下,使用了小波变换工具从图像中提取特征。接下来,在步骤 3 和 步骤 4 中,我们将数据进行训练-测试分割并汇总成数组。最后,在步骤 5 和 步骤 6 中,我们对数据集进行了基础分类器的训练和测试。高达 98% 的得分表明,从该数据集中提取的特征确实能够区分真实与伪造的钞票。
使用机器学习进行广告屏蔽
广告屏蔽是指在网页浏览器或应用程序中移除或更改在线广告。在这个食谱中,你将利用机器学习检测广告,以便能够屏蔽广告,畅快无忧地浏览!
准备工作
准备这个食谱时,需要在 pip
中安装 scikit-learn
和 pandas
。命令如下:
pip install sklearn pandas
为了准备这个食谱,从 UCI 机器学习库下载互联网广告数据集:archive.ics.uci.edu/ml/datasets/internet+advertisements
。
如何实现……
以下步骤展示了如何使用机器学习实现广告屏蔽:
-
收集互联网广告的数据集。
-
使用
pandas
导入数据到数据框中:
import pandas as pd
df = pd.read_csv("ad.data", header=None)
df.rename(columns={1558: "label"}, inplace=True)
- 数据存在脏数据问题,即有缺失值。让我们找出所有缺失值的行:
improper_rows = []
for index, row in df.iterrows():
for col in df.columns:
val = str(row[col]).strip()
if val == "?":
improper_rows.append(index)
- 在当前的情况下,删除缺失值的行是合理的,如下代码所示:
df = df.drop(df.index[list(set(improper_rows))])
- 将标签转换为数值形式:
def label_to_numeric(row):
"""Binarize the label."""
if row["label"] == "ad.":
return 1
else:
return 0
df["label"] = df.apply(label_to_numeric, axis=1)
- 将数据分为训练数据和测试数据:
from sklearn.model_selection import train_test_split
df_train, df_test = train_test_split(df)
- 将数据分配到特征数组和标签数组中:
y_train = df_train.pop("label").values
y_test = df_test.pop("label").values
X_train = df_train.values
X_test = df_test.values
- 实例化一个随机森林分类器并进行训练:
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier()
clf.fit(X_train, y_train)
- 在测试数据上对分类器进行评分:
clf.score(X_test, y_test)
以下是输出结果:
0.9847457627118644
它是如何工作的……
我们通过导入数据集来开始阻止不必要广告的步骤。我们在这个食谱中使用的数据已经经过了特征工程处理。在步骤 2 中,我们将数据导入数据框。查看数据,我们发现它由 1,558 个数值特征和一个广告或非广告标签组成:
特征编码了图像的几何形状、URL 中的句子、图像的 URL、alt 文本、锚文本和靠近锚文本的单词。我们的目标是预测图像是否为广告(ad)或不是(non-ad)。我们通过在步骤3 和4中删除缺失值的行来清理数据。通常,使用其他技术来填补缺失值是有意义的,比如使用平均值或最常见的值。继续到步骤5,我们将目标转换为数字形式。然后,在步骤6 和7中,我们将数据进行训练集和测试集的拆分,为学习做好准备。最后,在步骤8 和9中,我们在数据上训练并测试一个基本分类器。结果表明,这些特征确实提供了很高的区分能力。
最近的方法利用深度学习处理屏幕图像来应对广告。这个方法非常有前景,但由于深度学习对对抗性攻击的敏感性,到目前为止并未成功。随着对抗攻击的鲁棒性在该领域的提升,基于深度学习的广告拦截器可能会变得普及。
无线室内定位
黑客停在某个住宅外,恶意入侵他们的网络的故事堪称传奇。虽然这些故事可能夸大了此情景的易得性和动机,但在许多情况下,最佳做法是仅允许家中的用户,或者在企业环境中,仅允许特定区域的用户拥有指定的网络权限。在这个案例中,您将利用机器学习基于 Wi-Fi 信号来定位一个实体。我们将使用的数据集是在室内空间收集的,数据通过观察智能手机上可见的七个 Wi-Fi 信号的信号强度获得。四个房间中的一个是决策因素。
准备工作
本案例的准备工作包括安装 scikit-learn
和 pandas
。在您的 Python 环境中,运行以下命令:
pip install sklearn pandas
为了准备本案例,下载 UCI 机器学习库中的无线室内定位数据集:archive.ics.uci.edu/ml/datasets/Wireless+Indoor+Localization.
如何操作…
要利用机器学习基于 Wi-Fi 信号定位一个实体,请按照以下步骤操作:
-
从感兴趣区域的不同位置收集 Wi-Fi 信号强度的数据集。
-
使用
pandas
将数据加载到数据框中:
import pandas as pd
df = pd.read_csv("wifi_localization.txt", sep="\t", header=None)
df = df.rename(columns={7: "room"})
- 将数据框分割为训练集和测试集:
from sklearn.model_selection import train_test_split
df_train, df_test = train_test_split(df)
- 将特征和标签分配到数组中:
y_train = df_train.pop("room").values
y_test = df_test.pop("room").values
X_train = df_train.values
X_test = df_test.values
- 实例化一个随机森林分类器:
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier()
- 将分类器拟合到训练数据上:
clf.fit(X_train, y_train)
- 在测试数据集上进行预测并打印出混淆矩阵:
y_pred = clf.predict(X_test)
from sklearn.metrics import confusion_matrix
print(confusion_matrix(y_test, y_pred))
以下输出显示了我们的混淆矩阵:
[[124 0 0 0]
[ 0 124 4 0]
[ 0 2 134 0]
[ 1 0 0 111]]
工作原理…
第一步 是从感兴趣区域的不同位置收集 Wi-Fi 信号强度的数据集。这是一个相对容易完成的任务,只需要带着启用 GPS 功能的手机走遍一个房间,并运行一个脚本记录 Wi-Fi 的信号强度。在 第二步 中,我们将数据读取到数据框中,然后将目标列重命名为 room
,这样我们就知道它指的是什么。接下来,在 第三步 中,我们将数据进行训练测试集拆分,为学习做准备。我们将特征和标签拆分成数组(第四步)。最后,在 第五步 和第六步 中,我们训练并测试一个基本的分类器。观察到模型的表现非常优秀。这表明,基于设备能够接收到的 Wi-Fi 信号强度进行定位并不是一项困难的任务,只要该区域已经被预先学习过。
第七章:使用机器学习保护和攻击数据
在本章中,我们将学习如何利用机器学习(ML)来保护和攻击数据。我们将介绍如何使用机器学习评估密码强度,以及如何利用深度学习破解密码。同样,我们还会介绍如何利用隐写术将信息藏匿在明面上,以及如何使用机器学习检测隐写术。此外,我们还将将机器学习与硬件安全结合,利用 AI 攻击物理不可克隆函数(PUF)。
在本章中,我们将涵盖以下内容:
-
使用机器学习评估密码安全性
-
使用深度学习进行密码破解
-
深度隐写术
-
基于机器学习的隐写分析
-
针对 PUF 的机器学习攻击
-
使用深度学习进行加密
-
HIPAA 数据泄露——数据探索与可视化
技术要求
在本章中,我们将使用以下技术:
-
PyTorch
-
TensorBoardX
-
XGBoost
-
scikit-learn
-
pandas
-
TensorFlow
-
Keras
-
Octave
本章的代码和数据集可以在github.com/PacktPublishing/Machine-Learning-for-Cybersecurity-Cookbook/tree/master/Chapter07
找到。
使用机器学习评估密码安全性
密码破解是系统化地发现安全系统密码的过程。破解可以包括使用常见密码、巧妙生成的候选密码(例如,将字母 O 替换为数字 0 或将单词反向书写),或直接使用暴力穷举搜索。为了使密码更难破解,必须选择一个强密码。
准备工作
为了准备这个配方,我们需要在pip
中安装pandas
、sklearn
和xgboost
。使用以下代码进行安装:
pip install pandas sklearn xgboost
此外,解压归档数据集,即PasswordDataset.7z
。
如何操作…
在接下来的步骤中,我们将读取一个包含密码及其强度标签的数据集,并构建一个分类器来评估密码强度。让我们开始吧:
- 导入
pandas
并将密码读取到数据框中:
import pandas as pd
df = pd.read_csv(
"passwordDataset.csv", dtype={"password": "str", "strength": "int"}, index_col=None
)
- 随机打乱数据:
df = df.sample(frac=1)
- 将数据框拆分为两个独立的数据框,一个用于训练,一个用于测试:
l = len(df.index)
train_df = df.head(int(l * 0.8))
test_df = df.tail(int(l * 0.2))
- 创建所需的标签和特征数据:
y_train = train_df.pop("strength").values
y_test = test_df.pop("strength").values
X_train = train_df.values.flatten()
X_test = test_df.values.flatten()
- 定义一个函数,将字符串拆分为其字符:
def character_tokens(input_string):
"""Break string into characters."""
return [x for x in input_string]
- 创建一个管道,对密码的字符进行 TF-IDF 处理,然后进行梯度提升:
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import TfidfVectorizer
from xgboost import XGBClassifier
password_clf = Pipeline(
[("vect", TfidfVectorizer(tokenizer=character_tokens)), ("clf", XGBClassifier()),]
)
- 训练和测试管道:
password_clf.fit(X_train, y_train)
password_clf.score(X_test, y_test)
以下是输出结果:
0.9137365878426307
- 设置一个变量为常用密码,另一个变量为计算机生成的高熵密码:
common_password = "qwerty"
strong_computer_generated_password = "c9lCwLBFmdLbG6iWla4H"
- 检查分类器对两个密码强度的预测:
password_clf.predict([common_password, strong_computer_generated_password])
以下是输出结果:
array([0, 2])
它是如何工作的…
我们首先导入pandas
,然后将数据读取到一个数据框中(步骤 1)。数据中有两个字段:密码和密码强度。密码强度分为三种难度等级。我们在步骤 2中打乱数据,以创建更强健的训练。接着,在步骤 3中,我们通过 80-20 的比例划分数据框,然后将特征和标签分配到数组中(步骤 4)。在步骤 5中,我们定义一个函数,将密码字符串拆分成字符,从而将密码分词为字符,而不是单词。这样可以让分类器学习密码数据集中的细粒度信息。在步骤 6中,我们定义一个管道,对密码的字符进行自然语言处理(NLP),然后使用 XGBoost 分类器。接下来,在步骤 7中,我们训练并测试我们的分类器。对于这样的主观任务,分类器的表现不一定会在高分或低分上体现。
完成训练后,我们进行合理性检查/演示分类器的有效性。在步骤 8中,我们选择最常见的密码之一和一个使用密码管理系统生成的密码。在步骤 9中,我们可以看到分类器确实将常见密码分类为弱(强度 0),而强密码分类为强(强度 2)。成功。
密码破解的深度学习
现代的密码破解工具,如John the Ripper,允许黑客在几秒钟内测试数十亿个潜在密码。这些工具不仅能让黑客尝试字典中所有常见密码,还可以通过连接(例如,password1234
)、键盘替换(p4s5w0rd
)等技巧自动转换密码。虽然这些技巧有前景,但发现其他有效的转换方法是一个困难的任务。被称为 PassGAN 的机器学习系统使用生成对抗网络(GAN),通过观察大量实际密码泄露数据集,自动学习这些规则,并生成高概率的密码候选。 在这个食谱中,你将训练 PassGAN 来处理泄露的密码语料库,并使用它生成密码猜测。
该项目需要一台配有 GPU 的机器。
准备工作
为了准备这个食谱,请执行以下步骤:
- 使用以下命令克隆
PassGAN
仓库:
git clone https://github.com/emmanueltsukerman/PassGAN.git
- 将数据集放置在
data
文件夹下。例如,你可以使用以下命令下载著名的rockyou
密码数据集:
curl -L -o data/train.txt https://github.com/brannondorsey/PassGAN/releases/download/data/rockyou-train.txt
在运行密码数据集时,你应该看到如下内容:
此外,本食谱要求预先安装 CUDA 8。所需的 pip
包可以通过运行以下命令来安装:
pip install -r requirements.txt
如何做……
在接下来的步骤中,我们将使用泄露的密码语料库来训练 PassGAN,并用它生成新的密码猜测。让我们开始吧:
- 通过运行以下命令,使用数据集训练你的神经网络:
python train.py --output-dir output --training-data data/train.txt
- 通过运行以下命令生成(100,000)个密码猜测的列表:
python sample.py \
--input-dir pretrained \
--checkpoint pretrained/checkpoints/195000.ckpt \
--output gen_passwords.txt \
--batch-size 1024 \
--num-samples 100000
你的终端应该显示如下内容:
它是如何工作的…
在本教程中,我们在步骤 1中直接训练了我们的神经网络。根据需要,还可以使用几个附加的标志来定制训练。现在我们已经训练好了模型,接下来需要输出一个包含 100,000 个密码的列表,所有这些密码都是由模型生成的(步骤 2)。这些密码是可能的智能猜测。通过检查步骤 2的输出,我们可以看到密码的展示方式如下:
现在,我们可以将这些作为破解密码的候选项。
还有更多内容
描述 PassGAN 的原始论文可以在arxiv.org/abs/1709.00440
找到。
深度隐写术
隐写术是将一条消息(即秘密)隐藏在另一种媒介中的实践,比如文件、文本、图像或视频(即封面)。当秘密嵌入到封面中时,结果称为容器。在本教程中,我们将使用深度神经网络来创建隐藏和揭示过程。与常见的隐写方法不同,深度学习将秘密分布在所有位上,而不是仅仅编码在封面的最低有效位(LSB)中。
准备就绪
在这个教程中,你将需要访问一个 GPU。
如何操作…
- 使用以下命令克隆仓库:
git clone https://github.com/emmanueltsukerman/PyTorch-Deep-Image-Steganography.git
- 准备一个预训练模型:
cat ./checkPoint/netH.tar.gz* | tar -xzv -C ./checkPoint/
- 在
example_pics
文件夹中准备一个秘密图像和一个封面图像:
如你所见,我们使用以下图像作为封面图像:
我们使用以下图像作为秘密图像:
- 执行预训练模型生成容器图像和重建的秘密:
CUDA_VISIBLE_DEVICES=0 python main.py –test=./example_pics
输出的第一部分显示在以下截图中:
输出的第二部分显示在以下截图中:
输出的最后一部分显示在以下截图中:
- 在训练文件夹下查看结果。你应该能看到以下图像:
第 1 行:封面。第 2 行:容器。第 3 行:秘密。第 4 行:重建的秘密
它是如何工作的…
在步骤 1中,我们简单地克隆了深度隐写术项目的存储库。关于该项目的理论和实现的背景可以在论文在明处隐藏图像:深度隐写术中找到(papers.nips.cc/paper/6802-hiding-images-in-plain-sight-deep-steganography
)。
基本思想是有一个隐藏网络(H-net)和一个揭示网络(R-net),两者都是通过对抗训练的。继续到步骤 2,我们准备我们的预训练模型。我们在这里使用的模型是在 ImageNet 的 45000 张图像上训练的,并在 5000 张图像上进行了评估。所有图像都被调整为 256×256,没有归一化,任务在一块 NVIDIA GTX 1080 Ti 上进行了 24 小时的训练。接下来,我们选择两张我们选择的图像作为封面和秘密(步骤 3)。请随意使用您自己的图像对。在步骤 4和5中,我们运行模型,创建一个容器图像(包含隐藏秘密的图像),并生成一个显示我们结果的图像。正如您所看到的,容器图像和封面图像在人眼中是无法区分的,这意味着没有人能够看出您在封面图像中隐藏了一个秘密。
基于机器学习的隐写分析
隐写术中的主要技术之一是通过改变像素的最低有效位(LSB)来隐藏消息在图像中。结果是一个带有隐藏消息的图像,人眼无法区分其与原始图像的区别。这是因为在改变图像像素的 LSB 时,像素值只会被微小地改变,导致一个视觉上相似的图像。
LSB 有两种显著的方法:
-
天真的方法被称为 LSB 替换。在这种方法中,如果消息位与 LSB 相同,则 LSB 位保持不变;否则,该位被改变。因此,奇数像素的强度减少 1,而偶数像素值增加 1。然而,这会导致图像直方图的不平衡,可以很容易地通过统计方法检测到进行隐写分析。
-
LSB 隐写术的第二种方法,LSB 匹配,通过在 LSB 位不匹配的情况下随机增加或减少像素值 1 来解决这个问题。这避免了直方图不平衡的问题,并使得仅仅使用简单的统计方法进行隐写分析变得困难。
以下图像展示了 LSB 隐写术的一个实例。
以下图像将被表示为封面图像:
以下图像将被表示为秘密图像:
以下图像将被表示为容器图像:
以下图像将被显示为恢复的秘密图像:
准备工作
推荐你在 Linux 机器上完成此配方。按照以下步骤设置好环境:
- 安装
octave
以及其包image
和signal
:
sudo apt-get install octave octave-image octave-signal
- 克隆
aletheia
的代码库,如以下代码所示:
git clone https://github.com/emmanueltsukerman/aletheia.git
- 下载
BOSS
数据集,你可以通过以下链接下载:
wget http://dde.binghamton.edu/download/ImageDB/BOSSbase_1.01.zip
这将检索一个灰度图像数据库。
- 解压数据集并重命名
BOSSbase
文件夹:
unzip BOSSbase_1.01.zip
为了方便你,处理过的数据集,即bossbase.7z
和bossbase_lsb.7z
,可以在本书的代码库中找到。
如何操作…
在本配方中,我们将策划一个 LSB 数据集,然后训练和测试一个机器学习模型,检测图像中是否存在 LSB 隐写术。让我们开始吧:
- 使用以下命令创建一个 LSB 数据库:
python aletheia.py lsbm-sim bossbase 0.40 bossbase_lsb
结果是一个名为bossbase_lsb
的新文件夹,包含了带有嵌入的 BOSS 图像。它通过 LSB 匹配模拟器完成此操作。
- 对
BOSS
数据集进行特征化,如以下代码所示:
./aletheia.py srm bossbase bossbase.fea
- 对 LSB 数据集进行特征化,如以下代码所示:
./aletheia.py srm bossbase_lsb bossbase_lsb.fea
剩余的步骤可以在 Python 环境中运行,方便你使用。
- 创建一些指向提取特征路径的变量:
bossbase_features_path = "bossbase.fea"
bossbase_lsb_features_path = "bossbase_lsb.fea"
features_with_labels = [(bossbase_features_path, 0), (bossbase_lsb_features_path, 1)]
- 收集特征和标签并将它们放入数组中:
X = []
y = []
for feature_path, label in features_with_labels:
with open(feature_path, "r") as f:
for line in f:
fv = line.split()
X.append(fv)
y.append(label)
- 执行训练-测试集划分:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=11
)
- 实例化一个
RandomForestClassifier
并进行训练:
from sklearn.ensemble import RandomForestClassifier
clf = RandomForestClassifier()
clf = clf.fit(X_train, y_train)
- 在测试集上对分类器进行评分:
print(clf.score(X_test, y_test))
以下是输出结果:
0.825
它是如何工作的…
我们从使用被称为 Aletheia 的软件创建一个大型 LSB 隐写容器图像数据集开始(步骤 1)。Aletheia 提供了广泛的功能。运行以下命令,不带任何参数:
$ ./aletheia.py
上述命令将打印出关于aletheia
的以下信息:
./aletheia.py <command>
COMMANDS:
Attacks to LSB replacement:
- spa: Sample Pairs Analysis.
- rs: RS attack.
ML-based detectors:
- esvm-predict: Predict using eSVM.
- e4s-predict: Predict using EC.
Feature extractors:
- srm: Full Spatial Rich Models.
- hill-maxsrm: Selection-Channel-Aware Spatial Rich Models for HILL.
- srmq1: Spatial Rich Models with fixed quantization q=1c.
- scrmq1: Spatial Color Rich Models with fixed quantization q=1c.
- gfr: JPEG steganalysis with 2D Gabor Filters.
Embedding simulators:
- lsbr-sim: Embedding using LSB replacement simulator.
- lsbm-sim: Embedding using LSB matching simulator.
- hugo-sim: Embedding using HUGO simulator.
- wow-sim: Embedding using WOW simulator.
- s-uniward-sim: Embedding using S-UNIWARD simulator.
- j-uniward-sim: Embedding using J-UNIWARD simulator.
- j-uniward-color-sim: Embedding using J-UNIWARD color simulator.
- hill-sim: Embedding using HILL simulator.
- ebs-sim: Embedding using EBS simulator.
- ebs-color-sim: Embedding using EBS color simulator.
- ued-sim: Embedding using UED simulator.
- ued-color-sim: Embedding using UED color simulator.
- nsf5-sim: Embedding using nsF5 simulator.
- nsf5-color-sim: Embedding using nsF5 color simulator.
Model training:
- esvm: Ensemble of Support Vector Machines.
- e4s: Ensemble Classifiers for Steganalysis.
- xu-net: Convolutional Neural Network for Steganalysis.
Unsupervised attacks:
- ats: Artificial Training Sets.
Naive attacks:
- brute-force: Brute force attack using a list of passwords.
- hpf: High-pass filter.
- imgdiff: Differences between two images.
- imgdiff-pixels: Differences between two images (show pixel values).
- rm-alpha: Opacity of the alpha channel to 255.
在步骤 2和步骤 3中,我们使用 Aletheia 的srm
命令提取原始图像和容器图像的特征。srm
命令提取了完整且空间丰富的特征集。还有其他替代的特征集可供选择。接下来,我们创建指向数据集路径的变量(步骤 4),然后将我们的特征和标签收集到数组中(步骤 5)。在步骤 6至步骤 8中,我们创建训练集和测试集,训练分类器,并进行测试。通过查看在平衡数据集上 80%的表现,我们可以看出这些特征确实有助于我们区分原始图像和容器图像。换句话说,我们可以得出结论:机器学习可以检测隐写术。
针对 PUF 的机器学习攻击
经典密码学提供了多种保护电子设备的措施。这些措施主要依赖于一个秘密密钥和昂贵的资源,因为设备会永久存储一段对我们的对手未知的数字信息。实际上,很难保持这些信息的机密性。这个问题促使了 PUF 的发明——物理设备,它能快速评估输出,但却难以预测。
要使用 PUF 进行身份验证,我们需要构建一个挑战-响应对(CRP)的数据库。挑战是一个二进制字符串(例如,1100101...01),长度为n,响应是另一个二进制字符串,长度为m。为了判断一个未知设备是否为前述的 PUF,我们需要向其发出多个挑战,验证它是否产生正确的响应,直到我们达到所期望的概率,确认它确实是同一个 PUF。请注意,PUF 本身并不是 100%可靠的,相同的挑战可能由于环境条件和噪声的变化而产生不同的响应:
图 8:基于 PUF 的商业 RFID 标签
在此食谱中,我们将使用机器学习攻击一个特定的 PUF。请注意,该领域不断发展,已经提出了其他更安全的 PUF,以及利用机器学习提高 PUF 可靠性和安全性的方法。
准备工作
对于此食谱,我们需要通过pip
安装pandas
、sklearn
和xgboost
。使用以下代码来安装:
pip install pandas sklearn xgboost
此外,已为此食谱提供了CRPDataset.csv
数据集。
如何做...
让我们学习如何用机器学习破解一个 PUF:
- 加载一个 CRP 数据集,在本例中为
CRPDataset.csv
:
import pandas as pd
df = pd.read_csv("CRPdataset.csv")
数据由对(x,y)组成,其中x是长度为 64 的二进制字符串,y是一个二进制数字。这里,x是挑战,y是响应。
- 将
pandas
数据框转换为 NumPy 特征和标签数组:
y = df.pop("Label").values
X = df.values
- 执行训练-测试拆分:
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=11
)
- 实例化并训练 XGBoost 分类器:
from xgboost import XGBClassifier
clf = XGBClassifier()
clf.fit(X_train, y_train)
print(clf.score(X_train, y_train))
以下是输出结果:
0.6405208333333333
- 测试分类器,如以下代码所示:
clf.score(X_test, y_test)
以下是输出结果:
0.6270833333333333
它是如何工作的…
我们首先将一个 CRP 数据集读取到一个数据框中(步骤 1)。在步骤 2中,我们创建 x 和 y 的 NumPy 数组来保存特征和标签。接下来,我们对数据进行训练-测试拆分(步骤 3),然后训练和测试一个针对 CRP 的分类器(步骤 4和步骤 5)。根据性能结果,我们可以看到,机器学习可以准确预测 PUF 挑战的响应。其意义在于,在使用我们训练好的模型时,我们可以构建一个 PUF 的软件克隆,并用它来(虚假地)进行身份验证。
还有更多
该食谱的原始未处理数据集可以在archive.ics.uci.edu/ml/datasets/Physical+Unclonable+Functions
找到。更多背景信息可以在论文《基于机器学习的资源受限物联网 XOR PUF 安全漏洞研究》(Aseeri, A. O.,Zhuang, Y.,和 Alkatheiri, M. S.,2018 年 7 月,发表于 2018 年 IEEE 国际物联网大会(ICIOT)(第 49-56 页),IEEE)中找到。
使用深度学习进行加密
加密是将信息转换为代码以防止未经授权的访问的过程。在此食谱中,我们将利用卷积神经网络对数据进行加密和解密。
准备工作
对于这个食谱,你需要在pip
中安装click
、keras
、tensorflow
和tqdm
包。使用以下代码来安装:
pip install click keras tensorflow tqdm
此外,请使用以下命令克隆该仓库:
git clone https://github.com/emmanueltsukerman/convcrypt.git
如何操作…
以下步骤将指导你如何使用 ConvCrypt 来加密图片。让我们开始吧:
- 运行
encrypt.py
脚本,对你希望加密的图片或文件进行加密:
python encrypt.py --input_file "input file path" --output_file "encrypted file path" --key_file "key file name"
上述代码的输出显示在以下截图中:
若要确认文件已被加密,尝试打开它。你会发现它无法打开,因为它已经被加密:
- 若要解密文件,执行
decrypt.py
脚本,传入加密文件和密钥文件:
python decrypt.py --input_file "encrypted file path" --output_file "reconstructed file path" --key_file "key file name"
结果是原始文件。
它是如何工作的…
我们通过使用 ConvCrypt 对图片进行加密开始这个食谱(步骤 1)。ConvCrypt 是一种概念验证实验性加密算法,使用n维卷积神经网络。目前,它仅支持三维卷积。然后,在步骤 2中,我们将逆向解密并进行测试,以确保结果是原始文件。成功!
对于有兴趣的朋友,ConvCrypt 算法首先做的事情是将数据分成块。然后,为 3D 卷积生成一个密钥;这是一个随机生成的比特立方体,大小与数据块相同。最后,训练一个卷积神经网络,将密钥卷积到每个数据块中,这样每个数据块就会有自己训练好的网络。加密后的数据是每个网络的权重(即核张量的值)。
HIPAA 数据泄露——数据探索与可视化
数据探索是数据分析的初步步骤,通过可视化探索来理解数据集及其特点。数据可视化帮助我们通过将数据置于光学背景中,利用我们强大的视觉处理中心快速发现数据中的模式和相关性。
在这个食谱中,你将探索并可视化一个关于 HIPAA 机密信息泄露的公共数据集。
准备开始
对于这个食谱,你需要在pip
中安装pandas
和sklearn
。使用以下代码来安装:
pip install pandas sklearn
此外,提供了HIPAA-breach-report-2009-to-2017.csv
数据集,供你在本食谱中使用。
如何操作…
在接下来的步骤中,你将使用 pandas 可视化 HIPAA 泄露数据集,并使用 TF-IDF 从泄露描述中提取重要关键词。让我们开始吧:
- 使用
pandas
加载并清洗 HIPAA 泄露数据集:
import pandas as pd
df = pd.read_csv("HIPAA-breach-report-2009-to-2017.csv")
df = df.dropna()
上述代码的输出显示在以下截图中:
- 使用以下代码绘制受泄露影响的个人数量与泄露事件频率的直方图:
%matplotlib inline
def_fig_size = (15, 6)
df["Individuals Affected"].plot(
kind="hist", figsize=def_fig_size, log=True, title="Breach Size Distribution"
)
以下输出展示了泄露大小分布:
- 基于实体类型绘制平均泄露大小:
df.groupby("Covered Entity Type").mean().plot(
kind="bar", figsize=def_fig_size, title="Average Breach Size by Entity Type"
)
以下截图显示了按实体类型划分的平均泄露大小:
- 绘制一个饼图,显示按州划分的每个州受泄露影响的个人数量,并筛选出前 20 个州:
df.groupby("State").sum().nlargest(20, "Individuals Affected").plot.pie(
y="Individuals Affected", figsize=def_fig_size, legend=False
)
以下图表展示了每个州受泄露影响的个人数量:
- 绘制平均泄露大小与泄露类型(盗窃、丢失、黑客攻击等)之间的关系:
df.groupby("Type of Breach").mean().plot(
kind="bar", figsize=def_fig_size, title="Average Breach Size by Entity Type"
)
以下图表显示了泄露类型:
- 实例化 TF-IDF 向量化器:
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer()
- 将向量化器拟合到泄露描述,并进行向量化:
df["Web Description"] = df["Web Description"].str.replace("\r", " ")
X = df["Web Description"].values
X_transformed = vectorizer.fit_transform(X)
- 根据 TF-IDF 选择泄露描述中最重要的 15 个特征:
import numpy as np
feature_array = np.array(vectorizer.get_feature_names())
tfidf_sorting = np.argsort(X_transformed.toarray()).flatten()[::-1]
n = 15
top_n = feature_array[tfidf_sorting][:n]
print(top_n)
输出如下:
['this' 'review' '842' 'south' 'ransomware' 'memorial' 'specific' 'birthdates' 'consolidated' 'malware' 'license' 'driver' 'found' 'clinic' 'information']
- 打印出包含
review
关键词的几个泄露描述:
k = 2
i = 0
for x in df["Web Description"].values:
if "review" in x:
i += 1
print(x)
print()
if i == k:
break
以下是部分输出片段:
A laptop was lost by an employee... all employees received additional security training.
The covered entity's (CE) business associate (BA) incorrectly... BA to safeguard all PHI.
它是如何工作的…
我们首先将 HIPAA 数据集读取到数据框中,并删除包含 NAs 的行(步骤 1)。接下来,在步骤 2中,我们可以看到大多数泄露规模相对较小,但少数泄露事件规模巨大。这与帕累托原则一致。在步骤 3中,我们按行业绘制泄露事件,以确保最大规模的泄露发生在商业合作伙伴中。然后,在步骤 4中,我们检查哪些州发生了最多的 HIPAA 泄露事件。在步骤 5中,我们得知最大规模的泄露原因通常未知!在步骤 6和7中,我们对泄露描述进行基本的自然语言处理(NLP)。这将帮助我们提取更多感兴趣的信息。在步骤 8中,我们可以看到 TF-IDF 能够找到一些非常有信息量的关键词,比如ransomware(勒索软件)和driver(驱动程序)。最后,在步骤 9中,我们打印出包含关键词review的泄露描述。结果表明,review(回顾)是一个极其重要的词,它在质量控制和事件响应工具中经常出现。
第八章:安全与私密的人工智能
机器学习可以帮助我们诊断和对抗癌症,决定哪个学校最适合我们的孩子,甚至做出最聪明的房地产投资。但你只有在能访问私人和个人数据的情况下才能回答这些问题,这就需要一种全新的机器学习方法。这种方法叫做安全与私密的人工智能,近年来已经取得了显著进展,正如你在以下食谱中所看到的那样。
本章包含以下食谱:
-
联邦学习
-
加密计算
-
私有深度学习预测
-
测试神经网络的对抗鲁棒性
-
使用 TensorFlow Privacy 的差分隐私
技术要求
本章的技术前提如下:
-
TensorFlow Federated
-
Foolbox
-
PyTorch
-
Torchvision
-
TensorFlow Privacy
安装说明、代码和数据集可以在github.com/PacktPublishing/Machine-Learning-for-Cybersecurity-Cookbook/tree/master/Chapter08
找到。
联邦学习
在本食谱中,我们将使用 TensorFlow federated 框架训练一个联邦学习模型。
为了理解联邦学习的价值,考虑你在手机上写短信时使用的下一个词预测模型。出于隐私原因,你不希望你的数据,也就是你的短信,发送到中央服务器上用于训练下一个词预测器。但你依然希望有一个准确的下一个词预测算法。该怎么办?这时,联邦学习就派上用场了,这是一种为解决此类隐私问题而开发的机器学习技术。
联邦学习的核心思想是训练数据集由数据的生产者保管,既能保持隐私和所有权,又能用于训练一个集中式模型。这一特点在网络安全领域尤其有吸引力,例如,从多个不同来源收集良性和恶性样本对于创建强大的模型至关重要,但由于隐私问题,这一过程相当困难(例如,良性样本可以是个人或机密文件)。
顺便提一下,联邦学习因数据隐私重要性日益增加而越来越受到关注(例如,GDPR 的实施)。大型公司,如苹果和谷歌,已经开始大量投资这一技术。
准备工作
为了准备本食谱,需通过 pip
安装 tensorflow_federated
、tensorflow_datasets
和 tensorflow
包。命令如下:
pip install tensorflow_federated==0.2.0 tensorflow-datasets tensorflow==1.13.1
我们将安装这些包的特定版本,以防止代码出现中断。
如何操作…
在接下来的步骤中,你将创建两个虚拟数据集环境——一个属于爱丽丝,另一个属于鲍勃——并使用联邦平均算法来保持数据的保密性。
- 导入 TensorFlow 并启用急切执行:
import tensorflow as tf
tf.compat.v1.enable_v2_behavior()
- 准备一个数据集,通过导入 Fashion MNIST 并将其分成 Alice 和 Bob 两个独立环境:
import tensorflow_datasets as tfds
first_50_percent = tfds.Split.TRAIN.subsplit(tfds.percent[:50])
last_50_percent = tfds.Split.TRAIN.subsplit(tfds.percent[-50:])
alice_dataset = tfds.load("fashion_mnist", split=first_50_percent)
bob_dataset = tfds.load("fashion_mnist", split=last_50_percent)
- 现在,定义一个
helper
函数将数据类型从整数转换为浮点数:
def cast(element):
"""Casts an image's pixels into float32."""
out = {}
out["image"] = tf.image.convert_image_dtype(element["image"], dtype=tf.float32)
out["label"] = element["label"]
return out
- 然后,定义一个
helper
函数,将数据展平以便输入到神经网络中:
def flatten(element):
"""Flattens an image in preparation for the neural network."""
return collections.OrderedDict(
[
("x", tf.reshape(element["image"], [-1])),
("y", tf.reshape(element["label"], [1])),
]
)
- 现在,定义一个
helper
函数来预处理数据:
import collections
BATCH_SIZE = 32
def preprocess(dataset):
"""Preprocesses images to be fed into neural network."""
return dataset.map(cast).map(flatten).batch(BATCH_SIZE)
- 预处理数据:
preprocessed_alice_dataset = preprocess(alice_dataset)
preprocessed_bob_dataset = preprocess(bob_dataset)
federated_data = [preprocessed_alice_dataset, preprocessed_bob_dataset]
- 现在,定义一个用于神经网络的
loss
函数:
def custom_loss_function(y_true, y_pred):
"""Custom loss function."""
return tf.reduce_mean(
tf.keras.losses.sparse_categorical_crossentropy(y_true, y_pred)
)
- 定义一个函数来实例化一个简单的 Keras 神经网络:
from tensorflow.python.keras.optimizer_v2 import gradient_descent
LEARNING_RATE = 0.02
def create_compiled_keras_model():
"""Compiles the keras model."""
model = tf.keras.models.Sequential(
[
tf.keras.layers.Dense(
10,
activation=tf.nn.softmax,
kernel_initializer="zeros",
input_shape=(784,),
)
]
)
model.compile(
loss=custom_loss_function,
optimizer=gradient_descent.SGD(learning_rate=LEARNING_RATE),
metrics=[tf.keras.metrics.SparseCategoricalAccuracy()],
)
return model
- 然后,创建一个虚拟样本批次并定义一个函数,从 Keras 模型返回一个联邦学习模型:
batch_of_samples = tf.contrib.framework.nest.map_structure(
lambda x: x.numpy(), iter(preprocessed_alice_dataset).next()
)
def model_instance():
"""Instantiates the keras model."""
keras_model = create_compiled_keras_model()
return tff.learning.from_compiled_keras_model(keras_model, batch_of_samples)
- 声明一个联邦平均的迭代过程,并运行一个计算阶段:
from tensorflow_federated import python as tff
federated_learning_iterative_process = tff.learning.build_federated_averaging_process(
model_instance
)
state = federated_learning_iterative_process.initialize()
state, performance = federated_learning_iterative_process.next(state, federated_data)
- 然后,通过运行以下命令显示计算的指标:
performance
输出结果如下:
AnonymousTuple([(sparse_categorical_accuracy, 0.74365), (loss, 0.82071316)])
它是如何工作的...
我们首先导入 TensorFlow 并启用急切执行模式(步骤 1)。通常,在 TensorFlow 中,操作不会立即执行,而是构建一个计算图,并在最后一起执行所有操作。在急切执行模式下,计算会尽可能快地执行。接下来,在步骤 2中,我们导入 Fashion MNIST 数据集。这个数据集已经成为 MNIST 的事实替代品,相比 MNIST 提供了几项改进(如增加了挑战)。然后,我们将数据集按 50:50 的比例分配给 Alice 和 Bob。接下来,我们定义一个函数,将 Fashion MNIST 的像素值从整数转换为浮点数,以便用于训练神经网络(步骤 3),并定义另一个函数将图像展平为单个向量(步骤 4)。这样,我们就可以将数据输入到全连接神经网络中。在步骤 5和步骤 6中,我们使用之前定义的便捷函数对 Alice 和 Bob 的数据集进行预处理。
接下来,我们定义一个适合我们 10 类分类任务的损失函数(步骤 7),然后定义我们的 Keras 神经网络以准备训练(步骤 8)。在步骤 9中,我们创建一个虚拟样本批次并定义一个函数,从 Keras 模型返回一个联邦学习模型。虚拟样本批次指定了模型预期的输入形状。在步骤 10中,我们运行一个联邦平均过程的阶段。关于该算法的详细信息,请参阅题为《从分散数据中高效学习深度网络的通信》的论文。
从基本层面来看,算法结合了每个客户端数据的局部随机梯度下降(SGD),然后使用一个执行模型平均的服务器。结果是为客户端(在我们的例子中是 Alice 和 Bob)保留了保密性。最后,在步骤 11中,我们观察性能,发现算法确实能够训练并提高准确率,如预期那样。
加密计算
在这个配方中,我们将介绍加密计算的基础知识。特别地,我们将重点讲解一种流行的方法,称为安全多方计算。你将学习如何构建一个简单的加密计算器,可以对加密数字进行加法操作。这个配方中的理念将在私有深度学习预测配方中派上用场。
准备工作
以下配方除了 Python 外没有其他安装要求。
如何操作…
- 导入随机库并选择一个大素数
P
:
import random
P = 67280421310721
- 定义一个用于三方的加密函数:
def encrypt(x):
"""Encrypts an integer between 3 partires."""
share_a = random.randint(0, P)
share_b = random.randint(0, P)
share_c = (x - share_a - share_b) % P
return (share_a, share_b, share_c)
- 对一个数值变量进行加密:
x = 17
share_a, share_b, share_c = encrypt(x)
print(share_a, share_b, share_c)
16821756678516 13110264723730 37348399908492
- 定义一个函数来解密,给定三个分享份额:
def decrypt(share_a, share_b, share_c):
"""Decrypts the integer from 3 shares."""
return (share_a + share_b + share_c) % P
- 解密加密变量
x
:
decrypt(share_a, share_b, share_c)
输出如下:
17
- 定义一个函数来对两个加密数字进行加法操作:
def add(x, y):
"""Addition of encrypted integers."""
z = list()
z.append((x[0] + y[0]) % P)
z.append((x[1] + y[1]) % P)
z.append((x[2] + y[2]) % P)
return z
- 对两个加密变量进行加法并解密它们的和:
x = encrypt(5)
y = encrypt(9)
decrypt(*add(x, y))
14
工作原理…
我们从导入随机库开始,第 1 步,以便在第 2 步中生成随机整数。我们还定义了一个大素数 P,因为我们需要对其进行随机分布模 P 操作。在第 2 步中,我们定义了一个函数,通过将整数分割给三个参与方来加密该整数。这里的 x 值在三个参与方之间随机地加法拆分。所有操作都在模 P 的整数域内进行。接下来,在第 3 步中,我们展示了使用我们的方法加密整数的结果。在第 4 步和第 5 步中,我们定义了一个函数来反向加密,即解密,然后证明该操作是可逆的。在第 6 步中,我们定义了一个函数来对两个加密数字进行加法操作(!)。请注意,加密加法只是对单个组件进行加法操作,并对结果进行模 P 处理。在加密深度学习预测配方中,PySyft 中的.share(client, server,...)
命令被使用。这个命令实际上和我们在这个配方中使用的加密程序是相同的,所以请记住,这些加密方案使用的是我们在这里讨论的技术。最后,在第 7 步中,我们展示了如何在加密实体上进行计算。
私有深度学习预测
在许多情况下,A 公司可能拥有一个训练好的模型,并希望将其作为服务提供。然而,A 公司可能不愿分享这个模型,以避免其知识产权被盗用。这个问题的简单解决方法是让客户将数据发送到 A 公司,然后从公司获得预测结果。然而,当客户希望保持数据隐私时,这就成了一个问题。为了解决这个棘手的情况,公司和客户可以利用加密计算。
在这个配方中,你将学习如何与客户共享一个加密的预训练深度学习模型,并允许客户使用加密模型对他们自己的私人数据进行预测。
准备工作
这个配方的准备工作包括在pip
中安装 PyTorch、Torchvision 和 PySyft。命令如下:
pip install torch torchvision syft
此外,还包括一个名为 server_trained_model.pt
的预训练模型,将在此食谱中使用。
如何操作……
以下步骤利用 PySyft 模拟客户端-服务器交互,其中服务器拥有一个预训练的深度学习模型,模型被保留为一个黑盒,而客户端希望使用该模型对保密数据进行预测。
- 导入
torch
并访问其数据集:
import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision import datasets, transforms
- 导入 PySyft 并将其挂载到
torch
上:
import syft as sy
hook = sy.TorchHook(torch)
client = sy.VirtualWorker(hook, id="client")
server = sy.VirtualWorker(hook, id="server")
crypto_provider = sy.VirtualWorker(hook, id="crypto_provider")
- 定义一个简单的神经网络:
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(784, 500)
self.fc2 = nn.Linear(500, 10)
def forward(self, x):
x = x.view(-1, 784)
x = self.fc1(x)
x = F.relu(x)
x = self.fc2(x)
return x
- 实例化模型并加载其在 MNIST 上训练的预训练权重:
model = Net()
model.load_state_dict(torch.load("server_trained_model.pt"))
model.eval()
- 加密
client
和server
之间的网络:
model.fix_precision().share(client, server, crypto_provider=crypto_provider)
- 定义一个 MNIST 数据的加载器:
test_loader = torch.utils.data.DataLoader(
datasets.MNIST(
"data",
train=False,
download=True,
transform=transforms.Compose(
[transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))]
),
),
batch_size=64,
shuffle=True,
)
- 定义一个私有加载器,利用 MNIST 数据的加载器:
private_test_loader = []
for data, target in test_loader:
private_test_loader.append(
(
data.fix_precision().share(client, server, crypto_provider=crypto_provider),
target.fix_precision().share(
client, server, crypto_provider=crypto_provider
),
)
)
- 定义一个函数来评估私有测试集:
def test(model, test_loader):
"""Test the model."""
model.eval()
n_correct_priv = 0
n_total = 0
- 遍历私密数据,使用模型进行预测,解密结果,然后打印出来:
with torch.no_grad():
for data, target in test_loader:
output = model(data)
pred = output.argmax(dim=1)
n_correct_priv += pred.eq(target.view_as(pred)).sum()
n_total += 64
n_correct =
n_correct_priv.copy().get().float_precision().long().item()
print(
"Test set: Accuracy: {}/{} ({:.0f}%)".format(
n_correct, n_total, 100.0 * n_correct / n_total
)
)
- 执行测试过程:
test(model, private_test_loader)
结果如下:
Test set: Accuracy: 63/64 (98%)
Test set: Accuracy: 123/128 (96%)
Test set: Accuracy: 185/192 (96%)
Test set: Accuracy: 248/256 (97%)
Test set: Accuracy: 310/320 (97%)
Test set: Accuracy: 373/384 (97%)
Test set: Accuracy: 433/448 (97%)
<snip>
Test set: Accuracy: 9668/9920 (97%)
Test set: Accuracy: 9727/9984 (97%)
Test set: Accuracy: 9742/10048 (97%)
它是如何工作的……
我们首先导入 torch
及其数据集,和一些相关库(步骤 1)。然后我们导入 pysyft
并将其挂载到 torch
上(步骤 2)。我们还为客户端和服务器创建虚拟环境,以模拟数据的真实隔离。在此步骤中,crypto_provider
作为一个可信的第三方,用于加密和解密。接着在 步骤 3 中,我们定义一个简单的神经网络,并在 步骤 4 中加载它的预训练权重。请注意,在 步骤 5 中,以及一般来说,每当使用 .share(...)
命令时,你应该将共享的对象视为已加密,并且只有在所有相关方的协助下才能解密。特别地,在 步骤 9 中,测试函数执行加密评估;模型的权重、数据输入、预测和用于评分的目标都是加密的。然而,为了验证模型是否正常工作,我们解密并显示其准确性。在 步骤 5 中,我们加密网络,确保只有在服务器和客户端协作时,才能解密网络。
在接下来的两个步骤中,我们为 MNIST 数据定义常规和私有加载器。常规加载器仅加载 MNIST 数据,而私有加载器加密常规加载器的输出。在 步骤 8 和 步骤 9 中,我们定义了一个 helper
函数来评估私有测试集。在该函数中,我们遍历私有数据,使用模型进行预测,解密结果,然后打印出来。最后,我们应用 步骤 8 和 步骤 9 中定义的函数,以确保模型在保护隐私的同时能够良好地执行。
测试神经网络的对抗鲁棒性
对神经网络进行对抗性攻击的研究揭示了它们对对抗性扰动的惊人敏感性。即使是最准确的神经网络,在没有防护的情况下,也已被证明容易受到单像素攻击和对人眼不可见的噪声干扰的影响。幸运的是,近期该领域的进展已提供了解决方案,帮助神经网络抵御各种对抗性攻击。其中一种解决方案是名为综合分析(ABS)的神经网络设计。该模型背后的主要思想是它是一个贝叶斯模型。模型不仅直接预测给定输入的标签,还利用变分自编码器(VAEs)学习类条件样本分布。更多信息请访问arxiv.org/abs/1805.09190
。
在这个教程中,你将加载一个预训练的 ABS 网络用于 MNIST,并学习如何测试神经网络的对抗性鲁棒性。
正在准备中
以下教程已在 Python 3.6 中测试过。准备这个教程需要在pip
中安装 Pytorch、Torchvision、SciPy、Foolbox 和 Matplotlib 包。命令如下:
pip install torch torchvision scipy foolbox==1.8 matplotlib
如何实现...
在接下来的步骤中,我们将加载一个预训练的 ABS 模型和一个传统的 CNN 模型用于 MNIST。我们将使用 Foolbox 攻击这两个模型,以查看它们在防御对抗性攻击方面的表现:
- 首先导入一个预训练的 ABS 模型:
from abs_models import models
from abs_models import utils
ABS_model = models.get_VAE(n_iter=50)
- 定义一个
convenience
函数,使用模型预测一批 MNIST 图像:
import numpy as np
def predict_on_batch(model, batch, batch_size):
"""Predicts the digits of an MNIST batch."""
preds = []
labels = []
for i in range(batch_size):
point, label = utils.get_batch()
labels.append(label[0])
tensor_point = utils.n2t(point)
logits = model(tensor_point)[0]
logits = [x for x in logits]
pred = np.argmax(logits)
preds.append(int(pred))
return preds, labels
- 对一批数据进行预测:
batch = utils.get_batch()
preds, labels = predict_on_batch(ABS_model, batch, 5)
print(preds)
print(labels)
结果如下:
[4, 4, 9, 1, 8]
[4, 4, 9, 1, 8]
- 使用 Foolbox 包装模型,以启用对抗性测试:
import foolbox
if ABS_model.code_base == "tensorflow":
fmodel = foolbox.models.TensorFlowModel(
ABS_model.x_input, ABS_model.pre_softmax, (0.0, 1.0), channel_axis=3
)
elif ABS_model.code_base == "pytorch":
ABS_model.eval()
fmodel = foolbox.models.PyTorchModel(
ABS_model, bounds=(0.0, 1.0), num_classes=10, device=utils.dev()
)
- 从 Foolbox 导入攻击库并选择一个 MNIST 图像:
from foolbox import attacks
images, labels = utils.get_batch(bs=1)
- 选择攻击类型,在本例中为边界攻击:
attack = attacks.DeepFoolL2Attack(fmodel)
metric = foolbox.distances.MSE
criterion = foolbox.criteria.Misclassification()
- 使用 Matplotlib 显示原始图像及其标签:
from matplotlib import pyplot as plt
%matplotlib inline
plt.imshow(images[0, 0], cmap="gray")
plt.title("original image")
plt.axis("off")
plt.show()
生成的图像如下:
- 使用 Foolbox 查找对抗性实例:
gradient_estimator = foolbox.gradient_estimators.CoordinateWiseGradientEstimator(0.1)
fmodel = foolbox.models.ModelWithEstimatedGradients(fmodel, gradient_estimator)
adversary = foolbox.adversarial.Adversarial(
fmodel, criterion, images[0], labels[0], distance=metric
)
attack(adversary)
- 显示发现的对抗性样本:
plt.imshow(a.image[0], cmap="gray")
plt.title("adversarial image")
plt.axis("off")
plt.show()
print("Model prediction:", np.argmax(fmodel.predictions(adversary.image)))
生成的对抗性图像如下:
- 实例化一个在 MNIST 上训练的传统 CNN 模型:
from abs_models import models
traditional_model = models.get_CNN()
模型架构如下:
CNN(
(net): NN(
(conv_0): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
(bn_0): BatchNorm2d(20, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(nl_0): ELU(alpha=1.0)
(conv_1): Conv2d(20, 70, kernel_size=(4, 4), stride=(2, 2))
(bn_1): BatchNorm2d(70, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(nl_1): ELU(alpha=1.0)
(conv_2): Conv2d(70, 256, kernel_size=(3, 3), stride=(2, 2))
(bn_2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(nl_2): ELU(alpha=1.0)
(conv_3): Conv2d(256, 10, kernel_size=(5, 5), stride=(1, 1))
)
(model): NN(
(conv_0): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))
(bn_0): BatchNorm2d(20, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(nl_0): ELU(alpha=1.0)
(conv_1): Conv2d(20, 70, kernel_size=(4, 4), stride=(2, 2))
(bn_1): BatchNorm2d(70, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(nl_1): ELU(alpha=1.0)
(conv_2): Conv2d(70, 256, kernel_size=(3, 3), stride=(2, 2))
(bn_2): BatchNorm2d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
(nl_2): ELU(alpha=1.0)
(conv_3): Conv2d(256, 10, kernel_size=(5, 5), stride=(1, 1))
)
)
- 执行合理性检查,确保模型按预期运行:
preds, labels = predict_on_batch(traditional_model, batch, 5)
print(preds)
print(labels)
输出结果如下:
[7, 9, 5, 3, 3]
[7, 9, 5, 3, 3]
- 使用 Foolbox 包装传统模型:
if traditional_model.code_base == "tensorflow":
fmodel_traditional = foolbox.models.TensorFlowModel(
traditional_model.x_input,
traditional_model.pre_softmax,
(0.0, 1.0),
channel_axis=3,
)
elif traditional_model.code_base == "pytorch":
traditional_model.eval()
fmodel_traditional = foolbox.models.PyTorchModel(
traditional_model, bounds=(0.0, 1.0), num_classes=10, device=u.dev()
)
- 攻击传统 CNN 模型:
fmodel_traditional = foolbox.models.ModelWithEstimatedGradients(fmodel_traditional, GE)
adversarial_traditional = foolbox.adversarial.Adversarial(
fmodel_traditional, criterion, images[0], labels[0], distance=metric
)
attack(adversarial_traditional)
- 显示发现的对抗性样本:
plt.imshow(adversarial_traditional.image[0], cmap="gray")
plt.title("adversarial image")
plt.axis("off")
plt.show()
print(
"Model prediction:",
np.argmax(fmodel_traditional.predictions(adversarial_traditional.image)),
)
生成的对抗性图像如下:
它是如何工作的...
我们首先导入一个预训练的 ABS 模型(步骤 1)。在步骤 2和步骤 3中,我们定义了一个convenience
函数,用于预测一批 MNIST 图像并验证模型是否正常工作。接下来,我们使用 Foolbox 包装模型,为测试其对抗鲁棒性做准备(步骤 4)。请注意,Foolbox 可以通过相同的 API 攻击 TensorFlow 或 PyTorch 模型,一旦被包装。太棒了!在步骤 5中,我们选择了一张 MNIST 图像作为攻击的媒介。为了明确,这张图像会被调整和变形,直到结果能欺骗模型。在步骤 6中,我们选择了要实施的攻击类型。我们选择了边界攻击,这是一种基于决策的攻击,从一个大的对抗扰动开始,然后逐渐减小扰动,同时保持对抗性。这种攻击需要很少的超参数调整,因此无需替代模型或梯度计算。有关基于决策的攻击的更多信息,请参考arxiv.org/abs/1712.04248
。
此外,请注意,这里使用的度量标准是均方误差(MSE),它用于衡量对抗样本与原始图像之间的接近程度。使用的标准是误分类,意味着一旦目标模型误分类了图像,搜索就会停止。替代标准可能包括置信度或特定类型的误分类。在步骤 7-9中,我们展示了原始图像以及从其生成的对抗样本。在接下来的两步中,我们实例化一个标准的 CNN 并验证它是否正常工作。在步骤 12-14中,我们在标准 CNN 上重复之前步骤中的攻击。通过查看结果,我们可以看出,该实验强烈表明,ABS 模型在对抗扰动方面比普通的 CNN 更具鲁棒性。
使用 TensorFlow Privacy 的差分隐私
TensorFlow Privacy (github.com/tensorflow/privacy
) 是 TensorFlow 家族中的一个相对较新的成员。这个 Python 库包括用于训练具有差分隐私的机器学习模型的 TensorFlow 优化器的实现。一个经过差分隐私训练的模型,在从其数据集中删除任何单一训练实例时,不会发生非平凡的变化。差分隐私(近似值)使用epsilon和delta来量化,衡量模型对单个训练示例变化的敏感性。使用 Privacy 库的方法很简单,只需包装常见的优化器(例如 RMSprop、Adam 和 SGD)即可将它们转换为差分隐私版本。这个库还提供了方便的工具来衡量隐私保证、epsilon 和 delta。
在这个教程中,我们将展示如何使用 Keras 和 TensorFlow Privacy 实现并训练一个用于 MNIST 的差分隐私深度神经网络。
准备工作
本教程的准备工作包括安装 Keras 和 TensorFlow。相关命令如下:
pip install keras tensorflow
TensorFlow Privacy 的安装说明可以在 github.com/tensorflow/privacy
找到。
如何实现...
- 开始时定义几个便捷函数来预处理 MNIST 数据集:
import tensorflow as tf
def preprocess_observations(data):
"""Preprocesses MNIST images."""
data = np.array(data, dtype=np.float32) / 255
data = data.reshape(data.shape[0], 28, 28, 1)
return data
def preprocess_labels(labels):
"""Preprocess MNIST labels."""
labels = np.array(labels, dtype=np.int32)
labels = tf.keras.utils.to_categorical(labels, num_classes=10)
- 编写一个便捷函数来加载 MNIST 数据集:
def load_mnist():
"""Loads the MNIST dataset."""
(X_train, y_train), (X_test, y_test) = tf.keras.datasets.mnist.load_data()
X_train = preprocess_observations(X_train)
X_test = preprocess_observations(X_test)
y_train = preprocess_labels(y_train)
y_test = preprocess_labels(y_test)
return X_train, y_train, X_test, y_test
- 加载 MNIST 数据集:
import numpy as np
X_train, y_train, X_test, y_test = load_mnist()
训练集大小为 60k,测试集大小为 10k。
- 导入差分隐私优化器并定义几个控制学习率和差分隐私程度的参数:
from privacy.optimizers.dp_optimizer import DPGradientDescentGaussianOptimizer
optimizer = DPGradientDescentGaussianOptimizer(
l2_norm_clip=1.0, noise_multiplier=1.1, num_microbatches=250, learning_rate=0.15
)
loss = tf.keras.losses.CategoricalCrossentropy(
from_logits=True, reduction=tf.losses.Reduction.NONE
)
- 为了衡量隐私性,定义一个函数来计算 epsilon:
from privacy.analysis.rdp_accountant import compute_rdp
from privacy.analysis.rdp_accountant import get_privacy_spent
def compute_epsilon(steps):
"""Compute the privacy epsilon."""
orders = [1 + x / 10.0 for x in range(1, 100)] + list(range(12, 64))
sampling_probability = 250 / 60000
rdp = compute_rdp(
q=sampling_probability, noise_multiplier=1.1, steps=steps, orders=orders
)
return get_privacy_spent(orders, rdp, target_delta=1e-5)[0]
- 为 MNIST 定义一个标准的 Keras CNN:
NN_model = tf.keras.Sequential(
[
tf.keras.layers.Conv2D(
16, 8, strides=2, padding="same", activation="relu", input_shape=(28, 28, 1)
),
tf.keras.layers.MaxPool2D(2, 1),
tf.keras.layers.Conv2D(32, 4, strides=2, padding="valid", activation="relu"),
tf.keras.layers.MaxPool2D(2, 1),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(32, activation="relu"),
tf.keras.layers.Dense(10),
]
)
- 编译
model
:
NN_model.compile(optimizer=optimizer, loss=loss, metrics=["accuracy"])
- 拟合并测试
model
:
NN_model.fit(
X_train, y_train, epochs=1, validation_data=(X_test, y_test), batch_size=250
)
- 计算
epsilon
值,即隐私性的度量:
eps = compute_epsilon(1 * 60000 // 250)
它是如何工作的...
我们从 步骤 1-3 开始,准备并加载 MNIST 数据集。接下来,在 步骤 4 中,我们导入 DPGradientDescentGaussianOptimizer
,这是一种优化器,使模型具备差分隐私。此阶段使用了多个参数,并且这些参数有待进一步解释。l2_norm_clip
参数表示在每个小批量数据点的训练过程中,每个梯度的最大范数。该参数限制了优化器对单个训练点的敏感度,从而使模型朝着差分隐私的方向发展。noise_multiplier
参数控制向梯度中添加的随机噪声量。通常,噪声越多,隐私性越强。完成此步骤后,在 步骤 5 中,我们定义一个计算差分隐私的 epsilon-delta 定义的 epsilon 值的函数。我们在 步骤 6 中实例化一个标准的 Keras 神经网络,步骤 7 中编译它,然后使用差分隐私优化器在 MNIST 上训练它 (步骤 8)。最后,在 步骤 9 中,我们计算 epsilon 的值,该值衡量模型的差分隐私程度。本教程的典型 epsilon 值大约为 1。
附录
在本章中,我们为读者提供了一份创建基础设施的指南,以应对机器学习在网络安全数据中的挑战。特别是,我们提供了设置虚拟实验室环境的操作步骤,以便进行安全有效的恶意软件分析。我们还提供了使用虚拟 Python 环境的指南,允许用户在避免包冲突的同时无缝地进行不同的 Python 项目工作。
本章将介绍以下操作:
-
设置虚拟实验室环境
-
使用 Python 虚拟环境
设置虚拟实验室环境
为了保护自己和网络,在处理和分析恶意软件时采取预防措施是至关重要的。最好的方法之一是设置一个隔离的虚拟实验室环境。虚拟实验室环境由一个或多个虚拟机(VM)组成,并且在一个隔离的网络中。隔离网络可以防止恶意软件通过网络传播,虽然这会牺牲一些更真实的恶意软件行为。
准备工作
为了准备这个操作,请执行以下步骤:
- 安装虚拟机监控程序。
虚拟机监控程序是允许你控制虚拟机的软件。一个实例是 VirtualBox,可以免费下载,www.virtualbox.org/
:
- 下载虚拟镜像。
虚拟镜像是虚拟机的模板。可以在developer.microsoft.com/en-us/microsoft-edge/tools/vms/
找到多个 Windows 虚拟镜像:
如何操作...
以下步骤将指导你设置和使用一个简单的虚拟实验室环境:
- 使用虚拟镜像创建虚拟机。
打开虚拟机镜像时,你的屏幕应该看起来像这样:
- 配置虚拟机的性能和安全性。例如,你可以将其完全从网络中断开。
以下截图展示了如何将虚拟机从网络中断开:
- 创建一个快照。
你可以在这里查看哪个菜单选项允许你创建快照:
- (可选)在虚拟机中引爆并分析恶意软件。
例如,我在我的虚拟机中运行了勒索软件,如下所示:
- (可选)将虚拟机恢复到之前的快照。
按下恢复按钮,如下所示:
工作原理...
我们通过创建一个虚拟机(步骤 1)开始了本章节的操作。虚拟机的创建取决于提供镜像的格式。对于引用的虚拟镜像,双击 .ovf
文件即可设置虚拟机。有时,你可能需要创建一个全新的操作系统安装,并挂载虚拟镜像。接下来,在步骤 2中,我们为恶意软件分析配置了虚拟机。有几个配置更改你可能需要进行。这些包括设置基础内存、处理器数量、视频内存和虚拟光驱;选择合适的网络设置;以及创建共享文件夹。
完成此操作后,在步骤 3中,我们保存了一个快照,允许我们保存所有重要的状态信息。快照的一个优点是,它们使用户能够轻松地回滚对虚拟机所做的更改。因此,如果你犯了错误,没关系——只需恢复到先前的快照即可。接下来,在步骤 4中,我们在虚拟机中引爆了恶意软件。我们建议在此步骤中要小心,并且仅在你知道自己在做什么的情况下进行操作。在这种情况下,你将在本书的仓库中找到一份恶意软件数据集。最后,在步骤 5中,我们点击了 VirtualBox 中的恢复按钮,将虚拟机恢复到创建快照时的状态。
最后,我们向 Yasser Ali 表示感谢,他提供了以下建议:在 macOS 上安装 VirtualBox 时,用户应该为 Adobe 软件设置安全例外,以使用安全设置。
使用 Python 虚拟环境
假设你有两个项目——项目 A 和项目 B——它们的 Python 库需求相互冲突。例如,项目 A 需要 scikit-learn 版本 0.21,而项目 B 需要 scikit-learn 版本 >0.22。或者,也许一个项目需要 Python 3.6,而另一个项目需要 Python 3.7。你可以在从一个项目切换到另一个项目时卸载并重新安装相应的库或 Python 版本,但这样做既繁琐又不现实。为了解决依赖冲突问题,我们推荐使用 Python 虚拟环境。在本章节中,你将看到如何使用 Python 虚拟环境。
准备就绪
虚拟环境模块 venv
包含在 Python 3.3 及更高版本的标准库中。
如何操作...
创建并激活虚拟 Python 环境,请按照以下步骤操作:
- 在终端中,运行以下命令:
python -m venv "name-of-your-environment"
- 在 Linux 或 macOS 终端中,运行以下命令:
source "name-of-your-environment"/bin/activate
在 Windows 上,运行此命令:
"name-of-your-environment"/Scripts/activate.bat
- 安装所需的包。
工作原理...
我们从第一步开始,创建了一个虚拟的 Python 环境。-m
标志表示要使用的模块,在这个例子中是venv
。接下来,在第二步中,我们激活了我们的 Python 环境,这样我们就可以使用它并进行更改。请注意,Python 环境的文件夹结构在 Windows 上与 Linux 或 Mac 上有所不同。一个标志,表明该环境当前是激活状态的,是在终端中看到环境的名称,如下所示:
("name-of-your-environment")
在第三步中,您可以像往常一样安装包,如下面这个例子:
pip install numpy
并且放心,这不会影响您在该环境外的包。太棒了!