news 2026/9/11 21:48:25

基于深度学习的智能合约漏洞检测:从Solidity到TextCNN的完整实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于深度学习的智能合约漏洞检测:从Solidity到TextCNN的完整实践

简介:面向计算机、人工智能、自动化等专业学生与从业者的深度学习区块链智能合约安全检测毕设资源包,解决从零实现合约漏洞分析与安全检测模型搭建的难题,适用于毕业设计、课程设计、期末大作业或区块链安全方向入门实践。项目经调试验证可稳定运行,答辩评审98分,代码结构清晰,适合小白学习进阶,也能为基础较强的开发者提供修改扩展的基础。压缩包共41个文件,大小仅71KB,以vue组件、js逻辑、scss样式为主,搭配json配置、svg图标、README说明与入口html,覆盖前端界面、状态管理、路由构建等模块,整体目录结构清晰,便于按功能模块定位阅读。已有410人学习下载;配合完整源码与文档说明,可直观了解深度学习与区块链智能合约安全检测的结合方式,掌握数据展示、交互逻辑与项目组织方法,亦可作为期末课程设计或毕业设计的参考模板。

1. 为什么把智能合约安全检测押在深度学习上

智能合约一旦部署上链,修复漏洞的成本高到难以承受,重入、整数溢出、未授权访问这类经典问题至今仍在不断造成实际损失。传统检测依赖人工编写的规则,比如匹配call.value()transfer()的调用模式,这类方案的可解释性强,但规则覆盖需要长期维护,对未见过的攻击模式几乎无能为力。把检测问题建模成数据驱动任务,让模型自己从大量已标注合约中学习漏洞代码的“长相”,这是近几年被验证过的有效路线。这篇文章要讲的就是一条从原始 Solidity 源码,到特征构造、模型训练、评估、再到系统化落地的完整路线。适合正在做相关课题的学生,也适合想给合约安全工具链补充深度学习能力的工程师——你不需要有链上开发经验,但最好有一些 Python 和 PyTorch 基础,能跟到命令和参数层面。

2. 智能合约数据形态与序列特征构造

用深度学习做检测,第一步不是选模型,而是搞清楚输入到底是什么。智能合约可从四个层面拿到数据:Solidity 源码、字节码、操作码、交易运行行为。多数研究项目和毕设首选 Solidity 源码,因为标注成本低、语义信息保留完整;字节码和操作码则更接近链上真实状态,适合做部署后监测,但反编译和特征提取的工作量明显更大。更稳妥的做法是源码为主、字节码为辅:源码用来训练分类模型,字节码用于对已部署合约做批量扫描。

2.1.1 源码级特征提取的最小实现

把 Solidity 文本转成模型可读的序列,常见做法是词法级切分加关键字映射。下面这段代码能跑通一条最小预处理管线:

import re import torch from torch.nn.utils.rnn import pad_sequence KEYWORDS = { 'contract': 1, 'function': 2, 'require': 3, 'assert': 4, 'call': 5, 'delegatecall': 6, 'transfer': 7, 'send': 8, 'revert': 9, 'mapping': 10, 'uint': 11, 'address': 12, 'block': 13, 'msg': 14, 'tx': 15, 'selfdestruct': 16 } def solidity_to_tokens(source: str) -> list: source = re.sub(r'[0-9]+', 'N', source) tokens = re.findall(r'[a-zA-Z_][a-zA-Z0-9_]*|[{}();,=:.]|<=|>=|==|!=', source) ids = [] for t in tokens: base = t.split('_')[0] ids.append(KEYWORDS.get(base, 1000)) return ids def collate_fn(batch): seqs, labels = zip(*batch) seqs = [torch.tensor(s, dtype=torch.long) for s in seqs] padded = pad_sequence(seqs, batch_first=True, padding_value=0) return padded, torch.tensor(labels, dtype=torch.long)

这段代码做了三件事:数字统一替换成 N,避免模型把10100当成不同特征;用正则把代码切成 token;再根据关键字表映射成 ID。函数名不做归一化时,训练集里出现的函数名分布会严重干扰模型判断,所以这里只取第一个下划线前的单词,能保留transferwithdraw这类语义,同时丢弃合约特有的随机后缀。pad_sequence负责把同一批次内不同长度的序列补齐,padding_value=0表示用 0 作为填充符,这也和后续nn.Embeddingpadding_idx=0相对应。

2.1.2 四种输入形态怎么选

不同输入形态对检测效果和应用场景的影响,用一个对比表就能说清:

数据形态获取成本信息密度适合的模型典型应用
Solidity 源码高(含注释、语义)TextCNN、LSTM审计辅助、静态检测
字节码中(需反编译)LSTM、Transformer链上批量扫描
操作码中(含控制流线索)BiLSTM、GNN已部署合约分析
交易行为低(依赖调用上下文)时序模型实时风控

源码的获取成本最低,开源合约仓库、etherscan 源码验证页面都能批量爬;字节码和操作码需要引入编译工具链,比如solc --opcodes可以导出合约的操作码序列,但编译版本和优化开关会影响结果,做实验时要固定为同一版本。标注数据方面,公开数据集可以搜 solc 相关漏洞合集,自己搭建测试集时建议每条样本同时保留源码和编译产物,方便后续补充特征。

2.1.3 序列长度和一个人工检查

合约源码长短差异很大,有的几十行,有的上千行。训练时如果直接截断到固定长度,容易丢尾部逻辑。建议先统计训练集长度分布,再定max_len,超过截断、不足补齐。这条处理逻辑必须在预处理阶段保留,否则推理时遇到超长合约会出现维度不匹配,排查起来非常费时间。

提示:预处理和模型训练用同一套代码,不要手工在验证集上“顺手处理”,这是我见过最多低级 bug 的来源。

3. 用 PyTorch 搭建一个可训练的漏洞检测模型

把检测问题建模为多分类:输入 token 序列,输出漏洞类别。TextCNN 是最好的起步方案,它结构简单、训练速度快、对合约这种局部模式更突出的数据表现稳定。BiLSTM 和 Transformer 也可以,但 BiLSTM 在长序列上的训练效率和收敛稳定性都不如 TextCNN,Transformer 对数据量要求高,放在毕设场景里容易因为样本不足而过拟合。

3.1.1 TextCNN 模型定义
import torch import torch.nn as nn class TextCNN(nn.Module): def __init__(self, vocab_size, embed_dim=128, num_classes=6, kernel_sizes=(3, 4, 5), num_filters=128): super().__init__() self.embedding = nn.Embedding(vocab_size, embed_dim, padding_idx=0) self.convs = nn.ModuleList([ nn.Conv1d(embed_dim, num_filters, k) for k in kernel_sizes ]) self.dropout = nn.Dropout(0.5) self.fc = nn.Linear(num_filters * len(kernel_sizes), num_classes) def forward(self, x): emb = self.embedding(x) # (B, L, E) emb = emb.transpose(1, 2) # (B, E, L) pooled = [] for conv in self.convs: c = torch.relu(conv(emb)) # (B, F, L - k + 1) p = torch.max(c, dim=2)[0] # 全局最大池化 pooled.append(p) out = torch.cat(pooled, dim=1) return self.fc(self.dropout(out))

padding_idx=0让 embedding 层在计算梯度时跳过填充位置,避免填充符影响卷积特征。卷积核大小分别取 3、4、5,对应 3-gram 到 5-gram 的局部模式,正好覆盖requirecalltransfer这类短关键字组合。全局最大池化保留每个卷积通道中最强的特征,比平均池化更能捕捉漏洞触发的关键片段。

3.1.2 训练参数和完整训练循环
from torch.utils.data import DataLoader, TensorDataset model = TextCNN(vocab_size=len(KEYWORDS) + 1001, num_classes=6) optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) criterion = nn.CrossEntropyLoss() for epoch in range(30): model.train() total_loss = 0 for tokens, labels in train_loader: optimizer.zero_grad() logits = model(tokens) loss = criterion(logits, labels) loss.backward() optimizer.step() total_loss += loss.item() print(f"epoch {epoch:02d} loss {total_loss / len(train_loader):.4f}")

学习率初始1e-3,batch size 用 32 或 64。类别不均衡时,给CrossEntropyLoss传入weight参数,按样本数倒数归一化,能明显提升少样本类别的召回。训练轮次不要拍脑袋定 30,应该配合 early stopping:记录验证集 F1,连续 5 轮不提升就停止,保存最优权重。这一步被很多入门代码忽略,结果就是模型在训练集上刷高分,验证集上表现平平。

3.1.3 环境配置里最容易卡住的点

PyTorch 版本和 CUDA 版本不匹配是重复率最高的报错来源。建议直接用 conda 创建独立环境:

conda create -n sol-det python=3.10 -y conda activate sol-det pip install torch --index-url https://download.pytorch.org/whl/cu118

CUDA 版本按自己显卡驱动选择,不确定就先用 CPU 版本跑通逻辑,再换 GPU。另一个常见问题是坑在混用torchtext版本,旧版torchtext已不再维护,不要引入不相干的全局实验记录器,保持环境最小化,出问题能快速定位。数据量不大时,用普通 Python 在collate_fn里做切分就够,不必上复杂流水线。

4. 评估要看得分更要看漏报:指标与对比实验

毕业设计答辩时,评委最关心的问题通常不是“准确率多高”,而是“漏报多少”“误报多少”。多分类任务里 Accuracy 会被多数类主导,比如 90% 样本是正常合约时,模型全输出正常也能有 90% 准确率,这个数字毫无意义。必须按类别分别看 Precision、Recall、F1,并针对漏洞类别单独关注召回率——漏一个漏洞的后果远大于多报一个。

4.1.1 一个可以直接跑完的评估脚本

模型训练完成后,在验证集上跑出完整分类报告:

from sklearn.metrics import classification_report, confusion_matrix import numpy as np LABELS = ["normal", "reentrancy", "integer_overflow", "access_control", "unchecked_call", "gas_grief"] model.eval() all_preds, all_labels = [], [] with torch.no_grad(): for tokens, labels in val_loader: logits = model(tokens) preds = logits.argmax(dim=1) all_preds.extend(preds.cpu().tolist()) all_labels.extend(labels.cpu().tolist()) print(classification_report(all_labels, all_preds, target_names=LABELS, digits=4)) print(confusion_matrix(all_labels, all_preds))

classification_report会分别输出每个类别的精确率、召回率和 F1,digits=4是为了在小样本类别上能看到差异,默认的 2 位小数会掩盖全是 1.00 的假象。查看模型输出的困惑是:多分类模型的argmax拿到的是索引,必须和LABELS列表顺序对齐,训练时的类别映射和评估时不一致,结果会错位到完全离谱但仍显示高分。

4.1.2 和现有工具做对比实验

很多毕设题目里会要求“对比实验”。常见做法是拿 Slither、Mythril 这类规则工具在同一批测试集上跑一遍,将结果作为基线。用批处理脚本汇总到一张表里:

slither sample_contracts/reentrancy_01.sol --json slither_out.json 2>/dev/null

Slither 输出 JSON 后,用 Python 解析出检测到的漏洞类别,再和模型预测做交集对比。规则工具在简单重入样本上几乎百发百中,但在未知模式上会漏;深度模型相反,能发现“长得像漏洞”却解释不清为什么的样本。评价自己的模型不需要全面超越 Slither,能证明“在误报率相近时检出更多变体”就有说服力。用不同随机种子跑 3 次取均值,能有效避开你只靠运气挑出好结果——这一条在论文里特别好使,测试结果的可复现性立刻上一个档次。

4.1.3 边界样本和错误分析

评估的意义不只是打印几个分数。从混淆矩阵里挑出预测错误最多的样本,逐条看预测时错误的模式。常见结果是两类跨越:unchecked_call被识别成reentrancy,因为两个模式都包含call关键字;access_control样本因为缺少require判定被归为normal。这类错误暴露的是特征表达不足,而不是模型隐藏层容量不够,方法也很明确:给requiremodifier这类权限控制相关 token 单独建特征通道,在用 embedding 时把modifierrequire编码到相邻区间,模型会更容易捕获它们的共现关系。

5. 从模型到毕业设计交付:系统架构与接口封装

模型能跑只是一个“深度学习训练的探索”,毕设要求的是可展示、可操作的系统。完整系统通常拆成数据层、检测层、服务层、展示层。数据层负责预处理和缓存,检测层加载模型做预测,服务层提供 REST 接口并做结果落库,展示层就是网页。中间任何一层出错,都要能看到日志定位而不是丢一个 500 了事。

5.1.1 用 FastAPI 封装检测接口
from fastapi import FastAPI, UploadFile, HTTPException from pydantic import BaseModel app = FastAPI() model, preprocessor = load_model("checkpoints/best.pt") class DetectResponse(BaseModel): vulnerability_type: str confidence: float position_hint: str = "" @app.post("/detect") async def detect(file: UploadFile): if not file.filename.endswith(".sol"): raise HTTPException(status_code=400, detail="unsupported file type") code = (await file.read()).decode("utf-8") tokens = preprocessor.encode(code) prob = model.predict_proba(tokens) idx = prob.argmax() if prob[idx] < 0.7: return DetectResponse(vulnerability_type="normal", confidence=prob[idx]) return DetectResponse(vulnerability_type=LABELS[idx], confidence=prob[idx])

predict_proba在模型内部对 logits 做 softmax 后返回完整概率分布;低于 0.7 时返回 normal,防止低置信度预测被当作实锤。这块逻辑务必要和训练脚本解耦:训练脚本负责模型迭代,API 只负责推理,模型文件用固定路径加版本号管理,避免训练时覆盖掉线上权重。

5.1.2 可视化页面和批量扫描

界面不炫没关系,但要完整闭环:后端提供一个 POST 接口负责单文件检测,一个 GET 接口负责扫描记录查询,前端写一个文件上传区域、解析按钮、结果展示列表。上传后服务端解析返回 JSON,前端渲染出漏洞类别和置信度。批量扫描通过os.walk遍历合约目录,把每条样本的检测结果写进 CSV 或 SQLite,方便后续统计。毕设展示时最有冲击力的演示是:放一个含重入漏洞的合约文本,页面几秒返回reentrancy 0.98,比任何指标都直观。

5.1.3 源码组织和文档说明怎么交

毕设源码要能一键跑起来:写清楚 Python 版本、依赖文件、训练入口、推理入口、每个模块的输入输出。常见便捷做法是用requirements.txt锁定全量依赖,而不是只写几个大包名。模型权重文件太大不进源码包,但必须说明获取途径“训练后由train.py输出”,并给出存放目录。文档里把实验环境、预处理流程、模型结构、评测表写清楚,答辩时基本不会被问倒。建议把一天内跑通的最小命令放在 README 第一屏:

pip install -r requirements.txt python train.py --data data/ --epochs 30 python api.py --port 8000

这里的三行命令要确保每个都真实可执行,我在不少项目里见过文档写得完整但实际运行就报错的。

6. 用阈值过滤和半监督把误报宰下去

模型的输出是概率分布,直接取 argmax 只是默认做法。调整一下判定阈值,对线上效果的影响往往比换模型还大。规则是:对漏报率要求高的场景,压低判为漏洞的阈值;对误报容忍度低的场景,抬高置信度门槛。具体做法是跑一遍验证集,按置信度从 0.5 到 0.9 间隔 0.05 遍历,记录每个阈值下的召回率和精确率,选一个让 F1 最高的点作为最终判定线。

未标注合约也能发挥作用:让模型给出预测后,把置信度大于 0.95 的样本当成伪标签混入训练集,做两轮迭代训练。对合约这种“同类漏洞代码相似度高”的数据,这种半监督训练对提升小类别召回的效果比单纯加宽模型更明显。你的全套流程最终会是:预处理脚本、训练脚本、评估脚本、推理服务、系统前端,五个模块都能单独跑通。做毕业设计时,这些模块加在一起会比只“养了一个网络”更有整体感,也经得起答辩现场现场跑一次测试。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 21:46:53

Word文档高清图片提取3种方法及自动化技巧

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 21:46:39

Task Plan: 迁移 CI 流水线到 GitHub Actions

Task Plan: 迁移 CI 流水线到 GitHub Actions 【免费下载链接】planning-with-files Persistent file-based planning for AI coding agents and long-running tasks. Crash-proof markdown plans, session recovery after /clear and compaction, per-turn re-injection again…

作者头像 李华
网站建设 2026/9/11 21:45:44

用QEMU仿真Apple芯片调试XNU内核:darwin-vm搭建与调试全攻略

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 21:44:18

LlamaIndex 集成 Bagel 向量数据库:BagelVectorStore 实战指南

LlamaIndex 集成 Bagel 向量数据库&#xff1a;BagelVectorStore 实战指南 【免费下载链接】llama_index LlamaIndex is the leading document agent and OCR platform 项目地址: https://gitcode.com/GitHub_Trending/ll/llama_index 导读 本文围绕 LlamaIndex 官方集…

作者头像 李华