I. Preface
A wave of Ethereum CVE has been audited recently. The vulnerabilities mentioned in this chapter include many problems, not only in code, but also in finance due to function design. In addition to analyzing the loopholes, I also think about the problems in this paper, and share my thoughts with the readers.
This vulnerability is only a representative of this type of vulnerability, and there are many similar contracts related to it. Here, only the most representative one is used for analysis and demonstration.
First of all, we need to briefly introduce the relevant background. This article is a kind of project represented by polyai and substratum. The substratum project is mainly to build a blockchain based network. In short, it is to reconstruct the current Internet services, including DNS, network space storage, etc. to solve the problem of "international network blockade", which is completely customized for China, and can solve the problem that domestic users need to access foreign websites through VPN services. From the perspective of the official website, developers should be familiar with China's national conditions. The Chinese language of the website and the white paper has made a lot of efforts, which proves that the development team attaches great importance to the Chinese market.
Poly AI focuses on the artificial intelligence based on deep learning. Google, apple and Facebook have recently made great contributions in this field. Poly AI uses blockchain technology to solve the problem of computing consumption and data source in the process of deep learning and training, and blockchain also provides more security than all traditional technologies.
These currencies are very practical and support a certain field.
2、 The problem
This type of application includes three types of problems. First of all, it can be over minted.
In order to facilitate the administrator to manage the application, the designer added the interface of issuing currency when designing this kind of application. The user of such issuing interface is owner and can increase the number of its own issuing currency without limitation.
function mintToken(address target, uint256 mintedAmount) onlyOwner {
balanceOf[target] += mintedAmount;
totalSupply += mintedAmount;
Transfer(0, this, mintedAmount);
Transfer(this, target, mintedAmount);
}
This function has two parameters, including the target address and the number of coins issued. When the owner calls this function, it can pass in the address of the pending wallet and add any balance to the wallet account. This method seems to increase the flexibility of the contract and be able to modify various variables in the contract more autonomously, but we all know that for the currency, this kind of over issuance without cost will lead to various problems. Although this kind of function is used by the owner, if the malicious owner exists, he can increase the amount of account currency arbitrarily, and when the amount increases, the market value of currency will be affected.
Secondly, there are serious loopholes in this type of contracts, that is, multiplication overflow.
Here are two examples.
First let's look at the functions in the substratum contract.
Substratum
In the key transfer function of the contract, the designer makes a judgment on the overflow.
function transfer(address _to, uint256 _value) {
if (balanceOf[msg.sender] < _value) throw; // Check if the sender has enough
if (balanceOf[_to] + _value < balanceOf[_to]) throw; // Check for overflows
if (frozenAccount[msg.sender]) throw; // Check if frozen
balanceOf[msg.sender] -= _value; // Subtract from the sender
balanceOf[_to] += _value; // Add the same to the recipient
Transfer(msg.sender, _to, _value); // Notify anyone listening that this transfer took place
}
That is, if (balanceof [_to] + _value < balanceof [_to]) throw; when the transfer amount overflows, the throw operation is performed.
if (balanceOf[_to] + _value < balanceOf[_to]) throw;
However, there is no overflow judgment in the later sell function.
function mintToken(address target, uint256 mintedAmount) onlyOwner {
balanceOf[target] += mintedAmount;
totalSupply += mintedAmount;
Transfer(0, this, mintedAmount);
Transfer(this, target, mintedAmount);
}
function freezeAccount(address target, bool freeze) onlyOwner {
frozenAccount[target] = freeze;
FrozenFunds(target, freeze);
}
function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner {
sellPrice = newSellPrice;
buyPrice = newBuyPrice;
}
function buy() payable {
uint amount = msg.value / buyPrice; // calculates the amount
if (balanceOf[this] < amount) throw; // checks if it has enough to sell
balanceOf[msg.sender] += amount; // adds the amount to buyer's balance
balanceOf[this] -= amount; // subtracts amount from seller's balance
Transfer(this, msg.sender, amount); // execute an event reflecting the change
}
function sell(uint256 amount) {
if (balanceOf[msg.sender] < amount ) throw; // checks if the sender has enough to sell
balanceOf[this] += amount; // adds the amount to owner's balance
balanceOf[msg.sender] -= amount; // subtracts the amount from seller's balance
if (!msg.sender.send(amount * sellPrice)) { // sends ether to the seller. It's important
throw; // to do this last to avoid recursion attacks
} else {
Transfer(msg.sender, this, amount); // executes an event reflecting on the change
}
}
}
In the above function, we can see that the user can execute the buy() function and pass in a certain amount, and then convert it to the corresponding specific token quota through the buyprice preset by the owner, and then increase the user balance. In the sell function, we can see that the contract only judges whether the user balance is sufficient. When it is sufficient, the user can sell tokens. The total amount of contract address tokens increases and the user balance decreases. However, there is a problem in executing msg.sender.send (amount * sellprice). Because amount and sellprice are settable and the contract does not detect overflow, there is a very serious vulnerability here. If the owner is an evil node, or sellprice is really very large, and because it is willing to multiply, when the amount reaches a certain amount, the amount* Sellprice will increase to a very large amount. When the amount exceeds the uint256 limit, it will overflow. At this time, for users, it costs a lot of money but does not get a return, which is very unfriendly.
buy()
sell
msg.sender.send(amount * sellPrice)
amount * sellPrice
In contrast, users can also exploit similar vulnerabilities to do evil. For example, SEC token:
SEC
function buy() payable returns (uint256 amount){
if(!usersCanTrade && !canTrade[msg.sender]) revert();
amount = msg.value * buyPrice; // calculates the amount
require(balanceOf[this] >= amount); // checks if it has enough to sell
balanceOf[msg.sender] += amount; // adds the amount to buyer's balance
balanceOf[this] -= amount; // subtracts amount from seller's balance
Transfer(this, msg.sender, amount); // execute an event reflecting the change
return amount; // ends function and returns
}
//user is selling us grx, we are selling eth to the user
function sell(uint256 amount) returns (uint revenue){
require(!frozen[msg.sender]);
if(!usersCanTrade && !canTrade[msg.sender]) {
require(minBalanceForAccounts > amount/sellPrice);
}
require(balanceOf[msg.sender] >= amount); // checks if the sender has enough to sell
balanceOf[this] += amount; // adds the amount to owner's balance
balanceOf[msg.sender] -= amount; // subtracts the amount from seller's balance
revenue = amount / sellPrice;
require(msg.sender.send(revenue)); // sends ether to the seller: it's important to do this last to prevent recursion attacks
Transfer(msg.sender, this, amount); // executes an event reflecting on the change
return revenue; // ends function and returns
}
We can see that there is a similar multiplication overflow problem in this contract. In the buy () function, we can find that the contract judges the legitimacy of the user in advance and calculates the purchase amount. The purchase amount here uses the function: amount = MSG. Value * buyprice. That is, the number of tokens passed in * the purchase amount. When the user needs to perform a large number of purchase requests, the amount change will overflow to obtain a low token quantity with a high amount.
buy()
amount = msg.value * buyPrice
We will demonstrate the specific operation process in the next chapter.
In addition to the multiplication overflow vulnerability, the contract also has unreasonable function design, such as setting the amount of token purchase and sale in the contract.
function setPrices(uint256 newSellPrice, uint256 newBuyPrice) onlyOwner {
sellPrice = newSellPrice;
buyPrice = newBuyPrice;
}
For example, the above code allows the owner to set the token purchase price and selling price. As we know, for tokens, the price of money needs to be defined through the market, and to a large extent, it needs to avoid artificial setting.
In the contract, we find that the user purchases and sells tokens with corresponding functions, and this function uses the price defined by the previous owner. There will be a very big problem. First of all, we will assume that when the owner does evil, he will maliciously increase or reduce the unit price of buying or selling tokens, which will lead to market price confusion; second, we can assume that the owner does not do evil, but as long as there is the unit price of buying or selling tokens in the contract, then there will be other problems. For example:
We know that the token used by Ethereum also has a corresponding exchange rate in the market, that is, the corresponding token is exchanged for Ethereum. However, there is a second way of exchange. This also leads to the existence of which exchange is more appropriate. For example:
- When the contract is sold > market purchase price, we can buy in the market and sell in the contract.
When the contract is sold > market purchase price, we can buy in the market and sell in the contract.
- 2 when the contract purchase price is less than the market selling price, we purchase the token through the contract and sell the token to the market.
2 when the contract purchase price is less than the market selling price, we purchase the token through the contract and sell the token to the market.
So we can use the above two methods to carry out circular arbitrage. The specific method is shown in the figure:
3、 Vulnerability simulation
We will simulate the overflow process in this chapter. In this simulation, we use the test environment and two wallet addresses for interactive testing. First, we deploy the contract:
We use the account with test environment and address 0xca35b7d915458ef540ade6068dfe2f44e8fa733c. The initial assignment to msg.sender account balance is 100000.
0xca35b7d915458ef540ade6068dfe2f44e8fa733c
Then we simulate the evil owner and try to modify various token parameters.
First, we set the token purchase amount to 100. The sales amount is 57896044618658097711785492504395399266349923328202820197287920039564819969. Why set this number? We calculated that 0xfff...... FFF (32 pieces) is 11579208923731619542357098500868790785326998466405640394575840079129639935, while 115792089331619542357098500868790785326998466405640394575840079129639935 / 2 = 5789604461865809771178549250439592663499233202820197287920039564819967. So we set the end number to 9 for our convenience.
115792089237316195423570985008687907853269984665640564039457584007913129639935/2 = 57896044618658097711785492504343953926634992332820282019728792003956564819967
We can see that after taking the extreme situation here, sellprice is as shown in the figure below:
After that, we call the sell function to sell two tokens. Due to the lack of overflow check, there will be overflow problems. So we can view the log records.
When selling 2token, we should get 2 * 5789604461865809771178549250439539926634992332820282019728792003956564819969 ether coins, but here we only get 2 Wei ether coins. Thus, users are wronged.
2*57896044618658097711785492504343953926634992332820282019728792003956564819969
2 wei
Similarly, this overflow is applicable to the following code, and the test can achieve the same effect. There will be no more demonstrations.
function buy() payable returns (uint256 amount){
if(!usersCanTrade && !canTrade[msg.sender]) revert();
amount = msg.value * buyPrice; // calculates the amount
require(balanceOf[this] >= amount); // checks if it has enough to sell
balanceOf[msg.sender] += amount; // adds the amount to buyer's balance
balanceOf[this] -= amount; // subtracts amount from seller's balance
Transfer(this, msg.sender, amount); // execute an event reflecting the change
return amount; // ends function and returns
}
There are code level and design problems in this contract. At present, the hidden danger is not very big, but there is the possibility of making mistakes. Because blockchain believes in the concept of "code is law", there should be no such hidden trouble.
4、 References
- https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-12082
- https://blog.peckshield.com/2018/06/11/tradeTrap/
https://blog.peckshield.com/2018/06/11/tradeTrap/
- https://etherscan.io/address/0x5121e348e897daef1eef23959ab290e5557cf274
https://etherscan.io/address/0x5121e348e897daef1eef23959ab290e5557cf274
- https://etherscan.io/address/0x12480e24eb5bec1a9d4369cab6a80cad3c0a377a
https://etherscan.io/address/0x12480e24eb5bec1a9d4369cab6a80cad3c0a377a
This is an original manuscript. Please indicate the source of reprint. Thank you.