价格操纵的黑魔法:Balancer V2 不变量计算漏洞剖析

CN
6小时前
操纵BPT价格获利

作者:BlockSecTeam

编译:白话区块链

2025 年 11 月 3 日,Balancer V2 的组合型稳定池(Composable Stable Pools)以及多个链上的几个分叉项目遭受了一次协同攻击,导致总损失超过 1.25 亿美元。BlockSec 在第一时间发出了警报,并随后发布了初步分析。

这是一次高度复杂的攻击。我们的调查显示,根本原因是不变量(invariant)计算中的精度损失导致的价格操纵,进而扭曲了 BPT(Balancer 池Token)的价格计算。这种不变量操纵允许攻击者通过单次批量交换(batch swap)从一个特定的稳定池中获利。尽管一些研究人员提供了富有洞察力的分析,但某些解读存在误导性,其根本原因和攻击过程尚未完全阐明。本博客旨在对该事件进行全面而准确的技术分析。

关键要点 (TL;DR)

  • 根本原因:舍入不一致和精度损失

    • 放大(upscaling)操作使用单向舍入(向下舍入),而缩小(downscaling)操作使用双向舍入(向上和向下舍入)。

    • 这种不一致性造成了精度损失,当通过精心设计的交换路径加以利用时,违反了“舍入应始终有利于协议”的标准原则。

  • 攻击执行

    • 攻击者精心设计了参数,包括迭代次数和输入值,以最大化精度损失的影响。

    • 攻击者使用两阶段方法来规避检测:首先在单笔交易中执行核心攻击,不立即获利,然后在另一笔交易中通过提取资产来实现利润。

  • 运营影响与放大

    • 由于某些限制,协议无法暂停。这种无法停止运营的情况加剧了攻击的影响,并导致了大量后续或模仿攻击。

在以下部分中,我们将首先提供有关 Balancer V2 的关键背景信息,然后深入分析已发现的问题和相关攻击。

0x1 背景

1. Balancer V2 的组合型稳定池

本次攻击中受影响的组件是 Balancer V2 协议的组合型稳定池。这些池专为预期保持接近 1:1 锚定(或以已知汇率交易)的资产而设计,并允许以最小的价格影响进行大额交换,从而显著提高同类或相关资产之间的资本效率。每个池都有自己的 Balancer 池Token (BPT),代表流动性提供者在池中的份额,以及相应的底层资产。

该池采用了 Stable Math(基于 Curve 的 StableSwap 模型),其中不变量 D 代表了池的虚拟总价值。

BPT 价格可以近似为:

$$\text{BPT price} \approx \frac{D}{\text{totalSupply}}$$

从上述公式可以看出,如果 D 在账面上可以被改小(即使没有任何实际资金损失),BPT 价格就会显得更便宜。

2. batchSwap() 和 onSwap()

Balancer V2 提供了 batchSwap() 函数,该函数支持在 Vault内进行多跳交换(multi-hop swaps)。根据传递给此函数的参数,有两种交换类型:

  • GIVEN_IN(“给定输入”):调用者指定确切的输入Token数量,池子计算相应的输出数量。

  • GIVEN_OUT(“给定输出”):调用者指定期望的输出数量,池子计算所需的输入数量。

通常,batchSwap() 由多个通过 onSwap() 函数执行的Token间交换组成。以下概述了当一个 SwapRequest被分配为 GIVEN_OUT 交换类型时的执行路径(请注意 ComposableStablePool 继承自 BaseGeneralPool):

Vault.batchSwap()  BaseGeneralPool._swapGivenOut()    BaseGeneralPool._onSwapGivenOut()      StableMath._calcInGivenOut()

以下显示了 GIVEN_OUT 交换类型中 amount_in 的计算,这涉及不变量 D。

// inGivenOut token x for y - polynomial equation to solve// ax = amount in to calculate                                     // bx = balance token in                                                                 // x = bx + ax (finalBalanceIn)                                                                // D = invariant// A = amplification coefficient// n = number of tokens// S = sum of final balances but x                                                             // P = product of final balances but x                                                                            D                     D^(n+1)    x^2 + ( S - ----------  - D) * x -  ------------- = 0                        (A * n^n)               A * n^2n * P

3. 缩放与舍入

为了在不同Token余额之间规范化计算,Balancer 执行以下两个操作:

  • 放大(Upscaling):在执行计算前,将余额和金额放大到统一的内部精度。

  • 缩小(Downscaling):将结果转换回其原生精度,并应用定向舍入(例如,输入金额通常向上舍入,以确保池子不会收费不足,而输出金额则经常向下舍入)。

显然,放大和缩小在理论上是成对的操作——分别是乘法和除法。然而,这两种操作的实现中存在不一致。具体来说,缩小操作有两个变体或方向:divUpdivDown。相比之下,放大操作只有一个方向,即 mulDown

这种不一致的原因尚不清楚。根据 _upscale() 函数中的注释,开发人员认为单向舍入的影响是微乎其微的。

// 放大(Upscale)舍入不一定会始终朝同一方向:例如,在一次交换中,

// 输入Token的余额应向上舍入,而输出Token的余额应向下舍入。这是我们对所有金额都进行同向舍入的唯一地方,

// 因为这种舍入的影响预计是最小的

// (并且除非 _scalingFactor() 被覆盖,否则没有舍入误差)。

0x2 漏洞分析

根本问题源于在 BaseGeneralPool._swapGivenOut() 函数中执行放大(upscaling)操作时所执行的向下舍入操作。特别是,_swapGivenOut() 通过 _upscale() 函数错误地将 swapRequest.amount 向下舍入。这个舍入后的值随后被用作 amountOut,在通过 _onSwapGivenOut() 计算 amountIn 时使用。这种行为与“舍入应以有利于协议的方式应用”的标准实践相矛盾。

因此,对于给定的池(wstETH/rETH/cbETH),计算出的 amountIn 低估了实际所需的输入。这允许用户以较少数量的一种底层资产(例如 wstETH)交换另一种资产(例如 cbETH),从而由于有效流动性减少而导致不变量 D 减少。因此,相应的 BPT(wstETH/rETH/cbETH)的价格变得被人为压低(deflated),因为 $\text{BPT price} = \frac{D}{\text{totalSupply}}$。

0x3 攻击分析

攻击者执行了

两阶段攻击,可能是为了最小化被检测的风险:

  • 在第一阶段,核心利用在单笔交易中执行,没有立即获利。

  • 在第二阶段,攻击者在另一笔交易中通过提取资产来实现利润。

第一阶段可以进一步分为两个阶段:参数计算和批量交换。下面,我们使用 Arbitrum 上的一个攻击交易(TX)示例(https://app.blocksec.com/explorer/tx/arbitrum/0x7da32ebc615d0f29a24cacf9d18254bea3a2c730084c690ee40238b1d8b55773)来说明这些阶段。

参数计算阶段

在此阶段,攻击者将链下计算与链上模拟相结合,根据组合型稳定池的当前状态(包括缩放因子、放大系数、BPT 汇率、交换费用和其他参数),精确调整下一阶段(批量交换)中每一跳的参数。有趣的是,攻击者还部署了一个辅助合约来协助这些计算,这可能是为了减少被“抢跑”(front-running)的风险。

首先,攻击者收集目标池的基本信息,包括每个Token的缩放因子、放大参数、BPT 汇率和交换费百分比。然后他们计算一个关键值 trickAmt,这是用于引发精度损失的被操纵的目标Token数量。

将目标Token的缩放因子(scaling factor)记为 sF,计算如下:

$$\text{trickAmt} = \frac{10^{18} - 1}{\text{sF}}$$

为了确定下一阶段(批量交换)步骤 2 中使用的参数,攻击者使用以下 calldata 对辅助合约的 0x524c9e20 函数进行后续模拟调用:

uint256[] balances; // 池Token的余额(不包括 BPT)uint256[] scalingFactors; // 每个池Token的缩放因子uint tokenIn; // 此跳模拟的输入Token索引uint tokenOut; // 此跳模拟的输出Token索引uint256 amountOut; // 期望的输出Token数量uint256 amp; // 池的放大参数uint256 fee; // 池交换费百分比

返回数据为:

uint256[] balances; // 交换后池Token的余额(不包括 BPT)

具体来说,初始余额和迭代循环次数是链下计算的,并作为参数传递给攻击者的合约(报告分别为 100,000,000,000 和 25)。每次迭代执行三次交换:

  1. 交换 1:将目标Token的数量推至 trickAmt + 1,假设交换方向为 0 → 1。

  2. 交换 2:继续用 trickAmt 换出目标Token,这会触发 _upscale() 调用中的向下舍入。

  3. 交换 3:执行一次反向交换操作(1 → 0),要交换的数量是通过截断池中当前Token余额的两个最高有效十进制数字得出的,即向下舍入到最接近的 $10^{d-2}$ 的倍数,其中 $d$ 是十进制数字的位数。例如,324,816 -> 320,000。

请注意,由于 StableMath 计算中使用了牛顿-拉弗森(Newton–Raphson)方法,此步骤可能偶尔会失败。为了缓解这种情况,攻击者实现了两次重试尝试,每次使用原始值的 9/10 作为备用值。

攻击者的辅助合约派生自 Balancer V2 的 StableMath 库,这一点可以通过其包含“BAL”风格的自定义错误消息来证明。

批量交换阶段

然后,batchSwap() 操作可以分解为三个步骤:

  1. 步骤 1:攻击者将 BPT (wstETH/rETH/cbETH) 换成底层资产,以精确地将一个Token(cbETH)的余额调整到舍入边界的边缘(amount = 9)。这为下一步的精度损失创造了条件。

  2. 步骤 2:然后,攻击者使用一个精心设计的数量(= 8)在另一种底层资产(wstETH)和 cbETH 之间进行交换。由于在缩放Token数量时向下舍入,计算出的 Δx 变得略小(从 8.918 到 8),导致 Δy 被低估,从而使不变量(D,来自 Curve 的 StableSwap 模型)变小。由于 $\text{BPT price} = \frac{D}{\text{totalSupply}}$,BPT 价格被人为压低。

  3. 步骤 3:攻击者反向将底层资产换回 BPT,恢复平衡,同时从被压低的 BPT 价格中获利。

0x4: 攻击与损失

我们在下表中总结了这些攻击及其相应的损失,总损失超过 1.25 亿美元。

[表格显示攻击和损失数据的占位符]

0x5 结论

该事件涉及一系列针对 Balancer V2 协议及其分叉项目的攻击交易,导致了重大的财务损失。在最初的攻击之后,在多个链上观察到了大量后续和模仿交易。这次事件凸显了 DeFi 协议设计和安全的几个关键教训:

  • 舍入行为与精度损失:放大(upscaling)操作中使用的单向舍入(向下舍入)与缩小(downscaling)操作中使用的双向舍入(向上和向下舍入)不同。为防止类似漏洞,协议应采用更高精度的算术,并实施稳健的验证检查。必须坚持“舍入应始终有利于协议”的标准原则。

  • 漏洞利用的演变:攻击者执行了一次复杂的两阶段漏洞利用,旨在规避检测。在第一阶段,攻击者在单笔交易中执行核心利用,没有立即获利。在第二阶段,攻击者在另一笔交易中通过提取资产来实现利润。这再次凸显了安全研究人员与攻击者之间持续的“军备竞赛”。

  • 运营意识与威胁响应:此事件强调了关于初始化和运营状态的及时警报的重要性,以及主动的威胁检测和预防机制,以减轻持续或模仿攻击造成的潜在损失。

在保持运营和业务连续性的同时,行业参与者可以利用 BlockSec Phalcon 作为最后一道防线来保护他们的资产。BlockSec 专家团队随时准备为您的项目进行全面的安全评估。

本文链接:https://www.hellobtc.com/kp/du/11/6106.html

来源:https://x.com/BlockSecTeam/status/1986057732810518640

免责声明:本文章仅代表作者个人观点,不代表本平台的立场和观点。本文章仅供信息分享,不构成对任何人的任何投资建议。用户与作者之间的任何争议,与本平台无关。如网页中刊载的文章或图片涉及侵权,请提供相关的权利证明和身份证明发送邮件到support@aicoin.com,本平台相关工作人员将会进行核查。

分享至:
APP下载

X

Telegram

Facebook

Reddit

复制链接