第三章 数据集构建:开源漏洞库、真实审计报告与人工标注规范
好,咱们进入第三章。这一章我打算聊聊数据集构建。说白了,就是喂给模型吃的“教材”从哪来、怎么洗、怎么标。
你想想看,一个智能合约审查模型,如果没见过真正的漏洞长什么样,它怎么学得会?我刚开始做这个方向时,踩过不少坑。最惨的一次,模型在测试集上准确率95%,一上真实审计项目,直接漏掉一个重入漏洞。后来复盘发现,训练数据里全是教科书级别的例子,太“干净”了。
所以,数据集构建不是简单的“找一堆代码扔进去”。它是个手艺活。今天我就把这三条路——开源漏洞库、真实审计报告、人工标注规范——掰开揉碎了讲。
3.1 开源漏洞库:SWC Registry 与 SmartContractVulnerabilityDB
先说说开源漏洞库。这是最省力的起点。我个人习惯把这两个库当作“基础教材”。
3.1.1 SWC Registry
SWC Registry 是 ConsenSys Diligence 维护的智能合约漏洞分类标准。它有点像 OWASP Top 10,但专攻以太坊。
它把漏洞分成了 100 多个类别,每个都有编号、描述、示例代码和修复建议。比如 SWC-101 是重入攻击,SWC-102 是整数溢出。
我在项目中遇到过,有些团队拿 SWC 当“检查清单”用。但说实话,它更适合做“分类标签”。你训练模型时,可以用 SWC ID 作为输出类别。
3.1.2 SmartContractVulnerabilityDB
这个库是另一个宝藏。它收集了真实项目中被利用过的漏洞合约。每个条目都包含:
- 漏洞合约源码
- 攻击交易哈希
- 漏洞类型描述
- 修复后的合约版本
嗯,这里要注意。这个库的数据量不大,大概几百条。但每条都是“真金白银”的实战案例。我建议把它当作验证集,而不是训练集。为什么?因为数据太少,容易过拟合。
| 对比维度 | SWC Registry | SmartContractVulnerabilityDB |
|---|---|---|
| 数据量 | 100+ 类别,每个类别 1-3 个示例 | 约 500 个真实案例 |
| 代码长度 | 短(10-50 行) | 中长(100-500 行) |
| 适用场景 | 分类标签定义、概念学习 | 验证集、真实场景测试 |
| 局限性 | 示例过于理想化 | 覆盖的漏洞类型不全 |
3.2 真实审计报告爬取:从 PDF 到结构化数据
开源库只是开胃菜。真正的大头是真实审计报告。我做过一个统计,市面上 80% 的公开审计报告都藏在 PDF 里。怎么把它们变成模型能吃的结构化数据?
我曾经花了两周时间,写了一套爬虫+解析管线。核心步骤就三步:
- 爬取源:主要抓 Trail of Bits、ConsenSys、OpenZeppelin 等知名审计公司的公开报告。
- PDF 解析:用 PyMuPDF 或 pdfplumber 提取文本。但这里有个坑——很多报告是扫描件,OCR 跑出来的文本错位严重。
- 结构化提取:用正则或简单 NLP 把“漏洞描述”、“风险等级”、“代码片段”、“修复建议”拆出来。
爬下来的数据长什么样?我给你看个例子:
{
"source": "Trail of Bits - Uniswap V3 Audit",
"vulnerability": {
"type": "Reentrancy",
"severity": "High",
"description": "The `flash` function does not follow the checks-effects-interactions pattern...",
"code_snippet": "function flash(uint256 amount) external {\n uint256 balanceBefore = address(this).balance;\n ...\n (bool success, ) = msg.sender.call{value: amount}(\"\");\n if (address(this).balance < balanceBefore) revert();\n}",
"fix_suggestion": "Move the balance check before the external call."
}
}
这个结构,直接喂给模型做微调,效果比纯文本好得多。
3.3 人工标注规范:让标注员不吵架
数据有了,但没标注等于白搭。人工标注是数据集构建里最耗时、最容易被低估的环节。
我见过最离谱的标注项目:两个标注员对同一个漏洞,一个标“重入”,一个标“逻辑错误”。为什么?因为没有统一的标注规范。
所以,我总结了一套标注规范,核心就三条:
3.3.1 漏洞类型定义要明确
别用模糊词。比如“重入攻击”要定义为:
- 外部调用(call/delegatecall)发生在状态更新之前
- 攻击者可以通过回调函数重复执行关键逻辑
每个类型都要配 2-3 个正例和反例。标注员先看例子,再动手。
3.3.2 标注粒度要统一
是按函数标注?还是按代码行标注?我建议按“漏洞点”标注。一个函数里可能有多个漏洞点,每个点单独标。
举个例子:
// 漏洞点1:重入(第15行外部调用)
// 漏洞点2:整数溢出(第20行算术运算)
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
(bool success, ) = msg.sender.call{value: amount}(""); // 漏洞点1
balances[msg.sender] -= amount; // 漏洞点2
}
3.3.3 争议处理机制
标注员之间意见不一致怎么办?我定了个“三级仲裁”流程:
- 标注员自行讨论,达成一致
- 讨论无果,提交给资深工程师(也就是我)仲裁
- 如果还是争议,标记为“不确定”,不进入训练集
3.4 知识体系总览
说了这么多,我画了张图帮你理清思路。这张图展示了数据集构建的完整流程:从开源库和审计报告获取原始数据,经过清洗和结构化,再到人工标注,最终形成训练集、验证集和测试集。
这张图从左到右展示了三条数据来源,最终汇聚到清洗、标注、输出三个环节。你训练模型时,建议按 7:2:1 的比例划分训练集、验证集和测试集。验证集最好全部来自真实审计报告,这样能更真实地评估模型在实战中的表现。
好了,数据集构建这块就聊到这儿。记住,数据质量决定模型上限。花再多时间在数据上都不为过。下一章咱们聊聊模型选型和训练策略,到时候见。