攻击者如何利用四舍五入错误从Balancer中抽走1.28亿美元
2025年11月5日
作者:Dikla Barda, Roaman Zaikin & Oded Vanunu
2025年11月3日,Check Point Research的区块链监控系统检测到一个针对Balancer V2的ComposableStablePool合约的复杂漏洞利用。攻击者利用了池不变值计算中的算术精度损失,在不到30分钟的时间内从六个区块链网络中抽走了1.2864亿美元。
这次攻击利用了 _upscaleArray 函数中的一个四舍五入错误漏洞,结合精心构造的 batchSwap 操作,使攻击者能够人为压低BPT(Balancer池代币)的价格,并通过重复的套利周期提取价值。漏洞利用主要发生在攻击者智能合约部署期间,构造函数执行了超过65次微交换,将精度损失累积到毁灭性的程度。
引言
2025年11月3日凌晨,Check Point的区块链威胁分析系统标记了以太坊主网上涉及Balancer V2 Vault合约的异常活动。几分钟内,我们的自动检测系统识别出正在进行的关键漏洞利用,多个流动性池出现大量资金流出。
此次攻击利用了Balancer的ComposableStablePool处理小额交换时的一个数学漏洞。当代币余额被推到特定的四舍五入边界(8-9 wei范围)时,Solidity的整数除法会导致显著的精度损失。攻击者通过执行批处理的交换序列,将这些微小误差累积成灾难性的不变值操纵,从而利用了这一点。
背景:Balancer V2 架构
Vault系统
Balancer V2使用一个集中的“Vault”合约(地址:0xBA12222222228d8Ba445958a75a0704d566BF2C8),该合约持有所有池中的所有代币,将代币存储与池逻辑分离,以降低Gas成本并提高资本效率。这种共享流动性设计意味着池数学中的单个漏洞可能同时影响所有ComposableStablePool——这正是本次攻击中发生的情况。
内部余额机制
Balancer V2的内部余额系统允许用户一次性存入代币,并在多次操作中使用它们,无需重复的ERC20转账:
|
|
这个系统对攻击至关重要。漏洞利用合约在部署期间将被盗资金积累在其内部余额中,然后在后续交易中将其提取到最终的接收者地址。
漏洞:稳定池数学中的算术精度损失
根本原因
ComposableStablePool使用Curve的StableSwap不变值公式来维持相似资产之间的价格稳定性。不变值D代表池的总价值,BPT价格的计算方式为D除以totalSupply。然而,为不变值计算准备余额的缩放操作会引入四舍五入误差。
易受攻击的代码路径:
|
|
mulDown 函数执行向下取整的整数除法。当余额很小(8-9 wei范围)时,这种四舍五入会产生显著的相对误差——每次操作精度损失高达10%。
这种精度误差会传播到不变值D的计算中,导致计算值异常减少。由于BPT价格等于D除以总供应量,降低的D会直接压低BPT价格,为攻击者创造套利机会。
单次交换产生的精度损失可以忽略不计,但在一个包含65次操作的 batchSwap 交易中,这些损失会急剧累积。由于缺乏不变值变化验证,攻击者能够通过累积的精度误差系统地压低BPT价格,从每个池中提取数百万美元的价值。
攻击分析
三阶段模式
攻击者在单个 batchSwap 交易中执行了复杂的三阶段交换序列:
阶段1:调整至四舍五入边界 将大量BPT兑换为基础代币,将某个代币的余额推至关键的8-9 wei阈值,在此阈值下四舍五入误差最大化。
阶段2:触发精度损失
执行涉及处于边界位置的代币的小额交换。_upscaleArray 函数在缩放过程中向下取整,导致不变值D被低估,BPT价格被人为压低。
阶段3:提取价值 以被压制的价格铸造或购买BPT,然后立即以全价赎回为基础资产。价格差异即代表纯利润。
这个三阶段循环在同一个 batchSwap 交易中重复了65次。所有阶段原子性地发生,防止了干预,并确保精度损失在共享的余额状态中累积,最终从每个目标池中提取数百万美元。
了解了漏洞机制后,我们来看看攻击者是如何自动化利用的。
漏洞利用合约架构
攻击者部署了合约 0x54B53503c0e2173Df29f8da735fBd45Ee8aBa30d,其操作结构涉及三个地址:
– Exploiter 1:0x506D1f9EFe24f0d47853aDca907EB8d89AE03207 (部署者)
– Exploit Contract:0x54B53503c0e2173Df29f8da735fBd45Ee8aBa30d
– Exploiter 2:0xAa760D53541d8390074c61DEFeaba314675b8e3f (接收者)
基于构造函数的攻击
对交易 0x6ed07db… 的分析显示,盗窃行为发生在合约部署期间。构造函数自动执行了四舍五入错误利用,同时针对两个Balancer池。
构造函数向Balancer的协议费用收集器生成了65笔代币转账——这些是在操纵过程中收集的交换费用,而非被盗资金本身。转账金额显示了迭代精度利用的特征模式,从0.414 osETH递减到0.000000000000000003 osETH,因为四舍五入误差累积到可忽略的值。
被盗的价值出现在 InternalBalanceChanged 事件中,这些事件记录了Vault内部会计系统中的余额更新。漏洞利用合约的内部余额增加了:
- 池 1 (osETH/wETH-BPT):+4,623 WETH, +6,851 osETH
- 池 2 (wstETH-WETH-BPT):+1,963 WETH, +4,259 wstETH
- 合计:6,586 WETH (4,623 + 1,963) + 6,851 osETH + 4,259 wstETH
这些内部余额的增加代表了实际被盗资金。InternalBalanceChanged 事件显示,漏洞利用合约的Vault内部账户被记入了被抽走的资产。虽然基础代币在物理上仍留在Vault合约中,但Vault的会计系统现在将这些余额的所有者识别为漏洞利用合约,从而使得后续提现成为可能。
提现功能
在构造函数累积了被盗资金后,函数 0x8a4f75d6 将其转移给 Exploiter 2:
|
|
该函数提现合约自身的内部余额。UserBalanceOp 中的发送者等于漏洞利用合约地址,因为该合约合法地拥有在构造函数执行期间累积的资金。
交易 0xd155207… 证实了此次提现将6,586 WETH从漏洞利用合约的内部余额转移到了 Exploiter 2 地址。
两阶段攻击
第一阶段 – 盗窃(构造函数执行):
- 交易:0x6ed07db1a9fe5c0794d44cd36081d6a6df103fab868cdd75d581e3bd23bc9742
- 操作:部署漏洞利用合约
- 方法:构造函数对两个池执行
batchSwap操作 - 结果:通过四舍五入错误抽走6300万美元,存储在合约的内部余额中
- 证据:65笔费用转账 +
InternalBalanceChanged事件显示 +6,586 WETH, +6,851 osETH, +4,259 wstETH
第二阶段 – 提取(函数调用):
- 交易:0xd155207261712c35fa3d472ed1e51bfcd816e616dd4f517fa5959836f5b48569
- 操作:调用函数
0x8a4f75d6 - 方法:将内部余额提现给 Exploiter 2
- 结果:资金转移到最终接收者
- 证据:
manageUserBalance的发送者 = 漏洞利用合约
结论
Balancer的漏洞利用事件证明了DeFi协议中的数学漏洞如何通过自动化和精心的参数调整被武器化。攻击者的成功源于认识到,当微小的四舍五入误差在原子交易的数十次操作中被放大时,就变得可利用。
尽管经过了广泛的审计,该漏洞依然存在,因为传统测试侧重于单个操作的正确性,而非对抗性批量操作的累积效应。行业必须朝着持续的安全验证、经济攻击建模和对抗性测试的方向发展,要考虑微小的缺陷如何累积成灾难性的漏洞利用。