14

This documentation says

Even if a contract’s code does not contain a call to selfdestruct, it can still perform that operation using delegatecall or callcode.

This worries me.

I've created some pseudo code to give a gist of a possible exploit. The code below creates an "exception: invalid opcode", but you'll be able to follow my logic. Assuming this style of attack works, what can be done to guard against it?

pragma solidity ^0.4.2;        
contract Steal{
      function delegatecall_selfdestruct(address _target) external returns (bool _ans) {
          _ans = _target.delegatecall(bytes4(sha3("selfdestruct(address)")), this); 
      }
}

contract Mark { 
  function Deposit() payable {
  }
}

The idea is to send some testnet ETH to the contract Mark then use the Steal contract to call Mark and make it selfdestruct and give the ETH to the Steal Contract.

2
  • 1
    There's a difference between built-in solidity functions and publicly accessable contract functions. Your attack will not work Commented Apr 10, 2017 at 20:15
  • @TjadenHess besides, are there any token listed on an exchange actually using DELEGATECALL or CALLCODE ? Commented Jun 20, 2018 at 20:38

2 Answers 2

20

It doesn't work to just call built-in Solidity functions in other contracts using delegatecall.

What delegatecall really does is call a function in another contract, which executes in the context of the first contract. It's really dangerous to make delegatecalls to contracts you don't trust. The documentation is warning you about this possibility:

pragma solidity ^0.4.2;        
contract Steal{
    address owner;
    function Steal() {
        owner = msg.sender;
    }
    function innocence() {
        selfdestruct(owner);
    }
}

contract Mark { 
    function Deposit() payable {}
    function call(address a) {
        a.delegatecall(bytes4(sha3("innocence()"))); 
    }
}

If Mark calls Steal, then Mark will selfdestruct.

3
  • 4
    This example helps a lot, but when I test this, Mark calls steal and self destructs, but the ETH that was in Mark gets sent to address 0x0 even though address owner != 0x0. Can you help me understand why, and how a non zero address can be specified? Thanks. Commented Apr 13, 2017 at 20:17
  • 6
    I think it must be because owner doesn't exist in Mark, and delegatecall uses Mark's environment, not Steal's. It might be interesting to add an "owner" variable to Mark and see if that gets the ETH. Commented Apr 14, 2017 at 20:52
  • 1
    @DennisPeterson I can add that Steal can even overwrite Mark bytecode (the bytecode stored at the address of Mark) through a Callcode to address 0 (this will create a contract not as the usual new address but at the address of Mark), because unlike CALL CodeAddr is not null. The bytecode which will be stored over Mark is Steal. Commented Jun 20, 2018 at 20:37
2

Here is a working and up-to-date example:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.2;        
contract Steal{
    fallback() external {
        selfdestruct(payable(address(this)));
    }
}

contract Mark { 
    function callSteal(address _c) public {
        (bool success, ) = _c.delegatecall(""); 
        require(success, "something went wrong");
    }
}

When you call callSteal the Mark contract will be "selfdestructed".

1
  • 1
    The code is correct too! Commented Aug 4, 2022 at 13:27

Your Answer

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

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