Preliminary Round

Welcome back to my writeup! In this post, I’ll take you step by step through how we approached and solved the challenges from the Trend Micro uCTF preliminaries, held on August 22, 2025. This CTF was intense and fast-paced, giving us only one hour to solve all the challenges, which really tested our problem-solving speed and teamwork. I’ll break down our thought process, the tools we used, and the strategies that helped us tackle each challenge efficiently.

Attached in the Email

In this challenge, we are given a Base64 encoded string, all we need to do is to decode it.

echo "<base64_encoded_string>" | base64 -d

Common Password

In this challenge, we are given a SHA256 hash, all we need to do is to crack this using John or Hashcat. In our case, we will use John.

john --format=raw-sha256 --wordlist=/usr/share/wordlists/rockyou.txt hash

Flag: uCTF{2_iloveyou}

Ancient File

For this challenge, we were given a file resembling a wordlist.

The description is straightforward, so there’s no need to overthink it—simply use Ctrl + F or grep to search for the flag format.

Follow the Train

In this challenge, we are given an image.

A train, since this is an OSINT challenge, we need to get the small details. If you noticed, The red train has the marking "VIEUX-LYON FOURVIÈRE". This is a funicular railway in Lyon, France, that goes up to the Basilica of Notre-Dame de Fourvière (a very famous pilgrimage site).

Let’s look up the Basilica’s contact info.

Flag: uCTF{14_33478251301}

In The Property

In this challenge, we are given a PNG file

Just from the title, it’s clear that something is hidden in the metadata. Let’s take a closer look.

File in a File

For this challenge, we’re given a file that’s been compressed multiple times. Here’s the sequence of compressions

  1. lz4 -d file file.out

  2. zstd -d file.out -o file.next

  3. unrar x file.next

  4. unzip file3

  5. bunzip -d file2

  6. gzip -d file2.gz

  7. tar -xf file2

Hidden in the Bytes

In this challenge, we are given a PNG file

Once again, the title hints that LSB is at play here, so let’s examine it using zsteg.

zteg file.png

PWSH Guess

In this challenge, we are given an obfuscated powershell.

It consists of a Base64 encoded string, when I decode it, this is the result

Now the flag is obvious here

If ($n -lt (644 % 2)) { Write-Host "$([char]0x75)$([char]0x43)$([char]0x54)$([char]0x46)$([char]0x7B)$([char]0x31)$([char]0x31)$([char]0x5F)$([char]0x56)$([char]0x47)$([char]0x68)$([char]0x70)$([char]0x63)$([char]0x30)$([char]0x6C)$([char]0x7A)$([char]0x51)$([char]0x55)$([char]0x5A)$([char]0x68)$([char]0x61)$([char]0x32)$([char]0x56)$([char]0x47)$([char]0x62)$([char]0x47)$([char]0x46)$([char]0x6E)$([char]0x7D)" }

This PowerShell snippet constructs a string (the flag) by converting a sequence of hexadecimal ASCII codes into characters using $([char]0xXX). Each $([char]0xXX) represents one character in the flag, and Write-Host prints them all together. The if condition $n -lt (644 % 2) evaluates whether $n is less than the remainder of 644 divided by 2 (which is 0), so the block only executes if $n < 0—meaning it’s mostly used as a “hidden” or conditional print. In Python, this can be decoded by taking the list of hex codes, converting each to its corresponding character using chr(), and joining them into a string, effectively reconstructing the exact flag the PowerShell code would display.

hex_values = [
    0x75, 0x43, 0x54, 0x46, 0x7B, 0x31, 0x31, 0x5F, 0x56, 0x47, 0x68,
    0x70, 0x63, 0x30, 0x6C, 0x7A, 0x51, 0x55, 0x5A, 0x68, 0x61, 0x32,
    0x56, 0x47, 0x62, 0x47, 0x46, 0x6E, 0x7D
]

flag = ''.join(chr(h) for h in hex_values)
print(flag)

Here's the output

Double Blind

In this challenge, we are provided with an encrypted string.

a2hhaGloemxuaHN2eWwuanZ0

As mentioned in the challenge, it's encrypted + encoded. The initial string looks like a Base64, let's try

Looks like ROT13, let's check

There it is, that's the DNS, now we will encode it to Base64 and we will use that as the flag.

Flag: uCTF{3_ZGF0YWJhc2VnYWxvcmUuY29t}

Need The Debugger

For this challenge, we are provided with a binary file. The title hints that a debugger will be required, but before using one, let's first analyze the file in Ghidra.

This is the main function, now let's check the do_guess function.

You see the Juicy part here?

if (numberofguess == 1) {
    builtin_memcpy(pp,"_i~lQ\x13uank\tlekkL|II^\x1bD\x12W",0x19);
    sVar4 = strlen((char *)pp);
    memfrob(pp,(long)(int)sVar4);
    puts((char *)pp);
}

This code snippet executes when the player correctly guesses the number on their very first attempt. It first copies an obfuscated string into the buffer pp using builtin_memcpy. The length of the string is calculated with strlen, and then memfrob is applied to the buffer, which is a simple reversible XOR-based transformation used to obscure data. Finally, the transformed string is printed with puts, effectively revealing a hidden message or flag that is only accessible if the player guesses the number correctly on their first try.

What is memfrob?

  • memfrob() is a glibc function that XORs each byte with 42 (0x2A).

  • So the hidden string is simply the above bytes XOR’d with 0x2A.

That means the flag is _i~lQ... XOR 0x2A.

data = b"_i~lQ\x13uank\tlekkL|II^\x1bD\x12W"
decoded = bytes([b ^ 0x2A for b in data])
print(decoded.decode(errors="ignore"))

Here's the output

That’s a fantastic milestone!

Out of 199 competing teams, we proudly secured 2nd place, demonstrating our skills and teamwork. Now, with the finals ahead, we’re more motivated than ever to push our limits, sharpen our strategies, and aim for the top spot. This achievement not only reflects our hard work but also sets the stage for an even greater challenge in the upcoming competition.

Last updated