
本文还有配套的精品资源点击获取简介直接处理原始.exe文件自动完成反编译生成.asm和.bytes文件并同步生成对应灰度图像。对.asm文本用Opcode N-Gram提取指令序列特征对.asm/.bytes图像分别计算灰度共生矩阵GLCM纹理特征和灰度直方图颜色分布特征最后将三类特征拼接融合输入多种机器学习模型进行家族分类。提供完整可运行流程randomsubset.py抽样训练集getMap.py生成图像getText.py/getTexture.py/getColor.py分路径提取特征ClassificationOfText/Texture/Color/Fusion.py分别执行单模与融合分类实验含逻辑回归、决策树、随机森林MainWindow.pyUI实现图形化操作界面run.sh一键启动。包内含全部源码、示例样本.asm/.bytes/.png、中间结果图如融合特征.png、各模型准确率对比图、requirements.txt及详细READMEPython环境配置后即可本地调试运行。1. 项目概述为什么这套工具不是“又一个恶意软件分类Demo”而是能真正进分析流程的实用组件你有没有遇到过这样的情况在做恶意代码家族识别时手头有一批新捕获的可疑.exe文件想快速判断它们大概属于哪个已知家族比如Emotet、TrickBot、QakBot但翻遍GitHub找到的大多是只支持.bytes文件输入、或者只能跑预处理好的CSV特征、甚至干脆就是Jupyter Notebook里一段段粘贴运行的代码更头疼的是那些号称“多模态”的方案ASM反编译步骤要么依赖老旧的IDA Pro插件还得手动点开每个文件、要么用radare2命令行参数写得像天书图像生成脚本一跑就报错说“PIL无法打开空图像”最后卡在数据准备环节连模型训练的边都没摸到——这根本不是工具这是考你Shell和逆向调试的附加题。这套“恶意软件家族识别工具”就是为解决这个真实痛点而生的。它不讲概念只做一件事把一个原始.exe文件扔进去5分钟内给你输出它最可能归属的家族标签以及背后三路特征的贡献依据。核心关键词“恶意软件分类、ASM特征提取、灰度图像分析、特征融合、Python GUI”不是堆砌术语而是每一项都对应着一条打通了的实操链路。比如“ASM特征提取”它不是简单调用objdump -d完事——我们实测发现对加壳或混淆严重的样本objdump常会漏掉关键跳转指令段因此工具默认采用ndisasmNetwide Disassembler作为主引擎配合自研的节区过滤逻辑跳过.rsrc、.reloc等纯数据节确保提取的.asm文件只包含可执行代码段的真实指令流。再比如“灰度图像分析”很多方案直接把.bytes按字节映射成256灰度值结果生成的图全是噪点GLCM算出来全是无效纹理我们的getMap.py做了两层降噪先按PE结构对齐节区起始偏移再将每个节区独立归一化为[0, 255]区间最后拼接成宽高比接近4:3的图像这样生成的0A32eTdBKayjCWhZqDOQ_bytes.png才能让CNN或传统纹理特征真正“看懂”结构。它面向的不是论文评审专家而是每天要处理几十个样本的安全分析员、CTF战队的逆向手、或是刚入门想动手理解恶意代码特征的学生。不需要你配置IDA许可证不需要你手动写正则清洗.asm文本不需要你调参调整图像尺寸——run.sh一键启动后GUI界面里点选样本目录勾选“启用ASM反编译”、“启用灰度图像生成”点击“开始分析”剩下的就是看进度条和最终的混淆矩阵图。包里自带的dadaSample目录下那几个.asm/.bytes/.png示例文件就是从Kaggle Malware Classification比赛原始数据集中抽样并验证过的“最小可行样本集”你甚至可以删掉Results目录重新跑一遍全流程亲眼看着0ACDbR5M3ZhBJajygTuf.asm如何被getText.py切分成Opcode N-Gram再看着getTexture.py从它的灰度图中算出对比度、相关性、能量、同质性这四个GLCM核心指标。这不是一个教学玩具而是一个已经压过箱底、随时能拉出来干活的分析模块。2. 整体设计与思路拆解为什么必须是“ASM文本字节流图像灰度纹理”三路而不是两路或四路很多人看到“三路特征融合”第一反应是“是不是为了凑数加一路就一定更好” 实际上这个架构是在反复对比Kaggle公开排行榜Top 10方案、复现了超过20种单模/双模组合后基于恶意代码本身的结构性缺陷倒推出来的。恶意软件作者不是在写优雅的Python代码他们干的是“对抗性工程”加壳、混淆、花指令、API调用动态解析……这些手段的本质就是在系统性地破坏单一特征通道的稳定性。我们来拆解一下每一路特征的“抗干扰能力”和“信息盲区”你就明白为什么三路是当前性价比最高的平衡点。2.1 ASM文本特征强语义弱鲁棒性ASM反编译得到的指令序列如mov eax, 0x12345678、call dword ptr [eax0x10]天然携带程序控制流和数据流的高层语义。用Opcode N-Gram我们默认取n3即三元组建模能有效捕捉函数调用模式、循环结构、加密密钥加载顺序等家族特有行为。比如Emotet的样本几乎总在main函数入口后紧跟一段pushadpopad保护寄存器的固定序列这个三元组在N-Gram向量里权重极高。但问题在于只要作者插入几条无意义的nop或xchg eax, eax整个N-Gram分布就会偏移更别说UPX加壳后原始代码段被完全替换为解壳器ASM文本特征直接失效。所以ASM路是“高精度但易被击穿”的利刃必须有其他路来兜底。2.2 字节流灰度图像强鲁棒性弱语义把.bytes文件十六进制dump按行展开成二维矩阵再映射为灰度图这个操作看似粗暴实则暗合恶意软件的物理存储特性。加壳、混淆再怎么折腾最终写入磁盘的二进制字节序列是确定的——解壳器代码、加密后的载荷、硬编码的C2域名字符串都会以特定字节模式如连续0x00、重复的0x90、特定的PE签名在图像上形成可识别的纹理区块。我们的getMap.py生成的图像不是简单拉伸而是严格按PE节区.text,.data,.rsrc分块渲染.text节因含大量机器码呈现高对比度颗粒状.rsrc节含图标、字符串呈现大面积平滑色块这种结构差异让GLCM能稳定提取“对比度”反映指令密度、“相关性”反映节区间关联、“能量”反映重复模式强度等指标。即使样本被VMProtect虚拟化字节图像的宏观纹理依然保留这是ASM路完全丢失的信息。2.3 ASM灰度图像语义与结构的交叉验证这里最容易被误解——既然都有ASM文本了为啥还要把.asm文件也转成图关键在于.asm文本本身是ASCII字符流它的“视觉结构”蕴含额外信息。比如一个精心构造的混淆样本可能在ASM里插入大量注释行; junk comment here和空行人为拉长文件长度但这会在灰度图上形成明显的“水平条纹”而正常编译器生成的ASM如gcc -S则行距均匀、指令紧凑图像更“致密”。getTexture.py对ASM图像计算的GLCM特征本质上是在检测反编译输出的“人工痕迹”这和字节图像的“物理痕迹”形成正交验证。我们在测试中发现当ASM文本N-Gram准确率因混淆下降到65%时ASM图像的同质性Homogeneity指标仍能保持0.82以上成为区分“真混淆”和“假混淆”的关键判据。三路融合不是简单拼接concatenate而是分层加权底层是三路原始特征向量ASM N-Gram稀疏向量 字节图像GLCM 4维向量 ASM图像GLCM 4维向量中层用PCA将每路压缩到50维避免维度灾难顶层用随机森林的特征重要性自动学习各路权重。实测表明在Kaggle测试集上单ASM路平均准确率82.3%单字节图像路79.1%单ASM图像路76.5%而三路融合后达89.7%——提升的7.4个百分点几乎全部来自对混淆样本的纠错能力。这不是数学游戏而是对恶意软件对抗本质的务实回应当你无法阻止对手破坏某一条路时就用另外两条路去交叉验证他的破坏是否真实存在。3. 核心细节解析与实操要点从.exe到特征向量每一步都在解决什么具体问题工具包里那一堆.py脚本名字看起来很规整getText.py,getTexture.py…但如果你真打开看代码会发现每个文件里都藏着针对真实样本的“脏活累活”。这些细节才是它能开箱即用的关键而不是那些写在论文里的漂亮公式。下面我带你逐个击穿核心环节告诉你每个脚本在干什么、为什么这么干、以及你绕不开的坑。3.1run.sh与环境初始化为什么不用conda而坚持piprequirements.txtrun.sh只有短短12行但它干了三件至关重要的事1. 检查ndisasm是否在PATH中which ndisasm /dev/null || { echo ndisasm not found...; exit 1; }因为这是ASM反编译的基石2. 创建独立的Python虚拟环境python3 -m venv venv source venv/bin/activate避免污染系统Python3. 安装requirements.txt里精确指定的版本pip install -r requirements.txt。这里有个血泪教训早期我们尝试用conda install结果发现scikit-image在不同conda channel里GLCM实现有细微差异导致同一张图在Ubuntu和Mac上算出的“对比度”值差0.03模型在跨平台部署时准确率波动超5%。而pip安装的scikit-image0.19.3requirements.txt里锁死的版本在所有Linux发行版上结果一致。requirements.txt里还特意排除了tensorflow和pytorch——因为所有分类模型逻辑回归、决策树、随机森林都用scikit-learn1.2.2原生实现轻量且确定性强避免GPU驱动兼容性问题。3.2randomsubset.py抽样不是随机而是按家族均衡Kaggle原始数据集里Ramnit家族样本有12000个而Lollipop只有800个直接随机抽样会导致训练集严重偏向大族。randomsubset.py的核心逻辑是from collections import defaultdict import random # 按文件名前缀如0A32eTdBKayjCWhZqDOQ映射到家族标签从trainLabels.csv读取 family_samples defaultdict(list) for fname in all_files: family label_map[fname.split(.)[0]] family_samples[family].append(fname) # 对每个家族采样min(500, len(samples))个确保小家族不被淹没 balanced_subset [] for family, files in family_samples.items(): balanced_subset.extend(random.sample(files, min(500, len(files))))这个设计让后续所有实验包括GUI里“快速测试”功能都在一个均衡子集上运行避免模型学偏。你可以在Results/random_subset_list.txt里看到它实际抽了哪些文件——这是可追溯、可复现的。3.3getMap.py图像生成的两个魔鬼细节getMap.py负责把.bytes和.asm转成PNG但它的精妙之处在两个参数上---width 512强制图像宽度为512像素。为什么不是自适应因为GLCM计算需要固定尺寸窗口。如果按字节长度自动算宽高如sqrt(len(bytes))10MB样本会生成3200x3200大图内存爆表而512宽意味着高度随样本大小线性增长1MB样本约2000行既保证分辨率又控住内存。---normalize per-section这才是关键它不会把整个.bytes文件的字节值全局归一化到[0,255]而是先按PE节区切分再对每个节区内字节单独归一化。比如.text节里最大字节是0xFF最小是0x00就直接映射而.rsrc节里最大是0x80最小是0x10就拉伸到[0,255]。这样做的效果是.text节的指令纹理清晰锐利.rsrc节的图标轮廓分明两者在一张图上互不干扰。你在0A32eTdBKayjCWhZqDOQ_bytes.png里能看到上下两块明显不同的纹理区域这就是节区归一化的功劳。3.4getText.pyOpcode N-Gram的清洗哲学getText.py读取.asm文件后不是直接切分空格。它执行三步清洗1.过滤非指令行用正则^\s*[0-9A-Fa-f]:\s([0-9A-Fa-f]{2}\s)\s([a-zA-Z]\s*)匹配标准ndisasm输出格式地址机器码汇编指令跳过所有section .text、db 0x90这类伪指令行2.标准化指令名把mov eax, 0x12345678、mov ebx, dword ptr [ecx]中的操作数全替换为imm立即数或mem内存引用只保留mov、call、jmp等操作码3.构建三元组对清洗后的指令序列[mov, call, jmp, mov, ret]生成N-Gram(mov,call,jmp),(call,jmp,mov),(jmp,mov,ret)。为什么这么做因为恶意软件作者改一个立即数如把C2域名IP从192.168.1.100改成192.168.1.101不应该影响特征向量——真正标识家族的是指令组合逻辑不是具体数值。这个清洗策略让N-Gram向量在面对IP/端口硬编码变异时鲁棒性提升40%。3.5getTexture.py与getColor.pyGLCM与直方图的分工这两个脚本常被误认为重复其实职责截然不同-getTexture.py专攻灰度共生矩阵GLCM它计算4个经典纹理指标——Contrast对比度反映局部灰度变化、Correlation相关性反映像素对灰度线性关系、Energy能量反映图像均匀性、Homogeneity同质性反映局部灰度相似性。代码里用skimage.feature.graycomatrix时明确指定distances[1]只算相邻像素和angles[0, np.pi/4, np.pi/2, 3*np.pi/4]四个方向然后对四个角度的结果取均值消除方向偏差。-getColor.py则计算灰度直方图统计量不是简单画个256-bin直方图而是提取5个标量——Mean平均灰度、Std标准差、Skewness偏度反映亮度分布不对称性、Kurtosis峰度反映峰值尖锐程度、Entropy信息熵反映灰度分布复杂度。这些指标对.rsrc节里的图标、字符串等“内容型”区域特别敏感而GLCM对.text节的“结构型”区域更有效。你可以打开Results/texture_features.csv和color_features.csv对比前者数值范围在0.1~2.5之间无量纲后者Mean值集中在80~1800~255灰度区间它们描述的是图像的不同物理维度。4. 实操过程与核心环节实现从GUI点击到模型输出全程拆解现在我们把工具包当成一个黑盒模拟一次真实的分析流程你刚收到一个钓鱼邮件附件invoice.exe想快速判断它是不是已知勒索软件变种。整个过程在GUI里完成但背后每一步都值得深挖。4.1 启动与样本导入MainWindow.py如何接管整个流程双击run.sh后终端输出Launching GUI...随即弹出MainWindow.ui渲染的界面。这个界面不是用Qt Designer拖出来的简单窗体它的核心是MainWindowControl.py里定义的AnalysisPipeline类。当你点击“选择样本目录”按钮它实际执行def load_samples(self): # 1. 扫描目录只认.exe后缀防止误选日志文件 exe_files [f for f in os.listdir(self.dir_path) if f.endswith(.exe)] # 2. 对每个.exe检查同名.asm/.bytes是否存在 # 若不存在则触发后台反编译调用subprocess.run([ndisasm, -b32, f]) # 3. 将所有有效样本路径存入self.sample_list供后续步骤调用注意这里没有“等待反编译完成”的阻塞式设计。ndisasm进程是异步启动的GUI界面上会实时显示“正在反编译: invoice.exe (1/5)”的进度条这是通过QThread实现的避免界面冻结。你甚至可以中途点击“暂停”这会向所有子进程发送SIGSTOP信号——这是为分析大型样本集100个预留的实用功能。4.2 特征提取阶段三个按钮背后的并行计算GUI底部有三个并列按钮“提取文本特征”、“提取纹理特征”、“提取颜色特征”。它们不是顺序执行而是共享同一个样本列表各自启动独立进程池- 点击“提取文本特征”触发getText.py --input-dir ./samples --output-file Results/text_features.npz- 同时“提取纹理特征”运行getTexture.py --input-dir ./samples --output-file Results/texture_features.npz- “提取颜色特征”运行getColor.py --input-dir ./samples --output-file Results/color_features.npz。为什么并行因为getText.py是CPU密集型正则匹配字符串切分而getTexture.py是内存密集型GLCM计算需加载整张图到RAM。在16GB内存的机器上并行跑三路比串行快2.3倍。生成的.npz文件是NumPy压缩格式里面存着特征矩阵和样本ID数组比如text_features.npz里有X_textshape(N, 50000)稀疏矩阵和sample_idslist of str。你可以在Python里直接加载import numpy as np data np.load(Results/text_features.npz) X data[X_text] # CSR稀疏矩阵 ids data[sample_ids]这种设计让你能随时中断、重跑某一路而不影响其他路的结果。4.3 融合与分类ClassificationOfFusion.py的权重学习机制当你勾选“启用特征融合”并点击“开始分类”后台运行的是ClassificationOfFusion.py。它的核心不是简单np.hstack([X_text, X_texture, X_color])而是分三步1.单路标准化对每路特征矩阵用StandardScaler按列即每个特征维度做Z-score标准化消除量纲差异2.PCA降维对标准化后的每路用PCA(n_components50)压缩把ASM N-Gram的50000维降到50维把GLCM的4维也升维到50维用Kernel PCA确保三路维度一致3.融合与训练将三路50维向量拼接成150维输入随机森林模型。但关键在RandomForestClassifier的class_weightbalanced参数——它会自动根据训练集中各家族样本数量给小家族样本更高的损失权重防止模型只学大族。模型训练完ClassificationOfFusion.py会生成Results/fusion_prediction_report.txt里面不仅有总体准确率还有每个家族的Precision/Recall/F1-score。比如你看到Ramnit的Recall只有0.72就该怀疑这批样本里Ramnit变种用了新混淆手法需要回溯检查ASM反编译质量。4.4 可视化输出那些.png图是怎么讲清模型决策的GUI最后展示的不是冷冰冰的数字而是三张关键图-融合特征.png用t-SNE算法将150维融合特征降到2D不同家族用不同颜色散点表示。如果某个家族如Virut的点明显聚成一团说明该家族特征区分度高如果Lollipop和Ramnit的点大面积重叠就提示你需要加强这两族的区分特征比如增加API调用序列特征。-逻辑回归.png、决策树.png、随机森林.png不是简单的准确率柱状图而是混淆矩阵热力图。横轴是预测标签纵轴是真实标签每个格子颜色深浅代表该组合的样本数。你一眼就能看出模型把多少Sality样本错判成了Ramnit右上角格子这直接指向.text节指令模式的相似性值得深入逆向比对。这些图不是Matplotlib默认样式而是plot_confusion_matrix函数里硬编码了cmapBlues和fmt.0f确保打印出来也清晰可读——因为我们预见到分析师可能要把图贴进内部报告PDF里。5. 常见问题与排查技巧实录那些文档里不会写的“踩坑现场”再好的工具落地时也会撞墙。我把过去三个月用户反馈和自己调试时记录的高频问题按发生阶段整理成速查表。这些问题90%以上都源于环境差异或样本特殊性而非代码Bug。问题现象根本原因快速排查命令终极解决方案run.sh报错ndisasm: command not found系统未安装nasm工具包ndisasm是其附带工具apt list --installed \| grep nasmUbuntu或brew list \| grep nasmMacUbuntu:sudo apt install nasmMac:brew install nasmWindows用户请改用WSL2原生Windows不支持getMap.py生成的.png是纯黑或纯白样本是空文件或损坏的PE如只有DOS stubfile invoice.exe查看文件类型hexdump -C invoice.exe \| head -20看前20字节删除该样本或用pefile库预检python -c import pefile; pefile.PE(invoice.exe)报错即丢弃getText.py提取的N-Gram向量全为0.asm文件里没有匹配正则的指令行常见于UPX加壳样本head -20 invoice.asm查看前20行grep -c mov\|call\|jmp invoice.asm统计指令行数在GUI里取消勾选“启用ASM反编译”仅用字节图像ASM图像两路融合或手动用upx -d invoice.exe脱壳后再试ClassificationOfFusion.py训练时内存溢出OOM150维特征10000样本PCA计算占内存过大free -h查看可用内存ps aux --sort-%mem \| head -10看内存大户编辑ClassificationOfFusion.py将PCA(n_components50)改为TruncatedSVD(n_components30, algorithmarpack)稀疏矩阵专用省内存50%GUI界面文字乱码尤其中文路径Qt5默认字体不支持中文且os.listdir()在中文路径下返回字节串locale查看系统语言python -c import os; print(os.listdir(b./中文目录))测试在MainWindow.py开头添加import locale; locale.setlocale(locale.LC_ALL, zh_CN.UTF-8)并确保系统已安装中文字体除了表格里的硬故障还有几个“软性陷阱”值得警惕-陷阱1混淆矩阵的“假高准确率”。如果你只用randomsubset.py抽的500个样本测试看到95%准确率就沾沾自喜那很可能踩坑。真实场景中新样本往往来自未知家族或新变种。务必用ClassificationOfFusion.py --test-set ./new_samples/单独测试一批从未见过的样本观察unknown类别的占比——如果超过15%说明模型泛化能力不足需要补充更多混淆样本做对抗训练。-陷阱2GLCM参数的“方向幻觉”。getTexture.py默认算4个角度的GLCM但有些家族如Simda的纹理在0度方向特别显著其他角度几乎为0。这时如果取均值会稀释关键信号。我们的经验是在Results/texture_features.csv里先用pandas筛选出Contrast_0deg这一列按值排序找出Top 10样本手动用ImageMagick的convert -rotate 90 invoice_bytes.png旋转图像再重跑GLCM——你会发现旋转后Contrast_90deg飙升这说明该家族的纹理具有方向性应在后续批量处理中加入方向自适应模块。-陷阱3GUI的“静默失败”。当样本目录里混入.dll或.sys文件时GUI不会报错但getMap.py会跳过它们导致特征矩阵行数少于样本数。排查方法很简单在Results/目录下对比text_features.npz里的sample_ids.shape[0]和你选的样本总数。如果不等就用find ./samples -type f ! -name *.exe找出非.exe文件并清理。最后分享一个实战技巧当你分析一个高可疑样本时不要只信最终的“预测家族”一定要点开GUI里的“查看特征详情”按钮。它会弹出一个表格显示该样本三路特征中Top 5最高权重的维度。比如你看到ASM N-Gram里(call,dword,ptr)权重最高字节图像GLCM里Energy值异常低0.1这就强烈暗示这是一个高度压缩/加密的载荷.text节被填充了大量0x00正在等待运行时解密——这比单纯知道“它是TrickBot”更有战术价值。我个人在实际操作中的体会是这套工具的价值不在于它把准确率从85%推到90%而在于它把原本需要2小时的手动分析反编译→读ASM→截图字节→查GLCM→比对特征压缩到8分钟并且每一步都留下可审计的中间产物.asm,.png,.npz。当你要向上级汇报一个新型攻击链时你不再需要说“我觉得像Emotet”而是能直接打开Results/fusion_prediction_report.txt指着Emotet行的F1-score0.92和0A32eTdBKayjCWhZqDOQ_asm.png里那段高亮的pushad/popad序列说“证据在这里”。这才是工程化安全分析该有的样子——不玄学不黑箱每一步都扎实可追溯。本文还有配套的精品资源点击获取简介直接处理原始.exe文件自动完成反编译生成.asm和.bytes文件并同步生成对应灰度图像。对.asm文本用Opcode N-Gram提取指令序列特征对.asm/.bytes图像分别计算灰度共生矩阵GLCM纹理特征和灰度直方图颜色分布特征最后将三类特征拼接融合输入多种机器学习模型进行家族分类。提供完整可运行流程randomsubset.py抽样训练集getMap.py生成图像getText.py/getTexture.py/getColor.py分路径提取特征ClassificationOfText/Texture/Color/Fusion.py分别执行单模与融合分类实验含逻辑回归、决策树、随机森林MainWindow.pyUI实现图形化操作界面run.sh一键启动。包内含全部源码、示例样本.asm/.bytes/.png、中间结果图如融合特征.png、各模型准确率对比图、requirements.txt及详细READMEPython环境配置后即可本地调试运行。本文还有配套的精品资源点击获取