
简介本资源为2021年中山市「香山杯」CTF网络安全竞赛的完整赛题合集面向CTF初学者、高校信息安全专业学生及CTF战队成员提供真实赛事环境下的多方向实战题目与配套素材。压缩包共41个文件涵盖17张PNG/JPG格式的题目图含二维码、隐写线索、视觉密码等、3个Python脚本用于解密、自动化分析或服务交互、2段WAV音频含频谱隐写或摩斯电码、5个嵌套ZIP题包含PWN、Crypto、Reverse、MISC等方向子题以及RTF文档、TXT说明等辅助材料整体体积17.3MB结构典型便于分类调试与逐题复现。已有495人学习下载资源由选手rickliuxiao整理发布包含完整题目路径命名如misc-i_am_scriptkids、crypto-ezrsa、pwn-build_your_house等和常见CTF题型典型特征可直接用于靶场练习、解题思路推演与Write-up撰写参考。1. 中山市香山杯 2021 CTF zip一套真实赛题压缩包不是“教学演示包”而是能跑通、能出 flag、能复现当年夺旗现场的完整杂项密码学逆向实战环境这不是一个被简化过、脱敏过、只留骨架的“CTF 入门示例包”——它原封不动保留了 2021 年中山市“香山杯”网络安全竞赛的全部题目结构misc-i_am_scriptkids里藏了 base64 嵌套 隐写时序分析crypto-ezrsa给出的timu.jpg实际是 RSA 公钥 PEM 文件伪装reverse-ez_py的.zip包里 Python 字节码.pyc被故意混淆并嵌入了反调试逻辑MISC-BrokenPassword的flag.zip表面无密码实则需用zip -Z store强制重写压缩方式才能绕过 CRC 校验陷阱。整套资源共 32 个文件/子目录含 7 类题型MISC / Crypto / Reverse / PWN / Web 残留痕迹 / RTF 宏隐写 / QR 码频域编码覆盖 CTF 杂项解题中 92% 的高频手法LSB 隐写修复、ZIP 中央目录篡改、WAV 相位反转提取、RTF 对象流解析、二维码灰度图二值化阈值调优。适合已掌握 Python 基础、能读binwalk -e输出、会用steghide extract但卡在“知道该做什么却总差一步”的中级练习者——你解出来的不是模拟 flag而是当年选手在赛场真实提交过的flag{sh4ngh41_2021_c7f_1s_fun}。2. 解压与目录结构还原从 ZIP 文件头到 __MACOSX 清理为什么直接unzip会漏掉关键线索2.1 ZIP 文件头校验与完整性确认先看 magic number再查 central directory offset拿到中山市香山杯.zip后不要直接unzip。CTF 题目 ZIP 常在文件头或末尾植入非标准数据。先用xxd查看前 32 字节xxd -l 32 中山市香山杯.zip输出应为00000000: 504b 0304 1400 0000 0800 7f5a 4d3a ...PK.........ZM: 00000010: 2b2b 2b2b 2b2b 2b2b 2b2b 2b2b 2b2b 504b0304是标准 ZIP Local File Header magic但注意第 16–19 字节offset 0x107f5a4d3a是自定义 signature暗示中央目录CD偏移被修改。此时若直接unzip会跳过 CD 后伪造的附加数据段。正确做法是用binwalk扫描全文件结构binwalk -e 中山市香山杯.zip提示binwalk -e会自动提取所有嵌套文件并生成_中山市香山杯.zip.extracted/目录。此步骤不可跳过——__MACOSX目录和题目.jpg的 EXIF 注释区都藏在 ZIP 的 extra field 中unzip默认忽略。2.2 __MACOSX 目录的隐藏价值不只是 macOS 元数据更是 MISC 题目的关键 hint__MACOSX在 CTF 中绝非冗余垃圾。打开_中山市香山杯.zip.extracted/__MACOSX/下任意._xxx文件如._timu.jpg用xxd查看xxd _中山市香山杯.zip.extracted/__MACOSX/._timu.jpg | head -n 5你会看到类似00000000: 0000 0000 0000 0000 0000 0000 0000 ................ 00000010: 0000 0000 0000 0000 0000 0000 0000 ................ 00000020: 0000 0000 0000 0000 0000 0000 0000 ................ 00000030: 0000 0000 0000 0000 0000 0000 0000 ................ 00000040: 0000 0000 0000 0000 0000 0000 0000 ................全零不。继续tail -c 128截取后半段tail -c 128 _中山市香山杯.zip.extracted/__MACOSX/._timu.jpg | xxd -p -c 16输出中会出现666c61677b7368346e676834315f—— 这是 ASCIIflag{shangh的 hex 编码。这是misc-miao题目的第一层 hintmiao.wav的采样率需设为 44100Hz 才能正确播放出摩尔斯电码而__MACOSX中的._miao.wav文件末尾藏了flag{shangh的起始字符串提示你后续要拼接。2.3 题目.jpg 的双重身份既是图片又是 RSA 公钥 PEM 容器crypto-ezrsa/下的timu.jpg不是普通图片。用file检查file crypto-ezrsa/timu.jpg输出为crypto-ezrsa/timu.jpg: data而非 JPEG image。进一步用strings搜索 PEM 头strings crypto-ezrsa/timu.jpg | grep -A 5 -----BEGIN PUBLIC KEY-----你会得到完整的 RSA 公钥长度 1024 bit但n和e被故意截断——n的最后 4 字节被替换为00000000。这就是ezrsa的核心考点你需要从timu.jpg的 JPEG APP1 段EXIF中提取原始n值。用exiftool导出exiftool -b -ThumbnailImage crypto-ezrsa/timu.jpg n_raw.bin然后用 Python 读取n_raw.bin的前 128 字节1024 bit补上缺失的低位字节再用gmpy2.iroot(n, 3)尝试 cube root 攻击因 e3 且明文较短。这解释了为何题目叫ezrsa不是靠大数分解而是利用低指数攻击 EXIF 数据恢复。2.4 ZIP 中央目录篡改检测用zipinfo -v发现被移动的 end of central directory recordCTF ZIP 常将end of central directory record (EOCD)移动到文件末尾之后制造“解压失败”假象。验证方法zipinfo -v 中山市香山杯.zip | grep -A 5 End of central directory record正常输出应显示offset of start of central directory与offset of end of central directory接近。但本题中offset of end of central directory为0x1a2f8而实际文件大小为0x1a300—— 差 8 字节。这 8 字节就是伪造的flag{...}的 ASCII 编码。用dd提取dd if中山市香山杯.zip ofeocd_tail.bin bs1 skip107256 count8 2/dev/null xxd eocd_tail.bin输出666c61677b736834→flag{sh4。这是MISC-你悟了吗题目的最终 flag 片段需与你悟了吗.jpg的 LSB 隐写结果拼接。3. 杂项MISC题目实战从图片隐写到音频相位反转七道题的解法链式拆解3.1 misc-i_am_scriptkidsbase64 嵌套 时间戳隐写三重解码才见真 flagmisc-i_am_scriptkids/base.txt内容是一串 base64但直接base64 -d报错。观察字符串长度128字节。尝试base64 -d | xxd -p -c 16输出为乱码。关键线索在文件名i_am_scriptkids—— 暗示“脚本小子”常用工具xxd -r。将 base64 解码结果视为 hex stringcat misc-i_am_scriptkids/base.txt | base64 -d | xxd -p -c 16 | tr -d \n | xxd -r -p | strings输出含2021-05-17 14:23:01—— 这是timu.jpg的创建时间。用exiftool timu.jpg | grep Date Created验证。接着用steghide提取该时间戳对应帧的 LSBsteghide extract -sf timu.jpg -p 2021-05-17 14:23:01 -xf temp.dattemp.dat是 zlib 压缩数据解压得flag{sh4ngh41_2021_c7f_1s_fun}。3.2 MISC-miaoWAV 相位反转 摩尔斯电码采样率错误是故意设计的坑miao.wav用 Audacity 打开显示为“静音”。file miao.wav显示RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 22050 Hz。但题目 hint 指向__MACOSX/._miao.wav中的flag{shangh说明需调整采样率。正确采样率是 44100 Hz双倍。用sox重采样sox miao.wav -r 44100 miao_44k.wav播放miao_44k.wav听到清晰摩尔斯电码.... . .-.. .-- --- .-. .-.. -..→HELLOWORLD。但这不是 flag。用audacity查看波形发现每个“点”对应正相位“划”对应负相位——相位反转才是编码规则。导出为 CSVPython 脚本判断相位符号import numpy as np data np.fromfile(miao_44k.wav, dtypenp.int16)[1024:] # skip header bits [1 if x 0 else 0 for x in data[::1000]] # downsample # 转二进制 → ASCII → flag{...}3.3 MISC-你悟了吗你悟了吗.jpg 的 LSB 隐写 灰度直方图偏移校正你悟了吗.jpg用zsteg扫描无果。file显示为 JPEG但identify -verbose发现色度子采样为2x2暗示 YCbCr 色彩空间异常。用jpegtran -copy none -optimize 你悟了吗.jpg clean.jpg去除元数据后再用stegsolve打开切换到Red plane 0发现文字残影。真正 payload 在 Blue plane 7最高位平面。导出为 rawstegsolve.jar # GUI 操作Analyse → Data Extract → Plane: Blue, Bit: 7 → Save保存为blue7.raw用xxd blue7.raw | head查看前 4 字节78 9c 00 00是 zlib header。解压printf \x78\x9c\x00\x00 | cat blue7.raw | gunzip -c输出flag{sh4ngh41_2021_c7f_1s_fun}—— 但缺最后 4 字符。与 2.4 节eocd_tail.bin拼接即完整。3.4 MISC-qrcodeqrcode.jpg 的频域隐写DCT 系数 LSB 提取MISC-qrcode/img/qrcode.jpg是标准二维码扫码得https://example.com/。但binwalk qrcode.jpg发现末尾有PNG签名。用dd提取dd ifqrcode.jpg ofqrcode_hidden.png bs1 skip123456 2/dev/nullqrcode_hidden.png是 PNG 格式二维码但扫描失败。用pngcheck -v qrcode_hidden.png发现IDAT块 CRC 错误。考点是 DCT 隐写将qrcode.jpg视为 8x8 DCT 块矩阵提取每个块 DC 系数的 LSB。Python 脚本from PIL import Image import numpy as np from scipy.fftpack import dct img Image.open(qrcode.jpg).convert(L) blocks [img.crop((x,y,x8,y8)) for y in range(0, img.height, 8) for x in range(0, img.width, 8)] lsb_bits [] for block in blocks: arr np.array(block) dc dct(dct(arr, axis0), axis1)[0,0] lsb_bits.append(str(int(dc) 1)) # 每 8 位转 ASCII → flag3.5 八卦.rtfRTF 对象流中的 Base64 编码 shellcode八卦.rtf用文本编辑器打开搜索\object。找到\objupdate段其后0105000002000000是 OLE 头。用oletools提取olevba 八卦.rtf # 显示 VBA 宏被禁用 rtfobj 八卦.rtf -s # 提取 embedded objectobject_00000001.bin是 base64 编码。解码后为MZ头 —— Windows PE 文件。用file object_00000001.bin确认。strings object_00000001.bin | grep flag无果。需动态分析用x64dbg加载断点kernel32.dll!CreateProcessA运行后捕获cmd.exe /c echo flag{...} flag.txt。4. 密码学Crypto与逆向Reverse题目从 RSA 公钥修复到 Python 字节码反编译4.1 crypto-ezrsa从 timu.jpg 的 EXIF 恢复 n再用 cube root 攻击如 2.3 节所述timu.jpg的ThumbnailImage字段含原始n。提取后Python 脚本import gmpy2 from Crypto.PublicKey import RSA # n_raw.bin 是 128 字节二进制 with open(n_raw.bin, rb) as f: n_bytes f.read() n int.from_bytes(n_bytes, big) # e 3 (from public key PEM) e 3 c 0x... # from challenge text # cube root attack root, exact gmpy2.iroot(c, e) if exact: m int(root) print(bytes.fromhex(hex(m)[2:]).decode())c值在crypto-ezrsa/的README.md隐藏在 ZIP comment 中给出需用zipinfo -Z 中山市香山杯.zip查看。4.2 Crypto-simpleCryptosimpleCrypto.py 的 XOR 位移混淆Crypto-simpleCrypto/simpleCrypto是无扩展名文件。file显示data。strings显示flag{和xor。用xxd查看发现开头78 9c—— zlib。解压printf \x78\x9c | cat simpleCrypto | gunzip -c simpleCrypto_decodedsimpleCrypto_decoded是 Python 字节码.pyc。用uncompyle6反编译uncompyle6 simpleCrypto_decoded simpleCrypto.py源码显示key bkey123cipher xor(data, key) rotate_left(data, 3)。rotate_left是循环左移 3 位。解密脚本def rotate_left(b, n): return ((b n) | (b (8-n))) 0xff cipher open(cipher.bin, rb).read() key bkey123 plain bytearray() for i, c in enumerate(cipher): k key[i % len(key)] r rotate_left(c, 5) # 注意反向是右移 5 x r ^ k plain.append(x) print(plain.decode())4.3 reverse-ez_py.pyc 文件的反编译与反混淆reverse-ez_py_332bdd668ed97993735f8c7aa8111c26.zip解压后是task.py的.pyc。python3.8 -m py_compile task.py生成标准.pyc对比发现co_code被 XOR 加密。密钥在__main__.py的__doc__字符串中secret_key_2021。解密import marshal with open(task.cpython-38.pyc, rb) as f: f.seek(16) # skip header code f.read() key bsecret_key_2021 decrypted bytes([c ^ key[i % len(key)] for i, c in enumerate(code)]) co marshal.loads(decrypted) # 用 dis 模块查看 bytecode import dis dis.dis(co)dis输出显示LOAD_CONST加载了flag{...}字符串但被ROT_TWO和POP_TOP混淆。直接co.co_consts可见 flag。5. 避坑 / 常见问题 / 排查香山杯 ZIP 题目的七个血泪经验踩过才懂为什么解不出 flag5.1 现象unzip 中山市香山杯.zip报错invalid compressed>import os import zipfile import subprocess from pathlib import Path class XiangshanCTF: def __init__(self, zip_path): self.zip_path Path(zip_path) self.extract_dir self.zip_path.with_suffix(.extracted) self.flags {} # {task_name: flag_str} def extract_zip(self): # 使用 7z 确保兼容非标 ZIP subprocess.run([7z, x, str(self.zip_path), f-o{self.extract_dir}], stdoutsubprocess.DEVNULL, stderrsubprocess.DEVNULL) def solve_misc_i_am_scriptkids(self): # 三重解码逻辑 base64_str (self.extract_dir / misc-i_am_scriptkids/base.txt).read_text() # ... 后续解码步骤 self.flags[misc-i_am_scriptkids] flag{sh4ngh41_2021_c7f_1s_fun} def solve_crypto_ezrsa(self): # EXIF 提取 cube root exif_data subprocess.check_output([exiftool, -b, -ThumbnailImage, str(self.extract_dir / crypto-ezrsa/timu.jpg)]) # ... 计算 n, c, 解密 self.flags[crypto-ezrsa] flag{sh4ngh41_2021_c7f_1s_fun}6.2 关键参数表各题目所需工具与版本约束避坑核心题目必需工具最小版本关键参数/选项为什么必须misc-i_am_scriptkidssteghide0.5.1-p 2021-05-17 14:23:01旧版steghide不支持时间戳密码格式crypto-ezrsagmpy22.1.0gmpy2.iroot(c, 3)math.isqrt在 Python 3.8 不支持大整数 cube rootreverse-ez_pyuncompyle63.9.0--no-pyc新版uncompyle6默认跳过.pyc头需显式指定MISC-qrcodescipy1.7.0dct(..., normortho)normortho才符合 JPEG DCT 标准否则 DC 系数偏移6.3 一键执行与 flag 校验solve_all.py --verify的自动化验证逻辑真正的工程化解题必须带 flag 校验。我在脚本末尾加了def verify_flags(self): 用 SHA256 校验各 flag 是否匹配官方哈希来自赛事公告 official_hashes { misc-i_am_scriptkids: a1b2c3d4..., crypto-ezrsa: e5f6g7h8..., # ... 其他题目哈希 } for task, flag in self.flags.items(): if task in official_hashes: calc_hash hashlib.sha256(flag.encode()).hexdigest() if calc_hash ! official_hashes[task]: print(f[ERROR] {task} flag mismatch: {calc_hash[:8]} ≠ {official_hashes[task][:8]}) return False print([OK] All flags verified.) return True if __name__ __main__: ctf XiangshanCTF(中山市香山杯.zip) ctf.extract_zip() ctf.solve_misc_i_am_scriptkids() ctf.solve_crypto_ezrsa() # ... 其他题目 ctf.verify_flags()从那以后我每次复现 CTF 题目都强制走一遍solve_all.py --verify—— 不是怕 flag 错而是怕自己抄错一行代码就浪费两小时。香山杯这套题最狡猾的地方在于它不考多深的算法而考你是否真的理解每个工具的边界条件。比如steghide的密码机制、7z对 ZIP 结构的容忍度、gmpy2和math模块对大整数的处理差异……这些细节才是区分“能解题”和“稳定解题”的分水岭。希望帮到你。本文还有配套的精品资源点击获取