Poison Block Explorer Byte Code

February 11, 2019 by Daniel Luca

You will understand how to trick a block explorer into displaying different byte code of your choosing, other than the one deployed on the chain.

This is important because a user can be tricked by a hacker to think they interact with a good contract, when actually the user interacts with malicious contract. It is indeed the same contract address, but the byte code is not the one reported by the block explorer.

The problem

The core technical issue is how block explorers handle transactions that create contracts and are then reverted. I found two explorers, BlockChair and BlockScout, that incorrectly store byte code from the reverted transaction. (See the Disclosure section for the current status.)

The setup

To make this work we need 3 things:

a trustworthy contract that a user wants to interact with;

another contract that is malicious, that the user does not want to interact with;

a factory contract that pretends to deploy the trustworthy contract and actually deploys the malicious one.

Trustworthy contract

Below is a simple contract that acts like a safe. A user can store funds and retrieve those funds at a later date:

Factory contract

Below is the factory contract:

contractDeployer{functioncreateGood()**external**{// Pretend to deploy the contract and revert
newSafe();revert();}functioncreateMalicious()external{MaliciousContractnewContract=newMaliciousContract();emitDeployed(address(newContract));}eventDeployed(addresssafe);}

The method createGood() pretends to deploy the good contract, but in the end it reverts the transaction. Trying to deploy the good contract means that it creates a message that is picked up by the block explorer, which contains the byte code and the new contract address. Reverting the transaction means no new contract is deployed and that the contract’s nonce is not changed.

The address for an Ethereum contract is deterministically computed from the address of its creator and the creator’s nonce. We can define it like this without going into detail:

The method createGood() does not increase the contract’s nonce because it reverts. This means that calling createMalicous() will generate the same address for the newly deployed contract and this time it will not revert but it will have a different byte code.

This is exactly what the Deployer contract exploits.

Tricking the block explorer

To trick the block explorer you need 2 actions:

The hacker first calls createGood() which will appear to the block explorer to deploy a new contract with the Safe contract’s byte code. If the block explorer does not handle the revert() properly, the byte code will be associated with the new contract address.

The hacker then calls createMalicious(), which creates a contract with the MaliciousContractbyte code at the same contract address as previously recorded.

The block explorer must handle the errors properly and only save the byte code when the contract message succeeds. Otherwise it will associate an incorrect byte code with a contract address.

The users checking the contract will see an incorrect byte code on the website. They will think they interact with a good contract when actually they interact with a malicious one.

Result

When I tried this, some block explorers reported incorrect byte code. This happened because we first pretend to deploy a contract and then revert. The block explorer observes the pretend deployment and saves the byte code as the one deployed. When we actually deploy a new contract with different byte code, the explorer does not overwrite the previously saved byte code.

In the end the reported byte code is not the one on the chain.

Disclosure

I wanted to see if any explorers had this bug, and this was the result: