基于机器学习的恶意软件检测系统构建与实践

admin 2024年10月10日21:15:33评论54 views字数 6983阅读23分16秒阅读模式

简介

本文通过使用人工神经网络(ANN)来创建一个可行性的恶意软件检测系统,详细介绍了包含数据预处理特征工程、模型算法选择、模型训练等步骤;争取达到小学生都能够看懂的程度,不会讲具体算法原理啦(毕竟太偏数学基础,我也不会呀,太难了)

传统恶意检测方式

传统的恶意软件检测引擎依赖于使用签名,简单理解就是给已知恶意程序生成一个md5数据,使用这个md5来识别是不是恶意程序;很明显,此类检测手段很容易绕过,比如在程序中填充一些数据,生成的md5就会变化,那么就检测不到

Windows PE格式

Windows PE 格式 官方介绍:https://docs.microsoft.com/en-us/windows/desktop/debug/pe-format

1PE 具有多个标头,描述其属性和各种寻址细节,例如 PE 将在内存中加载的基地址以及入口点的位置。

1PE 有多个部分,每个部分包含数据(常量、全局变量等)、代码(在这种情况下该部分被标记为可执行)或有时两者兼有。

1PE 包含导入了哪些 API 以及从哪些系统库导入的声明    

基于机器学习的恶意软件检测系统构建与实践

例如,Firefox PE 部分如下所示

基于机器学习的恶意软件检测系统构建与实践

而在某些情况下,如果 PE 已经用UPX 之类的打包程序处理过,它的部分可能会看起来有点不同,因为主代码和数据部分被压缩了,并且添加了一个在运行时解压缩的代码存根:

基于机器学习的恶意软件检测系统构建与实践

数据集

约 20万 个Windows PE样本,分为恶意样本(VirusTotal 上有 10+ 检测结果)和 安全样本(已知且 VirusTotal 上有 0 检测结果)    

由于在同一个数据集上训练和测试模型没有多大意义(因为它可以在训练集上表现得非常好,但无法在新的样本上推广),因此该数据集将自动分为 3 个子集:

  • 训练集:包含70%的样本,用于训练。

  • 验证集:包含15% 的样本,用于在每个训练阶段对模型进行基准测试。

  • 测试集:包含 15% 的样本,用于训练后对模型进行基准测试。

理想情况下,数据集应定期使用较新的样本更新,并重新训练模型,以便即使在出现新的独特样本时也能保持其准确性

数据预处理特征工程

基于Windows PE格式,将这些本质上非常异构的值(它们是各种间隔的数字和可变长度的字符串)编码为标量数字向量,每个标量数字都在区间 [0.0,1.0] 内标准化,并且长度恒定,变成机器学习模型能够理解的数据

首先需要使用开源的工具ergo(https://github.com/evilsocket/ergo) 来生成一个检测项目

ergo create ergo-pe-sec

根据开源工具的用法,在encoder.py文件中实现特征提取算法,

各特征数据处理

基础属性

根据PE文件格式提取11个特征向量用于判断、检测,为true则即为1.0,为false则写0.0    

基于机器学习的恶意软件检测系统构建与实践

点击图片可查看完整电子表格

处理代码

Python                  
def encode_properties(pe):                  
global properties                  
props = np.array([0.0] * len(properties))                  
for idx, prop in enumerate(properties):                  
props[idx] = 1.0 if getattr(pe, prop) else 0.0                  
return props

PE入口点函数

PE 入口点函数的前 64 个字节,每个元素都[0.0,1.0]通过除以来标准化255这将有助于模型检测那些具有非常独特的入口点的可执行文件,这些入口点在同一家族的不同样本之间仅略有不同(可以将其视为一个非常基本的签名)

Python                  
ep_bytes=[0]*64                  
try:                  
ep_offset = pe.entrypoint - pe.optional_header.imagebase                  
ep_bytes = [int(b) for b in raw[ep_offset:ep_offset+64]]                  
except Exception as e:                  
log.warning("can't get entrypoint bytes from %s: %s", filepath, e)                  
# ...                  
# ...                  
def encode_entrypoint(ep):                  
while len(ep) < 64: # pad                  
ep += [0.0]                  
return np.array(ep) / 255.0 # normalize
       

重复字节数据特征

二进制文件中 ASCII 表每个字节重复数据 - 该数据点将对有关文件原始内容的基本统计信息进行编码

Python                  
ef encode_histogram(raw):                  
histo = np.bincount(np.frombuffer(raw, dtype=np.uint8), minlength=256)                  
histo = histo / histo.sum() # normalize                  
returnhisto

PE导入表

手动选择了数据集中最常见的 150 个库(https://github.com/evilsocket/ergo-pe-av/blob/master/encoder.py#L22),并且对于 PE 使用的每个 API,将相关库的列增加一,创建另一个包含 150 个值的直方图,然后根据导入的 API 总量进行归一化

Python                  
def encode_libraries(pe):                  
global libraries                  
                 
imports = {dll.name.lower():[api.name if not api.is_ordinal else api.iat_address                  
for api in dll.entries] for dll in pe.imports}                  
                 
libs = np.array([0.0] * len(libraries))                  
for idx, lib in enumerate(libraries):                  
calls = 0                  
dll= "%s.dll" % lib                  
if lib in imports:                  
calls = len(imports[lib])                  
elif dll in imports:                  
calls = len(imports[dll])                  
libs[idx] += calls                  
tot = libs.sum()                  
return ( libs / tot ) if tot > 0 else libs # normalize
       

PE Section特征

对 PESection的一些信息进行编码,例如包含代码的部分与包含数据的部分的数量、标记为可执行的部分、每个部分的平均香农熵以及它们的大小与虚拟大小的平均比率 - 这些数据点将告诉模型 PE 是否以及如何被打包/压缩/混淆:

Python                  
def encode_sections(pe):                  
sections = [{                  
'characteristics': ','.join(map(str, s.characteristics_lists)),                  
'entropy': s.entropy,                  
'name': s.name,                  
'size': s.size,                  
'vsize': s.virtual_size } for s in pe.sections]                  
                 
num_sections = len(sections)                  
max_entropy= max(

展开收缩
for s in sections]) if num_sections else 0.0                  
max_size= max(
展开收缩
for s in sections]) if num_sections else 0.0                  
min_vsize= min(
展开收缩
for s in sections]) if num_sections else 0.0                  
norm_size= (max_size / min_vsize) if min_vsize > 0 else 0.0                  
                 
return [                  
# code_sections_ratio                  
(len(
展开收缩
]) / num_sections) if num_sections else 0,                  
# pec_sections_ratio                  
(len(
展开收缩
]) / num_sections) if num_sections else 0,                  
# sections_avg_entropy                  
((sum(
展开收缩
for s in sections]) / num_sections) / max_entropy) if max_entropy > 0 else 0.0,                  
# sections_vsize_avg_ratio                  
((sum(
展开收缩
/ s['vsize'] for s in sections]) / num_sections) / norm_size) if norm_size > 0 else 0.0,                  
]

特征数据汇总处理

将上述一个所有特征属性数据汇总到一起,代表一个PE文件的全部特征向量数据

Python                  
def encode_pe(filepath):                  
log.debug("encoding %s ...", filepath)                  
                 
if hasattr(filepath, 'read'):                  
raw = filepath.read()                  
                 
else:                  
with open(filepath, 'rb') as fp:                  
raw = fp.read()                  
                 
sz= len(raw)                  
pe= lief.PE.parse(list(raw))                  
ep_bytes = [0] * 64                  
try:                  
ep_offset = pe.entrypoint - pe.optional_header.imagebase                  
ep_bytes= [int(b) for b in raw[ep_offset:ep_offset+64]]                  
except Exception as e:                  
log.warning("can't get entrypoint bytes from %s: %s", filepath, e)                  
                 
v = np.concatenate([                  
encode_properties(pe),                  
encode_entrypoint(ep_bytes),                  
encode_histogram(raw),                  
encode_libraries(pe),                  
[ min(sz, pe.virtual_size) / max(sz, pe.virtual_size)],                  
encode_sections(pe)                  
])                  
                 
return v

通过自定义ergo 项目中prepare.py文件的prepare_input将恶意文件输入并调用encode_pe函数获取到对应文件的特征向量数据

Python                  
def prepare_input(x, is_encoding = False):                  
# file upload                  
if isinstance(x, werkzeug.datastructures.FileStorage):                  
return encoder.encode_pe(x)                  
# file path                  
elif os.path.isfile(x) :                  
return encoder.encode_pe(x)                  
# raw vector                  
else:                  
return x.split(',')
       

执行后我们将获取对应特征工程的数据

Python                  
ergo encode /path/to/ergo-pe-sec /path/to/dataset --output /path/to/dataset.csv

基于机器学习的恶意软件检测系统构建与实践

模型训练

将使用人工神经网络(ANN)的计算结构、在Adam 优化算法对其进行训练

人工神经网络(ANN)是一种模仿生物神经网络的计算模型。它通过大量的节点(或“神经元”)连接起来,形成一个多层的网络结构,可以用于各种任务,如分类、回归和模式识别。

ANN的基本组成部分包括:

输入层:接收输入数据。

隐藏层:处理输入数据,通常有多层,负责学习特征和模式。

输出层:产生模型的最终输出。

在训练过程中,ANN使用反向传播算法来调整权重,以最小化预测误差。常见的激活函数有ReLU、Sigmoid和Tanh等。    

基于机器学习的恶意软件检测系统构建与实践

假设我们的数据集中的数据点之间存在数值相关性,我们对此并不了解,但如果知道,我们将能够将该数据集划分为输出类。我们所做的是要求这个黑盒获取数据集并通过迭代调整其内部参数来近似此类函数

在创建的项目model.py文件中可以发现我们的 ANN 的实现,有两个隐藏层,每个层有 70 个神经元,ReLU作为激活函数,在训练期间有 30% 的dropout(正则化技术,主要用于防止神经网络过拟合。其基本思想是在训练过程中随机“丢弃”一部分神经元,使得网络在每个训练批次中只使用一部分神经元进行前向传播和反向传播)

Python                  
n_inputs = 486                  
                 
return Sequential([                  
Dense(70, input_shape=(n_inputs,), activation='relu'),                  
Dropout(0.3),                  
Dense(70, activation='relu'),                  
Dropout(0.3),                  
Dense(2, activation='softmax')                  
])
       

使用ergo开始模型训练过程,根据 CSV 文件中向量的总量,此过程可能需要几分钟、几小时甚至几天的时间。如果您的机器上有 GPU,Ergo 将自动使用它们而不是 CPU 核心,以显著加快训练速度

Python                  
ergo train /ergo-pe-sec --dataset /dataset.csv

模型评估

等待模型训练完成后,可以使用ergo view来查看模型性能统计数据

Python                  
ergo view /ergo-pe-sec

将显示训练历史记录,我们可以验证模型准确度确实随着时间的推移而增加(在我们的例子中,在第 30 个时期左右达到了 97% 的准确度),以及ROC 曲线,它告诉我们模型可以有效地区分恶意与否

基于机器学习的恶意软件检测系统构建与实践

此外,还将显示每个训练集、验证集和测试集的混淆矩阵。左上角的对角线值(深红色)表示正确预测的数量,而其他值(粉红色)表示错误预测的数量(我们的模型在约 30000 个样本的测试集上有 1.4% 的误报率):    

基于机器学习的恶意软件检测系统构建与实践

工程化验证

上述使用ergo已经写好了特征化工程、模型训练等脚本,现在只需要将该项目跑起来即可;按照ergo工具的使用方式,先删除掉临时文件

Python                  
ergo clean /ergo-pe-sec

加载模型并将其用作 API:

Python                  
ergo serve /ergo-pe-sec --classes "safe, malicious"

在本地向该检测服务上传一个恶意文件验证下效果

Python                  
curl -F "x=@/evil.exe" "http://localhost:8080/"

可以看到很明显已经检测出来为恶意文件的概率为0.999999999999

基于机器学习的恶意软件检测系统构建与实践

原文始发于微信公众号(暴暴的皮卡丘):基于机器学习的恶意软件检测系统构建与实践

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年10月10日21:15:33
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   基于机器学习的恶意软件检测系统构建与实践https://cn-sec.com/archives/3246542.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息