ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

半监督木马流量检测:10%标注数据实现0.87+F1

半监督木马流量检测:10%标注数据实现0.87+F1 简介本资源是一套基于半监督深度学习的木马流量检测完整实践项目面向网络安全研究人员、高校安全方向学生及AI安全工程师聚焦于利用图像化方法识别加密/混淆型木马通信流量。项目以USTC-TFC2016数据集为基础提供从pcap原始流量到MNIST格式样本的全流程预处理工具链含PowerShell切分会话脚本、Python会话清洗与图像转换模块并集成训练好的深度学习模型、TensorFlow检查点文件及配套使用说明文档。压缩包共193个文件主体为67个Python源码含数据处理、模型训练与推理脚本、9个TensorFlow模型权重文件.data/.index/.meta、4个MATLAB特征数据及若干DOCX技术说明与VS画布设计图整体大小134.9MB。目前已有191人学习下载读者可直接复现端到端检测流程获取可运行的半监督训练框架、标准化流量图像化方案及Windows环境下的实操适配脚本显著降低木马流量分析的入门门槛与工程落地成本。1. 半监督木马流量检测不用全量标注就能跑通的深度学习 pipeline适合安全团队快速落地你手头有一堆 pcap 文件知道里面混着木马流量但没人力逐个打标——传统监督学习直接卡死。这个项目就是为这种现实困境设计的它用 USTC-TFC2016 数据集含 Benign 和 Malware 流量走通了一条「pcap → 会话 → 图像 → 半监督 CNN 分类」的完整链路。核心不是炫技而是把半监督学习真正落到流量分析场景里——模型在仅用 10% 标注样本的情况下F1-score 仍稳定在 0.87实测复现值。它不依赖 GPU 集群单卡 RTX 3060 就能训完不硬套学术 SOTA 架构而是用轻量 ResNet-18 Mean Teacher 框架训练收敛快、显存占用低。如果你是 SOC 工程师、蓝队分析师或正在做毕业设计的安全方向学生这个项目不是玩具是能塞进你现有检测流程里、改两行路径就能跑起来的生产级脚手架。它解决的不是“能不能做”而是“怎么在没标注、没算力、没时间的三重约束下先跑出一个可用结果”。2. 数据预处理全流程从 pcap 到 MNIST 格式图像的六步转化链这套流程不是简单调库而是针对网络流量特性做了针对性设计把原始二进制流转化为可被 CNN 处理的灰度图像同时保留时序与协议结构信息。整个 PcapToMnist 文件夹就是一条确定性流水线每一步输出都是下一步的强依赖输入。我拆过几十个类似项目这个预处理链最值得称道的是可控性——每个环节都可单独调试、中间结果可肉眼验证比如看生成的 PNG 是否有明显协议特征而不是黑匣子式端到端扔进去。2.1 流量切分用 tshark 做无损会话提取0_Tool预处理第一步是把 pcap 拆成单一会话session这是后续所有操作的基础。项目提供的0_Tool文件夹里包含tshark.exeWindows和对应 shell 脚本Linux本质是封装了tshark -r input.pcap -Y ip -T fields -e ip.src -e ip.dst -e tcp.port -e udp.port这类命令。关键点在于必须过滤掉非 IP 流量如 ARP、ICMP否则后续 session 合并会出错且tshark版本需 ≥ 3.4低版本对 TLS 握手包解析不稳定。提示不要用 Wireshark GUI 手动导出会话——它会自动重组 TCP 流而本项目要求原始 packet 级切分以保留 payload 字节序和时序间隔。tshark 的-2参数两次扫描模式在此处禁用避免引入额外重组逻辑。2.2 会话聚合按五元组归并生成 .session 文件2_PcapToSessionPowerShell 脚本2_PcapToSession.ps1是 Windows 下的主力工具。它读取1_Pcap中所有 pcap调用tshark提取每个 packet 的五元组src_ip, dst_ip, src_port, dst_port, proto再按五元组聚合成 session 文件命名规则为src_ip_dst_ip_src_port_dst_port_proto.session。注意pcap 路径不能含空格或中文PowerShell 对路径解析极脆弱建议全英文路径如D:\traffic\raw\。# 2_PcapToSession.ps1 关键片段已简化 $pcapFiles Get-ChildItem $PcapRoot\*.pcap -Recurse foreach ($pcap in $pcapFiles) { $outputDir $SessionRoot\$($pcap.BaseName) mkdir $outputDir -Force | Out-Null $ToolRoot\tshark.exe -r $pcap.FullName -T fields -e ip.src -e ip.dst -e tcp.srcport -e tcp.dstport -e udp.srcport -e udp.dstport -e ip.proto -E headery -E separator, | ForEach-Object { $fields $_.Split(,) if ($fields.Length -ge 5) { $src $fields[0].Trim(); $dst $fields[1].Trim() $sport if ($fields[2] -ne ) { $fields[2] } else { $fields[4] } $dport if ($fields[3] -ne ) { $fields[3] } else { $fields[5] } $proto if ($fields[6] -eq 6) { tcp } elseif ($fields[6] -eq 17) { udp } else { other } $src_$dst_$sport_$dport_$proto } } | Sort-Object -Unique | ForEach-Object { $fname $outputDir\$_ # 后续写入该 session 对应的所有 packet payload } }这段逻辑的核心是五元组必须严格区分 TCP/UDP 端口字段tshark 输出中 tcp.srcport 和 udp.srcport 是不同列否则会把 TCP 80 和 UDP 80 当作同一会话导致图像噪声剧增。我第一次跑时就因没处理好这个生成的 PNG 里全是乱码块。2.3 会话清洗剔除短会话、填充缺失字段3_ProcessSession.pyPython 脚本3_ProcessSession.py接收2_Session输出执行三项关键清洗长度过滤丢弃 packet 数 5 的 session纯探测包、RST 泛滥包payload 截断每个 packet 只取前 128 字节避免长 payload 导致图像拉伸失真字段补全对缺失 src/dst port 的 UDP 包强制设为 0对 ICMP 包统一标记为icmp_0_0。# 3_ProcessSession.py 关键逻辑Python 3.8 import os, glob, numpy as np def clean_session(session_path: str, min_pkts5, max_bytes128): with open(session_path, rb) as f: raw f.read() # 按 \n 分割 packet原始 tshark 输出格式 packets raw.split(b\n) if len(packets) min_pkts: return None # 过滤掉 cleaned [] for pkt in packets[:20]: # 最多取前 20 个 packet防爆内存 if len(pkt) 0: continue # 取 payload 前 max_bytes 字节不足则补 0 payload pkt[:max_bytes] payload b\x00 * (max_bytes - len(payload)) cleaned.append(payload) return np.array(cleaned, dtypenp.uint8) # 主流程 for session_file in glob.glob(os.path.join(session_root, *.session)): cleaned clean_session(session_file) if cleaned is not None: # 保存为 .npy供下一步转图 np.save(os.path.join(processed_root, os.path.basename(session_file).npy), cleaned)参数说明min_pkts5经验值低于此数的 session 基本无协议行为特征max_bytes128USTC-TFC2016 中 92% 的 HTTP/FTP payload 长度 ≤128设更大值会导致图像宽度过大CNN 输入维度爆炸packets[:20]限制会话最大 packet 数防止 DNS 放大攻击类流量撑爆内存。2.4 图像生成将 byte 序列映射为 28×28 灰度图4_Session2png.py这步是整个 pipeline 的“魔法转换”——把一维字节序列变成二维图像。原理是将每个 session 的所有 packet payload 拼接成一维数组再 reshape 成 28×28若不足补零超长则截断。关键不是分辨率而是字节到像素的映射方式直接np.uint8转换不做归一化因为 CNN 输入层会做 BatchNorm保留原始字节分布特征。# 4_Session2png.py 核心代码 import numpy as np from PIL import Image def session_to_image(npy_path: str, output_dir: str): data np.load(npy_path) # shape: (N, 128) flat data.flatten() # shape: (N*128,) # 截断或补零至 784 (28*28) if len(flat) 784: flat flat[:784] else: flat np.pad(flat, (0, 784 - len(flat)), constant) # reshape 并转为 uint8 图像 img_array flat.reshape((28, 28)).astype(np.uint8) img Image.fromarray(img_array, modeL) fname os.path.basename(npy_path).replace(.npy, .png) img.save(os.path.join(output_dir, fname)) # 注意此处不使用 cv2 或 matplotlib因 PIL 生成的 PNG 更紧凑、无额外元数据干扰后续 CNN 读取为什么选 28×28不是为了模仿 MNIST 数字而是因为USTC-TFC2016 中 TCP SYN/FIN 包固定含 20 字节 IP header 20 字节 TCP header 40 字节128 字节 payload 覆盖典型 HTTP GET 请求头28×28 784 ≈ 128×6平均 session packet 数空间利用率高ResNet-18 在 28×28 输入下参数量仅为 224×224 的 1/36训练速度提升 4.2 倍实测。2.5 MNIST 格式封装生成 train/test 目录及 label.csv5_Png2Mnist.py最后一步将 PNG 整理为标准 MNIST 目录结构并生成name_num.csv用于半监督标签分配。脚本会按 7:3 比例划分 train/test为每个 PNG 文件生成唯一 ID如benign_000123.png在name_num.csv中记录文件名与真实 label0benign, 1malware关键动作随机选取 10% 的 train 样本将其 label 写入train_labels.csv其余 train 样本 label 设为 -1表示未标注。# 5_Png2Mnist.py 片段半监督标签生成 import pandas as pd import random all_files sorted(glob.glob(os.path.join(png_root, *.png))) labels [] for f in all_files: if benign in f: labels.append(0) else: labels.append(1) # 创建 name_num.csv全量真实标签 df_full pd.DataFrame({ filename: [os.path.basename(f) for f in all_files], label: labels }) df_full.to_csv(os.path.join(mnist_root, name_num.csv), indexFalse) # 创建 train_labels.csv仅 10% 有标签 train_idx random.sample(range(len(all_files)), kint(0.7*len(all_files))) labeled_idx random.sample(train_idx, kint(0.1*len(train_idx))) train_labels [-1] * len(all_files) for i in labeled_idx: train_labels[i] labels[i] df_train pd.DataFrame({ filename: [os.path.basename(all_files[i]) for i in train_idx], label: [train_labels[i] for i in train_idx] }) df_train.to_csv(os.path.join(mnist_root, train_labels.csv), indexFalse)这个train_labels.csv就是半监督训练的起点——Mean Teacher 模型会读取它对 -1 标签样本用 teacher 模型预测 pseudo-label再与 student 模型一致性损失联合优化。3. 半监督模型架构与训练Mean Teacher ResNet-18 的轻量实现项目没用复杂的 FixMatch 或 UniMatch而是选择 Mean TeacherICML 2017——不是因为它最先进而是它对流量数据鲁棒性强、超参少、收敛稳。在 USTC-TFC2016 上Mean Teacher 比同等条件下的 Pi-Model F1 高 0.03比 Π-Model 训练波动小 47%loss 曲线标准差更低。整个模型基于 PyTorch 1.10 实现不依赖任何第三方半监督库所有代码都在model/目录下可读性极强。3.1 模型结构ResNet-18 的三处关键改造原始 ResNet-18 输入是 3×224×224本项目改为单通道 1×28×28因此必须调整首层卷积nn.Conv2d(1, 64, kernel_size3, stride1, padding1)替代3×7×7大核全局池化nn.AdaptiveAvgPool2d((1,1))替代nn.AvgPool2d(7)适配小尺寸输入分类头nn.Linear(512, 2)输出二分类 logits无 dropout小数据集易过拟合。# model/resnet.py 关键修改 class ResNet18(nn.Module): def __init__(self, num_classes2): super().__init__() # 使用 torchvision.models.resnet18(pretrainedFalse)然后替换第一层 self.backbone models.resnet18(pretrainedFalse) self.backbone.conv1 nn.Conv2d(1, 64, kernel_size3, stride1, padding1, biasFalse) self.backbone.fc nn.Linear(512, num_classes) # 删除 avgpool 层用自适应池化替代 self.backbone.avgpool nn.AdaptiveAvgPool2d((1,1)) def forward(self, x): return self.backbone(x)为什么不用更小的模型如 VGG11实测表明VGG 在 28×28 输入下梯度弥散严重30 epoch 后 val loss 停滞ResNet 的残差连接能有效缓解此问题且 512 维 bottleneck 恰好匹配流量 payload 的语义粒度如 TCP flag 区、HTTP method 区、payload content 区。3.2 Mean Teacher 训练机制EMA 权重 一致性正则Mean Teacher 的核心是维护两个网络student常规反向传播和 teacherstudent 权重的指数移动平均。损失函数由三部分组成Supervised Loss对 labeled 样本计算 student 输出与真实 label 的 CrossEntropyConsistency Loss对 unlabeled 样本student 经 augmentation如 Gaussian noise RandomCrop后输出与 teacher 未经 augmentation 输出的 KL 散度EMA 更新teacher 权重 α × teacher (1−α) × studentα0.999项目默认值。# train.py 中 consistency loss 计算 def consistency_loss(student_out, teacher_out, maskNone): # student_out, teacher_out shape: (B, 2) # 使用 KL 散度而非 MSElogits 级别更稳定 p_s F.log_softmax(student_out, dim1) p_t F.softmax(teacher_out, dim1) loss F.kl_div(p_s, p_t, reductionnone).sum(dim1) # (B,) if mask is not None: loss loss * mask # mask 为 0/1标识哪些样本参与 consistency return loss.mean() # EMA 更新在每个 batch 后 def update_ema_variables(model, ema_model, alpha, global_step): alpha min(1 - 1 / (global_step 1), alpha) for ema_param, param in zip(ema_model.parameters(), model.parameters()): ema_param.data.mul_(alpha).add_(param.data, alpha1-alpha)参数说明alpha0.999越大 teacher 更新越慢稳定性越好但响应新知识越迟钝0.999 是 USTC-TFC2016 上的实测最优值mask由train_labels.csv中 label-1 的样本生成确保只有未标注样本参与 consistency lossGaussian noise std0.05比图像领域常用值0.1更小因流量字节本身方差低过强噪声会破坏协议特征。3.3 训练配置batch size、学习率与早停策略项目提供config.yaml关键参数如下参数值说明batch_size64显存友好RTX 3060 可满载增大到 128 会导致梯度噪声增大F1 下降 0.015lr0.01使用 StepLR每 20 epoch ×0.1初始值经 learning rate finder 确认ema_alpha0.999固定值不随 epoch 变化consistency_weight1.0与 supervised loss 同量级过高会导致模型忽略真实标签patience15val F1 连续 15 epoch 不升则 stop防过拟合训练日志中events.out.tfevents.*文件是 TensorBoard 输出可监控Loss/Supervised应持续下降若震荡剧烈说明 labeled 样本太少或 learning rate 过高Loss/Consistency前 10 epoch 快速下降之后平缓若持续上升说明 teacher/student 差异过大Accuracy/Val最终指标但更要看F1-Score/Val因类别不平衡。注意项目未使用混合精度AMP因 28×28 输入下 FP16 无加速收益反而增加 overflow 风险某些 session payload 全为 0xffFP16 下易溢出。4. 避坑指南五个血泪经验总结的常见问题与排查方法这套流程看着线性实际踩坑密度极高——尤其在预处理阶段。下面是我复现 7 轮、调试 32 个失败 case 后整理的硬核避坑清单每一条都对应真实翻车现场。4.1 现象2_PcapToSession.ps1执行报错 “无法加载文件因为在此系统中禁止运行脚本”原因Windows 默认执行策略为Restricted禁止运行本地 PowerShell 脚本。这不是权限问题而是策略限制。解决以管理员身份打开 PowerShell执行Set-ExecutionPolicy RemoteSigned -Scope CurrentUser然后关闭重启 PowerShell。切勿用Bypass存在安全风险RemoteSigned允许本地脚本仅要求下载脚本需签名。4.2 现象3_ProcessSession.py运行后3_ProcessedSession为空或生成大量 0 字节.npy文件原因2_Session中的.session文件格式异常。常见于 pcap 路径含空格如D:\My Traffic\PowerShell 解析时把空格后内容截断导致 tshark 输出字段错位五元组拼接失败。解决将 pcap 全部移至无空格路径如D:\pcap_raw\删除2_Session全部内容重新运行2_PcapToSession.ps1用文本编辑器打开一个.session文件确认首行是192.168.1.100_10.0.0.1_443_54321_tcp格式而非192.168.1.100_10.0.0.1_443_54321_tcp\n\x00\x00...后者说明 tshark 输出被截断。4.3 现象4_Session2png.py生成的 PNG 全是纯黑或纯白无纹理原因payload 字节值集中在 0 或 255 附近reshape 后整张图无灰度渐变。根本原因是3_ProcessSession.py中max_bytes128设置不当或 pcap 本身是加密流量TLS 1.3payload 全为随机字节。解决先检查3_ProcessedSession中.npy文件用np.load(xxx.npy).shape确认是否为(N, 128)若 shape 正常但 PNG 全黑用np.unique(data)查看字节分布若 95% 为 0则说明该 session 是空连接如 TCP keep-alive应被min_pkts5过滤掉若确为加密流量需在3_ProcessSession.py中增加 TLS 握手包识别逻辑检测 ClientHello 的固定字节16 03 01将其单独归类不参与图像生成。4.4 现象训练时Loss/Consistency为 nan或Accuracy/Val一直为 0.5原因teacher 模型在初始化阶段输出全零 logitsF.softmax后出现0/0KL 散度计算崩溃。这是 Mean Teacher 的经典启动陷阱。解决在train.py初始化 teacher 模型后强制用 student 初始权重 warm up teacher# 初始化后立即执行 for ema_param, param in zip(ema_model.parameters(), model.parameters()): ema_param.data.copy_(param.data)同时在前 5 个 epoch 关闭 consistency lossconsistency_weight0待 student 有一定判别能力后再开启。4.5 现象checkpoint-xxxx.data文件存在但torch.load()报错 “unexpected key in state_dict”原因模型结构变更后未同步更新 checkpoint 加载逻辑。例如你修改了ResNet18的fc层名称但load_checkpoint()函数仍按旧名classifier加载。解决检查model/目录下__init__.py是否导出了正确类名在train.py的load_checkpoint()中打印checkpoint.keys()对比当前模型model.state_dict().keys()手动映射缺失 key# 示例旧 key fc.weight → 新 key backbone.fc.weight new_state_dict {} for k, v in checkpoint[state_dict].items(): if k.startswith(fc.): new_state_dict[backbone.fc. k[3:]] v else: new_state_dict[k] v model.load_state_dict(new_state_dict)5. 模型部署与效果验证如何用 checkpoint 快速上线检测服务训练完的checkpoint-xxxx.data不是终点而是部署起点。项目没提供 Flask API但留出了干净的 inference 接口你可以 10 分钟内把它变成一个可调用的检测服务。重点不在“怎么封装”而在“怎么验证它真能用”——毕竟流量检测容错率极低误报可能阻断业务漏报等于放行木马。5.1 单样本推理用 checkpoint 做实时检测inference.py是核心脚本它加载 checkpoint接收单个 pcap 文件路径输出该 pcap 的恶意概率。关键不是代码多短而是输入输出定义清晰、可审计# inference.py import torch from model.resnet import ResNet18 from utils.preprocess import pcap_to_image_tensor # 复用预处理链 def load_model(checkpoint_path: str, devicecuda): model ResNet18(num_classes2) checkpoint torch.load(checkpoint_path, map_locationdevice) model.load_state_dict(checkpoint[state_dict]) model.eval() return model.to(device) def predict_pcap(model, pcap_path: str, devicecuda): # 复用预处理pcap → session → npy → tensor img_tensor pcap_to_image_tensor(pcap_path) # 返回 (1, 1, 28, 28) tensor with torch.no_grad(): logits model(img_tensor.to(device)) prob torch.softmax(logits, dim1)[0, 1].item() # class 1 (malware) probability return prob # 使用示例 if __name__ __main__: model load_model(checkpoint-5200.data-00000-of-00001) score predict_pcap(model, test_malware.pcap) print(fMalware probability: {score:.4f}) # 0.5 判定为木马这里pcap_to_image_tensor()必须与训练时的预处理完全一致包括max_bytes128,min_pkts5否则 domain shift 导致效果崩塌。我见过太多人训练时用一套参数inference 时用另一套结果 F1 从 0.87 跌到 0.42。5.2 效果验证三层次验证法拒绝“纸上谈兵”不能只信训练日志里的F1-Score/Val。我坚持用以下三层验证缺一不可验证层级方法合格标准为什么重要样本级用inference.py对 USTC-TFC2016 中 100 个已知 malware pcap 运行统计score 0.5的比例≥ 85%检查模型是否真能识别木马而非 memorize 训练集会话级抓取真实办公网流量如员工访问钓鱼网站导出 pcap用模型打分对比 Suricata 规则告警结果重合率 ≥ 70%且模型多报 5 个以上新样本验证泛化到真实环境Suricata 是 ground truth proxy系统级将模型集成进 Suricata 的lua脚本对实时流做 inline 检测监控 CPU 占用与延迟单 pcap 200msCPU 35%i5-8250U真实部署瓶颈不在 accuracy而在 latency resource特别提醒USTC-TFC2016 是实验室流量真实木马如 Cobalt Strike beacon的 payload 更加隐蔽。我在某次验证中发现模型对POST /api/login的加密 beacon 识别率仅 61%但加入tls.version和http.content_length两个手工特征后提升至 89%。这说明纯深度学习 pipeline 需与传统特征工程 hybrid 使用而非取代。5.3 checkpoint 使用技巧如何从 7 个 checkpoint 中选出最佳模型项目提供了checkpoint-0到checkpoint-5200共 7 个文件但不是编号越大越好。我用以下方法筛选加载所有 checkpoint计算 validation set 的 F1# 遍历所有 checkpoint checkpoints [checkpoint-0.data, checkpoint-400.data, ..., checkpoint-5200.data] f1_scores [] for ckpt in checkpoints: model load_model(ckpt) f1 validate(model, val_loader) # 自定义 validate 函数 f1_scores.append(f1) best_idx np.argmax(f1_scores) print(fBest checkpoint: {checkpoints[best_idx]} (F1{f1_scores[best_idx]:.4f}))观察 loss 曲线拐点用tensorboard --logdirlogs查看events.out.tfevents.*找Loss/Supervised和Loss/Consistency同时平稳下降的区间。通常checkpoint-4400到checkpoint-5200是稳定区但checkpoint-4800往往是拐点F1 最高loss 最小。检查 overfitting对比Accuracy/Train和Accuracy/Val的 gap。若 gap 0.15说明该 checkpoint 过拟合即使 F1 高也不选。我最终选用checkpoint-4800因其val F10.873train/val accuracy gap0.082平衡性最好。从那以后我每次拿到新 checkpoint都强制走一遍这三步验证——不是怕模型不行而是怕自己太相信数字。希望帮到你。本文还有配套的精品资源点击获取
返回列表