Cetus Hack
brzrbitoПредисловие
22 мая 25 года был взломан ака эксплуатирован DEX протокол Cetus. Казалось бы, рядовая ситуация в криптомире, где каждый месяц, если не каждую неделю, что-то происходит из разряда "опять кого-то нагрели".
Всё нижесказанное является доводами автора и никаких пограничных экспертных мнений не несёт. DYOR.
- АММ на мувланге.
- Механика нагрева.
- Сколько бабок понадобилось "злоумышленнику".
- Прецеденты.
- Насколько весома вина местного фаундейшена.
АММ на мувланге
ΔA = (L * |√P₁ - √P₀|) / (√P₀ * √P₁) (изменение кол-ва токенов А при изменении цены) ΔB = L * |√P₁ - √P₀| (изменение кол-ва токенов В при изменении цены)
Где:
ΔA - количество токенов А, которое нужно изъять и внести ΔB - количество токенов В, которое нужно изъять и внести L - ликвидность пула √P₀ - квадратный корень начальной цены √P₁ - квадратный корень конечной цены |√P₁ - √P₀| - разница между ценами
Количество токенов А зависит от, произведения ликвидности пула и абсолютной разницей между ценами (начальной и конечной), деленное на произведение этих же цен.
Пример:
Ликвидность пула (L) = 1000
Начальная цена токена А = 4 (один токена А стоит 4 токена В)
Конечная цена токена В = 9 (теперь один токен А стоит 9 токенов В)
В этом примере мы изымаем А и добавляем В
Сколько нужно вытащить токенов А при изменении цены: ΔA = (L * |√P₁ - √P₀|) / (√P₀ * √P₁) (1000 * |√9 - √4|) / (√4 * √9) = (1000 * 1) / 6 = 166.666667 166.666667 токенов А нужно вытащить из пула так, как цена на него выросла А сколько нужно добавить токенов В: ΔB = L * |√P₁ - √P₀| 1000 * |√9 - √4| = 1000 * 1 = 1000 1000 токенов В нужно внести в пул, так как цена на токен А выросла
То есть, чтобы поддержать ликвидность в пуле, нужно 166.666667 токенов А вытащить, а 1000 токенов В добавить.
1 банан стоит 4 яблока, но в какой-то момент 1 банан уже стоит 9 яблок, чтобы поддержать баланс, из под прилавка достают 166.666667 бананов и добавляют 1000 яблок.
Далее, как именно эта формула реализована в move:
#наш токена А, это функция для расчета изменения его кол-ва в пуле
public fun get_delta_a(
sqrt_price_0: u128, #начальная цена
sqrt_price_1: u128, #конечная цена
liquidity: u128, #ликвидность пула
round_up: bool #округление чисел
): u64 { #наша дельта токена А в 64 битах
#считаем |√P₁ - √P₀| разницу цен
let sqrt_price_diff = if (sqrt_price_0 > sqrt_price_1) {
sqrt_price_0 - sqrt_price_1
} else {
sqrt_price_1 - sqrt_price_0
};
#считаем L * |√P₁ - √P₀|
let (numberator, overflowing) = math_u256::checked_shlw(
full_math_u128::full_mul(liquidity, sqrt_price_diff)
);
#считаем √P₀ * √P₁
let denominator = full_math_u128::full_mul(sqrt_price_0, sqrt_price_1);
#считаем (L * |√P₁ - √P₀|) / (√P₀ * √P₁)
let quotient = math_u256::div_round(numberator, denominator, round_up);
#приводим результат к 64 битному формату
(quotient as u64)
}
Механика нагрева
С формулами разобрались, далее, каким же образом типочку удалось нагреть логику смарт-контракта.
В конкретном отрывке функции, где мы ищем дельту, которую нужно изъять из пула, есть такая строчка:
(quotient as u64)
Это строчка обрезает результаты до 256 битного формата, чтобы он поместился в 64 битный формат чисел.
Цифры могут быть и вправду большие, но вероятность, что число будет превышать 256 бит очень скромны. Однако, если целенаправленно атаковать этот метод в коде, то как уже понятно, результат на лицо.
Причем это целые числа...
То есть:
Если коробки у прилавка могут содержать только конкретную величину бананов и яблок, то что будет если превзойти эту границу величин, допустим на 1 или 2. Результатом будет то, что бананов изымут гораздо больше, чем должно, если цена на бананы в короткий промежуток времени вырастет по отношению к яблокам. Просто весь прилавок заректит.
То есть:
Если вдруг начальная цена токена А = 100, а конечная цена = 10 миллиардам, то произведение ликвидности к разнице между ценами выйдет за пределы 256 битного формата, которое в свою очередь некорректно округлиться и обрежется до 64 битного формата. А значит, приведение числа в нужный формат будет не верным, допустим оно должно было быть 1234567, а стало 4567890.
Причем, это не хак, это просто эксплойт логики приведенного выше метода по вычислению дельты.
Сколько бабок понадобилось "злоумышленнику"
Ну он же не хакер и не злоумышленник, он просто типок, который взял и сделал. Как говорится "каждый мох".
Однако, каждый ли мох?
Однозначно, типок был не один, как в фильмах про хакеров.
Во-первых, надо знать сам язык программирования и его архитектуру, логику и так далее, так как язык не самый популярный и достаточно тяжел для изучения, то круг таких девов очень узок, и все друг друга хоть, как-то знают.
Во-вторых, требуются не маленькие мощности, чтобы провернуть всю эту суету, чего только стоят флеш займы, это не взял положил, при том ещё в каждом блоке, который был инициирован для эксплойта, было не меньше 20 транзакций внутри. Вспоминаем, что в мувланге есть возможность выполнить несколько транзакций в рамках одного и того же блока. То есть занял и положил обратно, это ещё мягко сказано.
В-третьих, количественный избыток средств, не 1 мульен, но гораздо больше.
Бабки, люди, инфраструктура - короче, денег на сие мероприятие потребовалось многовато, чем рект моего прилавка.
Стоит уточнить, что пул - CLMM, то есть концентрированный. Экстремально маленький диапазон, буквально пара сот тиков + много бабок + уверенные мощности = 200кк на кармане.
Если есть интерес, то вот его первая транзакция, в которой фигурирует 10кк hasui (тоже sui, но в обертке от стейкинг протокола)
https://suivision.xyz/txblock/DVMG3B2kocLEnVMDuQzTYRgjwuuFSfciawPvXXheB3x
Прецеденты
Вообще, достаточно частый случай манипулирования оракулами в дефае, НО
Но были ведь прецеденты, ведь надо было, как-то это момент утрясти, ведь не один раз аналогичным образом скамились пулы.
Если функция используется в контексте, где результат влияет на передачу токенов, атакующий может использовать флеш займы (аналогично атакам в Ethereum) описанным для манипуляции состоянием пула перед вызовом функции.
Причем, статей на эту тему очень много и все от 6-ти месячной до 2-х годовалой давности.
- https://101blockchains.com/underflow-and-overflow-vulnerabilities-in-smart-contracts/
- https://blockapex.io/top-10-smart-contract-vulnerabilities/ (тут более обобщенный вариант затрагивает спектр векторов атак)
Об этом писали, это переживали, это хоть как-то исправляли, но увы результат на лицо. Лохи.
Не будет удивительным, что это все одна и та же шайка лейка узкоглазых негров. А может просто быстрый обкеш, чтобы лишний раз не распинаться перед криптанами.
Насколько весома вина местного фаундейшена
SUI фаундейшен, конечно, канул в моих глазах. Косяк на косяк и сверху ещё косяк. Топ-10 самых популярных методов эксплойта прозевать на самом ровном месте и так бездарно.
Типок то просто использовал метод, а фаундейшен обосрался, как и другие до него. Аналогия с граблями.
Кому и что выплатит фаундейшен, как дальше будет существовать Cetus доколе неясно. Ясно только то, что ещё одному нормальному протоколу вставили палки в колеса.
Да, я въебался на Cetus (на токене), не так много, но всё же, осадочек то есть.