资讯详情

基于Solidity AST与图神经网络的智能合约行级漏洞检测

📅 2026/9/14 19:23:48 | 华诺云谱 👁 阅读
基于Solidity AST与图神经网络的智能合约行级漏洞检测
简介本资源是一套高分毕业设计级的智能合约漏洞检测实践方案面向计算机、人工智能、区块链等方向的学生与开发者聚焦源代码层面的图神经网络建模与多维度漏洞识别问题。项目采用AST-GNN等图融合技术覆盖重入攻击、算术溢出、时间戳依赖等典型漏洞的标签构建、数据流/控制流信息注入、模型训练与预测全流程适合作为课程设计、毕设参考或安全研究入门范例。压缩包共48个文件含35个Python核心脚本如main.py、model.py、data_process.py及各类attack_label生成模块、6个XML配置与数据定义文件、2个说明文档含环境还原指南、1个docx手册、1个yml环境配置及配套gitignore等整体14.84MB结构清晰、模块解耦。已有43人学习下载提供完整可运行代码、详细文档、预处理数据集与测试用例附带编译工具链、评估指标实现及多种分类模型合约级/行级训练脚本便于快速复现、调试与二次开发。1. 这不是又一个“调用 web3.py 扫地址”的检测脚本而是一套从 Solidity 源码 AST 出发、融合控制流与数据流图、用 GNN 做细粒度行级分类的漏洞识别闭环你可能已经试过 Slither、Mythril 或 Oyente——它们能报出 reentrancy、integer overflow但报错位置常是函数级无法定位到具体哪一行赋值语句触发了危险的数据流你也可能跑过基于 IR 的模型但 IR 已丢失原始变量命名、注释、结构体嵌套等语义线索。这个毕设项目反其道而行不编译、不反编译、不依赖 EVM 字节码直接吃.sol源文件解析出 AST再把 AST 节点、控制流边、数据流边三者构建成异构图喂给一个定制化的 AST-GNN 模型最终输出每行代码的漏洞概率分。它不是黑盒扫描器而是可解释、可调试、可逐层可视化图结构的检测流水线。98 分答辩的核心支撑正是这套“源码 → AST → 图构建 → GNN 编码 → 行级预测”的全链路可控性。适合两类人一是刚接触智能合约安全的学生能看清从require(msg.sender owner)到图节点再到预测标签的完整映射二是已有静态分析经验的工程师可直接复用append_control_flow_information.py和append_data_flow_information.py中的 CFG/DFG 构建逻辑嵌入自己的检测框架。2. AST 解析与多维度图构建为什么必须用 Solidity AST 而非通用编程语言解析器2.1 Solidity 特异性 AST 提取绕过通用 parser 的语义失真Solidity 的语法糖如a b展开为a a b、类型推导uint x;vsuint256 x;、继承链is Ownable, ERC20在通用 AST 工具如 tree-sitter中常被扁平化或忽略。本项目采用py-solc-x配合自定义 visitor先编译生成标准 JSON AST再用Node.py封装节点类型系统。关键在于保留ContractDefinition、FunctionDefinition、VariableDeclaration、BinaryOperation等原生节点并打上sourceLocation字段含起始/结束行号、列号这是后续行级标注的基础。# utils.py 中 extract_ast_nodes 的核心逻辑 def extract_ast_nodes(sol_file: str) - List[Dict]: # 使用 py-solc-x 编译获取 AST JSON compiled compile_files([sol_file], solc_version0.8.19) ast_json compiled[sol_file][ast] nodes [] for node in traverse_ast(ast_json): # 自定义深度优先遍历 if node.get(nodeType) in [VariableDeclaration, BinaryOperation, IfStatement]: # 严格提取 sourceLocation过滤掉无位置信息的 synthetic node loc node.get(src, ).split(:) if len(loc) 3: nodes.append({ type: node[nodeType], line_start: int(loc[1]), line_end: int(loc[1]) (int(loc[2]) // 100), # 粗略估算行数跨度 code: node.get(name, ) or node.get(operator, ), children: [c[id] for c in node.get(nodes, [])] }) return nodes提示py-solc-x必须指定与合约匹配的 Solidity 版本如0.8.19否则src字段可能为空或格式错乱。line_end的估算逻辑来自 Solidity 官方 AST 文档中src字段的start:length:file格式length单位是字符数除以 100 是经验性换算因平均行宽约 100 字符实际部署时建议改用asttokens库精确定位。2.2 控制流图CFG与数据流图DFG的联合注入仅靠 AST 节点无法捕获跨语句依赖。例如reentrancy漏洞需识别“外部调用前未更新状态”这一模式这要求连接state_var value与external_call()之间的数据依赖边。项目通过两阶段注入append_control_flow_information.py基于FunctionDefinition节点用if/else/for/while关键字识别基本块边界用return/break/continue识别跳转目标生成CFGEdge(from_node_id, to_node_id, edge_typeconditional|unconditional)append_data_flow_information.py对每个VariableDeclaration和BinaryOperation追踪leftHandSide变量在后续ExpressionStatement中是否作为rightHandSide出现生成DFGEdge(var_node_id, use_node_id, data_typetaint|control)# append_data_flow_information.py 中的关键追踪逻辑 def build_dfg_for_function(ast_nodes: List[Dict]) - List[Dict]: dfg_edges [] # 第一步收集所有变量声明节点及其作用域 var_decls {n[id]: n for n in ast_nodes if n[type] VariableDeclaration} # 第二步遍历所有表达式节点检查其右操作数是否引用已声明变量 for expr_node in [n for n in ast_nodes if n[type] BinaryOperation]: rhs_vars extract_identifiers(expr_node.get(rightHandSide, {})) for var_name in rhs_vars: # 在当前函数作用域内查找该变量声明 decl find_var_declaration_in_scope(var_name, var_decls, expr_node) if decl: dfg_edges.append({ from: decl[id], to: expr_node[id], type: data_dependence, var_name: var_name }) return dfg_edges注意extract_identifiers不是简单正则匹配而是递归解析Identifier、MemberAccess如msg.sender、IndexAccess如arr[i]节点确保捕获所有变量引用路径。find_var_declaration_in_scope实现了作用域链查找支持for (uint i0; i10; i)中的i仅在 for 块内有效。2.3 异构图融合AST-GNN 的输入图结构设计最终输入 GNN 的图不是单一类型而是三类节点、四类边构成的异构图节点类型示例数量级单合约AST_NodeBinaryOperation,IfStatement200–800ControlFlowBlockBasicBlock_0x1a,ExitBlock10–50DataVariableowner,balance[msg.sender]5–30边类型连接语义ast_child_ofBinaryOperation→IdentifierAST 层次结构cfg_nextBasicBlock_A→BasicBlock_B控制流顺序dfg_taintsowner→require(msg.sender owner)数据污染传播ast_in_blockBinaryOperation→BasicBlock_AAST 节点归属基本块该结构被序列化为 PyTorch Geometric 的HeteroData对象存入built_vector_dataset.py生成的.pt文件。model.py中的ASTGNN模型会为每类节点初始化独立 embedding再通过HeteroConv层进行跨类型消息传递。3. AST-GNN 模型训练与行级漏洞标注如何让模型学会“看懂” Solidity 语义3.1 漏洞标签的生成逻辑从合约级标签到行级概率公开数据集如 SmartBugs只提供合约级标签has_reentrancy: True/False但本项目要求行级预测。解决方案是规则驱动人工校验的半自动标注make_reentry_attack_label.py扫描所有call.value()、delegatecall、staticcall调用点向前追溯最近的state_var ...赋值语句在同函数内且无条件分支阻断将该赋值行标记为reentrancy_risk: 0.9make_timestamp_attack_label.py匹配block.timestamp或now的使用并检查是否用于访问控制如if (block.timestamp deadline)将该行标记为timestamp_dependency: 0.8make_arithmetic_attack_label.py用asttokens精确定位,-,*,/操作符所在行若操作数含uint类型且无require(a b)校验则标记为arithmetic_overflow: 0.7# make_reentry_attack_label.py 中的追溯逻辑 def find_state_update_before_call(call_node: Dict, ast_nodes: List[Dict]) - Optional[Dict]: # 获取 call_node 所在行号 call_line call_node[line_start] # 向上搜索同一函数内的 VariableDeclaration 节点 for node in reversed(ast_nodes): if node[type] VariableDeclaration and node[line_start] call_line: # 检查该变量是否为状态变量有 storage 关键字或无 memory 关键字 if storage in node.get(visibility, ) or memory not in node.get(visibility, ): # 检查从该赋值到 call 之间是否有 if/for/while 阻断简单启发式 if not has_control_flow_between(node[line_end], call_line, ast_nodes): return node return None提示has_control_flow_between并非精确 CFG 分析而是扫描行号区间内是否存在if(、for(、while(字符串这是在毕设精度与实现复杂度间的务实折中。真实工业场景应替换为CFGEdge查询。3.2 AST-GNN 模型架构三层异构消息传递的设计意图model.py中的ASTGNN不是简单堆叠 GCN 层而是针对 Solidity 语义设计的三阶段处理AST Embedding Layer对AST_Node类型节点用TransformerConv处理ast_child_of边学习语法结构如IfStatement下必有condition和trueBody子节点Cross-Modal Fusion Layer用HeteroConv聚合ast_in_block边将 AST 节点特征注入ControlFlowBlock节点使基本块获得其内部 AST 的语义摘要Line-Level Prediction Head对每个AST_Node节点拼接其自身 embedding 与所属ControlFlowBlock的 embedding经Linear(256, 128) → ReLU → Linear(128, 4)输出四类漏洞概率reentrancy / timestamp / overflow / unchecked_call# model.py 中的 forward 方法关键片段 class ASTGNN(torch.nn.Module): def __init__(self, hidden_channels128): super().__init__() # Step 1: AST-specific encoding self.ast_conv TransformerConv( in_channels64, out_channelshidden_channels, heads4, dropout0.1, edge_dim16 ) # Step 2: Heterogeneous fusion self.hetero_conv HeteroConv({ (AST_Node, ast_in_block, ControlFlowBlock): SAGEConv((-1, -1), hidden_channels), (ControlFlowBlock, cfg_next, ControlFlowBlock): GATv2Conv((-1, -1), hidden_channels), }, aggrsum) # Step 3: Line-level classification self.classifier Sequential( Linear(hidden_channels * 2, 128), ReLU(), Dropout(0.3), Linear(128, 4) # 4 vulnerability types ) def forward(self, data: HeteroData) - Tensor: # Encode AST nodes x_dict {} x_dict[AST_Node] self.ast_conv( data[AST_Node].x, data[AST_Node, ast_child_of, AST_Node].edge_index ) # Fuse with control flow blocks x_dict self.hetero_conv(x_dict, data.edge_index_dict) # Predict per AST node (i.e., per line) ast_x x_dict[AST_Node] block_x x_dict[ControlFlowBlock][data[AST_Node].block_id] # block_id 是预计算的索引 combined torch.cat([ast_x, block_x], dim1) return self.classifier(combined)注意data[AST_Node].block_id是在dataset.py中预计算并存入HeteroData的张量避免运行时重复查询。SAGEConv用于ast_in_block边因其聚合邻居时更鲁棒对 block 内节点数量变化不敏感GATv2Conv用于cfg_next边因其能学习不同控制流路径的重要性权重。3.3 训练配置与验证指标为何选用 F1-macro 而非准确率数据集由 127 个真实合约含 OpenZeppelin、Uniswap V2 子集和 38 个人工构造漏洞合约组成按 7:2:1 划分训练/验证/测试集。关键配置如下参数值说明batch_size8因图大小差异大小 batch 避免 OOMlearning_rate2e-4AdamW 优化器配合线性 warmupepochs120验证集 F1-macro 连续 15 轮不升则早停loss_fnFocalLoss(alpha0.75, gamma2.0)解决四类漏洞样本不均衡reentrancy 占 42%unchecked_call 仅 8%验证时不用整体准确率因漏洞行占比常低于 5%准确率易被大量负样本拉高。metric.py中的compute_f1_macro计算每类漏洞的 F1 值后取平均# metric.py def compute_f1_macro(y_true: np.ndarray, y_pred: np.ndarray) - float: y_true: (N,) int array, 0reentrancy, 1timestamp, 2overflow, 3unchecked_call, 4clean y_pred: (N, 5) float array, softmax output y_pred_labels np.argmax(y_pred, axis1) # 计算每类的 precision/recall/f1忽略 clean 类类别 4 f1_scores [] for cls in [0, 1, 2, 3]: tp np.sum((y_true cls) (y_pred_labels cls)) fp np.sum((y_true ! cls) (y_pred_labels cls)) fn np.sum((y_true cls) (y_pred_labels ! cls)) precision tp / (tp fp) if (tp fp) 0 else 0.0 recall tp / (tp fn) if (tp fn) 0 else 0.0 f1 2 * precision * recall / (precision recall) if (precision recall) 0 else 0.0 f1_scores.append(f1) return np.mean(f1_scores)4. 本地环境还原与端到端推理从解压 ZIP 到输出带高亮的漏洞报告4.1 MVD-HG 环境还原Conda 环境与依赖冲突的解决路径项目附带environment.yml但直接conda env create -f environment.yml常失败原因有三pytorch-geometric与pytorch版本强绑定、py-solc-x依赖特定solc二进制、Windows 下tree-sitter编译失败。推荐分步还原# 步骤1创建基础环境指定 Python 3.9避坑 pytorch-geometric 2.4 conda create -n astgnn python3.9 conda activate astgnn # 步骤2安装 PyTorch根据 CUDA 版本选此处以 CPU 为例 pip install torch2.0.1cpu torchvision0.15.2cpu torchaudio2.0.2cpu -f https://download.pytorch.org/whl/torch_stable.html # 步骤3安装 PyTorch Geometric严格对应 torch 2.0.1 pip install torch-scatter torch-sparse torch-cluster torch-spline-conv -f https://data.pyg.org/whl/torch-2.0.1cpu.html pip install torch-geometric2.3.0 # 步骤4安装 Solidity 工具链 pip install py-solc-x8.4.0 # 手动下载 solc 0.8.19https://github.com/ethereum/solidity/releases/download/v0.8.19/solc-static-linux chmod x solc-static-linux sudo mv solc-static-linux /usr/local/bin/solc提示若pip install torch-geometric2.3.0报No matching distribution请确认torch版本是否为2.0.1cpu而非2.0.1末尾cpu是关键标识。py-solc-x8.4.0是最后一个兼容solc 0.8.19的版本。4.2 端到端推理流程main.py的三阶段执行链main.py是入口脚本执行data_process.py → train.py → load_model_to_predict.py三阶段。新手可跳过训练直接用预训练模型# 解压资源包后进入项目根目录 unzip 基于源代码的图融合的智能合约漏洞检测全部资料详细文档高分毕设.zip cd 基于源代码的图融合的智能合约漏洞检测全部资料详细文档高分毕设 # 步骤1预处理单个合约生成 AST CFG DFG python data_process.py --input contracts/sample.sol --output processed/sample.pt # 步骤2加载预训练模型并预测模型文件在 model/ 目录下 python load_model_to_predict.py \ --model_path model/best_astgnn_epoch112.pt \ --data_path processed/sample.pt \ --output_report reports/sample_vuln.json # 步骤3生成 HTML 报告高亮风险行 python Test.py --json reports/sample_vuln.json --sol contracts/sample.sol --html reports/sample_report.htmlTest.py会读取sample_vuln.json中的{line: 42, vul_type: reentrancy, score: 0.93}在sample.sol第 42 行添加span classvul-reentrancy标签最终生成带颜色高亮的 HTML。4.3 漏洞报告解读与可信度验证技巧生成的sample_vuln.json结构如下[ { line: 87, vul_type: reentrancy, score: 0.89, explanation: External call at line 87 uses msg.sender without prior state update; data flow from balance[msg.sender] (line 72) to call.value() (line 87) detected, ast_path: [FunctionDefinition, Block, ExpressionStatement, BinaryOperation] } ]验证可信度的三个动作查 AST 路径用print_tree.py sample.sol输出 AST确认ast_path中的节点确实存在且顺序正确追数据流打开processed/sample.pt用torch.load()查看data[DFGEdge]确认from节点 ID 是否指向balance[msg.sender]的声明比对基线用 Slither 运行同一合约slither contracts/sample.sol --solc-allow-paths .看其报告的reentrancy位置是否与本项目第 87 行重合——若重合可信度大幅提升若不重合检查make_reentry_attack_label.py的追溯逻辑是否漏掉中间赋值注意print_tree.py输出的是缩进式文本 AST非 JSON。它用solc --ast-json生成原始 AST再用Node.py的pretty_print方法美化适合快速定位节点类型与行号无需启动 IDE。5. 进阶技巧如何将此框架迁移到新漏洞类型或新语言5.1 新漏洞类型的快速适配以 “delegatecall 代理劫持” 为例要检测delegatecall被恶意合约利用如Proxy模式中 implementation 地址被篡改只需新增make_delegatecall_hijack_label.py其逻辑与make_reentry_attack_label.py高度相似但检查重点变为delegatecall调用点是否使用address(this).delegatecall(...)implementation变量是否为public或external函数可修改implementation的赋值是否发生在构造函数之外即运行时可变# make_delegatecall_hijack_label.py伪代码 def detect_delegatecall_hijack(ast_nodes: List[Dict]) - List[Dict]: hijack_lines [] # 步骤1找到所有 delegatecall 调用 for call_node in [n for n in ast_nodes if delegatecall in n.get(code, )]: # 步骤2检查是否为 address(this).delegatecall(...) if address(this) in get_caller_expr(call_node): # 步骤3向上追溯 implementation 变量 impl_var find_implementation_var(call_node, ast_nodes) if impl_var and is_publicly_writable(impl_var, ast_nodes): # 步骤4检查 impl_var 是否在构造函数外被赋值 if not is_assigned_in_constructor(impl_var, ast_nodes): hijack_lines.append({ line: call_node[line_start], vul_type: delegatecall_hijack, score: 0.85, explanation: fdelegatecall on line {call_node[line_start]} uses mutable implementation {impl_var[name]} }) return hijack_lines提示is_publicly_writable的判断逻辑是扫描impl_var的所有赋值节点若存在FunctionDefinition节点且visibility为public或external则返回True。这比检查modifier onlyOwner更底层也更可靠。5.2 迁移至其他语言AST-GNN 的跨语言泛化能力边界本框架的 AST 解析部分utils.py,Node.py是 Solidity 特化的但图构建与 GNN 模型model.py,dataset.py具有强泛化性。迁移到 Vyper 或 RustSolana的关键动作是动作Solidity 当前做法迁移至 Vyper 的改动AST 提取py-solc-x JSON AST改用vyper-parser库其输出ast.ASTNode对象需重写traverse_ast以适配 Vyper 的FunctionDef/Assign/Call节点类型CFG 构建基于if/for/while字符串匹配Vyper 无for循环需改为识别for i in range(...)和with语句块DFG 构建追踪Identifier引用Vyper 的self.storage.var访问需特殊处理extract_identifiers需增加MemberAccess解析逻辑真正不可迁移的是漏洞规则引擎make_*.py。Vyper 的reentrancy检测逻辑完全不同因其默认禁用重入此时应废弃make_reentry_attack_label.py转而实现make_vyper_storage_access_label.py聚焦self.storage的越界读写。5.3 模型轻量化部署ONNX 导出与推理加速train.py训练完成后可用以下脚本导出 ONNX 模型供 C 或 WebAssembly 调用# export_onnx.py import torch from model import ASTGNN from torch_geometric.data import HeteroData # 加载训练好的模型 model ASTGNN(hidden_channels128) model.load_state_dict(torch.load(model/best_astgnn_epoch112.pt)) model.eval() # 构造 dummy input需与 dataset.py 中的 HeteroData 结构一致 dummy_data HeteroData() dummy_data[AST_Node].x torch.randn(50, 64) dummy_data[AST_Node, ast_child_of, AST_Node].edge_index torch.randint(0, 50, (2, 100)) # ... 其他节点和边的 dummy tensor # 导出 torch.onnx.export( model, (dummy_data,), # 注意HeteroData 是 tuple 输入 astgnn.onnx, input_names[hetero_data], output_names[vul_probs], dynamic_axes{ hetero_data: {0: num_nodes}, # 节点数动态 vul_probs: {0: num_predictions} }, opset_version15 )导出的astgnn.onnx可用onnxruntime在 CPU 上达到 120ms/合约RTX 3090 测试比 PyTorch 原生推理快 3.2 倍且内存占用降低 60%。这对于集成到 Remix IDE 插件或 CI/CD 流水线至关重要。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。