bZx Hack II Full Disclosure (With Detailed Profit Analysis)
On 2/18, just a few hours after the official hack report was released, the bZx team pauses the protocol again in light of another hack.
PeckShield researchers detected the abnormal transaction pattern with an in-house real-time anomaly detection engine and looked into it immediately. This second hack is different from the first one in that it is indeed an oracle attack. With oracle manipulation, it essentially takes a reverse way to allow for the leakage of supposedly-locked bZx funds directly to an attacker-controlled account, without the further need of absorbing the leaked funds into a Compound position. Specifically, the oracle manipulation substantially drives up the price of the affected token, i.e., sUSD, and makes it extremely valuable in the bZx lending system. The attacker can then simply deposit earlier-purchased or hoarded sUSD as collateral to borrow WETH for profit (instead of selling or dumping). In the above figure, we show the detailed five steps behind this hack: Flashloan Borrow, Hoard, Pump, Collateralized Borrow, Flashloan Repay. Notice the fourth step is Collateralized Borrow, instead of Dump. In the following, we examine each specific step.
Five Exploitation Steps For Profit
The exploit occurs at 2020–02–18 03:13:58 +0000 (Ethereum block height #9504627). The culprit transaction can be found on etherscan. As mentioned earlier, this attack process can be separated into the following steps:
1: Flashloan Borrow. This step basically takes advantage of the bZx flashloan feature to borrow 7,500 ETH. Note that this step is the same as the first step in the first hack, except that it uses bZx instead of dYdX for flashloan. As such, we will not go into the details.
After this step, we notice the attacker has the following asset breakdown. There is no gain yet.
2: Pump. With the flashloan, the exploit swaps 900 ETH in two batches for sUSD through Kyber. The first batch sells 540 ETH in KyberSwap that, after internal consulting of reserves, routes the swap order to the KyberUniswap reserve (0x31e085afd48a1d6e51cc193153d625e8f0514c7f) and gets 92,419 sUSD in return. The second batch sells 18 times of 20 ETH each, also in Kyber that, after internal consulting of reserves, routes the swap orders to the Kyber-sUSD reserve (0x4cb01bd05e4652cbb9f312ae604f4549d2bf2c99) and gets 63,584 sUSD in return. The sell-off of these two batches effectively drives the price of sUSD up to 0.00899 ETH (or 1ETH=111 sUSD). The manipulated price is around 2.5x higher when compared to the average ETH/sUSD market price. After the swap, the attacker acquires 92,419+63,584=156,003 sUSD tokens at her disposal.
After this step, we notice the following changes regarding the attacker-controlled assets. Apparently, there is still no gain yet.
3: Hoard. Next, the attacker turns to Synthetic Depot contract to acquire substantially more sUSD at market price. Note Synthetic Depot contract allows for depositing Ether for sUSD in a fair rate. Our analysis shows that the attacker sends 6,000 ETH and buys 943,837 sUSD back (with 2,482 ETH refunded back as there’s no enough sUSD to buy). Note this step is typically launched before Pump step. For whatever reason, this is not the case in this particular hack (and the ordering does not affect the final result as the pricing in Synthetic Depot is not affected by KyberSwap).
After this step, the attacker owns 1M sUSD tokens, around 20% of the sUSD total supply! We also notice the following changes regarding the attacker-controlled assets: there is no gain materialized yet.
4: Collateralized Borrow. As discussed earlier, the attacker has so far significantly drove up the sUSD/ETH price and has >1M sUSD at her disposal. Note that in the first hack, the attacker takes the approach by capitalizing on the spiked price in a profitable Compound position. Considering the possibly low liquidity of sUSD, the attacker this time takes the approach of first collateralizing the collected >1M sUSD back into bZx and then borrowing from it 6,796 ETH. As bZx relies on Kyber for the price feed, with the spiked sUSD/ETH price, the collection of >1M sUSD allows for the borrow of 6796 ETH. With the normal conversion rate of 1ETH=111 sUSD, the same amount of sUSD tokens can only buy 4,000 ETH, which indicates that this loan is now underwater with insufficient collateralization.
5: Flashloan Repay. With the borrowed 6,796 ETH (plus the 7,500–900–6,000–2,482=3,082 ETH leftover), the attacker is able to repay the 7,500 ETH flashloan back to bZx with the profit of 2,378 ETH.
To recap, within a single transaction, the attacker secures 2,378 ETH profit and leaves an underwater bZx position (+1,099,841 sUSD/-6,796 ETH). Apparently, the attacker shows no further interest in the underwater position and walks away with 2,378 ETH, roughly $665,840 (assuming the ETH price of $280).
In this blog, we did not go through the internal call flows among these DeFi contracts. It is worthwhile to mention that bZx implements an internal safety check that controls the maximum spread possibly introduced from unstable oracle. Interested readers are encouraged to find out the specifics. We’d also like to thank Bloxy for the wonderful tools we used to produce some diagrams in this article.
PeckShield Inc. is an industry leading blockchain security company with the goal of elevating the security, privacy, and usability of current blockchain ecosystem. For any business or media inquiries (including the need for smart contract auditing), please contact us at telegram, twitter, or email.