Table of contents



Note

Writeup for the first web3 challenge from RetroPixel 2025. This CTF was organized by Ynov Bordeaux’s SSI Lab and was really fun to tackle. It was my first time diving into web3 challenges, and I learned a ton along the way!

Description

No description for this challenge.

File information

We were given an entrypoint.sol file. Let’s check it out:

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.7.0;

contract entrypoint {
    address public owner;
    mapping(address => uint) public bank;
    bool public step1 = false;
    bool public step2 = false;
    bool public step3 = false;

    constructor() {
        owner = msg.sender;
    }

    function Welcome() public returns (string memory) {
        require(
            !step1,
            "Already welcomed. Next step: call eleven_is_prime(true)."
        );

        step1 = true;
        return "Welcome! Next, call eleven_is_prime(true).";
    }

    function eleven_is_prime(bool _prime) public returns (string memory) {
        require(step1, "Call welcome() first.");
        require(
            !step2,
            "Already proven that 11 is prime. Next step: deposit 100 gwei."
        );
        require(_prime, "We only accept that 11 is prime in this lesson.");

        step2 = true;
        return "Great! Now call deposit() with at least 100 gwei.";
    }

    function deposit() public payable returns (string memory) {
        require(msg.value >= 100 gwei, "Insufficient amount");
        step3 = true;
        bank[msg.sender] += msg.value;
        return
            "Thanks for depositing! Now send at least 1000 gwei directly to this contract address.";
    }

    receive() external payable {
        require(step1 && step2 && step3, "Complete the previous steps first.");
        require(msg.value >= 1000 gwei, "Insufficient amount, at least 1000 gwei required to claim ownership.");
        owner = msg.sender;
    }
}

This is the source code of the smart contract we need to exploit to snag the flag.

Looking at it, we can see there are 4 key steps:

  • Call welcome()
  • Call eleven_is_prime(true)
  • Call deposit() with at least 100 gwei
  • Send at least 1000 gwei directly to the contract

After completing these steps, we should become the owner of the contract and be able to claim the flag.

Getting the connection information

After deploying an instance and connecting with netcat, we’re presented with this interface:

nc interface

We have two options: get the connection information or get the flag.

Choosing the first option gives us the RPC URL, a Private Key with its associated Address, and the Contract’s Address.


What is a RPC?

An RPC (Remote Procedure Call) acts like a bridge connecting your applications to a blockchain network. Think of it as a messenger service that lets you talk to the blockchain without needing to understand all its complex technical details.

When you want to check your crypto balance, send tokens, or interact with a smart contract, your app needs to communicate with the blockchain somehow. The RPC provides standardized methods (like “get balance” or “send transaction”) that your application can use.


Now that we have the connection details, let’s set up our environment.

Connecting Metamask

First, we need to connect to the wallet via Metamask. Here’s how:

At the top of the Metamask window, click your account name (the one with the downward arrow next to it)

metamask account

Click “Add account or hardware wallet”

add acconut

Click “Import account”

import account

Enter the private key we were given

private key

Now that we’re connected to the wallet, we need to use the right network:

In the top left corner of Metamask, click the Ethereum icon

eth mainnet

Click “Add a custom network”

custom network

Enter any network name, like “CTF Network”

Add the RPC URL (in our case, http://entry.labossi.xyz:33063)

For the chain ID, use 1337 or 7331

Enter ETH for the symbol

network settings

Save the network and switch to it

metamask

If you suddenly see you’re RICH, it worked! If not, you either messed something up or the RPC isn’t working right.

Setting up Remix IDE

I chose to use Remix IDE for this challenge. There are many ways to interact with smart contracts, but I find this one simple and clear for beginners like me.

Creating & Compiling the file

First, we need to import the file into Remix and compile it with the correct version:

remix ide

We can see there’s an error with the version, but we’ll change it in the compiler settings before compiling:

compiler

The green checkmark indicates that the file has been compiled properly. Now we need to head to the “Deploy and Run transactions” page to start interacting with the contract.

Interacting with the contract

In the “Deploy and Run transactions” menu, there are a few things to do before we can communicate with the contract:

  1. Set the environment to Metamask
  2. Verify that the account is the right one
  3. Fill the “Load contract from address” field with the given Contract Address
  4. Connect to the contract by clicking “At address”

deploy

In the deployed contracts section, we can now see our contract and the available interactions:

actions

Time to start interacting with it!

Solving the challenge

For each interaction, a Metamask popup will ask us to confirm the transaction. We’ll then need to wait for the transaction to be processed.

Let’s start by calling welcome() by clicking the associated button in Remix. After confirming the transaction and waiting a few seconds, we see this message in the console:

console

This indicates that the transaction was successful, meaning welcome() has been called and the step1 variable is now set to True.

The second step is calling eleven_is_prime with the True parameter. After confirming this transaction and getting confirmation, step2 is also set to True.

The third step is calling the deposit() function with at least 100 gwei.


What is a “gwei”?

Gwei (pronounced “giga-wei”) is a denomination of Ether (ETH), the cryptocurrency used on the Ethereum blockchain. 1 Gwei equals 0.000000001 ETH, or one billionth of an Ether.

Gwei is primarily important because it’s the standard unit used to measure gas prices in Ethereum transactions. When you send a transaction on Ethereum, you pay a fee to network validators in the form of “gas”. This fee is calculated by multiplying the gas used by the gas price, which is typically expressed in Gwei.


To do this, we need to set a specific value in the “Value” field in Remix:

value

Pressing deposit and confirming the transaction will set step3 to True.

The last step is to trigger the receive() function so we can become the contract’s owner. There’s no button in Remix for this function.

This is because the receive() function is triggered automatically when the contract receives a transaction. So for the last step, we’ll use Metamask directly to send at least 1000 gwei to the contract.

1000 gwei = 0.000001 ETH

In Metamask, click “Send” and enter the Contract’s Address (in our case, 0x0aa32BA9ac382Efc405702e6B5FcAFf272AEa36a).

In the amount field, enter 0.000001 ETH and confirm the transaction.

send

After this, we should be the contract’s owner. We can verify this by clicking the owner button in Remix. It will display the owner’s address. If it matches the address given by the RPC, then we’re now the owner.

We can now reconnect to the RPC and type “2” to get the flag. It will check if we’re the contract’s owner, and if we are, it’ll display the flag.

Conclusion

Even though this challenge didn’t exploit any vulnerability, it was a great introduction to web3, and I learned a lot doing it. I’ve tried to explain it as clearly as possible so others looking to get into web3 can understand the process.

If I missed anything or made any mistakes, feel free to reach out to me on Discord.

Thanks for reading!

back to top