Ethernaut CTF Series - 01

·

4 min read

Hello Ethernaut

The first level in this series walks us through the setup of the CTF series. When we click on "Get new instance", it deploys the contract code shown in the challenge to the testnet you select (in my case, Sepolia) to which we can interact through our browser console.

This level helps us understand the interface easily and teaches us about how to interact with the ABI. As stated in the 9th section, we can begin by interacting with the functions from the smart contract with the help of web3 library which wraps around the contract, as shown below:

We begin by entering the specified command (await contract.info()) in the console which leads us to a simple way to call the consecutive functions. Reaching the final stage it asks us for a password and when we read the functions in the ABI, there is a password() function, which upon calling serves us with the password for authenticate() function which is ethernaut0.

Entering this password will send a transaction and upon approval, we can click on "Submit Instance" to get the message confirmation that we cleared the level.

Upon confirming the transaction we will receive this message box indicating that we've cleared the level:

Furthermore, this also provides us with the contract running behind the level. This is a good way to learn the bad practices and understand the fixes that can be deployed in order to fix those vulnerabilities.

Assessing the Code

Below is a snippet of the code:

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

contract Instance {

  string public password;
  uint8 public infoNum = 42;
  string public theMethodName = 'The method name is method7123949.';
  bool private cleared = false;

  // constructor
  constructor(string memory _password) {
    password = _password;
  }

  ...

  ...

  function authenticate(string memory passkey) public {
    if(keccak256(abi.encodePacked(passkey)) == keccak256(abi.encodePacked(password))) {
      cleared = true;
    }
  }

  function getCleared() public view returns (bool) {
    return cleared;
  }
}

A few suggestions to improve this codebase are below and I will try to add more as I learn and get better at this:

  1. The variable infoNum should be declared as uint256 instead of uint8 and the reason for that is the gas usage will be low.
    Explanation:
    The Ethereum Virtual Machine (EVM) operates on 256-bit words. It means that the most efficient way for the EVM to perform calculations is with 256-bit data types, like uint256. These 256-bit operations are called native operations because they directly align with the EVM's underlying architecture. The more data the EVM needs to handle, the more gas is consumed. When we perform operations on a uint8, EVM has to perform additional operations to fit the uint8 result into a 256-bit word. This extra effort consumes more gas compared to the addUint256 function, which operates directly on 256-bit data.

Foundry PoC

We will use Foundry to write our PoC as working with it is a crucial skill and will allow us to interact with the contract in a much easier manner.

Now you can find multiple installation guides for Foundry online. I simply use Ziion which is an OS-Distro with all the major tools pre-configured.

  1. Initialize a foundry project using forge init. My current folder structure looks like this:

  2. Configure your foundry.toml to your preferred testnet along with your API keys. In my case, the testnet will be Sepolia.

     [profile.default]
     src = "src"
     out = "out"
     libs = ["lib"]
    
     eth_rpc_url = "https://eth-mainnet.g.alchemy.com/v2/dZyuU1e8FUY2Rtd_xF4rhtUREO6fkOL2"
     etherscan_api_key = "dZyuU1e8FUY2Rtd_xF4rhtUREO6fkOL2"
    
  3. First we will deploy it locally to run some tests. In order to do this we will run the anvil command in order to simulate a local blockchain. From here, we can copy one private key which will be used to deploy our contract instance:

  4. Now in order to deploy our contract, we execute the following forge command:

     ❯ forge create src/ethernaut0.sol:Instance --constructor-args "ethernaut0" --rpc-url http://127.0.0.1:8545 --interactive
    

    This will provide us with the address for our locally deployed contract which in our case will be:

  5. Now we will write a test script which will be stored in the test folder. We will use the standards mentioned in Foundry docs to write our test cases. Our current test script should look like this:

    Please note that the address passed in the Instance is the one of our locally deployed contract.

  6. Now we will run the test script using the following command:

     forge test --match-path test/test.t.sol -vvvv --rpc-url http://127.0.0.1:8545
    

    The command gives us a valid response. We can also verify this hex value by decoding it to ascii using cast:

     ❯ cast to-ascii 0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000a65746865726e6175743000000000000000000000000000000000000000000000
    
     ethernaut0
    
  7. Now we are ready to test it against the live instance that we deployed on our testnet using the ethernaut website. To do so, we will write a script to broadcast them on to the blockchain. Our script currently looks like:

  8. Now we can deploy this using the following command:

     forge script script/ethernaut0.s.sol --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast --verify -vvvv
    

    This runs our script against the actual contract instance:

    This marks the completion of our first challenge.

References:

  1. Aditya Dixit's Blog

Did you find this article valuable?

Support hexbyte by becoming a sponsor. Any amount is appreciated!