大语言模型辅助智能合约静态审计:利用 AST 语法树解析与 LLM 提示词链漏洞扫描实战
大语言模型辅助智能合约静态审计:利用 AST 语法树解析与 LLM 提示词链漏洞扫描实战
Web3 安全是去中心化金融(DeFi)生态的生命线。传统的智能合约静态审计工具(如 Slither, Mythril)主要依赖预设的特征规则或符号执行(Symbolic Execution),虽然能够快速筛查出经典的逻辑漏洞,但对于复杂的业务逻辑漏洞与协议级复合攻击却显得力不从心。同时,随着大语言模型(LLM)的发展,直接将几千行的智能合约源码塞入大模型容易受到上下文窗口限制、关键信息稀释以及严重“幻觉(Hallucination)”的困扰。本文将提出一种创新的静态审计范式——通过AST(抽象语法树)预筛选与函数切片提取,精确锁定敏感执行路径,再结合LLM 提示词链(Chain of Thought)进行深度漏洞推理与分类审计。
一、 智能合约审计的局限与演进
传统的静态代码分析工具通过遍历代码并生成中间表示(如 Slither 的 SlithIR),基于控制流图(CFG)和数据流依赖关系查找漏洞。
然而,传统静态审计往往存在以下局限性:
- 误报率(False Positive Rate)极高:因为工具无法识别真实的业务语义,例如将正常的升级重置逻辑误判为权限丢失。
- 多合约级联调用难以追踪:无法理解跨 DeFi 协议的闪电贷(Flashloan)套利与操纵预言机(Oracle Manipulation)等金融攻击。
大模型(LLM)的引入打破了这一僵局。LLM 拥有强大的代码语义理解能力,能够识别高层次的代码设计意图。但其核心痛点在于:如何确保 LLM 只专注于真正有风险的敏感逻辑?
因此,“AST 预分析(精密制导) + LLM 语义判断(精确打击)”是当前提升代码审计精度和效率的最佳路径。
二、 审计流水线架构设计
本系统的基本审计逻辑如下:
- AST 解析阶段:将 Solidity 合约文件输入 AST 解析器,生成结构化的抽象语法树。
- 敏感操作码检测与提取:遍历 AST 树,搜索潜在的高风险操作码(例如:外部转账
call、自毁指令selfdestruct、修改所有权的transferOwnership等)。 - 漏洞相关上下文切片(Slicing):追踪包含上述高风险节点的完整函数定义,提取该函数的参数定义、状态修改语句以及调用逻辑。
- LLM 提示词链审计:利用提示词链分步推理,评估是否存在重入漏洞(Reentrancy)、重入锁失效或状态更改后置等逻辑缺陷。
flowchart TD Source[Solidity 源代码] --> Parser[AST 语法树解析器] Parser --> AST[AST 结构化 JSON] AST --> Detector{高风险操作检测} Detector -->|发现 call.value / selfdestruct| Slicer[提取漏洞函数切片] Detector -->|无风险节点| Pass[忽略/通过] Slicer --> SystemPrompt[构造 Step-by-Step 推理提示词] SystemPrompt --> LLM[LLM 推理引擎] LLM --> Step1[步骤 1: 追踪外部调用前后的状态变量修改] Step1 --> Step2[步骤 2: 分析重入攻击的可能性与利用链] Step2 --> Report[生成结构化漏洞诊断报告]三、 AST 节点结构与重入风险特征
在 Solidity 的 AST 中,一个重入漏洞通常具有以下拓扑特征:
- 存在一个
FunctionDefinition节点。 - 该函数内部包含一个
ExpressionStatement,其底层是一个MemberAccess为call且附带value参数的成员函数调用(即外部转账操作)。 - 在上述
call操作之后,存在对StateVariable的写入或修改操作(违反了“检查-效果-交互”规范)。
四、 工业级 AST 预处理与 LLM 审计管道 Python 实现
下面提供一个完全闭环、手写的 Python 审计脚本。该脚本包含一个轻量级 Solidity 语法扫描器(模拟 AST 节点遍历),提取可能存在重入攻击的函数切片,并构造高控制度的 Chain-of-Thought(思维链)提示词,结合大模型客户端完成漏洞分析。代码中不包含任何占位符。
import json import re # ========================================================================= # 模拟 Solidity AST 树结构数据 (用于演示无需安装 solc 的完整闭环运行) # ========================================================================= MOCK_SOL_CODE = """ contract VulnerableBank { mapping(address => uint256) public balances; function withdraw(uint256 _amount) public { uint256 bal = balances[msg.sender]; require(bal >= _amount); // 外部转账操作,容易遭到重入攻击 (bool success, ) = msg.sender.call{value: _amount}(""); require(success); // 状态更新后置,违反了 Checks-Effects-Interactions 规范 balances[msg.sender] -= _amount; } } """ MOCK_AST_DATA = { "nodeType": "SourceUnit", "children": [ { "nodeType": "ContractDefinition", "name": "VulnerableBank", "children": [ { "nodeType": "FunctionDefinition", "name": "withdraw", "parameters": ["_amount"], "body": [ "uint256 bal = balances[msg.sender];", "require(bal >= _amount);", "(bool success, ) = msg.sender.call{value: _amount}(\"\");", "require(success);", "balances[msg.sender] -= _amount;" ] } ] } ] } class SoliditySmartAuditor: def __init__(self, raw_code: str, ast_json: dict): self.raw_code = raw_code self.ast_json = ast_json self.findings = [] def scan_sensitive_nodes(self) -> list: """ 遍历 AST,查找包含敏感操作(例如外部 call 调用)的函数切片 """ vulnerable_slices = [] contracts = [node for node in self.ast_json.get("children", []) if node.get("nodeType") == "ContractDefinition"] for contract in contracts: functions = [node for node in contract.get("children", []) if node.get("nodeType") == "FunctionDefinition"] for func in functions: func_body = func.get("body", []) has_external_call = False has_state_modification_after_call = False call_index = -1 # 遍历函数体语句 for idx, stmt in enumerate(func_body): # 匹配外部 call 发送以太币的操作 if ".call{" in stmt or "transfer(" in stmt or "send(" in stmt: has_external_call = True call_index = idx break # 如果存在外部调用,检查之后是否进行了状态变更 if has_external_call and call_index != -1: for idx in range(call_index + 1, len(func_body)): stmt_after = func_body[idx] # 简单匹配状态变量修改操作(如 -=, +=, = 赋值) if re.search(r"balances\[.*?\]\s*[\-+=\*]=", stmt_after) or "=" in stmt_after: has_state_modification_after_call = True break if has_external_call: # 提取函数上下文切片 func_slice = { "contract": contract.get("name"), "function": func.get("name"), "code": "\n".join(func_body), "has_risk_state_order": has_state_modification_after_call } vulnerable_slices.append(func_slice) return vulnerable_slices def build_llm_prompt(self, func_slice: dict) -> str: """ 构造高控制度的 Chain-of-Thought 推理提示词 """ prompt = f""" 你是一名资深的智能合约安全审计专家。请针对以下提取的 Solidity 函数代码切片进行静态审计。 [合约名称] {func_slice['contract']} [函数定义] {func_slice['function']} [提取的代码切片] ```solidity {func_slice['code']}请遵循以下步骤逐步进行分析:
步骤 1: 确定函数中所有执行的外部调用(External Call)操作,并评估它们是否会将控制权转移给不受信任的外部合约。
步骤 2: 追踪所有在外部调用之后执行的状态变量修改操作。
步骤 3: 分析此函数是否容易遭到“重入漏洞(Reentrancy Attack)”的攻击,如果是,请指出攻击者可能实施的具体利用链。
步骤 4: 评估上述状态顺序不当的潜在危害(是否会导致资金被盗、重置错误等)。
最终,请以 JSON 格式输出审计结论,要求包含以下字段:
{{
"is_vulnerable": true/false,
"vulnerability_type": "漏洞名称",
"reasoning_steps": ["步骤1结论", "步骤2结论", ...],
"remediation": "修复方案建议"
}}
"""
return prompt.strip()
def run_audit(self): """ 执行审计流程 """ print("[静态扫描] 开始解析 AST 语法结构树...") slices = self.scan_sensitive_nodes() if not slices: print("[静态扫描] 未发现任何包含敏感外部调用的函数。") return print(f"[静态扫描] 提取出 {len(slices)} 个敏感函数切片进行大模型定向投递。") for s in slices: print(f"\n--- 针对函数 {s['contract']}.{s['function']} 进行审计 ---") if s['has_risk_state_order']: print("[警告] 静态检查发现该函数疑似违反了 'Checks-Effects-Interactions' 规范(状态修改滞后)。") prompt = self.build_llm_prompt(s) print("[大模型投递准备] 提示词链构建完毕。提示词预览:") print("-" * 60) print("\n".join(prompt.split("\n")[:12]) + "\n... (省略后续详细引导步骤) ...") print("-" * 60) # 模拟 LLM 响应 (为了保证没有外部 API Key 依赖依然可以闭环运行) mock_llm_response = { "is_vulnerable": True, "vulnerability_type": "Reentrancy (重入漏洞)", "reasoning_steps": [ "步骤 1: `msg.sender.call{value: _amount}` 确实会将执行控制权转交给调用者地址。", "步骤 2: `balances[msg.sender] -= _amount` 减法更新余额的操作,放在了外部 call 调用之后执行。", "步骤 3: 攻击者可以在 `fallback` 或 `receive` 函数中反复调用 `withdraw`,由于余额未被及时扣除,会不断提取合约中的余额直至耗尽。", "步骤 4: 该漏洞会直接导致项目合约的全部资金被非法洗劫。" ], "remediation": "将 `balances[msg.sender] -= _amount;` 语句移动到 `msg.sender.call` 调用之前执行(即先修改状态,后发起外部交互)。" } print("[大模型审计结果]") print(json.dumps(mock_llm_response, indent=2, ensure_ascii=False))=========================================================================
执行主程序
=========================================================================
ifname== "main":
auditor = SoliditySmartAuditor(MOCK_SOL_CODE, MOCK_AST_DATA)
auditor.run_audit()
