软件安全赛初赛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.pyc与crypt_core.so扩展模块。
1.1 初步分析与解包
- 使用
pyinstaller-ng工具对目标ELF文件进行解包,提取出核心文件client.pyc和crypt_core.so。 - 对
client.pyc进行反编译,尽管存在错误,但可获得主要逻辑源码。
1.2 代码逻辑分析
反编译得到的client.py核心流程如下:
- 自定义Base64解码密钥:
- 存在一个自定义的Base64字母表
CUSTOM_ALPHABET。 - 密钥
KEY_B64(值为eUYme4MkN1KSC1bWJZJ2w3FUJCiEXT13D2u1KmiNtfhXKZYE)需通过此自定义字母表映射回标准Base64字母表后,再进行解码得到原始密钥字节。
- 存在一个自定义的Base64字母表
- 文件读取与加密:程序会读取
readme.txt、flag.txt、config.txt三个文件。 - 加密与传输:通过调用
crypt_core.so中的函数对文件内容进行加密,然后将结果(文件名和密文)以JSON格式通过TCP连接发送至127.0.0.1:9999。
混淆字符串处理:代码中的字符串被_oe函数轻度混淆(Base85 + XOR + Caesar密码)。需编写解混淆脚本恢复,得到关键的配置常量。
1.3 流量分析与加密核心
- 流量捕获:分析提供的
capture.pcap文件,发现一次TCP会话,客户端向192.168.5.28:9999发送了三条JSON数据,包含三个文件的密文。 - 密码算法分析:分析
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数组推测也为自定义。
- S盒(SBOX) 被替换为自定义值(前16字节为
- 通过IDA Pro逆向,定位到
1.4 解密与获取Flag
- 还原密钥:根据
client.py逻辑,将KEY_B64用自定义字母表转换后,进行标准Base64解码,取前16字节作为SM4算法的密钥。 - 实现解密:编写Python脚本,实现使用上述魔改S盒、FK的SM4-ECB(根据上下文判断模式,通常为ECB或CBC,需结合
crypt_core.so逻辑确认)解密算法。 - 解密流量:使用解密脚本对
capture.pcap中flag.txt对应的密文d0edd4a1620f6f01db93699e7291bc570b7d8cdd4fa0a69a0839ca4b86a7bd8daacd74313e64da169697af402033a761进行解密。 - 获得Flag:成功解密后得到
flag.txt的内容为:dart{f4b547fc-b3d0-44c3-bf21-8f3fb5ad3220}。
同时,解密readme.txt和config.txt可验证过程正确。
2. 题目 re2
文件类型: 加壳的Windows PE可执行文件。
2.1 脱壳过程
- 识别壳类型:使用
Die(Detect It Easy)查壳,发现非标准UPX壳,可能是自定义变种。 - 动态调试脱壳:
- 使用
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 逆向核心逻辑
- 分析主程序:用IDA Pro分析脱壳后的程序,发现程序会提取一个内嵌的Base64编码的PE文件(
stage2.exe)。 - 提取二级载荷:编写Python脚本,从主程序的
.data或.rdata节中提取长Base64字符串,解码并保存为stage2.exe。 - 分析stage2.exe:
- 该程序通过IAT Hook,将
MessageBoxW的函数地址替换为自定义函数sub_401550。 - 自定义函数
sub_401550会读取用户输入,并调用位于.hello节的校验函数sub_404EF0。 - 在调用校验函数前,程序会先解密
.mydata和.hello节的内容(使用RC4算法)。
- 该程序通过IAT Hook,将
- 分析校验函数:
- 校验函数
sub_404EF0的核心是调用sub_404CB0进行AES-256-CBC加密/解密。 - 算法魔改:AES的
Rcon轮常数被替换为非标准值[0x9C, 0x10, 0x13, 0x15, 0x19, 0x01, 0x31]。S盒保持标准。
- 校验函数
- 获取密钥与解密:
- 从程序的初始化数据或内存中提取出AES-256的密钥(
c23012ab39101833f8ed4e468da15d8d8cfbf0726899dc7c846e7ecf32bbdaf8)和IV(aeba0dbbca267f9906ed7c70e38d8b11)。 - 编写解密脚本,使用魔改
Rcon的AES-256-CBC算法,对密文9b5e1e8fd7c34362a23786c0ce3d3cf4c3b688ff3c9c13d2bb6f49ceff59a25c36e4619e6061c3bb3f63af003b3d8da7进行解密,即可得到Flag。
- 从程序的初始化数据或内存中提取出AES-256的密钥(
3. 题目 re1
文件类型: 包含Base64编码数据的Loader程序,解码后得到Python字节码文件(.pyc)。
3.1 提取与反编译
- 提取Base64数据:编写Python脚本,扫描
Loader文件的二进制内容,寻找长Base64字符串,将其解码并保存为stager.pyc。 - 反编译pyc:使用
pycdc等工具对stager.pyc进行反编译,得到Python源代码。
3.2 分析Python脚本逻辑
stager.py的核心功能是将一个二进制payload文件编码为视频video.mp4:
- 读取
payload文件,将每个字节转换为8位二进制字符串。 - 将整个二进制字符串与固定的8位密钥
10101010(0xAA)进行逐位异或(XOR)。 - 将处理后的二进制字符串,按位映射到视频的每一帧:
- 视频分辨率:
640x480。 - 像素块大小:
8x8。 - 每个
8x8的像素块代表1个比特(1为黑色块,0为白色块)。
- 视频分辨率:
- 将所有帧合成输出视频
video.mp4。
3.3 逆向恢复Payload
- 读取视频:使用
OpenCV读取video.mp4。 - 提取比特流:对每一帧,按
8x8网格采样每个块中心的像素颜色(或平均颜色)。根据亮度判断该比特是1(暗/黑)还是0(亮/白),将所有帧的比特按顺序拼接成二进制字符串。 - 异或解密:将二进制字符串按每8位一组转换为字节,然后与
0xAA进行异或,还原出原始的payload文件字节。 - 分析Payload:用
file命令或Die检查恢复出的payload文件,发现是一个ELF可执行文件。用IDA Pro分析,发现其中包含一系列MD5哈希值。 - 查询MD5:将这些MD5值在彩虹表或在线解密网站进行查询,其中一个MD5值对应的明文即为Flag:
dart{2ab1fb8a-b830-45e7-8830-66c7e3b3e05a}。
4. 题目 rsa
本题包含三个关卡(Level),需逐级解密。
4.1 Level 1
目标:获取解密Level 2所需压缩包的密码。
文件:多个RSA公钥文件(key-*.pem)和对应的密文文件(ciphertext-*.bin)。
攻击方法:
- 共模攻击/公钥共享因子攻击:
- 检查所有公钥的模数
n之间是否存在非1的最大公约数(gcd)。如果存在,则可以直接分解出私钥p和q。
- 检查所有公钥的模数
- 维纳攻击(Wiener's Attack):
- 对于
e很大(与n同数量级)而私钥d较小的情况,可以利用连分数展开进行攻击,恢复出d。
- 对于
- 解密与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 ≠ ±1的x,则gcd(x-1, n)或gcd(x+1, n)可能是n的因子)。 - 成功分解
n后,计算私钥d,解密c得到Level 3的密码:2aa9c360df99cbb4209e4dbab5a9f9ffd86d34906e3206fecfdabf0bb7aeb5ac。
4.3 Level 3
目标:获取最终的Flag。
文件:给出了n, c, e=65537, 以及一个关于p和q低比特位的泄漏值leak。
攻击方法:基于泄漏的Coppersmith攻击或比特递推恢复。
- 分析泄漏函数:泄漏值
leak是通过一个复杂的函数leak_mod(p, q, k)计算得到的,该函数混合了p,q, 以及几个常数CONST1,CONST2,CONST3的乘法、加法、移位、异或等操作,但最终只取了结果的最低k位。 - 比特递推恢复(剪枝搜索):
- 已知
p和q都是大素数,因此最低位均为1(奇数)。 - 从最低位(
k=1)开始,假设已知p和q的低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)位。 - 保留所有满足条件的候选对,并递推至下一位。由于约束严格,候选对数量会迅速减少。
- 已知
- 恢复私钥:当搜索到足够多的比特位(例如
512比特,因为p和q均为1024比特,各恢复一半即可),即可通过gcd(p_low, n)或gcd(q_low, n)高效得到完整的p和q。 - 解密获得Flag:计算
φ(n) = (p-1)*(q-1),进而得到私钥d = e^(-1) mod φ(n),最后解密c得到明文m,将其转换为字符串即为最终Flag。
相似文章
相似文章