
简介这是一套面向网络安全与机器学习交叉领域初学者及进阶实践者的高分课程设计级项目聚焦加密恶意流量的自动化识别与检测难题适用于CTF备赛、安全分析工具开发及AI安全教学场景。资源包含80个文件主体为14个核心Python脚本含Scapy流量采集getgoodx.py、PCAP解析模块、特征工程流水线、9个真实攻击流量样本pcap文件、8个HTML/CSS前端页面支撑Flask可视化界面以及模型文件pkl、训练日志log.txt、中英文README说明和多张系统界面截图PtSc*.png与分析图表PieChart.png整体压缩包仅1.11MB轻量易部署。已有148人学习下载提供从原始流量捕获、清洗建模SVM/随机森林/集成学习、到Web化上传监测的完整闭环方案附带可直接运行的Flask服务、预训练模型及详细文档特别适合理解安全场景下模型可解释性与工程落地的平衡实践。1. 这不是又一个“用 sklearn 跑个 SVM”的 demo它把 TLS 握手包里藏的恶意行为用可解释的特征Flask 界面真跑通了你见过多少「加密恶意流量检测」项目十有八九停在论文图、PPT 架构、或者pip install scikit-learn python train.py就戛然而止。但这个高分项目不一样——它从 Scapy 抓包开始硬啃 TLS/QUIC 流量中那些被加密掩盖的时序、长度、方向、重传模式等非内容型特征用真实 pcap 文件含 Mirai、DNS tunnel、HTTPS C2 等典型攻击样本训练出三个可部署模型SVM、RF、XGBoost最后塞进一个带上传、可视化、结果导出的 Flask Web 平台。它不碰明文解密不依赖私钥、不越权只靠流量元数据建模它不堆深度学习黑盒没用 LSTM/Transformer而是用特征工程可解释模型满足安全运维对「为什么判恶意」的刚性需求。适合正在做毕业设计、CTF 流量分析模块开发、或想落地轻量级网络威胁感知能力的安全工程师和机器学习初学者——尤其当你手头只有.pcap文件、没有标签数据、还要给 SOC 同事讲清楚「为什么这个包是 C2」时这份源码文档组合是少有的能抄作业、能改参数、能上线验证的完整闭环。2. 从原始 pcap 到可用特征为什么getgoodx.py和feature_engineer.py是整个项目的地基这个项目最耗时间、也最容易翻车的环节不是模型训练而是如何从加密流量里挖出稳定、鲁棒、业务可理解的特征。它没走「全包解析→提取 payload→统计熵值」这种玄学老路而是聚焦在 TLS/SSL/TCP 层的协议握手行为指纹和流级统计模式上。下面拆解两个核心脚本的真实逻辑和实操细节。2.1getgoodx.py用 Scapy 做「无侵入式」正常流量采集不是简单sniff()就完事这个脚本不是拿来即用的监听器而是一个可控采样策略引擎。它默认监听eth0但关键在于支持按「连接数阈值」和「流持续时间」双条件终止避免采集到半截 TCP 流或空闲长连接干扰后续建模# getgoodx.py 核心片段已去噪保留业务逻辑 from scapy.all import * import time import os def capture_normal_traffic(interfaceeth0, max_flows500, timeout_per_flow30): interface: 监听网卡建议用 bridge 或 veth pair 避免干扰宿主机 max_flows: 每次采集最多捕获多少个完整 TCP/UDP 流非包数 timeout_per_flow: 单个流最长等待秒数超时则丢弃防僵尸流 flows {} start_time time.time() def packet_handler(pkt): if not (TCP in pkt or UDP in pkt): return # 提取五元组作为流 key忽略 IP因内网环境常 NAT key (pkt[IP].src, pkt[IP].dst, pkt.sport, pkt.dport, pkt.proto) if key not in flows: flows[key] { pkts: [], start_ts: pkt.time, last_ts: pkt.time } flows[key][pkts].append(pkt) flows[key][last_ts] pkt.time # 检查是否超时或达到最大包数 if len(flows[key][pkts]) 1000 or \ (pkt.time - flows[key][start_ts]) timeout_per_flow: # 触发流结束保存为 .pcap save_flow_pcap(flows.pop(key), fnormal_flow_{int(time.time())}.pcap) sniff(ifaceinterface, prnpacket_handler, timeout60*10, # 总采集时长 10 分钟 storeFalse) # 清理未完成流超时未触发的 for key, flow in list(flows.items()): if len(flow[pkts]) 10: # 至少 10 包才认为有效 save_flow_pcap(flow, fnormal_flow_{int(time.time())}_incomplete.pcap) def save_flow_pcap(flow_dict, filename): wrpcap(filename, flow_dict[pkts]) print(f[] Saved {len(flow_dict[pkts])} packets to {filename}) if __name__ __main__: capture_normal_traffic(interfacebr0, max_flows300, timeout_per_flow45)提示getgoodx.py的成败取决于你的采集环境。我踩过最大的坑是直接在宿主机eth0上抓包结果混入大量 DNS、NTP、系统更新流量导致特征分布严重偏移。正确做法是用docker network create --driver bridge --subnet 172.20.0.0/16 net1创建隔离网桥让测试客户端如 curl、wget、浏览器走该网桥再在br0对应网桥的 host-side interface上监听。这样抓到的全是「可控应用行为」不是操作系统背景噪音。2.2feature_engineer.py把 pcap 转成 42 维特征向量每维都有业务含义项目没用tshark -T fields或pyshark而是坚持用 Scapy 原生解析——因为要精确控制 TLS 握手阶段ClientHello → ServerHello → Certificate → Finished的时序、长度、扩展字段存在性等细节。特征表不是随便列的而是按「协议层→行为层→统计层」三级组织特征大类具体特征示例计算方式业务含义是否用于 SVMTLS 握手层tls_ch_lenClientHello 包总长度含 TLS header反映客户端 TLS 实现如 OpenSSL vs BoringSSL✅tls_ext_countClientHello 中 TLS 扩展字段数量某些 C2 工具如 Cobalt Strike会省略扩展以规避检测✅is_tls_1_3是否为 TLS 1.3通过 handshake type version 判断TLS 1.3 的 0-RTT 行为易被滥用✅TCP 行为层syn_ack_ratioSYN 包数 / ACK 包数流内正常 HTTPS 流 ACK 密集C2 流常单向发包✅retransmit_rate重传包数 / 总包数恶意 C2 常用弱网络环境如 IoT 设备重传率异常高✅流统计层pkt_size_std所有包长度的标准差加密流量包长应较均匀C2 常混入小控制包大数据包✅inter_arrival_cv包到达间隔的变异系数CV std/mean正常流有应用层节奏如视频帧C2 常固定间隔心跳✅特征生成主函数extract_features_from_pcap(pcap_path)返回一个numpy.ndarrayshape(1, 42)每个维度严格对应上述表格。这不是为了凑维度而是为了让安全分析师能指着某维说「这个值 0.87说明它用了 TLS 1.3 的 0-RTT结合重传率 12%高度疑似 Mirai 变种」。代码里所有特征计算都附带# [REF: RFC 8446 Sec 4.1.2]类注释方便溯源。2.3 特征清洗的血泪经验为什么clean_and_normalize.py必须跑两遍原始特征矩阵必然含缺失值如某流没 TLS 握手、无穷值除零、极端离群点抓包中断导致的超长间隔。项目用两阶段清洗第一遍粗洗用SimpleImputer(strategymedian)填充数值型缺失OneHotEncoder(handle_unknownignore)处理类别型如 TLS version再用RobustScaler归一化比 StandardScaler 更抗离群点第二遍精洗对归一化后的矩阵用IsolationForest(contamination0.01)检测并剔除 1% 最异常样本——这些往往是抓包抖动、ARP 泛洪、或误标样本。注意clean_and_normalize.py的contamination参数不能设为 0.05 或更高。我试过一次设 0.05结果把所有 Mirai 样本当离群点删了——因为 Mirai 的retransmit_rate确实远高于正常流但这就是它的指纹。特征清洗不是让数据「看起来漂亮」而是让模型能区分「噪声」和「信号」。最终清洗后保留的样本中正常流与攻击流的pkt_size_std分布明显分离见ImageForReadme/PtSc1.png这才是有效清洗的标志。3. 三个模型怎么选SVM 不是摆设随机森林真能告诉你「哪个特征最致命」项目没用「AUC 最高者胜」的粗暴逻辑而是按安全运维场景分配模型角色SVM 用于实时 API 响应低延迟随机森林用于后台批量分析可解释性强XGBoost 作为兜底增强处理非线性交互。所有模型都在train_test/下有独立.py文件且训练脚本强制输出feature_importance.png和classification_report.txt。3.1 SVM用 RBF 核 GridSearchCV 找到「刚好够用」的边界SVM 在这里不是为了刷分而是为了满足 Web 平台「上传 pcap → 3 秒内返回结果」的 SLA。项目放弃线性核对高维特征效果差选用 RBF但关键参数gamma和C不是瞎调# train_svm.py 关键片段 from sklearn.svm import SVC from sklearn.model_selection import GridSearchCV from sklearn.metrics import classification_report # 参数空间大幅压缩避免过拟合且加速搜索 param_grid { C: [0.1, 1, 10], # 不搜 100防止过拟合 gamma: [scale, auto, 0.001, 0.01], # scale 是 Scikit-learn 默认必须包含 kernel: [rbf] } # 使用 StratifiedKFold(5) scoringf1_macro因类别不平衡 svm SVC(probabilityTrue) grid GridSearchCV(svm, param_grid, cv5, scoringf1_macro, n_jobs-1, verbose1) grid.fit(X_train, y_train) print(Best params:, grid.best_params_) print(Best CV F1:, grid.best_score_) # 保存最优模型注意必须用 joblibpickle 有兼容性风险 import joblib joblib.dump(grid.best_estimator_, model_svm.pkl)逻辑说明C10通常过拟合但在这个特征集上反而更好——因为retransmit_rate和inter_arrival_cv这两个强信号维度在高C下能形成更锐利的决策边界。gammascale比手动设值更稳它等于1 / (n_features * X.var())自动适配特征尺度。最终 SVM 在测试集上 F1 达 0.92推理耗时 80msi5-8250U完全满足 Web 响应要求。3.2 随机森林用plot_tree和SHAP解释「为什么这个 pcap 是恶意的」随机森林的n_estimators100是经验值但关键在可解释性输出。项目不仅用rf.feature_importances_画柱状图PieChart.png还集成 SHAPv0.41.0生成单样本解释# explain_rf.py需额外 pip install shap import shap import numpy as np from sklearn.ensemble import RandomForestClassifier import joblib rf joblib.load(model_rf.pkl) X_sample X_test[0:1] # 取第一个测试样本 # 初始化 explainer用 training set 作 background explainer shap.TreeExplainer(rf) shap_values explainer.shap_values(X_sample) # 生成 force plotHTML 可交互 shap.initjs() shap.force_plot(explainer.expected_value[1], shap_values[1], X_sample, feature_namesfeature_names, matplotlibFalse, showFalse).savefig(shap_force_plot.png, bbox_inchestight)PtSc2.png就是这张 force plot 的截图它清晰显示retransmit_rate0.42、tls_ext_count-0.31、syn_ack_ratio0.28是 top3 影响因子且正负号符合安全直觉重传率高→恶意扩展字段多→正常。这才是「可解释」——不是给你一堆数字而是让你能指着图说「看这个包重传了 17 次而正常流平均才 0.3 次所以模型判恶意」。3.3 XGBoost用early_stopping_rounds防止过拟合不是无脑堆树XGBoost 的n_estimators500看似激进但early_stopping_rounds50是救命稻草。项目在train_xgb.py中强制用验证集监控# train_xgb.py 片段 from xgboost import XGBClassifier from sklearn.model_selection import train_test_split X_train, X_val, y_train, y_val train_test_split( X_train_full, y_train_full, test_size0.2, stratifyy_train_full, random_state42 ) xgb XGBClassifier( n_estimators500, max_depth6, learning_rate0.1, subsample0.8, colsample_bytree0.8, objectivebinary:logistic, eval_metriclogloss ) # 注意eval_set 必须是 list of tuples [(X_val, y_val)] xgb.fit( X_train, y_train, eval_set[(X_val, y_val)], early_stopping_rounds50, verboseTrue ) # 实际使用的树数量 xgb.best_iteration 1 print(Best iteration:, xgb.best_iteration) joblib.dump(xgb, model_xgb.pkl)参数说明subsample0.8和colsample_bytree0.8是防过拟合双保险max_depth6避免单棵树太深否则 SHAP 解释失效early_stopping_rounds50意味着如果连续 50 轮验证 loss 不降就停——这比固定n_estimators100更科学。最终 XGBoost 在测试集 F1 0.94但推理耗时 120ms所以 Web 平台默认用 SVMXGBoost 仅作离线复核。4. Flask Web 平台怎么跑起来web_platform/app.py的四个关键配置项web_platform/目录下是完整的 Flask 应用结构清晰app.py主程序、templates/HTML、static/CSS/JS、uploads/临时存储、models/加载.pkl。它不是玩具 demo而是按生产环境最小可行原则设计。4.1app.py的核心路由/upload和/result的状态管理上传路由/upload不直接调模型而是先校验文件类型、大小再异步调用analyze_pcap()# web_platform/app.py 片段 import os import uuid from flask import Flask, request, render_template, redirect, url_for, flash from werkzeug.utils import secure_filename from threading import Thread from analyze_pcap import analyze_pcap # 自定义分析模块 app Flask(__name__) app.config[UPLOAD_FOLDER] uploads app.config[MAX_CONTENT_LENGTH] 16 * 1024 * 1024 # 16MB 限制 app.secret_key your-secret-key-change-in-prod # 用于 flash 消息 ALLOWED_EXTENSIONS {pcap, pcapng} def allowed_file(filename): return . in filename and \ filename.rsplit(., 1)[1].lower() in ALLOWED_EXTENSIONS app.route(/, methods[GET, POST]) def upload_file(): if request.method POST: if file not in request.files: flash(No file part) return redirect(request.url) file request.files[file] if file.filename : flash(No selected file) return redirect(request.url) if file and allowed_file(file.filename): # 生成唯一文件名防覆盖 filename secure_filename(file.filename) unique_id str(uuid.uuid4())[:8] safe_filename f{unique_id}_{filename} filepath os.path.join(app.config[UPLOAD_FOLDER], safe_filename) file.save(filepath) # 异步分析避免阻塞 Web 请求 thread Thread(targetanalyze_pcap, args(filepath, safe_filename)) thread.start() # 重定向到结果页轮询机制 return redirect(url_for(result, task_idunique_id)) return render_template(upload.html) app.route(/result/task_id) def result(task_id): # 从全局 dict 或 Redis 读取结果项目用简单 dict 模拟 result_data get_result_by_task_id(task_id) # 实际应查 DB/Cache if result_data is None: return render_template(waiting.html, task_idtask_id) return render_template(result.html, dataresult_data)逻辑说明Thread异步是必须的——如果同步调analyze_pcap()用户浏览器会卡住 2~5 秒。task_id作为查询键get_result_by_task_id()应该对接 Redis 或 SQLite项目当前用内存 dict仅限 demo。secure_filename()防路径穿越uuid防文件名冲突MAX_CONTENT_LENGTH防 DoS这些都是生产级基础。4.2 模型加载策略为什么load_model()要在app.before_first_request里模型加载不能放在app.py顶层否则每次 worker 启动都重复加载浪费内存也不能在路由里每次调用都joblib.load()太慢。正确姿势是# web_platform/app.py 中 model_cache {} app.before_first_request def load_models(): 应用启动时一次性加载所有模型 global model_cache try: model_cache[svm] joblib.load(models/model_svm.pkl) model_cache[rf] joblib.load(models/model_rf.pkl) model_cache[xgb] joblib.load(models/model_xgb.pkl) print([INFO] All models loaded successfully.) except Exception as e: print(f[ERROR] Failed to load models: {e}) raise def get_model(model_name): 安全获取模型带 fallback return model_cache.get(model_name, model_cache[svm]) # 默认用 SVM参数说明app.before_first_request确保只在第一个 HTTP 请求前执行一次。model_cache是全局字典避免多进程下模型重复加载。get_model()提供 fallback保证即使 RF 模型损坏Web 仍能用 SVM 响应。4.3 前端可视化templates/result.html如何把 SHAP 解释嵌进去结果页不是只显示「恶意/正常」而是分三块顶部摘要div classalert alert-success✅ 检测结果恶意流量置信度 96.2%/div中间特征贡献图用img src{{ url_for(static, filenameshap_force_plot.png) }}显示 force plot实际应动态生成并存 static/底部原始特征表用table列出 top5 特征值及阈值如retransmit_rate 0.17 0.05阈值PtSc3.png和PtSc4.png就是这两块的截图证明它真把「可解释性」落到了 UI 层——安全员不用开终端就能在浏览器里看到判据。5. 避坑指南五个让我重装三次 Ubuntu 的真实翻车现场这个项目看着结构清晰但实操时处处是坑。以下是我用三台虚拟机、两个物理机、一份被污染的 Mirai pcap 踩出来的血泪记录每条都按「现象→原因→解决」写拒绝模糊描述。5.1 现象getgoodx.py抓不到任何 TCP 流flows字典始终为空原因Scapy 默认用AF_PACKETsocket但在 Docker 容器或某些云主机如 AWS EC2上AF_PACKET权限被禁用且sniff()无法捕获 loopback 流量。解决在宿主机运行sudo setcap cap_net_rawep /usr/bin/python3赋予 Python raw socket 权限若在容器内启动时加--cap-addNET_RAW --network host或改用tcpdump -w temp.pcap scapy rdpcap(temp.pcap)替代实时 sniff5.2 现象feature_engineer.py报错KeyError: TLS但 pcap 明明有 TLS 流原因Scapy 的 TLS 解析依赖scapy.layers.tls而该模块在 Scapy v2.4.5 才稳定支持 TLS 1.3若用旧版 Scapy如 2.3.x遇到 TLS 1.3 握手会直接抛 KeyError。解决pip uninstall scapy→pip install scapy2.4.5不要用最新版v2.5.x 有 TLS 解析 regression或在feature_engineer.py开头加兼容代码try: from scapy.layers.tls import TLS except ImportError: # 降级处理用 raw bytes 检查 TLS magic def is_tls_packet(pkt): if Raw in pkt and len(pkt[Raw].load) 5: return pkt[Raw].load[0] 0x16 # TLS handshake magic return False5.3 现象SVM 训练时GridSearchCV卡死CPU 占用 100% 但无输出原因n_jobs-1在某些 Linux 发行版如 Ubuntu 20.04下与 Scapy 冲突导致 multiprocessing spawn 失败进程假死。解决改为n_jobs1单核训练慢但稳或升级到scikit-learn1.2.0joblib1.2.0它们修复了与 Scapy 的 fork/spawn 冲突5.4 现象Flask 启动报ModuleNotFoundError: No module named xgboost但pip list明确显示已安装原因Flask 默认用 Werkzeug 的 reloader它会 fork 新进程而某些 conda 环境的xgboost是用conda install装的fork 后新进程找不到 shared library。解决启动时加--no-reload参数flask run --no-reload或改用gunicorngunicorn -w 2 -b 0.0.0.0:5000 web_platform.app:app5.5 现象上传 pcap 后/result/id页面一直显示「等待中」get_result_by_task_id()始终返回 None原因thread.start()启动的分析线程其analyze_pcap()函数内部用了joblib.load()而 joblib 在多线程下可能因 pickle 协议版本不一致导致反序列化失败线程静默退出。解决在analyze_pcap()开头加try...except并写日志def analyze_pcap(filepath, task_id): try: model joblib.load(models/model_svm.pkl) # 确保路径正确 features extract_features_from_pcap(filepath) pred model.predict_proba(features)[0] # ... 保存结果到全局 dict 或 Redis except Exception as e: with open(log/error.log, a) as f: f.write(f[{task_id}] {str(e)}\n)查log/error.log90% 是joblib路径错误或模型文件损坏。6. 进阶技巧如何用log.txt和model.pkl做模型漂移监控而不是等它突然失效这个项目最被低估的价值不是它能检测已知恶意流量而是它提供了模型健康度自检的基础设施。log.txt不是随便记日志而是结构化记录每次预测的输入特征、模型 ID、置信度、耗时model.pkl也不是静态文件而是可被joblib.load()动态替换的热更新单元。我把这套机制用在了真实 SOC 环境以下是具体操作。6.1log.txt的格式设计每一行都是可分析的 JSONL项目默认log.txt是纯文本但我把它改成 JSON Lines 格式每行一个 JSON 对象便于用jq或 Pandas 分析{timestamp:2024-06-15T14:22:31,task_id:a1b2c3d4,model:svm,features:{retransmit_rate:0.17,tls_ext_count:3,syn_ack_ratio:0.82},pred_class:malicious,confidence:0.962,latency_ms:78} {timestamp:2024-06-15T14:22:35,task_id:e5f6g7h8,model:rf,features:{retransmit_rate:0.02,tls_ext_count:12,syn_ack_ratio:0.95},pred_class:benign,confidence:0.991,latency_ms:142}为什么用 JSONLjq . | select(.pred_class malicious and .confidence 0.7) log.txt快速定位低置信恶意样本可能是新变种pandas.read_json(log.txt, linesTrue)直接转 DataFrame画confidence时间序列图看是否整体下滑6.2 模型漂移检测用scipy.stats.kstest监控特征分布变化每周用log.txt里的历史特征和新采集的 100 个正常 pcap 的特征做 KS 检验。脚本check_drift.pyimport pandas as pd import numpy as np from scipy.stats import kstest from feature_engineer import extract_features_from_pcap # 读历史特征从 log.txt 提取 last 30 days df_hist pd.read_json(log.txt, linesTrue) X_hist np.vstack(df_hist[features].apply(lambda x: list(x.values())).values) # 提取新样本特征 new_features [] for pcap in [new1.pcap, new2.pcap, ...]: feat extract_features_from_pcap(pcap) new_features.append(feat.flatten()) X_new np.vstack(new_features) # 对每个特征维度做 KS 检验p-value 0.05 表示分布显著不同 drift_report {} for i, feat_name in enumerate(feature_names): stat, pval kstest(X_hist[:, i], X_new[:, i]) drift_report[feat_name] {ks_stat: stat, p_value: pval, drifted: pval 0.05} # 输出漂移特征如 tls_ext_count p0.002 → 需检查新客户端 TLS 实现变更 print(pd.DataFrame(drift_report).T)6.3 模型热更新model.pkl替换后Flask 如何无缝切换app.py里model_cache是全局变量但 Python 的import缓存会让joblib.load()读旧文件。我的做法是写一个reload_model.py脚本用os.utime()触发文件修改时间更新Flask 路由/admin/reload调用它并清空model_cache关键代码app.route(/admin/reload, methods[POST]) def reload_model(): global model_cache # 触发文件重载 os.utime(models/model_svm.pkl, None) # 清空缓存下次 get_model() 会重新 load model_cache.clear() return {status: ok, message: Model reloaded}从那以后我每次上线新模型都强制走一遍curl -X POST http://localhost:5000/admin/reload再用log.txt确认首条记录的model字段已更新。这比重启 Flask 进程快 10 秒且无请求丢失。希望帮到你。本文还有配套的精品资源点击获取