HackThisSite - Steganography Mission 9
Challenge
关卡页在
https://www.hackthissite.org/missions/playit/stego/9/,题面只有两句:
Download the song Here kiss me alone
附件是
https://www.hackthissite.org/missions/stego/lvl/Stego9.zip。作者
tiksi, 答案大小写敏感(页面提示
All missions are case sensitive)。
状态:live 已通关(verified)。提交后 profile 的
Stego: 一行出现 (9), 关卡页回执变为
already done this mission。
Sample
1 | curl -sL -b "$HTS_COOKIE" -o Stego9.zip \ |
1 | Type = zip |
1 | Channels : 2 |
ZIP 里只有一个 40 秒、11025 Hz、16 bit 立体声的 WAV。单看左右声道各自都是一首连续的 电子舞曲,波形和频谱都很正常,秘密只在混音层面才会显形。
Solution
Step 1: WAV 块结构
先按 WAV 的块结构逐块核对尾部附加数据,以及头部声明长度与实际文件长度的一致性:
1 | import struct |
1 | riff b'RIFF' file_size 1784818 |
data
块正好覆盖到文件结尾(58 + 1784760 = 1784818),没有任何尾部附加数据;
头部声明的长度也和真实文件一致。信息只能在 PCM 采样里。
Step 2: 两声道反相
分别看左右声道的能量和相关性:
1 | import wave |
1 | corr(L,R) = -0.999697 |
这一步就是全部关键。两个声道的相关系数是 -0.9997:右声道是左声道的反相副本。 所以:
- 现场听感正常,是因为播放器把两声道混合后音乐照旧;
L - R的幅度几乎是被乘了 2 倍的歌曲本身(std 37281 ≈ 2×18641);L + R却塌缩成 std 只有 459 的极小残差。
音乐在求和时被抵消,留下的 L + R
才是承载信息的信号。把它单独导出成单声道:
1 | import wave |
1 | residual std=458.9 absmax=1044 |
这个残差和歌曲本身几乎不相关(corr(L+R, L) = 0.0115),是一个独立的信号。
Step 3: 残差频谱与 1 kHz 载波
对残差做精细 FFT,看能量集中在哪:
1 | import wave |
1 | 998.6 Hz mag 963327.3 |
残差里几乎只有一个 约 1000 Hz 的纯载波(相邻几条谱线都挤在 1 kHz 附近, 是同一根载波在 FFT 分辨率上的展宽)。再用时间轴看它,按 100 ms 一格量载波的能量, 会发现它一段一段地通断:
1 | import wave |
1 | 517 249 1034 781 4 1019 1027 12 747 1017 271 495 1035 482 0 1 997 1006 23 708 994 283 394 856 513 222 1033 676 1 947 |
每 100 ms 的能量在接近 1000 和接近 0 两档之间来回跳,这正是通断键控(OOK), 把通断的时间长度读出来就是莫尔斯电码。
Step 4: 包络通断 → 莫尔斯
对上一步的包络取阈值、做游程编码,按运行长度区分点 / 划和字符内间隔 / 字符间间隔 / 词间间隔,然后查表:
1 | import wave |
1 | on ms: [51, 201, 202, 202, 201, 202, 202, 202, 201, 202, 201, 201, 52, 50, 52, 52, 51, 52, 51, 51, 202, 52, 51, 52] |
时长分成非常干净的四档:
- 通 ≈ 51 ms(点)与 ≈ 201 ms(划),约 1:4;
- 断 ≈ 124 ms(字符内部间隔)、≈ 249 ms(字符之间)、≈ 499 ms(词之间),约 1:2:4。
按这个比例把游程翻译成 ./-,用 124/249/499
三档间隔切分字符和词:
1 | import wave |
1 | morse digits: 107 56 120 48 119 53 98 98 117 113 |
Step 5: 莫尔斯即十进制 ASCII 码
上一步每个词由 2 到 3
个莫尔斯字符组成,分别都是数字字符,例如第一个词是
.---- ----- --...(即 107)、第二个是
..... -....(即 56)。换句话说,
莫尔斯电码表出的是一串十进制数字,每 2 到 3
位一组;这些数字按十进制读成 ASCII 码, 就是真正的明文:
1 | 107 56 120 48 119 53 98 98 117 113 |
拼起来就是题面 kiss me alone 里埋的那个通关密码,
直接提交即可。
Key points
- 这道题的核心是把反相的两个声道混回单声道:音乐在
L + R里相互抵消,剩下的低电平残差(std 459,约为歌曲本身的 2.5%)才是秘密。 判断手法只需一个数字:corr(L, R) = -0.9997。 - 载体是通断键控的 1 kHz 载波:频域是一根尖峰(998.6 Hz),时域是一串方波包络。 用希尔伯特变换取包络、按 25% 阈值切成 0/1,运行长度自然分成 51/201 ms(点/划)与 124/249/499 ms(三档间隔),四档比例干净,噪声几乎不干扰判读。
- 明文是莫尔斯数字 → 十进制 ASCII 码 → 字符的两级 编码。看到词里每 2 到 3 个连续数字字符、并且总长度围绕 ASCII 可打印区间 (10x / 5x / x8 / 11x)时,就该想到十进制 ASCII。
- 排除顺序:结构(RIFF 分块 / 尾部附加)→ 声道相关 → 频谱 →
包络时长。 第 1 步排除了尾部附加数据,第 2 步定位到
L+R,第 3、4 步才把载波转成文本。 - 提交:
/missions/stego/template.php,字段为formkey(每次加载都变)、lvl、pass, 并且必须带Referer: .../missions/playit/stego/9/。答错时服务端不回任何错误文案, 唯一可靠的判据是提交后 profile 的Stego:一行出现(9)。