
简介这份资源是面向计算机、网络安全及人工智能方向学生的加密恶意流量分析与检测平台完整项目基于Python与机器学习算法实现前端采用Flask框架搭建可视化界面可用于期末大作业、课程设计或毕业设计参考。项目围绕加密流量的特征提取与恶意行为识别展开涉及DoH、CTU-13等数据集的相关系数与Boruta特征筛选并输出模型评估结果适合具备一定Python基础、希望深入理解机器学习在安全领域落地的学习者。压缩包共217个文件约25.65MB包含4个核心Python脚本、14个HTML页面、1个CSS样式文件以及大量log运行日志、csv特征与模型结果、npy数据文件、pcap流量包和jpg、png图表完整覆盖从数据处理到前端展示的各个环节。目前已有159人学习。代码注释较为详尽下载后可直接运行读者既能获得一套可复现的检测流程与特征工程思路也能在此基础上进行二次开发或功能扩展。1. 从一份 97 分课设说起加密流量检测到底难在哪很多人第一次接触加密恶意流量检测都会卡在同一个地方流量已经加密了payload 全是乱码传统基于签名的 IDS 直接失效。这个项目的价值就在于它绕开了「解密」这条走不通的路改用流量的统计特征和时序行为做机器学习分类。整套代码基于 Python前端用 Flask 框架搭了一个可视化平台配套了 CTU-13 和 DoH 两个数据集的相关系数特征、Boruta 特征筛选结果以及模型评估 CSV。下载下来就能跑注释密度对新手友好想二开的人也能直接接上自己的数据集。它适合三类人赶课程设计和期末大作业的学生、想入门流量安全方向的 Python 开发者、以及需要快速搭一个检测原型的安全从业者。2. 加密流量特征工程从 pcap 到 Boruta 筛选后的特征矩阵2.1 为什么不能直接喂原始流量给模型加密流量的 payload 经过 TLS 或 DoH 封装后字节层面几乎没有可解释性。你拿一个 CNN 去硬吃原始字节在小数据集上过拟合几乎是必然的。常见做法是走「流级统计特征」这条路把一条流五元组相同的双向包序列压缩成几十维数值向量比如包长均值/方差、包间隔时间的均值/方差、上下行字节比、流持续时间、包数量等。这些特征不依赖 payload 内容加密与否都能算。项目里给出的ctu13_corr_features.csv和doh_corr_features.csv就是相关系数筛选后的特征集而ctu13_boruta_features.csv和doh_boruta_features.csv是 Boruta 算法筛选后的结果。Boruta 的思路是对每个特征做随机打乱生成「影子特征」然后跑随机森林看真实特征的重要性是否显著高于影子特征。高于的保留低于的剔除。这比单纯看相关系数更稳健因为它考虑了特征之间的交互。2.2 特征提取的代码骨架下面这段是我一般会用的流特征提取骨架和项目里的思路一致你可以直接抄去改import numpy as np from scapy.all import rdpcap, IP, TCP, UDP def extract_flow_features(pcap_path): packets rdpcap(pcap_path) flows {} for pkt in packets: if IP not in pkt: continue proto pkt[IP].proto src pkt[IP].src dst pkt[IP].dst # 用五元组做流标识这里简化成 src-dst-proto key (src, dst, proto) if key not in flows: flows[key] {lengths: [], times: [], fwd_bytes: 0, bwd_bytes: 0} flows[key][lengths].append(len(pkt)) flows[key][times].append(float(pkt.time)) flows[key][fwd_bytes] len(pkt) features [] for key, flow in flows.items(): lengths np.array(flow[lengths]) times np.array(flow[times]) iats np.diff(times) if len(times) 1 else np.array([0.0]) features.append({ pkt_count: len(lengths), len_mean: lengths.mean(), len_std: lengths.std(), iat_mean: iats.mean(), iat_std: iats.std(), duration: times[-1] - times[0] if len(times) 1 else 0.0, fwd_bytes: flow[fwd_bytes], }) return features逻辑说明先用 scapy 读 pcap按五元组聚合包序列再对每条流计算统计量。len_mean和len_std反映包大小分布恶意流量常出现固定大小的心跳包iat_mean和iat_std反映发包节奏C2 回连往往有规律性间隔。参数上pkt_count太小的流比如少于 3 个包建议直接丢弃否则统计量没有意义。2.3 Boruta 筛选的落地步骤拿到全量特征后不要直接扔进模型。先跑 Borutafrom boruta import BorutaPy from sklearn.ensemble import RandomForestClassifier import pandas as pd df pd.read_csv(ctu13_corr_features.csv) X df.drop(columns[label]).values y df[label].values rf RandomForestClassifier(n_estimators200, n_jobs-1, random_state42) boruta BorutaPy(rf, n_estimatorsauto, verbose2, random_state42) boruta.fit(X, y) selected df.columns[:-1][boruta.support_].tolist() print(保留特征数:, len(selected)) print(selected)n_estimatorsauto让 Boruta 自己决定树的数量random_state42保证可复现。跑完后boruta.support_是布尔数组True 的就是保留特征。项目里已经把这一步的结果存成了*_boruta_features.csv你可以直接对比相关系数筛选和 Boruta 筛选的差异——通常 Boruta 保留的特征更少但模型精度不会掉太多这就是特征冗余被砍掉的证据。3. Flask 平台搭建把模型推理包装成可交互的 Web 界面3.1 项目结构怎么读拿到源码包先别急着跑。按这个顺序看根目录下的style.css是前端样式show_data_ctu-13.html和show_data_doh.html是两个数据集的可视化页面*_model_result.csv是模型评估结果。Flask 的入口文件一般在app.py或run.py路由里会挂载数据展示和预测接口。.gitattributes是 Git 配置不影响运行。我一般会先确认三件事Python 版本建议 3.83.10、依赖清单requirements.txt或手动装、数据集路径是否硬编码。硬编码路径是课设项目最常见的翻车点换台机器就报 FileNotFoundError。3.2 最小可运行的 Flask 推理接口下面是一个把训练好的模型挂到 Flask 上的最小示例和项目里的做法一致from flask import Flask, request, jsonify import joblib import numpy as np app Flask(__name__) model joblib.load(model/ctu13_boruta_model.pkl) feature_names joblib.load(model/feature_names.pkl) app.route(/predict, methods[POST]) def predict(): data request.get_json() # 按训练时的特征顺序组装向量 vec np.array([[data.get(f, 0.0) for f in feature_names]]) pred model.predict(vec)[0] prob model.predict_proba(vec)[0].max() return jsonify({label: int(pred), confidence: round(float(prob), 4)}) if __name__ __main__: app.run(host0.0.0.0, port5000, debugTrue)逻辑说明joblib.load加载模型和特征名列表feature_names必须和训练时完全一致顺序错了预测结果就是玄学。request.get_json()接收前端传来的 JSONdata.get(f, 0.0)做缺省填充避免某个特征缺失导致 500。debugTrue只在开发时开部署时要关掉否则有安全风险。参数上host0.0.0.0让局域网内其他机器也能访问port5000是 Flask 默认端口被占用就换 5001。如果你要处理文件上传而不是 JSON把request.get_json()换成request.files[file]再用 pandas 读成 DataFrame。3.3 前端页面与数据展示的对接项目里的show_data_ctu-13.html和show_data_doh.html是静态展示页通常用 ECharts 或 Chart.js 画特征分布和模型评估曲线。对接 Flask 时常见做法是在路由里用render_template渲染同时把 CSV 读成 JSON 传给模板import pandas as pd from flask import render_template app.route(/dashboard/ctu13) def dashboard_ctu13(): df pd.read_csv(data/ctu13_boruta_model_result.csv) records df.to_dict(orientrecords) return render_template(show_data_ctu-13.html, recordsrecords)orientrecords把 DataFrame 转成列表套字典前端遍历时最方便。如果你的 HTML 里是写死的静态数据改成{{ records | tojson }}注入即可。注意 CSV 里的中文列名在 Windows 下可能乱码读的时候加encodingutf-8-sig。4. 模型训练与评估CTU-13 和 DoH 两个数据集的差异处理4.1 两个数据集不能混着训CTU-13 是僵尸网络流量数据集包含 Neris、Rbot、Virut 等多种恶意家族流量以明文和加密混合为主。DoH 数据集则是 DNS over HTTPS 流量全是加密的恶意样本主要是 DoH 隧道外传。这两个数据集的流量形态差异很大CTU-13 的流持续时间长、包数量多DoH 的流短、包小、间隔密集。所以项目里分别给出了ctu13_*和doh_*两套特征和模型结果这是对的。你要是把两个数据集混在一起训一个模型精度会掉得很难看因为特征分布不在一个量纲上。常见做法是分别训练、分别评估前端做两个入口。4.2 训练脚本与参数设置from sklearn.model_selection import train_test_split from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report, confusion_matrix import pandas as pd import joblib df pd.read_csv(ctu13_boruta_features.csv) X df.drop(columns[label]) y df[label] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, stratifyy, random_state42 ) clf RandomForestClassifier( n_estimators300, max_depth20, min_samples_leaf2, class_weightbalanced, n_jobs-1, random_state42 ) clf.fit(X_train, y_train) y_pred clf.predict(X_test) print(classification_report(y_test, y_pred)) print(confusion_matrix(y_test, y_pred)) joblib.dump(clf, model/ctu13_boruta_model.pkl) joblib.dump(list(X.columns), model/feature_names.pkl)逻辑说明stratifyy保证训练集和测试集的类别比例一致恶意流量样本通常远少于正常流量不加这个参数测试集可能一个恶意样本都没有。class_weightbalanced自动给少数类加权比手动调权重省事。max_depth20和min_samples_leaf2是防过拟合的树太深会把训练集背下来。评估时重点看恶意类别的 recall 和 F1不要只看 accuracy。如果正常流量占 95%你全预测正常也有 95% 的 accuracy但恶意流量一个没抓到。项目里的*_model_result.csv应该记录了这些指标你可以直接对比不同特征集下的表现。4.3 模型结果 CSV 怎么读doh_boruta_model_result.csv和ctu13_boruta_model_result.csv里通常有 accuracy、precision、recall、f1-score 几列可能还有不同模型的对比行。读的时候注意如果 CSV 里有多行对应不同模型随机森林、XGBoost、SVM你要看的是恶意类别的指标不是加权平均。加权平均会被正常流量拉高掩盖恶意检测的真实水平。5. 避坑与排查跑不起来、精度低、页面空白怎么查5.1 现象pip install 报错scapy 装不上原因scapy 依赖 libpcapWindows 下没有预编译包Linux 下缺开发库。解决Windows 用pip install scapy一般能装纯 Python 版但读 pcap 需要额外装 NpcapUbuntu 先apt install libpcap-dev再 pip 装。如果只是跑模型不需要读 pcap可以跳过 scapy。5.2 现象Flask 启动后页面 404 或空白原因模板路径不对或者render_template找不到 HTML 文件。Flask 默认在templates/目录找模板静态文件在static/。项目里的show_data_ctu-13.html如果放在根目录要么移到templates/要么用render_template时写全相对路径。解决确认目录结构或者用app Flask(__name__, template_folder.)指定模板目录。5.3 现象模型预测结果全是同一个类别原因特征顺序错了或者输入向量全是 0。feature_names.pkl保存的顺序必须和训练时一致前端传 JSON 时字段名要对得上。解决在 predict 接口里打印vec的前几个值确认不是全 0再打印feature_names和训练时的列顺序对比。5.4 现象Boruta 跑几个小时不结束原因特征维度太高或者n_estimators设太大。Boruta 本身要跑多轮随机森林特征上百维时很慢。解决先用相关系数筛到 50 维以内再跑 Boruta或者把n_estimators从 auto 改成固定值如 100。项目里已经给了筛选结果 CSV赶时间的话直接用现成的不用重跑。5.5 现象CSV 读取中文乱码原因Windows 下 pandas 默认用 gbk 编码读而文件是 utf-8。解决pd.read_csv(path, encodingutf-8-sig)utf-8-sig能自动处理 BOM 头。如果还乱码用chardet检测编码后再读。6. 二开与验证换数据集、加模型、做交叉验证的三个技巧如果你想把这份课设改成自己的项目最直接的路子是换数据集。CTU-13 和 DoH 的 pcap 都能在网上找到公开版本但要注意换数据集后特征分布变了原来 Boruta 筛出来的特征不一定适用得重新跑一遍筛选。我一般会先抽 500 条流做快速验证确认特征提取脚本没报错再跑全量。加模型也是常见的二开方向。项目里用的是随机森林你可以加 XGBoost 或 LightGBM 做对比。下面是一个快速对比的写法from xgboost import XGBClassifier from sklearn.model_selection import cross_val_score models { RF: RandomForestClassifier(n_estimators300, class_weightbalanced, random_state42), XGB: XGBClassifier(n_estimators300, max_depth6, scale_pos_weight5, random_state42), } for name, m in models.items(): scores cross_val_score(m, X, y, cv5, scoringf1) print(f{name} F1: {scores.mean():.4f} ± {scores.std():.4f})scale_pos_weight5是 XGBoost 里处理类别不平衡的参数相当于给少数类加权。cross_val_score做 5 折交叉验证比单次 train_test_split 更稳。注意scoringf1默认是二分类的 F1多分类要改成f1_macro。验证模型有没有真的学到东西有个土办法把测试集里的恶意样本特征打乱一列看 F1 掉不掉。如果打乱后 F1 几乎不变说明那列特征根本没被用上Boruta 可能筛错了。这个技巧我每次做完特征工程都会跑一遍比看特征重要性排序更直观。还有一个容易忽略的点Flask 部署时不要用debugTrue跑生产。用gunicorn -w 4 -b 0.0.0.0:5000 app:app起多进程-w 4是 4 个 worker按 CPU 核数调。模型加载放在模块顶层不要放在路由函数里否则每次请求都重新加载模型慢得你怀疑人生。从那以后我每次拿到课设源码都强制先跑一遍最小推理链路——加载模型、构造一条假数据、调 predict 接口——确认通了再去看前端。希望帮到你。本文还有配套的精品资源点击获取