Articles

Ethernaut - Level 07 - Force

Image courtesy of OpenZeppelin Ethernaut 07 - Force This level from Ethernaut, Force, provides us with an empty smart contract containing only some ASCII-art. The goal here is to send some ether to the contract using the selfdestruct() instruction. # Level 07 - Force ### Source Code // SPDX-License-Identifier: MIT pragma solidity ^0.6.0; contract Force {/* MEOW ? /\\_/\\ / ____/ o o \\ /~____ =ø= / (______)__m_m) */} Before explaining why this smart contract is vulnerable, let’s get familiarized with the function selfdestruct().

Ethernaut - Level 06 - Delegation

Image courtesy of OpenZeppelin Ethernaut 06 - Delegation This level from Ethernaut, Delegation, is about a special Solidity method called delegatecall(). To complete this level, we must understand how this low level function works, how it can be used to delegate operations to on-chain libraries, and what implications it has on execution scope. // SPDX-License-Identifier: MIT pragma solidity ^0.6.0; contract Delegate { address public owner; constructor(address _owner) public { owner = _owner; } function pwn() public { owner = msg.

Ethernaut - Level 05 - Token

Image courtesy of OpenZeppelin Ethernaut 05 - Token This level from Ethernaut, called Token, is a good exercise to become familiar with the integer underflow and integer overflow concepts. In this challenge you are given 20 tokens to start with and you will beat the level if you somehow manage to get your hands on any additional tokens. Preferably a very large number of tokens. // SPDX-License-Identifier: MIT pragma solidity ^0.

Ethernaut - Level 04 - Telephone

Image courtesy of OpenZeppelin Ethernaut 04 - Telephone This level from Ethernaut, called Telephone, is a good exercise to learn the nuances between tx.origin and msg.sender and why you should never use tx.origin for authentication purposes: // SPDX-License-Identifier: MIT pragma solidity ^0.6.0; contract Telephone { address public owner; constructor() public { owner = msg.sender; } function changeOwner(address _owner) public { if (tx.origin != msg.sender) { owner = _owner; } } } Solidity has a global variable, tx.

Ethernaut - Level 03 - Coinflip

Image courtesy of OpenZeppelin Ethernaut 03 - Coinflip The third level in Ethernaut, CoinFlip, is a good exercise that will teach you how to exploit pseudo randomness implementations in smart contracts. This contract will require you to correctly guess the outcome of a coin flip ten times in a row: // SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import '@openzeppelin/contracts/math/SafeMath.sol'; contract CoinFlip { using SafeMath for uint256; uint256 public consecutiveWins; uint256 lastHash; uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968; constructor() public { consecutiveWins = 0; } function flip(bool _guess) public returns (bool) { uint256 blockValue = uint256(blockhash(block.

Ethernaut Challenges

Please read This is a work in progress article that will receive updates as we continue publishing detailed walkthroughs for each level. Ethernaut is OpenZeppelin Web3/Solidity based wargame to learn about Ethereum smart contract security and become familiar with programming principles in Solidity. Although the game was launched few years ago it has become a good place to start for those who are interested on security and smart contracts. Challenges are currently running on the Rinkeby testnet and you will require to use a Rinkey Faucet to get free testnet ETH and test the smart contracts.

Ethernaut - Level 01 - Fallback

Image courtesy of OpenZeppelin Ethernaut 01 - Fallback The first level from Ethernaut is pretty straightforward and we will use it to become familiar with some Solidity concepts and the Brownie framework that we will use to complete this challenge. The code for the smart contract that we must exploit is shown above: // SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import '@openzeppelin/contracts/math/SafeMath.sol'; contract Fallback { using SafeMath for uint256; mapping(address => uint) public contributions; address payable public owner; constructor() public { owner = msg.

Ethernaut - Level 02 - Fallout

Image courtesy of OpenZeppelin Ethernaut 02 - Fallout The goal for this level is to claim ownership of the contract, which has the following implementation: // SPDX-License-Identifier: MIT pragma solidity ^0.6.0; import '@openzeppelin/contracts/math/SafeMath.sol'; contract Fallout { using SafeMath for uint256; mapping (address => uint) allocations; address payable public owner; /* constructor */ function Fal1out() public payable { owner = msg.sender; allocations[owner] = msg.value; } modifier onlyOwner { require( msg.sender == owner, "caller is not the owner" ); _; } function allocate() public payable { allocations[msg.

Ethernaut - Level 06 - Delegation

September 16, 2022

Image courtesy of OpenZeppelin

Image courtesy of OpenZeppelin

Ethernaut 06 - Delegation

This level from Ethernaut, Delegation, is about a special Solidity method called delegatecall(). To complete this level, we must understand how this low level function works, how it can be used to delegate operations to on-chain libraries, and what implications it has on execution scope.

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

contract Delegate {

  address public owner;

  constructor(address _owner) public {
    owner = _owner;
  }

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

contract Delegation {

  address public owner;
  Delegate delegate;

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

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

Before we deep-dive into the contract’s implementation, lets explain some concepts that will make easier for us to understand the vulnerability that needs to be exploited.

Solidity provides different ways to call external contracts from within a given contract:

  • ABI is available: The contract Application Binary Interface (ABI), is the standard way to interact with contracts in the Ethereum ecosystem, both from outside the blockchain and for contract-to-contract interaction. In this scenario, if the ABI and the contract’s address are known, we can simply instantiate the contract and call its functions.
  • ABI is not available: Use delegatecall or call opcodes: In Ethereum, the transaction’s input data that is generated after making a call can be broken down into two subparts:
    • A method selector: First 4 bytes of a function call’s bytecode. Generated by hashing (kecccak256) the method’s name and the type of arguments expected.
    • The remaining input are method arguments in chunks of 32 bytes

Contracts can call other contracts or send Ether to non-contract accounts by the means of message calls (call). These calls are like transactions, in that they have a source, a target, data payload, Ether, gas and return data. This means, standard external calls to contracts are handled by the call opcode whereby code is run in the context of the external contract/function (call doesn’t preserve context).

How call works

How call works

On the contrary, there exists a special variant of a message call, named delegatecall which is identical to a message call (call) apart from the fact that the code at the target address is executed in the context of the calling contract and msg.sender and msg.value do not change their values.

How delegatecall works

How delegatecall works

The other concept that is interesting here are State Variables (or storage variables) and how are they stored in contracts. State or storage variables (variables that persist over individual transactions) are placed into slots sequentially as they are introduced in the contract. We do encourage you to read the Layout of State Variables in Storage for a more thorough understanding and the Delegatecall example written by SigmaPrime.

The idea on this contract is to understand that we will be running the function pwn() of the Delegate contract as if that code was executed inside our calling contract, with our own data.

Where is the vulnerability?

The Delegation fallback implements a delegatecall. By sending the right msg.data it is possible to trigger the pwn() function on the Delegate contract and become the contract’s owner. Since this function will be executed by a delegatecall, the context will be preserved.

But let’s explain step by step why this contract is vulnerable:

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

contract Delegate {

...omitted for brevity...

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

contract Delegation {

...omitted for brevity...

  fallback() external {
    (bool result,) = address(delegate).delegatecall(msg.data);
    if (result) {
      this;
    }
  }
}
  1. The fallback function defined in the Delegation contract will get executed once we create an instance of this contract and call a function that does not exist, like pwn(). Remember that pwn() has been defined in the Delegate contract.

  2. The delegatecall() from the Delegation contract will execute code from the Delegate contract as if the code in that contract was running in our own contract, preserving, and using our own data contract

  3. As we have explained earlier in this post, calls made through delegatecall() will run the code in the context of the calling contract and msg.sender, msg.value and msg.data will be preserved. That will cause that the pwn() method from the Delegate contract gets executed and the variable owner gets updated to our msg.sender.

Hacking the contract

The most difficult part to exploit this contract is to become familiar with the concepts explained earlier and understand how the delegatecall() method works at low level. If you have mastered these points the exploit can be narrowed to just one line of code (for real).

For this exploit we are going to use the web3.utils.keccak256 function to obtain the keccak-256 hash on a function that does not exist (pwn()) and that will trigger the behavior explained in the previous section:

from brownie import accounts, config, web3

def attack(target, hacker):
    hacker.transfer(to=target, data=web3.keccak(text='pwn()'))
    #print("Test "+ web3.keccak(text='pwn()').hex()) // 0xdd365b8b15d5d78ec041b851b68c8b985bee78bee0b87c4acf261024d8beabab

def main(target):
    hacker = accounts.add(config['wallets']['from_key'])
    attack(target, hacker)

Once we execute our exploit the following events will occur:

  1. It will attempt to call the pwn() function on the Delegation contract, which does not exist.
  2. This will trigger the fallback() method in the Delegation contract (remember from previous levels that once you call a function that does not exist, if there is a fallback method implemented in the contract, it will get triggered instead), which will call the pwn() function in the Delegate contract (address(delegate).delegatecall(msg.data);).
  3. The pwn() method from the Delegate contract will run as if it was running in the Delegation contract.
  4. This will set Delegation’s owner to the msg.sender, which will be our address, as its original value will be preserved.

Takeaways

This was probably one of the Ethernaut challenges that took me more time to solve, probably because I was unfamiliar to delegatecall’s internals and didn’t know how it works.

There are still scenarios where I can see how useful this method could be, especially if you would like to create a library with its own methods and call them from a different contract, like you would do in other programming languages like C or C++.

However, Solidity has the library keyword for implementing library contracts. This ensures the contract is stateless and non-self-destructive. As a library is an isolated piece of source code, it can only access state variables of the calling contract if they are explicitly supplied. This help to prevent attacks whereby attackers modify the state of the library directly in order to affect the contracts that depend on the library’s code.