Poison Block Explorer Byte Code

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:

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:

contract Safe {
    mapping(address => uint256) ledger;

    function() external payable {
        ledger[msg.sender] += msg.value;

    function withdraw() external {
        uint256 balance = ledger[msg.sender];
        ledger[msg.sender] = 0;

If the user wants to store some funds, they can just send them to the contract. In the future the user can call the withdraw() method to retrieve all of their stored funds.

Malicious contract

Below is a malicious contract that accepts funds but does not return them. Instead, it allows the contract’s owner to steal all the collected funds:

contract MaliciousContract {
    address payable owner;

    constructor() public {
        owner = msg.sender;

    function() external payable {
        // Accepts ether

    function steal() external {

Factory contract

Below is the factory contract:

contract Deployer {
    function createGood() **external** {
        // Pretend to deploy the contract and revert
        new Safe();

    function createMalicious() external {
        MaliciousContract newContract = new MaliciousContract();

        emit Deployed(address(newContract));

    event Deployed(address safe);

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:

  1. 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.
  2. The hacker then calls createMalicious(), which creates a contract with the MaliciousContract byte 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.


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.


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

I am in the process of contacting the explorers that have this problem to help them fix the issue.

Explorers that handle it correctly:

Explorers with this bug:

Other block explorers that display too little information or do not display the contract code:

A big thank you to Jules for seeding the initial idea, M H Swende for making me think more about this and Steve for all the support provided.

Thinking about smart contract security? We can provide training, ongoing advice, and smart contract auditing. Contact us.

More posts chevronRight icon