同一个地址,落入不同的口袋—详解 Wintermute OP 代币被窃

区块链安全 2年前 (2022) admin
597 0 0

前言

2022 年 6 月 9日,Optimism (下文简称OP)官方发布了一个公告,说本来给 Wintermute 提供的 2000万 OP 代币是用来作为流动性来做市的,结果被盗了,为啥呢,因为 Wintermute 提供了一个自己在以太坊上的 Gnosis Safe Proxy(下文 Gnosis 简称 GS) 的地址给 OP 官方来接受这 2000 万的代币,但是呢,我们都知道这个代币是发行在 Optimism 链上的,并没有发行在以太坊上,所以呢,OP 官方实际上是把这 2000 万的代币打到了 Optimism 的地址上,这个地址虽然和 Wintermute 在以太坊上是一样的,但是呢,由于不是同一个区块链,导致这个地址虽然在以太坊上是属于 Wintermute 的,但实际在 Optimism 链上实际上是处于无人认领的状态。在习惯认知上,如果你在一个 evm chain 上拥有一个地址的所有权,由于私钥通用的关系,理论上在其他 evm chain 上你也是拥有这个地址的所有权的,但是,this time things 开始有点 different 了。结果大家都知道了, Wintermute 这次没有拿到他的钱,反而被黑客截糊了 :D,Optimism上的地址也被黑客控制了。 为什么呢?窃听我娓娓道来。

EVM Chain 的地址生成

在以太坊或兼容 EVM 的链中,生成一个新的合约地址一共有两种方式,分别是使用 create 操作码和使用 create2 操作码,这两种操作码生成地址的区别是生成地址时各自使用的参数不同,create 操作码用的参数是将一笔交易中的 sender  nonce 通过 rlp 算法进行编码,然后算出来的地址,这里不展开说什么是 rlp 算法,感兴趣的可以自己去了解,而 create2 则是使用指定的前缀 0xff, 调用该操作码的合约地址,和一个用户指定的 secret value(salt) 通过 sha3 算出来的。这两个操作码的目的一样,都是用于生成地址,只不过后者可以生成一个预先确定的地址,并且摒除因交易信息的改变而导致生成地址出来的地址发生变化的情况,这样即使某个合约地址没有部署,但它依然是确定的,可随时生成的。在 solidity 中,如果单纯调用 new 关键字而不指定 salt 来生成新的合约的话,则是使用 create 操作码来生成新的合约地址。

在了解了上述的基础知识后,我们就可以看下这次 Wintermute 在以太坊上的 GS proxy 合约是怎么生成的了,我们知道(不知道的话现在知道),所有的 GS Proxy 实际上是通过一个叫 Gnosis Factory 的合约来生成的,我们可以看看 Gnosis Factory 中创建新的 GS Proxy的代码是怎么写的,代码如下:

    function createProxy(address masterCopy, bytes memory data)
public
returns (Proxy proxy)
{
proxy = new Proxy(masterCopy);
if (data.length > 0)
// solium-disable-next-line security/no-inline-assembly
assembly {
if eq(call(gas, proxy, 0, add(data, 0x20), mload(data), 0, 0), 0) { revert(0, 0) }
}
emit ProxyCreation(proxy);
}

从第5行,对应上文中我们关于地址创建的知识,不难发现,Gnosis Factory 实际上用的是 create 操作码来创建合约的,那么其实就说明,这个地址是和创建这个合约的交易的参数(sender,nonce)有关。也就是说只要能在其他 EVM chain 上拥有相同地址的 Gnosis Factory合约,那么理论上只要通过合约一直创建 GS Proxy,就可以在 Optimism上拥有和 Wintermute 一样的 GS Proxy 地址。而且由于 GS 本身其实是一个多签合约,如果你能创建出来这个地址,那么就肯定可以配置多签对应的 owner 地址,然后直接控制这个多签合约。

怎么创建相同的地址?交易重放的秘密

接下来,压力来到了创建相同地址的 GS Factory 这边,再次回顾上文,合约地址的创建是 create  create2,如果是个人地址直接创建合约的话,那么用的就是 create 操作码来创建。那么影响创建出来的地址的就是 sender  nonce 了。我们来看下 GS Factory 是怎么创建的:

同一个地址,落入不同的口袋—详解 Wintermute OP 代币被窃

通过上图,不难发现,GS Factory 合约实际上是通过 sender  0x1aa7451dd11b8cb16ac089ed7fe05efa00100a6a 配合 nonce 为 2 的交易创建出来的,也就是说只要用这个地址,在这个 nonce ,就能创建出相同地址的 GS Factory,然后再创建相同地址的 GS Proxy了。然后再从 Optimism 浏览器上查询这个地址的创建记录,发现在 OP上竟然也有 Gnosis Safe Deployer 这个地址创建了地址一模一y的 GS Factory

同一个地址,落入不同的口袋—详解 Wintermute OP 代币被窃

那么问题来了,这个地址是 Gnosis Safe Deployer 啊,难道私钥泄漏了?直觉告诉我不是,如果私钥泄漏了,这么重要的地址,不可能没有声明的,而且,通过比对以太坊和 OP 上的 GS Factory创建交易,不难发现它们的 transaction hash 是一样的,而 transaction hash 要求交易信息是一致的,也就是说,OP 上的这个合约创建交易和以太坊上的合约创建交易其实是同一个交易,用专业的术语来说,就是这笔交易被 重放 了。

这里引入了一个 交易重放的概念,由于交易本质上是私钥对交易信息的签名,然后通过签名恢复出签名者来判断签名者是否授权了某个操作,而签名是附带在交易信息里的,所以,理论上只要拿到了你对某一个交易的签名,就算你在没有私钥的情况下,你也可以到处说拥有这个私钥的人同意了某一笔交易,进而使用他的身份来进行某些操作。这就有点像支票,你从某个有钱人手上拿到了价值不菲的支票,那么就算你没这个有钱人的银行账户,你也可以通过这个支票到某个银行取钱,通过支票来代表他的操作。

那回到交易重放,延续上面支票的例子,假设这个有钱人在 A 银行和 B 银行都拥有账户,这个时候你可以拿着有钱人给你的支票先从 A 银行取钱,再拿着同样的支票到 B 银行取钱。而由于这张支票是有钱人给你的,银行只会检查支票是不是这个有钱人授权,而不会检查这个支票是否在其他银行已经用了,所以,我们就可以说,这张支票被 重放 了。一张支票了2份钱,显然是不允许的。为了解决这个问题,银行之前开发了一个方案,就是下次再开支票的情况下,支票上必须带上对应银行的信息,来启用重放保护。这种情况下,在 A 银行的支票就不会在 B 银行被使用了。而这个方案,在以太坊上就叫 EIP 155,而银行信息,就是 chainId, 也就是链编号。通过往交易中带上 chainId,那么理论上在以太坊的交易是不能重放在 OP 上的,因为 chainId 不一致,但是为什么这次的交易又可以呢?我们来看看 EIP 155 提案的说明:

同一个地址,落入不同的口袋—详解 Wintermute OP 代币被窃

注意看标红的这段话,意思是如果你打算启用重放保护,你是 应该 (SHOULD) 怎么做,而不是必须(MUST)怎么做,这就表明重放保护其实是以太坊上的一个可选项,当你发交易的时候,如果你交易中的签名中的 v 值是 27-28的话,则表明你是没有启用保护,如果你的 v 值是 {0,1} + CHAIN_ID * 2 + 35 的话,那么就说明你启用了保护。还是支票的例子,也就说这个有钱人在给你支票的时候,可以选择为支票制定银行,或者不指定,如果没制定,你还是可以到不同的银行重放这个支票。真是裂开。。。

为了验证这个想法,我们找到了 ethereum 源码中关于交易重放的实现,如下:

同一个地址,落入不同的口袋—详解 Wintermute OP 代币被窃

可以看到,在恢复 sender 时候, Protected  tx的一个属性,处于 if 逻辑里,是一个可选的配置,也就是说,就算 EIP155 硬分叉了,你依然可以发一个没有 protected(重放保护)的交易,同时也证实了我们的猜想。

那么回到创建 GS Factory 的这个交易商,这个交易可以被重放吗?为了探究这个事实,我们需要查看该交易的 v 值,如下:

同一个地址,落入不同的口袋—详解 Wintermute OP 代币被窃

再次回顾 EIP 155 提案的内容,如果一个交易的 v值是 27-28 的话,这个交易是没有开始重放保护的,所以也就是说,在以太坊上创建 GS Factory 的这个交易,实际上是可以被重放到 OP 上的,由于交易信息完全相同,所以我认为大概率就是重放,而不是所谓的私钥泄漏导致同样的 GS Factory地址出现在 OP 上。

那么接下来的事情就很简了,由于可以通过重放把原本在以太坊上的交易挪到 OP 上,自然而然可以通过这个 GS Factory 创建出相同的 GS Proxy, 进而控制2000万无人认领的 OP 代币,整波操作可谓行云流水。

总结

本次攻击实际上结合了2个以太坊上不常见的知识点,分别是地址创建和交易重放,通过结合这两个技术,攻击者成功的控制了本来属于 Wintermute 的代币,实现了从别人口袋拿钱的操作。



原文始发于微信公众号(蛋蛋的区块链笔记):同一个地址,落入不同的口袋—详解 Wintermute OP 代币被窃

版权声明:admin 发表于 2022年6月10日 下午5:01。
转载请注明:同一个地址,落入不同的口袋—详解 Wintermute OP 代币被窃 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...