在7月18日,我们的 DeFiRanger 系统 报告了几笔可疑交易。经过人工分析,我们确认这些交易是针对 Array Finance 的攻击。接下来,我们将以一笔攻击交易为例,说明攻击过程和漏洞的根本原因。
攻击交易
本文使用的攻击交易是: 0xa17bbc7c9ab17aa88fdb5de83b41de982845e9c9c072efff6709dd29febf0daa
攻击流程

如图 1 所示,我们发现攻击者在从 AAVE 借入闪电贷后,获得了 186.62 WETH 的利润(本文中不区分 WETH 和 ETH)。

图 2 展示了详细的攻击过程。
- 首先,攻击者调用了 Array Finance 的
buy函数。攻击者花费 45.91 WETH,铸造了 430 个 ARRAY 代币,并获得了这些代币。 - 接着,攻击者调用了一个闭源合约(Array Collater - 0xa800cda5)的
joinPool函数五次。他/她存入了 676,410.58 DAI + 679,080.46 USDC + 901.82 WETH + 20 WBTC + 20 renBTC,并获得了 726.38 个由 Array Collater 铸造的 aBPT 代币。 - 攻击者调用
sell函数,销毁了 430 个 ARRAY 代币,获得了 77.17 个 aBPT 代币。 - 最后,攻击者调用 Array Collater 的
exitPool函数。他/她销毁了在前两步中获得的 804.55 个 aBPT 代币,并获得了 748,271.55 DAI + 751,225.08 USDC + 997.62 WETH + 22.63 WBTC + 22.74 renBTC。
从图 2 可以看出,攻击者在第 5 步(图 2:调用 sell 函数)中获利。这是因为获得的 77.17 个 aBPT 代币比第 3 步(图 2:调用 buy 函数)中存入的 49.9142 WETH 更有价值。接下来,我们将分析代码,以了解攻击为何会发生。
代码漏洞
以下代码显示了 Array Finance 的 sell 函数。在该函数中,Array Finance 使用攻击者拥有的 ARRAY 代币余额,并调用内部的 _sell 函数来计算出售 ARRAY 代币可以获得的 aBPT 代币数量。

以下是 _sell 函数的实现。它调用 calculateLPtokensGivenArrayTokens 来获取给定 ARRAY 代币数量可获得的 aBPT 代币数量。然后,该函数销毁 ARRAY 代币并返回 aBPT 代币。

以下显示了 calculateLPtokensGivenArrayTokens 函数的实现。

请注意,有四个参数会影响 amountLPToken 的计算。在读取 saleTargetAmount 后,我们推断其公式如下:
arraySmartPool.totalSupply() * (1 - (1 - amount / ARRAY.totalSupply()) ^ (1000000 / reseveRatio))
arraySmartPool 是 Array Collater 的智能合约地址(0xa800cda5f3416a6fb64ef93d84d6298a685d190d)。当攻击者将从闪电贷借来的资金存入 Array Collater 时,arraySmartPool.totalSupply() 的值会增加(如下表所示)。
TxnIndex: 64 arraySmartPool.totalSupply(): 110162296218708026400
TxnIndex: 107 arraySmartPool.totalSupply(): 165243444328062039600
TxnIndex: 150 arraySmartPool.totalSupply(): 247865166492093059400
TxnIndex: 193 arraySmartPool.totalSupply(): 371797749738139589100
TxnIndex: 236 arraySmartPool.totalSupply(): 557696624607209383650
TxnIndex: 280 arraySmartPool.totalSupply(): 836544936910814075475
在阅读 arraySmartPool 的代码后,我们可以确认这一逻辑。以下显示了 arraySmartPool 的 joinPool 函数。

此函数首先调用 SmartPoolManager.joinPool 函数来计算需要从 msg.sender 获取的代币数量(actualAmountsIn)。然后,对于每种代币,它调用 _pullUnderlying 函数将其存入 arraySmartPool。最后,它调用 _mintPoolShare 和 _pushPoolShare 来铸造 aBPT 代币并将铸造的 aBPT 代币转移给 msg.sender。
请注意,arraySmartPool 继承自 PCToken。_mintPoolShare 函数调用 _mint 函数,如下所示。
_mint 函数会增加 varTotalSupply 变量,该变量直接由 totalSupply() 返回。因此,每次调用 joinPool 都会增加此值。
利润估算
| 攻击地址 | 攻击交易 | 利润 (ETH) |
|---|---|---|
| 0x1337 | 0xa17bbc7c9ab17aa88fdb5de83b41de982845e9c9c072efff6709dd29febf0daa | 186.62073907323577 |
| 0x1b4fb735b51a25b280499710d02f50e4f772949664f9eb088e9d968b41ca2a1c | 59.338842936817095 | |
| 0x356ae8a278754551d9d2ae7dc0ea876b37c342ff2d010bee14aa253bb0b6030b | 18.827787420260467 | |
| 0x388fd59eab65b6034c96c01a21197eb2889d6507f84ff07e553749c541563962 | 5.880854505948256 | |
| 0xf4227ca3c8e8b5c8a0b10f034c6dee6556788c618e4cd289dc343b3247e85add | 0.4327422187881 | |
| 总计 | 271.1009661550497 | |
| 0x80d2 | 0xf061f8fc19c894ec37310ff59977ec97de55fd1494f1b1e66ae89188552d5c60 | 1.8414029470248867 |
| 总计 | 1.8414029470248867 | |
| 总计 | 272.9423691020746 |
总结
总而言之,攻击者利用了 Array Finance 的价格机制依赖于 aBPT 代币的 totalSupply,而 totalSupply 是可以操纵的这一漏洞。该漏洞在我们研究论文 DeFiRanger: Detecting Price Manipulation Attacks on DeFi Applications 中已有讨论。
致谢
Junjie Fei, Yufeng Hu, Ziling Lin, Siwei Wu, Lei Wu, Yajin Zhou @BlockSec
(按姓氏字母顺序排列)
关于 BlockSec
BlockSec 是一家开创性的区块链安全公司,由一群杰出的全球安全专家于 2021 年创立。公司致力于为新兴的 Web3 世界增强安全性和可用性,以促进其大规模采用。为此,BlockSec 提供智能合约和 EVM 链安全审计服务,Phalcon 平台用于安全开发和主动阻止威胁,MetaSleuth 平台用于资金追踪和调查,以及 MetaDock 扩展,帮助 Web3 构建者在加密世界中高效冲浪。
截至目前,公司已为 MetaMask、Uniswap Foundation、Compound、Forta 和 PancakeSwap 等 300 多家尊贵客户提供服务,并在两轮融资中从 Matrix Partners、Vitalbridge Capital 和 Fenbushi Capital 等知名投资者那里获得了数千万美元的投资。
官方 Twitter 账号:https://twitter.com/BlockSecTeam



