Эксплойт DxSale вывел $7,3 млн в BNB через скрытый бэкдор
Бэкдор — это не баг, а функция, которую внедрили намеренно, но не для вас. Эксплойт DxSale на $7,3 млн в BNB — это не классический взрыв смарт-контракта через переполнение буфера или reentrancy. Это классический случай «привилегированного доступа», который разработчики заложили в контракт блокировки ликвидности, а злоумышленник — возможно, инсайдер или кто-то, кто получил приватный ключ — просто воспользовался этим черным ходом.
На момент инцидента индекс страха и жадности (Fear & Greed) находился на отметке 23 — «Экстремальный страх». Рынок и так был на нервах: BTC колебался около $73 345, ETH — $2 009, а BNB, родной актив сети, в которой произошел эксплойт, держался на $642. В такой атмосфере любой сбой в инфраструктуре — особенно в сервисе, управляющем ликвидностью тысяч пулов, — мгновенно конвертируется в панику. Но давайте разберем не последствия, а механику.
Как работал контракт блокировки ликвидности DxSale
DxSale — это платформа для запуска токенов на BNB Chain. Один из её ключевых модулей — Liquidity Locker. Идея простая: команды проектов блокируют токены ликвидности (LP-токены) в смарт-контракте DxSale, чтобы инвесторы были уверены, что ликвидность не выведут (rug pull). Контракт хранит LP-токены до истечения заданного срока блокировки, после чего владелец может их забрать.
Технически это выглядит так: пользователь (владелец пула) вызывает функцию lockLiquidity(), передавая адрес пула и количество LP-токенов. Контракт DxSale записывает эти данные в маппинг locks и переводит LP-токены на свой адрес. Через заданное время срабатывает unlock(), и токены возвращаются владельцу.
Проблема в том, что в этом контракте была ещё одна функция — недокументированная, не указанная в ABI публичного интерфейса, но присутствующая в байт-коде. Она позволяла вызывающему — с определённым адресом, который был «зашит» в контракте — извлекать LP-токены любого пользователя в любой момент, минуя таймлоки.
Механика бэкдора: почему это не reentrancy
В отличие от атаки на Curve в 2023 году, где использовалась ошибка в логике проверки состояний, здесь не было перегрузки стека или неожиданных внешних вызовов. Бэкдор — это скрытая функция, которая проверяла, совпадает ли msg.sender с жёстко заданным адресом. Если да — она вызывала safeTransfer() на LP-токены, не проверяя, кому они принадлежат и не истёк ли срок блокировки.
Злоумышленник, по данным Блокчейн-аналитиков, нашёл этот адрес в истории транзакций контракта — вероятно, он был указан в конструкторе контракта или передан через прокси. В современных прокси-контрактах (UUPS или Transparent) такие параметры хранятся в хранилище, а не в коде, но если разработчик не очистил этот слот после деплоя — адрес остаётся доступным для чтения через sload().
Что это значит на практике? Если вы — владелец пула на DxSale, ваши LP-токены были заблокированы не вами, а контрактом, который имел скрытый выключатель. Вы доверили ликвидность не коду, а человеку (или группе), у которого был ключ к этому выключателю. И этот человек — или тот, кто украл его приватный ключ — мог в любой момент обнулить все блокировки.
Сравнение с аналогами: Unicrypt, Team Finance, Mudra
Другие протоколы блокировки ликвидности решают эту проблему по-разному. Unicrypt, например, использует мультиподпись для администрирования контракта. Любое изменение — даже если есть функция экстренной разблокировки — требует подписей от нескольких независимых сторон. В контракте DxSale такой защиты не было: достаточно было одного приватного ключа.
Team Finance идёт дальше — они используют временные блокировки с открытым исходным кодом и публичным аудитом от нескольких фирм. Их контракты не имеют функций администрирования, которые позволяли бы изменять параметры блокировки постфактум. Если вы заблокировали LP-токены на 12 месяцев — они не выйдут раньше, даже если сам Team Finance закроется.
Mudra (ещё один популярный ланчер на BSC) в 2022 году столкнулся с похожей атакой, когда злоумышленник использовал функцию emergencyWithdraw(), которая была доступна только владельцу контракта. Разница в том, что у Mudra эта функция была явно задокументирована, и её существование знали пользователи. В DxSale же бэкдор был скрыт — не было ни события, ни документации.
Что могло пойти не так: риск централизованного управления
Главная уязвимость здесь — не в Solidity, а в модели доверия. DxSale — это не полностью децентрализованный протокол. У него есть админ-ключи, которые позволяют обновлять контракты, менять комиссии и, как выяснилось, изымать ликвидность. Даже если вы — опытный разработчик и проверили байт-код контракта, вы не могли увидеть скрытую функцию, если она была добавлена через прокси-слой.
Технически это выглядит так: контракт-логика (implementation) содержит функцию adminWithdraw(), которая вызывается только из прокси-контракта, если msg.sender == admin. Прокси-контракт, в свою очередь, может быть обновлён через upgradeTo(). Если злоумышленник получает доступ к админскому ключу прокси — он может переписать логику так, чтобы функция adminWithdraw() вызывалась при любом msg.sender.
В данном случае, судя по цепочке транзакций, злоумышленник не обновлял контракт — он просто вызвал уже существующую скрытую функцию. Это значит, что бэкдор был заложен на этапе деплоя, а не добавлен позже. Вероятно, оригинальный разработчик оставил его для себя, но приватный ключ утёк или был передан третьему лицу.
Вопрос, который остаётся без ответа
А что будет с другими подобными сервисами на BNB Chain, которые используют аналогичную архитектуру с централизованными прокси? Если в DxSale бэкдор был скрыт, сколько ещё контрактов имеют такие же «закладки»? И как пользователям — не аналитикам, а обычным владельцам пулов — проверять, не зашил ли разработчик в контракт функцию, которая позволяет забрать их ликвидность в любой момент?