runZero Coin Challenge

Sat 01 July 2023

I had this challenge in my browser tabs for months while I kept deluding myself about starting it; since it wrapped up at the end of June I forced myself to sit down and knock it out. I finished a lot faster than I expected; since I'm always lacking for content on my blog I decided to write up my process.

Part 0

The landing page for this challenge was a bit too straightforward for me, pointing at the clue so hard they might as well have linked to the next page directly:

The clue on the landing page of the challenge; it points to the website fpnaf.com

I checked the page's source for any comments that might hold additional information, but it was clean; it wasn't difficult to deduce the Roman numerals represented letters, and with little effort I had the address of the next puzzle.

Part 1

The address led to a sparse page, holding a few words and an outsized image.

The landing page of fpnaf.com

Checking the source for surreptitious comments also turned up nothing, so I took a look at the image with strings and a hex editor just in case, but both turned up nothing. As a bit of a longshot I checked HTTP request/response headers for anything unusual along with cookies and localstorage for clandestine information; everything looked perfectly normal. [1]

Sufficiently convinced I wasn't falling for some misdirection by the organizers, I decided to take the hint on the page and check robots.txt for anything the organizers didn't want Google to see:

# OK, cool, no robots allowed beyond this point... Humans only, please!
#
# /30a70cfed77efbf7.html

Part 2

The next puzzle was also on a fairly empty page, but this one has a black background instead of a white one to switch things up a bit.

The page for part 2 of the challenge; it says "Accidentally dropped my 'things I appreciate' notes around here... And it looks like I forgot to leave a light on..."

At least digging through the source of the pages was starting to pay off - the comment attention: 13 turns coming up ahead...! suggested some ROT13 shenanigans, but the table hidden in the background was definitely more interesting.

Inspecting the table more closely, I saw it was composed of a bunch of cells with a background color of #000000 or #000001, and it was obvious where to go from here - this is obviously binary, just get it back into text, etc - which is why I was surprised when the first byte of the subsequent binary had a 1 as its most significant bit, meaning there was no way this was ASCII text. I'll admit I clung to the secret binary theory longer than I should have, but the number of bits was evenly divisible by eight and there's no way that was a coincidence. [2] I started thinking of what kind of tricks someone making a CTF would use - maybe the bits were reversed, maybe the bits were inverted - but I could never get to UTF-8 or ASCII.

After half an hour of beating my head against a wall, I decided to change my strategy: the table has five rows of data, so what if I'm supposed to use five-bit vertical groups? So long as the numbers don't get too big, this puzzle might be a repeat of part 0. I checked the value of the first group - 31; this theory's busted too. OK, the values can't be base64 as that would require six-bit groups, but perhaps it might be base32? No good - mapping to the base32 alphabet and using a decoder still only generates garbage. At this point I was trying any five-bit encoding I could find, going as far back as ITA-2 and Baudot encodings, with no success.

After beating my head against a wall for another 45 minutes, I wondered what the table looked like if I lit up the cells with a background value of 1.

The cells in the table read "Network visibility!" and provide the location of the next puzzle

Oh. Well, at least I can put this behind me now.

Part 3

The page for the next puzzle was also barren, with little besides a link to a zipfile I noticed was password protected [3]. Inspecting the source, I found the comment ...and what is up with the hostname of this URL???. After drastically overthinking the last puzzle I decided to take a simpler approach this time around; putting the two comments I found in the HTML together, it wasn't too difficult to figure out the zipfile password was the ROT13 of the URL.

The contents of the zipfile were a number of single-byte files with names in hex; considering it looked like there weren't any gaps in the numbering it seemed like I was supposed to take the data out of each file in name order and do something with it, but that looked tedious so I made a computer do it for me:

from base64 import b64decode
from pathlib import Path
p = Path('~/runzero').expanduser()
s = ''
for f in sorted(p.rglob('*/0x*'), key=lambda p: int(p.name, 16)): s += f.read_text()
b64decode(s)
b'fingerprints! /dc96a60581d0c6f6.html'

Part 4

The next puzzle was simple like the rest - just a bunch of small pictures with the obvious goal of reordering them to create a cohesive big picture leading to the next puzzle.

A collection of small images in a seemingly random order

Since each image had an alt attribute with a number, I again took a wild guess and decided to arrange them by that number. Once more, the solution was tediously evident so I made a computer do it instead:

from pathlib import Path
p = Path('~/runzero/dc96a60581d0c6f6.html').expanduser()
hti = p.read_text()
pics = {int(i): d for (d, i) in findall("(?<=src=\")([^\"]+)\" alt=\"(\d+)\"", hti)}
s = '<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"></head><body><center><table><tbody>'

for i in range(40):
        if not i:
                s += '<tr>'
        s += f'<td><img width="50px" alt="{i+1}" src="{pics[i+1]}"></td>'
        if i % 10 == 9:
                s += '</tr>'

s += '</tbody></table></center></body></html>'
p.write_text(s)

At this point I just needed to look at the local copy I made of the page:

The images combine to read "Cyber hygiene!" and provide the location of the next puzzle

Looks good to me, let's keep moving.

Part 5

The next puzzle had the most barren page of all as a single image of the reversed phrase "asset inventory", but the page source had a comment that linked to a comment in a YouTube video suggesting an audio component. After downloading the image I noticed it used more space than I expected, so I threw it into a hex editor and noticed the image portion of the PNG ended way before the file itself. Scrolling past the end I found the file was surprisingly friendly:

A random "HELLO" in the middle of the file

Scrolling even further, I find the file has a bit more to say:

A header for a WAV file inside a PNG image

After stripping the WAV file from the image for a listen, what do I find? Morse code, UGH. I obviously wasn't going to take the time to translate this myself so I immediately started looking for a webapp on the Internet to do it for me. Thankfully I found one, but it must've not be very good since I couldn't get anything legible out of it:

The Morse decodes to "QEARDOØ7808OREŬADRREGSANEHTTIMVDSUANADL7BNHD0QU9UŁMOÆOREŬADROLAIOTOWESNEFPSEWAEFFNHÆEHTUEBFOSDOQ,STNRWAOÆ"

Since this was the only Morse audio decoder I could find I tried to make it work, tweaking the frequency range and volume thresholds in the case it wasn't hearing things correctly, but I couldn't stop getting output I knew was wrong. With no other ideas I tried manually translating the Morse myself, but some of the sequences in the audio didn't match any English letters, numbers or punctuation. [4] The YouTube comment mentioned lossy decoding, but WAV files are uncompressed 16-bit PCM.

After beating my head against a wall for another 15 minutes I realized I'd missed the hint staring me in the face: the puzzle picture was reversed because the audio was also reversed. Flipping the audio around was the last insight I needed to solve the puzzle and complete the challenge:

The Morse decodes to "CONGRATS,YOUSOLVEDTHECHALLENGES!PLEASEGOTOINFO.RUNZERO.COM/D1DY0UHAV3FUNANDSUBMITTHEANSWER:RUNZERO2023JOURNEY"

Conclusion

Now that I've completed this challenge and earned my coin, what are my parting thoughts? Honestly I wish it was a lot harder - the landing page told me to expect "cryptography, ethical hacking, and secret clues", but the closest I got to a cryptography challenge was some ROT13 and the closest I got to ethical hacking was looking at robots.txt. Maybe my expectations are high after working on Microcorruption lately, but as a confirmed crypto dork if you tell me to expect cryptography challenges I will be upset if I don't get one (1) hash collision challenge and one (1) AES challenge, but I would accept a Chinese Remainder Theorem-based RSA challenge as a replacement. [5]

Now that I think about it, the most valuable gift I got from this challenge might be the wrong ideas I came up with - using US-TTY codes for a CTF challenge sounds kinda cool. At least I got a challenge coin for my troubles.

The coin I got for completing the challenge; the rear of the coin matches the coin drawing on the landing page and the word "transparency" on the front of the coin is misspelled

[1]I did this for the rest of the puzzles as well; there was never anything worth bringing up so I'll only point it out once here.
[2]Narrator: It was a coincidence.
[3]Or maybe that's what they wanted me to think, since .zip is a TLD now it could be a social engineering trick.
[4]I used Wavacity to read the waveform since I was too lazy to install Audacity at the time.
[5]"Crypto", of course, meaning "cryptography"; if these tech bros want this hill they will have to drag my lifeless body off it.