【攻防世界】nice_bgm
⭕、知识点
MP3音频私有位二进制数据隐写/ascii码/脚本编写
一、题目
给了个mp3文件

二、解题
1、频域分析没有发现信息
2、binwalk只发现了一张正常的图片,只是音频的封面
3、查看文件属性也没有东西
4、搜索音频隐写方式发现有一种音频隐写方式是私有位隐写
5、用010Editor打开运行模板发现有些本应为0的private_bit为1,应该是隐藏了信息

6、编写脚本提取每一帧的private_bit,8个为一组作为ascii码打印得到flag
第一帧地址

总帧数

import textwrap
list_private_bit = []
# 帧序列开头的地址
first_frame_index = int("399D0", 16)
with open("1.mp3", "rb") as f:
# 将文件指针移动到第一帧开头
f.seek(first_frame_index)
# 因为从010Editor上看到总共有5910帧(其实应该编写更严谨的判定代码,但是懒得写)
for i in range(5911):
# 每一帧的帧头部数据占4字节,而私有位和padding位恰好在第三字节的
data = f.read(4)[2]
# 提取私有位并添加到列表中
list_private_bit.append(data & 0b00000001)
# 提取padding位
padding_bit = (data >> 1) & 0b00000001
# 如果padding位为0,则说明该帧大小为417字节,否则为418字节;方便文件指针精准定位到下一帧的开头
if padding_bit == 0:
first_frame_index += 417
else:
first_frame_index += 418
f.seek(first_frame_index)
# 处理提取完成后的私有位,转为文本后输出得到flag
list_private_bit_str = list(map(str, list_private_bit))
bits_wrapped = textwrap.wrap("".join(list_private_bit_str), 8)
flag = ""
for words in bits_wrapped:
flag += chr(int(words, 2))
print(flag)

注意:脚本仅为解题编写,并不严谨。MP3为逐帧解析的文件,本脚本默认把所有帧都是相同的比特率和采样率,但实际上有可能存在帧之间采样率与比特率不一致的情况,严谨的写法应该是先读取帧头部的数据并根据官方技术文档的定义判定比特率和采样率,再计算出实际的帧大小。
三、答案
flag{0k4_YOu_Seem_s0_cl3ver_t0_find_f1ag!}
四、总结与收获
1、学到一种新的音频隐写方式:帧头部私有位隐写
其实通过查阅资料,发现其实帧头部下的CopyRight位也可以进行信息隐藏
参考链接:https://blog.tms.im/2021/03/30/ctf-mp3-copyright-bit.html
2、通过查找ISO官方技术文档了解了MP3文件头的定义
文件定义:官方文档ISO/IEC 11172-3:1993
需要购买,价格昂贵,建议某宝。
下图是音频帧头部的官方1993版本的定义,注意ID仅占1位(我在学习时看到有些博客在误导他人,说ID占2位,而emphasis占1位,也不知道是怎么来的)

中文翻译:

下面是对这些部分元素进行详解(因为后面的帧大小计算和这几个元素有很大关系,所以重点介绍):
-
Layer:
![]()
-
bitrate_index(比特率标志位对应表):
![]()
-
sampling_frequency(采样率标志位对应表):
![]()
-
每帧样本数(这里文档的取名并不太好,frame_size容易被误认为是帧大小,但这里表示的是帧中样本数):
![]()
实践:
取题目中的音频的随意一帧帧头来看:

结合上面的对照表就能得到如下信息:
Layer: Ⅲ
采样率: 44100Hz
比特率: 128kbps
帧样本数:1152
3、学习了音频的比特率与采样率的定义以及如何计算帧大小的公式
先给本体帧大小计算公式: frame_size = (1152 / 采样率) * 比特率 / 8 = (1152 * 比特率)+ padding / (采样率 * 8) + padding [单位:Byte]
解释:
- 已知每一帧有1152样本,那么一帧的时间为:
(1152 / 采样率)[单位:seconds] - 则一帧的数据量为:
(1152 / 采样率) * 比特率[单位bits] - 转化为字节大小需要再除以8,得到最终帧大小:
(1152 / 采样率) * 比特率 /8[单位:Byte]
但是如果自己尝试以下会发现,解得的帧大小约为417.9字节,字节大小只能为整数,怎么会这样呢?
这就需要padding发挥作用了
4、了解到010Editor的脚本编写,通过这种方法也可以批量导出private位,但是由于语法不熟,没有做这方面的脚本。
5、考虑在后续有空时专门为这一类隐写编写一个带有UI的正向、逆向工具。
`
吐槽:知识量这么大的一道题难度只有1...?是我太菜了吗T_T~






浙公网安备 33010602011771号