使用Diffusc对可升级智能合约进行差分模糊测试

Diffusc是一款结合静态分析与差分模糊测试的工具,用于检测可升级智能合约版本间的行为差异。文章详细介绍了其技术实现原理,包括基于Slither的升级对比分析、污点传播算法以及自动生成Echidna测试合约的过程,并以Compound协议漏洞为例展示实际应用效果。

可升级智能合约

虽然存在其他可升级设计方案,但目前最常见的USC模式是基于delegatecall的代理模式。在该模式中,代理合约存储实现合约地址,合约所有者可更改该地址。关键特性是在代理的fallback函数中使用delegatecall操作码,它会从目标合约获取函数代码但在代理上下文中执行,因此所有业务逻辑都使用代理的存储空间。

差分模糊测试

模糊测试是通过随机生成输入来检测软件异常的安全分析技术。差分模糊测试是其中一种变体,它向两个相似实现提供相同输入,寻找执行差异。针对智能合约的专用模糊测试工具Echidna会监测不变量违反情况,而差分模糊测试通过外部测试函数比较两个实现的调用结果。

Diffusc实现架构

Diffusc是结合Slither静态分析与Echidna差分测试的工具链:

  1. 使用Slither扩展组件对比USC版本差异,通过污点分析识别受变更影响的代码区域
  2. 自动生成两种测试封装合约(标准模式与分叉模式)
  3. 用户需手动审查生成的封装逻辑,补充必要的前置条件
  4. 最终通过Echidna执行差分模糊测试

基于Slither的升级对比分析

核心分析流程包含:

  • 函数变更检测:通过SlithIR中间表示对比控制流图,识别语义级修改(忽略注释/格式变更)
  • 污点传播分析
    • 标记直接读写新/修改函数涉及的存储变量的所有函数
    • 对包含外部调用的函数执行跨合约污点分析
    • 在Compound案例中,分析将待测函数从69个减少到38个(减少45%)
1
2
3
4
5
6
7
8
9
// 示例:Compound升级引入的初始化函数
function _upgradeSplitCompRewards() public {
    require(msg.sender == comptrollerImplementation);
    for (uint i = 0; i < allMarkets.length; i++) {
        if (supplyState.index == 0) {
            supplyState.index = compInitialIndex; // 漏洞触发点
        }
    }
}

测试合约生成技术

工具自动生成包含以下要素的测试合约:

  1. 标准模式:本地部署所有合约,通过HEVM存储槽模拟升级
1
2
3
4
5
6
7
constructor() {
    hevm.store(
        address(proxyV1),
        bytes32(uint(0)),  // 代理实现地址存储槽
        bytes32(uint256(uint160(address(targetContractV1))))
    );
}
  1. 分叉模式:利用HEVM实验性功能创建链状态分叉
1
2
3
4
5
6
constructor() {
    fork1 = hevm.createFork();
    fork2 = hevm.createFork();
    hevm.selectFork(fork1);
    hevm.store(address(proxy), slot, addressV1);
}
  1. 差分测试封装:对每个目标函数生成包含断言逻辑的包装器
1
2
3
4
5
6
function Target_balanceOf(address a) public {
    (bool successV1, bytes memory outputV1) = proxyV1.call(...);
    (bool successV2, bytes memory outputV2) = proxyV2.call(...);
    assert(successV1 == successV2);
    assert(keccak256(outputV1) == keccak256(outputV2));
}

Compound漏洞复现案例

通过Diffusc成功复现了导致4000万美元损失的COMP代币分配漏洞,关键步骤包括:

  1. 自定义升级函数触发Comptroller的_become逻辑
1
2
3
4
function upgradeV2() external override {
    unitrollerV2._setPendingImplementation(address(comptrollerV2));
    comptrollerV2._become(address(unitrollerV2));
}
  1. 构造测试序列:先调用mint()→执行升级→调用claimComp()
  2. 标准模式下1小时内即可检测到余额不变量违反

使用建议

  1. 需人工审查自动生成的测试逻辑,特别是:
    • 合约初始化流程
    • 升级函数的具体实现
    • 特殊业务不变量
  2. 标准模式适合简单合约,分叉模式适合复杂协议交互场景
  3. 建议结合其他审计工具在升级前进行全面验证
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计