更新(10-30 02:23 UTC):币安在《Security Incident Update》中称已撤销受影响地址的授权,并把根因指向被攻陷的 DevOps 笔记本与服务容器 —— 与本文第七节根因矩阵里「签名基础设施被侵害」这一行的方向一致。但撤销交易的哈希仍未公布,链上尚无法独立复核,所以第七节「后门关了吗」的判断仍按公告前的状态保留。公告拆解见币安给出根因。
本文基于公开链上数据与第三方分析。币安尚未公布官方 post-mortem,根因未经确认。数据截至 2026-10-30 00:30 UTC。
TL;DR
签名即部署,部署即持久,执行即身份。但这次真正的技术含量不在合约,在工程封装。
- 不是临时起意。 准备期近 18 小时:02:28 部署测试合约 → 13:51 与 16:20 在三个自有钱包上彩排委托与扫荡 → 15:59 部署最终合约 → 20:20 才动手。中间留了 4.3 小时的观察窗口。
- 不是同一笔交易。 授权(Type-4,20:20:59)与清空(普通交易,20:24:23)隔了 17 个区块 / 3 分 24 秒 —— 而 Type-4 交易在 mempool 里是公开可见的。那 3 分 24 秒本来是可以拦截的窗口。
- 50 条授权是 nonce 降序,恰好只有 1 条生效。 降序是刻意的:升序会让命中后每条更高 nonce 依次生效,把账户 nonce 连推 15 次。
- 不是「63 种代币」。 calldata 解出来是 65 个数组元素:1 个
address(0)(ETH 占位符)+ 64 个 ERC-20。 - 委托持久性有直接证据:事后 operator 又发起 12 次
sweep,其中 4 次是 23,100 gas 的空轮询(0 笔转账)—— 一个机器人在反复试探有没有新入账。 - 成本荒谬:主扫荡手续费 0.000414 ETH ≈ $0.91,搬走约 25 亿美元,比例约 1 : 27 亿。
一、准备期:18 小时、三个测试钱包、一次彩排
把 operator 地址(0x7c8601d4…6b5ca0)的交易按时间排开,准备节奏非常清晰:
| UTC(10-29) | 动作 | 细节 |
|---|---|---|
| 02:28:59 | 部署测试合约 | 首笔合约创建,gas 324,710 |
| 13:51:35 / 13:51:59 | 两个测试 EOA 的 7702 委托 | 0x837810E3…、0x9C29BA2D… |
| 14:19:59 / 14:24:11 | 对上述地址执行测试扫荡 | gas 112,046 / 77,846 |
| 15:59:11 | 部署最终恶意合约 0x63e842… |
gas 415,057 |
| 16:20:47 / 16:25:59 | 第三个测试 EOA 委托到最终合约 | 两次授权,可能在测试 nonce 处理 |
| 16:30:11 / 16:47:23 | 对第三个 EOA 执行彩排扫荡 | 验证合约逻辑 |
| 20:20:59 | 对 Binance 14 发起真实授权 |
50 条授权,1 条生效 |
| 20:24:23 | 主扫荡 | 约 25 亿美元 |
这改变了对攻击者的判断。 他不是「拿到私钥后随手一转」,而是先在自己控制的账户上把完整链路跑通,再对目标执行。
恶意合约 15:59 部署,彩排 16:47 完成,真正动手在 20:20 —— 中间空了 3.5 小时。这说明攻击者在等待合适的执行时机:可能是等热钱包余额达到峰值,也可能是等某个签名窗口出现。
二、链上复核:授权与执行是分离的两笔交易
| 项目 | 授权交易 | 清空交易 |
|---|---|---|
| 时间(UTC) | 20:20:59 | 20:24:23 |
| 区块 | 26,300,753 | 26,300,770 |
| 类型 | Type 0x4(SetCode) | Type 0x2(普通 EIP-1559) |
| From | 0x7c8601d4…(operator) |
同一 operator |
| To | Binance 14(0x28c6…1d60) |
同一地址 |
| Input | 0x(空) |
0x8a179be4…(sweep) |
| Gas used | 1,258,562 | 2,455,349 |
| 手续费 | 0.006545 ETH(≈$14.5) | 0.0004138 ETH(≈$0.91) |
| 作用 | 把 EOA 代码写成 0xef0100‖0x63e842… |
以 EOA 身份执行批量转账 |
两个细节值得单独指出。
第一,授权交易的 input 是空的。 真正的内容在 Type-4 交易特有的 authorization_list 字段里 —— 常规的「看 calldata」监控完全看不到它。
第二,清空交易是一笔再普通不过的 EOA 调用。 任何地址都可以发;它能成功,只因为目标地址的代码已经被换成了攻击者的实现,而实现里硬编码了「只允许 operator 调用」。
这两笔交易之间隔了 3 分 24 秒,而 Type-4 交易在 mempool 里公开可见。 如果监控系统对「交易所地址发起 SetCode 交易」设了告警,这段时间足够暂停该钱包的签名权限。
没拦住,说明行业监控仍以「余额流出」为核心指标,而不是「代码被改写」。
三、Nonce 降序:50 条为什么恰好只有 1 条生效
这是整条攻击链里最精巧的一处,链上数据把它验证得很干净:
- 50 条授权的 nonce 从 18,914,824 递减到 18,914,775,全部以
Binance 14为 authority、指向同一合约、chain_id = 1 - 只有 18,914,809 生效,其余 49 条 均为
invalid_nonce - 生效后账户 nonce 变为 18,914,810,下方 34 条因「过低」全部失效
降序排列是关键。
规则是「元组 nonce 必须等于账户当前 nonce 才生效,否则跳过而不回滚」。所以:
- 处理 824 → 810 这 15 条时,链上 nonce 只有 809,全部「过高」被跳过
- 处理到 809 时命中,代码写入,nonce 变为 810
- 剩下的 808 → 775 共 34 条变成「过低」,全部跳过
如果按升序提交,命中 809 之后,810、811……824 会依次生效,nonce 被连续推高 15 次 —— 热钱包自己的交易队列会瞬间错位。降序保证了「最多一条生效」。
gas 消耗 1,258,562,而这个数字可以精确对上:
50 × 25,000 = 1,250,000 每条元组按 PER_EMPTY_ACCOUNT_COST 预收
− 12,500 唯一有效元组命中已存在账户,退 PER_AUTH_BASE_COST 的差额
+ 21,000 交易内在 gas
+ 62 calldata 等
= 1,258,562一分不差。 规范对授权列表按 PER_EMPTY_ACCOUNT_COST(25,000)× 条数预收,只对 authority 非空的有效元组退 12,500。所以 49 条诱饵不是免费的 —— 每条实打实烧掉 25,000 gas,发送者为真假元组一并付费。
50 条覆盖的是「签名到上链」的时间差
窗口顶部比实际 nonce 高 15 笔。对高频热钱包来说,这大约是几十秒到一分多钟的漂移量。
这提供了一个关于根因的线索:如果攻击者手里有私钥、又能直连节点读 nonce,一条就够。需要一次签 50 条,通常意味着签名侧看不到实时链上状态 —— 要么是批量预签,要么是通过某个只负责签、不负责读的接口拿到的授权。
这更接近「签名基础设施被利用」,而不是「私钥被复制后直接操作」。
签名对象不是 EIP-712 —— 所以硬件钱包渲染不出来
7702 授权的签名对象是:
keccak256(0x05 ‖ rlp([chain_id, address, nonce]))这不是 EIP-712 结构化数据。 意味着硬件钱包和多数签名界面无法把「你正在把账户代码委托给 0x63e8…」渲染成可读内容 —— 用户看到的很可能只是一串哈希。
这种「语义不可见」是 7702 从设计上就带来的盲签风险,不是某个钱包厂商的实现疏忽。
四、执行层:一笔调用扫走 65 个地址
恶意合约未验证源码,但从 calldata 可以还原接口:sweep(address recipient, address[] tokens),选择器 0x8a179be4。
arg0= 主接收地址(当前以太坊持仓约 21.7 亿美元)arg1= 65 个唯一地址,第一个是0x0000…0000,其余 64 个是 ERC-20
所以准确表述是:1 个 ETH 占位符 + 64 个代币,不是「63 种代币」。 公开分析里的「63」更可能是实际转账成功的数量 —— 差额来自零余额、转账被拒或重复资产。
三个设计细节:
- 硬编码 caller 校验 —— 只有 operator 能触发
address(0)作为 ETH 占位符 —— 同一个数组同时处理原生币和代币- 逐项容错 —— 单个代币失败不回滚整笔交易,这才让它能一次打包这么多资产
为什么不需要任何授权? 因为委托代码运行在 Binance 14 的上下文里:对每个代币合约而言,msg.sender 就是热钱包本身,balanceOf(address(this)) 读到的就是热钱包余额。
它不是在「调用别人的钱」,它自己就是持有人。 这是 7702 与传统 drainer(需要骗取 ERC-20 approve)最本质的区别。
五、持久性的直接证据:12 次扫荡与空轮询
授权生效后,operator 在 3.5 小时内又发起了 12 次 sweep 调用:
| 时间(UTC) | Gas used | 解读 |
|---|---|---|
| 20:24:23 | 2,455,349 | 主扫荡,65 个地址 |
| 20:41:23 | 100,738 | 小额补扫 |
| 20:59:47 | 83,626 | 小额补扫 |
| 21:04:23 | 806,166 | 中等批次 |
| 21:44:11 | 732,036 | 中等批次 |
| 22:39:35 | 4,578,329 | 大额新入账被扫 |
| 23:36:47 / 23:37:23 / 23:37:47 / 23:38:23 | 23,100 ×4 | 空轮询,0 笔代币转账 |
| 23:54:59 | 23,100 | 空轮询 |
| 23:56:47 | 31,900 | 小额扫荡后停止 |
23,100 gas 的调用已被验证为 0 笔代币转账 —— 21,000 的基础交易成本加 2,100 的额外开销。
这就是一个机器人每隔几十秒到几分钟调用一次 sweep:有余额就转走,没有就白花 2,100 gas。
这是「持久委托 = 自动收款机」最直接的链上证据,也彻底否定了「临时执行」的说法。
一个有意思的细节:Binance 14 目前仍留有约 4,790 万美元资产。原因很可能是攻击者的代币清单是离线枚举的固定列表(65 个地址),而不是链上全量扫描 —— 长尾代币和后来新入账的资产没被覆盖。这也解释了 22:39 那次 458 万 gas 的大扫:有新资产进入了清单范围。
六、一个被忽略的边界:chain_id = 1
50 条授权的 chain_id 全部是 1。
按 EIP-7702,只有 chain_id = 0 的授权才能在所有支持该 EIP 的 EVM 链上重放。这里没有用。 因此:
- 这条授权只能影响以太坊主网的
Binance 14 - 比特币、Tron、Solana 根本没有 EIP-7702 这个机制 —— 那些链上的资金流出不可能来自同一条授权
- 所谓「至少 9 条链、56–62 亿美元」的口径,必然混合了不同性质的动作:其他热钱包的独立失守、币安自己的归集或防御性转移、用户恐慌提现,或分析师的误标
这不是说多链损失不存在,而是说归因需要分开做。 以太坊上的 25 亿有完整的授权—执行因果链;其他链上的数字目前更多是余额变动,缺少同等级的因果证据。
把它统一记在「一次 7702 攻击」名下,会高估这次攻击的技术半径,也会误导后续的防御重点。
七、后门关了吗
截至可核验的最后一个时间点,委托仍然存在:
- 10-30 00:10:35 UTC 的一笔转入交易中,
Binance 14的to对象仍带proxy_type: eip7702与实现地址 Binance 14最近 50 笔转出中没有 Type-4 撤销交易(撤销需要签署一条指向address(0)的授权)- 攻击者的最后一次扫荡停在 23:56:47,之后 operator 只剩约 2,591 美元余额
两种可能:一是币安已通过其他方式(更换签名密钥、迁移地址)让委托失效,但公开数据没有撤销记录;二是委托仍在,只是攻击者暂时停手。
无论哪种,结论一样:7702 的撤销不是「停机」或「改密码」,而必须再签一条链上授权。 如果私钥已经泄露,撤销也只是临时的 —— 攻击者可以再次委托。
一个反向线索值得注意:如果攻击者完整持有私钥,币安签署撤销后他完全可以再次委托,形成拉锯。目前没有观察到这种拉锯 —— 这更倾向于「攻击者通过某个已被关闭的通道获得了签名能力」,而不是「长期持有私钥」。
根因矩阵因此需要更新:
| 假设 | 证据 | 评价 |
|---|---|---|
| 私钥直接泄露 | 50 条签名均有效;operator 有 Tornado 出资 | 仍可能,但无法解释为何要 50 条 nonce,以及为何停手后未再委托 |
| 签名基础设施被控 | 批量预签特征明显;测试钱包彩排;对 Hot Wallet 34 的 owner 关系有精准利用 | 当前解释力最强,但仍需 HSM/MPC 日志证实 |
| 内部人 / 供应链 | 18 小时准备、64 个代币的定制清单、知道热钱包的权限拓扑 | 不能排除,且与「签名接口被利用」可以并存 |
八、与合约漏洞的本质区别
| 维度 | 传统合约漏洞 | 7702 委托滥用 |
|---|---|---|
| 失效对象 | 合约逻辑 | 签名密钥与签名流程 |
| 触发方式 | 调用存在缺陷的函数 | 一条合法签名的授权元组 |
| 是否需要授权 | 通常依赖已有 allowance | 不需要,地址自身即持有人 |
| 影响范围 | 单一协议资金 | 该地址名下全部资产 |
| 修复方式 | 暂停、补丁、治理 | 必须再签一条链上授权才能撤销 |
| 事后可逆性 | 可暂停协议 | 已转出的原生资产不可逆 |
7702 把「账户安全」从每笔交易的授权边界问题,变成了一次签名的代码托管问题。
而热钱包为了支持高频提现必须保持在线签名能力 —— 这恰好与 7702 的最坏情形正面相撞。
九、防守启示
对交易所与托管方,按优先级:
- 策略层拒绝 —— 热钱包地址禁止发起 Type-4 交易,除非白名单委托目标加多签审批。这是唯一能从根本上消除该路径的措施。
- 签名可读性 —— 硬件钱包与 MPC 必须展示「将代码委托给 0x…」。由于签名对象不是 EIP-712,这需要钱包厂商专门实现,不会自动获得。
- 监控对象换掉 —— 从「大额转出」改为「
0xef0100代码变更 + mempool 中的 Type-4 交易 + nonce 异常跳变」。要读的是实际存储的 23 字节0xef0100‖address—— 按规范EXTCODESIZE返回 23,且EXTCODEHASH等外部读取操作不跟随委托,作用在委托指示符本身,所以能直接把委托目标解出来。 - 降低单点敞口 —— 热钱包只留运营浮动余额,超额自动归集;用「限额 allowance + 扫块合约」代替 EOA 直接持币。
- 权限隔离 —— 智能钱包的 owner 不要复用热钱包 EOA。本次攻击正是通过
Binance 14对它名下的智能钱包发起调用,又拉走了约 7.8–8.23 亿 USDC。 - 应急 —— 签署指向
address(0)的元组可清除委托,但私钥已泄露时攻击者可再次委托,必须同步轮换密钥并迁移地址。清除委托只是争取时间,不是修复。
对协议方:停止用 extcodesize、tx.origin == msg.sender 判断调用者身份 —— 7702 之后这些假设不再成立。
十、结论
这次攻击的真正结构是「18 小时准备 + 一条签名 + 一个机器人」:准备期用来验证合约与 nonce 策略,签名把热钱包变成攻击者可编程的账户,机器人在之后 3.5 小时里反复收割。
EIP-7702 没有制造漏洞。 它只是把「一次签名」的后果,从「转走一笔钱」放大成「接管一个地址的全部能力,并且长期有效」。
链上证据还修正了两个常见误解:委托不是「临时」的,它会一直存在到被显式清除;被扫走的也不是「63 个代币」,而是 64 个代币加原生 ETH,装在一个 65 元素的数组里。
对行业的启示不在合约层,而在签名语义层:只要签名系统无法区分「转账」和「委托代码」,无法把 0xef0100 的目标地址展示给审批人,无法在 mempool 阶段对 Type-4 交易告警,那么每一台在线签名的热钱包都是潜在的 Binance 14。
底线判断。 判断一个平台是否真正修复,不看它发了多少声明,而看三件事:链上是否出现指向 address(0) 的撤销授权、热钱包地址是否更换、签名策略是否开始拒绝 Type-4。 三者缺一,后门就只是暂时没人推。
参考资料
- EIP-7702 规范
- 授权交易(Type-4)
- 清空交易
- 恶意合约部署交易
- Zealynx:EIP-7702 钱包安全技术拆解
- Halborn:EIP-7702 安全考量
- Threat Landscape:币安热钱包与 7702 委托
- Ondology Labs:EIP-7702 攻击分析
配套阅读:币安热钱包失守:一条合法签名,8 分钟,56 亿美元、EIP-7702 的签名流程、币安还没公开那份签名日志。gas 成本按 ETH ≈ $2,209 折算。本文基于公开链上数据与第三方分析,币安未发布官方 post-mortem,根因未经确认。DYOR。