本人并非专业人士,在部分专业名词的表述上可能会出现错误,敬请谅解
前段时间在 BugKu 做题时碰到了这么一题 铁子,来一道 - Bugku CTF
(WriteUp 可见同学的博客 【BugKu】铁子,来一道_s1ameseL的博客-CSDN博客)
在解题过程中有一步,需要从音频中提取出摩斯密码。看了同学写的 WriteUp 后,我突发奇想,决定写一个自动从音频中提取摩斯密码的程序,于是就有了这么个工具
Github地址: https://github.com/Ling-Ink/MorseAudioDecoder
编写过程
1.载入音频
在这里使用的是 python 的 wave
库
# 加载音频
audio = wave.open(sys.argv[1], 'rb')
# 读音频信息
params = audio.getparams()
print(params)
n_channels, _, sample_rate, n_frames = params[:4]
# 读频谱信息
str_wave_data = audio.readframes(n_frames)
audio.close()
# 将频谱信息转为数组
wave_data = np.frombuffer(str_wave_data, dtype=np.short).T
2.提取数据
计算出横轴的时间轴后,使用 pylab
库绘制频谱图像
time = np.arange(0, n_frames) * (1.0 / sample_rate)
pylab.plot(time, wave_data)
pylab.show()
结果如下
对于如何区分长(“-”)与短(“.”),我想到的方法是计算出所有信号的平均长度,大于平均长度的即为长(“-”)
# 计算平均频率
wave_avg = int(sum([abs(x / 10) for x in wave_data]) / len(wave_data))
在此处有个小插曲,由于音频的波形呈现正弦型,所以在带有信息的区域也会出现频率值为0的情况,最终生成的数据也无法转换为摩斯密码
后来在 Python 波形处理_Rone-X的博客-CSDN博客 这篇博客中发现可以取一段区域内的平均值
比较代码如下,使用了 tqdm
库显示绘制进度
# 绘制摩斯图像
morse_block_sum = 0 # 待划分的数据
morse_block_length = 0 # 待划分的数据长度
morse_arr = []
time_arr = []
pbar = tqdm(wave_data, desc="Drawing Morse Image")
for i in pbar:
# 高于平均值记为 1 ,反之为 0
if abs(i) > wave_avg:
morse_block_sum += 1
else:
morse_block_sum += 0
morse_block_length += 1
# 将数据按照指定长度划分
if morse_block_length == 100:
# 计算划分块的平均值
if math.sqrt(morse_block_sum / 100) > 0.5:
morse_arr.append(1)
else:
morse_arr.append(0)
# 横坐标
time_arr.append(len(time_arr))
morse_block_length = 0
morse_block_sum = 0
最后生成的图像如下
接着取出 0 位和 1 位的长度信息
# 摩斯电码 按信号长度存储
morse_type = []
morse_len = []
# 摩斯电码长度 0 1
morse_obj_sum = [0, 0]
morse_obj_len = [0, 0]
for i in morse_arr:
if len(morse_type) == 0 or morse_type[len(morse_type) - 1] != i:
morse_obj_len[i] += 1
morse_obj_sum[i] += 1
morse_type.append(i)
morse_len.append(1)
else:
morse_obj_sum[i] += 1
morse_len[len(morse_type) - 1] += 1
# 计算信息与空位的平均长度
morse_block_avg = morse_obj_sum[1] / morse_obj_len[1]
morse_blank_avg = morse_obj_sum[0] / morse_obj_len[0]
与平均长度比较
# 转换为摩斯电码
morse_result = ""
for i in range(len(morse_type)):
if morse_type[i] == 1:
# 大于平均长度为"-"
if morse_len[i] > morse_block_avg:
morse_result += "-"
# 小于平均长度即为"."
elif morse_len[i] < morse_block_avg:
morse_result += "."
# 大于平均空位长度的为分割
elif morse_type[i] == 0:
if morse_len[i] > morse_blank_avg:
morse_result += "/"
3.解码数据
使用如下字典解码
morse_dict = {
'.-': 'A', '-...': 'B', '-.-.': 'C', '-..': 'D', '.': 'E', '..-.': 'F',
'--.': 'G', '....': 'H', '..': 'I', '.---': 'J', '-.-': 'K', '.-..': 'L',
'--': 'M', '-.': 'N', '---': 'O', '.--.': 'P', '--.-': 'Q', '.-.': 'R',
'...': 'S', '-': 'T', '..-': 'U', '...-': 'V', '.--': 'W', '-..-': 'X',
'-.--': 'Y', '--..': 'Z',
'.----': '1', '..---': '2', '...--': '3', '....-': '4', '.....': '5',
'-....': '6', '--...': '7', '---..': '8', '----.': '9', '-----': '0',
'.-.-.-': '.', '---...': ':', '--..--': ',', '-.-.-.': ';', '..--..': '?',
'-...-': '=', '.----.': '\'', '-..-.': '/', '-.-.--': '!', '-....-': '-',
'..--.-': '_', '.-..-.': '"', '-.--.': '(', '-.--.-': ')', '...-..-': '$',
'.--.-.': '@'
}
# 摩斯电码解码
morse_array = morse_result.split("/")
plain_text = ""
for morse in morse_array:
plain_text += morse_dict[morse]
plain_text
变量中即为解码后的数据
验证
参考:
利用python自动解析摩斯电码音频文件_如何从音频中提取摩斯密码_Rabbit_Gray的博客-CSDN博客
使用Python绘制语音信号的波形图_python画信号图_进击的小杨人的博客-CSDN博客
Python 波形处理_Rone-X的博客-CSDN博客