CTF 题目解法详解与教学指南
概述
本文档基于 QnQSec CTF 2025 部分题目的解法,系统性地总结各类 CTF 题型的解题思路、技术要点和实现方法。内容涵盖 Misc、Hardware、Crypto 和 Reverse 等多个方向,旨在为 CTF 参赛者提供全面的技术参考。
1. Misc 类题目
1.1 Echoes of the Unknown
题目类型:音频隐写
技术要点:
- 使用传统隐写工具(如 Stegsolve、Audacity 等)进行分析
- 重点检查音频的波形图,可能隐藏可视信息
- 波形图可能直接包含 Flag 文本
解题步骤:
- 用音频编辑软件打开题目提供的音频文件
- 查看波形图显示模式
- 直接识别波形图中的 Flag 文本
答案:QnQSec{h1dd3n_1n_4ud1o}
1.2 Catch Me
题目类型:GIF 分帧与二维码识别
技术要点:
- GIF 文件包含多帧图像,每帧可能包含部分信息
- 需要编写脚本进行分帧处理
- 批量识别二维码内容
解题步骤:
- 使用 Python 的 PIL/Pillow 库对 GIF 进行分帧
from PIL import Image
def extract_frames(gif_path):
with Image.open(gif_path) as img:
frames = []
for frame in range(img.n_frames):
img.seek(frame)
frames.append(img.copy())
return frames
- 对每帧图像进行二维码识别
import pyzbar.pyzbar as pyzbar
def decode_qr_from_frames(frames):
results = []
for frame in frames:
decoded = pyzbar.decode(frame)
if decoded:
results.append(decoded[0].data.decode())
return results
- 组合所有二维码内容得到完整 Flag
答案:QnQSec{C4TCH_M3_1F_Y0U_C4N}
1.3 John Cena
题目类型:GIF 隐写
技术要点:
- 关注 GIF 的最后一帧(可能隐藏信息)
- 使用 Stegsolve 等工具分析图像隐写
解题步骤:
- 对 GIF 进行分帧,检查每一帧
- 发现最后一帧为全白图像(可疑)
- 使用 Stegsolve 分析该帧,发现隐藏的 Flag
答案:QnQSec{HOW_CAN_YOU_SEE_ME?}
1.4 HeartBroken
题目类型:PDF 签名提取
技术要点:
- 使用 PyMuPDF 库处理 PDF 文件
- 提取 PDF 中嵌入的图像/签名信息
解题步骤:
- 使用 PyMuPDF 打开 PDF 文件
import fitz # PyMuPDF
def extract_images_from_pdf(pdf_path):
doc = fitz.open(pdf_path)
images = []
for page_num in range(len(doc)):
page = doc.load_page(page_num)
image_list = page.get_images(full=True)
for image_index, img in enumerate(image_list):
xref = img[0]
pix = fitz.Pixmap(doc, xref)
if pix.n - pix.alpha > 3: # 检查是否为 RGB 图像
pix.save(f"image_{page_num}_{image_index}.png")
pix = None
doc.close()
- 分析提取出的图像,找到 Flag
答案:QnQSec{I_4ctually_st1ll_l0v3_y0u_Rima!!}
2. Hardware 类题目
2.1 SmartCoffee
题目类型:固件/EEPROM 分析
技术要点:
- 对嵌入式设备固件进行静态分析
- 识别和提取 EEPROM 数据区
- 对混淆数据进行暴力破解
解题步骤:
-
分析固件反汇编,找到 33 字节的 EEPROM 数据区(
byte_20E0[33]) -
数据被标记为"likely obfuscated",需要尝试常见变换:
- 单字节 XOR 操作
- 加/减常量操作
- 位循环移位(ROL/ROR)
- 按位取反(NOT)
- 两字节重复密钥模式
-
编写爆破脚本系统尝试所有可能变换
def brute_force_eeprom(data):
results = []
for key in range(256): # 单字节 XOR 密钥
decrypted = bytes([b ^ key for b in data])
if b'QnQSec{' in decrypted:
results.append(decrypted)
# 尝试其他变换...
return results
- 在 XOR 密钥 0x3B 时找到可读 Flag
答案:QnQSec{3x_s3snum_c4n_d0_h4rdw4r3}
3. Crypto 类题目
3.1 myLFSR?
题目类型:GF(3) 上的 LFSR 密码分析
技术要点:
- LFSR(线性反馈移位寄存器)在 GF(3) 上工作
- 利用已知明文攻击恢复初始状态和掩码
- 基数为 3 的异或操作
数学原理:
- LFSR 状态向量长度:40
- 输出:state[0](最低位)
- 状态更新:右移并在末尾追加
b = sum(state[i]*mask[i]) mod 3 - 加密:明文转换为基数为 3 的展开,与密钥流逐位异或
解题步骤:
- 将已知明文
b'\xff'*k转换为基数为 3 的展开 - 通过密文与明文的异或得到密钥流片段
- 利用 LFSR 的线性特性建立方程组
- 求解初始状态和反馈多项式系数
- 生成完整密钥流,解密 Flag
核心代码思路:
def solve_lfsr_gf3(gift_ct, known_plaintext, key_length=40):
# 将明文转换为三进制展开
pt_gift = expand_to_base3(known_plaintext, key_length)
# 获取密钥流
stream = [pt_gift[i] ^ gift_ct[i] for i in range(len(pt_gift))]
# 建立线性方程组求解 mask
# ... 线性代数求解过程
return initial_state, mask
答案:QnQSec{i_L1K3_B3RleK4mP_m4Ss3y_0n_m0d_3_f1elD}
3.2 Mandatory RSA
题目类型:RSA Wiener 攻击
技术要点:
- 识别大公钥指数 e 的情况
- 使用 Wiener 攻击恢复私钥
- 常规 RSA 解密
解题步骤:
- 观察 RSA 参数,发现 e 很大,符合 Wiener 攻击条件
- 使用连分数展开攻击:
def wiener_attack(e, n):
# 连分数展开 e/n
# 检查每个收敛分数是否满足 RSA 条件
# 返回可能的 d 值
pass
- 找到正确的私钥 d 后正常解密
答案:QnQSec{I_l0v3_Wi3n3r5_@nD_i_l0v3_Nut5!!!!}
3.3 myPrime
题目类型:结构化素数与椭圆曲线密码分析
技术要点:
- 识别特殊结构的素数:
p = pp² + pp + 3 - 利用模运算信息恢复素数
- AES-CTR 模式解密
数学推导:
- 设
s = pp,则p = s² + s + 3 - 已知
E_.order() ≡ gift (mod mod) - 根据 Hasse 定理:
|E(F_p)| ≈ p - 建立模方程:
s² + s + 3 ≡ gift (mod mod)
解题步骤:
- 解模方程得到 s 在模 mod 下的残类
- 枚举合适的 t 值,使 s 落在正确范围内(约 256 位)
- 对每个候选 s 计算 p 并检查素数性
- 使用 p 的前 16 字节作为 AES 密钥解密
核心代码:
def recover_prime(gift, mod):
# 解二次方程 s² + s + (3 - gift) ≡ 0 (mod mod)
R = (3 - gift) % mod
discriminant = (1 + 4 * R) % mod
# 求平方根(如果存在)
sqrt_d = tonelli_shanks(discriminant, mod)
s_candidates = []
if sqrt_d is not None:
s1 = ((-1 + sqrt_d) * pow(2, -1, mod)) % mod
s2 = ((-1 - sqrt_d) * pow(2, -1, mod)) % mod
s_candidates = [s1, s2]
# 枚举平移倍数
for s0 in s_candidates:
for t in range(-10, 10): # 小范围枚举
s = s0 + t * mod
if 2**255 < s < 2**257: # 检查位数
p = s*(s+1) + 3
if is_prime(p) and (p % mod == gift % mod):
return p
return None
答案:QnQSec{H1lb3rt_cl4sS_p0lynOmiAl_w1tH_DiScR1m1nANt_5pEc1al_d3s1gn3D}
4. Reverse 类题目
4.1 baby_baby_reverse
题目类型:简单逆向工程
技术要点:
- 使用 IDA Pro 进行静态分析
- 识别加密算法(异或加密)
- 提取密钥和加密逻辑
解题步骤:
- 用 IDA 打开可执行文件,分析主函数
- 发现输入长度要求为 41 字节(0x29)
- 识别加密逻辑:
encrypted[i] = plaintext[i] ^ key[i % 15] - 密钥字符串:
"Th1s_1s_th3_k3y" - 反向计算得到 Flag
解密代码:
def decrypt_baby_reverse(encrypted_data):
key = b"Th1s_1s_th3_k3y"
key_len = len(key)
result = bytearray()
for i in range(len(encrypted_data)):
result.append(encrypted_data[i] ^ key[i % key_len])
return bytes(result)
答案:QnQSec{This_1s_4n_3asy_r3v3rs3_ch4ll3ng3}
4.2 Baby_Reverse_Revenge_From_NHNC
题目类型:Shellcode 分析与 AES 解密
技术要点:
- 分析嵌入式 shellcode 的动态密钥生成
- AES-256-CBC 模式解密
- 逆向工程中的动态分析技巧
解题步骤:
- 分析程序中的 shellcode 数据段
- 识别 shellcode 实际是在构造固定密钥
- 提取 shellcode 中设置的字节值:
th1_1s_th3_valu3_0f_k3y+ 9 个空字节 - 使用该密钥进行 AES-256-CBC 解密
解密代码:
from Crypto.Cipher import AES
def decrypt_aes_ctr(ciphertext, key, nonce):
cipher = AES.new(key, AES.MODE_CTR, nonce=nonce)
return cipher.decrypt(ciphertext)
# 密钥提取
key = b"th1_1s_th3_valu3_0f_k3y" + b"\x00" * 9 # 补足 32 字节
答案:QnQSec{a_s1mpl3_fil3_3ncrypt3d_r3v3rs3}
5. 通用解题技巧总结
5.1 文件格式分析
- 音频文件:检查波形图、频谱图、元数据
- 图像文件:分帧分析、隐写工具、通道分离
- PDF 文件:PyMuPDF 提取嵌入对象
- 固件文件:二进制分析、反汇编、数据区识别
5.2 密码分析模式识别
- 流密码:寻找线性关系、已知明文攻击
- 分组密码:识别加密模式、密钥恢复
- 公钥密码:参数特殊性分析(如 Wiener 攻击条件)
- 特殊结构:代数结构利用、模运算分析
5.3 逆向工程方法论
- 静态分析:IDA Pro 反汇编、字符串搜索、交叉引用
- 动态分析:调试器跟踪、内存转储、API 监控
- 代码理解:识别加密函数、输入输出分析
5.4 脚本编写技巧
- 批量处理:文件遍历、多线程/进程
- 数据转换:进制转换、编码解码
- 暴力破解:合理缩小搜索空间、优化算法
结语
本教学文档系统总结了 QnQSec CTF 2025 中各类题目的解题思路和技术要点。掌握这些技能需要理论与实践相结合,建议读者在实际 CTF 比赛中多加练习,不断提升分析问题和解决问题的能力。CTF 竞赛的核心在于创造性思维和系统性方法,希望本文档能为您的学习之路提供有价值的参考。