Ramblings of a Tampa engineer
Logo and design by https://www.ransomwear.net

As May 17, 2025 came to an end another iteration of the BSides Tampa conference had ended. I'm always amazed how a great group of people (The ISC2 Tampa chapter) can put on an event each year raising the bar each time.

One of these years I hope for my talk to be accepted, so this year I was just attending as a regular attendee excited to hear some great talks.

One thing I haven't really focused too much is the challenge that exists on each and every badge given. This year I wanted to spend some time attempting to solve the challenge, but pretty quickly I ran into an Xbox friend from a long time ago and we caught up on some stories instead. Always a great time to catch up with someone from the era of Halo 2-3, Xbox, Xbox 360 and all the drama that occurred during that era.

That is the benefit of these conferences - they are built for everyone to take their own track through them. You could attend a talk for every hour and head home full of knowledge. You could experiment with the vendor hall, the villages or various challenges throughout the event. You could socialize with folks and network around. You could take part in any or all of the 3 different CTFs present. You could grab lunch from one of the 4 different lunch options present. The point saying that everyone can schedule out the day to fit their preference from a large variety of things.

So once I got home I decided to take the puzzle for a spin and wanted to document it for others who might be confused on how these puzzles work.


Spoilers ahead. Do not read if you wish to solve the 2025 badge CTF on your own.
BSides Tampa 2025 Badge

Each attendee got the badge above which when attached with a battery had some light up LEDs to brighten the badge. As you scan the board you can see some text hiding right before the bells that looks a bit like gibberish.

bWlkbulnaHQuY29kZXMK

That text was above and pretty quickly an obvious first check to do is base64 decoding.

➜ echo "bWlkbulnaHQuY29kZXMK" | base64 -d     
midn?ght.codes

This resulted in a domain that decoded to some Unicode spelling, but pretty quickly this was obviously "midn[i]ght.codes". The first option was "EASY" or "HARD" and this being my first badge challenge I took the easy route.

The homepage.

However, this was not my first CTF so it made sense what was going on pretty quickly. The pattern had started to evolve that each page had the hint of the flag at top of like - ./________.____, then the hint below. So you knew exactly what format you were trying to obtain which went into the URL.

The first hint was eharpbqr.ugzy which after doing enough Cicada 3301 puzzles is clearly some iteration of .html so lets just apply every single ROT cipher (0-25) and see if anything makes this readable.

ROT-12: qtmdbncd.gslk
ROT-13: runecode.html
ROT-14: svofdpef.iunm

Sure enough ROT-13 spelled out the domain and we were on our way. The next puzzle just had an image dropped in front of us.

Easy - Stage2

With any image the first thing I do is head to FotoForensics and upload it for investigation. It didn't take long exploring the tabs of this tool to find the flag was injecting directly into the image data.

Strings @ https://www.fotoforensics.com

Stage 3 offered a password prompt, but as I entered various characters I saw no network calls go outbound. So this code had to be client side and it didn't take long to find it.

const encoded = "637962657274656d706c65";

function encode(str) {
  return Array.from(str)
    .map(c => c.charCodeAt(0).toString(16).padStart(2, '0'))
    .join('');
}
    
function checkPassword() {
  const input = document.getElementById('password').value;
  const inputHex = encode(input);
    
  const match = inputHex === encoded;
  document.getElementById('result').textContent = match ? "success" : "failure";
}      

Our input is encoded and compared to a hash, so we just have to reverse this hash to identify the password which according to the hint is ./___________.html We can tell our input is converted into the value of the ASCII character then converted to hex (padded to 2 characters).

In the era of AI, I could write a script to explode that string into pairs, undo the hex to code point to character, but also AI could as well. So I gave it the original function and asked AI to reverse it in 1 line.

(hexString => [...Array(hexString.length/2)].map((_, i) => String.fromCharCode(parseInt(hexString.substr(i*2, 2), 16))).join(''))("637962657274656d706c65")

Sure enough a little sample script that resulted in cybertemple and we were on to stage 4.

A story with no beginning is hard to read...
DOWNLOAD

I'm not sure if this was supposed to be harder, but I opened up the PDF and the flag was printed in plain text in it.

./nullobelisk.html

Stage 5 offered a hint of bGFuZ2lzY2lsZXIK, but the .html was already known.

➜ echo "bGFuZ2lzY2lsZXIK" | base64 -d
langisciler

With another simple test of base64 we had a human readable string that wasn't right. However, I don't think "langisciler" is a real word. However reading it backwards was "relic" so I just applied rev and ran it again.

➜ echo "bGFuZ2lzY2lsZXIK" | base64 -d | rev
relicsignal

This led us to stage 6 which just offered an SVG image and a hint. I intentionally changed the colors closer to white to make it more visible.

Stage 6 image

While on that journey to brighten up this image I thought a chunk of the SVG was intentionally hidden or the password was hidden among a layer. A by-product of wanting to brighten the image led me to discovering the flag.

Stage 6 - Firefox Dev Tools

As I was sitting there brightening the colors I saw two properties that my browser claimed were invalid (stroke-start and start-end). The values for both of those seemed like they were telling a story of hexmonolith - which had no such luck of working.

I encoded monolith into hex and tried a few other iterations, but then I tried removing /easy from the URL and put hexmonolith at the root and it worked. Had I just looked at the hint at the top of the page that had ../ (go back 1 directory) I would have known that, but I was blind to that aspect after a few stages of this.

I guess the easy and hard parts had connected based on that change. Now stage 7 was asking me to download a file called EchoChamber.zip

The truth is buried in layers.
Unwrap it carefully...
DOWNLOAD

However of course it wouldn't be that easy and the .zip file had a password on it. It seemed the flag was inside so I just needed the password. I went back to the HTML page and saw a little HTML comment hiding on the page.

<!--  4*xEhJo  -->

Sure enough that password worked and we were on our way to stage 8.

➜ unzip EchoChamber.zip -d Stage8
Archive:  EchoChamber.zip
[EchoChamber.zip] Layer1.zip password: 
 extracting: Stage8/Layer1.zip       
 extracting: Stage8/flag.txt         
  inflating: Stage8/log.txt          
  inflating: Stage8/log copy.txt 

With another zip file and associated files, I knew the flag was going to be false and it was.

➜ cat flag.txt 
Nice try; but you're not done yet.%  

This time we had 2 large files called log.txt and log_copy.txt and I wondered what made them different. So I opened up Araxis Merge to get a diff report and sure enough there was 1 tiny change between the files.

Stage 8 log files.

This differences between the files were:

  • log.txt - ВΤо𝟛еΙυ
  • log copy.txt - BTo3eIu

It seemed the plaintext password was the password. This led to once again another layer.

➜  Stage8 unzip Layer1.zip -d Stage9
Archive:  Layer1.zip
[Layer1.zip] flag.txt password: 
 extracting: Stage9/flag.txt         
 extracting: Stage9/Layer2.zip       
  inflating: Stage9/communications-extraterrestrial-intelligence.pdf 

Before I even investigated the .pdf file I listed out the next zip file and some random text spewed out into my console.

➜ unzip -l Layer2.zip 
Archive:  Layer2.zip
V2+m^o6
  Length      Date    Time    Name
---------  ---------- -----   ----
       23  05-06-2025 23:22   flag.txt
   778106  05-06-2025 23:02   Layer3.zip
     2303  05-06-2025 23:20   d867ffd.eml
---------                     -------
   780432                     3 files

I wondered if those characters were the password and funny enough - it was. So now I was onto stage 10 with a new set of files and another false flag. This time we had an email that appeared to be someone requesting the password from IT again.

Stage 10 email

Nothing looked obvious in the email, so I dumped the headers of the email to investigate the more hidden aspects of it.

Return-Path: <diane.mcpherson@cryptforge.local>
Received: from mail.cryptforge.local (mail.cryptforge.local [192.168.1.50])
    by smtp.cryptforge.local (Postfix) with ESMTP id 9C2A3123456
    for <it-support@cryptforge.local>; Mon, 06 May 2025 08:14:35 -0400 (EDT)
Date: Mon, 06 May 2025 08:14:33 -0400
From: Diane McPherson <diane.mcpherson@cryptforge.local>
To: IT Support <it-support@cryptforge.local>
Subject: Re: Password for Layer3.zip (again...)
Message-ID: <20250506081433.12345@mail.cryptforge.local>
MIME-Version: 1.0
Content-Type: multipart/alternative;
    boundary="----=_Part_9847_482132571.1714996473000"
X-Mailer: CryptForgeMail 2.1
X-L3Z-Encoded: 4734652637407a
X-User-Complaint-Level: 2
X-Meta-Tag: routine follow-up
X-Auth-Check: passed

There was a lot of data here that seemed like it was possible to be hiding a secret, but this X-L3Z-Encoded stood out. It wasn't base64 and since I didn't see any letter outside of a-f I figured it was hex.

Thankfully still had xxd memorized from the Cicada 3301 puzzles so another quick command to turn this back into ASCII.

➜ echo "4734652637407a" | xxd -r -p
G4e&7@z%  

This indeed was the password and unzipped once again another layer that I referred to as stage 11.

➜ unzip Layer3.zip -d Stage11
Archive:  Layer3.zip
[Layer3.zip] flag.txt password: 
 extracting: Stage11/flag.txt        
 extracting: Stage11/Layer4.zip      
  inflating: Stage11/logo.jpeg  

This was the same deal with another fake flag and this time an image (the image you see as the featured image in this blog). Like the last image I opened it up in FotoForensics and went exploring.

Stage 11 via https://www.fotoforensics.com

This didn't take long to see the embedded exif data for "Comment", which was the password to another layer.

➜  Stage11 unzip Layer4.zip -d Stage12
Archive:  Layer4.zip
[Layer4.zip] flag.txt password: 
 extracting: Stage12/flag.txt        
 extracting: Stage12/Layer5.zip  

It seemed the puzzle was continuing, but the flag contained a URL that led to the end of the puzzle. I wondered if the Layer5 zip had another direction of the puzzle, but the flag.txt was the same size outside and inside the zip. I figured since the puzzle ended with the URL contained in flag.txt that I was done.

Stage12 website.

I probably should have picked the hard option, but that was my first badge CTF ever so I wasn't sure what to expect. There was also a DnD and regular CTF at this event so 3 unique different CTFs at once.

I enjoy making CTFs as much as doing them so maybe next year I can help out making one that increases in difficulty while guiding all levels of attendees to attempt it. I made one CTF way back in 2021 for work for all engineers to attempt, so it has been a solid few years since I've made one.

I'm excited every year for BSides and we have BSides Tampa, BSides Orlando and BSides St.Pete all within driving distance of myself. Too bad this year I'll be out of town for both St.Pete & Orlando.

You’ve successfully subscribed to Connor Tumbleson
Welcome back! You’ve successfully signed in.
Great! You’ve successfully signed up.
Success! Your email is updated.
Your link has expired
Success! Check your email for magic link to sign-in.