P3RF3CT ROOT CTF 2024 Writeup - Web3 and OSINT

NjeriiNjerii
8 min read

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! 🏁

15
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