THM Hackfinity 2025 Blockchain Challenge Write-Up: Passcode
Goal
The objective is to interact with the smart contract such that the isSolved()
function returns true
.
Steps
1. Environment Setup
Set up the necessary environment variables using the provided API endpoint.
# Define URLs
export RPC_URL="[http://10.10.24.116:8545](http://10.10.24.116:8545)"
export API_URL="[http://10.10.24.116](http://10.10.24.116)"
# Fetch dynamic values using curl and jq
export PRIVATE_KEY=$(curl -s ${API_URL}/challenge | jq -r ".player_wallet.private_key")
export CONTRACT_ADDRESS=$(curl -s ${API_URL}/challenge | jq -r ".contract_address")
export PLAYER_ADDRESS=$(curl -s ${API_URL}/challenge | jq -r ".player_wallet.address")
# Verify (optional)
echo "RPC_URL: $RPC_URL"
echo "CONTRACT_ADDRESS: $CONTRACT_ADDRESS"
echo "PLAYER_ADDRESS: $PLAYER_ADDRESS"
# Note: It is best practice to not echo the PRIVATE_KEY for security reasons
2. Foundry Installation
Install the Foundry development toolkit if you haven't already.
# Download and install Foundry
curl -L [https://foundry.paradigm.xyz](https://foundry.paradigm.xyz) | bash
# Update your current shell environment
source ~/.bashrc
foundryup
3. Contract Analysis
Upon reviewing the provided Solidity source code:
- The
unlock(uint256)
function takes an integer input. If this input matches the storedcode
variable, it sets theunlock_flag
boolean variable totrue
. - The
isSolved()
function simply returns the current value of theunlock_flag
. - A
hint()
function exists, which might reveal the value needed for thecode
variable. - The
getFlag()
function returns the challenge flag (a string), but only ifunlock_flag
istrue
.
4. Getting the Hint
Call the hint()
function on the smart contract to find the code.
Command:
cast call $CONTRACT_ADDRESS "hint()(string)" --rpc-url ${RPC_URL}
Output:
"The code is 333"
This reveals the required code is 333
.
5. Unlocking the Contract
Send a transaction to call the unlock()
function with the discovered code (333
).
Command:
cast send $CONTRACT_ADDRESS "unlock(uint256)" 333 --rpc-url ${RPC_URL} --private-key $PRIVATE_KEY --gas-price 1 --legacy
(Note: cast send
executes a state-changing transaction. It will output transaction details like the hash, but the success is confirmed by checking the state in the next step.)
6. Verifying the Solution
Check if the unlock_flag
was successfully set to true
by calling isSolved()
.
Command:
cast call $CONTRACT_ADDRESS "isSolved()(bool)" --rpc-url ${RPC_URL}
Output:
true
The output true
confirms the contract is now in the solved state.
7. Retrieving the Flag
Now that the contract is solved (unlock_flag
is true), call getFlag()
to retrieve the final flag.
Command:
cast call $CONTRACT_ADDRESS "getFlag()(string)" --rpc-url ${RPC_URL}
Output:
"THM{web3_h4ck1ng_code}"
Vulnerability Exploited
The core vulnerability lies in the public visibility and informative nature of the hint()
function within the smart contract. This function directly revealed the required code
needed to pass the check in the unlock(uint256)
function.
By:
- Calling
hint()
to obtain thecode
(333
). - Calling
unlock(333)
using the obtained code.
The internal unlock_flag
state variable was set to true
. This state change satisfied the condition required by the getFlag()
function, allowing the retrieval of the final flag: THM{web3_h4ck1ng_code}
. Essentially, the secret needed to solve the challenge was publicly exposed via the hint()
function.