零时科技 || Cetus 攻击事件分析

我们监控到 SUI 上针对 Cetus 的攻击事件,攻击共造成 223M USD 的损失

登链封面(事件).jpg

<!--StartFragment-->

背景介绍

2025年5⽉22⽇,我们监控到 SUI 上针对 Cetus 的攻击事件:

https\://suiscan.xyz/mainnet/tx/DVMG3B2kocLEnVMDuQzTYRgjwuuFSfciawPvXXheB3x

攻击共造成 223M USD 的损失。

<!--EndFragment-->

<!--StartFragment-->

攻击及事件分析

攻击者⾸先通过 flash_swap 来将 haSUI 兑换为 SUI , flash_swap 是 swap 和 flashloan 的变体,在 token0 对 token1 的 flash_swap 中,可以先获得 token1 ,再通过 repay_flash_swap 来⽀付 token0 。

<!--EndFragment-->

0.5.png

<!--StartFragment-->

通过上述操作,攻击者获得了 5,765,124.79 SUI ,需要在同⼀个 transaction 中⽀付 10,024,321.29 haSUI 。且该池⼦的 haSUI 的 sqrtPriceX64 由 18,956,530,795,606,879,104 , tick = 545 ,变为了 18,425,720,184,762,886 , tick =-138185 ,相当于价格由原先的 1.056 降低到了 0.0000009977 ,价格⼤幅降低⾄原来的 0.00009% 。

随后,攻击者通过 open_position 创建了⼀个 Liquidity Position ,其中该 Position 的 tickLower 为 300000 , tickUpper 为 300200 。

<!--EndFragment-->

1.png

2.png

<!--StartFragment-->

tick 区间对应的 price 的区间为:

<!--EndFragment-->

3.png

<!--StartFragment-->

随后,攻击者在该价格区间内添加流动性 10,365,647,984,364,446,732,462,244,378,333,008 。

<!--EndFragment-->

4.png

<!--StartFragment-->

我们看⼀下添加流动性的代码:

<!--EndFragment-->

5.png

<!--StartFragment-->

可以看出,添加流动性需要的 amount_a 和 amount_b 由 get_amount_by_liquidity 得出,接下来,我们看⼀下get_amount_by_liquidity 的具体实现和对应的参数。

<!--EndFragment-->

6.png

<!--StartFragment-->

由于 arg2 为 currentTick ,且 currentTick = -138185 ⼩于 arg0 lowerTick 。所以,接下来代码会走到

<!--EndFragment-->

7.png

<!--StartFragment-->

由于 arg0 为 lowerTick ,所以 cetus::tick_math::get_sqrt_price_at_tick(arg0) = 60257519765924248467716150 ,且 arg1 为 upperTick , cetus::tick_math::get_sqrt_price_at_tick(arg1) = 60,863,087,478,126,617,965,993,239 。

函数 get_delta_a 的具体实现如下:

<!--EndFragment-->

8.png

<!--StartFragment-->

Cetus 的核⼼问题出在 checked_shlw 函数上,我们看该函数的具体代码:

<!--EndFragment-->

9.png

<!--StartFragment-->

该函数的逻辑很简单,将⼀个数左移动⼀个 word ,就是64位。如果发⽣溢出,就返回0,如果没发⽣溢出就返回左移后的数。所以,检测左移后是否溢出需要判断 input > 2 ^ (256 - 64) - 1 ,然⽽,代码中却判断 input > 0xffffffffffffffff <<192 ,由于 (0xffffffffffffffff << 192) > 2 ^ (256 - 64) ,所以该代码会导致 input 在 2 ^ (256 - 64) < input <(0xffffffffffffffff << 192) 的数字被截断返回,且溢出检测失效。此时,攻击者构造的数据为:

<!--EndFragment-->

10.png

<!--StartFragment-->

所以, input = 10365647984364446732462244378333008 * 605567712202369498277089 =6277101735386680763835789423207666416102355444464034512896 , input ⼤于 2^(256-64) ,但是 input ⼩于 0xffffffffffffffff << 192 。所以,左移时必然发⽣溢出,但是程序的溢出检测失败。发⽣溢出后, v1 = liquidity * (upperSqrtPriceX64 - lowerSqrtPriceX64) - 2 ^ (256 - 64) =491983144293873864340816 。

由于,该值远⼩于 lowerSqrtPriceX64 * upperSqrtPriceX64 ,最终 get_delta_a 的返回值为0。因此,攻击者需要⽀付的token_a 为1(p + 1),即可添加 10365647984364446732462244378333008 的 liquidity 。

<!--EndFragment-->

11.png

<!--StartFragment-->

随后,攻击者通过 remove_liquidity 将添加的流动性移除,并通过 repay_flash_swap ⽀付 flash_swap 未⽀付的 token 后完成攻击,攻击者获利 5,765,124 SUI 和 10,024,321 haSUI 。最后,Cetus团队通过两个PR完成了对该漏洞的修复:

https\://github.com/CetusProtocol/integer-mate/pull/6/files

<!--EndFragment-->

12.png

<!--StartFragment-->

第⼀次修复没有完全修复该漏洞, mask = 1 << 192 = 2 ^ 256 ,所以只有 n >= mask 或 n > mask - 1 ,才能完全修复,避免左移64位后发⽣溢出截断:

https\://github.com/CetusProtocol/integer-mate/pull/7/files

<!--EndFragment-->

13.png

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
零时科技
零时科技
0xbD0b...A354
专注区块链生态安全