6 minutes
🇬🇧 RetroPixel 2025 - web3/entrypoint
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 least100 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:
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)
Click “Add account or hardware wallet”
Click “Import account”
Enter the private key we were given
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
Click “Add a 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
or7331
Enter
ETH
for the symbol
Save the network and switch to it
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:
We can see there’s an error with the version, but we’ll change it in the compiler settings before compiling:
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:
- Set the environment to Metamask
- Verify that the account is the right one
- Fill the “Load contract from address” field with the given Contract Address
- Connect to the contract by clicking “At address”
In the deployed contracts section, we can now see our contract and the available interactions:
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:
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:
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.
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!