VNCTF2025
VNCTF 2025
入坑CTF以来打过最难的个人赛,打完的表情就像先辈的一张表情包一样
比赛期间就出了一题,这才是正统misc吗哈哈(
VN_Lang
不能较真的签到题,附件一个exe,一个莫名其妙的源代码。看起来花里胡哨,实际010一查就出来了
echo_flowers
区块链的基本知识和取证搭配的好题。大概?
给了一个安卓的镜像,先VM开了,不得不吐槽的一点是,操作是真吃力啊。。。
先放个hint
我没有那么多知识储备,所以其实能用的hint也不多,就一个软件本身没有缓存
看到干干净净的桌面其实就能感觉到不对劲了哈哈,连文件管理都没有还整什么,果断放弃仿真取证。挂DiskGenius
既然说软件本身没有任何缓存。。那么从哪里入手呢,其实和bashhistory有点相似吧(刚好之前看到过类似的题目)
也就是,从输入法的缓存,或者说,输入的记录入手。
定位交给AI
正好我们是可以找到files这一文件夹的
接下来一个个看过去其实也可以,最终是可以定位到sgim_gd_usr.bin这个文件的,当然按理来说我们应该先排查.db,.dat,.log这类文件的
最终找到助记词ranch only space define laundry carpet muscle ramp high twenty couch fashion
挂上Metamask,记得一定要用没用使用过metamask的浏览器,因为如果你有账户你要导入一般是会让你直接输入密钥登录的
接下来按照指示输入助记词,重置密码,点击你的账户->账户详情->查看密钥就结束了
ezSignal
C3师傅的旷世之作(不仅指半夜更新附件)
当然其实C3师傅给的hint已经相当多了,或者说,都已经把解题步骤告诉你了哈哈(
直接解压会碰到这个问题,那么有人就要问了为什么呢
查下或者根据提示可以发现包里是有一个名字为空格的文件的,而在Windows系统中这种文件是不会被显示的.当然在不知道这点的前提下,可以观察一下压缩包的结构
可以明显看到frFileNameLength是1,并且文件名显示的是’ ‘
那么将压缩包复制到Ubuntu
unzip ezSignal_fix.zip
mv ' ' 2
再复制回来,010查一下2,
问一下AI,知道是一个grc文件,搭配GNU使用,改后缀为grc
喂AI/看hint,这是一个窄带调谐+将复数信号转换为虚部实部分别输出的过程,而我们的任务是逆向一下它,写个Python脚本。(赛后C3说可以Cyber chef,可以Linux。。诶我怎么和我的电脑一个温度了)
喂了半天ChatGPT报错报了一下午+一晚上,活全家了哈哈()
抱着试一试的心情给Deepseek跑了一下,两遍过,最支持国产的一集。。。
import numpy as np
from scipy.io import wavfile
from scipy.signal import butter, lfilter
def read_gr_file(filename, dtype=np.float32, endian="<"):
"""
读取 GRC 生成的二进制文件(如 blocks_file_sink 的输出)
- dtype: 数据类型(默认 float32)
- endian: 字节序(默认小端序 "<",大端序用 ">")
"""
# 以二进制模式读取文件
with open(filename, "rb") as f:
raw_bytes = f.read()
# 转换为指定字节序和类型的 numpy 数组
dt = np.dtype(f"{endian}{dtype().dtype.char}")
data = np.frombuffer(raw_bytes, dtype=dt)
return data
def main():
# 参数配置(与 GRC 一致)
samp_rate = 48000 # 音频采样率
if_rate = 192000 # 中频速率(usrp_rate/3 = 576000/3)
max_dev = 5e3 # FM最大频偏
endian = "<" # 字节序(GRC 默认小端序)
try:
# 1. 读取I/Q数据(二进制模式)
i_data = read_gr_file("flag1.txt", dtype=np.float32, endian=endian)
q_data = read_gr_file("flag2.txt", dtype=np.float32, endian=endian)
# 检查长度一致性
if len(i_data) != len(q_data):
raise ValueError("I/Q数据长度不一致!")
# 2. 合并为复数信号(I + jQ)
complex_signal = i_data + 1j * q_data
# 3. FM解调(相位差分法)
phase = np.unwrap(np.angle(complex_signal))
demodulated = np.diff(phase) / (2 * np.pi * max_dev) * if_rate
demodulated = demodulated.astype(np.float32)
# 4. 重采样到音频采样率(48 kHz)
demodulated_resampled = demodulated[::4] # 简单下采样
# 5. 低通滤波(300-5000 Hz,与GRC一致)
b, a = butter(4, 5000, fs=samp_rate, btype='low')
demodulated_resampled = lfilter(b, a, demodulated_resampled)
# 6. 归一化并保存为WAV
demodulated_resampled /= np.max(np.abs(demodulated_resampled)) * 1.2
wavfile.write("flag_recovered.wav", samp_rate, demodulated_resampled)
print("还原成功!保存为 flag_recovered.wav")
except FileNotFoundError:
print("错误:未找到 flag1.txt 或 flag2.txt!")
except Exception as e:
print(f"错误:{str(e)}")
if __name__ == "__main__":
main()
得到一个92.7MB的wav,那其实一想就是SSTV了,用RX-SSTV跑一下
得到一张阿兹特克码,扫一下得到flag
唉,不能老实做传统misc了,不去折腾研究一些东西感觉永远都只能是入门仔了。也算是吃一堑长一智吧。