软件安全赛初赛Re Crytpo Write up
字数 5270
更新时间 2026-03-18 13:40:17

CTF 逆向与密码学题目详解

本文档基于先知社区文章《软件安全赛初赛Re Crytpo Write up》整理,详细解析了re3、re2、re1、rsa四道题目的解题思路、关键技术点与详细操作步骤。

1. 题目 re3

文件类型: PyInstaller打包的ELF文件,内含client.pyccrypt_core.so扩展模块。

1.1 初步分析与解包

  1. 使用pyinstaller-ng工具对目标ELF文件进行解包,提取出核心文件client.pyccrypt_core.so
  2. client.pyc进行反编译,尽管存在错误,但可获得主要逻辑源码。

1.2 代码逻辑分析

反编译得到的client.py核心流程如下:

  1. 自定义Base64解码密钥
    • 存在一个自定义的Base64字母表CUSTOM_ALPHABET
    • 密钥KEY_B64(值为eUYme4MkN1KSC1bWJZJ2w3FUJCiEXT13D2u1KmiNtfhXKZYE)需通过此自定义字母表映射回标准Base64字母表后,再进行解码得到原始密钥字节。
  2. 文件读取与加密:程序会读取readme.txtflag.txtconfig.txt三个文件。
  3. 加密与传输:通过调用crypt_core.so中的函数对文件内容进行加密,然后将结果(文件名和密文)以JSON格式通过TCP连接发送至127.0.0.1:9999

混淆字符串处理:代码中的字符串被_oe函数轻度混淆(Base85 + XOR + Caesar密码)。需编写解混淆脚本恢复,得到关键的配置常量。

1.3 流量分析与加密核心

  1. 流量捕获:分析提供的capture.pcap文件,发现一次TCP会话,客户端向192.168.5.28:9999发送了三条JSON数据,包含三个文件的密文。
  2. 密码算法分析:分析crypt_core.so文件。
    • 通过IDA Pro逆向,定位到encode_data函数和PyMethod表。
    • 确认加密算法为魔改的SM4算法。
    • 关键魔改点
      • S盒(SBOX) 被替换为自定义值(前16字节为EC CA 0E F3 08 F0 2A A2 3B 18 2B 5C 37 BD 12 A8)。
      • FK常量 被替换为:0xA4861F3B, 0x2D33F783, 0x8EBAAD58, 0x733FDC71
      • CK数组推测也为自定义。

1.4 解密与获取Flag

  1. 还原密钥:根据client.py逻辑,将KEY_B64用自定义字母表转换后,进行标准Base64解码,取前16字节作为SM4算法的密钥。
  2. 实现解密:编写Python脚本,实现使用上述魔改S盒、FK的SM4-ECB(根据上下文判断模式,通常为ECB或CBC,需结合crypt_core.so逻辑确认)解密算法。
  3. 解密流量:使用解密脚本对capture.pcapflag.txt对应的密文d0edd4a1620f6f01db93699e7291bc570b7d8cdd4fa0a69a0839ca4b86a7bd8daacd74313e64da169697af402033a761进行解密。
  4. 获得Flag:成功解密后得到flag.txt的内容为:dart{f4b547fc-b3d0-44c3-bf21-8f3fb5ad3220}

同时,解密readme.txtconfig.txt可验证过程正确。

2. 题目 re2

文件类型: 加壳的Windows PE可执行文件。

2.1 脱壳过程

  1. 识别壳类型:使用Die(Detect It Easy)查壳,发现非标准UPX壳,可能是自定义变种。
  2. 动态调试脱壳
    • 使用x64dbg载入程序。
    • 在程序入口点附近找到典型的push rbx; push rsi; push rdi; push rbp序列(常见于UPX/NRV解压代码),在此处下断点。
    • 寻找OEP(原始程序入口点):通过跟踪代码执行或内存访问断点,定位到可能的OEP地址0x4014E0,并在此下断点。
    • 运行程序至OEP断点,此时程序的原代码已解压到内存中。
    • 使用Scylla插件进行Dump:
      • 点击Dump保存进程内存镜像。
      • 点击IAT Autosearch自动搜索导入地址表。
      • 点击Get Imports获取导入函数信息。
      • 点击Fix Dump修复刚Dump的文件,生成可正常运行的脱壳后程序。

2.2 逆向核心逻辑

  1. 分析主程序:用IDA Pro分析脱壳后的程序,发现程序会提取一个内嵌的Base64编码的PE文件(stage2.exe)。
  2. 提取二级载荷:编写Python脚本,从主程序的.data.rdata节中提取长Base64字符串,解码并保存为stage2.exe
  3. 分析stage2.exe
    • 该程序通过IAT Hook,将MessageBoxW的函数地址替换为自定义函数sub_401550
    • 自定义函数sub_401550会读取用户输入,并调用位于.hello节的校验函数sub_404EF0
    • 在调用校验函数前,程序会先解密.mydata.hello节的内容(使用RC4算法)。
  4. 分析校验函数
    • 校验函数sub_404EF0的核心是调用sub_404CB0进行AES-256-CBC加密/解密。
    • 算法魔改:AES的Rcon轮常数被替换为非标准值[0x9C, 0x10, 0x13, 0x15, 0x19, 0x01, 0x31]。S盒保持标准。
  5. 获取密钥与解密
    • 从程序的初始化数据或内存中提取出AES-256的密钥(c23012ab39101833f8ed4e468da15d8d8cfbf0726899dc7c846e7ecf32bbdaf8)和IV(aeba0dbbca267f9906ed7c70e38d8b11)。
    • 编写解密脚本,使用魔改Rcon的AES-256-CBC算法,对密文9b5e1e8fd7c34362a23786c0ce3d3cf4c3b688ff3c9c13d2bb6f49ceff59a25c36e4619e6061c3bb3f63af003b3d8da7进行解密,即可得到Flag。

3. 题目 re1

文件类型: 包含Base64编码数据的Loader程序,解码后得到Python字节码文件(.pyc)。

3.1 提取与反编译

  1. 提取Base64数据:编写Python脚本,扫描Loader文件的二进制内容,寻找长Base64字符串,将其解码并保存为stager.pyc
  2. 反编译pyc:使用pycdc等工具对stager.pyc进行反编译,得到Python源代码。

3.2 分析Python脚本逻辑

stager.py的核心功能是将一个二进制payload文件编码为视频video.mp4

  1. 读取payload文件,将每个字节转换为8位二进制字符串。
  2. 将整个二进制字符串与固定的8位密钥101010100xAA)进行逐位异或(XOR)。
  3. 将处理后的二进制字符串,按位映射到视频的每一帧:
    • 视频分辨率:640x480
    • 像素块大小:8x8
    • 每个8x8的像素块代表1个比特(1为黑色块,0为白色块)。
  4. 将所有帧合成输出视频video.mp4

3.3 逆向恢复Payload

  1. 读取视频:使用OpenCV读取video.mp4
  2. 提取比特流:对每一帧,按8x8网格采样每个块中心的像素颜色(或平均颜色)。根据亮度判断该比特是1(暗/黑)还是0(亮/白),将所有帧的比特按顺序拼接成二进制字符串。
  3. 异或解密:将二进制字符串按每8位一组转换为字节,然后与0xAA进行异或,还原出原始的payload文件字节。
  4. 分析Payload:用file命令或Die检查恢复出的payload文件,发现是一个ELF可执行文件。用IDA Pro分析,发现其中包含一系列MD5哈希值。
  5. 查询MD5:将这些MD5值在彩虹表或在线解密网站进行查询,其中一个MD5值对应的明文即为Flag:dart{2ab1fb8a-b830-45e7-8830-66c7e3b3e05a}

4. 题目 rsa

本题包含三个关卡(Level),需逐级解密。

4.1 Level 1

目标:获取解密Level 2所需压缩包的密码。

文件:多个RSA公钥文件(key-*.pem)和对应的密文文件(ciphertext-*.bin)。

攻击方法

  1. 共模攻击/公钥共享因子攻击
    • 检查所有公钥的模数n之间是否存在非1的最大公约数(gcd)。如果存在,则可以直接分解出私钥pq
  2. 维纳攻击(Wiener's Attack)
    • 对于e很大(与n同数量级)而私钥d较小的情况,可以利用连分数展开进行攻击,恢复出d
  3. 解密与CRT组合
    • 对能成功分解或通过维纳攻击得到私钥的密文进行解密,得到部分明文片段(plaintext-*.bin.txt)。
    • 这些明文片段实际上是Shamir秘密共享的份额,格式为:d_i: k_i: bits。其中d_i是模数,k_i是份额值,bits是原始消息的比特长度。
    • 使用中国剩余定理(CRT) 组合多个份额,即可恢复出完整的原始消息m
    • m转换为字节,即得到Level 2压缩包的密码:9Zr4M1ThwVCHe4nHnmOcilJ8

4.2 Level 2

目标:获取解密Level 3所需压缩包的密码。

文件:一个RSA任务脚本task.py,给出了n, e, c

攻击方法

  • 本题e非常大,接近n,但维纳攻击不直接生效。文章中提到可利用λ(n)(Carmichael函数)与e的关系进行攻击。
  • 具体方法是:枚举一个小整数g,计算(e * g) / n的连分数收敛,尝试可能的(k, d)对。对于每个候选的d',计算λ' = (e*d' - 1)/k。如果λ'λ(n)的有效值,则可以尝试利用它来分解n(例如,通过计算a^(λ'/2) mod n寻找满足x^2 ≡ 1 (mod n)x ≠ ±1x,则gcd(x-1, n)gcd(x+1, n)可能是n的因子)。
  • 成功分解n后,计算私钥d,解密c得到Level 3的密码:2aa9c360df99cbb4209e4dbab5a9f9ffd86d34906e3206fecfdabf0bb7aeb5ac

4.3 Level 3

目标:获取最终的Flag。

文件:给出了n, c, e=65537, 以及一个关于pq低比特位的泄漏值leak

攻击方法基于泄漏的Coppersmith攻击或比特递推恢复

  1. 分析泄漏函数:泄漏值leak是通过一个复杂的函数leak_mod(p, q, k)计算得到的,该函数混合了p, q, 以及几个常数CONST1, CONST2, CONST3的乘法、加法、移位、异或等操作,但最终只取了结果的最低k位。
  2. 比特递推恢复(剪枝搜索)
    • 已知pq都是大素数,因此最低位均为1(奇数)。
    • 从最低位(k=1)开始,假设已知pq的低k位,枚举它们第k+1位的两种可能性(0或1)。
    • 对于每一对候选的(p_low, q_low),检查两个约束条件:
      a. (p_low * q_low) mod 2^(k+1) == n mod 2^(k+1)
      b. leak_mod(p_low, q_low, k+1) == leak的低(k+1)位
    • 保留所有满足条件的候选对,并递推至下一位。由于约束严格,候选对数量会迅速减少。
  3. 恢复私钥:当搜索到足够多的比特位(例如512比特,因为pq均为1024比特,各恢复一半即可),即可通过gcd(p_low, n)gcd(q_low, n)高效得到完整的pq
  4. 解密获得Flag:计算φ(n) = (p-1)*(q-1),进而得到私钥d = e^(-1) mod φ(n),最后解密c得到明文m,将其转换为字符串即为最终Flag。
相似文章
相似文章
 全屏