CTF Writeup: "Wait for the Clock" — Cyber Sprint 2025
Category: Digital Forensics Difficulty: Beginner Point: Dynamic (400) Flag: CS{Hack3r_C4N_F1nd_Y0U}
1. Objective
The objective of this challenge is to analyze an unknown file, determine its actual format, extract embedded data, follow a sequence of hidden clues, and ultimately recover the final flag. The challenge tests fundamental forensics skills such as file identification, archive extraction, metadata interpretation, and data decoding.
2. Challenge Understanding
Participants are provided with a single file without context. The challenge intentionally conceals its nature behind misleading extensions and a multi-stage extraction trail.
Key expectations:
- Identify the true file type.
- Extract resources from a CAD file.
- Examine file naming patterns.
- Recognize encoded URLs.
- Follow external-link breadcrumbs.
- Decode an SVG artifact to recover the flag.
This design encourages systematic analysis and attention to detail.
3. Methodology
A structured forensic approach was used:
- Initial File Identification Determine the real format of the provided file using standard Linux utilities.
- Archive Extraction Use CAD-specific extraction tools to unpack the content.
- Filename Pattern Analysis Investigate the meaning behind the numbered image filenames.
- URL Reconstruction Combine extracted characters to reveal a Pastebin URL.
- External File Retrieval Follow the Pastebin link to obtain a Google Drive resource.
- Secondary Extraction
Unzip
clockwork.zipto reveal an SVG file. - Data Decoding Use a custom Python script to decode hidden content within the SVG.
- Flag Recovery Extract the final flag from the decoded output.
4. Detailed Analysis
4.1 Identifying the File Type
The unknown file was inspected using:
file laptopgallary.zip
The output confirmed that the file was a CAD archive. CAD files often store multiple resources and require specific utilities for extraction.
4.2 Extracting CAD Contents
Using cadextract, the archive was decompressed:
cadextract laptopgallary.zip
Extraction produced 21 image files. Each filename contained:
- A leading numeric index
- A trailing single character
This hinted at an encoded message.
4.3 Sorting Filenames for Message Reconstruction
To correctly order the images, a natural sort was applied:
ls -v
After sorting, the sequential characters formed a readable URL. This URL pointed to a Pastebin page, indicating the next stage of the challenge.
4.4 Extracting URL-Based Clues
Visiting the Pastebin URL revealed a Google Drive link. The drive contained a file named:

4.5 Extracting clockwork.zip
The archive was extracted via:
unzip clockwork.zipThis produced:
clockwork.svgSVGs can visually appear simple, yet internally contain structured text-based data capable of hiding encoded information.
4.6 Decoding the SVG File
A dedicated Python decoding script was used to parse and interpret the embedded data within clockwork.svg.
The script successfully extracted hidden text, revealing the final output.
#!/usr/bin/env python3
import math, base64, argparse, sys
import xml.etree.ElementTree as ET
def parse_float(v, default=0.0):
try:
return float(v)
except Exception:
try:
return float(str(v).rstrip('px'))
except Exception:
return default
def get_coords(line):
return tuple(parse_float(line.attrib.get(k, "0")) for k in ("x1","y1","x2","y2"))
def stroke_width(elem):
v = elem.attrib.get('stroke-width') or elem.attrib.get('{http://www.w3.org/2000/svg}stroke-width')
if v is None:
return 1.0
try:
return float(str(v).rstrip('px'))
except Exception:
return 1.0
def find_namespace(root):
if '}' in root.tag:
ns = root.tag.split('}')[0].strip('{')
else:
ns = "http://www.w3.org/2000/svg"
return {'svg': ns}
def angle_to_dir(x1, y1, x2, y2):
dx, dy = (x2 - x1), (y2 - y1)
# Flip dy because SVG y-axis increases downward
ang = math.degrees(math.atan2(-dy, dx)) % 360.0
# Snap to one of 8 directions (0..7) at 45° steps, with 0 = right
return int((ang + 22.5) // 45) % 8
def puzzle_transform(v):
# Mirror then rotate +2 steps (i.e., +90°)
return ((8 - v) % 8 + 2) % 8
def cluster_by_center(lines, center_round=0.5):
# Group lines by rounded (x1,y1) to associate two hands per clock
def r(v): return round(v / center_round) * center_round
groups = {}
for ln in lines:
x1, y1, x2, y2 = get_coords(ln)
key = (r(x1), r(y1))
groups.setdefault(key, []).append(ln)
# keep only groups that look like clocks (>=2 lines)
return {k: v for k, v in groups.items() if len(v) >= 2}
def read_order(keys, mode="grid"):
if mode == "grid":
# top-to-bottom, then left-to-right
return sorted(keys, key=lambda p: (p[1], p[0]))
elif mode == "x":
return sorted(keys, key=lambda p: (p[0], p[1]))
else:
return sorted(keys)
def b64_from_vals(vals):
table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
b64 = "".join(table[v] for v in vals if 0 <= v <= 63)
# pad to multiple of 4
pad_len = (4 - len(b64) % 4) % 4
return b64 + ("=" * pad_len)
def decode_svg(svg_path, center_round=0.5, order="grid", debug=False):
tree = ET.parse(svg_path)
root = tree.getroot()
ns = find_namespace(root)
lines = list(root.findall(".//svg:line", ns))
if not lines:
raise RuntimeError("No <line> elements found in SVG (are hands drawn with <path>?)")
groups = cluster_by_center(lines, center_round=center_round)
if not groups:
raise RuntimeError("No line clusters by centers found. Try a larger --center-round (e.g., 1.0 or 2.0).")
vals = []
for key in read_order(list(groups.keys()), mode=order):
hands = sorted(groups[key], key=stroke_width, reverse=True)[:2]
if len(hands) < 2:
continue
thick, thin = sorted(hands, key=stroke_width, reverse=True)
A = angle_to_dir(*get_coords(thick)) # thick → high bits
B = angle_to_dir(*get_coords(thin)) # thin → low bits
# Apply the puzzle-specific transform
A = puzzle_transform(A)
B = puzzle_transform(B)
if debug:
print(f"center={key} -> raw(A,B)={A},{B}")
vals.append((A << 3) | B)
b64 = b64_from_vals(vals)
try:
decoded = base64.b64decode(b64, validate=False)
return b64.rstrip("="), decoded
except Exception as e:
raise RuntimeError(f"Base64 decode error: {e}")
def main():
ap = argparse.ArgumentParser(description="Decode this clock-cipher SVG (with puzzle-specific transform).")
ap.add_argument("svg", help="Path to SVG file (e.g., clockwork.svg)")
ap.add_argument("--center-round", type=float, default=0.5, help="Rounding for centers (default 0.5 px)")
ap.add_argument("--order", choices=["grid","x"], default="grid", help="Reading order: grid=y→x (default) or x→y")
ap.add_argument("--raw", action="store_true", help="Print raw bytes instead of UTF-8 text")
ap.add_argument("--debug", action="store_true", help="Print per-clock details")
args = ap.parse_args()
b64, decoded = decode_svg(args.svg, center_round=args.center_round, order=args.order, debug=args.debug)
print("Base64:", b64)
if args.raw:
print("Decoded (raw):", decoded)
else:
try:
print("Decoded (utf-8):", decoded.decode("utf-8", errors="replace"))
except Exception:
print("Decoded (raw):", decoded)
if __name__ == "__main__":
main()5. Findings
The decoded SVG content contained the challenge flag:

CS{Hack3r_C4N_F1nd_Y0U}This confirms that all extraction stages were completed correctly and that the investigative chain followed the intended path.
6. Conclusion
The challenge demonstrated a multi-layer forensic workflow, requiring:
- Correct identification of file formats
- Use of specialized extraction tools
- Recognition of patterns in filenames
- Reconstruction of encoded URLs
- Decoding of structured graphical formats
The final flag was successfully obtained.
7. Learning Outcomes
Participants completing this challenge gain experience in:
- Using
fileto determine real file formats - Handling CAD-based archives with
cadextract - Applying natural sorting (
ls -v) to reveal patterns - Recognizing encoded information in filenames
- Following chained OSINT-style clues (Pastebin → Google Drive)
- Understanding that SVG files can hide data
- Executing Python scripts to extract embedded content
These are foundational skills in digital forensics, especially in CTF environments where layered hiding techniques are common.