In Could 2021, we witnessed a number of hacks concentrating on BSC DeFi merchandise. Specifically, a loophole associated to reward minting within the yield aggregator, PancakeBunny, was exploited to mint ~7M BUNNY tokens from nothing, resulting in a whopping $45M monetary loss. After the bloody hack, three forked tasks — AutoShark, Merlin Labs, and PancakeHunny — had been attacked with comparable methods. Amber Group’s Blockchain Safety group, led by Dr. Chiachih Wu, elaborates on the loophole and offers a step-by-step account of the exploit by reproducing the assault in opposition to PancakeBunny.
Hidden Assault Floor: balanceOf()
Many individuals imagine that composability is essential to the success of DeFi. Token contracts (e.g., ERC20s) play a necessary position on the underside layer of DeFi legos. Nonetheless, builders could overlook some uncontrollable and unpredictable circumstances when integrating ERC20s into their DeFi tasks. For instance, you possibly can’t predict when and what number of tokens you’ll obtain while you retrieve the present token steadiness. This uncertainty creates a hidden assault floor.
In lots of circumstances, good contracts reference the balances of ERC20s of their enterprise logic. For instance, when a person deposits some XYZ tokens into the good contract, XYZ.balanceOf() is invoked to test how a lot cash is acquired. Should you’re accustomed to the Uniswap codebase, you most likely know that the UniswapV2Pair contract has many balanceOf() calls.
Within the code snippet, UniswapV2Pair.mint() makes use of the present balances (balance0, balance1) and the book-keeping knowledge (amount0, amount1) to derive the quantities deposited by the person(amount0, amount1). Nonetheless, if a foul actor transfers some property (token1 or token2) proper earlier than the mint() name, the sufferer would offer extra liquidity than anticipated, i.e., extra LP tokens are minted. If the rewards are calculated primarily based on the quantity of LP tokens, the unhealthy actor can revenue when the rewards exceed the bills.
The UniswapV2Pair.burn() has an analogous danger. The mint() operate caller would possibly jeopardize himself with no thorough understanding of the dangers concerned. That is what occurred within the case of PancakeBunny.
Within the code snippet above, line 140 retrieves the steadiness of LP token by way of balanceOf() and shops it into liquidity. In strains 144–145, the portion of whole LP tokens owned by UniswapV2Pair (i.e., liquidity out of _totalSupply) is used to derive (amount0, amount1) with the present balances (balance0, balance1) of the 2 property (i.e., token0 and token1). Afterward, (amount0, amount1) of the 2 property are transferred to the handle in strains 148–149.
Right here, a foul actor may manipulate (balance0, balance1) and the liquidity by sending some token0+token1 or the LP token into the UniswapV2Pair contract proper earlier than the mint() operate is invoked to make the caller get extra token0+token1 out. We’ll stroll you thru the PancakeBunny supply code and present you ways the unhealthy actor can revenue from doing this.
Loophole Evaluation: BunnyMinterV2
Within the PancakeBunny supply code, the BunnyMinterV2.mintForV2() operate is accountable for minting BUNNY tokens as rewards. Particularly, the quantity to be minted (i.e., mintBunny)is derived from the enter parameters, _withdrawalFees, and _performanceFee. The computation is said to 3 features: _zapAssetsToBunnyBNB() (line 213), priceCalculator.valueOfAsset() (line 219) and amountBunnyToMint() (line 221). For the reason that unhealthy actor can mint a considerable amount of BUNNY, the issue lies in one of many three features talked about above.
Let’s begin from the _zapAssetsToBunnyBNB() operate. When the passed-in asset is a Cake-LP (line 267), a certain quantity of LP tokens is used to take away liquidity and take (amountToken0, amountToken1) of (token0, token1) from the liquidity pool (line 278). With the assistance of the zapBSC contract, these property are swapped for BUNNY-BNB LP tokens (strains 287–288). A corresponding quantity of BUNNY-BNB LP tokens is then returned to the caller (line 298). Right here, we now have an issue. Does the quantity match the quantity of LP tokens you assume to be burned?
Within the implementation of PancakeV2Router.removeLiquidity(), liquidity of LP tokens (quantity in zapAssetsToBunnyBNB()) can be despatched to the PancakePair contract (line 500) and PancakePair.burn() can be invoked. If the present LP token steadiness of PancakePair is larger than 0, the precise quantity to be burned can be better than quantity, which not directly will increase the BUNNY quantity to be minted.
One other challenge in _zapAssetsToBunnyBNB() is the zapBSC.zapInToken() name. The logic behind that is to change the 2 property collected by the removeLiquidity() into BUNNY-BNB LP tokens. Since zapBSC swaps property by way of PancakeSwap, the unhealthy actor may use flash loans to control the quantity of swapped BUNNY-BNB.
Again to BunnyMinterV2.mintForV2(), the bunnyBNBAmount returned by zapAssetsToBunnyBNB() can be handed into priceCalculator.valueOfAsset() to cite the worth primarily based on BNB (i.e., vauleInBNB), just like an oracle mechanism.
Nonetheless, priceCalculator.valueOfAsset() references the quantity of BNB and BUNNY (reserve0, reserve1) within the BUNNY_BNB PancakePair as the value feed, which permits the unhealthy actor to make use of flash loans to control the quantity of BUNNY tokens minted.
The amountBunnyToMint() operate is a straightforward math calculation. The enter contribution is multiplied by 5 (bunnyPerProfitBNB = 5e18), which itself has no assault floor, however the amplification magnifies the manipulation talked about above.
Put together for Fight
For the reason that assault is triggered by getReward(), we have to be certified for rewards first.
As proven within the Etherscan screenshot above, the PancakeBunny hacker invoked the init() operate of the exploit contract to change 1 WBNB to WBNB-USDT-LP tokens and deposit() them into the VaultFlipToFlip contract, such that he would get some rewards by invoking getReward().
As proven above, utilizing the Exp.put together() operate we reproduced the vaultFlipToFlip.deposit() name (line 62). We additionally used the ZapBSC contract to simplify acquiring LP tokens (strains 54-57). Nonetheless, one isn’t capable of get rewards till the PancakeBunny keeper triggers the following harvest() name. For that reason, the PancakeBunny hacker didn’t set off the assault till the primary harvest() transaction following the init() transaction.
In our simulation, no keeper can set off the harvest(). Subsequently, we leverage the function of eth-brownie to impersonate the keeper and manually provoke the harvest() transaction (line 25).
Recursive Flash Loans
To leverage funds, the PancakeBunny exploiter utilized eight totally different fund swimming pools together with seven PancakePair contracts and the ForTube Financial institution. Right here, Amber Group’s Blockchain Safety group solely used the next seven PancakePair contracts’ flash-swap function to mortgage 2.3M WBNB:
handle pairs = [ address(0x0eD7e52944161450477ee417DE9Cd3a859b14fD0), address(0x58F876857a02D6762E0101bb5C46A8c1ED44Dc16), address(0x74E4716E431f45807DCF19f284c7aA99F18a4fbc), address(0x61EB789d75A95CAa3fF50ed7E47b96c132fEc082), address(0x9adc6Fb78CEFA07E13E9294F150C1E8C1Dd566c0), address(0xF3Bc6FC080ffCC30d93dF48BFA2aA14b869554bb), address(0xDd5bAd8f8b360d76d12FdA230F8BAF42fe0022CF) ];
To simplify the flash-swap calls, we packed two parameters into the fourth enter argument of the PancakePair.swap() calls (line 72 or line 74): stage and asset. The extent variable signifies which stage of swap() name we’re in; the asset variable is Zero or 1, that means we have to borrow token0 or token1.
Utilizing the callback operate pancakeCall(), we recursively name PancakePair.swap() with stage+1 till we attain the seventh stage. On the high stage, we invoke shellcode() to carry out the actual motion in line 98. When shellcode() returns, the asset variable returns the borrowed asset in every corresponding stage (strains 102–104).
Pull the Set off
The shellcode() operate invoked by the seventh stage of pancakeCall() is the precise exploit code. First, we maintain the present steadiness of WBNB in wbnbAmount (line 108), swap 15,000 WBNB into WBNB-USDT-LP tokens (line 112), and ship them to the contract which minted these LP tokens (i.e., the PancakePair contract) in line 113. This step goals to control the removeLiquidity() name contained in the _zapAssetsToBunnyBNB() operate as analyzed above, enabling us to obtain extra WBNB+USDT than anticipated.
The second step is to control the USDT value referenced by _zapAssetsToBunnyBNB() to swap USDT into WBNB. Since _zapAssetsToBunnyBNB() makes use of WBNB-USDT PancakePair to swap USDT to WBNB, we may swap the remainder of the flash loaned WBNB to USDT on PancakeSwap. Doing so would make WBNB extraordinarily low cost, and _zapAssetsToBunnyBNB() would obtain a disproportionately great amount of WBNB when swapped from USDT. Notice that the value manipulation right here happens on the Pancake V1 pool, not Pancake V2’s PancakePair as within the earlier step.
The ultimate step is the getReward() name. The straightforward contract name may mint 6.9M BUNNY tokens (line 125). The BUNNY tokens may then be swapped for WBNB on PancakeSwap to pay again the flash mortgage.
In our simulation, the unhealthy actor pays 1 WBNB and walks away with 104ok WBNB + 3.8M USDT (equal to ~$45M).
About Amber Group
Amber Group is a number one world crypto finance service supplier working around the globe and across the clock with a presence in Hong Kong, Taipei, Seoul, and Vancouver. Based in 2017, Amber Group companies over 500 institutional shoppers and has cumulatively traded over $500 billion throughout 100+ digital exchanges, with over $1.5 billion in property below administration. In 2021, Amber Group raised $100 million in Sequence B funding and have become the most recent FinTech unicorn valued over $1 billion. For extra info, please go to: www.ambergroup.io.