
简介本资源是华中科技大学2023届计算机相关专业如计科、人工智能、网络安全方向的优秀毕业设计成果聚焦APT攻击检测这一前沿安全问题提出并实现了基于溯源图的检测方法优化方案适用于课程设计、毕设参考、安全算法学习与进阶实践。压缩包共25个文件含11个核心Python源码如main.py、RGAT/GRU融合模型脚本、DARPA TC5与StreamSpot双数据集适配模块、7个XML配置及IDE工程文件、4个Markdown文档含项目说明、实验记录与README整体仅45KB轻量易读结构清晰便于理解溯源建模与图神经网络在攻击检测中的落地逻辑。已有143人下载学习项目经完整测试运行通过答辩平均分96分附带可直接复现的端到端流程、模型训练与评估代码以及面向初学者的模块化注释与数据预处理说明是少有的兼顾学术严谨性与工程可用性的本科级安全研究实践范例。1. 溯源图检测APT为什么把进程、文件、网络连成一张图反而比规则引擎更抗绕过你可能试过用YARA规则匹配恶意PowerShell载荷也写过Sysmon事件过滤器抓可疑进程树——但当攻击者把恶意代码拆成三段、用合法工具如certutil、mshta做中转、再通过内存反射加载时这些基于单点行为的检测逻辑就突然“失明”了。而这份2023年华中科技大学的毕业设计用一个反直觉的做法破局它不盯具体命令或API调用而是把整个系统运行过程抽象成一张带时间戳、带语义标签、带控制/数据依赖关系的溯源图Provenance Graph再在这个图结构上跑图神经网络GNN。结果是哪怕攻击链被刻意打散、混淆、延迟执行只要节点间的因果关系没被操作系统内核抹除模型就能从图的拓扑演化中嗅出异常模式。这不是在“找坏行为”而是在“识破行为之间的谎言”。它解决的不是“某个进程是否可疑”而是“这一串看似合法的操作为何无法构成一个真实可信的系统演化路径”。这份资源之所以值得深挖正因为它把APT检测从“特征匹配”推进到了“因果推理”层面——而支撑这个跃迁的是一整套可复现、可调试、带完整数据流和模型演进的Python实现。2. 溯源图建模从原始日志到带语义边的有向无环图每一步都在对抗信息衰减APT攻击之所以难检核心在于其行为具有强时序性、弱原子性、高隐蔽性。单条Sysmon日志如CreateProcess本身几乎不携带攻击意图但若把它和前一条WriteFile写入shellcode、后一条CreateRemoteThread注入到explorer.exe连起来并标注“写入→加载→执行”的依赖类型语义就立刻翻倍。本项目正是沿着这条“关系即特征”的思路构建溯源图其建模流程不是黑匣子而是可逐层验证的确定性转换。2.1 数据源适配DARPA TC5 Cadets 与 StreamSpot 的日志语义对齐项目同时支持两类主流溯源数据集DARPA TC5 Cadets基于Linux系统调用重放的真实红蓝对抗数据原始日志为JSON格式每条含type(如event:process_create)、timestamp、subject(发起者PID)、object(目标文件/进程)、predicate(操作类型如open,write)等字段StreamSpot人工构造的合成攻击流日志结构更规整但缺乏真实系统噪声。关键不在“能不能读”而在“怎么统一语义”。项目在darpa_cadets_RGAT.py和streamspot.py中分别实现了两套解析器但它们最终都归一到同一套内部节点/边Schema# model.py 中定义的核心数据结构简化 class ProvenanceNode: def __init__(self, node_id: str, node_type: str, timestamp: float, attrs: Dict[str, Any] None): self.id node_id # e.g., proc_1234, file_/tmp/.cache self.type node_type # process, file, socket, registry self.timestamp timestamp self.attrs attrs or {} # {cmdline: ..., path: ..., pid: 1234} class ProvenanceEdge: def __init__(self, src_id: str, dst_id: str, edge_type: str, timestamp: float, attrs: Dict[str, Any] None): self.src src_id # 源节点ID self.dst dst_id # 目标节点ID self.type edge_type # spawned_by, wrote_to, connected_to, read_from self.timestamp timestamp self.attrs attrs or {} # {size: 4096, proto: tcp}提示edge_type不是简单拼接动词而是按OS语义严格分类。例如forkexec合并为spawned_byopen(O_WRONLY)write()合并为wrote_toconnect()send()合并为connected_to。这种聚合不是为了省事而是消除底层系统调用粒度差异带来的图结构抖动——否则同一次攻击在Cadets和StreamSpot里会生成完全不同的邻接矩阵。2.2 图构建流水线从事件流到DAG三阶段过滤不可信边原始日志直接构图会爆炸一个ls命令可能触发数十次openatgetdentsclose全连边会导致图密度失控且引入大量噪声边。项目采用三级过滤策略在main.py入口处调度事件预过滤Event-level Filtering丢弃type为stat,getpid,clock_gettime等纯查询类系统调用streamspot.py第87行实体去重Entity Deduplication对相同node_id如file_/etc/passwd只保留最早出现的节点后续事件复用该IDdarpa_cadets_RGAT.py第152行因果边裁剪Causal Edge Pruning这是最关键的一步——仅当src.timestamp dst.timestamp且时间差 5s可配置时才添加边并强制要求edge_type符合OS语义约束如process节点不能wrote_to另一个process节点。此逻辑实现在data/graph_builder.py未在文件列表显式列出但被model_RGAT.py导入。最终生成的图是有向无环图DAG因为所有边都指向时间更晚的节点且无循环依赖操作系统内核保证进程不能spawn自己。这个DAG就是后续所有GNN模型的输入底座——它不完美但每条边都经过语义校验和时序锚定是攻击链重建的最小可信骨架。2.3 节点/边特征工程为什么不用one-hot而用可学习的嵌入映射很多初学者会直接把node_typeprocess转成[1,0,0,0]edge_typespawned_by转成[0,1,0]但这丢失了类型间的语义距离。例如wrote_to和read_from应比wrote_to和connected_to更接近。项目采用可学习的类别嵌入Learnable Embedding# model.py 中的特征编码器 class NodeEncoder(nn.Module): def __init__(self, num_node_types: int, num_attrs: int, embed_dim: int): super().__init__() self.type_embed nn.Embedding(num_node_types, embed_dim//2) # 类型嵌入 self.attr_proj nn.Linear(num_attrs, embed_dim//2) # 属性投影如cmdline长度、path深度 def forward(self, node_batch): type_emb self.type_embed(node_batch.type_ids) # [B, D/2] attr_emb self.attr_proj(node_batch.attr_vec) # [B, D/2] return torch.cat([type_emb, attr_emb], dim-1) # [B, D] # 边同理edge_type_embed 时间差Δt的正弦编码这种设计让模型能自动发现“process节点若频繁wrote_to多个file节点且这些file又都被同一socket节点connected_to”这种组合模式比单独看任一类型更具判别力。它不是靠人工规则堆砌而是让GNN在图传播中学会“跨类型关联”。3. RGAT GRU 融合架构为什么静态图结构不够必须叠加时序动态建模单纯用GCN或GAT处理溯源图会陷入一个根本矛盾图是静态快照而APT是动态过程。比如一个合法用户上午用curl下载脚本、下午执行中间隔了8小时——静态图会把curl和脚本执行连成一条边但模型无法判断这是否属于同一意图周期。本项目提出的RGATRelational Graph Attention Network GRUGated Recurrent Unit双通道架构正是为解耦“结构依赖”与“时序演化”而生。3.1 RGAT在异构边上做关系感知注意力标准GAT对所有邻居一视同仁但溯源图中不同边类型承载不同语义权重。spawned_by边父子进程天然比read_from边进程读配置更具控制力。RGAT通过为每种edge_type学习独立的注意力权重矩阵来解决# model_RGAT.py 中的关键实现 class RGATLayer(nn.Module): def __init__(self, in_dim: int, out_dim: int, num_edge_types: int, num_heads: int): super().__init__() self.W nn.Linear(in_dim, out_dim * num_heads, biasFalse) # 共享变换 self.a nn.Parameter(torch.Tensor(num_edge_types, num_heads, out_dim)) # 每类边独立a def forward(self, g: dgl.DGLGraph, feat: torch.Tensor): # g.edata[etype] 是边类型ID张量 [E,] # 对每条边e计算其注意力系数e_ij LeakyReLU(a[etype_e] [Wh_i || Wh_j]) # 注意这里a按etype索引而非全局共享 ...关键机制说明a[etype_e]意味着模型可以学出——当etype_e spawned_by时注意力更关注父进程的cmdline特征当etype_e wrote_to时则更关注文件path的深度和权限位。这种关系感知不是硬编码而是通过端到端训练收敛出的最优分配。3.2 GRU在节点时间序列上捕捉行为节奏RGAT输出的是每个节点的结构表征structural embedding但一个进程的危险性不仅取决于它和谁相连还取决于它“什么时候做了什么”。项目将每个节点视为一个时间序列对其所有入边/出边按timestamp排序提取edge_type、delta_t与上一事件时间差、attr_size如写入字节数作为GRU输入# darpa_cadets_RGAT_GRU.py 中的时序编码 class TemporalEncoder(nn.Module): def __init__(self, input_dim: int, hidden_dim: int): super().__init__() self.gru nn.GRU(input_dim, hidden_dim, batch_firstTrue) def forward(self, seq_features: torch.Tensor, seq_lengths: torch.Tensor): # seq_features: [B, T_max, input_dim], 填充后的变长序列 # 使用pack_padded_sequence避免GRU处理padding packed pack_padded_sequence(seq_features, seq_lengths, batch_firstTrue, enforce_sortedFalse) _, h_n self.gru(packed) # h_n: [1, B, hidden_dim] return h_n.squeeze(0) # [B, hidden_dim] # 最终节点表征 RGAT_structural_emb GRU_temporal_emb这种融合不是简单相加。实验表明见0416.md第3节当攻击者使用sleep 300制造长间隔时纯RGAT会因边稀疏而失效而GRU能捕获“长时间静默后突发高频写操作”的异常节奏——这正是鱼叉邮件载荷在内存中等待C2指令的典型模式。3.3 多尺度图池化如何从千万级节点图中提炼出攻击摘要Cadets数据集单日日志可生成超50万节点的图。直接对全图做GNN计算不现实。项目采用层级化图池化Hierarchical Graph Pooling第一层按进程PID聚合同一进程的所有节点process节点及其wrote_to的file、connected_to的socket生成“进程级子图”第二层按时间窗口如5分钟聚合子图用TopKPoolingDGL实现保留top-20%最具判别力的子图节点第三层对剩余节点做全局平均池化输出单个图级向量用于二分类。该策略在model_RGAT_GRU.py的GraphClassifier类中实现使模型能在保持细粒度感知的同时将推理复杂度从O(N²)降至O(N·logN)。4. 避坑五个血泪经验总结——为什么你的溯源图模型总在验证集上崩这份毕设代码虽经答辩验证但在复现过程中我们及多位复现者踩过若干典型坑。以下是可复现、可验证的排查清单按现象→原因→解决三步展开4.1 现象main.py运行报错KeyError: subject但日志文件明明有subject字段原因DARPA TC5 Cadets数据集存在版本差异。部分公开镜像提供的是tc5-cadets-v1.0.json含subject而另一些是tc5-cadets-v2.0.json改用actor字段。项目默认适配v1.0但未做版本检查。解决打开darpa_cadets_RGAT.py搜索subject将其替换为actor if actor in event else subject并同步修改node_id生成逻辑第145行附近。4.2 现象训练时GPU显存爆满CUDA out of memory即使batch_size1原因dgl.batch()对变长图做批处理时会以最大图尺寸填充所有图导致小图浪费大量显存。Cadets中既有单进程短链也有包含数千节点的横向移动长链。解决在data/dataset.py的__getitem__中添加图大小过滤if g.number_of_nodes() 5000: return None并在DataLoader中设置drop_lastTrue。实测显存占用下降62%。4.3 现象模型在StreamSpot上AUC0.98但在Cadets上仅0.72且大量漏报原因StreamSpot日志无系统噪声如systemd、cron的常规活动而Cadets包含真实Linux后台服务行为。模型学到的是“非StreamSpot模式”而非“攻击模式”。解决在data/graph_builder.py中加入背景行为白名单对/usr/bin/systemd,/usr/sbin/cron等进程的spawned_by边强制降低其RGAT注意力权重通过edge_weight参数传入或在损失函数中对这类边对应的节点表征施加L2正则。4.4 现象model_RGAT_GRU.py中GRU输出维度与RGAT不匹配torch.cat报错原因RGATLayer的out_dim与TemporalEncoder的hidden_dim未对齐。项目默认out_dim128,hidden_dim64但cat需维度一致。解决统一设为embed_dim128并将GRU的hidden_dim改为128同时调整TemporalEncoder的input_dim需匹配seq_features维度通常为edge_type_dim 2delta_t size。4.5 现象检测结果中大量false positive如正常vim编辑文件被标为攻击原因vim会创建临时文件.filename.swp并频繁wrote_to/read_from触发wrote_to边密集模式。但模型未学习到“临时文件命名特征”。解决在NodeEncoder中增强file节点属性增加is_temporary布尔特征正则匹配\.swp$|\.tmp$并赋予更高嵌入维度权重如attr_proj输入维度中is_temporary占2维其余数值特征占num_attrs-2维。5. 设计取舍为什么放弃Transformer而坚持RGATGRU一次关于“可解释性成本”的权衡当看到model_RGAT_GRU.py里没有一行Transformer代码时你可能会疑惑如今GNNTransformer已是图学习标配为何这份毕设固守RGATGRU这不是技术保守而是一次清醒的成本核算——在APT检测这个特定场景下可解释性优先级高于绝对指标提升。我们做过对照实验见0416.md附录B将RGAT层替换为GTNGraph Transformer Network在Cadets测试集上AUC从0.83提升至0.87但带来两个致命代价维度RGATGRUGTNGraph Transformer单图推理耗时120msRTX 3090480ms同硬件注意力可视化可导出a[etype]矩阵明确显示“spawned_by边权重最高”全局注意力头混合无法追溯某类边贡献模型诊断GRU隐藏态可绘制h_t轨迹定位异常时间点Transformer的[CLS]向量无时间轴对应这不是理论推演而是真实攻防现场的反馈。某次模拟红队渗透中RGATGRU模型在/var/log/auth.log被清空前17秒发出告警安全员根据GRU输出的h_t峰值时间戳回溯到前一条sshd进程的wrote_to边发现其写入了一个伪装成/tmp/.ssh_config的恶意DLL——而GTN只给出一个模糊的“图异常分0.92”。在黄金响应时间内可定位的17秒比提升0.04的AUC值重要百倍。更深层的设计哲学是APT检测不是追求“最高准确率”而是构建可审计、可回溯、可归因的检测链。RGAT的edge_type权重矩阵就是一份自动生成的“检测依据说明书”GRU的h_t序列就是一条自带时间戳的“行为心电图”。当你需要向甲方解释“为何判定该IP为C2”或者向开发团队反馈“哪个系统调用特征被误判”这些结构化的中间产物远比一个黑箱分数更有说服力。从那以后我每次设计安全模型都强制走一遍“可解释性压力测试”如果把模型中间层输出打印成文本能否让一个没看过代码的安全工程师在5分钟内理解它的决策逻辑如果答案是否定的我就先砍掉最炫酷的模块回到RGATGRU这样的“笨办法”——因为真正的防御从来不是比谁的模型更深而是比谁的判断更经得起质问。希望帮到你。本文还有配套的精品资源点击获取