Emanuele RicciEmanuele Ricci

Emanuele Ricci

3 min read

EthernautDAO CTF 6 — Hackable Contract Solution

ΞthernautDAO is common goods DAO aimed at transforming developers into Ethereum developers.

They started releasing CTF challenges on Twitter, so how couldn't I start solving them?

https://twitter.com/EthernautDAO/status/1556278995909427202

CTF 6: Hackable

For this challenge, we have to deal only with a single Smart Contract called hackable, a simple smart contract.

The smart contract has been deployed with the following configuration values:

  • lastXDigits equal to 45
  • mod equal to 100
  • done equal to false

Our goal is to be able to solve the challenge, become the winner and flip the value of done from false to true.

Study the contracts

This contract is simple to understand and easy to solve, the annoying thing to become the winner is just wait for the correct time to call the function cantCallMe. You will understand it in just a few moments ;)

Let's see the code

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

contract hackable {
    uint256 public lastXDigits;
    uint256 public mod;
    bool public done;
    address public winner;

    constructor(uint256 digits, uint256 m) {
        lastXDigits = digits;
        mod = m;
        done = false;
    }

    function cantCallMe() public {
        require(done == false, "Already done");
        uint256 res = block.number % mod;
        require(res == lastXDigits, "Can't call me !");
        winner = msg.sender;
        done = true;
    }
}

As you can see, there are not many lines of code to understand, so let's just directly in the solution.

To make cantCallMe to not revert, we have to call it in the correct block.number for which the result of block.number % mod == lastXDigits.

The contract is deployed with the current configuration:

  • lastXDigits equal to 45
  • mod equal to 100

This mean that to pass the check, we have to call the function in a specific block number for which block.number % 100 == 45.

As I said, the challenge was straightforward, you just need to call the function in a block where the block.number last two digits are equal to 45. This mean that you have to patiently wait for the blockchain to mint new blocks and be able to insert a transaction in the correct one.

Solution code

Now what we have to do is:

  • Create an Alchemy or Infura account to be able to fork the Goerli blockchain
  • Choose a good block from which we can create a fork. Any block after the creation of the contract will be good
  • Run a foundry test that will use the fork to execute the test

Here's the code that I used for the test:

function testFindTheGoodBlock() public {
    address player = users[0];

    // Random block number just to test the solution
    uint256 solutionBlockNumber = 948574245;

    // warp the blockchain to the blocknumber that will solve the challenge
    vm.roll(solutionBlockNumber);

    // Assert that the solution is correct
    assertEq(solutionBlockNumber % hackableContract.mod(), hackableContract.lastXDigits());

    // Solve the challenge
    vm.prank(player);
    hackableContract.cantCallMe();

    // assert it has been solved
    assertEq(hackableContract.winner(), player);
    assertEq(hackableContract.done(), true);
}

Here is the command I have used to run the test: forge test --match-contract HackableTest --fork-url <your_rpc_url> --fork-block-number 7335616 -vv

Just remember to replace <your_rpc_url> with the RPC URL you got from Alchemy or Infura.

You can read the full solution of the challenge, opening Hackable.t.sol

Further reading

This contract has no further reading material.

Disclaimer

All Solidity code, practices and patterns in this repository are DAMN VULNERABLE and for educational purposes only.

do not give any warranties and will not be liable for any loss incurred through any use of this codebase.

DO NOT USE IN PRODUCTION.