BSC闪电贷攻击:PancakeBunny

这篇文章详细分析了2021年5月BSC DeFi产品被黑客攻击的事件,重点探讨了PancakeBunny在奖励铸造中存在的漏洞,描述了如何通过操控智能合约及流动性池实现攻击,并深入讲解了该攻击的实现过程和所使用的技术,提供了对漏洞的逐步剖析。

在2021年5月,我们目睹了多个针对BSC DeFi产品的黑客攻击。特别是在收益聚合器PancakeBunny中,与奖励铸造相关的一个漏洞被利用,从而从无到有铸造了约700万的BUNNY代币,导致了4500万美元的经济损失。黑客攻击后,三个派生项目——AutoShark、Merlin Labs和PancakeHunny,均使用类似技术被攻击。以下我们深入探讨这个漏洞,并通过重现攻击PancakeBunny来详细说明这一过程。

隐藏攻击面: balanceOf()

许多人认为,可组合性是DeFi成功的关键。代币合约(例如ERC20)在DeFi乐高的底层发挥着重要作用。然而,开发者在将ERC20集成到DeFi项目时,可能会忽略一些不可控和不可预测的条件。例如,在检索当前代币余额时,你无法预测何时以及将获得多少代币。这种不确定性创造了一个隐藏的攻击面。

在许多情况下,智能合约在其业务逻辑中引用ERC20的余额。例如,当用户将一些XYZ代币存入智能合约时,会调用XYZ.balanceOf()来检查接收到多少资金。如果你熟悉Uniswap代码库,你可能知道UniswapV2Pair合约有多个balanceOf()的调用。

在代码片段中,UniswapV2Pair.mint()使用当前余额(balance0,balance1)和记账数据(amount0,amount1)来推导用户存入的数量(amount0,amount1)。然而,如果恶意操作者在mint()调用之前转移一些资产(token1或token2),受害者可能会提供比预期更多的流动性,即铸造更多的LP代币。如果奖励是基于LP代币的数量计算的,恶意操作者可以在奖励超过支出时获利。

UniswapV2Pair.burn()具有类似的风险。mint()函数的调用者可能在没有充分了解相关风险的情况下让自己处于危险之中。这正是PancakeBunny案例中发生的事情。

在上面的代码片段中,第140行通过balanceOf()检索LP代币的余额并将其存储到liquidity中。在第144-145行中,UniswapV2Pair拥有的总LP代币的部分(即流动性除以_totalSupply)用于基于两个资产的当前余额(即token0和token1)推导(amount0,amount1)。稍后,这两个资产的(amount0,amount1)在第148-149行被转移到地址中。

在这里,恶意操作者可以通过在mint()函数调用之前向UniswapV2Pair合约发送一些token0+token1或LP代币来操控(balance0,balance1)和流动性,从而使调用者获得更多的token0+token1。我们将带你了解PancakeBunny源代码,并向你展示恶意操作者如何从中获利。

漏洞分析: BunnyMinterV2

在PancakeBunny源代码中,BunnyMinterV2.mintForV2()函数负责铸造作为奖励的BUNNY代币。具体来说,待铸造数量(即mintBunny)由输入参数:__withdrawalFees_performanceFee推导得出。该计算与三个函数相关:_zapAssetsToBunnyBNB()_(第213行)、priceCalculator.valueOfAsset(第219行)和amountBunnyToMint(第221行)。由于恶意操作者能够铸造大量BUNNY,问题出在上述三个函数中的一个。

让我们从_zapAssetsToBunnyBNB()函数开始。当传入的资产是Cake-LP(第267行)时,使用一定数量的LP代币来移除流动性并从流动性池中获取(token0, token1)的(amountToken0, amountToken1)(第278行)。借助zapBSC合约,这些资产被交换为BUNNY-BNB LP代币(第287-288行)。然后,将相应数量的BUNNY-BNB LP代币返回给调用者(第298行)。在这里,我们遇到一个问题。金额是否与你假定被销毁的LP代币数量匹配?

在PancakeV2Router.removeLiquidity()的实现中,裤代币的流动性(来自zapAssetsToBunnyBNB()的amount)将被发送到PancakePair合约(第500行)并调用PancakePair.burn()。如果PancakePair当前的LP代币余额大于0,则实际被销毁的数量将大于amount,这也间接增加了待铸造的BUNNY数量。

__zapAssetsToBunnyBNB()中的另一个问题是zapBSC.zapInToken()调用。 其逻辑是将通过removeLiquidity()收集的两种资产交换为BUNNY-BNB LP代币。由于zapBSC通过PancakeSwap交换资产,恶意操作者可以使用闪电贷来操控交换的BUNNY-BNB数量。

回到BunnyMinterV2.mintForV2(),从zapAssetsToBunnyBNB()返回的bunnyBNBAmount将被传递到priceCalculator.valueOfAsset()中,用于基于BNB(即valueInBNB)引用价值,类似于预言机机制。

然而,priceCalculator.valueOfAsset()引用了BUNNY_BNB PancakePair中的BNB和BUNNY(reserve0, reserve1)的数量作为价格源,这使得恶意操作者可以利用闪电贷操控铸造的BUNNY代币数量。

amountBunnyToMint()函数是一个简单的数学计算。输入贡献被乘以五(bunnyPerProfitBNB = 5e18),这本身没有攻击面,但放大了上述操控的效果。

准备攻击

由于攻击是通过getReward()触发的,因此我们首先需要获得奖励的资格。

如上面的Etherscan截图所示,PancakeBunny黑客调用了利用合约的init()函数,将1 WBNB兑换为WBNB-USDT-LP代币并存入VaultFlipToFlip合约,以便通过调用getReward()获得一些奖励。

如上所示,利用Exp.prepare()函数我们重现了vaultFlipToFlip.deposit()调用(第62行)。我们还使用了ZapBSC合约来简化获取LP代币的过程(第54-57行)。但是,直到PancakeBunny守护者触发下一次harvest()调用之前,用户是无法获得奖励的。因此,PancakeBunny黑客并没有在init()交易之后立即触发攻击,而是等到第一次harvest() 交易后才触发。

在我们的模拟中,没有守护者能够触发harvest()。因此,我们利用eth-brownie的特性来模拟守护者,并手动启动harvest()交易(第25行)。

递归闪电贷

为了利用资金,PancakeBunny攻击者利用了包括七个PancakePair合约和ForTube Bank在内的八个不同资金池。在这里,Amber的区块链安全团队仅使用了以下七个PancakePair合约的闪电换特性借入2.3M WBNB:

address[7] pairs = [\
address(0x0eD7e52944161450477ee417DE9Cd3a859b14fD0),\
address(0x58F876857a02D6762E0101bb5C46A8c1ED44Dc16),\
address(0x74E4716E431f45807DCF19f284c7aA99F18a4fbc),\
address(0x61EB789d75A95CAa3fF50ed7E47b96c132fEc082),\
address(0x9adc6Fb78CEFA07E13E9294F150C1E8C1Dd566c0),\
address(0xF3Bc6FC080ffCC30d93dF48BFA2aA14b869554bb),\
address(0xDd5bAd8f8b360d76d12FdA230F8BAF42fe0022CF)\
];

为了简化闪电换调用,我们将两个参数打包到PancakePair.swap()调用的第四个输入参数中(第72行或第74行):level和asset。level变量指示我们当前在swap()调用的哪个层级;而asset变量为0或1,表示我们需要借入token0或token1。

使用回调函数pancakeCall(),我们递归调用PancakePair.swap(),直到达到第七层级。在顶层,我们调用shellcode()执行实际操作(第98行)。当shellcode()返回时,asset变量将返回每个对应层级借入的资产(第102-104行)。

拉动扳机

由第七层pancakeCall()调用的shellcode()函数是实际的攻击代码。首先,我们在wbnbAmount(第108行)中保存当前的WBNB余额,将15,000 WBNB交换成WBNB-USDT-LP代币(第112行),并在第113行将其发送到已铸造这些LP代币的合约(即PancakePair合约)。此步骤旨在操控_\zapAssetsToBunnyBNB()函数中的removeLiquidity()调用,正如前面分析的那样,使我们能够获得超过预期的WBNB+USDT。

第二步是操控由\zapAssetsToBunnyBNB()引用的USDT价格,将USDT交换为WBNB。由于\zapAssetsToBunnyBNB()使用WBNB-USDT PancakePair将USDT交换为WBNB,我们可以在PancakeSwap上将其余借来的WBNB交换为USDT。 这样做将使WBNB变得极为便宜,对于通过USDT交换获得的WBNB的数量将极为不成比例。请注意,此处的价格操控发生在Pancake V1池中,而不是之前步骤中提到的Pancake V2的PancakePair中。

最后一步是getReward()调用。 这个简单的合约调用可以铸造690万BUNNY代币(第125行)。然后,这些BUNNY代币可以在PancakeSwap上交换为WBNB,以偿还闪电贷。

在我们的模拟中,恶意操作者支付了1 WBNB,最终获利104k WBNB + 3.8M USDT(相当于约4500万美元)。

免责声明

此帖中包含的信息(“ 信息 ”)仅为信息目的而准备,以摘要形式呈现,并不构成完整。该信息不是,也无意成为,出售任何证券的要约或对购买请求的招揽。

该信息并不提供,也不应被视为投资建议。该信息未考虑特定的投资目标、财务状况或任何潜在投资者的特定需求。对于该信息的公正性、正确性、准确性、合理性或完整性,均不作任何明示或暗示的陈述或保证。我们不承诺更新该信息。潜在投资者不应将其视为替代他们自己判断或研究的替代品。潜在投资者应根据需要咨询各自的法律、监管、税务、业务、投资、财务和会计顾问,并根据他们的判断及认为必要的顾问的建议作出投资决策,而不是基于本文中表达的任何见解。

  • 原文链接: medium.com/amber-group/b...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
ambergroup
ambergroup
江湖只有他的大名,没有他的介绍。