Post
Share your knowledge.
A Hacker's Guide to Conquering Move CTF 2025 (Week 1)
In blockchain security, there’s no better training ground than a Capture The Flag (CTF) challenge.
Developers forge their skills in the fires of friendly competition. The Move CTF 2025 Week 1 challenge on the Sui blockchain is a perfect example designed to test your understanding of the Move language.
We'll go from setting up the environment to analyzing the smart contract's defenses, crafting a solver script, and finally executing the transaction that reveals the coveted prize: CTF{Letsmovectf_week1}.
Whether you're a Move novice or a seasoned security researcher, this walkthrough will provide a practical roadmap to sharpen your skills. Let's dive in!
Step 1: Reconnaissance - Understanding the Targe
Before writing a single line of code, our first job is to understand the battlefield. The challenge is centered around a single Move module, week1::challenge
, which we need to analyze.
public struct Challenge has key {
id: UID,
secret: String,
current_score: u64,
round_hash: vector<u8>,
finish: u64,
}
Our mission, simply put, is to successfully call the get_flag
entry function. A successful call will trigger a FlagEvent
, broadcasting our victory and the flag to the entire network.
However, the get_flag
function is protected by a series of five distinct validation checks. To succeed, we must satisfy all of them in a single transaction.
Step 2: Deconstructing the Defenses - The Five Hurdles
The get_flag
function is a fortress, and its walls are made of assert!
statements. Let's break down each of these hurdles one by one.
Hurdle 1: The score Check
The Task: Calculate the sha3_256 hash of the current challenge.secret string. The correct score is a u64 number derived from the first 4 bytes of that hash (using big-endian byte order).
The Concept: This is a basic cryptographic challenge to ensure you can correctly perform hashing and byte manipulation as required by the contract.
Hurdle 2: The guess Check (A Proof-of-Work Puzzle)
The Task: Find a guess (a byte vector, which acts as a nonce) such that when you hash your guess and the secret together (sha3_256(guess || secret)), the first two bytes of the resulting hash match the first two bytes of the challenge.round_hash.
The Concept: This is a classic Proof-of-Work (PoW) mechanism. It can't be solved mathematically; it requires computational effort ("brute force") to find a valid guess, proving you've spent resources to solve it.
Hurdle 3: The hash_input Check (Identity Binding)
The Task: Provide a hash that correctly combines the secret and your personal github_id. The trick here is that the secret must first be serialized using Binary Canonical Serialization (bcs::to_bytes). This is different from just using its raw bytes, as BCS prepends the data with its length. The final calculation is sha3_256(bcs(secret) || github_id).
The Concept: This check links your solution to your unique identifier, preventing others from simply replaying your successful transaction. The use of BCS is a common pattern in Move and a key detail to notice.
Hurdle 4 & 5: The seed and magic_number Check
The Task: These two parameters are linked.
seed is calculated as length(secret) * 2.
magic_number is calculated as (score % 1000) + seed.
The Concept: These are logic puzzles that test your ability to read the Move code carefully and replicate its simple arithmetic operations.
Step 3: Setting Up Your Toolkit - Environment and Deployment
With the theory understood, it's time to get our hands dirty.
First, ensure your Sui CLI is installed and configured for the testnet.
# Check your Sui version
sui --version
> sui 1.50.1-homebrew
# Check your active environment
sui client envs
> testnet
# Check your active address and gas balance
sui client active-address
sui client gas
Next, build and publish the Move contract to the Sui testnet. This will compile the code and deploy it, creating the initial shared Challenge
object we need to interact with.
# Build the project
sui move build
# Publish the contract to the testnet
sui client publish --gas-budget 100000000
After a successful publish, the CLI will output the results, including two critical pieces of information you must save:
Package ID: The address of your newly published contract.
Object ID: The address of the shared Challenge
object created by the init
function.
Step 4: Forging the Keys - Crafting the Python Solver
Now, we need to calculate the correct arguments to pass to get_flag
. Manually doing this is tedious, so we'll write a Python script to automate it. This script reads the initial secret
, "Letsmovectf_week1", and computes all five required parameters.
import hashlib
# --- Configuration ---
github_id = b"qiaopengjun5162"
secret = b"Letsmovectf_week1"
# --- 1. Calculate Score ---
hash_bytes = hashlib.sha3_256(secret).digest()
score = int.from_bytes(hash_bytes[:4], 'big')
print(f"✅ Score: {score}")
# --- 2. Solve Proof-of-Work for Guess ---
target_prefix = hash_bytes[:2]
found_guess = None
for i in range(1000000): # Brute-force loop
guess_candidate = f"guess{i}".encode()
combined_data = guess_candidate + secret
random_hash = hashlib.sha3_256(combined_data).digest()
if random_hash[:2] == target_prefix:
found_guess = guess_candidate
print(f"✅ Guess Found: {found_guess.decode()}")
print(f" (as hex): {found_guess.hex()}")
break
# --- 3. Calculate Hash Input (with BCS) ---
# BCS serialization for a string prepends its length as a ULEB128 integer.
# For short strings, this is just a single byte representing the length.
bcs_encoded_secret = bytes([len(secret)]) + secret
bcs_input_data = bcs_encoded_secret + github_id
hash_input = hashlib.sha3_256(bcs_input_data).digest()
print(f"✅ Hash Input (hex): {hash_input.hex()}")
# --- 4 & 5. Calculate Seed and Magic Number ---
secret_len = len(secret)
seed = secret_len * 2
magic_number = (score % 1000) + seed
print(f"✅ Seed: {seed}")
print(f"✅ Magic Number: {magic_number}")
Step 5: The Heist - Calling get_flag and Capturing the Prize With our parameters calculated, we'll use another Python script to construct and execute the final sui client call command. This automates the tedious process of formatting the arguments for the command line.
call_get_flag.py (summary)
import subprocess
# --- Configuration (using values from previous script) ---
package_id = "0x804c92e4eef709b83f135d6cc667005ce35d7eccd49384570cbd7b1b40e32434"
challenge_id = "0xd28bc35560711a8b6ca93e2bf8e353fa6e17c15cbc426c48ece1ade9d83ce5ee"
random_id = "0x8" # Default Random object on Sui
gas_budget = "10000000"
# --- Calculated Parameters ---
score = 1478524421
guess = [103, 117, 101, 115, 115, 55, 54, 57, 48, 56] # b'guess76908'
hash_input = [...] # The full byte array from the solver
github_id = "qiaopengjun5162"
magic_number = 455
seed = 34
# --- Construct and Execute the Sui CLI Command ---
command = [
"sui", "client", "call",
"--package", package_id,
"--module", "challenge",
"--function", "get_flag",
"--args",
str(score), str(guess), str(hash_input), f'"{github_id}"',
str(magic_number), str(seed), challenge_id, random_id,
"--gas-budget", gas_budget
]
# Execute the command and print the output
result = subprocess.run(command, capture_output=True, text=True, check=True)
print(result.stdout)
Upon running this script, the transaction is sent. If all our parameters are correct, the output will show Status: Success and, most importantly, the emitted FlagEvent:
"ParsedJSON": {
"flag": "CTF{Letsmovectf_week1}",
"github_id": "qiaopengjun5162",
"rank": "1",
"success": true
}
Success! The flag is captured.
A well-designed CTF doesn't end after one solve. After our successful call, the get_flag function uses the Random object to generate a new secret and updates the Challenge object.
I confirmed this by querying the object again, finding a new secret, and repeating the process. My solver scripts were reusable—I just had to update the secret variable, re-run the calculation, and execute the call again to capture the flag a second and third time, climbing the ranks.
https://suiscan.xyz/testnet/tx/AzJywLk8zv1Fx5Pc8p4ZrZwFZQ9hmTbtvweo14MVn8fZ
Conclusion: More Than Just a Flag
Tackling the Move CTF 2025 Week 1 challenge was an incredible learning experience. It forced me to move beyond theory and engage directly with the Sui blockchain, reinforcing key concepts like:
Smart Contract Logic: Carefully reading and understanding Move code is paramount.
Cryptography in Practice: Applying hashing (sha3_256) and understanding byte manipulation.
On-Chain Mechanics: The importance of specific serialization formats like BCS.
Automation: Using scripts to make complex, repeatable tasks manageable.
This challenge is a testament to the power of hands-on learning. If you're looking to grow your skills in blockchain development and security, I highly encourage you to jump into the
- Sui
Sui is a Layer 1 protocol blockchain designed as the first internet-scale programmable blockchain platform.

- 24p30p... SUI+78
1
- MoonBags... SUI+71
2
- Meaning.Sui... SUI+43
3
- ... SUIJojo+34
- ... SUIOpiiii+31
- ... SUI0xduckmove+20
- ... SUIHaGiang+20
- ... SUIfomo on Sui+16
- ... SUI
- ... SUI
- Why does BCS require exact field order for deserialization when Move structs have named fields?53
- Multiple Source Verification Errors" in Sui Move Module Publications - Automated Error Resolution43
- Sui Transaction Failing: Objects Reserved for Another Transaction25
- How do ability constraints interact with dynamic fields in heterogeneous collections?05