#
题目列表
- 花斯卡、火斯卡
- 路由器 - 0、路由器 - 1
- Chiikawa
- void null() {}
#
花斯卡、火斯卡
本题所需要的工具 「ffmpeg」「flac」
这两个工具都可以很方便的下到
如果对音视频分析,
你通常还会用到 Audacity、REAPER、SPEK 、VLC
这些工具可以辅助你的解题和日常处理音视频的需求
但解出本题其实只需要ffmpeg即可
#
FFMPEG
官网: ffmpeg.org
FFmpeg 是一款功能强大的开源多媒体处理工具。
在视频处理方面,它可以进行格式转换,将一种视频格式转换为另一种,满足不同设备和平台的需求。能对视频进行裁剪、拼接、添加字幕等操作,方便视频制作和编辑。在音频处理上,可进行音频格式转换、混音、提取音频等。它还支持流媒体处理,用于直播推流、拉流以及视频点播服务。FFmpeg 具有高效、稳定的特点,广泛应用于影视制作、在线教育、广播电视等众多领域,为多媒体内容的处理和传播提供了有力的支持。
你可能听过OBS Studio,这一软件的音视频处理部分就大量的使用了FFMPEG。
而在商业软件中,FFMPEG也是绝对的压倒性地位
诸如某某视频客户端,某某短视频,可以说几乎所有和音视频相关的软件中都有FFMPEG
我敢保证你现在的手机或电脑里就有很多个FFMPEG构建成的软件。
在Deb系列 Linux中安装 ffmpeg
1
2
| apt update
apt install ffmpeg -y
|
macOS使用homebrew安装ffmpeg
Windows安装ffmpeg
1
| ffmpeg.org 下载热心网友预编译的版本
|
#
分析
拿到本题,如果你有仔细听,在视频的结尾,会有一声爆音
你可以在Audacity、REAPER中明确的看到这一点
通过下载网络上公开的视频,可以发现,结尾是非常干净的
此时我们的切入点就是结尾这个爆音所隐藏的数据
我们可以使用 ffprobe 命令查看该视频文件的元信息
看看该视频所用的编码是哪些
1
| ffprobe ~/Downloads/sparkle.mp4
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf61.7.100
Duration: 00:02:01.40, start: 0.000000, bitrate: 26425 kb/s
Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 1920x1080 [SAR 1:1 DAR 16:9], 25489 kb/s, 30 fps, 30 tbr, 15360 tbn (default)
Metadata:
handler_name : Video Media Handler
vendor_id : [0][0][0][0]
encoder : AVC Coding
Stream #0:1[0x2](und): Audio: flac (fLaC / 0x43614C66), 48000 Hz, stereo, s32 (24 bit), 931 kb/s (default)
Metadata:
handler_name : SoundHandler
vendor_id : [0][0][0][0]
|
我们可以看到,该视频的容器是mp4,视频编码采用了h264, 音频编码采用了flac
如果经常接触视频创作的朋友可能就会很熟悉了
mp4容器配合分发常用的音频编码应该是 aac
就算稍微少见一点也应该是 opus,mp3
而这里却给了一个flac,很不常见,我们的入口点应该在flac编码身上
而如果你是玩Hi-Fi的朋友 (这就是音乐.raw)
或者平时关注听歌的品质
再或者你去问CHATGPT
你会得到一个信息「FLAC是一种无损压缩音频编码」
和zlib, gzip, 这些格式一样,无损压缩意味着你可以想解压一样还原数据原本的样子
而没有任何笋丝 (损失)
那么我们的思路就是
- 把音频没有任何笋丝的提取出来
- 把FLAC音频解码
- 看看解码后的音频结构
#
提取音频
在ffmpeg中,只要参数正确,我们可以无损的把视频数据或音频数据提取出来
1
| ffmpeg -i sparkle.mp4 -vn raw.flac
|
这条命令的作用是
-i sparkle.mp4 (指定输入的文件)
-vn (video no, 不处理视频,我们只需要音频)
raw.flac (存储的文件名)
当你看到以下输出时,说明音轨被无损的提取了出来
1
2
3
4
| Stream mapping:
Stream #0:1 -> #0:0 (flac (native) -> flac (native))
Press [q] to stop, [?] for help
Output #0, flac, to 'raw.flac':
|
native to native
#
解码FLAC
使用 flac -d 命令,将FLAC解编码
1
2
3
4
5
6
7
8
9
| flac -d raw.flac
flac 1.4.3
Copyright (C) 2000-2009 Josh Coalson, 2011-2023 Xiph.Org Foundation
flac comes with ABSOLUTELY NO WARRANTY. This is free software, and you are
welcome to redistribute it under certain conditions. Type `flac' for details.
raw.flac: done
|
在当前目录下,会生成一个 raw.wav
1
2
3
4
5
| ffprobe raw.wav
Input #0, wav, from 'raw.wav':
Duration: 00:02:01.40, bitrate: 2304 kb/s
Stream #0:0: Audio: pcm_s24le ([1][0][0][0] / 0x0001), 48000 Hz, stereo, s32 (24 bit), 2304 kb/s
|
我们可以发现 FLAC 已经解编码为 pcm_s24le 格式了
如果学过信号与系统的朋友应该很清楚这是什么了
PCM 音频是未经压缩的原始音频数据,能够准确地还原声音的本来面貌,具有高保真度。它通过对模拟音频信号进行采样、量化和编码来实现数字化。采样频率决定了每秒采集的样本数量,较高的采样频率能捕捉更丰富的音频细节;量化位数则表示每个采样点的精度,位数越高,声音的动态范围和精度就越高。
这里已经比较Hi-Fi了 (相比DSD或者192khz来说)
还记得 之前提到的结尾有一声尖刺吗
我们这时候再对 raw.wav 分析
1
| tail -b 100 raw.wav|xxd
|
查看 raw.wav 结尾的100字节数据,我们直接就看到FLAG了
或者你也可以用 strings 命令直接提取 wav 中的可见字符,一把拿下
这时候就有朋友要说了,不是说好了本题只需要ffmpeg吗,怎么还用了flac命令行
现在讲的是轮椅做法,位深、采样率什么的 flac程序都帮你处理好了
接下来讲如何只用ffmpeg提取该音频
使用ffprobe分别查看视频流和音频流的长度
1
2
3
4
5
6
7
8
| x@mac ~ % ffprobe -v error -select_streams a:0 -show_entries stream=duration ~/Downloads/sparkle.mp4
[STREAM]
duration=121.400083
[/STREAM]
x@mac ~ % ffprobe -v error -select_streams v:0 -show_entries stream=duration ~/Downloads/sparkle.mp4
[STREAM]
duration=121.400000
[/STREAM]
|
可以看到音频流 a:0 比视频流 v:0 多出了0.000083s
而正常视频软件生成会裁剪对齐音视频的,明显音频被动手脚了
之前我们看到音频的元数据是 s32 (24bit)
1
2
3
4
| Stream #0:1[0x2](und): Audio: flac (fLaC / 0x43614C66), 48000 Hz, stereo, s32 (24 bit), 931 kb/s (default)
Metadata:
handler_name : SoundHandler
vendor_id : [0][0][0][0]
|
“S32” 表示有符号的 32 位整数数据类型。在音频处理中,用于存储音频样本值。
“24bit” 通常指音频的量化位数为 24 位。量化位数决定了音频信号的精度和动态范围。
“S32(24bit)” 意味着音频数据以有符号 32 位整数的形式存储,并且实际有效的量化精度为 24 位。
我们查看 ffmpeg 有哪些音频解码器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| ffmpeg -decoders|grep pcm
A....D pcm_s16be PCM signed 16-bit big-endian
A....D pcm_s16be_planar PCM signed 16-bit big-endian planar
A....D pcm_s16le PCM signed 16-bit little-endian
A....D pcm_s16le_planar PCM signed 16-bit little-endian planar
A....D pcm_s24be PCM signed 24-bit big-endian
A....D pcm_s24daud PCM D-Cinema audio signed 24-bit
A....D pcm_s24le PCM signed 24-bit little-endian
A....D pcm_s24le_planar PCM signed 24-bit little-endian planar
A....D pcm_s32be PCM signed 32-bit big-endian
A....D pcm_s32le PCM signed 32-bit little-endian
A....D pcm_s32le_planar PCM signed 32-bit little-endian planar
A....D pcm_s64be PCM signed 64-bit big-endian
A....D pcm_s64le PCM signed 64-bit little-endian
|
在这里,由于音频实际有效精度为 24bit
所以我们选 pcm_s24le 解码器
如果你有一些计算机组成原理知识
就会知道字节在计算机系统里分为大端序存储和小端序存储
而在 x86 以及我们日常生活中通常用小端序,所以这里选little-endian
1
2
3
4
5
| ffmpeg -i ~/Downloads/sparkle.mp4 -acodec pcm_s24le -vn r1.wav
Stream mapping:
Stream #0:1 -> #0:0 (flac (native) -> pcm_s24le (native))
Press [q] to stop, [?] for help
|
这时视频中的音频就以 24bit PCM 的解码方式,存储至 r1.wav
我们strings一下就出flag了
1
2
3
4
5
6
7
8
9
| x@xdeMacBook-Air ~ % strings r1.wav
RIFFv
WAVEfmt (
qLIST
INFOISFT
Lavf61.7.100
data
xujc{https://b23.tv/zPZoAcz}
x@xdeMacBook-Air ~ %
|
到这里,花斯卡题目就完整解出了
接下来我们开始火斯卡
拿到了一个B站的链接
https://b23.tv/zPZoAcz
我们去访问一下
发现又是一个视频,但我们会注意到访问后地址栏多了一堆东西,貌似还跳了一下
似乎是什么东西消失了
我们把浏览器的地址复制一下
然后使用curl命令访问一下原始链接,看看什么消失了
1
2
| 浏览器
https://www.bilibili.com/video/BV1cC411W7pH?A-Router=1&bv_session_id=2bdcd921513a531399dbb2e44047ad37902bda7a68268f2d46bd61f73f8d9a2f&plat_id=124&share_from=h5&share_plat=iphone&share_source=COPY&share_source_iphone=&share_tag=s_i×tamp=1729249354&unique_k=zPZoAcz
|
1
2
| CURL
https://www.bilibili.com/video/BV1cC411W7pH?A-Router=1&bv_session_id=2bdcd921513a531399dbb2e44047ad37902bda7a68268f2d46bd61f73f8d9a2f&p=78756a637be88ab1e781ab5f3565346234307d&plat_id=124&share_from=h5&share_plat=iphone&share_source=COPY&share_source_iphone=&share_tag=s_i×tamp=1729249354&unique_k=zPZoAcz
|
我们会发现 有个参数 p 消失了
熟悉叔叔(Bilibili)的朋友就知道了, p 参数指的是合集视频里的索引 (p也就是Part,多Part视频)
而 p 为什么会消失呢?我们来小小逆向一下 叔叔的JavaScript 代码
通过搜索 query 等参数改变信息,我们可以定位到这一段代码
可以看标蓝的第二行
1
| +n > 1 ? a.query.p = n : delete a.query.p
|
这是一句三目运算
判断了 n 是否 大于1,如果大于1,则url中 p 参数为 n
否则调用 delete 删除 url 中 p 参数
而 +n 是 JavaScript里的一种比较奇妙的用法
可以通过 +n 实现其他语言 parseInt 的效果
通过以上测试可以看出,当 p 的值符合数字时,+n > 1 才成立
而消失的 p 是一串字符
这也就是 为什么 p 会消失
拿到了消失的 p 参数
我们就可以提取flag了,常玩MISC的同学应该能看出,这是一段HEX
用你喜欢的工具解编码就可以拿到 火斯卡 的FLAG了
至于如何给叔叔服务器塞FLAG,那就不是本题讨论的范围了,自己研究8
1
2
| x@xdeMacBook-Air ~ % echo 78756a637be88ab1e781ab5f3565346234307d|xxd -r -p
xujc{花火_5e4b40}
|
那么这题全通过音频就能解出来,你让我下个这么大的视频什么意思?
#
路由器 - 0、路由器 - 1
这两题主要考察信息搜集能力和binwalk使用
下载后会拿到一个bin文件
使用binwalk对其分析,可以看到其结构大体为三段
uboot、kernel、rootfs
再使用binwalk解包固件
1
| binwalk -e firmware.bin
|
#
默认TFTP服务器
通过搜索引擎搜索 bootloader tftp、uboot tftp
可以搜索到几个使用tftp uboot启动内核的文章
https://openwrt.org/docs/guide-user/installation/generic.flashing.tftp
其中会有诸如以下命令
1
2
3
4
5
6
| setenv ipaddr 192.168.1.1
setenv serverip 192.168.1.100
tftpboot 0x80000000 openwrt-xxx-generic-xxx-squashfs-factory.bin
erase 0x9f020000 +0x332004
cp.b 0x80000000 0x9f020000 0x332004
boot.m 0x9f020000
|
这里使用strings直接搜索bootloader字符串
1
2
3
4
| x@xdeMacBook-Air /tmp % strings _Rel.25439.bin.extracted/200|grep serverip
serverip=192.168.1.10
serverip
Using serverip from env %s
|
这里的 serverip 便是默认TFTP服务器
同理搜索netmask获取设备子网掩码
1
2
3
4
5
| x@xdeMacBook-Air /tmp % strings ~/binwalk/_TL-7DR7280易展Turbo版\ V1.0_1.0.16_Build\ 230718\ Rel.25439.bin.extracted/200|grep netmask
netmask=255.255.255.0
netmask
\.callbacks:callbacks,\.flags:flags,baudrate:baudrate,bootfile:bootfile,ipaddr:ipaddr,gatewayip:gatewayip,netmask:netmask,serverip:serverip,nvlan:nvlan,vlan:vlan,eth\d?addr:ethaddr,loadaddr:loadaddr,stdin:console,stdout:console,stderr:console,
eth\d?addr:ma,ipaddr:i,gatewayip:i,netmask:i,serverip:i,nvlan:i,vlan:i,dnsip:i,
|
答案便是 192.168.1.10.255.255.255.0
#
内部代号
这题需要一些经验
大公司研发产品会有一个叫CI/CD的东西
比如某某部门对代码进行了改动,将代码推送至仓库后,例如GitHub Action,Jenkins会自动拉取代码,生成对应版本的产物
在这里,该信息遗留在编译好的内核中
我们可以以 be7200 关键词,搜索内核中的字符串
最后可以定位到这几行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| x@xdeMacBook-Air /tmp % strings _Rel.25439.bin.extracted/602E8|grep be7200
/var/lib/jenkins/workspace/soho6_tdmp_qca_be7200_release/slp-sp-target-src/qca/linux-5.4/init/main.c
/var/lib/jenkins/workspace/soho6_tdmp_qca_be7200_release/slp-sp-target-src/qca/linux-5.4/init/initramfs.c
/var/lib/jenkins/workspace/soho6_tdmp_qca_be7200_release/slp-sp-target-src/qca/linux-5.4/arch/arm64/kernel/setup.c
/var/lib/jenkins/workspace/soho6_tdmp_qca_be7200_release/slp-sp-target-src/qca/linux-5.4/arch/arm64/kernel/traps.c
/var/lib/jenkins/workspace/soho6_tdmp_qca_be7200_release/slp-sp-target-src/qca/linux-5.4/arch/arm64/kernel/smp.c
/var/lib/jenkins/workspace/soho6_tdmp_qca_be7200_release/slp-sp-target-src/qca/linux-5.4/arch/arm64/kernel/topology.c
/var/lib/jenkins/workspace/soho6_tdmp_qca_be7200_release/slp-sp-target-src/qca/linux-5.4/kernel/params.c
/var/lib/jenkins/workspace/soho6_tdmp_qca_be7200_release/slp-sp-target-src/qca/linux-5.4/kernel/async.c
/var/lib/jenkins/workspace/soho6_tdmp_qca_be7200_release/slp-sp-target-src/qca/linux-5.4/kernel/irq/manage.c
/var/lib/jenkins/workspace/soho6_tdmp_qca_be7200_release/slp-sp-target-src/qca/linux-5.4/kernel/irq/irqdomain.c
/var/lib/jenkins/workspace/soho6_tdmp_qca_be7200_release/slp-sp-target-src/qca/linux-5.4/kernel/irq/cpuhotplug.c
/var/lib/jenkins/workspace/soho6_tdmp_qca_be7200_release/slp-sp-target-src/qca/linux-5.4/kernel/irq/msi.c
/var/lib/jenkins/workspace/soho6_tdmp_qca_be7200_release/slp-sp-target-src/qca/linux-5.4/kernel/freezer.c
|
这里爆出的就是该Linux内核编译源文件对应的路径
/var/lib/jenkins/workspace/soho6_tdmp_qca_be7200_release/
内部代号即为
#
Chiikawa
解题视频 -> https://www.bilibili.com/video/BV1RN4y127ky
本题可以Misc、Web方式做
也可以Reverse、Crypto方式做
该题考察对浏览器开发工具的熟悉度
打开开发者工具,可以看到向 /flag 请求了数据,但返回的是一团乱码
但首页在该请求后却出现了形如flag的字符串,我们对JS文件进行全局搜索
可以定位到下面的代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| const [e,t] = x.useState(!0)
, [n,r] = x.useState(null)
, [o,i] = x.useState("是什么呢");
return x.useEffect( () => {
e && (fetch("/flag").then(s => s.arrayBuffer()).then(s => {
const a = new Uint8Array(s);
r(a)
}
).catch(s => console.error("Error fetching data:", s)),
t(!1))
}
, []),
x.useEffect( () => {
if (n) {
const s = new Uint8Array([162, 62, 141, 197, 128, 113, 130, 47, 27, 147, 187, 37, 159, 48, 46, 136])
, a = new Uint8Array(n.length);
for (let d = 0; d < n.length; d++)
a[d] = n[d] ^ s[d % s.length];
let l = new TextDecoder().decode(a)
, u = l.indexOf("{")
, c = l.indexOf("}");
if (u !== -1 && c !== -1) {
let d = l.substring(u + 1, c)
, p = l.replace(d, "乌拉呀哈~呀哈乌拉~");
i(p)
}
}
}
|
可以看出使用了React的useState和useEffect
flag的初始值为 “是什么呢”
当页面加载时获取 /flag 内容
对内容进行一系列异或,最后将 {} 括号内内容替换为 “乌拉呀哈~呀哈乌拉~”
(这个时候Reverse/Crypto手已经打开Python/CyberChef了)
Reverse/Crypto做法就是把 /flag 保存下来
用逆向出来的 Key 异或解密
Misc/Web的做法就是在这行打几个断点,刷新页面就可以直接出
1
2
3
| let l = new TextDecoder().decode(a)
, u = l.indexOf("{")
, c = l.indexOf("}");
|
#
void null()
睡眠排序是一种基于时间的排序算法
解压后可以看到一堆乱七八糟的 .null 文件
但时间不同
这里按时间排序,将文件名拼接到一块,就可以获得 FLAG
Windows可以编写脚本
Linux直接一行命令梭了
1
| ls -tr|cut -d "." -f 1|xxd -r -p > 0.raw
|
ls -tr
是按时间升序排列目录下的所有文件
cut -d “.” -f 1
将获取到的文件名通过 . 分割,获取第一个值,就是把文件名的 .null 去除
xxd -r -p > 0.raw
将获取到的HEX数据通过xxd还原成文件
看一下文件类型
1
2
3
| x@xdeMacBook-Air s1eep % file 0.raw
0.raw: RIFF (little-endian) data, Web/P image
x@xdeMacBook-Air s1eep %
|
Web/P image
改名为 0.webp ,用浏览器打开就直接看到flag