图片[1] - Yearn Finance攻击事件分析

然后在 Curve 上,攻击者将 500 万 DAI 兑换成了 695, 000 USDT,并将 350 万 USDC 兑换成 151 USDT:

图片[2] - Yearn Finance攻击事件分析

攻击者调用 IEarnAPRWithPool 的recommend函数来检查当前的 APR。此时,只有 Aave 的 APR 不等于 0 :

图片[3] - Yearn Finance攻击事件分析

接下来,攻击者将 800, 000 USDT 转移到了攻击合约 0x9fcc1409b56cf235d9cdbbb86b6ad5089fa0eb0f 中。在该合约中,攻击者多次调用了 Aave:Lending Pool V1的r**函数,帮助其他人偿还债务,以使 Aave 的 APR 等于 0 :

图片[4] - Yearn Finance攻击事件分析

攻击者调用了 yUSDT 的deposit函数,抵押了 900, 000 USDT,并获得了 820, 000 yUSDT:

图片[5] - Yearn Finance攻击事件分析

接下来,攻击者调用了 bZx iUSDC 的mint函数,使用 156, 000 USDC 铸造了 152, 000 bZx iUSDC,并将其转移到了 Yearn yUSDT:

图片[6] - Yearn Finance攻击事件分析

攻击者调用 Yearn:yUSDT 的withdraw函数,将 820, 000 yUSDT 兑换成 1, 030, 000 USDT。此时,合约中只剩下攻击者转移的 bZx iUSDC:

图片[7] - Yearn Finance攻击事件分析

接下来攻击者调用 Yearn:yUSDT 的rebalance函数,销毁 bZx iUSDC:

图片[8] - Yearn Finance攻击事件分析

然后攻击者向 yUSDT 合约转移了 1/e 6 个 USDT,并调用了deposit函数,抵押了 10, 000 USDT,获得了 1, 252, 660, 242, 850, 000 yUSDT:

图片[9] - Yearn Finance攻击事件分析

然后在 Curve 上,攻击者将 70, 000 yUSDT 兑换成 5, 990, 000 yDAI,将 4 亿 yUSDT 兑换成 4, 490, 000 yUSDC,将 1, 240, 133, 244, 352, 200 yUSDT 兑换成 1, 360, 000 yTUSD:

图片[10] - Yearn Finance攻击事件分析

然后在 yearn: yDAI 和 yearn: yUSDC 中分别调用 withdraw ,提取 678 万 个 DAI 和 562 w 万个 USDC,并归还闪电贷:

图片[11] - Yearn Finance攻击事件分析

图片[12] - Yearn Finance攻击事件分析

可以看到 share 的数量和变量 pool 相关,pool 越小,share 越大,而 pool 的值由 _calcPoolValueInToken 获得:

图片[13] - Yearn Finance攻击事件分析

攻击者在调用 rebalance 函数 后,合约中只存在了 USDC,但是 _balance() 获取的是 USDT 的余额,USDC 的余额并不计入其中,因此此时的 pool 为 1 (攻击者转入的) :

图片[14] - Yearn Finance攻击事件分析

图片[15] - Yearn Finance攻击事件分析

图片[16] - Yearn Finance攻击事件分析

这里显然是项目方的配置错误,yUSDT 合约中 应当都是 USDT 类的代币,但是其 fulcrum 变量却是 USDC 相关的 bZx IUSDC 代币,因此 yUSDT 中的 USDC 不计入 balance 中:

图片[17] - Yearn Finance攻击事件分析

图片[18] - Yearn Finance攻击事件分析

攻击者为什么能调用 rebalance 函数来 burn 掉 bZx iUSDC 代币呢?查看 rebalance 函数的实现:

图片[19] - Yearn Finance攻击事件分析

图片[20] - Yearn Finance攻击事件分析

图片[21] - Yearn Finance攻击事件分析

可以看到在 _withdrawFulcrum() 中会 存在 redeem 和 burn 操作,因此我们需要让 "newProvider != provider" 成立 ,  其中 recommend() 的实现:

图片[22] - Yearn Finance攻击事件分析

攻击者通过控制 IIEarnManager(apr).recommend(token) 的返回值,使其为都为 0 来操控 newProvider:

图片[23] - Yearn Finance攻击事件分析

如何让其都为 0 呢,该函数的返回值和计算出的各个 DeFi 中的 APR 相关,由于 Compound,bZx,dydx 中没有池子,因此只需要控制 Aave ( Aave: Lending Pool Core V1) 即可:

图片[24] - Yearn Finance攻击事件分析

要使其值返回为 0 ,需要让 apr.calculateInterestRates 函数的第一个返回值为 0 :

图片[25] - Yearn Finance攻击事件分析

即让 currentLiquidityRate 为 0 ,该值和 _totalBorrowsStable、_totalBorrowsVariable 相关,当这两个个值都为 0 时,currentLiquidityRate 为 0 :

图片[26] - Yearn Finance攻击事件分析

图片[27] - Yearn Finance攻击事件分析

_totalBorrowsVariable 为 0 ,即 Aave: Lending Pool Core V1 此时没有人存在债务,为了达成这个条件,攻击者将池中所有人的债务进行了 r** :

图片[28] - Yearn Finance攻击事件分析

最后,攻击者让 _totalBorrowsVariable 变为 0 ,所以它能够调用 rebalance 函数 burn 掉 bZx iUSDC 代币:

图片[29] - Yearn Finance攻击事件分析

总结

此次 Yearn 攻击事件的根本原因是项目方的配置错误。攻击者通过一系列精妙的手法利用了该漏洞,最终获利大约 1000 万美元。

关于我们

At Eocene Research, we provide the insights of intentions and security behind everything you know or don't know of blockchain, and empower every individual and organization to answer complex questions we hadn't even dreamed of back then.

了解更多: Website | Medium | Twitter