Tornado Cash governance 治理攻击事件

本文分析了 Tornado Cash Governance 被攻击的事件,攻击者利用 CREATE、CREATE2 和 selfdestruct 等 Solidity 特性,通过部署包含 selfdestruct 函数的恶意提案,在提案通过后删除原合约并在相同地址部署恶意合约,从而控制了 Governance 合约,盗取了大量资金,并详细解释了攻击的步骤和技术原理,以及如何利用 CREATE 和 CREATE2 部署相同地址的合约。

发生了什么?

5月13日,Tornado Cash 的治理系统被黑客攻击。

让我们了解一下这是如何发生的,以及漏洞是什么。这篇文章不会涉及统计数据,更多的是关于攻击的技术路线。

简而言之——攻击者主要使用 CREATE、CREATE2 和 selfdestruct 来利用治理系统。他们提出了一个与之前通过的提案相同的合约,但这个提案有一个 selfdestruct 函数,没有被注意到。在被接受后,黑客删除了提案合约,并在相同的地址部署了一个恶意合约。由于治理系统已经接受了这个地址,他们获得了对治理合约的完全控制权。

什么是治理?

首先,治理的工作方式是成员提交他们的提案,其他成员投票赞成或反对该提案。要参与治理,你需要锁定特定的代币(在这种情况下是 TORN 代币)。

在投票或创建提案后,代币将被完全锁定,直到提案执行或被拒绝。提案必须以经过验证的智能合约的形式提交,如果获得 DAO 的批准,代码将通过 delegatecall 在治理合约中执行。

来源 — https://tornado-cash.medium.com/tornado-cash-governance-proposal-a55c5c7d0703

到目前为止一切似乎都很好,除非我们了解到攻击者使用的主要漏洞是 selfdestruct() 以及 CREATE2 操作码。

逐步攻击

  1. 黑客锁定他们的 TORN 代币以参与治理。

https://etherscan.io/tx/0xf93536162943bd36df11de6ed11233589bb5f139ff4e9e425cb5256e4349a9b4

  1. 部署一个提案合约,声称与先前已被治理系统接受的提案相同。但事实并非如此,攻击者在合约中引入了一个变更,即包含了一个函数 emergencyStop,该函数应该执行 selfdestruct 函数并删除合约。我们可以用“特洛伊木马”这个词来形容它。

攻击者部署提案合约。来源 — https://youtu.be/whjRc4H-rAc

  1. 当这个提案被投票者通过后,攻击者使用 selfdestruct,然后在相同的地址部署一个新的恶意合约。

攻击者删除提案合约。来源 — https://youtu.be/whjRc4H-rAc

  1. 现在,通过的提案的逻辑已被更改为他们想要的任何内容,他们首先将大多数选票分配给自己,然后耗尽了他们可以获得的所有资金。

攻击者在相同的地址部署一个新的恶意合约并劫持合约,来源 — https://youtu.be/whjRc4H-rAc

好的,但是主要的失效点是什么?

让我们理解上面提到的步骤 2。他们如何能够在相同的地址上部署具有不同代码的新合约?

为了回答这个问题,我们应该了解的最基本的概念是在使用 CREATE 和 CREATE2 操作码时智能合约地址生成的流程。

CREATE

当我们使用 CREATE 操作码来部署合约时,为该合约生成的地址取决于创建者的地址和创建者的 nonce。

以下是使用发送者的地址和 nonce 生成地址的方式:

address of contract = last 20 bytes of sha3(rlp(sender,nonce))

你知道吗? 智能合约地址也有一个 nonce。但它与 EOA nonce 不同,因为它只在合约部署另一个合约时才会增加,而不是每次合约调用我们称之为“内部交易”的其他合约函数时都会增加。在 EIP 161 之后,nonce 从 1 开始,而不是新部署的合约中的 0。请记住这一点,当我们继续前进。

CREATE2

在 CREATE2 中,不需要 nonce。地址生成取决于 4 个参数。

  1. 0xFF — 用于防止与 CREATE 冲突的常量
  2. 发送者的地址 — 部署者合约的地址
  3. salt — 创建者发送的任意字节格式的数据
  4. 创建代码— 要部署的合约的创建代码

我们甚至可以在部署之前计算地址。Here 是一个你可以查看的小例子。

用于生成地址的 solidity 代码如下所示。

address of contract =
          address(
                 uint160(
                    uint256(
                        keccak256(
                            abi.encodePacked(
                                bytes1(0xFF),
                                address(this),
                                salt,
                                keccak256(creation code)
                            )
                        )
                    )
                )
            );

我们现在可以说,为了在与之前相同的地址上部署合约,我们需要满足以下 2 个条件。

  1. 在 CREATE 和 CREATE2 中,部署者地址应该相同。
  2. 在 CREATE 中,nonce 应该相同,而在 CREATE2 中,合约创建代码应该相同。

现在让我们看看黑客如何在这种情况下结合 selfdestructCREATECREATE2 的潜力。我将再次写下这些步骤,但这次是以更容易理解的方式。

  1. 攻击者部署一个合约。假设是“ 部署者”合约。
  2. 从这个部署者合约中,他们使用 CREATE2 部署另一个合约,让我们将其命名为“ 发送者”合约。
  3. 从发送者合约中,他们使用 CREATE 操作码部署提案合约。

到目前为止,流程如下所示:

这些框是合约

  1. 治理系统投票并通过该提案后,攻击者在提案合约和发送者合约中都使用了 selfdestruct,并在相同的地址再次部署发送者合约。然后,与之前类似,他们使用发送者合约在之前销毁的提案合约的地址上部署恶意合约。

虚线框是销毁(删除)的合约

关键点 🥤

CREATE 取决于发送者的地址和 nonce,每次合约部署另一个合约时,nonce 都会增加 1。

CREATE2 取决于发送者的地址、salt 和创建代码。这意味着 nonce 不需要相同,但合约创建代码应该相同。

很明显,为了在与提案合约地址相同的地址上部署恶意合约,我们不能使用 CREATE2,因为代码已经更改,创建代码也是如此。

这意味着我们需要使用默认的部署方法,即 CREATE。

但这里我们又遇到了另一个问题,即在部署提案合约后,发送者合约的 nonce 将增加 1,并且在部署恶意合约时,将生成不同的合约地址。

这就是黑客也销毁发送者合约的原因,self-destruct 也会将地址 nonce 重置为 0。这样,在使用 CREATE2 部署发送者合约后,nonce 将再次为 1,并且地址已经相同。这导致在之前部署已接受的提案合约的相同地址上部署恶意合约。

就这样,攻击者获得了完全控制权。通过这次接管,他们解锁了锁定的 TORN 代币,并且每个受攻击者控制的地址都分配了 10,000 个治理代币,总计 120 万张选票。

有了这么多选票,攻击者完全控制了 Tornado Cash 治理系统,因为只有大约 70,000 张合法的选票存在。他们拿走了价值约一百万美元的资产。还使用了 Tornado Cash 本身来取出 ETH。

  • 原文链接: decipherclub.com/tornado...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
decipherclub
decipherclub
江湖只有他的大名,没有他的介绍。