P3RF3CT ROOT CTF 2024 Writeup - Web3 and OSINT
Introduction
"Are you okay?" "No, no, I'm not."
My eyes are still recovering from staring at my laptops’ screen until 7 AM, and I'm pretty sure I've seen enough dust to make a desert jealous. Why, you ask? I just survived my first official CTF (Capture the Flag) competition, and let me tell you—subscribing to OnlyPain™ has never been more real (if you know, you know... K 😅).
Before I dive into my tale of confusion, let me introduce myself.
I'm a budding Rustacean (yes, I'm still learning, and yes, I'm claiming that title—growth mindset, right?) I am still learning Solidity while doing programmable cryptography and math for Web3. I like to tinker with ZKPs, And now, apparently, collecting dust in CTF challenges. Curiosity is my superpower (and sometimes my kryptonite) As they say, curiosity killed the cat, but satisfaction brought it back—though right now, I'm still waiting for that satisfaction part! 😅
Out of all the challenges I managed to crack two of them. And guess what? They were both blockchain/Web3 related! Talk about staying in your lane, right? Sure, I only solved 2 out of many, but hey, everyone starts somewhere, right?
Let's dive into how this Rustacean tried to swim in the CTF ocean... and mostly just found dust. 🌪️
The Challenge
Here we were provided with:
A Sepolia testnet contract address: 0x5e4728870e3c2b05df8966a8719819f079c1711f
A Solidity contract file which I am supposed to download.
What appeared to be a flag format guideline: r00t{xxxxx_xxxxxxx_xxxxx}
“There are no secrets on the blblockchain." Such huge hint!
The Contract and Analysis
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
contract P3rf3ctFlag {
uint256 private _flag;
constructor(uint256 flag_) {
_flag = flag_;
}
function setFlag(uint256) public pure returns(bool){
return true;
}
}
Looking deeply at the contract, we see there is a private variableuint256 private _flag
which stores our flag. The private
keyword might make you think the value is hidden—this is a classic blockchain CTF misdirection.
In blockchain, there's no such thing as truly "private" dadata—everythings visible onchain.
We also see a constructor that takes a uint256
parameter calledflag_
and sets the private variable _flag
to this value.
This is crucial because constructor arguments are stored in the contract's creation transaction. Even though we can't directly read _flag
, we can see what value was passed to set it.
The Deceptive Function
setFlag(uint256)
: A function that seems like it might be useful. It returns true
regardless of input. Marked as pure
meaning it doesn't even read the contract state. This is clearly a distraction; it doesn't interact with our private variable at all.
So what?
In blockchain, "private" only means other contracts can't read the value, and all data, including constructor arguments, is publicly visible on the blockchain. We don't need to interact with the contract; we just need to look at its creation.
This Chal requires knowledge of how to use block explorers, tests the ability to decode contract creation data, and shows why "private" in Solidity doesn't mean "secret."
This code, while simple, perfectly encapsulates a fundamental blockchain concept: transparency is inevitable.
Solution Path
Since all data on the blockchain is public, I examined the contract creation transaction on Etherscan.
Here we see the contract details
Creator Address: 0xe36039A5...CD10C94FA
, created through transaction: 0x8b791b95c2...
Age: 27 days ago
Transaction Details: Only 1 transaction (the contract creation), method ID: 0x60806040
(standard contract creation), and the block number: 6916867
Notable Information: No token transfers (ERC-20). no other interactions besides the creation and the contract verified on EtEtherscan,hich allows us to view and analyze the source code.
Next step? I clicked the Transaction Hash. 0x8b791b95c24c39d2822e4c021401e98da63a5bcce95ea0d966eba593fa528bbb
. But wait, what's a transaction hash anyway?
Think of a transaction hash as a unique identifier—like a fingerprint for each blockchain transaction. It contains the full story of what happened: who sent what, when, and most importantly for us CTF hunters, what data was included!
The transaction details revealed quite a story:
Status: Success (phew, at least we're looking at a valid transaction!)
Block: 6916867 (where in the blockchain timeline this happened)
From: 0xe36039A564104fC066F436e9e1b8540CD10C94FA
(our mysterious contract deployer)
To: 0x5e4728870e3c2b05df8966a8719819f079c1711f
(the contract being created)
Value: 0 ETH (not here for the money, here for the flag!)
The "More Details" Rabbit Hole
Clicking "More Details”, and suddenly, I'm faced with:
Gas Limits
Transaction Fees
Input Data (now this looks interesting! 👀)
Here's where things got spicy 🌶️. The input data offered three viewing options:
UTF-8 (human-readable text)
Default View
Original View
Being the curious cat I am, I chose UTF-8, and... wait, what's this at the end?
The False Hope
"Aha!" I thought, "blBl0ck_3xpl0r3rs_3xp0s3_4l0t_init” looks suspiciously like a flag!" My heart raced as I remembered the format: r00t{xxxxx_xxxxxxx_xxxxx}
.
I started trimming and formatting:
Try 1: Remove some characters... ❌
Try 2: Match the exact format... ❌
Try 3: Maybe just the first parts... ❌
At this point, I was deep in the CTF solver's classic trap—overthinking. The flag was right there, readable in the UTF-8 view, but was it really this easy? The format specification had me second-guessing everything.
So what then???
At this point, all the root formats I keyed in were wrong, and that is when I decided to look elsewhere: the contract details
The Contract Tab Investigation
Navigating to the Contract tab on Etherscan, I saw a lot of info:
Contract verification status ✅
Source code in all its glory
Contract ABI: The ABI (application binary interface) defines how to interact with the contract and shows all public functions and their parameters, which is crucial for understanding contract functionality and helps identify potential interaction points.
Contract Creation Code & Deployed Bytecode
And most importantly, .... Constructor Arguments! 👀
What We're Looking At
The Hex String (626c30...
)
This is a hexadecimal representation, and each pair of characters represents one byte. This is how data is actually stored on the blockchain.
When converted to ASCII/text, gives us our flag
The Decoded View (445178126083231162
………)
Shows the argument as a uint256 (big number)
This is the same data but represented as a decimal number
When converted to hex, it gives us back our original hex string!
The Data Flow
Hex -> ASCII: 626c30... -> bl0ck_3xpl0r3rs_3xp0s3_4l0t_init
Hex -> Decimal: 626c30... -> 44517812608...
Decimal -> Hex: 44517812608... -> 626c30...
bl0ck_3xpl0r3rs_3xp0s3_4l0t_init
Why Both Numbers Mean the Same Thing
The hex string is how computers store text. The big decimal number is just another way to represent the same data
It's like writing "ten" vs "10" vs "A" (in hex)—same value, different representations
The Moment of Realization 🤯
There I was, staring at my screen for what felt like hours, when suddenly...
bl0ck_3xpl0r3rs_3xp0s3_4l0t_init
Wait a minute... I've seen this before! This is the EXACT same text from the input data field!
My brain went into overdrive.
"Is this... could this be...?"
"The format... it doesn't match r00t{xxxxx_xxxxxxx_xxxxx}
!"
"I must be missing something..."
My inner monologue was going wild:
"The author clearly specified the format..." "This string is too long..." "But it appears TWICE in different places..." "What if... what if the format itself is the trick?"
With trembling fingers, I typed:
r00t{bl0ck_3xpl0r3rs_3xp0s3_4l0t_init}
Click…
And then...
CORRECT!!! 🎉
"WAIT... WHAT... HOW... THE AUTHOR PLAYED US!!!"
It hit me like a ton of bricks. The format specification was a DELIBERATE MISDIRECTION! While I was trying to fit the square peg in a round hole...the flag was right there, completely intact! The author had masterfully planted a fake format to make us overthink! 🎭
The truth? Just wrap the whole thing in r00t{}
! Sometimes the obvious answer IS the answer, and most importantly,... TRUST YOUR INSTINCTS!
A Note to the Challenge Author(c0deg33k)
Slow clap…..
Well played, mysterious challenge creator... well played indeed. You got us all with the oldest trick in the book—making us overthink by providing "helpful" format guidelines.
The ‘Grandpas’ chchallenge—OSINT
"We lay the groundwork for blockchain. Hope you know our names." Flag Format: r00t{Xxxxx_Xxxxxxx_Xxxxx}
(last names)
Let me be real with you—when I first saw this challenge, my mind went blank. As a Web3 developer focused on smart contracts and current blockchain tech, I had a knowledge gap about the historical figures who laid the groundwork.😅
Initial Thoughts
"Grandpas... okay, we're looking for older figures"
"Groundwork for blockchain... must be pre-Bitcoin era."
"Multiple names needed based on format."
frantically opens Google 🏃♂️
The Google-Fu Phase
My search journey:
"Blockchain original inventors"
"Blockchain groundwork creators"
And there they were! Three names kept popping up in every article:
Stuart Haber
W. Scott Stornetta
Dave Bayer
The pieces started falling into place:
These three cryptographers created blockchain concepts in 1991-1992
They solved the timestamping problem
Their work was literally cited in the Bitcoin whitepaper!
The challenge name "Grandpas" suddenly made perfect sense
Crafting the Flag
Looking at the format requirements:
Needed last names
Proper capitalization
Underscore separation
Result: r00t{Haber_Stornetta_Bayer}
Click…
And then...
CORRECT!!! 🎉
Conclusion
What started as a night of confusion turned into an incredible journey of discovery.
A massive shoutout to P3rfe3ctr00t for organizing this CTF! The challenges were well-crafted, engaging, and honestly, addictive. Even when I was seeing more dust than flags (K, you know what I mean 😉), I couldn't stop clicking through and investigating each challenge.
As for the future? I'm eagerly awaiting the next CTF, hopefully with more Solidity /Web3 challenges that I can sink my teeth into.
See you at the next CTF! Maybe next time I'll see less dust and capture more flags! 🏁
Subscribe to my newsletter
Read articles from Njerii directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
Njerii
Njerii
Rust Intern @SupraOracles| Founder @Ledgerlady|I am going to slay the dragon