Допис
Діліться своїми знаннями.
Розділення використання протоколу Cetus на Sui
22 травня 2025 року великий експлойт націлився на протокол Cetus на блокчейні Sui, що призвело до збитків, за оцінками, 223 мільйони доларів. Цей інцидент привернув негайну увагу всієї екосистеми, особливо з боку технічних спостерігачів через незвичайну механіку. Далі йде всебічний аналіз атаки від флеш-замін та маніпуляцій галочками до безшумної вразливості в логіці виявлення переповнення.
https://suiscan.xyz/mainnet/tx/DVMG3B2kocLEnVMDuQzTYRgjwuuFSfciawPvXXheB3x
Налаштування
Зловмисник ініціював експлойт, використовуючи flash_swap, щоб позичити велику кількість токенів SUI в обмін на HaSui. На відміну від традиційних flash-позик, flash_swap в Cetus дозволяє користувачеві отримувати token1 (в даному випадку SUI) заздалегідь, а потім повертати token0 (HaSui) в рамках тієї ж транзакції. Цей механізм є центральним у налаштуванні зловмисника.
У цьому конкретному випадку зловмисник успішно придбав 5,76 мільйона SUI і був зобов'язаний повернути 10,02 мільйона HaSUI. Але під час цього свопу ціною HaSui в пулі ліквідності кардинально маніпулювали. Ціна пулу була знищена з тику, що представляє відношення 1,056 до тіка настільки низького, що ціна досягла лише 0,0000009977 - величезна девальвація до 0,0000009% від початкової ціни.
Ціновий діапазон, відповідний діапазону кліків, становить:
Стратегічне введення ліквідності
Після цього краху ціни зловмисник використав open_position для створення позиції ліквідності вузького діапазону, вибравши дуже специфічний діапазон тиків (tickLower: 300000, tickUpper: 300200). У цьому маніпульованому середовищі вони ввели астрономічну кількість ліквідності: понад 10 ^ 34 одиниць, все це в межах ультрастисненого цінового діапазону, створеного колапсом тіка.
Це стало можливим завдяки, здавалося б, нешкідливій функції: get_amount_by_. У звичайних умовах ця функція обчислює, скільки токена А та токена B потрібно для відповідності певному рівню ліквідності. Він робить це за допомогою допоміжних функцій, таких як get_delta_a, яка спирається на get_sqrt_price_at_tick.
Однак через те, що маніпульований поточний галок становить -138185, а вибраний діапазон галок знаходиться далеко за межами цього (починаючи з 300000), логічний шлях всередині get_amount_by_гарантував, що обчислення пройдуть через вразливий розділ коду, де була задіяна операція зсуву вліво (checked_shlw).
Переповнення та скорочення: основна вразливість
Ось де експлойт стає справді технічним. Функція checked_shlw повинна обробляти зсув 256-бітного числа вліво на 64 біти. У середовищах, подібних до твердості, така операція є ризикованою, оскільки вона може переповнитися. Ця конкретна реалізація намагалася виявити переповнення, перевіривши, чи вхідні дані перевищили попередньо визначений поріг: 0xffffffffffffff << 192.
Фактичний поріг виявлення безпечного зсуву на 64 біт повинен становити 2^ (256 - 64) - 1. Однак (0xffffffffffffffffff << 192) перевищує 2^192. Це означає, що ретельно підібраний вхід може обійти перевірку переповнення, будучи нижче порогу, використаного в коді, але все ще достатньо великим, щоб викликати переповнення під час виконання.
Зловмисник надав такий вхід, де множення ліквідності та різниці цін безшумно переповнювалося, повертаючи набагато меншу вартість, ніж передбачалося - насправді майже нуль.
В результаті протокол підрахував, що зловмиснику потрібно було заплатити лише тривіальну суму токена — по суті одну одиницю — щоб додати величезну ліквідність пулу. Цей прорахунок дозволив зловмиснику ввести величезну ліквідність без реальних витрат.
Згодом зловмисник видалив додану ліквідність через remove_liquid і завершив атаку, сплативши неоплачені токени flash_swap через repay_flash_swap. Зловмисник отримав прибуток у розмірі 5 765 124 SUI та 10 024 321 HaSuI. Нарешті, команда Cetus виправила вразливість за допомогою двох PR:
https://github.com/CetusProtocol/integer-mate/pull/6/files
Cetus швидко випустив патч у своєму сховищі цілих партнерів, при цьому перший запит на витяг намагався уточнити виявлення переповнення. Однак це початкове виправлення було неадекватним. Він використовував маску 1 << 192, що все ще дозволяло прослизати значення крайових регістрів.
Другий запит на витяг супроводжувався більш суворим порівнянням, перевіряючи, чи вхід більше або дорівнює 2 ^ 192, забезпечуючи належні межі для безпечного зсуву вліво. Тільки за допомогою цієї корекції вразливість була ефективно пом'якшена.
- Sui
Першопричиною була не логічна помилка в механіці DeFi, а нездатність правильно виявити поведінку переповнення при низькорівневій арифметичній операції