ARTICLE DETAIL

资讯详情

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

图神经网络用于以太坊重入漏洞检测实战

图神经网络用于以太坊重入漏洞检测实战 简介本资源是一套面向高校计算机及相关专业人工智能、自动化、电子信息等学生与研究者的以太坊智能合约漏洞检测实践方案聚焦图神经网络GNN在区块链安全分析中的落地应用适用于毕业设计、课程设计、科研入门及漏洞检测技术进阶学习。压缩包共324个文件含285个Solidity智能合约源码覆盖DAO、重入、整数溢出等典型漏洞场景、23个Python训练与评估脚本支持GMN模型多轮训练权重gmn_epoch_1/2/3、5个标签与配置说明txt、2份核心PDFthesis.pdf含完整方法论与实验分析paper.pdf为可复现论文、1份Makefile构建脚本及IR中间表示、JSON标签数据等整体21.39MB结构清晰、模块完备。已有78人学习下载提供从合约解析、图构建、GNN建模到漏洞分类的全链路实现附带详细文档与导师认可的高分项目背书答辩95分代码均经实测运行通过可直接用于毕设演示或二次开发。1. 图神经网络不是万能锤但在以太坊智能合约漏洞检测里它真能“看见”重入漏洞你写完一个 Solidity 合约用 Truffle 测试跑通了所有用例Slither 也只报了两个低危警告——结果部署到测试网不到两小时就被 exploit资金抽空。这不是玄学是传统静态分析工具在控制流与数据流交织的复杂调用图面前的集体失语。而这份资料里的 GMNGraph Matching Network模型不依赖人工规则直接把.ir中间表示构建成合约控制流图CFG 数据依赖图DDG融合图用图神经网络学习节点间语义关系对 reentrancy_label_200.json 中标注的 200 个真实重入漏洞样本实现 92.3% 的 F1-score。它解决的不是“有没有漏洞”而是“为什么这个 call.value() 调用会触发重入而隔壁那个不会”——适合正在做区块链安全课设、毕设或想把 GNN 落地到真实合约审计场景的开发者。如果你手头有 Solidity 代码但缺乏漏洞标注数据这份资料里的simple_dao.ir和reentrancy_label_200.json就是现成的训练-验证闭环起点。2. 从 Solidity 到图结构GMN 模型如何把合约“翻译”成可学习的图数据2.1 为什么必须用图结构而非文本序列处理智能合约传统 NLP 方法将 Solidity 代码切词后喂给 LSTM 或 Transformer本质是在处理“语法字符串”。但重入漏洞的核心在于跨函数调用的数据流向withdraw()→call.value()→fallback()→balance[msg.sender]--→ 再次进入withdraw()。这种环状依赖无法被序列模型有效捕获。图结构天然适配合约语义函数是节点call、transfer、delegatecall是边变量读写是带方向的属性边。项目中simple_dao.ir文件就是关键桥梁——它不是原始 Solidity而是经过solc --ir编译生成的 Yul 中间表示已剥离语法糖保留了精确的控制流跳转和内存/存储访问指令。对比原始 SolidityIR 层更稳定不同 solc 版本编译结果差异小且节点粒度更细每个操作码为一节点为图构建提供可靠输入。提示不要直接用solc --ast生成的 AST。AST 包含大量语法结构如IfStatement、ForStatement但缺失底层存储槽storage slot映射关系而重入漏洞判定必须追踪msg.sender对同一 storage slot 的多次写入。2.2 图构建流程从 .ir 文件到邻接矩阵与节点特征张量项目中的Makefile隐藏了核心预处理逻辑。执行make preprocess实际调用 Python 脚本解析.ir步骤如下# preprocess.py 关键逻辑简化版 def build_contract_graph(ir_path: str) - nx.DiGraph: graph nx.DiGraph() # Step 1: 解析 IR 获取基础块Basic Block blocks parse_ir_blocks(ir_path) # 提取 jumpdest 标签分隔的指令块 # Step 2: 为每个块创建节点注入语义特征 for i, block in enumerate(blocks): node_features { opcodes: [op.code for op in block.ops], # 操作码序列 has_call: any(op.name CALL for op in block.ops), writes_storage: any(SSTORE in op.name for op in block.ops), reads_msg_sender: any(CALLER in op.name for op in block.ops) } graph.add_node(i, **node_features) # Step 3: 构建控制流边jump/jumpi 目标 for i, block in enumerate(blocks): if block.jump_target: graph.add_edge(i, block.jump_target, typecontrol) if block.jumpi_target: graph.add_edge(i, block.jumpi_target, typeconditional) # Step 4: 添加数据依赖边基于 storage slot 计算 storage_deps compute_storage_dependencies(blocks) for src, dst, slot in storage_deps: graph.add_edge(src, dst, typedata, slotslot) return graph这段代码输出的是 NetworkX 图对象后续通过torch_geometric.utils.from_networkx()转为 PyTorch Geometric 的Data对象。关键参数说明opcodes特征向量经torch.nn.Embedding(256, 64)映射为稠密向量256 覆盖全部 EVM 操作码has_call等布尔特征转为 float 后拼接构成 128 维初始节点嵌入控制流边权重固定为 1.0数据依赖边权重设为1/slot_depthslot 深度越小依赖越强。2.3 GMN 模型架构双分支图匹配网络的设计动机与实现细节项目目录中的GMN_epoch_1至GMN_epoch_3是训练过程保存的检查点对应模型在验证集上 F1 值从 86.1% → 89.7% → 92.3% 的提升。其核心不是单图分类而是图匹配Graph Matching将待测合约图与已知漏洞模式图来自reentrancy_label_200.json中的正样本子图进行相似度计算。模型结构如下表所示模块输入输出关键参数作用Node Encoder节点初始特征128维节点嵌入256维GCN 层数2dropout0.3学习局部邻域语义Graph Encoder所有节点嵌入图级表示512维使用 Set2Set 聚合迭代步数6生成全局合约摘要Matching Layer两个图级表示匹配分数0~1双线性匹配矩阵 W ∈ ℝ⁵¹²ˣ⁵¹²计算漏洞模式相似度# model.py 中 MatchingLayer 的核心实现 class MatchingLayer(nn.Module): def __init__(self, hidden_dim512): super().__init__() self.W nn.Parameter(torch.randn(hidden_dim, hidden_dim) * 0.01) self.sigmoid nn.Sigmoid() def forward(self, g1_repr: torch.Tensor, g2_repr: torch.Tensor) - torch.Tensor: # g1_repr, g2_repr: [batch_size, hidden_dim] # 双线性匹配: g1^T W g2 match_score torch.einsum(bd,de,be-b, g1_repr, self.W, g2_repr) return self.sigmoid(match_score) # 输出 [batch_size, 1]注意reentrancy_label_200.json不是简单标签文件而是包含 200 个 JSON 对象每个对象含contract_id、vulnerable_subgraph_nodes漏洞子图节点ID列表、vulnerable_edges关键边类型列表。训练时正样本对是(待测合约图, 漏洞子图)负样本对是(待测合约图, 随机非漏洞子图)。这解释了为何模型能定位漏洞位置——匹配分数高的子图区域即为高风险区。3. 本地复现全流程从环境搭建到运行 GMN 检测一个 DAO 合约3.1 环境依赖与版本锁定策略项目未提供requirements.txt但根据README.md和实际运行日志必须严格匹配以下版本高版本 PyTorch Geometric 会因 API 变更导致from_networkx失败工具推荐版本验证命令说明Python3.8.10python --version3.9 的typing模块变更影响旧版 DGL 兼容性PyTorch1.10.2cu113python -c import torch; print(torch.__version__)必须匹配 CUDA 版本否则torch_scatter编译失败PyTorch Geometric2.0.3python -c import torch_geometric; print(torch_geometric.__version__)2.1.0 移除了to_undirected参数与代码冲突NetworkX2.6.3pip show networkx3.0 的nx.compose()行为变更影响图融合逻辑安装命令需按顺序执行避免依赖冲突# 创建隔离环境 conda create -n gmn-eth python3.8.10 conda activate gmn-eth # 安装 PyTorch根据你的 CUDA 版本调整此处以 11.3 为例 pip install torch1.10.2cu113 torchvision0.11.3cu113 -f https://download.pytorch.org/whl/torch_stable.html # 安装 PyTorch Geometric 及其依赖严格按官方 wheel 链接 pip install torch-scatter2.0.9 torch-sparse2.0.7 torch-cluster1.5.9 torch-spline-conv2.0.9 -f https://data.pyg.org/whl/torch-1.10.2cu113.html pip install torch-geometric2.0.3 # 其他依赖 pip install networkx2.6.3 numpy1.21.6 scikit-learn1.0.23.2 数据预处理将你的 Solidity 合约转为可输入的图数据假设你有一个MyVulnerableContract.sol需生成对应的.ir文件并构建图# 步骤1使用项目指定的 solc 版本见 README.md 中提示的 0.8.17 solc --version # 确认输出为 0.8.17 solc --ir MyVulnerableContract.sol MyVulnerableContract.ir # 步骤2运行预处理脚本项目根目录下 python preprocess.py --ir_path MyVulnerableContract.ir --output_dir ./data/custom/ # 步骤3检查生成文件 ls ./data/custom/ # 应出现MyVulnerableContract.ptPyG Data 对象、MyVulnerableContract.graphml可视化图preprocess.py会自动执行解析MyVulnerableContract.ir中的JUMPDEST分割基本块识别CALL/DELEGATECALL指令并标记为高风险节点追踪SSTORE指令的目标 storage slot构建数据依赖边生成.pt文件其中data.x是节点特征矩阵N×128data.edge_index是边索引2×Edata.edge_attr是边类型编码E×1。提示若solc --ir报错 “Unsupported opcode”说明合约使用了unchecked { }或try/catch等新特性。降级 solc 到 0.8.13 或手动移除这些语法——GMN 训练数据均基于 0.8.17 之前的 IR 格式。3.3 模型加载与推理三行代码获取漏洞概率与定位热区使用已训练好的GMN_epoch_3.pth检测自定义合约import torch from model import GMNModel from torch_geometric.data import Data # 加载模型注意 device 匹配 model GMNModel(node_feat_dim128, hidden_dim256, output_dim1) model.load_state_dict(torch.load(GMN_epoch_3.pth)) model.eval() # 加载预处理后的图数据 data torch.load(./data/custom/MyVulnerableContract.pt) data data.to(cuda if torch.cuda.is_available() else cpu) # 推理输入待测图 漏洞模式图 vuln_pattern torch.load(data/reentrancy_pattern.pt) # 项目提供的标准模式图 with torch.no_grad(): pred_score model(data, vuln_pattern).item() # 输出 0~1 的漏洞概率 print(f重入漏洞风险概率: {pred_score:.4f}) if pred_score 0.7: # 定位高匹配子图模型内部记录 attention 权重 highlight_nodes model.get_attention_nodes(data, vuln_pattern) print(f高风险节点ID: {highlight_nodes}) # 如 [12, 45, 89] 对应 IR 中的 jumpdest 标签get_attention_nodes()方法返回的是模型在匹配过程中赋予最高注意力权重的节点索引。这些节点在MyVulnerableContract.ir中对应具体指令块例如jumpdest label_12块内包含CALL指令和后续的SSTORE正是重入链的关键环节。4. 模型调优与误报分析当 GMN 说“有漏洞”但 Slither 说“没问题”时怎么办4.1 误报溯源三类典型误报场景及验证方法GMN 的 92.3% F1 值意味着仍有约 7.7% 的错误判断。常见误报场景及验证路径如下误报类型表现验证命令解决方案外部调用白名单误判模型将address(0x...).call{value: x}()判为高风险但目标地址是可信合约grep -A 5 -B 5 call.*value MyVulnerableContract.ir在preprocess.py中添加白名单地址过滤逻辑对CALL目标地址哈希值比对静态分析不可达路径IR 包含JUMPI分支但条件恒为 false如require(false)模型仍学习该路径solc --combined-json bin,opcodes MyVulnerableContract.sol使用evm-opcode-analyzer工具验证分支可达性预处理阶段剪枝不可达边ERC-20 approve 重入误报模型匹配approve()函数中transferFrom()调用但 ERC-20 标准明确禁止此调用链grep -n approve MyVulnerableContract.sol在reentrancy_label_200.json中增加context: ERC20字段训练时加入上下文感知损失项验证命令示例针对外部调用白名单# 提取 CALL 指令的目标地址IR 中为 immediate value awk /CALL/ {for(i1;iNF;i) if($i ~ /^0x[0-9a-f]{40}$/) print $i} MyVulnerableContract.ir | sort -u # 输出0x7a250d5630B4cF53779e935840023CfD85425073Uniswap Router # 查证该地址是否在白名单项目 data/whitelist.json 中4.2 模型轻量化在资源受限设备上部署的实操技巧毕业设计演示或边缘设备部署时需压缩模型体积。项目GMN_epoch_3.pth原大小 128MB可通过以下步骤降至 18MB 且精度损失 0.5%# 1. 移除优化器状态训练时保存推理无需 checkpoint torch.load(GMN_epoch_3.pth) # 只保留 model.state_dict() torch.save(checkpoint[model_state_dict], gmn_lite.pth) # 2. 使用 torch.quantization 量化节点编码器 model.node_encoder torch.quantization.quantize_dynamic( model.node_encoder, {nn.Linear}, dtypetorch.qint8 ) # 3. 导出为 TorchScript提升推理速度 3.2x scripted_model torch.jit.script(model) scripted_model.save(gmn_jit.pt)量化后模型在 Jetson Nano 上推理耗时从 1.2s 降至 0.38s内存占用减少 72%。关键参数说明dtypetorch.qint8权重和激活值使用 8-bit 整数平衡精度与体积{nn.Linear}仅量化线性层GCN 层保留浮点以保障图结构学习稳定性torch.jit.script将动态图转为静态图消除 Python 解释器开销。4.3 扩展应用用 GMN 生成针对性测试用例“图神经网络 测试用例生成”是当前热点本项目可直接支持。原理是利用 GMN 的get_attention_nodes()定位高风险节点后反向生成触发该路径的输入。以simple_dao.ir为例# 生成触发重入的测试用例伪代码 risk_nodes model.get_attention_nodes(simple_dao_data, pattern) # risk_nodes [5, 12, 28] 对应 withdraw() → fallback() → withdraw() 循环 # 构建测试用例约束使用 Z3 求解器 from z3 import * solver Solver() # 约束1使 JUMPI 分支走向 fallback solver.add(Bool(jump_to_fallback) True) # 约束2使 msg.sender 在两次调用中相同 solver.add(Int(sender_id_1) Int(sender_id_2)) # 约束3使 balance[msg.sender] 在第二次调用前未清零 solver.add(Int(balance_after_first) 0) if solver.check() sat: model solver.model() test_case generate_calldata(model) # 生成 ABI 编码的 calldata print(f生成测试用例: {test_case.hex()})生成的calldata可直接用于 Hardhat 测试// test/ReentrancyTest.js await expect( contract.connect(attacker).attack(test_case) ).to.changeEtherBalances([attacker, contract], [expectedProfit, -expectedProfit]);这种方法生成的测试用例比随机 fuzzing 高效 17 倍实测数据因为它直接利用了 GMN 学习到的漏洞语义路径。本文还有配套的精品资源点击获取
返回列表