CTF
この writeup は TSG 内部で行われた TSG CTF 2021 レビュー中に書かれたものです。
フラグから 440 Hz のトーンを用いたモールス信号を生成し、それを前後にシャッフルして出力した音声が与えられる。
まずは出力される音声に使われている正弦波を確認する。
wave[i] = np.sin(i * 440 / 44100 * (2 * np.pi))
wave
には
よって出力結果に現れる数値も高々 results.wav
の各値がどの wave[i]
に一致するかに変換しても情報量は変わらない。また results.wav
はシャッフルされているため、wave[i]
が何回現れるかのマッピングに変換しても情報量は変わらない。
(実装上の注意として、音声ファイルは 24 ビット浮動小数点数で出力されているので、値同士の比較方法に注意する。)
さて、配列というデータを離散フーリエ変換の文脈でわかりやすく操作するために、以下を導入する。
これを wave[i]
と一致している。
生成されるモールス信号におけるひとつの単位は、ある時点から連続した
生成される音声は以下のように表される。
前述の「result.wav
の各値に wave[i]
が何回現れるかのマッピング」を
ここで
ただし TSGCTF{
であることがわかっているので
TSGCTF{
の分を取り除いたものを
となり、これは畳み込みの形をしている。
よって逆畳み込みによって
が得られる。あとはここから
from collections import Counter
import soundfile as sf
import numpy as np
from utils import signals_to_string, string_to_signals
# Generate a cycle
cycle = [0] * 2205
for i in range(len(cycle)):
cycle[i] = np.sin(i * 440 / 44100 * (2 * np.pi))
# Round values to 24-bit
sf.write("cycle.wav", cycle, 44100, "PCM_24")
cycle, _ = sf.read("cycle.wav")
cycle = list(cycle)
# Get result.wav
result, _ = sf.read("result.wav")
# Count
counts = [0] * 2205
for value, count in Counter(result).items():
index = cycle.index(value)
counts[index] = count
# Remove spaces
counts[0] = counts[1]
# Prepare leading signals
leading_signals = string_to_signals("TSGCTF{")
# Remove leading signals
for i in range(len(leading_signals)):
for j in range(2000):
counts[(2000 * i + j) % len(counts)] -= leading_signals[i]
# Deconvolution!
def deconvolve(h, g):
n = len(h)
H = np.fft.rfft(h, n)
G = np.fft.rfft(g, n)
F = H / G
f = np.fft.irfft(F, n)
return np.rint(f).astype(np.int64)
reordered_signals = deconvolve(counts, [1] * 2000)
# Restore signals
signals = [0] * 473
signals[:len(leading_signals)] = leading_signals
for i in range(len(leading_signals), len(signals)):
signals[i] = reordered_signals[2000 * i % len(reordered_signals)]
print(signals_to_string(signals))