Array Finance 攻击策略
引用知乎报告
Last updated
Was this helpful?
引用知乎报告
Last updated
Was this helpful?
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是Array Collater的地址 (0xa800cda5),arraySmartPool.totalSupply() (就相当于aBPT token的totalSupply)的值会随着攻击者不断将FlashLoan借入的资金填充进Array Collater里而不断增加。
通过深入分析整个交易,我们发现在下面的内部交易中arraySmartPool.totalSupply() (相当于aBPT token的totalsupply)的值由于攻击者借用FlashLoan向池子中注入资金而不断增加:
其中后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 问题。