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 stored code variable, it sets the unlock_flag boolean variable to true.
  • The isSolved() function simply returns the current value of the unlock_flag.
  • A hint() function exists, which might reveal the value needed for the code variable.
  • The getFlag() function returns the challenge flag (a string), but only if unlock_flag is true.

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:

  1. Calling hint() to obtain the code (333).
  2. 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.