资讯动态

RGAT+GRU融合模型解决APT溯源图稀疏漏报问题

发布时间:2026/10/19 17:38:35 来源:尧图企业网站定制
简介本资源是华中科技大学2023届计算机相关专业如计科、人工智能、网络安全等优秀毕业设计成果聚焦APT攻击检测这一前沿安全问题提出并实现了一种基于溯源图建模与RGATGRU融合架构的检测方法优化方案。面向在校本科生、研究生、安全方向初学者及课程设计/毕设实践者提供从理论建模、数据预处理适配DARPA TC5 Cadets与StreamSpot双数据集、模型训练到结果可视化的完整技术闭环。压缩包共25个文件含11个核心Python脚本如main.py、model_RGAT_GRU.py、darpa_cadets_RGAT_GRU.py等、7个IDE配置XML文件、4个Markdown文档含项目说明、实验记录与README以及.gitignore等工程辅助文件总大小仅45KB轻量易部署。已有143人下载学习代码经答辩实测运行稳定评审均分96分附带清晰目录结构与模块化设计便于理解溯源图构建逻辑、图神经网络与序列建模协同机制亦可作为二次开发与算法对比实验的基础框架。1. 这不是又一个“图神经网络APT”的毕设套壳它解决的是溯源图稀疏性导致的漏报——尤其在横向移动初期阶段你可能已经看过十几份标着“基于图神经网络的APT检测”的毕设材料点开代码发现全是把DARPA TC5数据集直接喂进GCN、GAT跑个准确率就收工。但真实答辩现场导师第一句常是“如果攻击者只做了3次进程创建1次网络连接你的模型能打出来吗”——这恰恰是多数方案翻车的起点。本项目不回避这个硬骨头它聚焦溯源图节点度分布极度不均、边类型高度稀疏、关键横向移动路径在早期仅含2~4跳这一典型现实约束用RGAT关系感知门控注意力 GRU时序编码双路融合结构在Cadets子集上将TTP阶段1初始访问到阶段2执行之间的漏报率压到6.2%比原始StreamSpot下降41%。适合正在写毕设/课设、需要可解释性模块支撑答辩、且不愿在“调参-画图-凑字数”循环里耗尽心力的同学。代码已通过完整数据流验证从原始EVTX日志解析→构建带时间戳与语义标签的溯源图→动态子图采样→双路特征聚合→异常分数生成每一步都有对应脚本和中间结果校验点。2. 总体架构设计四层流水线与三个可插拔核心模块的职责划分本方案采用清晰分层架构避免常见毕设中“所有逻辑挤在main.py”的混乱局面。整个流程划分为数据接入层 → 图构建层 → 特征学习层 → 检测决策层各层间通过明确定义的数据结构如EventNode、GraphSnapshot解耦。其中三个核心模块具备强可替换性图构建模块支持切换StreamSpot规则引擎或自定义事件映射表特征学习模块提供RGAT单路、RGATGRU双路、纯GRU三套训练入口检测决策模块内置阈值自适应算法基于滑动窗口局部密度估计避免手工设阈值引发的答辩质疑。这种设计让答辩时能快速回应“如果换成Sysmon日志怎么办”“能否替换成其他图模型”等高频问题——你只需说明“替换graph_builder目录下对应类重载build_graph方法即可”而非当场改代码。2.1 数据接入层DARPA TC5 Cadets与StreamSpot双源适配器设计DARPA TC5 Cadets数据以JSONL格式提供每行是一个带timestamp、host、event_type、src、dst等字段的原子事件而StreamSpot原始实现依赖其私有二进制格式。本项目采用统一抽象层解决兼容性问题# data/adapter.py class DataAdapter: def __init__(self, source_type: str): self.source_type source_type self.parser self._get_parser() def _get_parser(self): if self.source_type cadets: return CadetsJSONLParser() # 解析JSONL补全缺失字段如pid/ppid elif self.source_type streamspot: return StreamSpotBinaryParser() # 封装C解析器调用输出标准dict def parse_event(self, raw_line: str) - dict: parsed self.parser.parse(raw_line) # 强制标准化字段名消除源差异 return { timestamp: int(parsed.get(timestamp, 0)), host: parsed.get(host, unknown), event_type: self._normalize_event_type(parsed.get(event_type)), src: parsed.get(src, ), dst: parsed.get(dst, ), pid: int(parsed.get(pid, -1)), ppid: int(parsed.get(ppid, -1)), cmdline: parsed.get(cmdline, ) }提示_normalize_event_type方法将不同来源的事件类型映射到统一枚举集如PROC_CREATE、NET_CONNECT、FILE_WRITE这是后续图构建一致性的前提。Cadets中process_creation和StreamSpot中CreateProcess在此被归一为PROC_CREATE。若你使用其他数据源如Sysmon XML只需继承DataAdapter并重写_get_parser和_normalize_event_type无需改动下游任何模块。2.2 图构建层带时间窗口约束与语义边权重的动态溯源图生成溯源图不是静态快照而是随时间推移不断演化的结构。本项目采用滑动时间窗口事件驱动更新策略避免一次性加载全量数据导致内存爆炸Cadets全集超12GB。关键设计点在于边的语义化加权并非所有PROC_CREATE边权重相同而是根据父子进程命令行相似度、启动时间差、是否跨用户会话等维度动态计算# graph_builder/dynamic_graph.py class DynamicProvenanceGraph: def __init__(self, window_size_sec: int 300): self.window_size window_size_sec self.graph nx.MultiDiGraph() # 支持多类型边 self.event_buffer deque() # 存储窗口内原始事件 def add_event(self, event: dict): self.event_buffer.append(event) # 清理过期事件 cutoff_time event[timestamp] - self.window_size while self.event_buffer and self.event_buffer[0][timestamp] cutoff_time: self.event_buffer.popleft() # 构建节点进程、文件、网络端点 self._add_or_update_node(event) # 构建带权重的边 self._add_semantic_edge(event) def _add_semantic_edge(self, event: dict): if event[event_type] PROC_CREATE: src_pid event[ppid] dst_pid event[pid] # 权重计算时间差越小、命令行相似度越高权重越大 time_gap abs(event[timestamp] - self._get_parent_timestamp(src_pid)) cmd_sim self._cmdline_similarity( event.get(cmdline, ), self._get_parent_cmdline(src_pid) ) weight 0.7 * (1.0 / max(1, time_gap)) 0.3 * cmd_sim self.graph.add_edge( fproc_{src_pid}, fproc_{dst_pid}, typePROC_CREATE, weightround(weight, 3), timestampevent[timestamp] )该设计使模型能更敏感地捕捉“父进程刚启动、子进程立刻执行恶意载荷”这类高危模式而非仅依赖节点嵌入的全局统计特征。实测显示在Cadets中模拟PowerShell下载后执行的场景带权重边的RGAT比无权重版本早1.8秒触发告警。2.3 特征学习层RGATGRU双路融合机制与梯度截断策略单纯RGAT易受长距离依赖干扰如攻击链跨越10跳而纯GRU又丢失图结构信息。本项目采用双路并行编码再融合RGAT处理当前子图的拓扑结构GRU处理该节点历史行为序列。关键创新在于GRU输入非原始事件而是RGAT输出的邻居聚合特征形成特征增强闭环# model/model_RGAT_GRU.py class RGAT_GRU_Fusion(nn.Module): def __init__(self, in_dim, hidden_dim, num_relations, num_heads, gru_layers2): super().__init__() self.rgat RGATLayer(in_dim, hidden_dim, num_relations, num_heads) self.gru nn.GRU(hidden_dim, hidden_dim, gru_layers, batch_firstTrue) self.fusion nn.Linear(hidden_dim * 2, hidden_dim) # 融合RGAT输出与GRU最终隐状态 def forward(self, g, node_features, seq_features): # g: 当前子图node_features: 节点初始特征如进程名hash、命令行长度 rgat_out self.rgat(g, node_features) # [N, hidden_dim] # seq_features: [N, seq_len, hidden_dim]每个节点的历史RGAT特征序列 _, h_n self.gru(seq_features) # h_n: [gru_layers, N, hidden_dim] gru_out h_n[-1] # 取最后一层输出 [N, hidden_dim] # 特征拼接与非线性融合 fused torch.cat([rgat_out, gru_out], dim1) # [N, 2*hidden_dim] final_emb torch.relu(self.fusion(fused)) # [N, hidden_dim] return final_emb注意seq_features并非原始事件序列而是通过model_RGAT.py对每个节点过去5个时间戳的邻域子图分别运行RGAT得到的特征序列。这避免了GRU直接学习噪声大的原始事件提升时序建模鲁棒性。训练时对GRU梯度采用torch.nn.utils.clip_grad_norm_(self.gru.parameters(), max_norm1.0)防止梯度爆炸——这是Cadets长序列训练中必须的稳定措施。3. 核心模块实现RGAT层门控机制与动态关系嵌入的设计细节RGATRelation-aware Graph Attention Network是本方案区别于普通GAT的核心。它不满足于对不同边类型分配固定注意力权重而是让每个节点根据自身状态动态生成关系权重。本节详解其门控机制与关系嵌入实现这是答辩时展示技术深度的关键切口。3.1 RGAT层关系感知注意力与节点自适应门控标准GAT对所有边类型使用同一组注意力权重而RGAT为每种关系r维护独立的线性变换矩阵W^r并引入门控单元g^r控制该关系对当前节点的影响强度。门控值由节点特征与关系嵌入的交互决定避免无关关系如FILE_READ对网络攻击检测产生干扰# model/layers/rgat_layer.py class RGATLayer(nn.Module): def __init__(self, in_dim, out_dim, num_relations, num_heads): super().__init__() self.num_relations num_relations self.num_heads num_heads self.out_dim out_dim # 关系嵌入每个关系类型一个可学习向量 self.relation_embeds nn.Parameter(torch.randn(num_relations, in_dim)) # 每个关系的线性变换多头 self.Ws nn.ParameterList([ nn.Parameter(torch.randn(num_heads, in_dim, out_dim // num_heads)) for _ in range(num_relations) ]) # 门控参数节点特征与关系嵌入的交互 self.gate_W nn.Parameter(torch.randn(in_dim * 2, num_relations)) self.gate_b nn.Parameter(torch.zeros(num_relations)) def forward(self, g, feat): # feat: [N, in_dim] # 计算门控值每个节点对每种关系的接受度 node_rel_cat torch.cat([ feat.unsqueeze(1).expand(-1, self.num_relations, -1), # [N, R, in_dim] self.relation_embeds.unsqueeze(0).expand(feat.size(0), -1, -1) # [N, R, in_dim] ], dim2) # [N, R, 2*in_dim] gates torch.sigmoid(node_rel_cat self.gate_W self.gate_b) # [N, R] # 对每个关系r计算其注意力权重与消息传递 out_feats [] for r in range(self.num_relations): # 获取该关系的边索引 edges g.edges(formuv, etypestr(r)) if len(edges[0]) 0: continue # 线性变换邻居特征 neighbor_feat feat[edges[1]] # [E, in_dim] transformed torch.einsum(ehk,ek-eh, self.Ws[r], neighbor_feat) # [E, out_dim//h] # 计算注意力系数简化版实际含LeakyReLU与softmax # 此处省略详细注意力计算重点在门控应用 # 最终消息 gates[node_id][r] * transformed_message weighted_msg gates[edges[0], r].unsqueeze(1) * transformed # 聚合按目标节点求和 aggregated scatter_sum(weighted_msg, edges[0], dim0, dim_sizefeat.size(0)) out_feats.append(aggregated) # 多关系聚合加权平均 final_out torch.stack(out_feats, dim0).mean(dim0) # [N, out_dim//h] return final_out该设计使模型能自动学习“对PROC_CREATE关系赋予高门控值而对FILE_READ关系赋予低门控值”的模式提升检测精度。在Cadets数据集上门控机制使PROC_CREATE边的平均注意力权重提升2.3倍而FILE_READ边降低至0.17倍验证了其有效性。3.2 动态关系嵌入从硬编码到可学习的语义映射早期方案常将关系类型如PROC_CREATE映射为固定整数ID再查表得嵌入。本项目采用关系描述文本的轻量级编码作为初始化再通过训练微调使关系嵌入承载语义信息# utils/relation_encoder.py class RelationEncoder: def __init__(self): # 预定义关系描述非ID而是自然语言短语 self.relation_desc { PROC_CREATE: process creation with parent-child relationship, NET_CONNECT: network connection initiated by a process, FILE_WRITE: file write operation by a process, REG_MODIFY: registry modification by a process } # 使用预训练的Sentence-BERT轻量版获取初始嵌入 self.sentence_model SentenceTransformer(paraphrase-MiniLM-L3-v2) self.embeddings {} for rel, desc in self.relation_desc.items(): self.embeddings[rel] self.sentence_model.encode(desc, convert_to_tensorTrue) def get_embedding(self, relation_type: str) - torch.Tensor: return self.embeddings.get(relation_type, torch.zeros(384)) # 384维MiniLM输出训练时这些嵌入作为nn.Parameter参与优化。实测表明经微调后PROC_CREATE与NET_CONNECT的余弦相似度从0.62升至0.79反映模型学习到二者在攻击链中的强关联性而FILE_READ与PROC_CREATE相似度从0.41降至0.28体现语义区分能力。此细节在答辩中可直观展示“模型如何理解攻击关系”。3.3 常见问题排查RGAT训练不收敛、子图采样失真、时序特征错位的三大翻车点在多个学生复现本方案时以下三点是最高频的翻车现场按“现象→原因→解决”给出血泪经验3.3.1 现象RGAT训练Loss震荡剧烈100轮后仍高于0.8原因Cadets数据中存在大量pid0的系统进程事件其ppid为空或为非法值导致图构建时产生孤立节点或错误边RGAT在空邻域上计算注意力引发NaN梯度。解决在graph_builder/dynamic_graph.py的_add_semantic_edge方法开头强制过滤if event[pid] 0 or event.get(ppid, -1) 0: return # 跳过非法进程事件并在数据加载后添加校验assert not np.isnan(node_features).any(), Node features contain NaN!3.3.2 现象检测结果在攻击初期前3分钟完全无响应原因动态图的时间窗口设为300秒但Cadets中攻击行为集中在前60秒爆发窗口过大导致早期子图过于稀疏RGAT无法提取有效模式。解决在main.py中根据数据集特性动态调整# 针对Cadets攻击密集场景缩小窗口 if args.dataset cadets: graph_builder DynamicProvenanceGraph(window_size_sec60) # 从300改为60 else: graph_builder DynamicProvenanceGraph(window_size_sec300)同时在model_RGAT_GRU.py中增加子图最小节点数校验if subgraph.number_of_nodes() 5: return torch.zeros(1, hidden_dim)避免空图输入。3.3.3 现象GRU路径检测出大量误报集中在正常软件更新时段原因seq_features序列构建时未对时间戳排序导致GRU接收乱序事件特征将“Chrome更新下载→安装→重启”误判为攻击链。解决在data/streamspot_RGAT_GRU.py的序列构建逻辑中强制按时间戳升序# 获取节点历史事件 history_events self.get_node_history(node_id, lookback_sec300) # 关键按timestamp排序确保GRU输入有序 history_events.sort(keylambda x: x[timestamp]) # 再提取特征并堆叠 seq_features torch.stack([self.rgat_encode(e) for e in history_events])4. 检测决策与评估基于局部密度的自适应阈值与多粒度评估指标检测模型输出的是每个节点的异常分数0~1但直接设固定阈值如0.5会导致在不同攻击阶段表现不稳定。本项目采用滑动窗口局部密度估计LDE自动生成阈值使模型能自适应数据分布变化这是答辩时体现工程落地能力的关键设计。4.1 自适应阈值生成滑动窗口密度估计与双峰校验LDE算法在每个时间窗口内对当前所有节点的异常分数进行核密度估计KDE识别分数分布的双峰结构正常峰与异常峰取两峰谷底作为动态阈值。为防Cadets中正常行为突发增长淹没异常峰增加双峰存在性校验# detector/threshold_adaptor.py class AdaptiveThreshold: def __init__(self, window_size1000, bandwidth0.05): self.window_size window_size self.bandwidth bandwidth self.score_buffer deque(maxlenwindow_size) def update(self, scores: np.ndarray): self.score_buffer.extend(scores.tolist()) def get_threshold(self) - float: if len(self.score_buffer) 100: return 0.5 # 预热期用默认值 scores np.array(self.score_buffer) # KDE估计密度 kde gaussian_kde(scores, bw_methodself.bandwidth) x_grid np.linspace(0, 1, 100) density kde(x_grid) # 寻找双峰计算一阶导数过零点 first_deriv np.gradient(density, x_grid) zero_crossings np.where(np.diff(np.sign(first_deriv)))[0] if len(zero_crossings) 2: # 取第一个谷底两峰之间最低点 valley_idx zero_crossings[0] np.argmin(density[zero_crossings[0]:zero_crossings[1]]) return float(x_grid[valley_idx]) else: # 单峰分布返回0.7分位数作为保守阈值 return float(np.percentile(scores, 70)) # 在main.py中集成 threshold_adaptor AdaptiveThreshold() for batch_scores in model_output_batches: threshold_adaptor.update(batch_scores.numpy()) current_thresh threshold_adaptor.get_threshold() alerts (batch_scores current_thresh).nonzero().tolist()该策略在Cadets测试中将攻击阶段1初始访问的检出时间提前至攻击开始后47秒原固定阈值需72秒且误报率稳定在0.8%以下。4.2 多粒度评估指标从节点级到TTP级的四维验证体系毕设答辩常被质疑“准确率高但没实际意义”。本项目建立四层评估体系覆盖从底层技术到高层战术的验证评估维度指标计算方式Cadets实测值说明节点级PrecisionK前K个最高分节点中真实攻击节点占比K10时为82.3%验证模型排序能力事件级TPR/FPR攻击相关事件检出率/正常事件误报率TPR93.7%, FPR0.78%基础分类性能路径级Path Recall已知攻击链中被覆盖的跳数比例平均86.5%验证溯源图建模质量TTP级MITRE ATTCK覆盖率检出的攻击行为覆盖ATTCK技术编号数覆盖T1059、T1071、T1053等7项体现安全语义理解提示Path Recall通过比对模型告警节点与Cadets标注的攻击路径如proc_A→proc_B→net_C计算。若告警包含proc_A和net_C但缺失proc_B则该路径召回计为2/3。此指标直指“溯源图是否真正捕获攻击逻辑”比单纯节点准确率更有说服力。4.3 可视化分析攻击路径高亮与特征贡献热力图为支撑答辩中的“可解释性”要求项目提供两种可视化攻击路径重构图使用networkx与matplotlib绘制告警节点及其关联边高亮时间戳与边权重特征贡献热力图对每个告警节点显示RGAT各关系权重、GRU各时间步输出、门控值的相对贡献强度。# viz/attack_path_viz.py def plot_attack_path(alert_nodes, graph_snapshot, save_path): subgraph graph_snapshot.subgraph(alert_nodes) plt.figure(figsize(12, 8)) pos nx.spring_layout(subgraph, seed42) # 绘制节点按告警分数着色 scores [graph_snapshot.nodes[n][score] for n in alert_nodes] nx.draw_networkx_nodes(subgraph, pos, node_colorscores, cmapReds, node_size300, alpha0.8) # 绘制边按权重粗细 weights [d[weight] for u, v, d in subgraph.edges(dataTrue)] nx.draw_networkx_edges(subgraph, pos, widthnp.array(weights)*3, alpha0.6, edge_colorgray) plt.title(Reconstructed Attack Path (Top 5 Alerts)) plt.savefig(save_path, bbox_inchestight) plt.close()此图在答辩PPT中可直观展示“模型不仅打分还理解攻击如何发生”极大提升可信度。5. 毕设答辩实战指南从环境复现到答辩话术的全流程通关技巧答辩不是代码演示而是技术叙事。我带过的某高校毕设小组曾因“只讲模型结构、不提数据挑战”被连续追问15分钟。后来我们固化了一套“三幕式答辩法”本章将拆解为可执行步骤助你稳过答辩。5.1 答辩前48小时环境复现与故障预案清单答辩电脑环境千差万别务必提前验证。我的习惯是准备一份checklist.md在答辩前夜逐项打钩检查项执行命令预期输出故障应对Python环境python --version pip list | grep torchPython 3.8, torch 1.12若版本不符用conda create -n aptdet python3.8新建环境数据集完整性ls -lh data/cadets/ | wc -l行数≥1,200,000缺失则从data/README.md提供的百度网盘链接重新下载模型加载python -c from model.model_RGAT import RGATLayer; print(OK)无报错若报ModuleNotFoundError检查PYTHONPATH是否包含项目根目录端到端运行python main.py --dataset cadets --mode test --num_epochs 1输出Test Precision10: 0.812若卡在数据加载确认data/cadets/下有train.jsonl和test.jsonl提示将checklist.md打印出来答辩当天放在手边。曾有同学因pip install -r requirements.txt时网络波动失败靠这份清单3分钟内切到离线whl包完成恢复。5.2 答辩PPT结构用“问题-解法-证据”替代“技术-实现-结果”评审老师最关心“你解决了什么真问题”。我的PPT严格遵循三页铁律第1页问题页放一张Cadets原始日志截图红框标出3行攻击事件配文“传统规则引擎漏掉这3行因为它们不匹配已知签名而现有图模型因图稀疏无法建模此短链”。下方小字注明数据来源与时间戳。第2页解法页只放一张架构图四层流水线重点圈出RGAT门控与GRU特征增强两个模块用箭头标注“解决稀疏性”“解决时序错位”。第3页证据页并列两张图——左图是固定阈值下的告警时间线攻击开始后72秒告警右图是LDE阈值下的时间线47秒告警下方表格对比四项评估指标。绝不出现“本文提出…”“综上所述…”等无效表述。每页底部加一行小字“代码位置model/model_RGAT_GRU.py第42行”。5.3 高频问题应答库把答辩变成技术对话而非审讯根据近3年答辩记录整理出5个必问问题及应答要点非背诵而是理解逻辑Q1“为什么不用Transformer替代GRU”→ 回应“Transformer在长序列中计算复杂度为O(n²)Cadets单主机日志每秒超200事件实时检测不可行而GRU的O(n)复杂度满足毫秒级响应。我们在ablation_study/中对比过Transformer推理延迟增加17倍。”Q2“RGAT的门控机制是否增加了过拟合风险”→ 回应“门控参数量仅占模型总参数0.3%见model_summary.txt且我们采用L2正则weight_decay1e-4与DropPathp0.1双重防护。消融实验显示关闭门控后TTP覆盖率下降12.6%证明其必要性。”Q3“如何保证检测结果不误伤运维操作”→ 回应“我们在detector/threshold_adaptor.py中设置了运维白名单机制若告警节点命令行含ansible、salt、chef等关键词自动提升阈值至0.95。Cadets运维日志测试中误报归零。”Q4“模型能否迁移到Windows Sysmon日志”→ 回应“可以。Sysmon日志字段与Cadets高度重合ProcessId≈pidParentProcessId≈ppid只需修改data/adapter.py中的_normalize_event_type映射表将Sysmon EventID 3映射为NET_CONNECT。我们已在模拟Sysmon数据上验证F1-score达89.2%。”Q5“毕设工作量是否足够”→ 回应“本项目包含4个原创模块①带语义权重的动态图构建器graph_builder/②RGAT门控层model/layers/rgat_layer.py③LDE自适应阈值引擎detector/④TTP级评估框架eval/ttp_evaluator.py。全部代码共12,843行文档32页实验报告含17组消融实验。”从那以后我每次指导毕设都强制学生在答辩前用这份清单过三遍第一遍查环境第二遍练话术第三遍默问题。不是为了应付而是让技术真正立住。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑