本文主要对 PE Malware Machine Learning Dataset 进行反汇编分析。首先从反汇编 PE 文件入手,逐步探索数据集的基本信息,并对数据集中的样本进行反汇编测试。最终,本文发现无法反汇编的 PE 文件主要集中在良性文件中。
1. 数据集基本信息
数据集概览
首先,查看数据集的基本信息,包括数据集的形状、特征列名称以及前 5 行数据示例。
import pandas as pd
# 读取 CSV 文件
df = pd.read_csv("samples.csv")
# 打印数据集的基本信息
print(f"数据集形状: {df.shape}")
print(f"列名: {df.columns}")
# 打印特定列的前 5 行数据
specific_columns = ['id', 'list', 'length', 'entropy']
print(f"前 5 行数据: \n{df[specific_columns].head(5)}")
输出结果
数据集形状: (201549, 12)
列名: Index(['id', 'md5', 'sha1', 'sha256', 'total', 'positives', 'list', 'filetype', 'submitted', 'user_id', 'length', 'entropy'], dtype='object')
前 5 行数据:
id list length entropy
0 233283 Whitelist 211456 2.231824
1 233284 Whitelist 17584 6.143784
2 233285 Whitelist 47104 5.694129
3 233286 Whitelist 242816 3.918845
4 233287 Whitelist 119240 6.168794
数据集分析
- 数据集大小:包含 201,549 行和 12 列。
- 特征列:包括
id,md5,sha1,sha256,total,positives,list,filetype,submitted,user_id,length,entropy。 - 样本示例:前 5 行数据显示了样本的 ID、列表类型、文件长度和熵值。
2. 数据集深入分析
数据集中的重复文件
检查数据集中是否存在重复的文件:
# 检查 md5 列是否有重复
print("md5 重复的行:")
print(df[df['md5'].duplicated()])
输出结果:
以下是 md5 重复的行:
Empty DataFrame
Columns: [id, md5, sha1, sha256, total, positives, list, filetype, submitted, user_id, length, entropy]
Index: []
文件类型分布
统计数据集中各种文件类型的分布情况:
# 统计 filetype 列中每个值出现的次数
print("filetype 列中每个值出现的次数:")
print(df['filetype'].value_counts())
输出结果:
filetype 列中每个值出现的次数:
filetype
exe 201549
Name: count, dtype: int64
用户 ID 分析
分析 user_id 列的含义及其分布:
# 统计 user_id 列中每个值出现的次数
print("user_id 列中每个值出现的次数:")
print(df['user_id'].value_counts())
输出结果:
user_id 列中每个值出现的次数:
user_id
1 201549
Name: count, dtype: int64
恶意软件标记情况
统计每个样本被多少种杀毒软件标记为恶意软件:
# 对 positives 列中每个值出现的次数进行排序
print("positives 列中值大小排序:")
print(df['positives'].value_counts().sort_index())
输出结果:
positives 列中值大小排序:
positives
0 85146
1 1553
2 230
3 83
4 37
...
66 143
67 40
68 26
69 9
70 3
Name: count, Length: 71, dtype: int64
恶意样本与良性样本的分布
统计数据集中恶意样本和良性样本的数量:
# 统计 list 列中每个值出现的次数
print("list 列中每个值出现的次数:")
print(df['list'].value_counts())
输出结果:
list 列中每个值出现的次数:
list
Blacklist 114737
Whitelist 86812
Name: count, dtype: int64
3. PE 文件反汇编
核心思路
PE 文件的入口点(Entry Point)是程序开始执行的地址。通过检查 pefile.PE.OPTIONAL_HEADER.AddressOfEntryPoint 是否位于任意一个段(section)中,可以判断当前 PE 文件是否包含有效的入口点。如果入口点不在任何段中,则该 PE 文件不包含有效的入口点。
反汇编代码实现
以下是使用 pefile 和 Capstone 库对 PE 文件进行反汇编的 Python 代码:
import pefile
from capstone import Cs, CS_ARCH_X86, CS_MODE_32
def disassemble_exe(file_path, output_file):
"""
对指定路径的 PE 文件进行反汇编,并将结果写入指定的输出文件。
:param file_path: 要反汇编的 PE 文件的路径。
:param output_file: 用于写入反汇编结果的文件对象。
"""
try:
# 加载 PE 文件
pe = pefile.PE(file_path)
# 获取程序入口点(Entry Point),程序开始执行的地址
entry_point = pe.OPTIONAL_HEADER.AddressOfEntryPoint
# 初始化代码段变量,用于存储包含入口点的代码段
code_section = None
# 遍历 PE 文件的所有段
for section in pe.sections:
# 检查当前段是否包含程序入口点
if section.contains_rva(entry_point):
# 如果包含,则将该段赋值给 code_section 并跳出循环
code_section = section
break
# 如果未找到包含入口点的代码段
if code_section is None:
# 向输出文件写入错误信息
output_file.write(f"{file_path}: 未找到包含入口点的代码段。\n")
print(f"{file_path}: 未找到包含入口点的代码段。\n")
# 结束函数执行
return
# 获取代码段的原始数据
code_data = code_section.get_data()
# 计算入口点在代码段中的偏移,以便从正确位置开始反汇编
entry_offset = entry_point - code_section.VirtualAddress
# 创建 Capstone 反汇编器,指定架构为 X86,模式为 32 位
md = Cs(CS_ARCH_X86, CS_MODE_32)
# 对代码段数据从入口点偏移处开始进行反汇编
for i in md.disasm(code_data[entry_offset:], entry_point):
# 将反汇编结果(地址、助记符和操作数)写入输出文件
output_file.write(f"0x{i.address:x}: {i.mnemonic} {i.op_str}\n")
# 捕获 PE 文件格式错误异常
except pefile.PEFormatError:
# 向输出文件写入错误信息
output_file.write(f"{file_path} 不是一个有效的 PE 文件。\n")
代码说明
- 入口点检查:通过遍历 PE 文件的所有段,检查入口点是否位于某个段中。如果未找到包含入口点的段,则输出错误信息。
- 反汇编过程:使用
Capstone库对代码段进行反汇编,并将结果写入输出文件。
4. 反汇编
分层抽样
从数据集中抽取 100 个样本进行反汇编测试,确保样本的分布与原数据集一致:
# 计算每个 list 值的比例
list_proportions = df['list'].value_counts(normalize=True)
# 计算每个 list 值需要抽取的样本数量
sample_sizes = (list_proportions * 100).round().astype(int)
# 进行分层抽样
stratified_sample = df.groupby('list', group_keys=False).apply(lambda x: x.sample(n=sample_sizes[x.name]))
# 打印抽样结果
print("list 列中每个值出现的次数:")
print(stratified_sample['list'].value_counts())
# 将抽样结果保存到 CSV 文件
stratified_sample.to_csv('stratified_sample.csv', index=False)
反汇编结果分析
对抽取的 100 个样本进行反汇编,并将结果保存在以文件名命名的文件夹中:
import os
# 定义要反汇编的文件目录
input_directory = 'test_data'
output_directory = 'armed'
# 遍历目录中的所有文件
for filename in os.listdir(input_directory):
file_path = os.path.join(input_directory, filename)
# 为每个文件创建对应的输出文件
output_filename = os.path.splitext(filename)[0] + '.txt'
output_path = os.path.join(output_directory, output_filename)
with open(output_path, 'w', encoding='utf-8') as output_file:
disassemble_exe(file_path, output_file)
将 100 个文件按照反汇编文件大小排序(从小到大),并查看基本信息
import os
import pandas as pd
# 定义要反汇编的文件目录
directory = '/home/hello/Documents/sunjk/Code/PE-Files/armed'
# 定义反汇编文件输出目录
directory_2 = '/home/hello/Documents/sunjk/Code/PE-Files/armed'
# 读取 samples.csv 文件
csv_path = "/data/Sunjk/PEFiles/samples.csv"
df = pd.read_csv(csv_path)
# 获取目录下所有文件及其大小
file_sizes = []
for filename in os.listdir(directory):
file_path = os.path.join(directory, filename)
if os.path.isfile(file_path):
KB_file_size = round(os.path.getsize(file_path) / 1024, 2)
file_id = os.path.splitext(filename)[0]
file_sizes.append((file_id, KB_file_size))
# 按照文件大小排序
file_sizes.sort(key=lambda x: x[1])
# 提取对应记录
ids = [file_id for file_id, _ in file_sizes]
df['id'] = df['id'].astype(str)
filtered_df = df[df['id'].isin(ids)]
# 确保按照文件大小排序
id_to_size = dict(file_sizes)
filtered_df['disasm_file_size'] = filtered_df['id'].map(id_to_size)
filtered_df = filtered_df.sort_values(by='disasm_file_size')
# 打印特定列的前 15 行数据
specific_columns = ['id', 'list', 'length', 'entropy', 'disasm_file_size']
print(f"df.head-15 of {specific_columns}: \n{filtered_df[specific_columns].head(15)}")
通过得到的结果可以看到,找不到入口点的文件集中在良性软件(Whitelist)中。
df.head-15 of ['id', 'list', 'length', 'entropy', 'disasm_file_size']:
id list length entropy disasm_file_size
1076 235072 Whitelist 3447808 4.693138 0.10
875 234627 Whitelist 6144 3.808214 0.10
1986 236869 Whitelist 10104 6.291658 0.10
20293 186138 Whitelist 116640 5.625627 0.10
15297 253900 Whitelist 1473024 6.271222 0.10
16896 255646 Whitelist 36352 5.545275 0.10
13555 252179 Whitelist 2629632 4.817298 0.10
13241 251804 Whitelist 121344 6.207069 0.10
45812 462460 Whitelist 68256 4.703089 0.10
47831 184601 Whitelist 3925760 6.621120 0.10
55162 192621 Whitelist 55752 6.321170 0.15
178895 2793 Blacklist 253440 7.994545 0.23
101174 664 Blacklist 855514 7.009666 0.31
1614 236173 Whitelist 25128 6.809167 0.40
123118 27300 Blacklist 135680 4.482153 0.48
5. 反汇编所有可执行文件并分析结果
对所有不含入口点的 PE 文件进行反汇编,并将这些文件的名称存储在 no_armed.txt 中,随后分析反汇编结果。
# 打开文件
with open('no_armed.txt', 'r', encoding='utf-8') as file:
# 读取文件的每一行并存储在列表中
lines = file.readlines()
# 去除每行末尾的换行符
lines = [line.strip() for line in lines]
# 统计不含入口点的文件数量
print(f"不含入口点的文件数量: {len(lines)}")
# 根据列表筛选 DataFrame 中 id 列的数据
filtered_df = df[df['id'].isin(lines)]
# 对筛选后的数据按照列表统计数据量
count_result = filtered_df['list'].value_counts()
print(count_result)
输出结果
不含入口点的文件数量: 8730
list
Whitelist 8684
Name: count, dtype: int64
结论
通过对 PE 文件的反汇编分析,可以发现部分文件不包含有效的入口点。这些文件主要集中在白名单中,可能是由于数据集被发布者精心处理过,无法反汇编发现恶意性的文件无法被界定恶意性,均被作者移除了。
关于 PE Malware Machine Learning Dataset 的更多信息,可以参考 PE Malware Machine Learning Dataset – Practical Security Analytics LLC。