2026软件系统安全赛 writeup
字数 7134
更新时间 2026-03-18 13:45:26

2026软件系统安全赛Writeup - 综合题型解析教学

引言

本文档基于2026年软件系统安全赛的官方Writeup,系统性地解析了其中涉及的关键安全技术与解题思路。内容涵盖逆向工程、密码学、网络流量分析、隐写术、Web安全等多个领域,旨在为学习者提供一套完整、深入的分析框架和方法论。


第一部分:逆向工程(Reverse Engineering)

题目1:PyC文件恢复与ELF提取

1.1 初始分析

  • 目标:从提供的Base64数据中恢复Python字节码(.pyc)文件
  • 核心代码片段显示,多个Base64片段拼接后解码,可得到完整的.pyc文件
  • 关键工具:IDA Pro用于分析主函数,确定需要恢复pyc文件

1.2 恢复与反编译

  1. Base64解码:将提供的b64_parts列表拼接后解码
  2. 文件写入:生成payload_encoder.pyc文件
  3. 反编译工具:使用PyLingual等工具对pyc文件进行反编译

1.3 核心逻辑分析

反编译后得到Payload_To_PixelCode_video.py,其主要功能是将任意文件编码为视频:

  • 读取输入文件,转换为二进制字符串
  • 使用固定异或密钥10101010(0xAA)对每8位进行异或加密
  • 将加密后的比特流按8x8像素块映射到视频帧中(黑=1,白=0)

1.4 逆向恢复流程

要恢复原始文件(此处为ELF可执行文件),需执行反向操作:

  1. 视频帧解析:按8x8像素块读取每帧,根据平均像素值判断比特(黑=1,白=0)
  2. 比特流重组:将所有帧的比特按顺序拼接
  3. 异或解密:每8位为一组,与0xAA异或,恢复原始字节
  4. 文件重建:将字节流写入文件,得到原始ELF

1.5 进阶分析:ELF中的MD5字符表

恢复出的ELF文件中包含一系列32位十六进制字符串,经验证为单字符的MD5哈希值。

  • 攻击方法:预计算可打印ASCII字符的MD5值,建立查找表
  • 恢复脚本:提取ELF中的MD5字符串,通过查表反推原始字符,拼接得到flag

最终flagdart{2ab1fb8a-b830-45e7-8830-66c7e3b3e05a}


题目2:多层壳保护程序分析

2.1 初步检测

  • 使用查壳工具检测,发现程序被加壳
  • 尝试使用UPX等工具脱壳,但可能遇到变种或自定义壳

2.2 导入表分析

导入函数极少,仅包含LoadLibraryAGetProcAddressVirtualProtect,表明程序动态解析API,是壳或Loader的典型特征。

2.3 动态脱壳与仿真

由于静态分析困难,采用动态或仿真方案:

  • 工具:使用Speakeasy仿真框架
  • 步骤
    1. 加载样本到仿真环境
    2. 执行所有入口点
    3. 提取解包后的内存镜像(unpacked.bin

2.4 内存镜像分析

unpacked.bin中搜索,发现一段很长的Base64数据,开头为TVqQ(即"MZ"的Base64编码),表明内嵌了一个PE文件。

2.5 第二阶段载荷提取

  1. 使用正则提取Base64 blob
  2. Base64解码,写入stage2.exe

2.6 第二阶段深入分析

stage2.exe为正常PE结构,但关键节区(.hello.mydata)被加密。

  • 加密算法识别:通过反汇编识别出典型的RC4算法特征(KSA和PRGA函数)
  • 密钥位置:存储在.rdata节区
  • 整体保护层次
    1. 第一层:UPX壳
    2. 第二层:Base64内嵌PE
    3. 第三层:RC4加密节区

解密.hello.mydata后,发现程序核心是AES输入验证逻辑。但题目目标并非破解输入,而是分析样本中隐藏的UUID标识符。

最终flagdart{c3d4f5cc-8aab-46ce-a188-2fc453f3b288}


题目3:Python打包程序与自定义块加密

3.1 程序解包

  • 目标:PyInstaller打包的Python可执行文件
  • 工具:使用在线工具(如pyinstxtractor-web)或本地工具解包,获取内部的pyc文件

3.2 反编译分析

反编译client.pyc,发现核心加密模块为crypt_core.so,以及自定义的Base64编码表和密钥处理逻辑。

  • 自定义Base64:码表被替换
  • 密钥推导:原始Base85编码的密钥材料,处理后取前16字节作为最终密钥

3.3 分析crypt_core.so

.so文件的反编译代码混乱,需从汇编和常量特征入手。

  • 核心函数encode_data(plaintext, key)
  • 算法特征
    • 分组大小16字节
    • PKCS#7填充
    • 轮密钥扩展
    • 轮函数中包含x ^ rol(x, 2) ^ rol(x, 10) ^ rol(x, 18) ^ rol(x, 24)等线性变换,类似SM4但非标准(为24轮变体)

3.4 解密流程

由于不是标准SM4,需逆向encode_data函数,实现对应的解密算法:

  1. 密文按16字节分组
  2. 轮密钥逆序应用
  3. 移除PKCS#7填充
    对提供的三段密文(readme.txt, flag.txt, config.txt)使用密钥passvkcDKWLAA45o解密。

最终flagdart{f4b547fc-b3d0-44c3-bf21-8f3fb5ad3220}


第二部分:密码学(Cryptography)

题目:多层RSA与秘密共享

1. 题目结构

提供level1/目录和加密的level2.zip

  • level1/包含:
    • 加密脚本encrypt.py
    • 明文生成脚本generate-plaintexts.py
    • 多个RSA公钥(key-*.pem
    • 多个密文(ciphertext-*.bin
  • 目标:解密ciphertext,恢复message,获得level2.zip密码。

2. 加密逻辑分析(encrypt.py)

根据RSA模数n的位数选择两种模式:

  1. n_bits >= 2048:使用RSA-OAEP加密一个32字节的随机AES-GCM密钥,再用该密钥加密消息。
  2. n_bits < 2048:使用裸RSA(pow(key_int, e, n))加密一个16字节的随机AES-GCM密钥。

3. 明文生成逻辑(generate-plaintexts.py)

  • 将10个message分别进行简化的Asmuth-Bloom秘密共享。
  • 每个message的共享数据(份额)被拼接,形成10个plaintext文件。
  • 每个plaintext-j.txt的第一行是message1的内容,后续9行分别是message2~message10的第j个份额。

4. 攻击步骤

4.1 恢复RSA私钥

  • 检查公钥间的共性:发现key-1key-2key-15key-4存在共因子,可通过计算GCD恢复私钥。
  • 对其他密钥尝试Wiener攻击、Fermat分解等,共恢复11个私钥。

4.2 解密密文
使用恢复的私钥解密对应的ciphertext-*.bin。解密后明文格式为固定头+多行份额数据。每行格式:modulus : remainder : bitlen

4.3 重构秘密(中国剩余定理)
对每个message(2~10),收集其足够的份额(di, ki),其中ki = S mod di。使用中国剩余定理(CRT)求解原始秘密S,即message内容。

关键message7的内容包含level2.zip的密码:9Zr4M1ThwVCHe4nHnmOcilJ8

5. 第二层:小私钥指数攻击

解压level2.zip得到task.pylevel3.zip

  • 关键参数:d为180-bit素数,e = d^{-1} mod lam,其中lam = lcm(p-1, q-1)
  • 攻击思路:由于d很小,可尝试推广的Wiener攻击。枚举g = gcd(p-1, q-1)的可能小值,对(e * g) / n进行连分数逼近,恢复d
  • 成功找到g=4,恢复pq
  • 下一层密码为sha256(str(p+q).encode()).hexdigest(),即:2aa9c360df99cbb4209e4dbab5a9f9ffd86d34906e3206fecfdabf0bb7aeb5ac

6. 第三层:基于泄露信息的RSA私钥恢复

解压level3.zip得到新的task.py,提供n, e, c和一个复杂的leak表达式。

  • 攻击思路:利用n = p * qleak表达式的特性,从低位到高位逐位恢复pq的比特。
  • 实现算法:对于每一位k,枚举pq当前位的可能值(0,1),检查(p*q) mod 2^k是否匹配n mod 2^k,同时检查leak mod 2^k是否匹配。由于每一步候选很少,可唯一确定。
  • 恢复pq后,标准RSA解密得到flag。

最终flagdart{379c9308-e9a8-45a1-bd55-45bbd822e86d}


第三部分:网络流量分析(Traffic Hunt)

1. 流量概览

  • 文件:一个网络流量pcapng文件。
  • 主要内容:HTTP流量,其中存在大量带有长rememberMe Cookie的请求,这是Apache Shiro框架反序列化漏洞的典型特征。

2. Shiro RememberMe 反序列化分析

  • 攻击者通过构造恶意的rememberMe值,利用Shiro的反序列化漏洞执行任意命令。
  • 从流量中提取rememberMe的Base64值,解码后得到Java序列化对象,可分析出利用链(如commons-collections)。
  • 攻击者执行了whoamipwdls等命令,并上传了一个Webshell。

3. Webshell通信分析

  • 上传的Webshell路径:/favicondemo.ico,并给出了连接密码和密钥。
  • 后续流量中出现大量与该Webshell的加密通信(POST请求,请求体/响应体为Base64字符串)。
  • 攻击者通过Webshell下载并执行了一个二进制程序/var/tmp/out,该程序携带AES密钥参数,并连接C2服务器10.1.243.155:7788

4. C2自定义协议解密

  • 过滤出目标IP端口7788的TCP流量。
  • 协议格式:4字节小端长度 + AES-GCM加密数据。
  • 密钥来自命令行参数:IhbJfHI98nuSvs5JweD5qsNvSQ/HHcE/SNLyEBU9Phs=(Base64解码为32字节AES密钥)。
  • 编写解密脚本,按长度字段分割数据流,使用AES-GCM解密(nonce为前12字节,tag为后16字节)。

5. 获取Flag

解密后的通信明文显示攻击者执行了echo命令,输出一串长字符串:3SoX7GyGU1KBVYS3DYFbfqQ2CHqH2aPGwpfeyvv5MPY5Dm1Wt9VYRumoUvzdmoLw6FUm4AMqR5zoi

  • 此字符串经Base58解码,再Base64解码后,得到最终flag。

最终flagdart{d9850b27-85cb-4777-85e0-df0b78fdb722}


第四部分:隐写术(Steganography)

1. 文件格式修复

  • 目标文件末端魔数被篡改(FFFFFFFF)。
  • 将其修复为正确的7z归档头部魔数:37 7A BC AF 27 1C

2. 图片隐写分析

修复后解压得到一张图片。使用隐写分析工具(如Stegsolve、zsteg)检查。

  • 发现LSB(最低有效位)隐写。
  • 在Stegsolve中,提取RGB通道最低位(RGB 0,0,0)的数据,得到一个加密的ZIP压缩包。

3. CRC32爆破攻击

  • 该ZIP包内包含多个pass*.zip文件,每个子ZIP内只有一个4字节的文本文件,并已知其CRC32值。
  • 攻击原理:CRC32是公开算法,已知CRC32值和文件长度(4字节),可爆破出原始内容。
  • 编写脚本,遍历所有4字节可打印字符组合,计算CRC32并与目标值比对。
  • 将所有爆破出的4字节字符串拼接,得到完整密码短语:c1!xxtLf%fXYPkaA

4. 零宽字符隐写

使用上述密码解压flag.zip,得到flag.txt。但内容非明文,包含大量零宽字符。

  • 零宽字符类型
    • 零宽空格 (U+200B) 代表比特 0
    • 零宽非连接符 (U+200C) 代表比特 1
  • 解码脚本:遍历文本中的每个字符,识别零宽字符并转换为比特流,每8位转换为一字节ASCII字符,得到最终flag。

最终flag:(从解码后的零宽隐写信息中获得)


第五部分:Web安全(Auth - 综合渗透)

1. 目标与入口

  • 目标Web应用,具备注册、登录、头像上传、管理员查看在线用户等功能。
  • 初始状态:普通用户权限。

2. 漏洞挖掘与利用链

2.1 任意文件读取/SSRF(漏洞点:头像URL下载)

  • 功能:/profile/avatar允许用户通过URL上传头像。
  • 底层使用urllib.request.urlopen(),未对协议和路径做限制。
  • 利用:通过file://协议读取服务器敏感文件。
    • file:///etc/passwd
    • file:///app/app.py (获取源码)
    • file:///etc/redis/redis.conf (获取Redis密码redispass123
    • file:///proc/self/environ/proc/<pid>/cmdline (获取进程信息,定位高权限本地服务)

2.2 源码分析与Redis交互逻辑
app.py得知:

  1. 登录后,用户对象被pickle.dumps()并存入Redis键online_user:{username}
  2. 管理员端点/admin/online-users会读取所有online_user:*键,并使用一个“受限”的RestrictedUnpickler进行反序列化。
  3. 该Unpickler允许__main__.OnlineUserbuiltins.getattr/setattr等,存在被利用的风险。

2.3 CRLF注入攻击Redis(漏洞点:头像URL下载的SSRF)

  • 利用urllib发送请求时,可在URL中插入\r\n(CRLF)来注入额外的协议命令。
  • 已知Redis运行在本地6379,密码从配置文件中获得。
  • Payload示例
    http://127.0.0.1:6379/\r\nAUTH redispass123\r\nHSET user:<当前用户名> role admin\r\nQUIT\r\n
    
  • 效果:将当前用户在Redis中的角色改为admin,重新登录后获得管理员会话。

2.4 定位高权限本地服务

  • 通过读取/proc/<pid>/cmdline,发现一个监听在高权限端口的Python XML-RPC服务(mcp_server_secure.py)。
  • 读取该脚本,获取认证token和端口号。该服务以root权限运行,可执行任意命令。

2.5 构造恶意Pickle对象触发RCE

  • 目标:在管理员访问/admin/online-users时触发反序列化,执行命令。
  • 虽然Unpickler受限,但允许getattr。可构造调用链,最终获取os.system
  • 最终Payload思路:让反序列化执行的命令,调用上述高权限XML-RPC服务,执行cat /flag,并将结果写入/tmp/flag_root

2.6 写入恶意Pickle到Redis
再次利用CRLF注入SSRF,将构造好的恶意Pickle字节码写入Redis键online_user:{当前用户名}

2.7 触发与获取Flag

  1. 以管理员身份访问/admin/online-users,触发反序列化,执行恶意命令。
  2. 命令调用本地root XML-RPC服务读取/flag并写入/tmp/flag_root
  3. 再次利用头像下载的任意文件读取功能,读取file:///tmp/flag_root,从返回的Base64图片数据中提取flag。

3. 利用总结

这是一个经典的“SSRF -> Redis未授权访问/注入 -> 篡改数据/写入恶意数据 -> 不安全反序列化 -> RCE -> 信息泄露”的综合利用链。关键点在于将文件读取、协议注入、反序列化漏洞与内网高权限服务相结合,实现权限提升和敏感信息窃取。


总结

本Writeup详细解析了软件系统安全赛中涉及的多种安全技术场景。从逆向工程中的文件格式分析、加壳脱壳,到密码学的复杂RSA场景与共享秘密恢复;从网络流量中的协议分析、加密通信解密,到隐写术的多层隐藏与Web安全的纵深渗透利用链,涵盖了CTF竞赛和实战中常见的技术要点。掌握这些分析方法与工具的使用,对于提升软件安全分析与漏洞挖掘能力至关重要。

相似文章
相似文章
 全屏