# TSG CTF 2021 Advanced Fisher Writeup
###### tags: `CTF`
この writeup は TSG 内部で行われた TSG CTF 2021 レビュー中に書かれたものです。
## 問題
フラグから 440 Hz のトーンを用いたモールス信号を生成し、それを前後にシャッフルして出力した音声が与えられる。
## 解法
まずは出力される音声に使われている正弦波を確認する。
```python
wave[i] = np.sin(i * 440 / 44100 * (2 * np.pi))
```
$44100 / \gcd(440, 44100) = 2205$ より `wave` には $2205$ 種類の数値が現れ、周期も $2205$ である。
よって出力結果に現れる数値も高々 $2205$ 種類である。ここで `results.wav` の各値がどの `wave[i]` に一致するかに変換しても情報量は変わらない。また `results.wav` はシャッフルされているため、`wave[i]` が何回現れるかのマッピングに変換しても情報量は変わらない。
(実装上の注意として、音声ファイルは 24 ビット浮動小数点数で出力されているので、値同士の比較方法に注意する。)
さて、配列というデータを離散フーリエ変換の文脈でわかりやすく操作するために、以下を導入する。
$$
W_{2025} = \exp\left({-i\frac{2\pi}{2025}}\right) \quad \text{($i$ は虚数単位)}
$$
これを $n$ 乗した $W_{2025}^{n}$ は $2205$ 種類の値を取り、周期も $2205$ である。この性質は最初に見た `wave[i]` と一致している。
生成されるモールス信号におけるひとつの単位は、ある時点から連続した $2000$ 個の $1$ であるから、次のように表される。
$$
\mathrm{unit}[i] = \begin{cases}
1 & (0 \le i \lt 2000) \\
0 & (2000 \le i \lt 2025)
\end{cases}
$$
$$
\mathrm{unit} = \sum_{i = 0}^{2024} \mathrm{unit}[i] \cdot W_{2025}^{i}
$$
生成される音声は以下のように表される。
$$
\begin{equation}
\begin{split}
\mathrm{result} &= \sum_{i = 0}^{472} \mathrm{signals}[i] \cdot W_{2025}^{2000i} \cdot \mathrm{unit}
\end{split}
\end{equation}
$$
前述の「`result.wav` の各値に `wave[i]` が何回現れるかのマッピング」を $\mathrm{counts}$ とおくと、$\mathrm{result}$ と $\mathrm{counts}$ は全く同一のものである。
$\mathrm{signals}$ はインデックスが $1$ 増える度に単位時間の開始が $2000$ ずれていて扱いづらい。よって、インデックスが $1$ 増える度に単位時間の開始が $1$ ずれるように並べ替えたものを考える。
$$
\mathrm{signals'}[2000i \bmod 2205] = \mathrm{signals}[i]
$$
ここで $\mathrm{signals'}$ の長さは $2205$ とし、上記にあてはまらないとき $\mathrm{signals'}[i] = 0$ とする。
ただし $\mathbb{Z}/2205\mathbb{Z}$ における $2000$ の加法群を考えると位数は $2205 / \gcd(2205, 2000) = 441$ である。$0 \le i \lt 473$ はこれを超えるため、重複が起こってしまうことが考えられるが、今回はフラグの先頭文字列が `TSGCTF{` であることがわかっているので $73 \le i \lt 473$ に絞ることができる。
$\mathrm{counts}$ から `TSGCTF{` の分を取り除いたものを $\mathrm{counts'}$ とすると
$$
\begin{equation}
\begin{split}
\mathrm{counts'} &= \sum_{i = 73}^{472} \mathrm{signals'}[2000i \bmod 2025] \cdot W_{2025}^{2000i} \cdot \mathrm{unit} \\
&= \sum_{i = 0}^{2024} \mathrm{signals'}[i] \cdot W_{2025}^{i} \cdot \mathrm{unit} \\
&= \left( \sum_{i = 0}^{2024} \mathrm{signals'}[i] \cdot W_{2025}^{i} \right) \cdot \left( \sum_{j = 0}^{2024} \mathrm{unit}[j] \cdot W_{2025}^{j} \right) \\
&= \sum_{n = 0}^{2024} \left( \sum_{i + j = n} \mathrm{signals'}[i] \cdot \mathrm{unit}[j] \right) \cdot W_{2025}^{n}
\end{split}
\end{equation}
$$
$\mathrm{counts'}$ の $W_{2025}^{n}$ 成分は
$$
\mathrm{counts'}[n] = \sum_{i = 0}^{n} \mathrm{signals'}[i] \cdot \mathrm{unit}[n - i]
$$
となり、これは畳み込みの形をしている。
よって逆畳み込みによって
$$
\mathrm{signals'} = \mathcal{F}^{-1}\left[\mathcal{F}[\mathrm{counts'}] / \mathcal{F}[\mathrm{unit}]\right]
$$
が得られる。あとはここから $\mathrm{signals}$ を復元すれば良い。
## コード
```python=
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))
```