flag format: tjctf{...} file challenge đã được đín...

बनाया गया: 17 मई 2026

का उपयोग करके उत्तर दिया गया GPT-5.5 Thinking द्वारा Chat01

प्रश्न

nesting.mp4
image.png

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

विचार

Finalizing the flag and write-up in Vietnamese

It looks like the spectrogram text is "SINE WAVES ARE THE BEST!" exactly. The final response should be in Vietnamese, including the flag. "tjctf{sine_waves_are_the_best!}" seems like the correct flag, with the exclamation mark included, as it appears in the phrase. I need to write a detailed yet concise write-up with the commands and script used.

Thought for 19m 2s

उत्तर

Flag tìm được:

text
tjctf{sine_waves_are_the_best!}

Hidden phrase trong spectrogram là:

text
SINE WAVES ARE THE BEST!

Write-up

File tên .mp4, nhưng thực tế là Matroska container:

bash
file nesting.mp4 ffprobe -hide_banner -show_streams -show_chapters nesting.mp4

Kết quả đáng chú ý:

text
nesting.mp4: Matroska data ... Stream #0:52: Subtitle: ass ... 50 audio streams pcm_f32le: stream 2 -> 51 12 chapters

1. Extract subtitle và đọc chapter timestamps

bash
mkdir -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ư:

text
0.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:

text
IA MN O T TH EF L AG .N OTY E T!

Ghép lại:

text
IAMNOTTHEFLAG.NOTYET!

Tức là subtitle/chapter chỉ là layer đầu, chưa phải flag.

2. Phân tích các audio stream PCM

File có 50 audio stream dạng pcm_f32le, từ stream 2 đến 51. Extract chúng:

bash
mkdir -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:

text
sec 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.

3. Script reproduce

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:

bash
python3 solve.py

Hoặc tạo spectrogram bằng sox:

bash
sox work/selected_overlay.wav -n spectrogram -x 3600 -y 600 -z 120 -r -o work/selected_overlay_sox.png

Spectrogram đọc được:

text
SINE WAVES ARE THE BEST!

Suy ra flag:

text
tjctf{sine_waves_are_the_best!}

इस प्रश्नोत्तर को साझा करें