Array Finance 攻击策略

引用知乎报告

2021年7月18号,我们的一直实时运行的攻击检测工具向我们报了多笔疑似攻击交易,经过人工分析,我们确定了这是一次针对Array Finance 的攻击事件。下面我们首先根据一笔攻击交易分析攻击这的攻击流程,然后再分析Array Finance 的代码来展现攻击原理。

0xa17bbc7c9ab17aa88fdb5de83b41de982845e9c9c072efff6709dd29febf0daa

攻击流程

图一

如图一所示,我们首先忽略了中间大量冗余的细节,只看这笔交易的头尾部分,可以发现,这笔交易的攻击者通过AAVE大量的闪电贷获利了 186.62 WETH (本文不区分Ether和WETH)。

图二

具体的攻击细节如图二所示, - 攻击者首先调用Array Finance的buy函数:用45.91个WETH,获得了430个由Array Finance铸出来的 ARRAY token; - 攻击者然后调用了一个闭源合约(Array Collater, 0xa800cda5)的joinPool函数连续五次:总共存入676,410.58 DAI + 679,080.46 USDC + 901.82 WETH + 20 WBTC + 20 renBTC, 获得了726.38 个由Array Collater 铸出来的 aBPT token - 攻击者再调用了Array Finance的sell函数:毁坏(Burn)了刚刚铸出来的430个ARRAY token, 获得了 77.17 个 aBPT token; - 攻击者最后调用了Array Collater 的exitPool函数:将上两笔获得的大概804.55 aBPT token 毁坏(Burn), 获得了总共 748,271.55 DAI + 751,225.08 USDC + 997.62 WETH + 22.63 WBTC + 22.74 renBTC.

根据图二,我们基本可以确定攻击者的获利来源:第5步(Invoke the sell function) 获得的77.17个aBPT 要比第3步(Invoke the buy function)中存入的 49.9142 WETH 更加值钱。我们下面通过分析代码来确认这种情况为什么会出现。

代码漏洞

下图是Array Finance的sell函数,可以看出,在这个调用中Array Finance计算攻击者拥有的ARRAY token的余额,并调用内部函数_sell计算能够获得的aBPT token数量

下图是_sell函数的实现,通过调用calculateLPtokensGivenArrayTokens函数计算给定的ARRAY token的数量能换取多少aBPT token。随后Burn掉ARRAY token,返回aBPT token。

下图是calculateLPtokensGivenArrayTokens函数的实现。

注意到有四个参数会影响amountLPToken的计算,通过阅读saleTargetAmount的函数源码,我们总结出的计算公式如下:

arraySmartPool.totalSupply() * (1 - (1 - amount / ARRAY.totalSupply()) ^ (1000000 / reseveRatio))

其中,arraySmartPool是Array Collater的地址 (0xa800cda5),arraySmartPool.totalSupply() (就相当于aBPT token的totalSupply)的值会随着攻击者不断将FlashLoan借入的资金填充进Array Collater里而不断增加。

通过深入分析整个交易,我们发现在下面的内部交易中arraySmartPool.totalSupply() (相当于aBPT token的totalsupply)的值由于攻击者借用FlashLoan向池子中注入资金而不断增加:

InternalTxnIndex: 64 arraySmartPool.totalSupply():  110162296218708026400
InternalTxnIndex: 107 arraySmartPool.totalSupply():  165243444328062039600
InternalTxnIndex: 150 arraySmartPool.totalSupply():  247865166492093059400
InternalTxnIndex: 193 arraySmartPool.totalSupply():  371797749738139589100
InternalTxnIndex: 236 arraySmartPool.totalSupply():  557696624607209383650
InternalTxnIndex: 280 arraySmartPool.totalSupply():  836544936910814075475

其中后5次数值变大正好对应了攻击者5次调用joinPool函数向池子中注入流动性。

从arraySmartPool的代码中也不难验证这一点。下面是arraySmartPool的joinPool函数(其中省略了部分注释和检查):

该函数首先调用SmartPoolManager.joinPool函数计算出需要从msg.sender那里拿到的token数量actualAmountsIn,然后对每个token和数量调用_pullUnderlying函数把token取到arraySmartPool中,最后调用_mintPoolShare_pushPoolShare函数,mint aBPT token并将这些aBPT Token转给msg.sender

注意到arraySmartPool本身是继承了PCToken类,其本身是一个ERC-20 Token的实现,而_mintPoolShare是直接调用_mint函数,后者的实现如下:

可以看出,_mint函数会操纵Token内部的varTotalSupply变量(增加指定的数量),而totalSupply()则会直接返回这个值。因此,每次joinPool都会使这个参数增加。

获利分析

攻击交易及对应的获利

总结

在本次Array Finance攻击事件中,攻击者利用 Array Finance 的计价机制依赖于 aBPT 的totalSupply这一点攻击了 Array Finance. 其攻击原理正是我们早些时候在论文DeFiRanger: Detecting Price Manipulation Attacks on DeFi Applications中揭露的price dependency 问题。

Last updated