SlowMist: Detailed Analysis of Balancer Hack (Released in 2020)
On June 28, 2020, the automated market maker service provider Balancer was attacked. After receiving the intelligence, the SlowMist security team conducted a comprehensive analysis of the attack as follows:
Information
Automated Market Maker(AMM):
Balancor is a contract that provides AMM services, that is, automated market maker services. The automated market maker service provider uses the ratio of the number of various tokens in the token pool to determine the price between tokens. Users can obtain the price between tokens through the dynamic ratio between tokens, and then exchange between tokens in the contract.
Deflationary Tokens:
A deflationary token model is one that removes tokens from the market over time. Tokens can be reduced from the market in a number of ways, including token buybacks and token burns by token creators. The protagonist of this attack — the STA token is a deflationary token, which realizes the deflation of the token by burning the balance of the transfer user during the transfer. The main implementation code is as follows (take transfer as an example):
After understanding the above two points, we can start detailed technical analysis.
Detailed Analysis
The transaction for this attack is as follows: https://etherscan.io/tx/0x013be97768b702fe8eccef1a40544d5ecb3c1961ad5f87fee4d16fdc08c78106
We can see that the attacker (0x81d) sent WETH to the Balancer contract (0x0e5) multiple times to exchange STA tokens.
In order to know more specific transaction details, we use the OKO contract browsing tool to analyze the transaction: https://oko.palkeo.com/0x013be97768b702fe8eccef1a40544d5ecb3c1961ad5f87fee4d16fdc08c78106/
By analyzing the specific details in the transaction, it can be found that the attacker frequently calls the swapExactAmountIn function (the above figure lists only a part, and readers can view the specific results through connection access). Next, we analyze the code with the function swapExactAmountIn, the code is as follows:
By analyzing the swapExactAmountIn function, we can know that the main flow of the function is as follows:
1. Obtain the balance of the two tokens for exchange
2. Calculate the price according to the balance of the token, and check whether the price before the transaction is reasonable
3. Calculate the transfer-out quantity of the target exchange token
4. Update the balance of the two tokens for exchange
5. Calculate the exchanged price and check whether the price is reasonable
6. Pull the token that the user uses for exchange, and transfer the target token that the user needs to exchange to the user
By analyzing this function, we did not find too many abnormalities. This is a normal exchange process, but it should be noted that the way to obtain the balance of the two tokens here is not through the balanceOf method, but stored in the _records variable the value of balance to obtain the balance of the specified token, understanding this is helpful for analyzing the next operation of the attacker. Since this is a normal process, why does the attacker need to call this function frequently? Here we will introduce the protagonist of this time — the deflationary token STA.
According to the above analysis, we know that STA will destroy part of the balance of the transferor when transferring money to achieve the purpose of deflation. According to this feature, when the Balancer contract pays STA tokens to users, part of the tokens will be burned, that is, the STA tokens received by the user will be less than expected. According to the transfer code of the STA, the STA balance of the Balancer will be reduced normally, but the STA balance received by the user is the balance after burning. That is to say, when the user performs the exchange, the exchange result is a loss. For example, the attacker uses 1 WETH to exchange STA, assuming that the number of STAs exchanged is 30,000, but due to burning reasons, the Balancer sends only 27,000 STAs to the attacker, that is to say, the attacker uses 1 WETH was exchanged for 30,000 STAs, but only 27,000 STAs were obtained in the end, that is, the transaction lost money this time. For Balancer’s fund pool, the number of WETH has indeed increased by 1, and at the same time, 30,000 STAs have been lost, which will not affect its own exchange algorithm. According to this logic, every time the attacker exchanges STA, he is doing a loss-making business. But the attacker is not stupid, this business must not lose money. So what is the attacker’s ultimate profit method? We need to proceed with the analysis based on the transaction details.
After calling the swapExactAmountIn function 24 times, the attacker has controlled the number of STA in the Balancer to a low point. At this time, the price of STA against WETH is already very high. At this time, the attacker starts to use the swapExactAmountIn function to exchange STA for WETH.
According to the normal process, even though the price for STA to exchange for WETH is already very high, when the attacker uses WETH to exchange for STA, due to the burning mechanism, the attacker does not get the STA with the equivalent value of WETH for exchange, so even if the attacker uses all the exchanged STA to exchange for WETH. Since the exchange process will cause the STA balance in the Balancer to increase, resulting in a decrease in the price of STA exchanged for WETH, the attacker will eventually lose money. The reason why the attacker can profit in the process of exchanging STA for WETH is the gulp function called in the transaction chain. The code of the function is as follows:
It can be seen that the gulp function mainly modifies the balance in the _records variable. As can be seen from the above, _records stores the balance information of the corresponding currency, so calling the gulp function is actually correcting the balance of the corresponding token. So why would the attacker call this function? By observing the number of STAs that the attacker calls swapExactAmountIn in the call chain above, it can be found that the number of incoming STAs is 1. Then, according to the burning mechanism of STA, during the transfer process, the attacker did not actually transfer STA to the Balancer contract.
The 1 STA of the transfer was burned during the transfer. Then we review the swapExactAmountIn function again:
Although the Balancer contract does not receive STA, since the exchange amount is directly obtained from the value passed in by the user, even if STA is not transferred to Balancer, a large amount of WETH can still be exchanged due to the high price of STA for WETH. There is another problem here. Although the Balancer contract did not receive STA, the related entry was recorded in _records (line 42). This will lead to a problem. With the increase of the number of exchanges, the recorded value of STA in the Balancer pool will continue to increase, and the price of STA’s exchange for WETH will gradually decrease. If this continues, it will not be possible to continue to make profits. How to maintain the lowest price for exchange every time? The answer lies in the gulp function.
When the attacker used STA to exchange WETH, the Balancer contract did not actually receive STA due to burning. As a result, although swapExactAmountIn used _records to record the corresponding entry, the real balance of the STA token in the Balancer contract did not change. . When the gulp function is called, since the gulp function obtains the token balance actually held by the contract, it will overwrite the value of inRecord.balance = badd(inRecord.balance, tokenAmountIn) executed when the swapExactAmountIn function was called previously. Then in the next exchange, the attacker can eliminate the impact of the price reduction caused by the increase in the STA token_records record value in the Balancer pool due to the exchange, so that the attacker can always exchange WETH at the highest price to obtain profit.
In addition to WETH, the attacker used the same method to exchange the WBTC, SNX, and LINK tokens in the pool with STA, and cashed out the corresponding tokens through Uniswap, and finally returned WETH to return the 104,000 WETH of the flash loan. At this point the attack is complete.
Complete Attack Process
1. Loan from dYdX
2. Constantly call the swapExactAmountIn function to reduce the number of STAs in the Balancer pool to a low point and push up the price of STAs for other tokens
3. Use 1 STA to exchange WETH, and call the gulp function after each exchange (call the swapExactAmountIn function) to cover the balance of STA, so that the price of STA’s exchange for WETH remains at a high point
4. Use the same method to attack other tokens in the token pool
5. Repay the Flash Loan Loan
6. Profit
Suggestions
This attack is mainly due to the incompatibility problem brought about by deflationary tokens. When users use deflationary tokens for exchange, the contract does not verify the balance of the received deflationary tokens, resulting in balance records mistake. The repair solution is also very simple. During the process of processing the exchange logic, the contract needs to check whether the contract has received the corresponding tokens during the exchange process of the two tokens to be exchanged, so as to ensure that the balance of the tokens is recorded correctly. You can’t just rely on the return value of the transfer in the ERC20 standard to avoid problems caused by errors in token balance records.
About SlowMist
SlowMist is a blockchain security firm established in January 2018. The firm was started by a team with over ten years of network security experience to become a global force. Our goal is to make the blockchain ecosystem as secure as possible for everyone. We are now a renowned international blockchain security firm that has worked on various well-known projects such as Huobi, OKX, Binance, imToken, Crypto.com, Amber Group, Klaytn, EOS, 1inch, PancakeSwap, TUSD, Alpaca Finance, MultiChain, O3Swap, etc.
Website:
https://www.slowmist.com
Twitter:
https://twitter.com/SlowMist_Team
Github:
https://github.com/slowmist/