Bybit 近日遭受一起涉及 15 亿美元的骇客攻击,此事件引发了区块链安全社群的广泛关注。骇客成功渗透 Bybit 的多签冷钱包,并透过某种方式窜改或利用既有的安全机制,将大额资产转移至不明地址。
这次攻击不仅对 Bybit 造成了巨大的财务损失,也对多签钱包的安全性提出了新的挑战。本文由 BSOS Labs 区块链资安研究员撰写,将深入剖析该攻击事件的技术细节,包括 Bybit 所使用的多签架构、攻击者的手法,以及此次事件带来的安全启示。
事件背景
Multisignature Wallet
在以太坊的区块链设计中,有两种类型的帐号,一个是 EOA 帐户 (Externally Owned Account),另一种是合约帐户 (Contract Account),Bybit 在这次事件中用来进行资产管理的多签冷钱包,就属于后者。这个钱包其实是一份智能合约,透过程式来自定义一些商业逻辑。在多签帐户的设计中,当这个钱包收到外部传进来的资产后,如果想要进行提取或是任何使用上的操作,则必须要通过这个多签钱包所设定的一些门槛,才能够实际执行。
一般用户可以把 Safe Wallet 想像成是一个公司的金库,而这座金库内由公司的董事们共同进行管理,当公司董事想要提取金库资产时,或是利用这些资产去进行外部投资时,他们必须要先通过以下的流程:
- 董事会内部先提案,决定未来要使用的资金金额,目标以及要执行的操作
- 董事会的成员针对这个提案进行签署,当签署人数超过门槛数值,才能实际执行
举例来说,一个 5 取 3 的多签钱包,总共会有 5 个董事,我们称之为 Signer,这些 Signer 可以首先发起提案,提案对应多签钱包的名词是 Transaction,也有一派说法是 Proposal,避免与区块链的 transaction 混淆。
而这个提案可以是简单的转送 ETH / ERC-20 到其他帐户,也可以是进行一些较为复杂的 DeFi 操作。接下来这些 Signer 针对这些 Proposal 进行签署,表示同意这份提案,过程中会产生一个 Signature 做为凭证。当有效的 Signature 数量大于 3 后,用户可以呼叫 Safe Wallet 的 executeTransaction 操作并且提交这些 Signature,当验证完这些 Signature 确实是由认证的 Signer,也就是该金库的董事们签署之后,就会实际的去执行当初 Proposal 的内容。
简单的流程如上图所示,首先有 Signer 0 去提交一份 Proposal,然后由 Signer 2, 3 确认过 Proposal 没有问题之后进行签署,通常提交 Proposal 的人会同时进行签署,所以此时已经拿到 3 个 signature,符合这个多签钱包设定的门槛。这时就可以执行这份 Proposal 的内容,在 Safe 前端的设计,如果你签署的时候刚好符合门槛,系统会询问你是否要签署后一起执行,你可以选择 Yes,或是选择 No,然后让别人来帮你执行,差别则是在谁来付这个 gas 的费用。
Proxy Pattern
以上是透过 Safe Wallet 的介绍来初步了解多签钱包的运作,我们接著看 Safe 的合约设计。上述所提到的逻辑,有很大一部分是写在该智能合约中的,有兴趣的话,可以参考 Safe Wallet 的主合约实作:https://etherscan.io/address/0x34cfac646f301356faa8b21e94227e3583fe3f5f#code
这份合约并不复杂,程式码的数量也没有很大,但是如果每次要创建 Safe 钱包时,都必须要部署一份这样的合约,是非常消耗 Gas 的,所以 Safe 采用了 Proxy Pattern 来降低成本。要注意的是,这里是指 Proxy Pattern 而非 Upgradeable Proxy,跟常见的 Transparent Proxy 与 UUPS Proxy 不太一样,这里没有预设升级的行为。具体的运作原理比较像 EIP-1167 中的 Minimal Proxy。
当每次使用者创建 Safe 钱包时,一份 Proxy 就会被创建,这份 Proxy 的程式非常精简,基本上只会利用 Delegatecall 呼叫主合约,呼叫的资料则是用户和 Proxy 互动时的 data。所有的商业逻辑都在主合约 0x34Cf…3F5F 之中,但是因为 Delegatecall 的特性,每份 Proxy 的状态 (如 token balance 等) 是分离的,如上图。这样的设计,不会因为只有单一一份主合约而造成大家资产混在一起。
简单来说,这个 Proxy 透过 Delegatecall 呼叫主合约,使用主合约的商业逻辑来修改自己 Proxy 本身的状态,例如 token transfer 等操作。
在 Bybit 的事件中,一样有 Proxy 和主合约两个地址,都是在以太坊主网区块链上。
- Proxy: 0x1Db92e2EeBC8E0c075a02BeA49a2935BcD2dFCF4
- Safe Wallet: 0x34CfAC646f301356fAa8B21e94227e3583Fe3F5F
我们接著来实际看一下这份 Proxy 的程式,你会发现有一个 masterCopy
的 address 变数 (L#14),这个数值在合约创建时 (详见合约的 Constructor
, L#18) 就会设定一次 ,这个数值的初始数值此时是主合约 0x34Cf…3F5F,而 function()
(L#26) 则是会将用户传进来的 data 都透过 assembly 区块内的 delegatecall
(L#39) 都一起传送给主合约。
NOTE: 熟悉 Solidity 的读者可能会对 function()
感到比较陌生,但其实这就是 Solidity 0.5.0 版本的 fallback
。
我们接著看一下主合约 execTransaction
的实作,这个函式传入了很多参数,但比较重要的是:
to
: 多签要互动的合约地址value
: 要传送的 ETH 数量data
: 要对to
地址呼叫时所要传送的data
operation
: 不同操作的模式,在Safe
内支援call
和delegatecall
signature
: 各个Signer
签署之后的 Signature
这里再稍微说明一下运作流程:
- L#25–32 透过
encodeTransactionData
和keccak256
还原出当时 Signer 签署的 Digest - L#33 验证该 Signature 是否为该多签的 Signer 所签署,并确认是否超过 Threshold
- L#40 实际执行 Transaction 的资料,底层是使用 low-level 的 call 或是 delegatecall
如果 Operation 是 0 的话,程式逻辑大概如同以下:
(bool success, ) = to.call{gas: gas}(data);require(success, "transaction execution failed");
Attack Transaction
那这次的攻击跟上述到底有什么关联呢?根据 Bybit CEO 的说法,他们在签署多签钱包的 Proposal 时,是希望可以把多签冷钱包中存放的一些资产,转移到热钱包,这本来只是一次例行性的资产转移,他们在签署过程中,有检查过 Safe 钱包网页是正确的,且转移的地址等资讯也是正确的,也就是说他们对于这份 Proposal 是有全面性的审核过才签署并执行的。
然而实际检验执行的 Transaction,会发现一些奇怪的地方。以下是这个 Transaction 执行时的参数片段,你可以发现 value 为 0,而 data 并非 null 数值。根据我们上述的讨论,value 应该是用来定义传送 ETH 数量的一个数值,但是这里却是 0。一个常规 ETH 的转移,根本不需要 data 的资料。
那如果不是 ETH 的转移,而是像 WETH 或是 stETH 等 ERC20 的转移呢?这样 value 为 0,data 非 null 的情况就合理了。透过解析 data 的前 4 个 Byte 所代表 function selector,我们可以用这个资讯还原出 function signature,试图理解 data 想要执行的操作为何。
把 0xa9059cbb
丢到 function signature database,可以查到以下资讯,确实 ID 145 这个 function selector 是 ERC-20 的 transfer 操作。
而且 decode 了这个 data 的剩余部分,参数也如同 ERC-20 的 Transfer,有 to (address) 和 amount (uint256)。所以这里伪装的很好,没有确切检查 data 可能会被 function selector 骗了。
但是这里奇怪的是,如果只是要进行 ETH / ERC-20 的移转,我们大可以用 call (Operation = 0) 来执行就好,但是这边的 Operation 数值为 1 (见图三),代表用了 delegatecall。综合我们前面对于 Proxy Pattern 的理解,这个操作等同是 Proxy 合约使用 to 合约 (0x9622…C7242) 的逻辑,来修改 Proxy 自身的状态。
透过 Dedaub 等逆向工具观察 0x9622…C7242 这份合约,我们可以看到这份合约有一个 transfer 操作 (L#15) 但跟 ERC-20 的 Transfer 一点关系都没有,他只把传进来的 recipient 参数指定给了 _transfer 这个变数 (L#07)。
为了方便起见,我们把此时的 to 地址 0x9622…C7242 称为 Malicious Contract,那为什么这边要将 _transfer
这个数值指定成 recipient
这个参数?我们将整个运作逻辑摊开来看,流程会像以下这样:
- Signer 签署后要执行操作,此时 Safe 会向 Proxy 合约呼叫 execTransaction
- 这个操作会透过 Delegatecall 传送到 Safe 的主合约,执行 execTransaction 的逻辑
- 在 execTransaction 的逻辑中,又对 to 地址用 Delegatecall 进行呼叫,触发 transafer 操作,这个 Transfer 操作修改 _transfer 的数值
简化这一连串的 Delegatecall 后,目前的操作就是 Proxy 使用 Malicious Contract 的逻辑,来对 Proxy 本身的状态进行修改。此时在 Malicious Contract 修改的 _transfer 变数位于 Storage Layout 的 Slot 0,那相对应更改到的就是 Proxy 合约 Storage Layout 的 Slot 0。根据图三,这个数值就是 MasterCopy 的数值。
上述的流程图,其实也可以呼应在 Phalcon 呈现出来的 invocation flow
当 MasterCopy 被修改后,以后 Signer 对 Proxy 合约进行呼叫,执行 Delegatecall 的对象将不再是 Safe 的主合约,而是后来设定的 0xbDd0…9516 地址,也就是传入的 recipient 的地址。
继续逆向 0xbDd0…9516 这份合约,可以看到两个操作,一个是 sweepETH 的 function。在 L#18 执行 call 把 ETH 转送到 receiver 的地址。
以及 SweepERC20,在 L#35 可以看出,这里在做 ERC-20 的 Transfer,会把合约内所有的 Balance 都做一次性的移转。
所以 Bybit 内部不小心在 execTransaction 更新了 MasterCopy 的地址后,后面的 sweepERC20 与 sweepETH 的操作就是这么来的,这些 Transaction 才是真正把资产进行转移的操作。
Blind Signing
所以,整起事件的根本原因是 Bybit 在进行多签钱包签署时,未对 Safe 的数值进行检查吗?如果当时对参数中的 Operation 数值以及其他关键地址资讯进行验证,是否能够及早识别异常操作,进而阻止这次攻击的发生?
这起案件仍存在许多疑点。首先,该多签提案是由谁发起的?其他 Signer 是否有仔细核对提案内容,包括 Safe 前端的 URL 等重要资讯?是否可能已有 Signer 的私钥泄漏,导致整个系统面临严重的安全风险?
根据 Bybit 的说法,其联合创办人兼 CEO Ben 在签署过程中,确实检查了 Safe 的网址及实际参数资讯。然而,冷钱包签署时显示了一串乱码,这是否意味著 Safe 的前端网站已遭到骇客篡改?目前,Safe 官方已暂时关闭前端服务,以进一步厘清事件经过。
另一种可能性是 Bybit 的电脑遭到入侵,导致显示的画面并非 Safe 官方介面,而是骇客精心伪造的页面。这种情况并非不可能,早在 2024 年 10 月 16 日的 Radiant Capital 事件中,就发生过类似的攻击手法:Radiant Post-Mortem。
Radiant 也使用 Safe 的多签钱包,当时三位资深工程师透过硬体冷钱包进行签署,并在签署前使用 Tenderly 模拟 Transaction 的结果,依照内部严格的 SOP 进行检查。然而,在签署与执行的过程中,MetaMask 弹出了错误讯息,要求重新签署。
这种情况并不罕见,Transaction 执行过程中,因 Nonce 或 Gas 相关问题导致失败,往往需要重新执行。然而,正因这类操作过于常见,当钱包弹出重新签署请求时,工程师未再次验证 Transaction 的具体内容,最终落入骇客的圈套。
那我们应该如何预防?还是这类攻击防不胜防?
其实,在这几起事件中,朝鲜骇客主要利用的仍然是人性的弱点。我们可能下意识地认为自己已经对这些操作非常熟练,因此容易掉入陷阱。
为了降低风险,我们可以将多签钱包的操作环境与其他设备隔离,确保每次签署时都能够仔细验证 Payload。如果发现 Safe 与 Ledger 之间的资讯存在任何异常,应立即停止操作,并确保私钥的安全存放。
建立起一定的 SOP 流程并每次严格遵守,确保每个阶段都符合规范,还是能有效防范这些事件的发生。
Conclusion
虽然 Web3 因为 Bybit 事件蒙上了一层阴影,安全性不足与高风险的印象似乎挥之不去,但是相信在这些事件频发的背后,会让大家更加重视 Operation Security,对于私钥管理和签章的教育与认知会再更加深入。
回顾过往,2021 年至 2023 年间骇客事件频传,动辄几周就会出现百万级别损失的智能合约漏洞,但是也让行业逐渐重视安全开发与审计的重要性,Bug Bounty 的制度也更加明确。在实际部署之后监控的系统也越加完善。
因此,在文章的结尾,我们可以用相较乐观的态度看待未来产业的发展。这些看似简单却造成重大损失的事件,随著产业逐步成熟,将会逐渐减少,并且损失也将随之降低。
本文由 BSOS Labs 区块链资安研究员撰写,作者拥有国立台湾大学资工系及硕士学位,并曾参与 DeFiHackLabs 的安全研究与贡献及 Ocean Finance 智能合约开发,同时亦担任区块链安全领域的 bootcamp 讲师。