PwnMe CTF quals 2025 - Mafia at the End of the Block Full [Blockchain/Misc]
Mafia at the End of the Block part 1 & 2 from PwnMe CTF 2025
Part 1
Difficulty : Easy
Description : You’re an agent, your unit recently intercepted a mob discussion about an event that’s going to take place on August 8, 2024. You already know the location, though. A password for the event was mentioned. Your job is to find it and return it so that an agent can go to the scene and collect evidence.
Author : wepfen & tzer
TL;DR
- Read the PCAP file with wireshark and notice the IRC protocol
- Find a link in the conversation and also contract addresses
- From now you can search for the contract on etherscan or open the link, get the abi and interact with the smart contract to get the flag.
Introduction
So for the first part of the challenge, we have a network capture in attachment and that’s all. We are supposed to retrieve essential informations as an URL and a smart contract address. Then retrieve the “password” on the smart contract.
Solving
Retrieve informations from the PCAP
First, I ended up messing up my network capture and it ended up having to much packet than it was supposed to be. Anyway, looking in statistics -> protocol hierarchy, we find that there is one interesting protocol which is Internet Relay Chat (or IRC)
we can filter with “irc” and read the conversation by right clicking -> follow -> TCP
We can get two informations from this :
- a link : https://shorturl.at/2O8nI
- an address : 0xCAB0b02864288a9cFbbd3d004570FEdE2faad8F8
If we continue to read the other IRC conversation, we get another contract which is wrong :
0x69E881DB5160cc5543b544603b04908cac649D38
- And a rick roll in base64 :
aHR0cHM6Ly93d3cueW91dHViZS5jb20vd2F0Y2g/dj1Fa2ZEbXpwcndydw==
-> https://www.youtube.com/watch?v=EkfDmzprwrw.
For this last contract address I just forgot to remove it so my bad.
Read the flag on Sepolia (was not the intended solution but game is game)
We can search for the contract address on etherscan
Inspecting the first transaction, we can directly read the input data as UTF-8 and get the flag. PWNME{1ls_0nt_t0vt_Sh4ke_dz8a4q6}
The flag is readable here because, while setting up the contract for the challenge, we sent a transaction to the function
set_secret(string)
and put the flag as argument. So it’s readable in plaintext on the blockchain.
Open the conversation and get the ABI of the contract and the flag
From the shortlink https://shorturl.at/2O8nI, we get redirected to https://www.swisstransfer.com/d/a4947af6-05c6-4011-958e-fd6b604587d1. It’s an archived telegram conversation.
We chose to archive it and not make it a live telegram group chat for the players who don’t want to join a telegram or create an account
We can open the html page from the archive and there is another conversation.
They are talking about the “ABI” and “interacting with the smart contract”. Also here’is the abi :
DarknetMafia.abi
|
|
The ABI(Application Binary Interface) is the representation of a contract in JSON. It defines the function, variables, visibility, parameter and all public informations we need to know how to interact with a contract. It gives a nicer representation of a smart contract and makes it easier to use with programming languages. More : https://docs.soliditylang.org/en/latest/abi-spec.html
On the ABI we learn about ask_secret
function so we understand we just have to call it using cast from foundry :
cast call 0xCAB0b02864288a9cFbbd3d004570FEdE2faad8F8 -r https://gateway.tenderly.co/public/sepolia "ask_secret()(string)"
"PWNME{1ls_0nt_t0vt_Sh4ke_dz8a4q6}"
lil foundry advertising if you don’t mind
Part 2
Difficulty : Medium
Description : You’re in the final step before catching them lacking. Prove yourself by winning at the casino and access the VIP room ! But remember, the house always win.
Author : wepfen & tzer
Tl;DR
- The player can try to spin the wheel for 0.1 ether but will end up to lose everytime
- By reading the smart contract one can understand that the random number generator is predictable
- So we have to read the contract storage, recover the state, and compute the next state. Spin the wheel with the correct value and get the flag in the vip.html page.
Introdcution
We can deploy a private blockchain which will give us an URL for the whell which is the same for the RPC, a private key and the address of the smart contract running the wheel. Players must predict the spin next values to solve solve the challenge.
The wheel interact with the CasinoPWNME
smart contract by call the function playCasino()
with “0” in argument. Which is doomed to always lose.
Solving
Walking through the webpage
Looking through the website of the wheel we can see that we can spin the wheel for 0.1 ether and also connect a wallet.
Trying to spin the wheel will ask us to connect to MetaMask but we don’t need to “really” spin the wheel to solve so we will not cover this part of MetaMask tomfoolery. Also, it’s here as a rabbit hole to waste the time of players 😈.
Reading the source code of the page we can retrieve informations such as the contract address, the ABI and the rpc. There is even the code that send the transaction to spin the wheel :
|
|
Meaning that we can only lose by spinning on the web interface.
The smart contracts
We have got two solidity files : Setup.sol and Casino.sol.
Setup.sol is only to deploy the casino smart contract :
Setup.sol
|
|
And Casino.sol holds the interesting part.
Casino.sol
|
|
Remebering the javascript code where playCasino()
is called.
Reading the function in the contract, inside this function a random number is generated by calling PRNG()
:
|
|
It computes a number using a LCG with known parameters (multiplier, increment and modulus are public) :
uint256 public multiplier = 14130161972673258133;
uint256 public increment = 11367173177704995300;
uint256 public modulus = 4701930664760306055;
Finally if the number passed to playCasino
is equal to the computed state, the gambler win and isWinner
is set to true
.
So we understand we need to predict the next state.
Exploit
As we already know the LCG parameter, we need to get the state so we can compute the next one. Luckily, even if a variable in a smart contract is private, we can recover it by reading the sotrage directly.
With foundry we can display the storage of a contract and then request the values :
|
|
Then we can recover the state : cast to-base $(cast storage $TARGET -r $RPC "4") dec
Compute the next state : (state * multiplier + increment) % modulus
And play the casino with the correct value : cast send $TARGET -r $RPC "playCasino(uint)" <state> --private-key $PRIVATE_KEY --value 0.1ether
We can check if we won by calling the isSolved()
function from the Setup contract : cast call -r $RPC $SETUP_ADDRESS "isSolved()(bool)"
which is supposed to return true
.
Then we can go to http://127.0.0.1:10019/88ce96d2-8f9e-4b07-a1b6-19a3f36ab18e/vip.html
to get the flag :
PWNME{th3_H0us3_41way5_w1n_bu7_sh0uld_be_4fr41d_0f_7h3_ul7im4te_g4m8l3r!}
.
Here’s a python script to solve the challenge :
solve.py
|
|
FLAG : PWNME{th3_H0us3_41way5_w1n_bu7_sh0uld_be_4fr41d_0f_7h3_ul7im4te_g4m8l3r!}
Other solves
There were other way to script it like this one from nikost i didn’t know. You can interact directly with the RPC by sending formatted json data which is less painful I think:
solve_nikost.py
|
|
Or even from 22sh to put the shell commands in a bash script :
solve-22sh.bash
|
|