新春杯CTF RE题目解析
字数 3920
更新时间 2026-02-27 01:26:47

新春杯CTF逆向工程题目解析与教学文档

本教学文档旨在详细解析“新春杯CTF”逆向工程(RE)题目的解题思路、涉及的技术点和详细操作步骤。文档基于提供的文章内容整理,力求全面、深入,适合有一定逆向基础的学习者参考。


题目总览与资源

  • 比赛名称:新春杯CTF
  • 题目类型:逆向工程(Reverse Engineering)
  • 题目打包文件逆向.zip
  • 文件下载链接https://pan.baidu.com/s/1p_XcsKWhNLwbiF0hkpSrcg?pwd=aaaa

题目共四道,难度和考察点递进。


第一题:签到

考察点:Python打包的Exe程序逆向处理流程。

核心步骤

  1. 解包Exe:使用PyInstaller提取工具(pyinstxtractor.py)对题目提供的exe文件进行解包,得到.pyc字节码文件。
  2. 环境配置:解包工具可能需要特定的Python环境(如3.11)。可使用Conda创建独立环境:
    conda create -n py311 python=3.11 -y
    
  3. 反编译PyC:使用.pyc反编译工具(如pycdc.exe)或在线网站(例如https://tool.lu/pyc/)将.pyc文件反编译为Python源代码。
  4. 分析源码:反编译后的关键源码如下:
    #!/usr/bin/env python
    # Version: Python 3.11
    a = '53 44 50 43 53 45 43 7b 72 65 76 65 72 73 65 5f 71 69 61 6e 5f 64 61 6f 7d 0a'
    correct_flag = bytes.fromhex(a).decode('utf-8').strip()
    user_input = input('please input your flag: ')
    if user_input == correct_flag:
        print('great')
        return None
    print('wrong')
    #SDPCSEC{reverse_qian_dao}
    
  5. 获取Flag:变量a是以空格分隔的十六进制字符串。将其拼接并转换即可得到Flag。
    Flag: SDPCSEC{reverse_qian_dao}

知识点:PyInstaller打包机制、.pyc文件结构、Python字节码反编译。


第二题:Ez_VM

考察点:C++编写的自定义虚拟机(VM)逆向分析。

程序逻辑概述

  1. main函数负责初始化虚拟机上下文(结构体vm_ctx_t)和指令码,然后调用verify函数进行校验。
  2. verify函数是虚拟机的解释器,循环执行指令码,根据执行结果设置标志位ok
  3. 最终根据ok标志位输出成功或失败信息。

关键数据结构(还原后):

typedef struct vm_ctx_t
{
    unsigned char *code_ptr; // +0x00,指令指针
    int ip;                   // +0x08,指令索引
    unsigned char regs[4];    // +0x0C,4个寄存器
    char *input_ptr;          // +0x10,输入指针
    int in_pos;               // +0x18,输入位置
    unsigned char ok;         // +0x1C,校验标志位
    unsigned char _pad1D[3]; // +0x1D,填充
} vm_ctx_t;

逆向分析要点

  • 指令分析verify函数中的关键指令是0x20,其后跟两个字节[A][B],含义为:regs[A] ^= B。这是一个简单的异或操作指令。
  • 动态验证main函数中复杂的代码实际上是在“填充”一段固定的指令序列(code_ptr指向的数据)。解题的关键是识别出这段指令序列,并模拟执行,得到用于异或比较的正确数据。
  • 解题脚本:通过分析,可以提取出硬编码的指令数据(文中以aWPvjL表示),并对每个字节异或0x11后得到Flag。
    aWPvjL = (b"w}pvj" + bytes([0x27]) + b"#&(<(#)!<\"#&" + bytes([0x27]) + b"< %$ %l")
    assert len(aWPvjL) == 27
    flag = bytes([b ^ 0x11 for b in aWPvjL]).decode("ascii")
    print(flag)
    
    Flag:需要运行脚本获取。

知识点:虚拟机逆向、结构体还原、自定义指令集分析、动态内存操作分析。


第三题:baby_rc4

考察点:UPX魔改壳与RC4流密码算法魔改识别。

解题步骤

  1. 脱壳:题目使用了魔改的UPX壳,需要将壳头特征序列(例如55 50 58 21)修复为标准UPX头,然后使用UPX官方工具或修改后的脱壳机进行脱壳。
  2. 识别算法:脱壳后分析主逻辑,识别出是RC4算法,但最后一步的加密/解密操作被修改。
  3. 标准RC4回顾
    • KSA(密钥调度算法):利用密钥key对256字节的S盒进行初始置换。
      S = list(range(256))
      j = 0
      for i in range(256):
          j = (j + S[i] + key[i % len(key)]) & 0xFF
          S[i], S[j] = S[j], S[i]
      
    • PRGA(伪随机生成算法):生成密钥流。
      i = j = 0
      for each byte output:
          i = (i + 1) & 0xFF
          j = (j + S[i]) & 0xFF
          S[i], S[j] = S[j], S[i]
          t = (S[i] + S[j]) & 0xFF
          keystream_byte = S[t]
      
    • 加密/解密cipher_byte = plain_byte ^ keystream_byte
  4. 魔改点分析:题目中的魔改在于,最后不是用异或,而是用加法(cipher_byte = (plain_byte + keystream_byte) & 0xFF)。因此解密时需对应使用减法(或加法的逆运算)。
  5. 编写解密脚本
    key = b"happynewyear"
    cipher = bytes([0x19,0x9D,0xDF,0x8D,0xED,0x42,0xAB,0x52,0x7E,0x25,0xC8,0xD2,0x48,0x56,0xEA,0xAB,0x60,0x94,0xCF,0x7E,0x66,0x4D,0x1C,0xE2,0x62,0x21,0xC7,0xAE,0xFD,0x39])
    
    # KSA (保持不变)
    S = list(range(256))
    j = 0
    for i in range(256):
        j = (j + S[i] + key[i % len(key)]) & 0xFF
        S[i], S[j] = S[j], S[i]
    
    # PRGA + 魔改解密 (plain = cipher - keystream)
    i = j = 0
    plain = bytearray()
    for c in cipher:
        i = (i + 1) & 0xFF
        j = (j + S[i]) & 0xFF
        S[i], S[j] = S[j], S[i]
        k = S[(S[i] + S[j]) & 0xFF]
        plain.append((c - k) & 0xFF) # 注意:此处是减法
    print(plain.decode())
    
    Flag:运行脚本后输出。

知识点:UPX壳与魔改修复、RC4算法原理与识别、密码算法魔改分析。


第四题:WebAssembly

考察点:WebAssembly(Wasm)模块逆向、自定义加密算法(Feistel网络、xorshift64* PRNG)、哈希算法(FNV-1a)。

题目架构
这是一个前端登录验证系统,包含HTML、JS和核心的Wasm模块。

  • index.html:用户界面,引入crypto-js.js(用于计算展示用的SHA1 ticket)和release.js(Wasm加载桥接)。
  • release.wasm:核心校验逻辑,导出authenticate函数。
  • 流程:用户输入userpass -> JS调用Wasm的authenticate -> Wasm内部校验 -> 返回结果和消息(包含Flag)。

Wasm内部校验逻辑详解
校验分为三个主要阶段,必须全部通过才能获取Flag。

阶段一:用户名验证(FNV-1a哈希)

  • 使用32位FNV-1a哈希算法计算用户名的哈希值。
  • 必须等于固定值0x477DDD25
  • 算法实现
    uint32_t fnv1a32(const char *s) {
        uint32_t h = 2166136261u; // 0x811C9DC5
        while (*s) {
            h ^= (uint8_t)(*s);
            h *= 16777619u; // 0x01000193
            s++;
        }
        return h;
    }
    
  • 解题:需要暴力破解或逆向找到一个字符串,使其FNV-1a哈希等于0x477DDD25

阶段二:密码验证(Feistel网络变换)

  1. 密钥派生:使用用户名(上一步验证通过的)派生8个64位的轮密钥(keys[0..7])。
    • 初始化一个64位状态s = 0xC3A5C85C97CB3127
    • 遍历用户名每个字节,用特定公式更新s
    • 循环8轮,每轮用s经过变换并乘以常数得到keys[r]
  2. Feistel加密:将用户输入的16字节密码视为一个128位的数据块,分为左右各8字节(L0, R0)。
    • 进行8轮Feistel变换:L_{i+1} = R_i, R_{i+1} = L_i ^ round_f(R_i, keys[i])
    • round_f是一个自定义的轮函数,包含加、循环移位、异或、乘等操作。
  3. 结果比对:经过8轮变换后,输出的16字节结果(mixed)必须等于程序中硬编码的16字节TARGET常量
  • 解题
    • 方法A(正向):已知正确的user,可以计算出keys。目标是找到一个16字节的pass,使得经过Feistel加密后等于TARGET。这需要逆向Feistel网络(实现feistel_inv_from_target函数)或暴力破解。
    • 方法B(直接获取Flag):如果目的是直接拿到Flag而不破解密码,可以绕过此步骤,但需要理解后续的Flag生成逻辑。

阶段三:Flag生成(流密码解密)
仅当阶段二验证通过(即mixed == TARGET)时执行。

  1. 生成种子(Seed)seed = mixed[0..7](小端加载为64位整数) ^ keys[0] ^ 0xA5A5A5A5A5A5A5A5
  2. 生成密钥流:以seed为初始状态,使用xorshift64* 伪随机数生成器生成任意长度的密钥流。
    • 迭代公式:x ^= (x >> 12); x ^= (x << 25); x ^= (x >> 27); x *= 2685821657736338717;
    • 每次迭代产生一个64位随机数,按小端序拆分为8字节密钥流。
  3. 解密Flag:将生成的39字节密钥流与程序中硬编码的39字节FLAG_CIPHERTEXT进行逐字节异或,得到明文的Flag。
  • 解题(直接法)
    1. 破解出正确的user(满足哈希条件)。
    2. 由于TARGET是已知常量,且keys可由user算出,因此可以直接计算出正确的seedseed = TARGET[0..7] ^ keys[0] ^ 0xA5A5A5A5A5A5A5A5
    3. 使用此seed运行xorshift64*生成39字节密钥流。
    4. 用密钥流异或FLAG_CIPHERTEXT得到Flag。

关键代码与Exp思路
文档中提供了大量的伪代码和结构定义,是编写解题脚本(Exp)的基础。例如,需要实现fnv1a32derive_keys_from_userrotl64round_ffeistel_invxorshift64star等函数。

整体依赖关系
user -> keys & 哈希校验
keys + pass -> mixed (需==TARGET)
mixed + keys[0] -> seed
seed -> keystream
keystream ^ FLAG_CIPHERTEXT -> flag

知识点:WebAssembly逆向工具使用(如wasm2c、wasm-decompile)、FNV哈希算法、Feistel密码结构、xorshift系列伪随机数生成器、Wasm与JavaScript的交互(导入导出表、内存操作)。


总结与工具链

  • 工具汇总:PyInstaller提取器、pycdc、UPX、Wasm逆向套件(wasm2wat、wasm-decompile)、IDA Pro(用于分析C++ VM)、调试器。
  • 核心技能:静态分析、动态调试、算法识别与还原、密码学基础、对不同文件格式(PE、Wasm)和语言(Python、C++)的逆向能力。
  • 学习路径:建议从简单的打包程序逆向开始,逐步过渡到虚拟机、壳保护、密码算法和Wasm等更复杂的领域。

通过系统性地练习以上题目,可以全面锻炼逆向工程中的各项核心能力。

相似文章
相似文章
 全屏