1. 知识点:
typedef FrameHeader

{          //数字代表大小,单位bite
unsigned int sync:12;                        //同步信息
unsigned int version:2;                      //版本
unsigned int layer: 2;                           //层
unsigned int error protection:1;           // CRC校验
unsigned int bitrate_index:4;              //位率
unsigned int sampling_frequency:2;         //采样频率
unsigned int padding:1;                    //帧长调节
unsigned int private:1;                       //保留字
unsigned int mode:2;                         //声道模式
unsigned int mode extension:2;        //扩充模式
unsigned int copyright:1;                           // 版权
unsigned int original:1;                      //原版标志
unsigned int emphasis:2;                  //强调模式
}
  • 帧长度是压缩时每一帧的长度,包括帧头的4个字节(32bit)。它将填充的空位也计算在内。
  • padding的值会影响每一帧的长度(具体分析见下面的题目)
  1. 题目文件 提取码:fwyh

    -----常规尝试略------

    将解压后的mp3文件用 010Editor (winhex等) 打开

    分析详见图片

    Snipaste_2022-08-16_21-31-39.png

    通过观察可知,private bit的数值的在0和1中变化,但多看一些 mf[] 发现,大部分是 0,应该不会有什么有效的信息,再看copyright 也是在 0 和 1 发生改变,去查资料,copyright代表着版权,应该是固定不变的值,所以copyright中应该有flag或者重要的信息,先提取出copyright

具体脚本如下:

import re

n = 984486  # 起始位置(是padding的起始位置)
result = ''
files = open('2.mp3', 'rb')   # 以二进制打开文件
# 提取
while n < 12658083:  # 结束位置
    files.seek(n, 0)
    head = files.read(1)   #读取 一个 字节(8位),见上图呀
    # print(head,end='')
    padding = '{:08b}'.format(ord(head))[-2]  # 该字节的倒数第二位是padding的值
    # print(padding)  检验padding 值提取是否有误
    files.seek(n + 1, 0)
    file_read_result = files.read(1)
    result += '{:08b}'.format(ord(file_read_result))[-4]
    # print(len(bin(ord(file_read_result))))
    # result += bin(ord(file_read_result))[-4]
    # n += 1045 if padding == "1" else 1044     # 高级写法
    # 普通写法
    if padding == "0":
        n += 1044
    else:
        n += 1045
# print(result)
# 拼接
flag = ''
textArr = re.findall('.{' + str(8) + '}', result)
for i in textArr:
    flag = flag + chr(int(i, 2)).strip('\n')
print(flag)

脚本分析:

  • 第3行的 n 为起始位置(padding的起始位置)

    • 通过 010Editor 可知,mp3文件的头帧起始位置是235984(用十进制表示)、由下面的两张图片可以的到第一帧的起始位置
    • 由上面的大图可以知道,每一帧的大小是32bit(即4字节),syncmpeq idlayer idprotection id共占了16bit(即2字节),所以padding的起始位置是头帧的起始位置 加2,即235986
  • 第7行的 结束位置 (拉到最下面,方法同上)
  • 第 19 ~ 22 行,增加的值取决于 帧长(大小,用十进制表示,如下图的蓝框),其大小还与padding的值有关

    Snipaste_2022-08-16_22-07-38.png

    图二

  • 第 8 行:seek(offset,x) -> 移动文件读取的指针到指定位置

    • offset:是偏移量,也就是要移动的字节数,如果是我负数,就是从后往前
    • x:可选,默认为 0 ,【0表示从头开始,1表示从当前位置开始,2表示从文件尾开始】
  • 第 9 行:read([size])读取文件内容

    • size:可选,代表从文件读取的字节数,默认为-1,表示读取整个文件
  • 第 11 行:获取padding对应的值

    • ord() -> 返回对应字符的十进制整数
    • format(num) -> 将括号里的整数 num 转换为对应到进制
    • {:08b} -> 域宽为 8 ,不足八位就 补 0,b代表是 二进制
  • 第13行:将文件读取的指针向下移动一个字节(看第一张图可以知道,copyright位于每一帧(4字节)的第四个字节中的第 5 个bite【注意:从零开始计数】(倒数第四个同理)(该MP3中,具体情况具体分析)
  • 第26 ~ 30行,就是正则表达式库(re库)函数的用法了
  1. nice_bgm

    链接 提取码:hack

    题目描述:我拿出自己的私密音乐来和你分享,一起享受快乐吧

    可以看着上面的题目来举一反三咯,思路同上,只是private bit的位置在第三个字节的最后一位(bit)

脚本:

import re
n = 235986
result = ''
number = 0
file = open('1.mp3', 'rb')
l = []
while n < 1369844:
    file.seek(n, 0)
    head = file.read(1)
    # print(head,end='、')
    padding = '{:08b}'.format(ord(head))[-2]
    # l.append(padding)
    # result += bin(ord(head))[-1]
    result += '{:08b}'.format(ord(head))[-1]
    if padding == "0":
        n += 417
    else:
        n += 418
    file.seek(n, 0)
# 拼接
flag = ''
textArr = re.findall('.{' + str(8) + '}', result)
# print(textArr)
# print(chr(int(textArr[0],2)))
for i in textArr:
    flag = flag + chr(int(i, 2)).strip('\n')
print(flag)
# print(l)
"""
验证padding
l1= []
for i in range(len(l)):
    if l[i] == '0':
        l1.append(i)
print(l1)
"""

代码详解:(基本原理遇上一题基本相似,大差不差)

不同点:

  • 第 14 行 没有seek(n+1,0)

    • 原因:private bitpadding在同一字节(在010Editor)中即可发现

才接触ctf不到一个月,这也是我的第一次写wp,多有不足,见谅。(抠了一天呢)

更多内容,见博客super,感谢来访、指正,共同进步

Last modification:February 10, 2023
请我喝瓶冰阔落吧