TRON
Published in

TRON

Smart Contract Security Class (№010)

This class will discuss the problem of create2 and give an example of exploiting this vulnerability. At the same time, everyone is welcome to follow @tron official twitter, and actively submit the contract code.

The following is the contract code from https://troneye.com (after referred to as TRON-Eye). TRON-Eye is a TRON verification platform from the community. The previous classes have introduced the TRON-Eye verification platform in detail. This contract is ShipperConfirmed(contract address: TWxpJVxSJ2VFNZQaiffLXcuAQbx4yPMarA)

Let’s talk about how to generate smart contract addresses in tron. Currently there are two ways:

1. When deploying a contract using deployContract (a transaction type), an address is generated with the formula:

Where

is ​​the fixed prefix of tron address 0x41,

is the current transaction hash, and

is the address of the contract deployer.

2. When creating a contract using the create directive in solidity code, the address generation formula is:

Where

is ​​the fixed prefix of tron address 0x41,

is the current transaction hash, and

is the number of external calls that the current contract (the contract where the create instruction is located) has been made.

The create2 directive was added to the tron solidity compiler v0.5.4 release, and also supported in java-tron Odyssey-v3.6.0. In this way, there is another way to generate a contract address in tron. The formula is:

Where

is ​​the fixed prefix of tron address 0x41,

is the msg.sender of the current contract (the contract where the create2 is located), salt is a customizable salt value,

is the original code of the new deployment contract, and is the same as the code in the deployContract, including the constructor and initialization parameters.

One thing to note is that the

is different from the bytecode that is stored in the smart contract. The stored bytecode does not include the constructor and initialization parameters.

Using the create2 directive is also very simple, the example is:

1 pragma solidity ^0.5.0;

2 contract Factory {

3 event Deployed(address addr, uint256 salt);

4 function deploy(bytes memory code, uint256 salt) public {

5 address addr;

6 assembly {

7 addr := create2(0, add(code, 0x20), mload(code), salt)

8 if iszero(extcodesize(addr)) {

9 revert(0, 0)

10 }

11 }

12 emit Deployed(addr, salt);

13 }

14 }

Why do we need create2 instruction?

Create2 provides a method to determine the contract address in advance, and the create instruction can’t do it, because the transaction information corresponding to txHash contains the timestamp, which means that txHash will change with time. After a certain time, the transaction will expire and cannot be on chain. The create2 instruction ensures that as long as address, salt, and init_code are the same, the address must be the same. This attribute is critical for state channel. For example, if the participants in the off-chain calculation do not have a dispute, they do not need a contract to resolve the dispute. When a dispute arises and needs to be submitted to the tron ​​to determine who is right and who is wrong, the dispute settlement contract is required.create2 is very useful. Participants can negotiate the address of this contract in advance, and do a series of operations off-chain based on this address. When there is a dispute, submit the contract. This greatly reduces the deployment of meaningless contracts, allowing the witnesses and fullnodes to handle more useful transactions and saving energy consumption.

However, this way of deploying contracts at a predetermined address can also pose some security risks, especially when the contract created with the create2 has the selfdestruct. The main safety implications are threefold:

1. After the selfdestruct, all storage state variables and balances will be cleared to zero. Then when you re-use the create2 instruction to deploy the contract, it is equivalent to add a function which resets all the variables of the contract, but this is not the result we hope to see, or it will affect our judgment on the security of smart contracts. The ShipperConfirmed we mentioned above is an example of this.

2. Init_code is not equivalent to the bytecode stored in the smart contract in the chain, using global variables provided by smart contracts (such as msg.value, blockhash, etc.) or external variables (such as stored in other contracts) to decide to use different Initialize parameters to set different states of the contract. In this case, although the contract address is the same, but the redeploy after the selfdestruct, the state of the contract is unknowingly modified.

3. As with point 2, init_code is not equivalent to the bytecode stored in the smart contract on the chain, and external byte variables can be used to deploy different bytecodes.

The root cause of the security hazard is that, without considering the hash collision, there are only three statuses of a contract before the create2 instruction: 1. not exist, 2. exist, 3. self-destruction. But after the create2 instruction, the status of a contract becomes four: 1. not exist, 2. exist, 3. self-destruct, 4. redeploy. If the user is aware of this, or follow the previous ideas, then it is likely to be cheated. Let’s start with the discussion of the first case with ShipperConfirmed. The source code is:

1 pragma solidity ^0.5.0;

2 contract ShipperConfirmed {

3 address owner;

4 address public shipper;

5 bool public isConfirmed = false;

6 event Confirmed();

7 constructor() public {

8 owner = msg.sender;

9 }

10 modifier onlyOwner {

11 require(msg.sender == owner);

12 _;

13 }

14 function setShipper(address newShipper) external {

15 if (shipper == address(0)) {

16 shipper = newShipper;

17 }

18 }

19 function confirmShip() external {

20 if (msg.sender == shipper) {

21 isConfirmed = true;

22 emit Confirmed();

23 }

24 }

25 function shutdown() external onlyOwner {

26 selfdestruct(msg.sender);

27 }

28 }

This is an example. When the goods are sent out and the person the user believes becomes the shipper, the user completely believes the change of the next state. It is assumed that the user listens the event log and finds that it has been set to the send status, then pay to the merchant. The user is smart. When he sees this contract, finds that once the shipper is set, it cannot be set/modified again, so when the user sees the sender that he believes, he is completely convinced. Before the create2 command, this is ok, but with create2, the developer can reddeloy after selfdestruct, and then directly set itself as the shipper, modify the status.

The above example is just a simple case of using selfdestruct-redeploy to modify the contract state.

A little extension: For the second case and the third case, can you give solidity examples according the example in this article?

Welcome to twitter, let me tell me the answer.

This class will be end here. Thanks to TRON-Eye for providing the contract verification tool. More information about them can be found directly on their website https://troneye.com.

We continue to call for source code for follow-up classes, and we welcome your official twitter submissions.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store