Analysis of hard core technology | bZx protocol attacked by hackers

On February 15, the bZx team issued an announcement on the official telegram group, saying that a hacker had carried out a vulnerability attack on the bZx protocol and had suspended other functions except lending. As for the details of the attack, bZx officials did not disclose it in detail. PeckShield security personnel proactively followed up the bZx attack incident and found that this incident was an attack on the design of shared composable liquidity among DeFi projects, especially in DeFi projects with leveraged trading and lending capabilities, this issue will be more easily exploited.

Figure 1: Five Arbitrage Steps in bZx Hack

The details of the attack are as follows:

This attack occurred at 2020-02-15 09:38:57 Beijing time (block height # 9484688). Attacker's transaction information can be found on etherscan. This attack process can be divided into the following five steps:

The first step: flash loans to obtain available funds

The attacker borrowed 10,000 ETH by calling the dYdX flash loan function in the deployed contract. This part is the basic borrowing function of dYdX, we will not explain further.

Figure 2: Flashloan Borrowing From dYdX

After the first step, the attacker's assets in the following table have no benefits at this time:

Step 2: Hoard WBTC spot
After obtaining ETH through the first flash loan, the attacker deposited 5,500 ETH into Compound as collateral and lent 112 WBTC. This is also the normal Compound lending operation, and the WBTC loaned out will be sold in the fourth step.

Figure 3: WBTC Hoarding From Compound

After this step, we can see that the assets controlled by the attacker have changed, but still no benefit at this time:

Step Three: Leverage WBTC Price
Use bZx's leverage trading function to short ETH and buy a large amount of WBTC. The specific steps are: the attacker deposits 1,300 ETH and calls the bZx leveraged trading function, namely the interface mintWithEther (), which will continue to call the interface marginTradeFromDeposit () internally. Next, the attacker will get 5,637.62 ETH from 5 times leverage of bZx and exchange it for 51.345576 WBTC through KyberSwap. Please note that short ETH here is 5 times borrowed. This transaction resulted in an increase of the WETH / WBTC exchange rate to 109.8, which is approximately three times the normal exchange rate (~ 38.5 WETH / WBTC). In order to complete this transaction, KyberSwap basically queries its reserves and finds the most favorable exchange rate. Ultimately, only Uniswap can provide such liquidity, so this transaction essentially promoted the WBTC price in Uniswap to triple.

Figure 4: Margin Pumping With bZx (and Kyber + Uniswap)

It should be noted that this step has a security check logic implemented inside the contract, but the lock value is not actually verified after the transaction. In other words, this check was not enabled when the attack occurred, and we will detail the issues in this contract later.

After this step, we noticed the following changes regarding hacked assets. However, there is still no profit after this step.

Step 4: Sell WBTC spot

After the WBTC price soared in Uniswap (the price is 61.4 WETH / WBTC), the attacker sold all 112 WBTC borrowed through Compound in the second step to Uniswap and returned the corresponding WETH. In this transaction, the attacker received a total of 6,871.41 ETH in return. After this step, you can see that the attacker has made a lot of profit.

Figure 5: WBTC Dumping With Uniswap

Step 5: Flash loan repayment
The attacker obtained 6,871.41 ETH from the 112 WBTC sold, and repaid 10,000 ETH of the flash loan to dYdX to complete the flash loan repayment. After this step, we recalculated the following asset details. The results show that the attacker obtained 71 ETH through this attack, plus these two lock positions: Compound (+ 5,500weth / -112WBTC) and bZx (-4,337WETH / + 51WBTC). bZx hedging is in a default state, and Compound's hedging is profitable. Apparently, after the attack, the attackers began to repay Compoud debt (112BTC) to redeem the 5,500 WETH mortgaged. Since the bZx lock position is already in a default state, the attacker is no longer interested.

Referring to the average market price of 1WBTC = 38.5WETH (1WETH = 0.025BTC), if an attacker purchases 112 WBTC at the market price, it will take about 4,300 ETH. This 112 WBTC is used to settle Compond's debt and retrieve 5,500 ETH in collateral . The total profit of the attacker is 71 WETH + 5,500 WETH-4,300 ETH = 1,271 ETH, for a total of approximately $ 355,880 (current ETH price $ 280).

Hard core analysis: bZx can avoid risk code logic flaws

According to the previous steps implemented by the attacker in the contract, it can be seen that the core cause of the problem is to call marginTradeFromDeposit () in the third step to borrow 1,300 ETH and add 5 times the leverage to short the ETH / WBTC transaction, so we further review The contract code was found to be an "avoidable arbitrage opportunity", but the logic of the code that can be used to avoid risks did not take effect because of a logical error in the code. The specific code is tracked as follows:

The first is marginTradeFromDeposit () calling _borrowTokenAndUse (). Here, the fourth parameter is true (line 840) because the leveraged transaction is the deposited asset.

In _borrowTokenAndUse (), when amountIsADeposit is true, _getBorrowAmountAndRate () is called and the borrowAmount is stored in sentAmounts [1] (line 1,348).

On line 1,355, sentAmounts [6] is set to sentAmounts [1] and _borrowTokenAndUseFinal () is called on line 1,370

Enter bZxContract's takeOrderFromiToken () function via the IBZx interface.

bZxContract belongs to another contract iTokens_loanOpeningFunctions So we continue to analyze the contract code and find a key logical judgment in the function:

On line 148, bZx actually attempts to use the shouldLiquidate () of the oracle contract to check if the leveraged position is healthy. However, because the first condition (lines 146 to 147) is already true, execution continues and the logical judgment of shouldLiquidate () is ignored.

In fact, the judgment of getCurrentMarginAmount () <= loanOrder.maintenanceMarginAmount is implemented in the shouldLiquidate () of the contract BZxOracle. If shouldLiquidate () is executed, this attack can be effectively avoided.

As mentioned earlier, this is an interesting attack that combines a variety of interesting features such as loans, leveraged transactions, and price increases. This attack is possible because current projects share a design that combines composable liquidity. In particular, 5x leverage trading allows users to borrow a large number of tokens at a relatively low cost, coupled with the liquidity shared between DeFi projects, resulting in transaction prices that are more easily manipulated.