flag format: tjctf{...} file challenge đã được đín...
作成日: 2026年5月17日
使用モデル GPT-5.5 Thinking by Chat01
作成日: 2026年5月17日
使用モデル GPT-5.5 Thinking by Chat01
flag format: tjctf{...}
file challenge đã được đính kèm, không cần bạn tải nữa
tìm ra flag rồi viết write up chi tiết kèm lệnh +script đã dùng
Flag tìm được:
texttjctf{sine_waves_are_the_best!}
Hidden phrase trong spectrogram là:
textSINE WAVES ARE THE BEST!
File tên .mp4, nhưng thực tế là Matroska container:
bashfile nesting.mp4 ffprobe -hide_banner -show_streams -show_chapters nesting.mp4
Kết quả đáng chú ý:
textnesting.mp4: Matroska data ... Stream #0:52: Subtitle: ass ... 50 audio streams pcm_f32le: stream 2 -> 51 12 chapters
bashmkdir -p work ffmpeg -hide_banner -y -i nesting.mp4 -map 0:s:0 work/sub.ass ffprobe -v error -show_chapters nesting.mp4
Các chapter có timestamp như:
text0.624, 0.998, 1.203, 1.578, 1.982, ...
Khi lấy text subtitle đúng tại các timestamp này, ta được các mảnh:
textIA MN O T TH EF L AG .N OTY E T!
Ghép lại:
textIAMNOTTHEFLAG.NOTYET!
Tức là subtitle/chapter chỉ là layer đầu, chưa phải flag.
File có 50 audio stream dạng pcm_f32le, từ stream 2 đến 51. Extract chúng:
bashmkdir -p work/audio for i in $(seq 2 51); do ffmpeg -v error -y -i nesting.mp4 -map 0:$i -c:a pcm_f32le work/audio/s$i.wav done
Khi tính RMS theo từng block 50 ms, một số stream có đoạn 1 giây rất mạnh. Các đoạn này nằm đúng theo từng giây và theo stream như sau:
textsec 00 -> stream 43 sec 01 -> stream 36 sec 02 -> stream 34 sec 03 -> stream 7 sec 04 -> stream 23 sec 05 -> stream 42 sec 06 -> stream 27 sec 07 -> stream 29 sec 08 -> stream 10 sec 09 -> stream 30 sec 10 -> stream 47 sec 11 -> stream 50 sec 12 -> stream 26 sec 13 -> stream 17 sec 14 -> stream 14 sec 15 -> stream 46 sec 16 -> stream 28 sec 17 -> stream 48 sec 18 -> stream 15 sec 19 -> stream 2 sec 20 -> stream 25 sec 21 -> stream 40 sec 22 -> stream 18 and 33
Lấy đúng các đoạn 1 giây đó, overlay lại thành một audio mới rồi xem spectrogram sẽ hiện text.
python#!/usr/bin/env python3 import json import os import re import subprocess from pathlib import Path import numpy as np from scipy.io import wavfile from PIL import Image INFILE = "nesting.mp4" WORK = Path("work") AUDIO = WORK / "audio" WORK.mkdir(exist_ok=True) AUDIO.mkdir(exist_ok=True) def run(cmd): print("[cmd]", " ".join(map(str, cmd))) subprocess.check_call(cmd) def ass_time_to_sec(t): h, m, s = t.split(":") return int(h) * 3600 + int(m) * 60 + float(s) def parse_ass(path): events = [] for line in Path(path).read_text(errors="replace").splitlines(): if not line.startswith("Dialogue:"): continue parts = line.split(",", 9) start = ass_time_to_sec(parts[1]) end = ass_time_to_sec(parts[2]) text = parts[9] events.append((start, end, text)) return events def subtitle_at(events, t): for start, end, text in events: if start <= t < end or abs(start - t) < 1e-6: return text return "" # 1. Extract ASS subtitle sub_path = WORK / "sub.ass" run(["ffmpeg", "-v", "error", "-y", "-i", INFILE, "-map", "0:s:0", str(sub_path)]) # 2. Read chapters chap_json = subprocess.check_output([ "ffprobe", "-v", "error", "-show_chapters", "-of", "json", INFILE ]) chapters = json.loads(chap_json)["chapters"] chapter_times = [float(c["start_time"]) for c in chapters] events = parse_ass(sub_path) chapter_msg = "".join(subtitle_at(events, t) for t in chapter_times) print("[chapter subtitle message]", chapter_msg) # 3. Detect PCM audio streams probe = subprocess.check_output([ "ffprobe", "-v", "error", "-show_entries", "stream=index,codec_name,codec_type", "-of", "json", INFILE ]) streams = json.loads(probe)["streams"] pcm_streams = [ s["index"] for s in streams if s.get("codec_type") == "audio" and s.get("codec_name") == "pcm_f32le" ] print("[pcm streams]", pcm_streams) # 4. Extract PCM streams for idx in pcm_streams: out = AUDIO / f"s{idx}.wav" if not out.exists(): run([ "ffmpeg", "-v", "error", "-y", "-i", INFILE, "-map", f"0:{idx}", "-c:a", "pcm_f32le", str(out) ]) # 5. Find strong 1-second windows selected = [] for idx in pcm_streams: sr, x = wavfile.read(AUDIO / f"s{idx}.wav") x = x.astype(np.float64) block = int(sr * 0.05) # 50 ms n = len(x) // block * block rms = np.sqrt(np.mean(x[:n].reshape(-1, block) ** 2, axis=1)) if rms.max() > 0.1: imax = int(np.argmax(rms)) sec = imax // int(1 / 0.05) selected.append((sec, idx, float(rms.max()))) selected.sort() print("[selected windows]") for sec, idx, amp in selected: print(f"sec={sec:02d} stream={idx:02d} rms={amp:.3f}") # 6. Overlay selected windows into one WAV duration = 23 sr0 = None y = None for sec, idx, amp in selected: sr, x = wavfile.read(AUDIO / f"s{idx}.wav") if sr0 is None: sr0 = sr y = np.zeros(duration * sr, dtype=np.float64) start = sec * sr end = min((sec + 1) * sr, len(x)) seg = x[start:end].astype(np.float64) seg -= seg.mean() if len(seg) < sr: seg = np.pad(seg, (0, sr - len(seg))) y[start:start + sr] += seg[:sr] y /= np.max(np.abs(y)) + 1e-12 overlay_wav = WORK / "selected_overlay.wav" wavfile.write(overlay_wav, sr0, y.astype(np.float32)) print("[wrote]", overlay_wav) # 7. Generate readable low-frequency spectrogram nfft = 1024 hop = 8 window = np.hanning(nfft) frames = [] for off in range(0, len(y) - nfft + 1, hop): frames.append(y[off:off + nfft] * window) frames = np.array(frames) S = np.abs(np.fft.rfft(frames, axis=1)) fmax = 800 maxbin = int(fmax / (sr0 / nfft)) + 1 S = S[:, :maxbin] L = np.log1p(S * 200) L = L - np.median(L, axis=0, keepdims=True) lo, hi = np.percentile(L, [60, 99.5]) img = np.clip((L - lo) / (hi - lo + 1e-9), 0, 1) img = (img.T[::-1, :] * 255).astype(np.uint8) out_png = WORK / "spectrogram.png" Image.fromarray(img).resize((5000, 700), Image.Resampling.NEAREST).save(out_png) print("[wrote]", out_png)
Chạy:
bashpython3 solve.py
Hoặc tạo spectrogram bằng sox:
bashsox work/selected_overlay.wav -n spectrogram -x 3600 -y 600 -z 120 -r -o work/selected_overlay_sox.png
Spectrogram đọc được:
textSINE WAVES ARE THE BEST!
Suy ra flag:
texttjctf{sine_waves_are_the_best!}