教程区块链区块链技术ch1212.4 审计方法论与自动分析工具

本页目录

智能合约审计是一个"多防线"的过程:自动化工具在 5 分钟内发现 80% 的"愚蠢错误",工具辅助的审查覆盖边界条件,而深度的经济博弈分析则需要经验丰富的审计师。


12.4.1 审计的多层模型

graph TB
    subgraph 自动层["自动层 0-5min"]
        S[Static Analysis<br/>Slither / SolorVite]
        F[Fuzzing<br/>Echidna / Foundry]
    end
    
    subgraph 半自动层["半自动层 1-3h"]
        M[符号执行<br/>Mythril / Halmos]
        D[Dependency 扫描<br/>Advisories]
    end
    
    subgraph 人工层["人工层 10-40h"]
        M1[架构审查<br/>业务逻辑分析]
        M2[经济攻击面分析<br/>MEV / 闪电贷场景]
        M3[链下集成风险<br/>预言机 / 跨链桥]
    end
    
    S --> M
    F --> D
    M --> M1
    D --> M2
    M1 --> M3
    
    style S fill:#c8e6c9
    style F fill:#c8e6c9
    style M1 fill:#bbdefb
    style M2 fill:#bbdefb
    style M3 fill:#bbdefb

12.4.2 Slither:静态分析的事实标准

核心能力

Slither(Trail of Bits)使用 抽象语法树(AST)+ 控制流图(CFG) 分析所有可能的执行路径,检测已知 bug 模式:

检测类型示例误报率
重入检测外部调用后未更新状态
未检查返回call() 返回值未检查(当用低层 call)
重命名继承变量名隐藏父类变量
中心化关键函数 onlyOwner 但缺乏 2-步操作
自委托智能合约可以自调用外部函数极低
可重入性循环中调用外部合约
typescript
/**
 * 简化静态分析逻辑:模拟重入检测的核心模式
 */
interface ASTNode {
  type: string;
  children: ASTNode[];
  externalCall?: boolean;  // msg.sender.call() 等外部调用
  stateWrite?: boolean;    // balances[x] = ... 等状态写
  functionName: string;
}

function detectReentrancy(functions: ASTNode[]): {
  vulnerabilities: { functionName: string; confidence: "high" | "low" }[];
  totalFunctions: number;
} {
  const vulns: { functionName: string; confidence: "high" | "low" }[] = [];
  for (const fn of functions) {
    // 模式:存在 externalCall 且在其之后有 stateWrite
    // 实际 Slither 使用数据依赖分析,这里是简化启发式
    const externalCalls = collectNodes(fn, n => n.externalCall);
    const stateWrites = collectNodes(fn, n => n.stateWrite);
    
    if (externalCalls.length > 0) {
      // 检查是否有 stateWrite 发生在 AFTER 外部调用之后
      const callIndex = fn.children.findIndex(findMaxDepth(externalCalls));
      const writeIndex = fn.children.findIndex(findMaxDepth(stateWrites));
      if (callIndex >= 0 && writeIndex > callIndex) {
        vulns.push({ functionName: fn.functionName, confidence: "high" });
      } else if (callIndex >= 0 && writeIndex === -1) {
        vulns.push({ functionName: fn.functionName, confidence: "low" });
      }
    }
  }
  return { vulnerabilities: vulns, totalFunctions: functions.length };
}

function collectNodes(root: ASTNode, predicate: (n: ASTNode) => boolean): ASTNode[] {
  const result: ASTNode[] = [];
  function walk(n: ASTNode) {
    if (predicate(n)) result.push(n);
    for (const c of n.children) walk(c);
  }
  walk(root);
  return result;
}

function findMaxDepth(nodes: ASTNode[]) {
  return (n: ASTNode) => nodes.some(target => target === n);
}

// 示例
const badWithdraw: ASTNode = {
  type: "function",
  functionName: "withdraw",
  children: [
    { type: "call", externalCall: true, functionName: "msg.sender.call", children: [], stateWrite: false },
    { type: "write", externalCall: false, functionName: "balances[msg.sender]=0", stateWrite: true, children: [] },
  ],
  externalCall: false,
  stateWrite: false,
};
console.log("Bad pattern:", detectReentrancy([badWithdraw]).vulnerabilities);
// 输出: high confidence: 外部调用在状态写之前

12.4.3 符号执行:Mythril / Halmos

符号执行不是运行程序时以具体数值,而是以符号变量(如 α,β\alpha, \beta)作为输入,跟踪程序在所有路径上的约束。

求解约束系统:SMT 求解器(Z3-based)可以精确发现溢出路径:

text
约束1: a = α, b = β, a >= 0, b >= 0
约束2: a + b < a  (溢出条件)
    => β > MAX_UINT256 - α

SMT solver response: 可解!示例: α = 2^255, β = 2^255

这可以精确找到边界条件,但状态空间爆炸限制了分析深度。

工具架构适用类型局限
Mythril基于 Python + Z3重入、溢出、权限路径爆炸,>1K 行难以分析
HalmosHaskell + Z3函数性质验证需要规范注解
CertoraJava + SMT业务逻辑、协议不变式高级,需要学习 Certora Script
Echidna基于属性的 Fuzzing快速覆盖不保证穷尽

12.4.4 审计报告结构

如果一名审计师给你回到报告,至少应包括:

等级严重度响应时间
C-0/Critical资金直接可虑取发布前必须修复
C-1/High资金在特殊条件下可虑取24 小时评估
C-2/Medium业务逻辑缺陷30 天内修复
C-3/Low代码质量/效率有空再修
Info/Gas不影响安全的优化可选

> ← 上一节:12.3 漏洞 | 前往 → 12.5 可升级合约 |*

评论

0

评论加载中…

发表评论

0/2000