IRCPuzzles is an IRC-based puzzle game hosted every year on April 1st. The event lasts for several days. The answers are keys to channels, and contestants progress from the first level to the final level. Since I enjoy solving puzzles and challenges, I participate in this event every year. Cluelessly staring at a vague hint for hours, relentlessly going down multiple rabbit holes, the joy of finally finding a solution—what’s not to love? It’s a delightful blend of frustration and fascination, much like a typical day in information security. If you’re curious, check out my writeup from the 2022 event.
This year, I decided to dive back into the fray, armed with a fresh cup of coffee and an unhealthy amount of optimism. After all, what’s the worst that could happen? Well, aside from the inevitable late-night puzzle-solving sessions and the existential dread of misinterpreting a clue—nothing I haven’t handled before. Naturally, I decided to check out this year’s puzzles.
For those who might not be familiar with IRC, it’s an old-school, text-based chat system that still has a passionate following. Here’s a quick rundown of the terms:
- IRC: A text-based chat system for real-time communication.
- Channel: Think of it like a chat room or group.
- Topic: The title or main subject of an IRC channel, where the puzzle clues are usually posted.
- Key: A secret passcode needed to access the next level’s channel, obtained by solving the current level’s puzzle.
The answers are always alphanumeric, stripped of spaces and punctuation, so Hello World!
would become helloworld
.
I participated in the event back in April, but it’s taken me until now to write it up. Nevertheless, the memories are still fresh.
Table of Contents
⚠️ This post is quite lengthy (the scroll bar is barely visible at this point), so feel free to jump to the challenges that interest you the most. The ones tagged #favorite
are my favorite levels.
Base Track
Level | Tags |
---|---|
Level 0 | #start (first level) |
Level 1 | #basics |
Level 2 | #hiragana |
Level 3 | #cryptography, #isostandard |
Level 4 | #miscellaneous |
Level 6 | #end (last level) |
A Track
Level | Tags |
---|---|
Level A1: Secret Snake | #cryptography, #steganography, #secrets |
Level A2: Scary Hallways | #videogames, #javascript, #favorite |
Level A3: A Hundred Fujis | #steganography, #art |
Level A4: Base64 Nonsense | #cryptography, #base64, #rsa |
Level A5: The Afterparty | #space, #astronomy |
Level A6: A Perspective Shift | #steganography, #history. #favorite |
Level A7: The Spiral | #science, #genetics |
Level A8 | #miscellaneous |
B Track
Level | Tags |
---|---|
Level B1: Tetris | #cryptography, #tetris, #favorite |
Level B2: Secret Code | #videogames, #konamicode |
Level B3: Thor’s Puzzle | #history, #mythology, #runes, #cryptography |
Level B4: Peeling Off Layers of Crypto | #cryptography, #cipher, #tor |
Level B5: Decoding Space Gibberish | #space, #cryptography, #nasa |
Level B6: Bad Apple | #video, #illusions, #stereogram, #favorite |
Level B7: Jörð’s Puzzle | #history, #mythology, #cryptography |
Level B8 | #miscellaneous |
Level 0
This is where the event begins. I joined the channel and found the following channel topic:
#ircpuzzles https://ircpuzzles.org/ | IT HAS BEGUN! join #ircpuzzles-2024-afpc-01 with the key ‘puzzle’ | Theme: Best Of ircpuzzles | #ircpuzzles-chat for off-topic chat | Rules: https://ircp.link/rules | Tips: https://ircp.link/tips
The answer (key to the next level) was puzzle
. I joined level 1 with /join #ircpuzzles-2024-afpc-01 puzzle
.
Level 1
Channel topic:
Welcome to Level 01: Intro [wolf] || If the key you find contains spaces, drop them. Join #ircpuzzles-2024-afpc-02. The key is “next level”. || If the next level to join is not explicitly named, it is always the number of the previous level + 1.
The answer was nextlevel
. This is another easy level added to familiarize the newer players with the answer format (no spaces/dashes, etc.).
Level 2
Channel topic:
#ircpuzzles-2024-afpc-02 Welcome to Level 02: Anniversary [wolf] || This year is #ircpuzzles’ 10th Anniversary! 🎉 || Expect a potpourri of past themes and check out: https://ircp.link/Ej0Zng || Clues: れ🚌
It’s the 10th anniversary of IRCPuzzles, so this year’s theme combines elements from all previous years. Interesting!
The clue is れ🚌
. Googling for れ
gave me:
The hiragana syllable れ (re). Its equivalent in katakana is レ (re). It is the forty-second syllable in the gojūon order; its position is ら行え段 (ra-gyō e-dan, “row ra, section e”).
Combining that with the bus emoji, I tried rebus
as the answer, and it worked!
Level 3
Channel topic:
Welcome to Level 03: Hard Hat [wolf] || Theme: Cryptography || Clues: https://ircp.link/78FmxZ
It looked like a bunch of random symbols. I tried to look for discernible patterns but couldn’t find anything obvious. I did a reverse image search on some of the symbols and found they are ISO 7010 hazard symbols.
The red symbols were “Prohibition” symbols, and the blue ones were “Mandatory” symbols. For instance, the mandatory symbols looked like this:
In the original clue image, I noticed there were groups of two and three. This probably meant it was a message in English (the
is three letters, and is
is two letters). In the Wikipedia section, I noticed each symbol also had a name: M001
, M002
, and so on. They could correspond to alphabet indexes (A=1, B=2, and so on). I decided to test that theory out.
I manually transcribed the clue to the following:
symbols = '''
P025 – Do not use this incomplete scaffold, M015 – Wear high visibility clothing, M021 – Disconnect before carrying out maintenance or repair, SPACE, P023 – Do not obstruct, M015 – Wear high visibility clothing, M021 – Disconnect before carrying out maintenance or repair, M012 – Use handrail, M004 – Wear eye protection, M014 – Wear head protection, M020 – Wear safety belts
P002 – No smoking, M005 – Connect an earth terminal to the ground, M012 – Use handrail, M009 – Wear protective gloves, M005 – Connect an earth terminal to the ground, M022 – Use barrier cream, M005 – Connect an earth terminal to the ground, SPACE, P013 – No activated mobile phone, M005 – Connect an earth terminal to the ground, SPACE, P009 – No climbing, M006 – Disconnect mains plug from electrical outlet, SPACE, P009 – No climbing
P020 – Do not use lift in the event of fire, M015 – Wear high visibility clothing, M012 – Use handrail, M004 – Wear eye protection, SPACE, P025 – Do not use this incomplete scaffol, M015 – Wear high visibility clothing, M021 – Disconnect before carrying out maintenance or repair, SPACE, P020 – Do not use lift in the event of fire, M008 – Wear foot protection, M001 – General mandatory action sign, M020 – Wear safety belts, SPACE, P020 – Do not use lift in the event of fire, M008 – Wear foot protection, M005 – Connect an earth terminal to the ground
P011 – Do not extinguish with water, M005 – Connect an earth terminal to the ground, M025 – Protect infants' eyes with opaque eye protection, SPACE, P006 – No access for forklift trucks and industrial vehicles, M015 – Wear high visibility clothing, M018 – Wear a safety harness, SPACE, P020 – Do not use lift in the event of fire, M008 – Wear foot protection, M005 – Connect an earth terminal to the ground, SPACE, P014 – No access for people with metallic implant, M005 – Connect an earth terminal to the ground, M024 – use this walkway, M005 – Connect an earth terminal to the ground
P012 – No heavy load, M005 – Connect an earth terminal to the ground, M022 – Use barrier cream, M005 – Connect an earth terminal to the ground, M012 – Use handrail, SPACE, P009 – No climbing, M019 – Wear a welding mask, SPACE, P018 – No sitting, M005 – Connect an earth terminal to the ground, M001 – General mandatory action sign, M012 – Use handrail, M012 – Use handrail, M025 – Protect infants' eyes with opaque eye protection
P023 – Do not obstruct, M005 – Connect an earth terminal to the ground, M009 – Wear protective gloves, M018 – Wear a safety harness, M004 – Wear eye protection
'''
For P025 – Do not use this incomplete scaffold
, I needed to get P025
, then 25
, and then convert that to the corresponding alphabet, which is Y
(Y
= 25). I quickly wrote the following code:
# Alphabet mapping
alphabet_mapping = {index + 1: letter for index, letter in enumerate(string.ascii_uppercase)}
for line in symbols.strip().splitlines():
for symbol_line in line.split(', '):
if symbol_line == 'SPACE':
print(' ', end='')
else:
# The format is "MP<number> – <description>"
index, _ = symbol_line.split(' – ')
# Remove the letters M, P from the start of the symbols and convert to int
index = int(index.lstrip('MP'))
print(alphabet_mapping[index], end='')
The output looked readable!
YOU WOULDNTBELIEVE ME IF ITOLD YOU THAT THEKEY FOR THE NEXELEVEL IS REALLYWEIRD
I tried reallyweird
as the answer, and that worked. Neat!
Level 4
Channel topic:
Welcome to Level 04: The Fork [wolf] || You are through the looking glass. There are two doors here, one leading to #ircpuzzles-2024-afpc-A1 labelled “jabber”, and one leading to #ircpuzzles-2024-afpc-B1 labelled “wocky”. There is also a trapdoor to 07 here, but you have a feeling you need to beat the other levels to figure out its key.
It looked like the paths diverged here into two tracks: A and B. This allows people to switch tracks when they get stuck on one.
I joined #ircpuzzles-2024-afpc-A1
with jabber
as the key and #ircpuzzles-2024-afpc-B1
with wocky
as the key.
Level A1: Secret Snake
Channel topic:
Welcome to Level A1: Lost Pet [za3k] || Theme: Cryptography || Clues: https://ircp.link/mqOBfS | Who is S.S.S.S.? | Hope you speak parseltongue | You don’t need all the secret messages | For technical reasons, drop the final 2 letters from the answer.
The Clue Image
Here’s the image that kicked off the puzzle:
The snake was saying something, and given the cryptography theme, I suspected there were multiple layers to this mystery. A quick search for ssss cryptography
led me to Shamir’s Secret Sharing. The SSS
part fit, but since I hadn’t used any hidden messages, I figured this was a red herring. It was time to decode some parseltongue!
Decoding Parseltongue
I thought it was only fitting to use Python to interpret a snake’s hisses. I wrote some code to convert the snake’s hissy fit into readable text:
import textwrap
# Original text from the snake's pattern
snake_text = """s SssS ssSsSSsSS
sSsSssSssssSSsSssSs
SSSssSSs S
sSsssssSSssSs
SsSSSsSss
sSsSssSSsSSsSSSssS
SssssSsSSsSsSSsSSss
SsS"""
# Remove spaces and newlines
clean_text = snake_text.replace(" ", "").replace("\n", "")
# Replace 's' with '0' and 'S' with '1' for binary conversion
binary_text = clean_text.replace("s", "0").replace("S", "1")
# Split binary text into 8-bit chunks and convert to ASCII
ascii_message = ''.join(chr(int(chunk, 2)) for chunk in textwrap.wrap(binary_text, 8))
print(ascii_message)
The script produced:
ImHisPetSnake
Bingo! But there were more secrets to uncover.
Reverse-Searching for More Clues
Instead of eyeballing the image, I did a reverse image search to find the original and compare it with the tampered version using GIMP. This revealed some Morse code on the snake’s neck:
Translating the Morse code:
... .... .- -- .. .-.
Which spelt SHAMIR
. That was not very useful. I kept looking.
Uncovering Hidden Messages
The snake had cryptic markings on its lower body:
These markings were like a jigsaw puzzle. I overlaid the bits in GIMP and managed to read the following:
Then, I added some more overlays:
It revealed: Adi is the crown prince in disguise
.
I had ImHisPetSnake
, SHAMIR
, and Adi is the crown prince in disguise
. But how did these connect with cryptography? I kept looking for more hidden secrets.
Decoding the Image Filename
The filename 2hhbWlyIGNhbid0IHJlYWQ=.jpg
was base64. I decoded it:
$ echo "U2hhbWlyIGNhbid0IHJlYWQ=" | base64 -d
Shamir can't read
It was not very enlightening. But I wasn’t done yet.
Checking EXIF Data
I took a peek at the EXIF data:
$ exiftool U2hhbWlyIGNhbid0IHJlYWQ\=.jpg
ExifTool Version Number : 12.40
<snip>
Comment : Shamir is actually twin brothers
Piecing It Together
After a lot of trial and error, I realized I was overthinking. The clue said to drop the last two characters due to a technical limitation. That likely meant the IRCD had a maximum channel name length. I gave it one more shot with the following:
ShamirsSecretSharingSna
That worked!
The design of this puzzle felt a bit off. It was disappointing to realize that I had the correct answer within a few minutes, yet I still embarked on a wild goose chase to uncover other secrets, which ultimately didn’t help. After finally solving it, I was ready for the next challenge. Hopefully, one that’s less slithery.
Level A2: Scary Hallways
Channel topic:
Welcome to Level A2: Hallways [wolf] || Theme: Video Games || Clues: https://ircp.link/St3bG9
I opened the link and was greeted with an intriguing page:
It looked fascinating! Naturally, I started testing some commands:
I needed to find all the points of interest and then do something with them. Channelling my inner hacker, I poked around the application to see if I could find a shortcut.
Inspecting the page source, I found this script:
<script src="interpreter/Hallways.gblorb.js" type="text/javascript"></script>
It contained a huge base64 string:
$(document).ready(function() {
GiLoad.load_run(null, 'R2x1bAA...AAACAAAAAAA==', 'base64');'
I decoded the base64 and ran strings
on it, expecting to find useful game text. Instead, I got a bunch of random words, none matching game terms like note
or dot
. I concluded that the strings were either dynamically generated or encoded differently.
After decoding and running strings
on the base64 content, I didn’t find the exact terms used in the game’s interface. This discrepancy led me to think there might be some level of obfuscation or that relevant strings were dynamically generated or encoded differently within the game script.
Next, I downloaded Quixe 2 (the game engine) source code and set up an instance locally, loading it with the Glblorb file. I then dove into the JavaScript code to see how it handled these strings. Using Chreme Developer Tools, I traced the function calls to GiLoad.load_run()
, which was responsible for initializing the game state from the decoded data. However, this quickly turned into a rabbit hole.
Deciding to take a different approach, I wrote a simple JavaScript snippet to automate sending commands to the game console to navigate through the hallways programmatically:
function simulateKeyPress(character) {
jQuery.event.trigger({ type: 'keypress', which: character.charCodeAt(0) });
}
function explore(direction) {
document.getElementById('win716_input').value = direction;
simulateKeyPress('\r'); // "Enter" key
}
function startExploration() {
const directions = ['north', 'south', 'east', 'west'];
let currentDirectionIndex = 0;
const explorationInterval = setInterval(() => {
if (currentDirectionIndex >= directions.length) {
console.log('Exploration complete');
clearInterval(explorationInterval);
return;
}
explore(directions[currentDirectionIndex]);
console.log(`Moved ${directions[currentDirectionIndex]}`);
currentDirectionIndex++;
}, 1000);
}
startExploration();
The script aimed to move in all cardinal directions from a starting room and log the changes. While basic navigation worked, handling the dynamic game state was too complex.
So, I decided to explore manually. How large could these hallways be?
I started moping^Z mapping the hallways and noticed some plaques:
>west
F1
You are at the western end of a west-east hallway.
You can see a weathered plaque here.
>examine plaque
The plaque crudely depicts a statue holding a globe with an eagle in its right hand and a staff in its other. The title below reads: The Republic, Chicago 1893
After some time navigating, I managed to map it out:
There were plaques (light yellow) and geometric shapes (blue). This was what each plaque and shape contained:
Cell | Description |
---|---|
C1/D2 | Mammoth Cave National Park - has been designated a World Heritage Site and joins a select list of protected areas around the world whose outstanding natural and cultural resources form the common inheritance of all mankind October 27, 1981. |
C3/G3 | Equilateral Triangle - An equilateral triangle stares back at you menacingly. |
H1 | wooden plaque Far over the misty mountains cold To dungeons deep and caverns old We must away ere break of day To seek the pale enchanted gold. |
D1 | (the digital plaque) emacs -batch -l dunnet |
H5 | A perfectly placed regular pentagon. |
I4/M3 | This red square seems to have been painted with the utmost care |
A3/I1 | NATURAL LANGUAGE, SEMANTIC ANALYSIS AND INTERACTIVE FICTION St Anne’s College, Oxford 10 April 2005; revised 10 April 2006 |
A1/Z7 | The plaque depicts a dark and dirty coastal town eternally shrouded in thick fog and cold rain. The title below reads: Anchorhead, Massachusetts |
P1 | The plaque depicts a multi-purpose robot gleefully playing with a paddleball set. The title below reads: “Floyd” |
Z1 | “WELCOME TO ZORK! ZORK is a game of adventure, danger, and low cunning. In it you will explore some of the most amazing territory ever seen by mortals. No computer should be without one!” |
P3 | A lonely line segment accentuates the otherwise monotonous hallway. |
F3 | A single dot, meticulously positioned and painted. |
F1 | (the weathered plaque) The plaque crudely depicts a statue holding a globe with eagle in its right hand, and a staff in its other. The title below reads: The Republic, Chicago 1893 |
G1 | (the modern plaque) The plaque depicts a lone statue on a pedestal. Below, it says: “Galatea”. |
However, I was stumped. My cries for help echoed through the hallways… Well, not really, but it definitely felt that way.
The plaque at A3/I1 read NATURAL LANGUAGE, SEMANTIC ANALYSIS AND INTERACTIVE FICTION
, which led me to a whitepaper—a significant piece in Interactive Fiction history. I read it, then the Wikipedia article about its author, Graham Nelson. Still, nothing clicked.
After staring at my Google Sheet map, inspiration struck: maybe it was a crossword! But what words? Zork, Galatea, Dunnet, etc., were Interactive Fiction games, but those didn’t fit.
I decided to replay the game from the beginning to see if I had missed anything. Sure enough, my inventory had a crumpled note. I hadn’t used that anywhere. The note said:
MINISTRY OF PUZZLES RELATED TO THE SURNAMES OF IMPORTANT PERSONAGES: ACCIDENT WAIVER AND RELEASE OF LIABILITY FORM
The answers were the creators’ names! After some Googling, I filled out the crossword:
Now, I needed to use the geometric shapes. The letters in the blue cells were: E, O, R, P, S
. I looked at their original values:
Cell | Description |
---|---|
C3/G3 | Equilateral Triangle - An equilateral triangle stares back at you menacingly. |
H5 | A perfectly placed regular pentagon. |
I4/M3 | This red square seems to have been painted with the utmost care |
P3 | A lonely line segment accentuates the otherwise monotonous hallway. |
F3 | A single dot, meticulously positioned and painted. |
Rearranging them by the number of sides (dot, line, triangle, square, pentagon), I got:
PROSE
I tried that as the answer. It worked! That was an incredibly cool level. Kudos to wolf for creating it.
Level A3: A Hundred Fujis
Welcome to Level A3: Photo Gallery [za3k] || Theme: Historic Personalities || Clues: https://ircp.link/k2A3HR | Hints: Nice view.
I opened the URL. It was a photo gallery of pictures of Mt. Fuji.
Some things stood out to me:
- Each image was formatted in a 256x256 resolution (a size significant in computing for various reasons: number of colors in a GIF, number of extended ASCII characters, and number of bits in an SHA-256 hash)
- There were 100 images (I could fold it as 5x20, 25x4, etc.)
- They were all pictures of Mt. Fuji, and their arrangement appeared random with some potentially AI-generated images—this likely meant that the visual content might be a red herring
I wanted to see if the images had something hidden inside. I downloaded all the photos:
wget -r -l1 -H -nd -A jpg,jpeg -e robots=off https://files.ircpuzzles.org/2024/qTTzW60SWcWoo/
After downloading the images, I extracted the EXIF metadata of all the photos using ExifTool and saved them to a text file (it’s fairly common in puzzles to embed clues or additional layers of data in the EXIF metadata):
for i in *.jpg; do exiftool $i >> exiftools.txt; done
I checked the User Comments
field for all the images:
$ grep User exiftools.txt
User Comment : SSUCv3H4sIAAAAAAAACnSSzU7DMAzH70i8Q5Uz09amg23vgDjsiDi4SdaGpfGUpEMI8e44TQMRglv8t/3zVz5ub6qKdeC1YIfqI1pka2MmHxwEjZbk+m7RnbJSOVI2WVFSB3QaTCl2EMRgYVQk2smYKH/OTuYDhMkrH4stkoCgemIk8RuROnpOdpUds5MyyMWOurd+fXwfOzSe3RUBfupiwKP2QhkDVuHkWfansv8wn7pXJcKftOMFXSgw6fGSh4ZeWfH+M0JkOkUrvKoR3FmFchpyNvwblRmk9g4ugxaOstyvhCs1hs6vMbW4PiHKvxBLXEBx/kXg9U98esxDLHdwyihIp3lOOHZ+C8qN5bFgkhqLO11RAC3/UPGCdHFaaNsXaRiGeZ6cJnCywcVtsaZp8raZQbxAZ+K/ORFWZX0A7ylDZr2oJeij4liUshjmGViiMknfK5o1b/hDs615/dDsthu+2y0B6UsOmjhzRxk0b1DLcnodO2Abyest8PsVr6Fbtfv2tNo39OLtqQW53xO7pUV/fgEAAP//AwARYucHXAMAAA==
User Comment : Version 1.0.0
User Comment : SSUCv3H4sIAAAAAAAEAJ2SwW7DIAyG75P2DhHnRgrQNElfpdrBAdqgEqiAbKqqvPsICRrrNE3aLf7s3/i383h9KQrUg5MMHYvHEoVYKjU5b8FLowPGu41bobmwgVSJCC69sRJUDnvwbNAwigD1pNSC55hEzoOfnHDZY9wKGJ2XsZxmtQy8uITm36rTqKc1LlIiJoMipBDaZcxNfWQJzbt/K9ePt2QTLkKz+zLbnE1thRKwOjytpej64YUdo4utCCYuTebq3TBQ7sn/zUom9SWTGT/E9ScZM5P29p5PjpQxN+jVssxz6CkSH8C5UM4Tzxcdjm3G7B1tfDSwdUU8XGIJMSUV6TpSN/saE4IbgtJZDbtK/utZEd63tDo0lKIfP8MggzqaSNLppgxwwb/Qcz9c122NK0zaNZ+OutxslN7/ra0JPWzabCK56BCnvDtzYGXdAC3D5E3ZAq5K3jHATAiK2z74mD8Bn9Ebnz0DAAA=
Among the mundane entries, there were two intriguing base64-encoded strings! I tried to decode them as base64, but that didn’t yield any intelligible results. Further manipulations, such as ROT-N ciphers and splitting or combining the strings, similarly failed.
Then, I found the largest substring between those two base64 texts: SSUCv3H4sIAAAAAAA
. After googling with that string, I found a forum post that contained the exact string. I quickly realized these might be standard additions by photo editing software rather than intentional puzzle elements and redirected my focus.
Taking a step back, I considered the broader context—100 pictures of Mt. Fuji. A quick search for 100 Mt Fuji images
led me to a Wikipedia page about One Hundred Views of Mount Fuji by Hokusai, an iconic series of woodblock prints:
One Hundred Views of Mount Fuji (Japanese: 富嶽百景, Hepburn: Fugaku hyakkei) is a series of three illustrated books by Japanese ukiyo-e artist Hokusai. It is considered one of Japan’s most exceptional illustrated books (e-hon), and alongside the Hokusai Manga, the most influential in the West.[1] The first two volumes were published in 1834 and 1835, shortly after the completion of his seminal Thirty-six Views of Mount Fuji, with a third released in the late 1840s.
That looked very promising! Why did I wait this long to try something so simple? I was confident this was the right path. Now, I just needed to find the correct answer key. The theme for this level was Historic Personalities
. So I tried the author’s name as the answer: Hokusai
. That was the answer!
In life, when faced with a challenge, sometimes, you just need to look at the big picture (or a hundred little ones).
Level A4: Base64 Nonsense
Channel topic:
Welcome to Level A4: Just Nonsense [pavonia] || Theme: Cryptography || Clues: Lq62c8hmBYj+u+56DX== || Hints: Think out of the box | clue is not in base64 | yet | “Watch what everyone else does …”
I dove straight into the clue by attempting to decode the base64 string:
$ echo "Lq62c8hmBYj+u+56DX==" | base64 -d
Surprisingly, this returned nothing. Not one to be easily dissuaded, I thought perhaps something was lurking in the binary shadows, so I piped the output through xxd
to get a hex view:
$ echo "Lq62c8hmBYj+u+56DX==" | base64 -d | xxd
00000000: 2eae b673 c866 0588 febb ee7a 0d ...s.f.....z.
The hexadecimal output did not offer any meaningful insight.
Reflecting on the hints, particularly the phrase Watch what everyone else does...
, which I recognized as a partial quote by Earl Nightingale, suggested the idea of doing the opposite—that was the missing part of the quote.
The hints nudged towards doing the opposite
and thinking out of the box
. The clue was purportedly not yet in base64, which made me scratch my head—what do they mean by yet
? So, I toyed around with the string: trimming characters, flipping it backward, trying different ROT-N ciphers, and even considering more obscure transformations. Sadly, no cigar.
“Think out of the box”—easy to say, hard to do. And do the opposite
wasn’t much help either. It felt like being told to swim by being thrown into the deep end. I played around with ciphers like Atbash, and others, flipping and twisting the string every which way, but still, nothing productive emerged.
Then, the do the opposite
hint struck me differently—what if I was supposed to encode rather than decode? It felt a bit like being asked to push a door marked ‘pull’. But in a fit of “why not?” I tried re-encoding the original clue back into base64:
$ echo "Lq62c8hmBYj+u+56DX==" | base64
THE2MmM4aG1CWWordSs1NkRYPT0K
It spelled out The 2 Magic Words Inkryptok
. After a double-take and a facepalm for reading Inkryptok
instead of in crypto
, I googled the two magic words in cryptography
, which led me straight to a Wikipedia page about the RSA challenge phrase:
“The Magic Words are Squeamish Ossifrage” was the solution to a challenge ciphertext posed by the inventors of the RSA cipher in 1977. The problem appeared in Martin Gardner’s Mathematical Games column in the August 1977 issue of Scientific American.
I tried SqueamishOssifrage
as the answer, and it was spot on!
This was a sneaky puzzle. Mixing decoding with encoding was pure trickery. It would have taken several trial and error attempts to find a base64 string that produces intelligible output when base64-encoded. Kudos to pavonia for cooking up this puzzle!
Level A5: The Afterparty
Channel topic:
Welcome to Level A5: The Afterparty [za3k] || Theme: Space || Clues: https://ircp.link/ymLhRp || Hints: The answer is your name
The link provided led to this intriguing description:
Hey, glad you could make it. Bacchus’s afterparty has everything. Half of Bacchus’s hot nurses just showed up from another party! The musician was pretty famous, but he left without his instrument. I hear that chick over there is some kind of royalty in Ancient Ethiopia—she’s been bragging about her kid for hours. Wait, I know you. What was your name again?
Who came doesn’t matter. Who (and what) is still at the party?
Hint: A priest came earlier chanting Latin, real old-school fundamentalist type. But Bacchus kicked him out. Took his cross and burned his Bible too. I know the guy was a party pooper, but geez, you know?
With The answer is your name
as the hint, I dove into identifying the partygoers.
First, Bacchus, also known as Dionysus, was straightforward. Next, Bacchus’s “hot nurses” led me to the Hyades star cluster, known as the “Nurses” in mythology.
For the royalty from ancient Ethiopia, I initially thought of Virgo but then found Cassiopeia, the boasting queen, to be a better fit. The famous musician without his instrument pointed me to Lyra, the constellation representing a lyre.
After identifying these, I still had some characters left. Frustrated, I guessed constellations randomly. When I tried Orion
, it worked!
In the follow-up discussion, it was explained:
The partygoers are constellations, arriving in order of their star count. The clues are in descending order. - You have: Pleiades (half of the hot nurses), Lyra (instrument), Cassiopeia (ancient Ethiopian royalty), Crux, Orion’s Belt (dropped it), Canis Minor, Polaris
I had missed many connections, but at least I wasn’t the only one! Sometimes, a bit of luck is all you need to crash the afterparty.
Level A6: A Perspective Shift
Channel topic:
Welcome to Level A6: Static A [za3k] || Theme: History || Clues: https://ircp.link/hW8X9y [Warning: video contains flickering/flashing lights].
I clicked the link, bracing myself for some epic historical revelation. Instead, I got a 1 minute 12-second video of pure, unadulterated static:
No audio, just black-and-white noise. Classic.
My first brainwave was to load the video into Audacity. Maybe there was a secret message hidden in the spectrogram view? Spoiler alert: there wasn’t—just pure silence mocking my efforts.
Next, I went all “Matrix” and inspected the video’s binary, hoping for some hidden file or an Easter egg. But alas, nada.
Realizing I had to go old school, I analyzed the video frame by frame. I knew it would be tedious, like finding a needle in a haystack. Then it hit me—I could solve this with code. So, I wrote a script to do the heavy lifting for me.
Here’s the code I conjured:
from moviepy.editor import VideoFileClip
import cv2
import os
import numpy as np
def extract_and_compare_frames(video_path, output_dir='output_frames'):
video = VideoFileClip(video_path)
fps = video.fps # Frames per second
total_frames = int(video.fps * video.duration)
# Create directory if not exists
if not os.path.exists(output_dir):
os.makedirs(output_dir)
reference_frame = None
for i, frame in enumerate(video.iter_frames()):
if i == 0:
# For now, use the first frame as the reference: this is one approach
# Can adjust the logic as needed
reference_frame = frame
continue
# Calculate the absolute difference between the current frame and the reference frame
difference = cv2.absdiff(reference_frame, frame)
# Threshold the difference to highlight significant changes
_, thresh = cv2.threshold(cv2.cvtColor(difference, cv2.COLOR_BGR2GRAY), 30, 255, cv2.THRESH_BINARY)
# Save the threshold image to visually inspect the differences
diff_path = os.path.join(output_dir, f'diff_frame_{i}.png')
cv2.imwrite(diff_path, thresh)
print(f"Processed frame {i}/{total_frames} | Saved: {diff_path}")
video_path = 'video.mkv'
extract_and_compare_frames(video_path)
After running it, I had a whole bunch of “difference” images in the output_frames
directory. I opened the first one. To my surprise, I had something that looked intriguingly mysterious:
Intrigued, I dove deeper, opening the first image:
It looked like ancient hieroglyphs or some secret code. I tried reverse image searching them, thinking they might be historic symbols, but no luck. I even considered they might be historical weapons. I spent too much time imagining crossbow bolts and war scythes—still, nothing.
Then I thought, maybe I needed to change my perspective—literally. I loaded the image into GIMP and started playing around with the perspective. And voilà! I saw something amazing:
It looked like 21
. I kept going and found more clues:
April
appeared next. It was beginning to look a lot like a date.
That read MMVII
, the Roman numeral for 2007.
And finally, Bhutan
. Putting all the pieces together, I had: April 21, 2007, Bhutan. A quick search led me to a Wikipedia page on Bhutanese democracy. Skimming through, I found this gem:
Elections, the cornerstone of participatory democracy, began in Bhutan with a mock election on April 21, 2007, to allow the population to become accustomed to the democratic process.
I tried mockelection
as the answer, and bingo, it worked!
What a clever puzzle, and hats off to za3k for crafting it. Another level conquered!
Level A7: The Spiral
Channel topic:
ChanServ set the topic to Welcome to Level A7: The Spiral [pavonia] || Theme: Historic Personalities || Clues: Roses are amber, violets are blue, don’t be a Sherlock to unsnarl the clew! | https://ircp.link/EGTAnV || Hints: #FACADE | hex colors point to puzzle topic | one circle, one information | ph < 7
I opened the URL. It loaded up a colorful spiral:
The spiral had random letters and numbers. But I had no idea how to fill the spiral.
My first wild guess was that the letters were elements from the periodic table, but that theory was a dud.
As any seasoned puzzle solver knows, the devil is in the details. A quick peek at the page source revealed some funky CSS:
.container {
--color-a: #FFBF00;
--color-ainv: #CC7722;
--color-b: MediumPurple;
--color-binv: Lavender;
--color-c: #635147;
--color-cinv: #A8C3BC;
/* snipped */
}
And the HTML was full of these colorful characters:
<body>
<div class="container">
<span class="sq binv">3</span>
<span class="sq ainv"></span>
<span class="sq ainv"></span>
<span class="sq ainv"></span>
<span class="sq cinv"></span>
<span class="sq c"></span>
<span class="sq a"></span>
<span class="sq a"></span>
<span class="sq ainv"></span>
<span class="sq ainv"></span>
<span class="sq ainv"></span>
<!-- snipped -->
<span class="sq b">Y</span>
<span class="sq ainv"></span>
<span class="sq ainv"></span>
<span class="sq b">5</span>
</div>
The colors seemed crucial, but how? I noticed the inv
suffix, hinting at an “inverse”, but I wasn’t sure how that fit into the puzzle yet.
Nothing else in the code stood out to me. When the spiral loaded up, there were some CSS animations and rotations, so I reviewed the code for the rotation math, and nothing seemed out of the ordinary. I ruled that out.
Analyzing the hints
I analyzed the hints one by one:
Hint | Analysis |
---|---|
Roses are amber, violets are blue, don’t be a Sherlock to unsnarl the clew! | This screamed, “focus on the colors”, but I didn’t know if “Sherlock” was relevant. |
#FACADE | This possibly indicates a need to look behind the scenes or simply a hex color code. |
hex colors point to puzzle topic | Direct link to hex colors, suggesting they were key to understanding the puzzle. |
one circle, one information | Each circle would provide a single piece of information. |
ph < 7 | Possibly indicating something acidic. |
The CSS had some of the colors mentioned as hex RGB, but others were named colors. That was odd. I decided to look up the color codes:
hex #ffbf00 (also known as Amber)
hex #cc7722 (also known as Ochre)
hex #635147 (also known as Umber)
hex #a8c3bc (also known as Opal)
I found that they’re all earthy elements. Googling for these colors, I stumbled upon a page about stop codons on Wikipedia:
In molecular biology, a stop codon (or termination codon) is a codon (nucleotide triplet within messenger RNA) that signals the termination of the translation process of the current protein.[1] Most codons in messenger RNA correspond to the addition of an amino acid to a growing polypeptide chain
Suddenly, the biology connection clicked! The spiral might represent a genetic sequence. It felt like a breakthrough!
DNA/RNA Revelation
Initially, the spiral seemed like an RNA/DNA sequence. However, filling it with the correct sequence was another matter.
First, I tried interpreting the letters as amino acids:
M - Methionine
B - Asparagine (N) or Aspartic acid (D)
D - Aspartic acid
R - Arginine
S - Serine
V - Valine
W - Tryptophan
K - Lysine
H - Histidine
However, that was just one interpretation of those letters; it could also be a DNA/RNA sequence, so I decided not to lock my thinking into any single one before I gathered additional information.
After some deep dives into molecular biology, I came across the IUPAC notation for bases:
Nucleobases (nitrogenous bases or simply bases) are nitrogen-containing biological compounds that form nucleosides, which, in turn, are components of nucleotides, with all of these monomers constituting the basic building blocks of nucleic acids. The ability of nucleobases to form base pairs and to stack one upon another leads directly to long-chain helical structures such as ribonucleic acid (RNA) and deoxyribonucleic acid (DNA). Five nucleobases—adenine (A), cytosine (C), guanine (G), thymine (T), and uracil (U)—are called primary or canonical. They function as the fundamental units of the genetic code, with the bases A, G, C, and T being found in DNA, while A, G, C, and U are found in RNA.
After learning more about the IUPAC notation for bases, I discovered there are complementary bases (see the last column in the image above).
I remembered seeing the CSS classes ainv
, binv
, and cinv
in the source code and thinking they could refer to “inverse”. That seemed to fit here!
I deduced the following:
a = A
,ainv = T
c = C
,cinv = G
b = G
,binv = C
That’s what I had been missing! It was non-intuitive and hard to notice because there’s no deeper logic here. You were supposed to assume and know that “CSS classes map to bases”.
Filling the Spiral
Using this epiphany, I filled the spiral:
Text:
3 T T T G C A A T T T T A T A A T A G M H C C K C T W T A G G M Y G T G T A C A G A T V G G C A A T T A S C A M T A Y A G A A G G T A C G Y A C A G T C A G R G M D C G G G T B C G G T A A T T M T A Y T T 5
The 5’ (five-prime) and 3’ (three-prime) ends of DNA/RNA indicate directionality, with 5’ being the start and 3’ the end. To read the sequence correctly, I reversed the string:
TTYATMTTAATGGCBTGGGCDMGRGACTGACAYGCATGGAAGAYATMACSATTAACGGVTAGACATGTGYMGGATWTCKCCHMGATAATATTTTAACGTTT
Since codons are triplets of bases, I divided the sequence into groups of three:
['TTY', 'ATM', 'TTA', 'ATG', 'GCB', 'TGG', 'GCD', 'MGR', 'GAC', 'TGA', 'CAY', 'GCA', 'TGG', 'AAG', 'AYA', 'TMA', 'CSA', 'TTA', 'ACG', 'GVT', 'AGA', 'CAT', 'GTG', 'YMG', 'GAT', 'WTC', 'KCC', 'HMG', 'ATA', 'ATA', 'TTT', 'TAA', 'CGT']
But none of these had values corresponding to it in the inverse DNA table: ['ATM', 'GCB', 'GCD', 'AYA', 'TMA', 'CSA', 'GVT', 'YMG', 'WTC', 'KCC', 'HMG']
Decoding the Genetic Code
However, several of these triplets did not correspond to standard amino acids in the inverse DNA codon table. This is because the IUPAC notation uses ambiguous letters to represent multiple possibilities. For example, ATM
could stand for either ATA
or ATC
, both of which are codes for Isoleucine.
This meant I needed to expand all those triplets to their non-ambiguous forms and take the single-letter value for the corresponding amino acid. I wondered how to achieve that using a programmatic solution most efficiently. Essentially, I wanted something like:
expand_iupac_sequence('YMG')
Which would give me the following:
['CAG', 'CCG', 'TAG', 'TCG']
Quickly, I was able to come up with the following code:
from itertools import product
import re
# Dictionary to expand IUPAC ambiguity codes into possible nucleotides
iupac_nucleotide_expansion = {
'A': ['A'], 'C': ['C'], 'G': ['G'], 'T': ['T'],
'R': ['A', 'G'], 'Y': ['C', 'T'], 'S': ['G', 'C'],
'W': ['A', 'T'], 'K': ['G', 'T'], 'M': ['A', 'C'],
'B': ['C', 'G', 'T'], 'D': ['A', 'G', 'T'], 'H': ['A', 'C', 'T'],
'V': ['A', 'C', 'G'], 'N': ['A', 'C', 'G', 'T']
}
# Dictionary to map codons to amino acids
codon_to_amino_acid = {
'GCT': 'A', 'GCC': 'A', 'GCA': 'A', 'GCG': 'A',
'CGT': 'R', 'CGC': 'R', 'CGA': 'R', 'CGG': 'R', 'AGA': 'R', 'AGG': 'R',
'AAT': 'N', 'AAC': 'N',
'GAT': 'D', 'GAC': 'D',
'TGT': 'C', 'TGC': 'C',
'CAA': 'Q', 'CAG': 'Q',
'GAA': 'E', 'GAG': 'E',
'GGT': 'G', 'GGC': 'G', 'GGA': 'G', 'GGG': 'G',
'CAT': 'H', 'CAC': 'H',
'ATT': 'I', 'ATC': 'I', 'ATA': 'I',
'CTT': 'L', 'CTC': 'L', 'CTA': 'L', 'CTG': 'L', 'TTA': 'L', 'TTG': 'L',
'AAA': 'K', 'AAG': 'K',
'TTT': 'F', 'TTC': 'F',
'CCT': 'P', 'CCC': 'P', 'CCA': 'P', 'CCG': 'P',
'TCT': 'S', 'TCC': 'S', 'TCA': 'S', 'TCG': 'S', 'AGT': 'S', 'AGC': 'S',
'ACT': 'T', 'ACC': 'T', 'ACA': 'T', 'ACG': 'T',
'TGG': 'W',
'TAT': 'Y', 'TAC': 'Y',
'GTT': 'V', 'GTC': 'V', 'GTA': 'V', 'GTG': 'V',
'ATG': 'START', 'CTG': 'STOP', 'UTG': 'STOP',
'TAA': 'STOP', 'TAG': 'STOP', 'TGA': 'STOP'
}
def expand_iupac_sequence(seq):
# Expanding each position in the given sequence
expanded_positions = [iupac_nucleotide_expansion[nuc] for nuc in seq]
# Generate all combinations of possible sequences
return [''.join(p) for p in product(*expanded_positions)]
def map_codons_to_amino_acids(codons):
return {codon: codon_to_amino_acid.get(codon, '?') for codon in codons}
s = 'TTYATMTTAATGGCBTGGGCDMGRGACTGACAYGCATGGAAGAYATMACSATTAACGGVTAGACATGTGYMGGATWTCKCCHMGATAATATTTTAACGTTT'
triplets = re.findall(r'.{3}', s)
for seq in triplets:
expanded_sequences = expand_iupac_sequence(seq)
mapped_amino_acids = map_codons_to_amino_acids(expanded_sequences)
print(' or '.join(set(mapped_amino_acids.values())))
I got the following output:
I saw two readable words! I was definitely getting somewhere! If I included the FIL
at the top, it could read as FIL START AWARD HAWK
. I googled that, but it did not yield any good results. I wondered what FIL
could mean. For a moment, I thought it was a film award. My searches led me to the Golden Hawk Film Award, but that didn’t get me anywhere. I assumed I had made an error somewhere.
Then, I remembered a crucial point. There were start and stop codons in there—these codons indicate the beginning and end of the mRNA sequence. I felt like I had to use this knowledge somehow.
I had initially considered identifying protein-coding sequences by simply taking every three letters (codons) at a time. However, this approach would fail to detect sequences that aren’t aligned with the reading frame, meaning they start at the second or third letter of a codon.
For example, in the sequence TTYATMTTAATGGCDMGRGACTGACAYGCATGGAAGACGGVTAGACATGTGY
, this method would correctly identify the first gene (ATGGCDMGRGACTGA
), which begins with the start codon ATG
and ends with the stop codon TGA
, neatly divided into codon triplets. But it would miss the second gene (ATGGAAGACGGVTAG
) because it’s offset by one letter due to the preceding sequence (CAYGC
).
To solve this, I realized I needed to consider all possible start (ATG
) and stop (TAG
, TAA
, TGA
) codons, and only select those pairs separated by a sequence whose length is a multiple of three (representing a whole number of codons).
This was something easily solved by regexes. I quickly came up with the following regex:
(ATG)(?:.{3})*?(TAG|TAA|TGA)
It found three matches! Here’s what it looked like:
I wrote some code to process the sequence groups:
def process_sequences(sequences):
for seq in sequences:
expanded_sequences = expand_iupac_sequence(seq)
mapped_amino_acids = map_codons_to_amino_acids(expanded_sequences)
print(' or '.join(set(mapped_amino_acids.values())))
s1 = 'GCB.TGG.GCD.MGR.GAC'.split('.')
s2 = 'GAA.GAY.ATM.ACS.ATT.AAC.GGV'.split('.')
s3 = 'TGY.MGG.ATW.TCK.CCH.MGA'.split('.')
all_sequences = [s1, s2, s3]
for sequences in all_sequences:
aa = process_sequences(sequences)
print()
I had the following output!
A
W
A
R
D
E
D
I
T
I
N
G
C
R
I
S
P
R
Unbelievable! I had some meaningful output. I quickly searched it on google. I found the following in the first result:
The Nobel Prize in Chemistry was jointly awarded on Wednesday to Emmanuelle Charpentier and Jennifer A. Doudna for their 2012 work on Crispr-Cas9, a method to edit DNA. The announcement marks the first time the award has gone to two women.
I tried EmmanuelleCharpentier
as the key, and it worked. What a satisfying challenge!
Now that I had the solution, I wanted to see if I adjust my script to automatically decode the sequence string using the previous regex. I quickly came up with the following:
def find_messages(dna_sequence):
pattern = r"ATG((?:.{3})*?)(?:TAG|TAA|TGA)"
matches = re.findall(pattern, dna_sequence)
for m in matches:
triplets = re.findall(r'.{3}', m)
for seq in triplets:
expanded_sequences = expand_iupac_sequence(seq)
mapped_amino_acids = map_codons_to_amino_acids(expanded_sequences)
print(' or '.join(set(mapped_amino_acids.values())), end='')
print()
s = 'TTYATMTTAATGGCBTGGGCDMGRGACTGACAYGCATGGAAGAYATMACSATTAACGGVTAGACATGTGYMGGATWTCKCCHMGATAATATTTTAACGTTT'
find_messages(s)
That produced:
AWARD
EDITING
CRISPR
Neat!
Level A8
Channel topic:
Graffiti here reads: “ATGGGGTAGACCACTGCTGGCTAGTTCGCTTCTACAGCCTAA - ali1234” || Congratulations! You’re done with Track A. Take this with you: deafening
The A track was finally complete! 🎉
Level B1: Tetris
Channel topic:
Welcome to Level B1: Blocks “R” Us [FireFly] || Theme: Video Games || Clues: https://ircp.link/24rJzU
I wrote a separate blog post for this level. You can read it here: Pwning Tetris: Exploiting a Weak RNG.
Level B2: Secret Code
Channel topic:
Welcome to Level B2: Secret Code [za3k] || Theme: Video Games || Clues: ankȯö_eidmc | 1. Unscramble 2. Same length 3. Rescramble || Hints: _ is allowed to move | anagram | contra | abbrev
When I first looked at the puzzle, the jumble of letters ankȯö_eidmc
looked like gibberish. The task was to unscramble, rescramble, and ensure everything stayed the same length. Alongside this, the hints provided—like _ is allowed to move
and anagram
—didn’t initially clarify things. They felt more like noise than help. Was the underscore a literal or figurative element? Did it represent a physical object in a game, like the paddle in Breakout? I had no clue.
Starting with what I could handle, I jumped straight into unscrambling. The part eidmc
quickly sorted itself into medic
, which was a neat little discovery but didn’t help much with the other letters. Now those umlauted characters, ȯ
and ö
, they were oddballs. Initially, I didn’t know if they were just there to throw me off or if they meant something crucial. After fussing over them without much progress, I decided to replace them with simple ‘o’s and see where that led. Suddenly, things started clicking, and I realized medic
was wrong. ankȯö_eidmc
was reshuffled into konami code
. That was a Eureka moment because it tied directly to the contra
hint in the puzzle, referencing the famous cheat code used in Contra and other games from Konami.
I took the first letters of the Konami Code directions: ↑↑↓↓←→←→BA[Start/Select]
. I got UUDDLRLRBAS
.
After spinning my wheels for a while, I decided to see if the transformation from ankȯö_eidmc
to konami code
could help me crack the code on UUDDLRLRBAS
. So, here’s what I thought: If ankȯö_eidmc
morphed into konami code
, perhaps I could use the same mapping idea for UUDDLRLRBAS
. I mapped out each letter’s transformation from the original scrambled clue to the Konami code, which looked something like this:
a
turned intok
n
turned intoo
… and so on, until the whole thing was mapped out.
Using the same mapping, I went back to UUDDLRLRBAS
and tried to rearrange it using the same logic I had applied earlier:
original, transformed, input_string = "ankȯö_eidmc", "kȯnami_cöde", "uuddlrlrbas"
mapping = {original.index(c): transformed.index(c) for c in original}
output_string = ''.join(input_string[mapping.get(i, i)] for i in range(len(input_string)))
print(output_string)
The result was dduublsralr
. I tried that on a whim, not expecting much. But to my surprise, it worked!
Looking back, every piece of the puzzle, even those that seemed to lead nowhere, actually built up to the solution.
Level B3: Thor’s Puzzle
Channel topic:
Welcome to Level B3: Thor’s Puzzle [pavonia] || Theme: Mythology || Clues: https://ircp.link/5hI9Sm || Hints: Potent songs nine from the famed son I learned of Bolthorn, Bestla’s sire | Halfdan was here | Younger | Spot ’em | Each panel has one
I opened the image:
Upon opening the image linked in the channel, I was hit with a mix of excitement and dread. Mythology-themed puzzles are notoriously tricky, and this one seemed no different. I dove into the challenge.
Analyzing the hints
I decided to analyze what each hint meant:
Hint | Analysis |
---|---|
Potent songs nine from the famed son I learned of Bolthorn, Bestla’s sire | A line from Odin’s Rune Song. Initial guess: connection to runes. |
Halfdan was here | Referencing a runic inscription by a Viking mercenary. Must be related to runes. |
Younger | Could refer to Younger Futhark, a type of rune. |
Spot ’em | Spot something in each panel. |
Each panel has one | Each contains one of these “somethings”. |
Digging Into Mythology
I dove deep into Thor’s mythology. Thor’s hammer, Mjölnir, seemed like a consistent theme across the panels. This led me to the Kvinneby amulet, and I found connections like fish, sea, Thor, and his hammer on Wikipedia. It seemed promising.
In the Poetic Edda, compiled during the 13th century from traditional source material reaching into the pagan period, Thor appears (or is mentioned) in the poems Völuspá, Grímnismál, Skírnismál, Hárbarðsljóð, Hymiskviða, Lokasenna, Þrymskviða, Alvíssmál, and Hyndluljóð.
I theorized that each panel represented one of these poems from the Poetic Edda. I spent hours trying to match the panels to specific poems. Despite my efforts, this approach was a dead end. Frustration mounted, and I decided to take a break.
Upon my return, I found the topic had changed:
Welcome to Level B3: Thor’s Puzzle [pavonia] || Theme: Mythology || Clues: https://ircp.link/5hI9Sm || Hints: Spot ’em | Each panel has one | ᚠᚢᚦᛅᚱᚴ | Younger
Great, it felt like my progress had been reset. The new hints clearly pointed to Younger Futhark runes, and Spot 'em
meant I had to look for these runes in the panels.
I still had no idea what to spot or what each panel contained. At this point, I had been looking at the image for hours without making any progress. Then, I explored the idea that these panels might hide some of the Younger Futhark runes.
After hours of staring at the image and researching Younger Futhark, I finally spotted a rune in one of the images. In the seventh panel, the second tree looked like a ᛘ
. The panels were indeed representing runes, albeit subtly. After painstakingly finding all the runes, here’s what I got:
The runes were:
ᛏᛅᚱᚴᛋᛅᛘᚢᛋ
Converting this to Latin equivalents, I had:
[td]ar[kgŋ]sam[uvwyo]s
Too lazy to list all possibilities by hand, I whipped up a quick script:
from itertools import product
first_letters = ['t', 'd']
middle_letters = ['k', 'g', 'ŋ']
last_letters = ['u', 'v', 'w', 'y', 'o']
all_combinations = product(first_letters, middle_letters, last_letters)
words = [f"{first}ar{middle}sam{last}s" for first, middle, last in all_combinations]
print(words)
The possibilities were:
tarksamus tarksamvs tarksamws tarksamys tarksamos targsamus
targsamvs targsamws targsamys targsamos tarŋsamus tarŋsamvs
tarŋsamws tarŋsamys tarŋsamos darksamus darksamvs darksamws
darksamys darksamos dargsamus dargsamvs dargsamws dargsamys
dargsamos darŋsamus darŋsamvs darŋsamws darŋsamys darŋsamos
Some of these were readable:
tarksamus
tarksamos
targsamus
targsamos
darksamus
darksamos
dargsamos
darksamus
caught my eye. A quick Google search revealed that Dark Samus is a character in the Metroid series. I tried it as the answer, and it worked!
Relief washed over me. This was a really frustrating level with many misdirections, but perseverance and a bit of coding saved the day. On to the next puzzle!
Level B4: Peeling Off Layers of Crypto
Channel topic:
Welcome to Level B4: Serial Experiments Lain [za3k] || Theme: Cryptography || Clues: https://ircp.link/ZhhFtN || Hints: Only for the final layer: Dessert | L,,psy | https://ircp.link/euwZTH | 37->39 (typo)
I opened the URL, and I saw the following:
It was a standard web page with a Shrek image. Nothing fancy. I decided to look under the hood and viewed the page source:
<html>
Shrek: There's a lot more to ogres than people think.<br>
<img src="shrek.jpg">
<!--
Shrek: Ogres are like onions!
YNLRE 1: JRVEQ
Qbaxrl: Gurl fgvax?
Fuerx: Lrf... Ab!
GRSMEIVtZwbtE0yFGSZXPxEiozgyrGbtG2tfVUEbMKxtoJSeMFO5o3HtL3W5CjbXFTyhqQbtq2uuqPOfMJ5aqTttnKZtqTuyVTAiMTIxVT1yp3AuM2H/PtcZYPkjp3x6oHk6oaLmM3cg4cFXrUu4rUu4DKy0pv7vyVcOpzqarat1oF5fo3u4rUu4rSyinT8hJIqdnF5knzclp3yerUu4rUu4EKIyqF5WVJq04evkrJL1rz1vrUu4rUu4rSWfrKGvyVcAJv5gGmc6MmW6nUu4rUu4rUtmMJqcIyWhDzuGJUS0oaAbrUu4rUu4rQcuMJ5cFaSiqyygMJfloKy4rUu4rUu4HUM0W2p0p2MfGUuzpJMcp3u4rUu4rUuGMJSfMGcypw9RraS6nl5drUu4rUu4rSxaoTyhEzSeFmIlqmI6I3S4rUu4rUu4D2IfqZBbHv52pwcmnQIxMzc4rUu4rUu4FT1vqUWHD25dIaN6pzSaoau4rUu4rUuSo3WfMIquMTASYP95Z2EdrUu4rUu4rBXHvaIiMrXHvyEup2qWnP9hM3qirUu4rUu4rRE0q3p2DJA5ryqmnmEwnzS4rUu4rUu4o2yhnTkQLKA2HaMjAJ11L3u4rUu4rUuhovkcMHSuoJ5RpJcgqUc2rUu4rUu4rTg0p3E04evkqaWxI2gzoUAfpau4rUu4rUuynUEyqRShraASoUIzATc3rUu4rUu4rUyyLJuyqz5arIqxpUcupv54rUu4rUu4BaAlLKWypl5mGayyrKc14evkrUu4rUu4rR91qTyeq3xhoIIkAzH2ozA4rUu4rUu4nT5mpzI5nF5loKqbpzAmrUu4rUu4rUtX
-->
</html>
Just as I suspected. Now, what did we have here? Qbaxrl: Gurl fgvax?
This looked like ROT13, so I tried that first.
ROT13-decoding gave me the following:
Fuerx: Bterf ner yvxr bavbaf!
LAYER 1: WEIRD
Donkey: They stink?
Shrek: Yes... No!
TEFZRVIgMjogR0lSTFMKCkRvbmtleTogT2gsIHRoZXkgbWFrZSB5b3UgY3J5PwoKSGludDogd2hhdCBsZW5ndGggaXMgdGhlIGNvZGVkIG1lc3NhZ2U/CgpMLCxwc3k6bUx6bnYzZ3pt4pSKeHh4eHh4QXl0ci7ilIpBcmdneng1bS5sb3h4eHh4eFlvaG8uWVdqaS5xampyc3lreHh4eHh4RXVldS5JIWd04rixeWY1em1ieHh4eHh4eFJseXTilIpNWi5tTzp6ZzJ6aHh4eHh4eHgzZWdpVlJuQmhTWHF0bnNoeHh4eHh4eDphZW5pSnFvdlltZWsybXl4eHh4eHh4UHZ0J2c0c2ZsTHhmcWZpc3h4eHh4eHhTZWFsZTplcj9EenF6ay5qeHh4eHh4eFknbGluRmFrSzVydzV6V3F4eHh4eHh4Q2VsdMOoUi52cjpzaDVkZmp4eHh4eHh4SG1idHJUQ25qVnA6cmFnbnh4eHh4eHhFb3JsZVdhZGNFLC95M2RqeHh4eHh4eOKUinVvZeKUilRhc2dJaC9uZ3dveHh4eHh4eER0d3c2QWN5eldzazRjamF4eHh4eHh4b2luaGxDYXN2UnZwNW11Y3h4eHh4eHhubixpZUFhbW5EcWptdHp2eHh4eHh4eGt0c3R04rixdnJkV2tmbHNscnh4eHh4eHhlaHRldEFuenNFbHVmNGp3eHh4eHh4eHllYWhldm5neVdkcHphci54eHh4eHh4OnNyYXJlcy5zTnlleXp14rixeHh4eHh4eE91dGlrd3kubVVxNmU2bmN4eHh4eHh4aG5zcmV5aS5ybXdocmNzeHh4eHh4eHgK
That worked!
Layer 1
LAYER 1: WEIRD
- There was a layer number, so it seemed like this puzzle had multiple layers. It felt like I was about to start a long journey!
This looked like base64, so I tried that:
amal@kiev:~$ echo "TEFZRVIgMjogR0lSTFMKCkRvbmtleTogT2gsIHRoZXkgbWFrZSB5b3UgY3J5PwoKSGludDogd2hhdCBsZW5ndGggaXMgdGhlIGNvZGVkIG1lc3NhZ2U/CgpMLCxwc3k6bUx6bnYzZ3pt4pSKeHh4eHh4QXl0ci7ilIpBcmdneng1bS5sb3h4eHh4eFlvaG8uWVdqaS5xampyc3lreHh4eHh4RXVldS5JIWd04rixeWY1em1ieHh4eHh4eFJseXTilIpNWi5tTzp6ZzJ6aHh4eHh4eHgzZWdpVlJuQmhTWHF0bnNoeHh4eHh4eDphZW5pSnFvdlltZWsybXl4eHh4eHh4UHZ0J2c0c2ZsTHhmcWZpc3h4eHh4eHhTZWFsZTplcj9EenF6ay5qeHh4eHh4eFknbGluRmFrSzVydzV6V3F4eHh4eHh4Q2VsdMOoUi52cjpzaDVkZmp4eHh4eHh4SG1idHJUQ25qVnA6cmFnbnh4eHh4eHhFb3JsZVdhZGNFLC95M2RqeHh4eHh4eOKUinVvZeKUilRhc2dJaC9uZ3dveHh4eHh4eER0d3c2QWN5eldzazRjamF4eHh4eHh4b2luaGxDYXN2UnZwNW11Y3h4eHh4eHhubixpZUFhbW5EcWptdHp2eHh4eHh4eGt0c3R04rixdnJkV2tmbHNscnh4eHh4eHhlaHRldEFuenNFbHVmNGp3eHh4eHh4eHllYWhldm5neVdkcHphci54eHh4eHh4OnNyYXJlcy5zTnlleXp14rixeHh4eHh4eE91dGlrd3kubVVxNmU2bmN4eHh4eHh4aG5zcmV5aS5ybXdocmNzeHh4eHh4eHgK" | base64 -d
LAYER 2: GIRLS
Donkey: Oh, they make you cry?
Hint: what length is the coded message?
L,,psy:mLznv3gzm┊xxxxxxAytr.┊Arggzx5m.loxxxxxxYoho.YWji.qjjrsykxxxxxxEueu.I!gt⸱yf5zmbxxxxxxxRlyt┊MZ.mO:zg2zhxxxxxxx3egiVRnBhSXqtnshxxxxxxx:aeniJqovYmek2myxxxxxxxPvt'g4sflLxfqfisxxxxxxxSeale:er?Dzqzk.jxxxxxxxY'linFakK5rw5zWqxxxxxxxCeltèR.vr:sh5dfjxxxxxxxHmbtrTCnjVp:ragnxxxxxxxEorleWadcE,/y3djxxxxxxx┊uoe┊TasgIh/ngwoxxxxxxxDtww6AcyzWsk4cjaxxxxxxxoinhlCasvRvp5mucxxxxxxxnn,ieAamnDqjmtzvxxxxxxxktstt⸱vrdWkflslrxxxxxxxehtetAnzsEluf4jwxxxxxxxyeahevngyWdpzar.xxxxxxx:srares.sNyeyzu⸱xxxxxxxOutikwy.mUq6e6ncxxxxxxxhnsreyi.rmwhrcsxxxxxxxx
That worked - it was simple enough! I was 2 for 2!
I didn’t have any immediate thoughts on how to decode this one. In the excitement, I had forgotten to review the clues and hints properly! I decided to look at them.
Analyzing the hints
- Title: Serial Experiments Lain
- Hints: Only for the final layer: Dessert | L,,psy | https://ircp.link/euwZTH | 37->39 (typo)
The text from layer 1 was: Shrek: Ogres are like onions!
and layer 2 was: Donkey: Oh, they make you cry?
It seems like a conversation between the animated character Shrek and donkey from the Shrek movie. The entire conversation is:
Shrek: For your information, there's a lot more to ogres than people think.
Donkey: Example?
Shrek: Example... uh... ogres are like onions!
[holds up an onion, which Donkey sniffs]
Donkey: They stink?
Shrek: Yes... No!
Donkey: Oh, they make you cry?
Shrek: No!
Donkey: Oh, you leave 'em out in the sun, they get all brown, start sproutin' little white hairs...
Shrek: [peels an onion] NO! Layers. Onions have layers. Ogres have layers... You get it? We both have layers.
[walks off]
Donkey: Oh, you both have LAYERS. Oh. You know, not everybody like onions. CAKE! Everybody loves cake! Cakes have layers!
Shrek: I don't care what everyone likes! Ogres are not like cakes.
Donkey: You know what ELSE everybody likes? Parfaits! Have you ever met a person, you say, "Let's get some parfait," they say, "Hell no, I don't like no parfait."? Parfaits are delicious!
Shrek: NO! You dense, irritating, miniature beast of burden! Ogres are like onions! End of story! Bye-bye! See ya later.
Donkey: Parfait's may be the most delicious thing on the whole damn planet!
The level’s title was Serial Experiments Lain
. I googled it and found it’s another animated show: https://en.wikipedia.org/wiki/Serial_Experiments_Lain. Ctrl + F’ing the page for layer
, I noticed the episode names were matching with the layer names I had:
Serial Experiments Lain had 13 episodes:
- Weird
- Girls
- Psyche
- Religion
- Distortion
- KIDS
- SOCIETY
- RUMORS
- PROTOCOL
- LOVE
- Infornography
- Landscape
- Ego
I wondered if there were going to be 13 layers; that would make it a really lengthy puzzle.
I had already seen the layers Weird
and Girls
, so I noted that the next level would be Psyche
.
I decided to look at the remaining hints. The URL https://ircp.link/euwZTH contained an image that said cryptogram
. Cryptograms are puzzles in which letters are substituted with other letters or symbols to conceal a hidden message.
Layer 2
The layer’s text was:
LAYER 2: GIRLS
Donkey: Oh, they make you cry?
Hint: what length is the coded message?
L,,psy:mLznv3gzm┊xxxxxxAytr.┊Arggzx5m.loxxxxxxYoho.YWji.qjjrsykxxxxxxEueu.I!gt⸱yf5zmbxxxxxxxRlyt┊MZ.mO:zg2zhxxxxxxx3egiVRnBhSXqtnshxxxxxxx:aeniJqovYmek2myxxxxxxxPvt'g4sflLxfqfisxxxxxxxSeale:er?Dzqzk.jxxxxxxxY'linFakK5rw5zWqxxxxxxxCeltèR.vr:sh5dfjxxxxxxxHmbtrTCnjVp:ragnxxxxxxxEorleWadcE,/y3djxxxxxxx┊uoe┊TasgIh/ngwoxxxxxxxDtww6AcyzWsk4cjaxxxxxxxoinhlCasvRvp5mucxxxxxxxnn,ieAamnDqjmtzvxxxxxxxktstt⸱vrdWkflslrxxxxxxxehtetAnzsEluf4jwxxxxxxxyeahevngyWdpzar.xxxxxxx:srares.sNyeyzu⸱xxxxxxxOutikwy.mUq6e6ncxxxxxxxhnsreyi.rmwhrcsxxxxxxxx
The coded message was 529 characters. I didn’t know how it was relevant.
I had noticed a cryptogram hint. If this were a substitution cipher, L,,psy
had to be Layer3
; however, that meant ,
had to map to a
and y
—that didn’t make any sense. Maybe that hint was meant for a later level. I decided to try other things.
Then, I noticed 529 was a perfect square. What if I folded the message 23x23?
I tried that with fold
command:
$ cat decoded.txt | tail -1 | fold -w 23
L,,psy:mLznv3gzm┊xxxx
xxAytr.┊Arggzx5m.loxx
xxxxYoho.YWji.qjjrsykxx
xxxxEueu.I!gt⸱yf5zmbx
xxxxxxRlyt┊MZ.mO:zg2z
...
The rows weren’t even length. I checked the length using wc
, which showed 547 characters. Ah, I knew what was going on. fold
isn’t Unicode safe, and there were Unicode characters in there, which had more than 2 bytes, so the length wasn’t evenly split. I decided to use grep
instead:
$ cat decoded.txt | tail -1 | grep -Eo '.{23}'
L,,psy:mLznv3gzm┊xxxxxx
Aytr.┊Arggzx5m.loxxxxxx
Yoho.YWji.qjjrsykxxxxxx
Eueu.I!gt⸱yf5zmbxxxxxxx
Rlyt┊MZ.mO:zg2zhxxxxxxx
3egiVRnBhSXqtnshxxxxxxx
:aeniJqovYmek2myxxxxxxx
Pvt'g4sflLxfqfisxxxxxxx
Seale:er?Dzqzk.jxxxxxxx
Y'linFakK5rw5zWqxxxxxxx
CeltèR.vr:sh5dfjxxxxxxx
HmbtrTCnjVp:ragnxxxxxxx
EorleWadcE,/y3djxxxxxxx
┊uoe┊TasgIh/ngwoxxxxxxx
Dtww6AcyzWsk4cjaxxxxxxx
oinhlCasvRvp5mucxxxxxxx
nn,ieAamnDqjmtzvxxxxxxx
ktstt⸱vrdWkflslrxxxxxxx
ehtetAnzsEluf4jwxxxxxxx
yeahevngyWdpzar.xxxxxxx
:srares.sNyeyzu⸱xxxxxxx
Outikwy.mUq6e6ncxxxxxxx
hnsreyi.rmwhrcsxxxxxxxx
Initially, nothing stood out to me. But reading it column-wise, I saw LAYER3:PSYCHE...
. Neat! I had to read the rest of it, too. I did that in Python:
rows = re.findall(r'.{23}', decoded)
grid = [list(row) for row in rows]
column_wise_message = ''.join([''.join(column) for column in zip(*grid)])
print(column_wise_message)
Which had the following output:
LAYER3:PSYCHE┊Donkey:Oh,youleave'emoutinthesun,theygetallbrown,startsproutin'littlewhitehairs...┊Vigenère┊6letterkey┊YIMRJ4:FRTWTACA⸱Avewy:AW!Znqsea.Caacaavnnsyimrjg.Bofrkvndsysmrzg...Lgitmhvl?Krjcgzvndsysmrzg.⸱OSYLD5:VEIWRDWEWNUmnzqy:Xmxzrsp,hsvqkldyqwvxjfzqefqwh://kpjfupe6h35j5gtkqz55ryn45mlfzyergmrz2n2fkzda3gcmts4az6cz.smzsmi.Wfgdwjuzljrunsmlybhhysjqjnjoacvrw.⸱cx┊okxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Layer 3
The decoded text looked like gibberish. I did notice one thing, though: it had ://
—maybe it was a URL. For now, I decided to stick to the first part, where I had more confidence.
In the sequence, the next layer was Layer 4 and the layer name would be: Religion
. We had ┊Vigenère┊6letterkey
. Did that mean I had to bruteforce the key? I decided to try that anyway.
I did notice fqwh://
, which could mean http://
. Given that Shrek references onions, I wouldn’t be surprised if it was an onion link. But it was like shooting in the dark, so I didn’t pursue this lead.
I created a Vignere decode function in Python. I decided to try out all the unique six-letter words from Lain, the Shrek movie. I downloaded the subtitles, cleaned them, and filtered the six-letter words. Then, I looped over them and tried to decode the ciphertext with the word as the key, and checked if RELIGION
was in the decoded plaintext (that was the next layer’s name). However, this didn’t work. The puzzle authors are clever and may have used a completely different word.
In the world of cryptography, there’s a famous saying: “Don’t roll your own”. I decided to try an online solver instead. I went to https://www.dcode.fr/vigenere-cipher and fed in my ciphertext and known plaintext:
Within seconds, I had found the key and decoded the ciphertext fully:
Layer 4
LAYER4:RELIGION
Shrek:NO!Layers.Onionshavelayers.Ogreshavelayers...Yougetit?Webothhavelayers.
BAKYV5:HRQIEVIREZHezmyk:Kejmzec,zeiywyvkdehkbrmyqsiiu://sbwxgcm6t35w5yfxyl55eqz45ztrmqqeoyer2z2sslqs3spuff4sl6ph.ezrezq.Isypjrgmdveczfexljtuqewyvabankheo.ok
BAKYV5
looked like it could be LEVEL5
. This meant HRQIEVIREZ
had to be DISTORTION
.
Given I knew some of the substitution values, I decided to try the cryptogram solver.
Using the known plaintext substitutions, I was able to get something readable! Finally, I was getting somewhere:
The last bit reads the quick brown x of jumps over the lazy dog
.
If it was the famous English pangram, it’s supposed to be fox
instead of x
. I noticed Quipquip was removing all the non-alphabet characters, which meant that in order to decode this, I had to do it by myself. Enter Python:
ciphertext = 'BAKYV5:HRQIEVIREZHezmyk:Kejmzec,zeiywyvkdehkbrmyqsiiu://sbwxgcm6t35w5yfxyl55eqz45ztrmqqeoyer2z2sslqs3spuff4sl6ph.ezrezq.Isypjrgmdveczfexljtuqewyvabankheo'
# remove all non-alphabet characters so it matches the quipquip output
ciphertext_mapping = re.sub(r'[^A-Za-z]', '', ciphertext)
# quipquip output
plaintext = 'layer distortion donkey you know not everybody likes http h l v f c w k m vex fejo s n n miks so geo in h h j sh hq p x x h j qd onions the quick brown x of jumps over a lazy dog'
# remove spaces from quipquip output
plaintext_mapping = re.sub(r'\s*', '', d)
# generating the substitution mapping
substitution_mapping = {ciphertext_char: plaintext_char for ciphertext_char, plaintext_char in zip(ciphertext_mapping, plaintext_mapping)}
# decode the given ciphertext with the newly created mapping
ciphertext_to_decode = "BAKYV5:HRQIEVIREZHezmyk:Kejmzec,zeiywyvkdehkbrmyqsiiu://sbwxgcm6t35w5yfxyl55eqz45ztrmqqeoyer2z2sslqs3spuff4sl6ph.ezrezq.Isypjrgmdveczfexljtuqewyvabankheo."
decoded_message = ''.join(substitution_mapping.get(c, c) for c in ciphertext_to_decode)
print(decoded_message)
To my surprise, I had a readable output:
layer5:distortiondonkey:youknow,noteverybodylikeshttp://hlvfcwk6m35v5exfej55osn45nmikssogeoi2n2hhjsh3hqpxx4hj6qd.onions.thequickbrownxofjumpsoveralazydog.
It contained an onion link, too. I had predicted it would be an onion link in layer 3! Excitedly, I visited the onion link http://hlvfcwk6m35v5exfej55osn45nmikssogeoi2n2hhjsh3hqpxx4hj6qd.onion
. Only to find out it didn’t work. I got the following error:
Details: 0xF6—The provided .onion address is invalid. This error is returned because the address checksum doesn't match, the ed25519 public key is invalid, or the encoding is invalid.
Then, I remembered. I hadn’t fixed the x of
error. After fixing the mapping, I got:
'layer5:distortiondonkey:youknow,noteverybodylikeshttp://hlvxcwk6m35v5efxej55osn45nmikssogeoi2n2hhjsh3hqpff4hj6qd.onions.thequickbrownfoxjumpsoveralazydog.'
I visited http://hlvxcwk6m35v5efxej55osn45nmikssogeoi2n2hhjsh3hqpff4hj6qd.onion
in a Tor browser. I was able to see some text!
Layer 6
The text was:
LAYER 6: KIDS
Donkey: CAKE! Everybody loves cake! Cakes have layers!
₿ip37 era approve eyebrow picture address other life hazard drive enforce fence rather actor motor second voice monster slice tail dolphin report dash symptom auction half arm amount melody share penalty light put grace need flat tell elegant mother random coin grace okay siege film audit exist family piano connect acoustic orange shaft craft joke lawsuit raw vacuum wise gown thrive web skate level tomato hunt forward bulk valley maple destroy tiger audit
11+11+11+11+11+11+11+11=8+8+8+8+8+8+8+8+8+8+8
The B in ₿ip37
was U+20BF Bitcoin Sign. A BIP, or Bitcoin Improvement Proposal, is a formal proposal to change Bitcoin. The BIP process organizes the Bitcoin community in the absence of a centralized leader. BIPs can propose changes to Bitcoin’s consensus layer, community standards, or the development process.
Googling for the words, I reached a “BIP39 list”: https://github.com/bitcoin/bips/blob/master/bip-0039/english.txt. All the words were from that list.
I spent some time researching BIP37, but I couldn’t find connections between it and the wordlist, so I reached out to a moderator. They confirmed that it was a typo; it’s supposed to be BIP39. They updated the level to change the text to BIP39. That was time wasted, but I was glad I was progressing.
Still, I had no idea what 11+11+11+11+11+11+11+11=8+8+8+8+8+8+8+8+8+8+8
meant. Both sides sum to 88. BIP88 didn’t seem particularly interesting.
I decided to focus on the words. My first thought was to get the word indices based on the original list.
import requests
wordlist = requests.get('https://raw.githubusercontent.com/bitcoin/bips/master/bip-0039/english.txt').text.splitlines()
words = 'era approve eyebrow picture address other life hazard drive enforce fence rather actor motor second voice monster slice tail dolphin report dash symptom auction half arm amount melody share penalty light put grace need flat tell elegant mother random coin grace okay siege film audit exist family piano connect acoustic orange shaft craft joke lawsuit raw vacuum wise gown thrive web skate level tomato hunt forward bulk valley maple destroy tiger audit'.split(' ')
indices = [wordlist.index(w) for w in words]
print(indices)
I had the following output:
[610, 86, 650, 1314, 27, 1256, 1034, 847, 538, 593, 680, 1426, 21, 1155, 1555, 1963, 1146, 1627, 1770, 518, 1463, 445, 1764, 119, 835, 93, 64, 1108, 1577, 1300, 1036, 1398, 811, 1182, 708, 1782, 572, 1153, 1421, 363, 811, 1231, 1600, 690, 120, 638, 660, 1312, 377, 16, 1246, 1575, 400, 962, 1009, 1428, 1924, 2019, 809, 1801, 1990, 1616, 1029, 1824, 891, 734, 240, 1927, 1084, 481, 1807, 120]
I attempted to take the modulus of them with % 26
and convert the results to letters, but it didn’t produce anything meaningful. I also tried interpreting them as ASCII values, combining the text, and dividing it into segments of 8 and 11 characters, but that approach was unsuccessful as well.
Then, I tried reading them as binary, but as groups of 11 rather than 8, because BIP 39 decodes to 11 bits:
# bip decodes as 11 bits
binary_representations = [format(index, '011b') for index in indices]
# read as normal 8 bits
binary_values = re.findall(r'.{8}', ''.join(binary_representations))
# convert to ASCII
ascii_characters = [chr(int(binary, 2)) for binary in binary_values]
print(''.join(ascii_characters))
Finally, I had some readable output!
LAYER 7: SOCIETY + ð«You know what ELSE everybody likes? + ð¥ / Bobs ððð / okxxxxxxxxx
Some of the characters looked weird. Then, I figured that must be because chr()
wasn’t handling Unicode characters correctly. I used a different approach:
from bitstring import BitArray
bits = BitArray(bin=''.join(binary_values))
utf8_string = bits.tobytes().decode('utf-8')
print(utf8_string)
Now I had the correct output:
LAYER 7: SOCIETY + 🫏You know what ELSE everybody likes? + 🔥 / Bobs 🍔🍔🍔 / okxxxxxxxxx
Layer 7
- 🫏 - donkey
- The answer to
You know what ELSE everybody likes?
wasparfaits
based on the Shrek conversation. - 🔥 - fire
- Bobs 🍔🍔🍔 - Bob’s Burgers, an animated sitcom
I tried to connect them. After some failed attempts, I remembered the clue had desserts
for the final level. Parfait is a dessert!
I searched for donkey parfaits + fire + bob's burgers
and stumbled upon: Mrs. Winthorpe's Seven-Layer Parfait Flambé
.
I tried SevenLayerParfaitFlambe
as the answer. And that worked!
That was a wild ride! The layers were very cleverly crafted. Masterpiece design!
Level B5: Decoding Space Gibberish
Channel topic:
Welcome to Level B5: NEXUS [pavonia] || Theme: Space || Clues: HUNTS VILEX XNVVU HVTIE HULHI NUESL XEHSS XINUT NTILE UEUEV UITVS NVVUX || Hints: https://ircp.link/CPz5hZ | https://ircp.link/E5wjwZ | Jupiter-C
The clue looked like some code:
HUNTS VILEX XNVVU HVTIE HULHI NUESL XEHSS XINUT NTILE UEUEV UITVS NVVUX
Googling for “HUNTSVILEX”, I came across an article that mentioned the following:
The Jupiter-C was part of the IRBM project, and the sequence of manufacture of the rockets was considered a military secret. So the designation painted on the sides of the rocket was not a serial number in clear text, but employed a simple transformation cypher that the staff would be sure not to forget. The key was taken from the name of the design and test base: Huntsville, Alabama.
By discarding all duplicate letters and appending the letter X, a ten-letter key was obtained: HUNTSVILEX. This resulted in a transformation cypher where the letter H was used for the digit 1, the letter U for the digit 2, …, the letter E for the digit 9 and the letter X for the digit 0. For example, the Jupiter-C modified to launch Explorer 1 had the encoded serial number UE painted on the side, indicating it was serial number 29 (U → 2, E → 9). The next version of the Jupiter-C had NX painted on its side, the encoded version of the serial number 30 (N → 3, X → 0).
Jackpot! I found out what the code is and how to decode it. I decided to look at the hints. The first URL showed an image of Tom Clancy’s The Division video game. I suspected I needed to take “division” from that hint. The second URL showed a picture of Redstone in Minecraft. It likely meant “red stone” or just “stone”. The hints weren’t directly relevant.
Given I knew how to decode the ciphertext, I started with that.
I needed to convert the ciphertext based on this mapping: H
-> 1, U
-> 2, … X
-> 0. Time for some code:
ciphertext = "HUNTS VILEX XNVVU HVTIE HULHI NUESL XEHSS XINUT NTILE UEUEV UITVS NVVUX".split()
# encoding system based on the Jupiter-C
encoding_key = {'H': '1', 'U': '2', 'N': '3', 'T': '4', 'S': '5', 'V': '6', 'I': '7', 'L': '8', 'E': '9', 'X': '0'}
decoded_message = [" ".join("".join(encoding_key[char] for char in word) for word in ciphertext)]
decoded_message
I got the following output:
['12345 67890 03662 16479 12817 32958 09155 07324 34789 29296 27465 36620']
Those looked like random numbers, and I could not find any similarities. I joined them into a single string, split them up in pairs, and read them as ASCII, alphabets, etc., but that didn’t seem helpful. Then, I started thinking about whether those numbers had anything in common with them. While exploring that line of thought, I thought maybe I should check if they have any common divisors. I wrote a quick Python script for it:
from math import gcd
from functools import reduce
def find_common_divisors(numbers):
return [i for i in range(2, reduce(gcd, numbers) + 1) if reduce(gcd, numbers) % i == 0]
numbers = [3662, 16479, 12817, 32958, 9155, 7324, 34789, 29296, 27465, 36620]
common_divisors = find_common_divisors(numbers)
common_divisors
The output was surprising! It was:
[1831]
1831 was a common divisor for all the numbers! Very well, time to divide each number by 1831 and look at the output:
divisions_by_1831 = {number: number // 1831 for number in numbers if 1831 in primefactors(number)}
divisions_by_1831
I had the following output:
{
3662: 2,
16479: 9,
12817: 7,
32958: 18,
9155: 5,
7324: 4,
34789: 19,
29296: 16,
27465: 15,
36620: 20
}
All the numbers were less than 26, so I tried mapping them to alphabets (1 = A
, 2 = B
, …, 26 = Z
):
import string
alphabet_mapping = {str(i): letter for i, letter in enumerate(string.ascii_lowercase, start=1)}
result = ''.join([alphabet_mapping[str(i)] for i in divisions_by_1831.values()])
print(result)
The output was:
bigredspot
Sweet! I googled jupiter-c big red spot
and arrived at the Wikipedia page: https://en.wikipedia.org/wiki/Great_Red_Spot. Now it was a matter of finding the correct key. I couldn’t find a matching key, but then I remembered I hadn’t included 1831 in my search terms. It wasn’t just a number; it could also be a year. Searching with jupiter-c big red spot 1831
, I found the following:
The first record of the Great Red Spot is a drawing made in 1831 by German amateur astronomer Samuel Heinrich Schwabe of the “Hollow” in which the spot sits.
I tried SamuelHeinrichSchwabe
as the answer and that worked! I had blitzed through this level. Not getting stuck in any rabbit holes for a change was very satisfying. Onto the next one!
Level B6: Bad Apple
Channel topic:
Welcome to Level B6: Static B [za3k] || Theme: Video Games || Clues: https://ircp.link/dr0U6D [Warning: video contains flickering/flashing lights] | You’re looking at a single famous thing || Hints: Just look. If you can’t look, contact your nearest optician or ircpuzzles staff. | Let’s turn on the auto stereo!
I opened the first URL. It had a video of 04:16 minutes with white noise, just like A6:
Skimming through the video, I didn’t find anything except noise.
The hint said I needed to contact an optician or ircpuzzles staff. I had no idea what this meant.
The next hint mentioned turning on the auto stereo. Automobile stereo? I had no clue what this meant, either.
Rewatching the video or analyzing it frame by frame didn’t help. The video file had nothing hidden inside, either. I was totally stumped.
Then, I figured auto stereo must mean something. It could be related to a game. But after a while, I discarded that possibility because I couldn’t find anything relevant.
I thought it could be related to video. I started searching for auto stereo video
. I noticed Autostereoscopy
and Autostereogram
in the search results. That looked interesting! I opened the Autostereogram Wikipedia page:
The image on the right looked similar to the video! That looked like the right direction.
I realized the video might need to be viewed as a stereogram. I had never done that before, so I looked it up on YouTube. After 30 minutes, I still had no idea, but I had a mild headache from twitching and turning my eyes to view the video in 3D.
I didn’t want to accept defeat. I looked up online and found a tool that solves stereograms: https://piellardj.github.io/stereogram-solver/. There was one problem, though. It only accepted images. No worries; I took a screenshot of the video from a random point and uploaded it to this tool.
I was surprised! In the decoded image, there were two women and what appeared to be two kids in hats! That means other frames also had data, but I was unable to view it with my eyes, as the puzzle author intended.
I started to think about ways to bypass it. My first thought was to re-implement the same logic on the website and process the video through it so that I could view the decoded video. However, that was going to take a while.
I remembered I had all the video frames extracted. I had a cool idea, but I didn’t know if it would work. What if I used JavaScript to load each frame from my local system and display them one after the other, with a small delay? It could potentially mimic a video.
I decided to try that approach out. I checked to see if I could load just one image. I executed the following on the browser console:
getImage('/home/user/b6/frames/frame-0404.png')
It failed with Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data
. How would I make this same origin? Thankfully, the solution was simpler than I thought. This was just a one-time thing, so I decided to employ a hack: I downloaded the HTML on https://piellardj.github.io/ locally. Then, I spun up a basic HTTP server using Python on the same directory in which the frames existed:
$ python3 -m http.server
Then, I opened the HTML file and executed:
getImage('/frames/frame-0454.png')
I removed everything except the image canvas. Then, I tried to load the images sequentially and mimic a video. With some trial and error, I came up with:
function loadImages(startIndex, endIndex) {
// pad the number with leading zeros
function padNumber(num, size) {
return num.toString().padStart(size, '0');
}
for (let i = startIndex; i <= endIndex; i++) {
// delay to create a staggered effect
let delay = (i - startIndex) * 10; // 1 second
setTimeout(() => {
const imagePath = `/frames/frame-${padNumber(i, 4)}.png`;
console.log(`Loading image: ${imagePath}`);
getImage(imagePath);
}, delay);
}
}
loadImages(100, 7886);
There were 7886 frames in total. The code would start from the first and continue until the last. This is what it looked like:
I added the functionality to play/pause so I can stop and focus on the elements if needed. This is what the code looked like:
function loadImages(startIndex, endIndex) {
let paused = false;
let currentImageIndex = startIndex;
let intervalId;
function padNumber(num, size) {
return num.toString().padStart(size, '0');
}
function loadImage() {
if (paused) return;
const imagePath = `/frames/frame-${padNumber(currentImageIndex, 4)}.png`;
console.log(`Loading image: ${imagePath}`);
getImage(imagePath);
currentImageIndex++;
if (currentImageIndex <= endIndex) {
intervalId = setTimeout(loadImage, 5);
}
}
function togglePause() {
paused = !paused;
if (!paused) loadImage();
}
function createButton() {
const button = document.createElement('button');
button.textContent = 'Pause/Play';
button.addEventListener('click', togglePause);
button.style.position = 'fixed';
button.style.top = '20px';
button.style.left = '20px';
document.body.appendChild(button);
}
loadImage();
createButton();
}
loadImages(100, 7886);
This is what it looked like (view on Vimeo):
Initially, I was unable to recognize what the video was. Then, after reverse-image-searching some still shots, I quickly realized it was a video called Bad Apple. Check it out here (view on YouTube):.
I tried badapple
as the answer, and it worked!
Level B7: Jörð’s Puzzle
Channel topic:
Welcome to Level B7: Jörð’s Puzzle [pavonia] || Theme: Mythology || Clues: And Odin instructed his beloved to add a super secret addendum to the Great Book of Hvatvetna, and Jörð did as she was bidden … || Hints: https://ircp.link/5hI9Sm | Who is Jörð? | https://ircp.link/m4UycZ | no Braille | Cipher | 🌻🛏️/🌸🍲
In IRCPuzzles, each level was technically solvable using just the information in the Clue section, and the Hints were merely helpful pointers. Initially, I assumed some hidden info lurked within the string And Odin instructed his beloved to add a super secret addendum to the Great Book of Hvatvetna, and Jörð did as she was bidden…
. However, after some thought, I tossed that idea aside, as it seemed to conceal nothing.
The first hint was this image:
This was the identical image to B3. I needed to confirm, so I quickly checked the topic for B3 and discovered the same image URL. This revelation meant the puzzle author had intertwined two puzzles within the same image. It also indicated this puzzle couldn’t be solved with the Clue text alone. I verified this with a moderator, and they confirmed the image was necessary to solve this level.
I had gazed at that image for so long during B3 that I nearly developed PTSD from it. Returning to the same image was hardly thrilling. Initially, I lacked the motivation to tackle this one, yet somehow, I dredged up the drive to proceed.
Analyzing the hints
I analyzed the hints one by one:
Hint | Analysis |
---|---|
Miley Cyrus’s “Flowers” video | Hinted at the flowers in the image. |
“No Braille” | Indicated no Braille reading was involved. |
Cipher | A cipher was hidden somewhere. |
🌻🛏️/🌸🍲 | The emojis were: sunflower, bed / cherry blossom, pot of food. |
The initial clue hinted at a Norse mythology backdrop involving Odin and Jörð tasked with a secretive addition to the Great Book of Hvatvetna. Despite delving into research, I was getting nowhere.
I then sifted through the provided texts and images for any overt or covert indicators that might suggest a cipher. This included searching for patterns in letter frequencies, word placements, and thematic symbols that could link to mythological narratives or historical cipher methods known in Norse culture.
I couldn’t make any progress. I took a break. Once I came back, I noticed the topic had shifted:
Welcome to Level B7: Jörð’s Puzzle [pavonia] || Theme: Mythology || Clues: https://ircp.link/eBXZ3b | Cipher | 🌻🛏️/🌸🍲 || Hints: https://ircp.link/sGhVkf | 🕛
Ah, the image from B3 was now part of the Clue section. “https://ircp.link/sGhVkf" (the new hint) was a blurred image:
I recalled encountering something vaguely similar during my searches but couldn’t pinpoint it. Eventually, I found it. It was a picture of the Rök runestone, specifically https://en.wikipedia.org/wiki/R%C3%B6k_runestone.
While skimming through the Wikipedia page, I noticed some mentions of cipher
(emphasis mine):
This is a direct Unicode transliteration of the runes, as close to the scriptio continua original as possible. Ciphers are within brackets, with the resolution following the equals sign:
…
[3:2 = ᚢ] [1:4 = ᛚ] [2:2 = ᚿ] [2:3 = ᛁ] [3:5 = ᚱ] [3:2 = ᚢ]ᚦᛧ [2:5 = ᛌ] [2:3 = ᛁ]ᛓᛁ [3:2 = ᚢ] [2:3 = ᛁ]ᛆ [3:2 = ᚢ] [2:4 = ᛆ]ᚱᛁ
…
Given the hints, flowers seemed crucial in cracking this puzzle. Perhaps a pattern in how the flowers were arranged in the image was vital. My attempts to find a pattern were fruitless.
Ciphers were within brackets, so perhaps I needed to represent the flowers in this format, such as 3:2
. But a careful examination of the image revealed nothing resembling this format.
After revisiting the hint, perhaps sunflower, bed / cherry blossom, pot of food
meant flower bed and flower pot. Both were present in the image, so I shifted my focus in that direction.
After numerous attempts using different grouping methods, none seemed logical. I decided to start from scratch, this time including the shrubberies:
Then, diving deeper into rune ciphers, I realized that cipher runes are encoded based on a row/column grid, either 6x3 or 8x3 (depending on Younger or Elder Futhark). I had assumed the numbers could only be as high as [4:4], but this was incorrect.
I realized that the specifics of that particular runestone weren’t crucial. If I had something like [2:1], how would I convert it into a rune? The traditional method would be to obtain rune number 1 from row 2, i.e., ᚼ (the exact runes depend on whether you’re using Elder or Younger Futhark, but all the puzzles had used Younger Futhark so far).
But I encountered another problem: how do I determine the ordering within these? How do you know whether something is 2:1 or 1:2? I noted that colors in some lots were too many to be a row, so I guessed that each color was consistently either a row or column.
Attempting to decode using Elder Futhark, I obtained:
3:3 2:3 1:4 / 2:1 2:4 / 1:2 3:5 2:4 1:1 1:1...
This translated to:
eia hj umjff
That did not look readable. After more errors, I wrote some code to convert the cipher to runes:
row1, row2, row3 = 'ᚠᚢᚦᚬᚱᚴ/ᚼᚾᛁᛅᛋ/ᛏᛒᛘᛚᛦ'.split('/') # Younger Futhark
runes_mapping = {
1: list(row1),
2: list(row2),
3: list(row3)
}
cipher_text = [(3,3), (2,3), (1,4), (2,1), (2,4), (1,2), (3,5), (2,4), (1,1), (1,1)]
decoded_text = [runes_mapping[row][pos-1] for row, pos in cipher_text]
decoded_text_joined = ''.join(decoded_text)
decoded_text_joined
This produced the output:
ᛘᛁᚬᚼᛅᚢᛦᛅᚠᚠ
Using https://valhyr.com/pages/rune-translator, I translated it to miohauʀaff
.
That wasn’t really a word. It didn’t result in any anagrams, either. Then, I considered flipping the rows around, as Vikings often did when drawing cipher runes.
runes_mapping = {
1: list(row3), # flipped
2: list(row2),
3: list(row1)
}
cipher_text = [(3,3), (2,3), (1,4), (2,1), (2,4), (1,2), (3,5), (2,4), (1,1), (1,1)]
decoded_text = [runes_mapping[row][pos-1] for row, pos in cipher_text]
decoded_text_joined = ''.join(decoded_text)
print(decoded_text_joined)
This produced ᚦᛁᛚᚼᛅᛒᚱᛅᛏᛏ
, which translated to: þilhabratt
. Googling this yielded no results.
I reevaluated my approach. Perhaps I had erred somewhere. I questioned the logic behind the ordering for ciphers in cipher_text
. How had I decided (3,3)
was the first? Perhaps there was a logical ordering.
I also had overlooked a hint: the emoji showing a clock at 12 o’clock. What did it mean? Was it merely indicating the number 12, or was it a random “clock”? After some thought, I speculated it might suggest a “clockwise” direction. I decided to arrange the ciphers in a clockwise order. Since I had coded it, this meant rearranging the tuples in cipher_text
. Quickly, I had:
cipher_text = [(1,2), (3,5), (2,4), (1,1), (1,1), (2,4), (2,1), (1,4), (2,3), (3,3)]
decoded_text = [runes_mapping[row][pos-1] for row, pos in cipher_text]
decoded_text_joined = ''.join(decoded_text)
print(decoded_text_joined)
This gave me ᛒᚱᛅᛏᛏᛅᚼᛚᛁᚦ
, which translated to brattahliþ
. Unfortunately, googling did not yield any results, even with an exact match search. I searched for brattahlí
(removing the last character, in case that might be causing issues). To my surprise, I finally had a result:
Brattahlíð, often anglicised as Brattahlid, was Erik the Red’s estate in the Eastern Settlement Viking colony he established in southwestern Greenland toward the end of the 10th century. The present settlement of Qassiarsuk, approximately 5 km southwest from the Narsarsuaq settlement, is now located in its place. Wikipedia
I tried brattahlid
as the answer, and that worked! Finally. That was a tough one. And I trudged through it.
Level B8
Channel topic:
Congratulations! You’re done with Track B. Take this with you: finalist || qt risti runaʀ þisaʀ
B track was complete! 🎉
Level 6
I had the two parts of the key from A8 and B8. I quickly typed /join #ircpuzzles-2024-afpc-07 deafeningfinalist
. I was in!
The channel topic read:
Welcome to Leve– wait, YOU’RE WINNER !
I had won! My IRCPuzzles journey was over. Kudos to the IRCPuzzles team for organizing the event and creating thoughtful puzzles.
If you made it this far without skipping, I applaud your perseverance—and I’m sorry for the length. If you found the writeup interesting, please share it in your circles.