此次攻击严重影响了 Cetus Protocol 的去中心化交易所(DEX)服务,导致平台无法正常进行兑换(swap)交易。
撰文:Exvul Security
5 月 22 日 UTC 时间上午 10 时,SUI 上的流动性提供商 Cetus Protocol 遭遇黑客攻击。此次事件导致逾 2.3 亿美元资产损失,并在市场引发连锁反应,多数加密货币价格跌幅超过 70%,市场恐慌情绪蔓延。
此次攻击严重影响了 Cetus Protocol 的去中心化交易所(DEX)服务,导致平台无法正常进行兑换(swap)交易。
事件发生后不久,Cetus 团队发布声明,表示在检测到协议异常后,已立即暂停智能合约以作为预防措施。团队确认正在对此次事件进行调查,并承诺在获得更多详细信息后立即公布进展。
黑客地址为:
https://suivision.xyz/account/0xe28b50cef1d633ea43d3296a3f6b67ff0312a5f1a99f0af753c85b8b5de8ff06
其中一笔攻击交易 TX 为:
https://suivision.xyz/txblock/CZ6o96UDHUt2fvqj7ScRtSSV875HDvNV6qk6nYbHJB6m?tab=Overview
攻击得手后,大量资金通过跨链桥转移至 ETH 链,目前已有价值 6000 万美元的资金被转至 ETH 链,并被用于购买 ETH,相关钱包地址为:https://debank.com/profile/0x89012a55cd6b88e407c9d4ae9b3425f55924919b。
部分在 SUI 链上的资金正流入 Scallop、Volo 等协议进行质押
最终所幸的是 sui 官方及时升级了链代码,其中的 1.6 亿美金已经挽回。
此次 Cetus 被盗事件的核心原因在于攻击者在添加流动性时利用计算错误,仅需投入极少量的单边资产就可以鲸吞大量 LP 代币。
经深入逆向分析发现,AddLiquidityEvent 在 add_liquidity_internal 函数中被触发,其中 liquidity 的值是由参数 arg3 来决定的。在 add_liquidity_internal 函数内,get_amount_by_liquidity 函数使用 arg3 进行计算,此过程中 v8 和 v9 分别被赋值给 v3 和 v4,而最终触发事件时,返回值中的 v3 和 v4 分别为 1 和 0。要使返回值正确呈现为 1 和 0,关键计算环节就在于 get_delta_a 函数,经过定位,该函数正是错误计算的根源所在(check_shlw 实现错误)。
以下是详细分析过程:
经过逆向推导,AddLiquidityEvent 在 add_liquidity_internal 函数中 emit。其中,liquidity 受参数 arg3 支配,在该函数内,get_amount_by_liquidity 函数运用 arg3 进行运算。
在运算过程中,v8、v9 被赋予 v3、v4 的值,依据最终 emit 的 event 来看,get_amount_by_liquidity 函数返回值中的 v3、v4 分别为 1、0。
欲使返回值准确无误地为 1、0,核心计算过程聚焦于 get_delta_a 函数,经定位,该函数存在错误计算问题,即是导致整个事件的关键所在。
这里可以看到是由于 check_shlw 函数计算错误导致的原因,这也是漏洞的核心所在。
`checked_shlw` 代码片段用于判断 `u256` 数左移 64 位是否会导致溢出。
但是这个函数实现存在问题:
掩码计算:
let mask = 0xffffffffffffffff << 192;
当这个值左移 192 位(`<< 192`)时,它变成了 (2^64 - 1) * 2^192。
错误原因
假设一个会导致溢出的值 `n`,例如 `n = 1 << 192`。该数字只有第 192 位被设置。将其左移 64 位(“(1 << 192) << 64”)将导致 `1 << 256`,这对于 `u256` 来说是一个溢出(该位被移出了 256 位范围)。因此,该函数应该返回 `(0, true)`。但是这里由于 mask 的错误,导致返回 ((n << 64), false)
具体过程:
`mask = 0xffffffffffffffff << 192`(即 `(2^64 - 1) << 192`)。这是一个很大的数,其第 192 位到第 255 位全部设置为 1。
`n = 1 << 192`(仅设置了第 192 位)。
* 条件为 `if ((1 << 192) > ((2^64 - 1) << 192))`。
该条件为 false,因为 `1 << 192` 比 `(2^64 - 1) << 192` 小得多。
由于条件为 false,因此执行 `else` 分支:`((n << 64), false)`。
计算结果为 `(( (1 << 192) << 64 ), false)`,实际上变为 `(0, false)`,因为在 `u256` 运算中,`1 << 256` 会返回 0。
最后,该函数错误地报告未发生溢出(“false”),即使确实发生了溢出。
目前,Cetus 团队在最新代码中已针对此问题进行了如下修复:
git commit:
https://github.com/CetusProtocol/integer-mate/commit/ffb2292d47e99e5b53fb63a044e48f4876c0ea8d
【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。