DEV Community

Cover image for Perfect Bird - TRX CTF 2025 Writeup
VaiTon for Ulisse

Posted on • Originally published at ctf.ulis.se

Perfect Bird - TRX CTF 2025 Writeup

Cover image: Asian pied starlings (Gracupica contra) - CC-BY-SA 4.0

Challenge description

As a bird soaring through the sky, you seek the perfect language, and then... you find this

Writeup

Opening the chall.db3 file, I come across a weird programming language that looks kind of like JavaScript. The code is full of strange symbols and conventions, and without a guide, it’s pretty hard to figure out what it does.

My go-to move is always to check the challenge description for hints. Here, the words bird and perfect language are italicized, which seems important. So, I search for "bird perfect language programming" online.

The first result is a video by the streamer ThePrimeTime, where he talks about this exact programming language.

A Google search for

From the video's description, I find my way back to the GitHub repository that explains this language: https://github.com/TodePond/GulfOfMexico.

It looks like it was originally called "DreamBerd," but now it goes by "GulfOfMexico."

DreamBerd - A Perfect Language

Be bold! End every statement with an exclamation mark!

print("Hello world")!

I start thinking about ways to decipher the code and come up with the idea of writing a script to rewrite it into an existing language (one with an existing interpreter!).

At first, I consider converting it to Python, but then I realize JavaScript would be a better choice since it’s closer to the original language.

So, I write a Python script to convert the code to JavaScript and run it to see the output.

Converting DreamBerd to JavaScript

The key here is that we don’t need a perfect (pun intended) conversion—just something accurate enough to run the challenge code.
It doesn’t have to handle every possible DreamBerd program, just this one.

The first thing we need to do is to fix the lifetimes in the code.

Gulf of Mexico has a built-in garbage collector that will automatically clean
up unused variables. However, if you want to be extra careful, you can specify a
lifetime for a variable, with a variety of units.

In particular, the one bit we cannot tollerate are negative lifetimes:

Variable hoisting can be achieved with this neat trick. Specify a negative
lifetime to make a variable exist before its creation, and disappear after its
creation.

print(name)! //Luke
const const name<-1> = "Luke"!
#!/bin/env python3
import sys
import re

def fix_lifetimes(lines):
    new_lines = []

    for i, line in enumerate(lines):
        match = re.match(r".+<(.+)>.+", line)
        if not match:
            new_lines.append(line)
            continue

        lifetime = match.group(1)

        if lifetime == "Infinity":
            new_lines.append(line)
            continue

        lifetime = int(lifetime)

        if lifetime >= 0:
            line = line.replace(f"<{lifetime}>", "")
            new_lines.append(line)
            continue

        new_pos = max(len(new_lines) + lifetime, 0)

        # remove the invalid lifetime
        line = line.replace(f"<{lifetime}>", "")
        new_lines.insert(new_pos, line)

        print(f"Moved line {i + 1} -> {new_pos + 1}", file=sys.stderr)

    return new_lines

lines = sys.stdin.readlines()
program = fix_lifetimes(lines)
program = "".join(program)

Enter fullscreen mode Exit fullscreen mode

Then we need to replace each "strange lang" construct with the corresponding JavaScript construct.

  • ! -> nothing
  program = re.sub(r"!+", "", program)
Enter fullscreen mode Exit fullscreen mode
  • ; -> !
  for i in range(1, 100):
      program = re.sub(r";([\w|!|(|)]+)", r"!(\1)", program)
Enter fullscreen mode Exit fullscreen mode
  • Every kind of variable declaration is replaced with let.
  program = program.replace("const const const", "let")
  program = program.replace("const const", "let")
  program = program.replace("const var", "let")
  program = program.replace("var var", "let")
Enter fullscreen mode Exit fullscreen mode
  • The variable 42 (which is an invalid identifier in JavaScript) is replaced with var_42.
  program = re.sub(r"let (\d+)", r"let var_\1", program)
Enter fullscreen mode Exit fullscreen mode
  • Every usage of 42 is replaced with var_42.
  program = program.replace("42 +=", "var*42 +=")
  program = program.replace("42 -=", "var_42 -=")
  program = program.replace("42 *=", "var*42 *=")
  program = program.replace("42 ^ ", "var*42 ^ ")
  program = program.replace("42 = ", "var_42 = ")
  program = program.replace("42 * ", "var*42 * ")
  program = program.replace("42 / ", "var_42 / ")
  program = program.replace("42 % ", "var_42 % ")
  program = program.replace("42 + ", "var_42 + ")
  program = program.replace("42 - ", "var_42 - ")
  program = program.replace("!42", "!var_42")
Enter fullscreen mode Exit fullscreen mode
  • Remove Infinity lifetimes.
  program = re.sub("<Infinity>", "", program)
Enter fullscreen mode Exit fullscreen mode
  • Replace functi with function.
  program = re.sub(r"functi (.+?) \(\) =>", r"function \1()", program)
Enter fullscreen mode Exit fullscreen mode
  • Replace print with console.log.
  program = re.sub(r"print", "console.log", program)
Enter fullscreen mode Exit fullscreen mode
  • Fix array starting convention, as in DreamBerd arrays start at -1 (oh, the horror).
  # arrays start at -1 ...
  program = re.sub(r"(\w+)\[(.+)\]", r"\1[\2 + 1]", program)
Enter fullscreen mode Exit fullscreen mode

Finally, we print the program.

print(program)
Enter fullscreen mode Exit fullscreen mode

Then we use the script to convert the code to JavaScript.

cat chall.db3 | python3 invertlines.py > chall_ok.js
Enter fullscreen mode Exit fullscreen mode

And we then run the code with Node.js.

$ node chall_ok.js
[
    84, 82, 88, 123, 116, 72, 105, 53, 95,
    73, 53, 95, 116, 104, 51, 95, 80, 51,
    114, 102, 51, 99, 116, 95, 108, 52, 110,
    71, 85, 52, 103, 51, 33, 33, 33, 33,
    33, 33, 125
]

Enter fullscreen mode Exit fullscreen mode

The result we get is an array of integers.
We can convert it to ASCII to get the flag.

#!/bin/env python3
m=[
   84,  82,  88, 123, 116, 72, 105, 53,  95,
   73,  53,  95, 116, 104, 51,  95, 80,  51,
  114, 102,  51,  99, 116, 95, 108, 52, 110,
   71,  85,  52, 103,  51, 33,  33, 33,  33,
   33,  33, 125
]

for i in range(0, len(m)):
    print(chr(m[i]), end="")
Enter fullscreen mode Exit fullscreen mode
$ python3 decode.py
TRX{tHi5_I5_th3_P3rf3ct_l4nGU4g3!!!!!!}
Enter fullscreen mode Exit fullscreen mode

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more