首页 / 资讯中心 / 文章详情

香山杯2021 CTF真题包:一次搞定Misc到PWN的实战训练

香山杯2021 CTF真题包:一次搞定Misc到PWN的实战训练 ★ FEATURED ARTICLE
简介2021年中山市香山杯CTF竞赛完整赛题资源包面向CTF参赛者、网络安全学习者与战队复盘训练整理自正式赛题可用于题目复现、思路拆解与技能提升。整个压缩包共41个文件约17.3MB涵盖MISC、PWN、Crypto、Reverse四大方向题目图片以png/jpg呈现Python脚本提供解题或加密逻辑wav音频用于隐写分析zip为打包附件或容器txt与rtf包含提示、说明或Flag目录结构清晰便于按模块检索。已有495人学习下载。包内包含MISC方向的脚本分析、音频隐写、二维码识别、密码爆破等题型PWN方向的堆/栈利用题Crypto方向的RSA与简单密码题以及Reverse方向的Python逆向题覆盖多种常见考点。读者可对照题目图片与附件自行尝试借助Python脚本还原加密与解题过程有助于赛事复盘、新手入门和赛前冲刺训练。1. 香山杯 2021 CTF 真题包一份能把 Misc、Crypto、Reverse 全流程跑通的训练靶场做 CTF 最怕的不是题难是找不到「有答案、有附件、能反复练」的成套题目。中山市香山杯 2021 的这份题目 zip 恰好补齐了这个缺口——它把 Misc、Crypto、Reverse、PWN 四个方向按真实比赛目录打包从签到级的图片隐写、二维码修复到需要写脚本攻击的 ezrsa、Python 逆向 ez_py再到堆题 build_your_house难度是梯度上升的。对准备入门或者想系统性刷题的人来说这份资源的价值在于你不用再去各个平台零散找题解压后按目录就能模拟一次完整的参赛过程。正文里涉及的文件名、分类结构都来自题包本身下文讲的解法也是这个题包场景下最主流的思路。先说清楚它的题包结构压缩根目录下按赛题分类命名MISC-开头的是杂项crypto-和Crypto-是密码学reverse-是逆向PWN-是二进制漏洞利用每个子目录里至少带一个timu.jpg或题目.jpg作为题干附图。这种组织方式对训练特别友好——每一类题都是一个自包含的最小复现单元解完一道就是一次完整的方法论演练。下面按方向逐个拆。2. Misc 题组实战从图片像素到音频频谱的隐写排查链路Misc 是 CTF 里最讲「套路」的方向香山杯这份题包的 Misc 题目覆盖了隐写的几条主干图片 LSB、文件拼接、音频频谱、二维码修复、压缩包伪加密。逐个过一遍基本就是一次完整的信息隐藏排查流程。2.1 i_am_scriptkids先从 base.txt 找线索再去看图这个题目录下就两个文件base.txt和timu.jpg。常规做法是先读base.txt里面大概率是一段 Base64 编码的字符串解码后可能是另一段编码、一段提示语或者直接指向图片里的隐写位置。CTF 里拿文本文件当入口常见思路是「编码层层剥」Base64 之后可能跟 Base32、十六进制甚至栅栏密码。# 先用命令行原地解码不要急着开脚本 cat base.txt | base64 -d decoded.txt file decoded.txt如果file命令显示decoded.txt是 ASCII text就继续读内容如果显示是图片或压缩包说明解码结果本身就是个文件需要改后缀再处理。这里的关键点是解码后先file一下别直接cat二进制文件直接打印到终端会把终端搞乱这是新手最容易踩的第一步。处理完文本再看timu.jpg用binwalk扫一遍然后看一眼文件尾部有没有追加数据binwalk timu.jpg # 常见输出Zlib 压缩数据、RAR 压缩包、PNG 图片 # 如果发现偏移量用 dd 切割 dd iftimu.jpg ofhidden.zip bs1 skip偏移量binwalk扫的是文件签名JPEG 末尾追加 ZIP 或另一个图片是 Misc 题最经典的藏法没有之一。切出来的文件是压缩包就继续解是图片就用 010 Editor 之类的工具对比两张图的十六进制差异。2.2 miao 与「你悟了吗」音频频谱图和 RTF 文档里的文字隐藏misc-miao目录里有miao.wav和题目图。音频隐写优先查频谱用 Audacity 打开 wav切到频谱图视图View → Spectrogram把视图拉宽看看有没有一段频率上排列规律的点阵或文字。这种题在香山杯这类比赛里出现频率很高因为出题成本低——录一段猫叫然后把 flag 文字以特定频率混进去就行。操作路径Audacity 打开 miao.wav → 点击轨道左侧的下拉箭头 → 选择 Spectrogram → 缩放比例调大横向拉伸到能看到完整频谱 → 观察 2kHz-8kHz 区间是否有规律纹理参数上注意频谱图默认的窗口大小是 1024分辨率不够时高频细节会糊设置里把 Window size 调到 2048 或 4096能有效提升文字可读性。如果频谱里看到的文字是反的用 Audacity 的「反转」效果处理后再看这是老选手都知道的细节。MISC-你悟了吗目录里是你悟了吗.jpg和八卦.rtf。RTF 文件本质是纯文本格式直接用文本编辑器打开就能看到控制字和内容文本。但 Misc 题里 RTF 的常见套路是把真正内容隐藏在十六进制层面或者文档里嵌入了对象。用strings直接扫strings -e l 八卦.rtf | head -50 # -e l 表示扫描 UTF-16LE 编码的字符串RTF 嵌入对象常用这种编码strings默认只处理 ASCII遇到 UTF-16 的中文内容会漏掉。加了-e l之后嵌入的 Unicode 文本会原形毕露。如果strings扫出来的是大段不可读控制字就把 RTF 拖进 010 Editor搜object关键词定位嵌入对象。2.3 MISC-qrcode二维码残缺时的两种修复手法这个目录是qrcode.jpg加题目.jpg。二维码题的主流考法有三个定位角被遮挡、二维码只有局部、二维码被 PS 过颜色。先打开图片看定位角三个角落的方块是否完整不完整就补角完整但扫不出来就对比题目.jpg找线索。# 安装 zbar 后直接命令行识别 zbarimg qrcode.jpg # 如果报错 NULL说明二维码有损坏需要图像修复遇到扫不出来的情况我一般是先把图片丢进 Photoshop用「阈值」调整把灰度图转纯黑白再手动补全三个定位角。二维码容错率分 L/M/Q/H 四级L 级只能容忍约 7% 的破损Q 级以上能到 30%。如果题图破损集中在边缘可以用 Python 的pyzbar配合 OpenCV 做形态学处理试试import cv2 from pyzbar import pyzbar img cv2.imread(qrcode.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) # 膨胀 腐蚀把断裂的模块连起来 kernel cv2.getStructuringElement(cv2.MORPH_RECT, (3, 3)) dilated cv2.dilate(gray, kernel, iterations2) _, thresh cv2.threshold(dilated, 127, 255, cv2.THRESH_BINARY) decoded pyzbar.decode(thresh) print(decoded)cv2.dilate的 iterations 参数控制膨胀强度设 2 是经验值太大容易把相邻模块粘在一起二维码反而更读不出来。pyzbar.decode返回的对象里.data就是二维码承载的原始内容可能是字符串也可能是二进制文件头。2.4 MISC-BrokenPassword伪装加密的 zip 与 babyrgb 的像素级操作MISC-BrokenPassword目录里是flag.zip、1.jpg和babyrgb目录。先看 zip用压缩包工具打开如果能预览到内部文件名但解压要密码多半是伪加密——zip 的加密标志位被改了不是真加密。伪加密的特征是文件头第 6、7 字节为奇数。处理方式两种用 ZipCenOp 之类的工具修复标志位或者直接改十六进制。# 十六进制方式处理伪加密定位 50 4B 01 02中央目录文件头 # 找到加密标志位通用位标志的 bit 0把 09 00 改成 08 00 # 保存后重新解压通常密码校验会直接跳过真加密的 zip 也别急着上爆破工具先看同目录下的1.jpg文件名带1往往暗示有多张图或者其他提示。babyrgb看名字就是 RGB 通道分离题——一张图把 R、G、B 三个通道分别存成文件或者一个像素值里藏着另一张图的编码。用 Python 拆通道from PIL import Image import numpy as np img np.array(Image.open(题目.jpg)) r, g, b img[:, :, 0], img[:, :, 1], img[:, :, 2] # 常见套路某个通道的低位全是隐写数据 # 提取低位并还原成 0-255 灰度 secret (r 0x0F) 4 Image.fromarray(secret).save(extracted.png)r 0x0F取的是红色通道的低 4 位 4把低 4 位抬到高 4 位这样灰度图才有对比度直接存低 4 位会得到一张几乎全黑的图。如果提取出来还是看不出内容就换 g、b 通道或者改成提取最低 1 位再乘 255。3. Crypto 题组拆解ezrsa 的数学攻击面与 simpleCrypto 的代码审计Crypto 方向这份题包给我最深的印象是「题目不长但每个都卡在一个具体的攻击模型上」。crypto-ezrsa和Crypto-MyBot、Crypto-simpleCrypto分别对应 RSA 攻击脚本、Python 源码审计、自定义加密算法逆向。做这类题的节奏是先看timu.jpg或task.py里给了什么再决定用什么攻击模型。3.1 ezrsa拿到加密脚本先判断是低加密指数还是共模攻击ezrsa目录下是加密脚本和timu.jpg。RSA 题的核心永远是「从已知量推未知量」。拿到脚本先看三件事e多大、有没有给出两组相同的n、p和q是否直接泄露。如果e3且消息很短低加密指数攻击直接开立方根如果同一个n配了两个不同的e那就是共模攻击如果n是 512 位以内直接上 YAFU 分解。# 低加密指数攻击m^e c当 m^e n 时直接开方 from gmpy2 import iroot c 0x # 题目给的密文 e 3 n 0x # 模数 m, exact iroot(c, e) if exact: # 转换成字节 flag int(m).to_bytes((int(m).bit_length() 7) // 8, big) print(flag)iroot返回两个值第一个是整数根第二个是布尔值表示是否开方精确。注意判断条件必须是exact为 True 才说明m^e没超过n否则结果是错的。如果开立方不精确就说明明文被 padding 过了要走 Coppersmith 或爆破填充位。共模攻击的脚本更固定——两组(e1, c1)和(e2, c2)共享同一个n用扩展欧几里得算出s1和s2满足e1*s1 e2*s2 1然后m (c1^s1 * c2^s2) mod n。# 共模攻击核心 def egcd(a, b): if b 0: return a, 1, 0 g, x, y egcd(b, a % b) return g, y, x - (a // b) * y _, s1, s2 egcd(e1, e2) if s1 0: s1 -s1 c1 pow(c1, -1, n) # s2 同理取反 m (pow(c1, s1, n) * pow(c2, s2, n)) % n这里最容易翻车的是s1或s2为负数的情况——Python 的pow不支持负指数必须先把对应的密文取模逆元再把指数转正。pow(c1, -1, n)是 Python 3.8 的内置模逆运算老版本得用gmpy2.invert(c1, n)。血泪经验gmpy2 没装一到算逆元就抛异常。3.2 Crypto-MyBot从 task.py 里读出加密的「黑匣子」这个题给的是task.py和timu.jpg。MyBot 这类题目名往往是个幌子真正要分析的是 Python 源码里的加密逻辑。打开task.py先别逐行读先搜关键词def、encrypt、flag、xor、ord。很多题目写的所谓「加密」本质就是把 flag 和某个 key 做异或key 可能藏在图片里或者由图片文件名暗示。# 异或加密的通用还原模板 cipher [0x1F, 0x2A, 0x33] # 密文字节 key bthe_key # 从题目其他文件推断或爆破 plain bytes([c ^ key[i % len(key)] for i, c in enumerate(cipher)]) print(plain)异或还原的关键是拿到 key。如果 key 长度未知先观察密文长度和可能的明文格式——flag 一般以flag{开头用cipher[0] ^ ord(f)就能推出 key 的第一个字节以此类推可以还原完整 key。如果 task.py 里用了random.seed()那就更简单——种子固定随机序列可复现。3.3 Crypto-simpleCrypto自定义加密算法的逆向思路simpleCrypto这名字暗示加密逻辑「简单但不标准」。处理自定义加密算法我的习惯是先把加密流程拆成三步密钥怎么生成、明文怎么变换、密文怎么输出。常见的简单实现是「移位 异或 置换」的组合每一步单独拿到都可以逆。# 假设加密为c ((m shift) ^ key) 0xFF # 还原m ((c ^ key) - shift) 0xFF def decrypt(cipher, key, shift): m (cipher ^ key) - shift return m 0xFFsolve_crypto.py这类脚本写到这个程度就够用。如果题目里出现了bytes到int的转换、long_to_bytes、bytes_to_long说明加密单位是大整数而不是字节那就要切成大数运算的思路来还原。不管哪种先确认题图timu.jpg里有没有写偏移量或密钥——CTF 出题人喜欢把 hint 放在图里用strings扫图是 30 秒的事不要一开始就死磕算法。4. Reverse 与 PWN 题组ez_py 的 Python 字节码还原与 build_your_house 的堆结构脉络Reverse 和 PWN 是这份题包里对工具链要求最高的两块。reverse-ez_py名字已经暴露了——Python 逆向大概率是 pyc 文件或打包后的 exe。PWN-build_your_house则是一道堆题需要 pwntools 和 glibc 调试环境。这两个方向入门门槛高但题包里的题选得比较克制适合做第一个实战样本。4.1 reverse-ez_py反编译 pyc 还是直接审计打包 exereverse-ez_py目录下是压缩包。解压后如果看到.pyc文件直接用uncompyle6或pycdc反编译。 uncompyle6 对 Python 3.8 以下支持良好遇到高版本就换decompyle3。如果拿到的是打包好的 exe先跑pyinstxtractor.py解包再从得到的pyc反编译。# pyc 反编译 uncompyle6 ez_py.pyc ez_py_source.py # exe 解包 python pyinstxtractor.py ez_py.exe # 输出目录里找 entry.pyc 或 main.pyc再反编译反编译出来的源码如果直接能看到 flag 计算逻辑那是最理想的情况。但很多题会做一层混淆——变量名改成无意义字符、控制流平坦化、字符串加密。碰上这种先别急着人工分析把反编译代码里的字符串提取出来strings ez_py_source.py | grep -i flag如果 flag 是分段拼的grep flag能直接定位到拼接位置。如果字符串被加密了就在反编译源码里搜decode或base64通常能找到解密函数。4.2 PWN-build_your_house了解堆结构后再决定打哪个点build_your_house这类堆题名字隐喻「自己构建堆布局」常见漏洞点是 UAF悬空指针、double free、堆溢出。做堆题的第一步永远是确定 glibc 版本——不同版本的 tcache、fastbin 机制差异巨大打错版本等于白做。# 查看题目给的 libc file libc.so.6 # 或通过 checksec 查看保护 checksec --filebuild_your_housechecksec输出里的Partial RELRO、No PIE、NX enabled直接决定了攻击路径。No PIE 意味着可以打固定地址Partial RELRO 意味着 GOT 表可写这是两个最常用的切入点。堆题脚本一般用 pwntools 写交互、发送 payload、接收输出都靠它from pwn import * context.arch amd64 # 本地调试 p process(./build_your_house) # 远程连接时改成 remote(ip, port) p.sendlineafter(b , b1) # 选择菜单功能 p.sendline(p64(0x404500)) # 传入目标地址 p.interactive()sendlineafter的b 参数要跟程序实际输出的提示符一致不一致会导致发送阻塞。p64是打包 64 位地址的标准函数字节序是小端——地址填错或者字节序不对是堆题脚本里最常见的两个报错点。做题顺序上「先本地打通再连远程」本地调试可以用 gdb 插件 gef/pwndbg 下断点看堆结构比纯黑盒测试高效得多。5. CTF 题包实战的五个典型翻车现场从解压到提权一条龙排查这一章写我在跑这套题包时遇到的和见过别人遇到的坑每条都是现象、原因、解决三步给全按操作先后排序。5.1 解压后出现 __MACOSX 和垃圾文件现象压缩包解压后多出__MACOSX目录里面是._开头的隐藏文件污染了题包目录找不到原题文件。原因题包在 macOS 系统上打包Finder 自动生成了资源派生文件zip命令默认把这些也压缩进去了。解决解压时直接用-x排除或者解压后统一删除。Linux 下用一条命令搞定unzip 中山市香山杯.zip -x __MACOSX/* # 已解压的情况 find . -name ._* -delete rm -rf __MACOSX5.2 base.txt 解码后直接 cat终端输出乱码现象cat decoded.txt后终端出现大量不可见字符滚动条刷屏甚至把终端配色搞乱。原因解码后的内容不是文本可能是图片或二进制文件直接当文本来读。解决解码后先file确认类型不要急着cat。已经乱码了就用reset命令恢复终端不影响后续操作。5.3 binwalk 扫不出图片尾部附加数据现象binwalk timu.jpg输出为空但图片打开后右下角有明显异常色块或者文件大小异常。原因出题人把附加数据偏移做了处理或者 binwalk 特征库没覆盖对应签名。解决手动查文件尾xxd timu.jpg | tail -20JPEG 的文件尾一般是FF D9后面如果还有非零字节就是藏了东西。另一种方式是看文件大小两张内容相似的图大小差几 KB基本可以断定有追加数据。5.4 zip 爆破工具跑了几小时没结果现象fcrackzip或john跑flag.zip字典加载完就一直在跑几小时不出结果。原因可能不是密码攻击问题——先确认是否为伪加密或者密码本身就是弱密码只是字典里没有也可能是「密码就是文件名或题干里的某个词」。解决先把同目录下的1.jpg、题目.jpg用strings扫一遍找找有没有类似password:、key:的提示。另外试试把目录名、文件名、比赛名当密码香山杯这类比赛经常用xiangshan2021或类似格式。爆破是最后手段不是第一手段。5.5 pyc 反编译失败uncompyle6 直接报错现象uncompyle6 ez_py.pyc抛出ValueError: bad marshal data或版本不支持的报错。原因pyc 的 Python 版本高于 uncompyle6 支持范围常见于 Python 3.9 编译产物或者 pyc 被处理过比如去掉头部。解决先file ez_py.pyc看 Python 版本。3.9 用pycdcDecompyle试试报错就用decompyle3。如果反编译不完整至少用marshal模块把代码对象加载出来import marshal, dis, importlib.util, sys with open(ez_py.pyc, rb) as f: f.read(16) # 跳过 pyc 头部 code marshal.load(f) dis.dis(code) # 直接看字节码dis.dis输出的字节码虽然可读性差但能看到全局变量名、函数名和常量表——flag 经常就躺在code.co_consts里。6. 把所有 Misc 题串成一条自动化检查流水线从单题手动到一键扫描Misc 题单题做多了你会发现流程高度重复文本解码、文件签名扫描、图片通道提取、频谱查看。把这些步骤串成脚本能节省大量重复劳动。我整理了一个最小可用的检查流程覆盖了这个题包里 80% 的 Misc 题入口。import os import subprocess from PIL import Image import numpy as np def scan_file(path): # 1. 文件类型识别 result subprocess.run([file, path], capture_outputTrue, textTrue) print(result.stdout.strip()) # 2. 尾部附加数据检查 with open(path, rb) as f: data f.read() tail data[-4:] print(tail bytes:, tail.hex()) # 3. 图片通道低位提取全自动跑一遍 if path.endswith((.jpg, .png)): img np.array(Image.open(path)) for i, name in enumerate([R, G, B]): secret (img[:, :, i] 0x01) * 255 Image.fromarray(secret.astype(uint8)).save(f{name}_lsb.png)这段脚本的逻辑是按顺序跑三个检查file识别真实类型、查看文件尾部字节、把图片每个通道的最低位提取成独立灰度图。执行后如果R_lsb.png或G_lsb.png里有肉眼可辨的轮廓就说明低位隐写了内容。 0x01是最低 1 位如果你怀疑出题人用了 4 位隐写改成 0x0F再乘 17 就能把高四位还原。这个流程在香山杯这套题上跑了一遍之后我的习惯变了以前拿到 Misc 题是先双击图片看现在强制先走脚本再人工判断。这个习惯帮助我避免了好几次「肉眼看了半天结果答案在 binwalk 里一键就出来」的尴尬。音频题也按类似逻辑处理——先看频谱再听内容先看波形再拉长高频区域永远是检查重点。遇到 wav 就先用 Audacity 的频谱图扫一遍配合strings扫文件里的文本标记基本能把 miao 这种题控制在五分钟内解完。做 CTF 题包复盘核心不是把某一道题解出来而是把一类题的检查顺序固化成肌肉记忆。这套流程你跑完一遍再回头看自己的做题路径会发现大多数题不是难在知识点而是难在「没想到去查那个位置」。希望这份题包的拆解和上面的检查顺序能帮到你遇到类似题目时少走几步弯路。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站