Skip to content

Delegation

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Delegate {

  address public owner;

  constructor(address _owner) {
    owner = _owner;
  }

  function pwn() public {
    owner = msg.sender;
  }
}

contract Delegation {

  address public owner;
  Delegate delegate;

  constructor(address _delegateAddress) {
    delegate = Delegate(_delegateAddress);
    owner = msg.sender;
  }

  fallback() external {
    (bool result,) = address(delegate).delegatecall(msg.data);
    if (result) {
      this;
    }
  }
}

Goal

The goal of this level is to claim ownership of the Delegation instance.

Exploit

In order to exploit this contract one must first understand how fallback methods and delegatecall low function works.

fallback is a special function that is executed when a function that does not exist is called from some smart contract.

delegatecall is a low level function similar to call, with the only difference when we make a delegatecall to some smart contract, the callee's smart contract code is executed in context of the caller (this means the caller storage is changed, and msg.sender and msg.value from the caller are taken into account).

In our case, when we try to call the pwn() function inside Delegation contract, since this function is not present in the Delegation contract, its fallback function will be triggered and it will delegatecall to the Delegate contract with the pwn() function funcion-selector argument for msg.data.

To exploit the contract we need to:

  1. copy the code from Ethernaut to Remix
  2. get the contract instance address from the web console
  3. load the Delegate.sol contract at the contract instance address (it is important to load Delegate.sol contract and not Delegation.sol)
  4. call the pwn() function inside Remix. NOTE: we need to increase gas limit in MetaMask in order for the transaction to succeed