Десятки кошельков GnosisPay опустошены: детальный разбор дерзкой атаки


1 июня 2026 года неизвестный злоумышленник вывел средства из десятков кошельков пользователей сервиса GnosisPay в сети Gnosis Chain. Причиной инцидента стала уязвимость в модуле Delay, который отвечает за дополнительную проверку и задержку выполнения транзакций. GetBlock AML Research детально разбирает атаку и схему вывода похищенных активов.
Зачем понадобилось подделывать смарт-контракты
Ошибка находилась в механизме проверки цифровых подписей. Модуль использовал функцию moduleTxSignedBy(), которая должна была удостоверяться, что транзакция действительно была одобрена владельцем кошелька. Однако данные для проверки извлекались непосредственно из части запроса, которую злоумышленник мог самостоятельно контролировать.
Используя специально сформированную структуру данных с несколькими уровнями вложенных подписей, атакующий заставил систему сначала обратиться к одному из сторонних кошельков Biconomy Safe, а затем перенаправить проверку на собственный вредоносный контракт. Этот контракт всегда возвращал ответ, который система интерпретировала как подтверждение подлинности подписи.
В результате модуль ошибочно признал мошеннические транзакции законными и разрешил их выполнение.
После этого злоумышленник поставил в очередь 41 транзакцию, дождался окончания обязательного периода ожидания, предусмотренного модулем Delay, и затем выполнил их. Каждая из транзакций переводила цифровые активы EURe и $GNO с кошельков жертв на адреса, контролируемые атакующим.
Общий ущерб составил примерно 265 тыс.
| Адреса злоумышленника в сети Ethereum |
| 0x81BA8A2b895D30280bca199C2Ff75f3F058d4C6c |
| 0xf6a9f265012130d02fda1f39f74e9faf8388d2f6 |
Ход атаки
Хакер провернул многоуровневую схему атаки с использованием десятков поддельных смарт-контрактов.
29 мая 2026 года, 13:29:55
За несколько дней до вывода средств злоумышленник развернул 41 отдельный вредоносный контракт. Эти контракты были специально созданы таким образом, чтобы при вызове функции проверки подписи isValidSignature() всегда возвращать значение 0x1626ba7e — специальный код стандарта EIP-1271, который обычно означает, что подпись признана действительной.
1 июня 2026 года, 05:26:10
Атакующий вызвал функцию Delay.execTransactionFromModule(). Первая часть переданных данных интерпретировалась системой как стандартная транзакция, содержащая адрес получателя, сумму перевода, дополнительные данные и тип операции. Именно эта часть была помещена в очередь на выполнение.
Внутри поля данных находились команды на перевод токенов EURe и $GNO с кошельков жертв на адреса злоумышленника. Проверка проходила через цепочку функций:
Delay.execTransactionFromModule() → moduleOnly() → moduleTxSignedBy()
Особенность заключалась в том, что функция moduleTxSignedBy() анализировала не только ожидаемую часть данных, но и дополнительную область сообщения, которую система не разбирала заранее. Именно в этой скрытой части злоумышленник разместил специально подготовленные параметры подписи r, s и v.
Использовались следующие значения:
- v = 0x24400
- r = 0x7f59e536f083a63b67adfe3bc793a47744dba7d80
- s = 0xd957b7cc73ae8d40082124eb2a67284d8e287babfe4e6859f0772a1eab8c54af
Система восприняла значение r как адрес предполагаемого подписанта и направила запрос на адрес 0x7f59e536f083a63b67adfe3bc793a47744dba7d80. Этот адрес принадлежал кошельку Biconomy Safe. Далее началась еще одна процедура проверки подписи внутри самого Safe:
0x1626ba7e() → Safe.0x20c13b0b() → Safe.checkSignatures()
На этом этапе функция signatureSplit() извлекла уже другой параметр r, который указывал на заранее подготовленный вредоносный контракт по адресу: 0x5a77953caa27ed4638f4dfdc665b8064d0e97a35. Этот адрес был назначен в качестве currentOwner и использовался для очередного этапа проверки подписи. Сама подпись в данном случае фактически не имела значения.
Вредоносный контракт был запрограммирован так, чтобы всегда возвращать код EIP-1271 0x1626ba7e, независимо от входящих данных. Во время проверки вызов завершался ошибкой, однако это не приводило к отмене всей транзакции. Система получала возвращенное значение 0x1626ba7e и принимала его за подтверждение подлинной подписи.
После этого вредоносная транзакция успешно добавлялась в очередь исполнения. Всего злоумышленник поставил в очередь 41 транзакцию и затем несколько дней ожидал завершения периода задержки, предусмотренного механизмом безопасности.
1 июня 2026 года, 05:57:35
После окончания периода ожидания атакующий вызвал функцию Delay.executeNextTx(). Это привело к выполнению подготовленных ранее команд, которые перевели EURe и $GNO с кошельков жертв на адреса злоумышленника. Целевым адресом каждой операции выступал кошелек Gnosis Safe конкретной жертвы, который был указан еще на этапе подготовки атаки.
Причина уязвимости
Основная причина инцидента заключалась в ошибке логики проверки подписей в цепочке функций:
Delay.execTransactionFromModule() → moduleOnly() → moduleTxSignedBy()
Система извлекала параметры подписи r, s и v из конца сообщения, полностью контролируемого атакующим. Особенно важным оказалось значение r, которое интерпретировалось как адрес подписанта и использовалось для проверки подлинности операции. Злоумышленник сформировал сообщение таким образом, чтобы оно содержало два уровня параметров r, s и v.
Первый параметр r направлял проверку в кошелек Biconomy Safe. Второй параметр r указывал на заранее развернутые вредоносные контракты, которые всегда возвращали требуемый код подтверждения EIP-1271. Благодаря этой многоуровневой схеме система ошибочно считала мошеннические транзакции легитимными.
Перемещение похищенных средств
После успешной атаки средства начали перемещаться между различными блокчейн-сетями. Кошелек злоумышленника 0x81BA8A2b895D30280bca199C2Ff75f3F058d4C6c перевел примерно $246 тыс. в USDT из сети Ethereum в сеть Hyperliquid.
После этого средства были отправлены на кошелек: 0xb1834575349c6eb56675c35b4109c3d3a77dd2fc
На этом адресе часть активов была обменена на криптовалюту Monero ($XMR), которая известна повышенным уровнем анонимности транзакций. На момент публикации расследования на кошельке злоумышленника оставались:
- 147 494,91 USDC
- 1,5174 ETH стоимостью около $2 700
- 28,32 $XMR стоимостью примерно $9 903
Дополнительные запасы Monero были распределены между четырьмя отдельными кошельками:
| Кошельки злоумышленника в $XMR | |
| 0xcce200e0df2f6d47ccffc0e64e6fddc145b13f67 | 27,33 $XMR |
| <0x3eb18b54a2f7500c3a581197cf7d9fbd62516160 | 55,06 $XMR |
| 0x0dda0f6aa7b3e0ec1273c4e47c56e7bed57a308c | 69,30 $XMR |
| 0x31c2c0c4ab37a89d38968735f8ad9f04e332576a | 83,31 $XMR |
